Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 643a8077dd |
@@ -1,25 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wii/Dolphin overrides for mbedtls 4.x.
|
|
||||||
* Used as both MBEDTLS_USER_CONFIG_FILE and TF_PSA_CRYPTO_USER_CONFIG_FILE,
|
|
||||||
* so intentionally has no include guard (operations are idempotent).
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* Disable the mbedtls TCP/socket layer — Wii has no Unix/Windows sockets.
|
|
||||||
* We provide our own socket implementation via networksocket.c. */
|
|
||||||
#undef MBEDTLS_NET_C
|
|
||||||
|
|
||||||
/* Disable built-in Unix/Windows entropy; use driver-provided entropy instead.
|
|
||||||
* We implement mbedtls_platform_get_entropy() in networktls.c. */
|
|
||||||
#undef MBEDTLS_PSA_BUILTIN_GET_ENTROPY
|
|
||||||
#define MBEDTLS_PSA_DRIVER_GET_ENTROPY
|
|
||||||
|
|
||||||
/* Disable the built-in ms_time; we implement mbedtls_ms_time() via OGC
|
|
||||||
* timer (gettime / ticks_to_millisecs) in networktls.c. */
|
|
||||||
#define MBEDTLS_PLATFORM_MS_TIME_ALT
|
|
||||||
@@ -1,201 +0,0 @@
|
|||||||
# Findmbedtls.cmake
|
|
||||||
#
|
|
||||||
# Usage:
|
|
||||||
# find_package(mbedtls REQUIRED)
|
|
||||||
#
|
|
||||||
# Optional cache variables the parent project may set before calling:
|
|
||||||
# MBEDTLS_FETCHCONTENT_VERSION e.g. "v3.6.4" or "mbedtls-4.1.0"
|
|
||||||
# MBEDTLS_FETCHCONTENT_GIT_REPOSITORY
|
|
||||||
# MBEDTLS_FETCHCONTENT_GIT_TAG
|
|
||||||
# MBEDTLS_FETCHCONTENT_BASE_DIR
|
|
||||||
# MBEDTLS_BUILD_SHARED ON/OFF
|
|
||||||
#
|
|
||||||
# Provided variables:
|
|
||||||
# mbedtls_FOUND
|
|
||||||
# MBEDTLS_FOUND
|
|
||||||
# MBEDTLS_INCLUDE_DIRS
|
|
||||||
# MBEDTLS_LIBRARIES
|
|
||||||
#
|
|
||||||
# Provided imported targets:
|
|
||||||
# MbedTLS::mbedtls
|
|
||||||
# MbedTLS::mbedx509
|
|
||||||
# MbedTLS::mbedcrypto
|
|
||||||
|
|
||||||
include_guard(GLOBAL)
|
|
||||||
|
|
||||||
include(FetchContent)
|
|
||||||
include(FindPackageHandleStandardArgs)
|
|
||||||
|
|
||||||
set(_MBEDTLS_DEFAULT_REPOSITORY "https://github.com/Mbed-TLS/mbedtls.git")
|
|
||||||
set(_MBEDTLS_DEFAULT_TAG "v4.1.0")
|
|
||||||
|
|
||||||
set(MBEDTLS_FETCHCONTENT_GIT_REPOSITORY
|
|
||||||
"${_MBEDTLS_DEFAULT_REPOSITORY}"
|
|
||||||
CACHE STRING "Git repository for fetching Mbed TLS")
|
|
||||||
|
|
||||||
if(DEFINED MBEDTLS_FETCHCONTENT_VERSION AND NOT DEFINED MBEDTLS_FETCHCONTENT_GIT_TAG)
|
|
||||||
set(MBEDTLS_FETCHCONTENT_GIT_TAG
|
|
||||||
"${MBEDTLS_FETCHCONTENT_VERSION}"
|
|
||||||
CACHE STRING "Git tag/branch/commit for fetching Mbed TLS")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
set(MBEDTLS_FETCHCONTENT_GIT_TAG
|
|
||||||
"${MBEDTLS_FETCHCONTENT_GIT_TAG}"
|
|
||||||
CACHE STRING "Git tag/branch/commit for fetching Mbed TLS")
|
|
||||||
|
|
||||||
if(NOT MBEDTLS_FETCHCONTENT_GIT_TAG)
|
|
||||||
set(MBEDTLS_FETCHCONTENT_GIT_TAG "${_MBEDTLS_DEFAULT_TAG}" CACHE STRING "" FORCE)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
option(MBEDTLS_BUILD_SHARED "Build Mbed TLS shared libraries" OFF)
|
|
||||||
|
|
||||||
# 1) Prefer an installed package config if available.
|
|
||||||
find_package(MbedTLS CONFIG QUIET)
|
|
||||||
|
|
||||||
if(TARGET MbedTLS::mbedtls AND TARGET MbedTLS::mbedx509 AND TARGET MbedTLS::mbedcrypto)
|
|
||||||
set(mbedtls_FOUND TRUE)
|
|
||||||
set(MBEDTLS_FOUND TRUE)
|
|
||||||
set(MBEDTLS_LIBRARIES
|
|
||||||
MbedTLS::mbedtls
|
|
||||||
MbedTLS::mbedx509
|
|
||||||
MbedTLS::mbedcrypto)
|
|
||||||
set(MBEDTLS_INCLUDE_DIRS "")
|
|
||||||
return()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# 2) If upstream exported plain targets instead of namespaced ones, alias them.
|
|
||||||
if(TARGET mbedtls AND TARGET mbedx509 AND TARGET mbedcrypto)
|
|
||||||
if(NOT TARGET MbedTLS::mbedtls)
|
|
||||||
add_library(MbedTLS::mbedtls INTERFACE IMPORTED)
|
|
||||||
target_link_libraries(MbedTLS::mbedtls INTERFACE mbedtls)
|
|
||||||
endif()
|
|
||||||
if(NOT TARGET MbedTLS::mbedx509)
|
|
||||||
add_library(MbedTLS::mbedx509 INTERFACE IMPORTED)
|
|
||||||
target_link_libraries(MbedTLS::mbedx509 INTERFACE mbedx509)
|
|
||||||
endif()
|
|
||||||
if(NOT TARGET MbedTLS::mbedcrypto)
|
|
||||||
add_library(MbedTLS::mbedcrypto INTERFACE IMPORTED)
|
|
||||||
target_link_libraries(MbedTLS::mbedcrypto INTERFACE mbedcrypto)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
set(mbedtls_FOUND TRUE)
|
|
||||||
set(MBEDTLS_FOUND TRUE)
|
|
||||||
set(MBEDTLS_LIBRARIES
|
|
||||||
MbedTLS::mbedtls
|
|
||||||
MbedTLS::mbedx509
|
|
||||||
MbedTLS::mbedcrypto)
|
|
||||||
set(MBEDTLS_INCLUDE_DIRS "")
|
|
||||||
return()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# 3) Fetch and build Mbed TLS.
|
|
||||||
# Upstream options:
|
|
||||||
# - USE_STATIC_MBEDTLS_LIBRARY / USE_SHARED_MBEDTLS_LIBRARY
|
|
||||||
# - ENABLE_PROGRAMS / ENABLE_TESTING
|
|
||||||
# - MBEDTLS_AS_SUBPROJECT / DISABLE_PACKAGE_CONFIG_AND_INSTALL
|
|
||||||
# - MBEDTLS_TARGET_PREFIX
|
|
||||||
#
|
|
||||||
# These are supported by the upstream CMake build. :contentReference[oaicite:1]{index=1}
|
|
||||||
|
|
||||||
set(FETCHCONTENT_QUIET FALSE)
|
|
||||||
|
|
||||||
if(MBEDTLS_FETCHCONTENT_BASE_DIR)
|
|
||||||
set(FETCHCONTENT_BASE_DIR "${MBEDTLS_FETCHCONTENT_BASE_DIR}")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# Avoid polluting the parent build and skip extras we usually do not want.
|
|
||||||
set(ENABLE_PROGRAMS OFF CACHE BOOL "" FORCE)
|
|
||||||
set(ENABLE_TESTING OFF CACHE BOOL "" FORCE)
|
|
||||||
set(DISABLE_PACKAGE_CONFIG_AND_INSTALL ON CACHE BOOL "" FORCE)
|
|
||||||
set(MBEDTLS_AS_SUBPROJECT ON CACHE BOOL "" FORCE)
|
|
||||||
set(MBEDTLS_TARGET_PREFIX "" CACHE STRING "" FORCE)
|
|
||||||
|
|
||||||
if(MBEDTLS_BUILD_SHARED)
|
|
||||||
set(USE_SHARED_MBEDTLS_LIBRARY ON CACHE BOOL "" FORCE)
|
|
||||||
set(USE_STATIC_MBEDTLS_LIBRARY OFF CACHE BOOL "" FORCE)
|
|
||||||
else()
|
|
||||||
set(USE_SHARED_MBEDTLS_LIBRARY OFF CACHE BOOL "" FORCE)
|
|
||||||
set(USE_STATIC_MBEDTLS_LIBRARY ON CACHE BOOL "" FORCE)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
FetchContent_Declare(
|
|
||||||
mbedtls_fc
|
|
||||||
GIT_REPOSITORY "${MBEDTLS_FETCHCONTENT_GIT_REPOSITORY}"
|
|
||||||
GIT_TAG "${MBEDTLS_FETCHCONTENT_GIT_TAG}"
|
|
||||||
GIT_SHALLOW TRUE
|
|
||||||
)
|
|
||||||
|
|
||||||
FetchContent_MakeAvailable(mbedtls_fc)
|
|
||||||
|
|
||||||
# 4) Normalize targets across upstream versions.
|
|
||||||
#
|
|
||||||
# Mbed TLS 3.x:
|
|
||||||
# mbedtls, mbedx509, mbedcrypto
|
|
||||||
#
|
|
||||||
# Mbed TLS 4.x:
|
|
||||||
# mbedtls, mbedx509, tfpsacrypto
|
|
||||||
#
|
|
||||||
# Map everything to stable namespaced targets for the consumer. :contentReference[oaicite:2]{index=2}
|
|
||||||
|
|
||||||
set(_mbedtls_tls_target "")
|
|
||||||
set(_mbedtls_x509_target "")
|
|
||||||
set(_mbedtls_crypto_target "")
|
|
||||||
|
|
||||||
if(TARGET mbedtls)
|
|
||||||
set(_mbedtls_tls_target mbedtls)
|
|
||||||
elseif(TARGET MbedTLS::mbedtls)
|
|
||||||
set(_mbedtls_tls_target MbedTLS::mbedtls)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(TARGET mbedx509)
|
|
||||||
set(_mbedtls_x509_target mbedx509)
|
|
||||||
elseif(TARGET MbedTLS::mbedx509)
|
|
||||||
set(_mbedtls_x509_target MbedTLS::mbedx509)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(TARGET mbedcrypto)
|
|
||||||
set(_mbedtls_crypto_target mbedcrypto)
|
|
||||||
elseif(TARGET tfpsacrypto)
|
|
||||||
set(_mbedtls_crypto_target tfpsacrypto)
|
|
||||||
elseif(TARGET MbedTLS::mbedcrypto)
|
|
||||||
set(_mbedtls_crypto_target MbedTLS::mbedcrypto)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(_mbedtls_tls_target AND NOT TARGET MbedTLS::mbedtls)
|
|
||||||
add_library(MbedTLS::mbedtls INTERFACE IMPORTED)
|
|
||||||
target_link_libraries(MbedTLS::mbedtls INTERFACE "${_mbedtls_tls_target}")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(_mbedtls_x509_target AND NOT TARGET MbedTLS::mbedx509)
|
|
||||||
add_library(MbedTLS::mbedx509 INTERFACE IMPORTED)
|
|
||||||
target_link_libraries(MbedTLS::mbedx509 INTERFACE "${_mbedtls_x509_target}")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(_mbedtls_crypto_target AND NOT TARGET MbedTLS::mbedcrypto)
|
|
||||||
add_library(MbedTLS::mbedcrypto INTERFACE IMPORTED)
|
|
||||||
target_link_libraries(MbedTLS::mbedcrypto INTERFACE "${_mbedtls_crypto_target}")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
find_package_handle_standard_args(
|
|
||||||
mbedtls
|
|
||||||
REQUIRED_VARS
|
|
||||||
_mbedtls_tls_target
|
|
||||||
_mbedtls_x509_target
|
|
||||||
_mbedtls_crypto_target
|
|
||||||
)
|
|
||||||
|
|
||||||
if(mbedtls_FOUND)
|
|
||||||
set(MBEDTLS_FOUND TRUE)
|
|
||||||
set(MBEDTLS_LIBRARIES
|
|
||||||
MbedTLS::mbedtls
|
|
||||||
MbedTLS::mbedx509
|
|
||||||
MbedTLS::mbedcrypto)
|
|
||||||
|
|
||||||
# Best-effort include directory discovery for legacy consumers.
|
|
||||||
get_target_property(_mbedtls_inc "${_mbedtls_tls_target}" INTERFACE_INCLUDE_DIRECTORIES)
|
|
||||||
if(_mbedtls_inc)
|
|
||||||
set(MBEDTLS_INCLUDE_DIRS "${_mbedtls_inc}")
|
|
||||||
else()
|
|
||||||
set(MBEDTLS_INCLUDE_DIRS "")
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
@@ -1,23 +1,9 @@
|
|||||||
# mbedtls/tf-psa-crypto user config overrides for Wii/Dolphin.
|
|
||||||
# Both variables point to the same file; it is included after each library's
|
|
||||||
# default config header, so #undef/#define are applied on top of the defaults.
|
|
||||||
# Must be set before find_package(mbedtls) so FetchContent picks them up.
|
|
||||||
set(TF_PSA_CRYPTO_USER_CONFIG_FILE
|
|
||||||
"${CMAKE_SOURCE_DIR}/cmake/mbedtls_dolphin_config.h"
|
|
||||||
CACHE FILEPATH "tf-psa-crypto user config for Wii/Dolphin" FORCE)
|
|
||||||
set(MBEDTLS_USER_CONFIG_FILE
|
|
||||||
"${CMAKE_SOURCE_DIR}/cmake/mbedtls_dolphin_config.h"
|
|
||||||
CACHE FILEPATH "mbedtls user config for Wii/Dolphin" FORCE)
|
|
||||||
|
|
||||||
# Target definitions
|
# Target definitions
|
||||||
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
|
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
|
||||||
DUSK_DOLPHIN
|
DUSK_DOLPHIN
|
||||||
DUSK_INPUT_GAMEPAD
|
DUSK_INPUT_GAMEPAD
|
||||||
DUSK_DISPLAY_WIDTH=640
|
DUSK_DISPLAY_WIDTH=640
|
||||||
DUSK_DISPLAY_HEIGHT=480
|
DUSK_DISPLAY_HEIGHT=480
|
||||||
MBEDTLS_PSA_DRIVER_GET_ENTROPY
|
|
||||||
MBEDTLS_PLATFORM_MS_TIME_ALT
|
|
||||||
THREAD_PTHREAD=1
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Custom compiler flags
|
# Custom compiler flags
|
||||||
|
|||||||
@@ -26,10 +26,6 @@ target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
|
|||||||
# CURL::libcurl
|
# CURL::libcurl
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
|
|
||||||
${MBEDTLS_INCLUDE_DIR}
|
|
||||||
)
|
|
||||||
|
|
||||||
# Define platform-specific macros.
|
# Define platform-specific macros.
|
||||||
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
|
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
|
||||||
DUSK_SDL2
|
DUSK_SDL2
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
FROM devkitpro/devkitppc
|
FROM devkitpro/devkitppc
|
||||||
WORKDIR /workdir
|
WORKDIR /workdir
|
||||||
RUN apt update && \
|
RUN apt update && \
|
||||||
apt install -y python3 python3-pip python3-polib python3-pil python3-dotenv python3-pyqt5 python3-opengl python3-jsonschema python3-jinja2 python3-jsonschema && \
|
apt install -y python3 python3-pip python3-polib python3-pil python3-dotenv python3-pyqt5 python3-opengl && \
|
||||||
dkp-pacman -S --needed --noconfirm gamecube-sdl2 ppc-liblzma ppc-libzip
|
dkp-pacman -S --needed --noconfirm gamecube-sdl2 ppc-liblzma ppc-libzip
|
||||||
VOLUME ["/workdir"]
|
VOLUME ["/workdir"]
|
||||||
@@ -46,16 +46,6 @@ if(NOT Lua_FOUND)
|
|||||||
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC Lua::Lua)
|
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC Lua::Lua)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(NOT mbedtls_FOUND)
|
|
||||||
find_package(mbedtls REQUIRED)
|
|
||||||
if(mbedtls_FOUND)
|
|
||||||
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC ${MBEDTLS_LIBRARIES})
|
|
||||||
target_include_directories(${DUSK_LIBRARY_TARGET_NAME} PUBLIC ${MBEDTLS_INCLUDE_DIRS})
|
|
||||||
else()
|
|
||||||
message(FATAL_ERROR "mbedtls not found. Please ensure mbedtls is correctly installed.")
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# Includes
|
# Includes
|
||||||
target_include_directories(${DUSK_LIBRARY_TARGET_NAME}
|
target_include_directories(${DUSK_LIBRARY_TARGET_NAME}
|
||||||
PUBLIC
|
PUBLIC
|
||||||
|
|||||||
+22
-37
@@ -22,36 +22,37 @@
|
|||||||
#include "physics/physicsmanager.h"
|
#include "physics/physicsmanager.h"
|
||||||
#include "network/network.h"
|
#include "network/network.h"
|
||||||
#include "network/networkinfo.h"
|
#include "network/networkinfo.h"
|
||||||
|
#include "network/networksocketclient.h"
|
||||||
#include "system/system.h"
|
#include "system/system.h"
|
||||||
|
|
||||||
#include "network/httpclient.h"
|
|
||||||
|
|
||||||
#include "display/mesh/cube.h"
|
#include "display/mesh/cube.h"
|
||||||
#include "display/mesh/plane.h"
|
#include "display/mesh/plane.h"
|
||||||
|
|
||||||
engine_t ENGINE;
|
engine_t ENGINE;
|
||||||
entityid_t phBoxEnt;
|
entityid_t phBoxEnt;
|
||||||
componentid_t phBoxPhys;
|
componentid_t phBoxPhys;
|
||||||
|
networksocketclient_t sockClient;
|
||||||
|
|
||||||
float_t onlineSwapTime = FLT_MAX;
|
float_t onlineSwapTime = FLT_MAX;
|
||||||
|
|
||||||
void goOnline();
|
void goOnline();
|
||||||
void goOffline();
|
void goOffline();
|
||||||
|
|
||||||
void onGETComplete(httpclient_t *client, void *user) {
|
void onSocketConnected(void *user) {
|
||||||
sceneLog("GET request complete!\n");
|
sceneLog("Socket connected.\n");
|
||||||
sceneLog("Response status: %u\n", client->statusCode);
|
|
||||||
for(size_t i = 0; i < client->responseHeaderCount; i++) {
|
|
||||||
sceneLog("Header: %s: %s\n", client->responseHeaders[i].name, client->responseHeaders[i].value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void onGETError(httpclient_t *client, errorret_t err, void *user) {
|
void onSocketError(errorret_t error, void *user) {
|
||||||
errorCatch(errorPrint(err));
|
sceneLog("Socket error: %s\n", error.state->message);
|
||||||
|
errorCatch(errorPrint(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
void onSocketDisconnected(void *user) {
|
||||||
|
sceneLog("Socket disconnected.\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
void onNetworkConnected(void *user) {
|
void onNetworkConnected(void *user) {
|
||||||
onlineSwapTime = TIME.time + 3.0f;
|
onlineSwapTime = TIME.time + 1.5f;
|
||||||
|
|
||||||
networkinfo_t info = networkGetInfo();
|
networkinfo_t info = networkGetInfo();
|
||||||
if(info.type == NETWORK_TYPE_IPV4) {
|
if(info.type == NETWORK_TYPE_IPV4) {
|
||||||
@@ -71,33 +72,17 @@ void onNetworkConnected(void *user) {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
char_t *domain = "https://google.com";
|
sceneLog("Network connected, opening socket: %.2f1.\n", onlineSwapTime);
|
||||||
sceneLog("Online, sending GET to %s...\n", domain);
|
networkSocketClientInit(
|
||||||
httpclient_t client;
|
&sockClient,
|
||||||
errorret_t ret;
|
"google.com",
|
||||||
ret = httpclientInit(&client);
|
443,
|
||||||
if(ret.code != ERROR_OK) {
|
|
||||||
errorCatch(errorPrint(ret));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
httpclientRequest(
|
|
||||||
&client,
|
|
||||||
"GET",
|
|
||||||
domain,
|
|
||||||
false,
|
|
||||||
NULL,
|
NULL,
|
||||||
NULL,
|
onSocketConnected,
|
||||||
onGETComplete,
|
onSocketError,
|
||||||
onGETError,
|
onSocketDisconnected
|
||||||
NULL
|
|
||||||
);
|
);
|
||||||
|
onlineSwapTime = FLT_MAX;
|
||||||
ret = httpclientDispose(&client);
|
|
||||||
if(ret.code != ERROR_OK) {
|
|
||||||
errorCatch(errorPrint(ret));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// sceneLog("Network connected, I will disconnect at: %.2f1.\n", onlineSwapTime);
|
// sceneLog("Network connected, I will disconnect at: %.2f1.\n", onlineSwapTime);
|
||||||
}
|
}
|
||||||
@@ -155,8 +140,8 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
|
|||||||
// errorChain(networkInit());
|
// errorChain(networkInit());
|
||||||
errorChain(gameInit());
|
errorChain(gameInit());
|
||||||
|
|
||||||
sceneLog("Init done, going to queue online in 1 seconds...\n");
|
|
||||||
onlineSwapTime = TIME.time + 1.0f;
|
onlineSwapTime = TIME.time + 1.0f;
|
||||||
|
sceneLog("Init done, going to queue online at %.2f1.\n", onlineSwapTime);
|
||||||
|
|
||||||
// Camera
|
// Camera
|
||||||
entityid_t cam = entityManagerAdd();
|
entityid_t cam = entityManagerAdd();
|
||||||
|
|||||||
@@ -7,7 +7,5 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
|||||||
PUBLIC
|
PUBLIC
|
||||||
network.c
|
network.c
|
||||||
networkinfo.c
|
networkinfo.c
|
||||||
networksocket.c
|
networksocketclient.c
|
||||||
networktls.c
|
|
||||||
httpclient.c
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,545 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "httpclient.h"
|
|
||||||
#include "util/memory.h"
|
|
||||||
#include "assert/assert.h"
|
|
||||||
|
|
||||||
/* ---- helpers ----------------------------------------------------------- */
|
|
||||||
|
|
||||||
static bool_t httpclientStrEqualCI(const char_t *a, const char_t *b) {
|
|
||||||
while(*a && *b) {
|
|
||||||
if(tolower((unsigned char)*a) != tolower((unsigned char)*b)) return false;
|
|
||||||
a++; b++;
|
|
||||||
}
|
|
||||||
return *a == '\0' && *b == '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads exactly len bytes from the TLS stream, retrying on timeout.
|
|
||||||
* Returns an error on I/O failure or unexpected connection close.
|
|
||||||
*/
|
|
||||||
static errorret_t httpclientReadExact(
|
|
||||||
networktls_t *tls,
|
|
||||||
errorstate_t *es,
|
|
||||||
uint8_t *buf,
|
|
||||||
size_t len
|
|
||||||
) {
|
|
||||||
size_t total = 0;
|
|
||||||
while(total < len) {
|
|
||||||
size_t got = 0;
|
|
||||||
errorret_t err = networktlsRead(tls, buf + total, len - total, &got);
|
|
||||||
if(err.code != ERROR_OK) return err;
|
|
||||||
if(got == NETWORKSOCKET_RECV_CLOSED) {
|
|
||||||
errorThrowState(es, "Connection closed before all expected bytes arrived");
|
|
||||||
}
|
|
||||||
total += got;
|
|
||||||
}
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads bytes one at a time until \r\n is found. Writes the line (without
|
|
||||||
* the terminator) into buf, null-terminates, and sets *outLen. Returns an
|
|
||||||
* error on I/O failure or if the line exceeds maxLen-1 characters.
|
|
||||||
*/
|
|
||||||
static errorret_t httpclientReadLine(
|
|
||||||
networktls_t *tls,
|
|
||||||
errorstate_t *es,
|
|
||||||
char_t *buf,
|
|
||||||
size_t maxLen,
|
|
||||||
size_t *outLen
|
|
||||||
) {
|
|
||||||
size_t len = 0;
|
|
||||||
uint8_t ch;
|
|
||||||
size_t got;
|
|
||||||
errorret_t err;
|
|
||||||
|
|
||||||
while(len < maxLen - 1) {
|
|
||||||
do {
|
|
||||||
got = 0;
|
|
||||||
err = networktlsRead(tls, &ch, 1, &got);
|
|
||||||
if(err.code != ERROR_OK) return err;
|
|
||||||
if(got == NETWORKSOCKET_RECV_CLOSED) {
|
|
||||||
errorThrowState(es, "Connection closed during header read");
|
|
||||||
}
|
|
||||||
} while(got == 0);
|
|
||||||
|
|
||||||
if(ch == '\r') {
|
|
||||||
/* consume the \n */
|
|
||||||
do {
|
|
||||||
got = 0;
|
|
||||||
err = networktlsRead(tls, &ch, 1, &got);
|
|
||||||
if(err.code != ERROR_OK) return err;
|
|
||||||
if(got == NETWORKSOCKET_RECV_CLOSED) break;
|
|
||||||
} while(got == 0);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
buf[len++] = (char_t)ch;
|
|
||||||
}
|
|
||||||
|
|
||||||
buf[len] = '\0';
|
|
||||||
*outLen = len;
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---- response parsing -------------------------------------------------- */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses status code from "HTTP/x.y NNN ..." — returns 0 on failure.
|
|
||||||
*/
|
|
||||||
static uint16_t httpclientParseStatus(const char_t *line) {
|
|
||||||
const char_t *p = strchr(line, ' ');
|
|
||||||
if(!p) return 0;
|
|
||||||
return (uint16_t)atoi(p + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses "Name: Value" into the client's responseHeaders array. Trims
|
|
||||||
* leading whitespace from the value.
|
|
||||||
*/
|
|
||||||
static void httpclientParseHeader(
|
|
||||||
httpclient_t *client,
|
|
||||||
const char_t *line
|
|
||||||
) {
|
|
||||||
const char_t *colon;
|
|
||||||
const char_t *valStart;
|
|
||||||
size_t nameLen;
|
|
||||||
size_t valLen;
|
|
||||||
httpheader_t *h;
|
|
||||||
|
|
||||||
if(client->responseHeaderCount >= HTTPCLIENT_HEADER_MAX) return;
|
|
||||||
|
|
||||||
colon = strchr(line, ':');
|
|
||||||
if(!colon) return;
|
|
||||||
|
|
||||||
nameLen = (size_t)(colon - line);
|
|
||||||
if(nameLen == 0 || nameLen >= HTTPCLIENT_HEADER_NAME_MAX) return;
|
|
||||||
|
|
||||||
valStart = colon + 1;
|
|
||||||
while(*valStart == ' ' || *valStart == '\t') valStart++;
|
|
||||||
valLen = strlen(valStart);
|
|
||||||
if(valLen >= HTTPCLIENT_HEADER_VALUE_MAX) valLen = HTTPCLIENT_HEADER_VALUE_MAX - 1;
|
|
||||||
|
|
||||||
h = &client->responseHeaders[client->responseHeaderCount++];
|
|
||||||
memoryCopy(h->name, line, nameLen);
|
|
||||||
h->name[nameLen] = '\0';
|
|
||||||
memoryCopy(h->value, valStart, valLen);
|
|
||||||
h->value[valLen] = '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
static const char_t *httpclientGetResponseHeader(
|
|
||||||
const httpclient_t *client,
|
|
||||||
const char_t *name
|
|
||||||
) {
|
|
||||||
size_t i;
|
|
||||||
for(i = 0; i < client->responseHeaderCount; i++) {
|
|
||||||
if(httpclientStrEqualCI(client->responseHeaders[i].name, name)) {
|
|
||||||
return client->responseHeaders[i].value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---- body delivery ----------------------------------------------------- */
|
|
||||||
|
|
||||||
static errorret_t httpclientReadBodyFixed(
|
|
||||||
httpclient_t *client,
|
|
||||||
size_t contentLength
|
|
||||||
) {
|
|
||||||
uint8_t buf[HTTPCLIENT_BODY_BUF_SIZE];
|
|
||||||
size_t remaining = contentLength;
|
|
||||||
size_t got;
|
|
||||||
errorret_t err;
|
|
||||||
|
|
||||||
while(remaining > 0) {
|
|
||||||
size_t want = remaining < sizeof(buf) ? remaining : sizeof(buf);
|
|
||||||
err = httpclientReadExact(&client->tls, &client->errorState, buf, want);
|
|
||||||
if(err.code != ERROR_OK) return err;
|
|
||||||
if(client->onData) client->onData(client, buf, want, client->user);
|
|
||||||
remaining -= want;
|
|
||||||
got = want;
|
|
||||||
(void)got;
|
|
||||||
}
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
static errorret_t httpclientReadBodyChunked(httpclient_t *client) {
|
|
||||||
char_t sizeLine[32];
|
|
||||||
size_t lineLen;
|
|
||||||
errorret_t err;
|
|
||||||
uint8_t buf[HTTPCLIENT_BODY_BUF_SIZE];
|
|
||||||
|
|
||||||
for(;;) {
|
|
||||||
err = httpclientReadLine(
|
|
||||||
&client->tls, &client->errorState, sizeLine, sizeof(sizeLine), &lineLen
|
|
||||||
);
|
|
||||||
if(err.code != ERROR_OK) return err;
|
|
||||||
|
|
||||||
/* strtol parses hex chunk size; stop on terminating "0" chunk */
|
|
||||||
size_t chunkSize = (size_t)strtol(sizeLine, NULL, 16);
|
|
||||||
if(chunkSize == 0) {
|
|
||||||
/* consume trailing CRLF after the zero chunk */
|
|
||||||
httpclientReadLine(
|
|
||||||
&client->tls, &client->errorState, sizeLine, sizeof(sizeLine), &lineLen
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t remaining = chunkSize;
|
|
||||||
while(remaining > 0) {
|
|
||||||
size_t want = remaining < sizeof(buf) ? remaining : sizeof(buf);
|
|
||||||
err = httpclientReadExact(
|
|
||||||
&client->tls, &client->errorState, buf, want
|
|
||||||
);
|
|
||||||
if(err.code != ERROR_OK) return err;
|
|
||||||
if(client->onData) client->onData(client, buf, want, client->user);
|
|
||||||
remaining -= want;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* consume the CRLF after chunk data */
|
|
||||||
err = httpclientReadLine(
|
|
||||||
&client->tls, &client->errorState, sizeLine, sizeof(sizeLine), &lineLen
|
|
||||||
);
|
|
||||||
if(err.code != ERROR_OK) return err;
|
|
||||||
}
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
static errorret_t httpclientReadBodyUntilClose(httpclient_t *client) {
|
|
||||||
uint8_t buf[HTTPCLIENT_BODY_BUF_SIZE];
|
|
||||||
size_t got;
|
|
||||||
errorret_t err;
|
|
||||||
|
|
||||||
for(;;) {
|
|
||||||
err = networktlsRead(&client->tls, buf, sizeof(buf), &got);
|
|
||||||
if(err.code != ERROR_OK) return err;
|
|
||||||
if(got == NETWORKSOCKET_RECV_CLOSED) break;
|
|
||||||
if(got > 0 && client->onData) {
|
|
||||||
client->onData(client, buf, got, client->user);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---- TLS onConnect — runs the full HTTP exchange ----------------------- */
|
|
||||||
|
|
||||||
static void httpclientTlsOnConnect(networktls_t *tls, void *user) {
|
|
||||||
httpclient_t *client = (httpclient_t *)user;
|
|
||||||
char_t reqBuf[HTTPCLIENT_REQ_BUF_SIZE];
|
|
||||||
char_t lineBuf[HTTPCLIENT_HEADER_NAME_MAX + HTTPCLIENT_HEADER_VALUE_MAX + 4];
|
|
||||||
int reqLen;
|
|
||||||
size_t lineLen;
|
|
||||||
size_t i;
|
|
||||||
errorret_t err;
|
|
||||||
|
|
||||||
/* ---- build request headers ----------------------------------------- */
|
|
||||||
reqLen = snprintf(
|
|
||||||
reqBuf, sizeof(reqBuf),
|
|
||||||
"%s %s HTTP/1.1\r\n"
|
|
||||||
"Host: %s\r\n"
|
|
||||||
"Connection: close\r\n"
|
|
||||||
"User-Agent: DuskEngine/1.0\r\n",
|
|
||||||
client->method,
|
|
||||||
client->url.path[0] != '\0' ? client->url.path : "/",
|
|
||||||
client->url.host
|
|
||||||
);
|
|
||||||
|
|
||||||
for(i = 0; i < client->requestHeaderCount; i++) {
|
|
||||||
int n = snprintf(
|
|
||||||
reqBuf + reqLen, sizeof(reqBuf) - (size_t)reqLen,
|
|
||||||
"%s: %s\r\n",
|
|
||||||
client->requestHeaders[i].name,
|
|
||||||
client->requestHeaders[i].value
|
|
||||||
);
|
|
||||||
if(n > 0) reqLen += n;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(client->requestBodyLen > 0) {
|
|
||||||
int n = snprintf(
|
|
||||||
reqBuf + reqLen, sizeof(reqBuf) - (size_t)reqLen,
|
|
||||||
"Content-Length: %zu\r\n",
|
|
||||||
client->requestBodyLen
|
|
||||||
);
|
|
||||||
if(n > 0) reqLen += n;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* blank line terminates headers */
|
|
||||||
if((size_t)reqLen + 2 < sizeof(reqBuf)) {
|
|
||||||
reqBuf[reqLen++] = '\r';
|
|
||||||
reqBuf[reqLen++] = '\n';
|
|
||||||
}
|
|
||||||
|
|
||||||
err = networktlsWrite(tls, (const uint8_t *)reqBuf, (size_t)reqLen);
|
|
||||||
if(err.code != ERROR_OK) {
|
|
||||||
if(client->onError) client->onError(client, err, client->user);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(client->requestBody != NULL && client->requestBodyLen > 0) {
|
|
||||||
err = networktlsWrite(tls, client->requestBody, client->requestBodyLen);
|
|
||||||
if(err.code != ERROR_OK) {
|
|
||||||
if(client->onError) client->onError(client, err, client->user);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---- read response status line ------------------------------------- */
|
|
||||||
err = httpclientReadLine(
|
|
||||||
tls, &client->errorState, lineBuf, sizeof(lineBuf), &lineLen
|
|
||||||
);
|
|
||||||
if(err.code != ERROR_OK) {
|
|
||||||
if(client->onError) client->onError(client, err, client->user);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
client->statusCode = httpclientParseStatus(lineBuf);
|
|
||||||
|
|
||||||
/* ---- read response headers until blank line ------------------------ */
|
|
||||||
client->responseHeaderCount = 0;
|
|
||||||
for(;;) {
|
|
||||||
err = httpclientReadLine(
|
|
||||||
tls, &client->errorState, lineBuf, sizeof(lineBuf), &lineLen
|
|
||||||
);
|
|
||||||
if(err.code != ERROR_OK) {
|
|
||||||
if(client->onError) client->onError(client, err, client->user);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(lineLen == 0) break; /* blank line = end of headers */
|
|
||||||
httpclientParseHeader(client, lineBuf);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(client->onHeaders) {
|
|
||||||
client->onHeaders(
|
|
||||||
client,
|
|
||||||
client->statusCode,
|
|
||||||
client->responseHeaders,
|
|
||||||
client->responseHeaderCount,
|
|
||||||
client->user
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---- read response body -------------------------------------------- */
|
|
||||||
|
|
||||||
/* 1xx, 204 No Content and 304 Not Modified carry no body */
|
|
||||||
bool_t noBody = (
|
|
||||||
client->statusCode < 200 ||
|
|
||||||
client->statusCode == 204 ||
|
|
||||||
client->statusCode == 304
|
|
||||||
);
|
|
||||||
|
|
||||||
if(!noBody) {
|
|
||||||
const char_t *transferEncoding =
|
|
||||||
httpclientGetResponseHeader(client, "Transfer-Encoding");
|
|
||||||
const char_t *contentLengthStr =
|
|
||||||
httpclientGetResponseHeader(client, "Content-Length");
|
|
||||||
|
|
||||||
if(transferEncoding != NULL &&
|
|
||||||
strstr(transferEncoding, "chunked") != NULL) {
|
|
||||||
err = httpclientReadBodyChunked(client);
|
|
||||||
} else if(contentLengthStr != NULL) {
|
|
||||||
size_t contentLength = (size_t)atoi(contentLengthStr);
|
|
||||||
err = httpclientReadBodyFixed(client, contentLength);
|
|
||||||
} else {
|
|
||||||
err = httpclientReadBodyUntilClose(client);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(err.code != ERROR_OK) {
|
|
||||||
if(client->onError) client->onError(client, err, client->user);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(client->onComplete) client->onComplete(client, client->user);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void httpclientTlsOnError(
|
|
||||||
networktls_t *tls,
|
|
||||||
errorret_t err,
|
|
||||||
void *user
|
|
||||||
) {
|
|
||||||
httpclient_t *client = (httpclient_t *)user;
|
|
||||||
if(client->onError) client->onError(client, err, client->user);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void httpclientTlsOnDisconnect(networktls_t *tls, void *user) {
|
|
||||||
/* nothing — httpclientTlsOnConnect drives the full lifecycle */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---- public API -------------------------------------------------------- */
|
|
||||||
|
|
||||||
errorret_t httpclientInit(httpclient_t *client) {
|
|
||||||
memoryZero(client, sizeof(httpclient_t));
|
|
||||||
errorChain(networktlsInit(&client->tls));
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t httpclientParseUrl(
|
|
||||||
httpurl_t *url,
|
|
||||||
errorstate_t *errorState,
|
|
||||||
const char_t *urlStr
|
|
||||||
) {
|
|
||||||
const char_t *schemeEnd;
|
|
||||||
const char_t *hostStart;
|
|
||||||
const char_t *portStart;
|
|
||||||
const char_t *pathStart;
|
|
||||||
size_t hostLen;
|
|
||||||
|
|
||||||
memoryZero(url, sizeof(httpurl_t));
|
|
||||||
|
|
||||||
schemeEnd = strstr(urlStr, "://");
|
|
||||||
if(!schemeEnd) {
|
|
||||||
errorThrowState(errorState, "URL missing scheme: %s", urlStr);
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t schemeLen = (size_t)(schemeEnd - urlStr);
|
|
||||||
if(schemeLen >= HTTPCLIENT_SCHEME_MAX) {
|
|
||||||
errorThrowState(errorState, "URL scheme too long");
|
|
||||||
}
|
|
||||||
memoryCopy(url->scheme, urlStr, schemeLen);
|
|
||||||
url->scheme[schemeLen] = '\0';
|
|
||||||
|
|
||||||
/* default port by scheme */
|
|
||||||
if(httpclientStrEqualCI(url->scheme, "https")) {
|
|
||||||
url->port = 443;
|
|
||||||
} else if(httpclientStrEqualCI(url->scheme, "http")) {
|
|
||||||
url->port = 80;
|
|
||||||
} else {
|
|
||||||
errorThrowState(errorState, "Unsupported URL scheme: %s", url->scheme);
|
|
||||||
}
|
|
||||||
|
|
||||||
hostStart = schemeEnd + 3;
|
|
||||||
pathStart = strchr(hostStart, '/');
|
|
||||||
portStart = strchr(hostStart, ':');
|
|
||||||
|
|
||||||
/* port present and appears before the path */
|
|
||||||
if(portStart != NULL && (pathStart == NULL || portStart < pathStart)) {
|
|
||||||
hostLen = (size_t)(portStart - hostStart);
|
|
||||||
url->port = (uint16_t)atoi(portStart + 1);
|
|
||||||
} else {
|
|
||||||
hostLen = pathStart != NULL
|
|
||||||
? (size_t)(pathStart - hostStart)
|
|
||||||
: strlen(hostStart);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(hostLen == 0 || hostLen >= HTTPCLIENT_HOST_MAX) {
|
|
||||||
errorThrowState(errorState, "URL host missing or too long");
|
|
||||||
}
|
|
||||||
memoryCopy(url->host, hostStart, hostLen);
|
|
||||||
url->host[hostLen] = '\0';
|
|
||||||
|
|
||||||
if(pathStart != NULL) {
|
|
||||||
size_t pathLen = strlen(pathStart);
|
|
||||||
if(pathLen >= HTTPCLIENT_PATH_MAX) pathLen = HTTPCLIENT_PATH_MAX - 1;
|
|
||||||
memoryCopy(url->path, pathStart, pathLen);
|
|
||||||
url->path[pathLen] = '\0';
|
|
||||||
} else {
|
|
||||||
url->path[0] = '/';
|
|
||||||
url->path[1] = '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
void httpclientSetHeader(
|
|
||||||
httpclient_t *client,
|
|
||||||
const char_t *name,
|
|
||||||
const char_t *value
|
|
||||||
) {
|
|
||||||
size_t nameLen;
|
|
||||||
size_t valLen;
|
|
||||||
size_t i;
|
|
||||||
httpheader_t *h;
|
|
||||||
|
|
||||||
assertNotNull(name, "header name must not be null");
|
|
||||||
assertNotNull(value, "header value must not be null");
|
|
||||||
|
|
||||||
/* replace existing header with same name */
|
|
||||||
for(i = 0; i < client->requestHeaderCount; i++) {
|
|
||||||
if(httpclientStrEqualCI(client->requestHeaders[i].name, name)) {
|
|
||||||
h = &client->requestHeaders[i];
|
|
||||||
valLen = strlen(value);
|
|
||||||
if(valLen >= HTTPCLIENT_HEADER_VALUE_MAX) valLen = HTTPCLIENT_HEADER_VALUE_MAX - 1;
|
|
||||||
memoryCopy(h->value, value, valLen);
|
|
||||||
h->value[valLen] = '\0';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assertTrue(
|
|
||||||
client->requestHeaderCount < HTTPCLIENT_HEADER_MAX,
|
|
||||||
"httpclientSetHeader: request header limit exceeded"
|
|
||||||
);
|
|
||||||
|
|
||||||
h = &client->requestHeaders[client->requestHeaderCount++];
|
|
||||||
|
|
||||||
nameLen = strlen(name);
|
|
||||||
if(nameLen >= HTTPCLIENT_HEADER_NAME_MAX) nameLen = HTTPCLIENT_HEADER_NAME_MAX - 1;
|
|
||||||
memoryCopy(h->name, name, nameLen);
|
|
||||||
h->name[nameLen] = '\0';
|
|
||||||
|
|
||||||
valLen = strlen(value);
|
|
||||||
if(valLen >= HTTPCLIENT_HEADER_VALUE_MAX) valLen = HTTPCLIENT_HEADER_VALUE_MAX - 1;
|
|
||||||
memoryCopy(h->value, value, valLen);
|
|
||||||
h->value[valLen] = '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
void httpclientRequest(
|
|
||||||
httpclient_t *client,
|
|
||||||
const char_t *method,
|
|
||||||
const char_t *url,
|
|
||||||
bool_t verifyPeer,
|
|
||||||
httpclientheadercallback_t onHeaders,
|
|
||||||
httpclientdatacallback_t onData,
|
|
||||||
httpclientcompletecallback_t onComplete,
|
|
||||||
httpclienterrorcallback_t onError,
|
|
||||||
void *user
|
|
||||||
) {
|
|
||||||
size_t methodLen;
|
|
||||||
errorret_t err;
|
|
||||||
|
|
||||||
assertNotNull(method, "method must not be null");
|
|
||||||
assertNotNull(url, "url must not be null");
|
|
||||||
|
|
||||||
methodLen = strlen(method);
|
|
||||||
if(methodLen >= HTTPCLIENT_METHOD_MAX) methodLen = HTTPCLIENT_METHOD_MAX - 1;
|
|
||||||
memoryCopy(client->method, method, methodLen);
|
|
||||||
client->method[methodLen] = '\0';
|
|
||||||
|
|
||||||
err = httpclientParseUrl(&client->url, &client->errorState, url);
|
|
||||||
if(err.code != ERROR_OK) {
|
|
||||||
if(onError) onError(client, err, user);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
client->onHeaders = onHeaders;
|
|
||||||
client->onData = onData;
|
|
||||||
client->onComplete = onComplete;
|
|
||||||
client->onError = onError;
|
|
||||||
client->user = user;
|
|
||||||
client->statusCode = 0;
|
|
||||||
client->responseHeaderCount = 0;
|
|
||||||
|
|
||||||
networktlsConnect(
|
|
||||||
&client->tls,
|
|
||||||
client->url.host,
|
|
||||||
client->url.port,
|
|
||||||
verifyPeer,
|
|
||||||
httpclientTlsOnConnect,
|
|
||||||
httpclientTlsOnError,
|
|
||||||
httpclientTlsOnDisconnect,
|
|
||||||
client
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void httpclientDisconnect(httpclient_t *client) {
|
|
||||||
networktlsDisconnect(&client->tls);
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t httpclientDispose(httpclient_t *client) {
|
|
||||||
return networktlsDispose(&client->tls);
|
|
||||||
}
|
|
||||||
@@ -1,170 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "network/networktls.h"
|
|
||||||
#include "util/endian.h"
|
|
||||||
|
|
||||||
#define HTTPCLIENT_METHOD_MAX 8
|
|
||||||
#define HTTPCLIENT_URL_MAX 1024
|
|
||||||
#define HTTPCLIENT_HOST_MAX 256
|
|
||||||
#define HTTPCLIENT_PATH_MAX 768
|
|
||||||
#define HTTPCLIENT_SCHEME_MAX 8
|
|
||||||
#define HTTPCLIENT_HEADER_NAME_MAX 64
|
|
||||||
#define HTTPCLIENT_HEADER_VALUE_MAX 512
|
|
||||||
#define HTTPCLIENT_HEADER_MAX 32
|
|
||||||
#define HTTPCLIENT_HEADER_BUF_SIZE 8192
|
|
||||||
#define HTTPCLIENT_BODY_BUF_SIZE 4096
|
|
||||||
#define HTTPCLIENT_REQ_BUF_SIZE 4096
|
|
||||||
|
|
||||||
/** Parsed components of a URL. */
|
|
||||||
typedef struct {
|
|
||||||
char_t scheme[HTTPCLIENT_SCHEME_MAX];
|
|
||||||
char_t host[HTTPCLIENT_HOST_MAX];
|
|
||||||
uint16_t port;
|
|
||||||
char_t path[HTTPCLIENT_PATH_MAX];
|
|
||||||
} httpurl_t;
|
|
||||||
|
|
||||||
/** A single HTTP header (name + value pair). */
|
|
||||||
typedef struct {
|
|
||||||
char_t name[HTTPCLIENT_HEADER_NAME_MAX];
|
|
||||||
char_t value[HTTPCLIENT_HEADER_VALUE_MAX];
|
|
||||||
} httpheader_t;
|
|
||||||
|
|
||||||
typedef struct httpclient_s httpclient_t;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called once the response status line and all headers have been parsed.
|
|
||||||
* Fired from the socket thread before any body data arrives.
|
|
||||||
*/
|
|
||||||
typedef void (*httpclientheadercallback_t)(
|
|
||||||
httpclient_t *client,
|
|
||||||
uint16_t statusCode,
|
|
||||||
const httpheader_t *headers,
|
|
||||||
size_t headerCount,
|
|
||||||
void *user
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called for each chunk of response body data.
|
|
||||||
* May be called multiple times per request. Fired from the socket thread.
|
|
||||||
* For binary payloads use endianReadBE32 / endianReadLE32 from util/endian.h
|
|
||||||
* to read multi-byte values with correct byte order.
|
|
||||||
*/
|
|
||||||
typedef void (*httpclientdatacallback_t)(
|
|
||||||
httpclient_t *client,
|
|
||||||
const uint8_t *data,
|
|
||||||
size_t len,
|
|
||||||
void *user
|
|
||||||
);
|
|
||||||
|
|
||||||
/** Called once when the response body has been fully received. */
|
|
||||||
typedef void (*httpclientcompletecallback_t)(
|
|
||||||
httpclient_t *client,
|
|
||||||
void *user
|
|
||||||
);
|
|
||||||
|
|
||||||
/** Called on any transport or protocol error. */
|
|
||||||
typedef void (*httpclienterrorcallback_t)(
|
|
||||||
httpclient_t *client,
|
|
||||||
errorret_t err,
|
|
||||||
void *user
|
|
||||||
);
|
|
||||||
|
|
||||||
typedef struct httpclient_s {
|
|
||||||
networktls_t tls;
|
|
||||||
errorstate_t errorState;
|
|
||||||
|
|
||||||
/* --- request config (set before httpclientRequest) --- */
|
|
||||||
char_t method[HTTPCLIENT_METHOD_MAX];
|
|
||||||
httpurl_t url;
|
|
||||||
httpheader_t requestHeaders[HTTPCLIENT_HEADER_MAX];
|
|
||||||
size_t requestHeaderCount;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Optional request body. The pointer must remain valid until onComplete or
|
|
||||||
* onError fires. The HTTP layer does not copy the body.
|
|
||||||
*/
|
|
||||||
const uint8_t *requestBody;
|
|
||||||
size_t requestBodyLen;
|
|
||||||
|
|
||||||
/* --- response state (populated during request) --- */
|
|
||||||
uint16_t statusCode;
|
|
||||||
httpheader_t responseHeaders[HTTPCLIENT_HEADER_MAX];
|
|
||||||
size_t responseHeaderCount;
|
|
||||||
|
|
||||||
/* --- callbacks (all fired from the socket thread) --- */
|
|
||||||
httpclientheadercallback_t onHeaders;
|
|
||||||
httpclientdatacallback_t onData;
|
|
||||||
httpclientcompletecallback_t onComplete;
|
|
||||||
httpclienterrorcallback_t onError;
|
|
||||||
void *user;
|
|
||||||
} httpclient_t;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes an HTTP client. Must be called before any other httpclient
|
|
||||||
* function.
|
|
||||||
*/
|
|
||||||
errorret_t httpclientInit(httpclient_t *client);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses a URL string into its components. Supports http:// and https://.
|
|
||||||
* Port defaults to 80 (HTTP) or 443 (HTTPS) when not specified.
|
|
||||||
*
|
|
||||||
* @return ERROR_NOT_OK if the URL is malformed.
|
|
||||||
*/
|
|
||||||
errorret_t httpclientParseUrl(
|
|
||||||
httpurl_t *url,
|
|
||||||
errorstate_t *errorState,
|
|
||||||
const char_t *urlStr
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds or replaces a request header. Must be called before httpclientRequest.
|
|
||||||
* Asserts if HTTPCLIENT_HEADER_MAX is exceeded.
|
|
||||||
*/
|
|
||||||
void httpclientSetHeader(
|
|
||||||
httpclient_t *client,
|
|
||||||
const char_t *name,
|
|
||||||
const char_t *value
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts an asynchronous HTTPS (or plain HTTP) request. All callbacks fire
|
|
||||||
* from the socket thread, not the main thread.
|
|
||||||
*
|
|
||||||
* @param method HTTP verb ("GET", "POST", etc.).
|
|
||||||
* @param url Full URL string — parsed internally.
|
|
||||||
* @param verifyPeer Pass true to verify the server TLS certificate. Pass
|
|
||||||
* false for self-signed / development servers.
|
|
||||||
* @param onHeaders Called when response headers arrive (may be NULL).
|
|
||||||
* @param onData Called for each body chunk (may be NULL).
|
|
||||||
* @param onComplete Called when the full response is received (may be NULL).
|
|
||||||
* @param onError Called on any error (may be NULL).
|
|
||||||
* @param user Passed to all callbacks.
|
|
||||||
*/
|
|
||||||
void httpclientRequest(
|
|
||||||
httpclient_t *client,
|
|
||||||
const char_t *method,
|
|
||||||
const char_t *url,
|
|
||||||
bool_t verifyPeer,
|
|
||||||
httpclientheadercallback_t onHeaders,
|
|
||||||
httpclientdatacallback_t onData,
|
|
||||||
httpclientcompletecallback_t onComplete,
|
|
||||||
httpclienterrorcallback_t onError,
|
|
||||||
void *user
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Requests cancellation of an in-flight request. Non-blocking.
|
|
||||||
*/
|
|
||||||
void httpclientDisconnect(httpclient_t *client);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Blocks until the request thread exits, then frees all resources.
|
|
||||||
*/
|
|
||||||
errorret_t httpclientDispose(httpclient_t *client);
|
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const char_t *key;
|
||||||
|
const char_t *value;
|
||||||
|
} networkhttpheader_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
|
||||||
|
} networkhttprequest_t;
|
||||||
|
|
||||||
|
void networkHTTP(
|
||||||
|
const char_t *url,
|
||||||
|
const char_t *method,
|
||||||
|
void *user,
|
||||||
|
void (*onComplete)(void *user),
|
||||||
|
void (*onError)(errorret_t error, void *user)
|
||||||
|
);
|
||||||
@@ -1,165 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "networksocket.h"
|
|
||||||
#include "util/memory.h"
|
|
||||||
#include "assert/assert.h"
|
|
||||||
|
|
||||||
static void networksocketThreadCallback(thread_t *thread) {
|
|
||||||
networksocket_t *socket = (networksocket_t *)thread->data;
|
|
||||||
errorret_t err;
|
|
||||||
bool_t hadError;
|
|
||||||
|
|
||||||
hadError = false;
|
|
||||||
|
|
||||||
err = networksocketPlatformConnect(socket);
|
|
||||||
if (err.code != ERROR_OK) {
|
|
||||||
socket->state = NETWORKSOCKET_STATE_DISCONNECTED;
|
|
||||||
if (socket->onError) socket->onError(socket, err, socket->user);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
socket->state = NETWORKSOCKET_STATE_CONNECTED;
|
|
||||||
if (socket->onConnect) socket->onConnect(socket, socket->user);
|
|
||||||
|
|
||||||
if (socket->onReceive != NULL) {
|
|
||||||
uint8_t recvBuf[NETWORKSOCKET_RECV_BUFFER_SIZE];
|
|
||||||
uint8_t localSendBuf[NETWORKSOCKET_SEND_BUFFER_SIZE];
|
|
||||||
size_t sendLen;
|
|
||||||
size_t recvLen;
|
|
||||||
|
|
||||||
while (!threadShouldStop(thread)) {
|
|
||||||
threadMutexLock(&socket->sendMutex);
|
|
||||||
sendLen = socket->sendLen;
|
|
||||||
if (sendLen > 0) {
|
|
||||||
memoryCopy(localSendBuf, socket->sendBuffer, sendLen);
|
|
||||||
socket->sendLen = 0;
|
|
||||||
}
|
|
||||||
threadMutexUnlock(&socket->sendMutex);
|
|
||||||
|
|
||||||
if (sendLen > 0) {
|
|
||||||
err = networksocketPlatformSend(socket, localSendBuf, sendLen);
|
|
||||||
if (err.code != ERROR_OK) {
|
|
||||||
hadError = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
recvLen = 0;
|
|
||||||
err = networksocketPlatformRecv(
|
|
||||||
socket, recvBuf, NETWORKSOCKET_RECV_BUFFER_SIZE, &recvLen
|
|
||||||
);
|
|
||||||
if (err.code != ERROR_OK) {
|
|
||||||
hadError = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (recvLen == NETWORKSOCKET_RECV_CLOSED) break;
|
|
||||||
|
|
||||||
if (recvLen > 0) {
|
|
||||||
socket->onReceive(socket, recvBuf, recvLen, socket->user);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
socket->state = NETWORKSOCKET_STATE_DISCONNECTING;
|
|
||||||
networksocketPlatformDisconnect(socket);
|
|
||||||
socket->state = NETWORKSOCKET_STATE_DISCONNECTED;
|
|
||||||
|
|
||||||
if (hadError) {
|
|
||||||
if (socket->onError) socket->onError(socket, err, socket->user);
|
|
||||||
} else {
|
|
||||||
if (socket->onDisconnect) socket->onDisconnect(socket, socket->user);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t networksocketInit(networksocket_t *socket) {
|
|
||||||
memoryZero(socket, sizeof(networksocket_t));
|
|
||||||
socket->state = NETWORKSOCKET_STATE_DISCONNECTED;
|
|
||||||
threadInit(&socket->thread, networksocketThreadCallback);
|
|
||||||
socket->thread.data = socket;
|
|
||||||
threadMutexInit(&socket->sendMutex);
|
|
||||||
errorChain(networksocketPlatformInit(socket));
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
void networksocketConnect(
|
|
||||||
networksocket_t *socket,
|
|
||||||
const char_t *host,
|
|
||||||
uint16_t port,
|
|
||||||
networksocketcallback_t onConnect,
|
|
||||||
networksocketerrorcallback_t onError,
|
|
||||||
networksocketcallback_t onDisconnect,
|
|
||||||
networksocketrecvcallback_t onReceive,
|
|
||||||
void *user
|
|
||||||
) {
|
|
||||||
size_t hostLen;
|
|
||||||
|
|
||||||
assertNotNull(host, "host must not be null");
|
|
||||||
hostLen = strlen(host);
|
|
||||||
assertTrue(hostLen < NETWORKSOCKET_HOST_MAX, "host exceeds NETWORKSOCKET_HOST_MAX");
|
|
||||||
|
|
||||||
memoryCopy(socket->host, host, hostLen + 1);
|
|
||||||
socket->port = port;
|
|
||||||
socket->onConnect = onConnect;
|
|
||||||
socket->onError = onError;
|
|
||||||
socket->onDisconnect = onDisconnect;
|
|
||||||
socket->onReceive = onReceive;
|
|
||||||
socket->user = user;
|
|
||||||
socket->sendLen = 0;
|
|
||||||
socket->state = NETWORKSOCKET_STATE_CONNECTING;
|
|
||||||
|
|
||||||
threadStart(&socket->thread);
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t networksocketSend(
|
|
||||||
networksocket_t *socket,
|
|
||||||
const uint8_t *data,
|
|
||||||
size_t len
|
|
||||||
) {
|
|
||||||
assertNotNull(data, "data must not be null");
|
|
||||||
assertTrue(len > 0, "len must be greater than 0");
|
|
||||||
|
|
||||||
threadMutexLock(&socket->sendMutex);
|
|
||||||
assertTrue(
|
|
||||||
socket->sendLen + len <= NETWORKSOCKET_SEND_BUFFER_SIZE,
|
|
||||||
"networksocketSend would overflow send buffer"
|
|
||||||
);
|
|
||||||
memoryCopy(socket->sendBuffer + socket->sendLen, data, len);
|
|
||||||
socket->sendLen += len;
|
|
||||||
threadMutexUnlock(&socket->sendMutex);
|
|
||||||
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t networksocketWrite(
|
|
||||||
networksocket_t *socket,
|
|
||||||
const uint8_t *data,
|
|
||||||
size_t len
|
|
||||||
) {
|
|
||||||
return networksocketPlatformSend(socket, data, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t networksocketRead(
|
|
||||||
networksocket_t *socket,
|
|
||||||
uint8_t *buf,
|
|
||||||
size_t maxLen,
|
|
||||||
size_t *outLen
|
|
||||||
) {
|
|
||||||
return networksocketPlatformRecv(socket, buf, maxLen, outLen);
|
|
||||||
}
|
|
||||||
|
|
||||||
void networksocketDisconnect(networksocket_t *socket) {
|
|
||||||
threadStopRequest(&socket->thread);
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t networksocketDispose(networksocket_t *socket) {
|
|
||||||
threadStop(&socket->thread);
|
|
||||||
errorChain(networksocketPlatformDispose(socket));
|
|
||||||
threadMutexDispose(&socket->sendMutex);
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
@@ -1,170 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "error/error.h"
|
|
||||||
#include "thread/thread.h"
|
|
||||||
#include "network/networksocketplatform.h"
|
|
||||||
#ifndef networksocketPlatformInit
|
|
||||||
#error "networksocketPlatformInit must be defined"
|
|
||||||
#endif
|
|
||||||
#ifndef networksocketPlatformConnect
|
|
||||||
#error "networksocketPlatformConnect must be defined"
|
|
||||||
#endif
|
|
||||||
#ifndef networksocketPlatformSend
|
|
||||||
#error "networksocketPlatformSend must be defined"
|
|
||||||
#endif
|
|
||||||
#ifndef networksocketPlatformRecv
|
|
||||||
#error "networksocketPlatformRecv must be defined"
|
|
||||||
#endif
|
|
||||||
#ifndef networksocketPlatformDisconnect
|
|
||||||
#error "networksocketPlatformDisconnect must be defined"
|
|
||||||
#endif
|
|
||||||
#ifndef networksocketPlatformDispose
|
|
||||||
#error "networksocketPlatformDispose must be defined"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define NETWORKSOCKET_HOST_MAX 256
|
|
||||||
#define NETWORKSOCKET_SEND_BUFFER_SIZE 4096
|
|
||||||
#define NETWORKSOCKET_RECV_BUFFER_SIZE 4096
|
|
||||||
|
|
||||||
/** Sentinel returned by networksocketPlatformRecv when the peer closed the connection. */
|
|
||||||
#define NETWORKSOCKET_RECV_CLOSED ((size_t)(-1))
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
NETWORKSOCKET_STATE_DISCONNECTED,
|
|
||||||
NETWORKSOCKET_STATE_CONNECTING,
|
|
||||||
NETWORKSOCKET_STATE_CONNECTED,
|
|
||||||
NETWORKSOCKET_STATE_DISCONNECTING,
|
|
||||||
} networksocketstate_t;
|
|
||||||
|
|
||||||
typedef struct networksocket_s networksocket_t;
|
|
||||||
|
|
||||||
typedef void (*networksocketcallback_t)(
|
|
||||||
networksocket_t *socket, void *user
|
|
||||||
);
|
|
||||||
typedef void (*networksocketerrorcallback_t)(
|
|
||||||
networksocket_t *socket, errorret_t err, void *user
|
|
||||||
);
|
|
||||||
typedef void (*networksocketrecvcallback_t)(
|
|
||||||
networksocket_t *socket, const uint8_t *data, size_t len, void *user
|
|
||||||
);
|
|
||||||
|
|
||||||
typedef struct networksocket_s {
|
|
||||||
networksocketstate_t state;
|
|
||||||
errorstate_t errorState;
|
|
||||||
|
|
||||||
thread_t thread;
|
|
||||||
threadmutex_t sendMutex;
|
|
||||||
|
|
||||||
char_t host[NETWORKSOCKET_HOST_MAX];
|
|
||||||
uint16_t port;
|
|
||||||
|
|
||||||
networksocketplatform_t platform;
|
|
||||||
|
|
||||||
uint8_t sendBuffer[NETWORKSOCKET_SEND_BUFFER_SIZE];
|
|
||||||
size_t sendLen;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called from the socket thread once TCP is established. If onReceive is
|
|
||||||
* NULL, the socket thread will exit after this returns and disconnect.
|
|
||||||
* If onReceive is set, a recv loop runs after this returns.
|
|
||||||
* Suitable entry point for TLS/HTTP layers that drive their own I/O.
|
|
||||||
*/
|
|
||||||
networksocketcallback_t onConnect;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When non-NULL, the socket thread runs a receive loop after connection
|
|
||||||
* and calls this for each incoming data chunk. When NULL, the thread only
|
|
||||||
* calls onConnect and then disconnects.
|
|
||||||
*/
|
|
||||||
networksocketrecvcallback_t onReceive;
|
|
||||||
|
|
||||||
networksocketerrorcallback_t onError;
|
|
||||||
networksocketcallback_t onDisconnect;
|
|
||||||
void *user;
|
|
||||||
} networksocket_t;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes a socket structure. Must be called before any other socket
|
|
||||||
* function.
|
|
||||||
*/
|
|
||||||
errorret_t networksocketInit(networksocket_t *socket);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts an asynchronous connection in a dedicated thread. All callbacks are
|
|
||||||
* invoked from the socket thread, not the main thread.
|
|
||||||
*
|
|
||||||
* If onReceive is non-NULL, the thread runs a receive loop after connection
|
|
||||||
* and delivers incoming data via onReceive. If onReceive is NULL, the thread
|
|
||||||
* calls onConnect and exits (useful for TLS or request-driven I/O that
|
|
||||||
* manages its own reads via networksocketRead/networksocketWrite).
|
|
||||||
*
|
|
||||||
* @param socket Socket to connect.
|
|
||||||
* @param host Hostname or IP address to connect to.
|
|
||||||
* @param port Port number.
|
|
||||||
* @param onConnect Called once TCP is established (may be NULL).
|
|
||||||
* @param onError Called on connect or I/O error (may be NULL).
|
|
||||||
* @param onDisconnect Called on graceful disconnect (may be NULL).
|
|
||||||
* @param onReceive Called for each received chunk; NULL disables recv loop.
|
|
||||||
* @param user User data passed to all callbacks.
|
|
||||||
*/
|
|
||||||
void networksocketConnect(
|
|
||||||
networksocket_t *socket,
|
|
||||||
const char_t *host,
|
|
||||||
uint16_t port,
|
|
||||||
networksocketcallback_t onConnect,
|
|
||||||
networksocketerrorcallback_t onError,
|
|
||||||
networksocketcallback_t onDisconnect,
|
|
||||||
networksocketrecvcallback_t onReceive,
|
|
||||||
void *user
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Queues data for sending. Thread-safe; may be called from any thread.
|
|
||||||
* The socket thread drains the queue on each recv-loop iteration.
|
|
||||||
* Asserts if the send buffer would overflow.
|
|
||||||
*/
|
|
||||||
errorret_t networksocketSend(
|
|
||||||
networksocket_t *socket,
|
|
||||||
const uint8_t *data,
|
|
||||||
size_t len
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes data directly to the socket without buffering. Must be called from
|
|
||||||
* the socket's own thread (e.g., from within onConnect or a TLS bio callback).
|
|
||||||
*/
|
|
||||||
errorret_t networksocketWrite(
|
|
||||||
networksocket_t *socket,
|
|
||||||
const uint8_t *data,
|
|
||||||
size_t len
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads data directly from the socket. Must be called from the socket's own
|
|
||||||
* thread. Returns 0 in *outLen on timeout (no data yet); returns
|
|
||||||
* NETWORKSOCKET_RECV_CLOSED in *outLen when the peer has closed the connection.
|
|
||||||
*/
|
|
||||||
errorret_t networksocketRead(
|
|
||||||
networksocket_t *socket,
|
|
||||||
uint8_t *buf,
|
|
||||||
size_t maxLen,
|
|
||||||
size_t *outLen
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Requests the socket thread to stop. Non-blocking; onDisconnect fires once
|
|
||||||
* the thread has finished.
|
|
||||||
*/
|
|
||||||
void networksocketDisconnect(networksocket_t *socket);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Blocks until the socket thread has stopped, then releases platform
|
|
||||||
* resources. Safe to call on an already-disconnected socket.
|
|
||||||
*/
|
|
||||||
errorret_t networksocketDispose(networksocket_t *socket);
|
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "networksocketclient.h"
|
||||||
|
#include "assert/assert.h"
|
||||||
|
#include "util/memory.h"
|
||||||
|
|
||||||
|
void networkSocketClientInit(
|
||||||
|
networksocketclient_t *client,
|
||||||
|
const char_t *host,
|
||||||
|
uint16_t port,
|
||||||
|
void *user,
|
||||||
|
void (*onConnect)(void *user),
|
||||||
|
void (*onError)(errorret_t error, void *user),
|
||||||
|
void (*onDisconnect)(void *user)
|
||||||
|
) {
|
||||||
|
assertNotNull(client, "Client cannot be NULL");
|
||||||
|
assertStrLenMin(host, 1, "Host cannot be empty");
|
||||||
|
assertNotNull(onConnect, "onConnect callback cannot be NULL");
|
||||||
|
assertNotNull(onError, "onError callback cannot be NULL");
|
||||||
|
assertNotNull(onDisconnect, "onDisconnect callback cannot be NULL");
|
||||||
|
|
||||||
|
memoryZero(client, sizeof(networksocketclient_t));
|
||||||
|
client->user = user;
|
||||||
|
client->onConnect = onConnect;
|
||||||
|
client->onError = onError;
|
||||||
|
client->onDisconnect = onDisconnect;
|
||||||
|
client->state = NETWORK_SOCKET_CLIENT_STATE_CONNECTING;
|
||||||
|
|
||||||
|
// Pass to platform for implementation.
|
||||||
|
networkSocketClientPlatformInit(
|
||||||
|
client,
|
||||||
|
host,
|
||||||
|
port,
|
||||||
|
user,
|
||||||
|
onConnect,
|
||||||
|
onError,
|
||||||
|
onDisconnect
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "error/error.h"
|
||||||
|
#include "network/networksocketclientplatform.h"
|
||||||
|
|
||||||
|
#ifndef networkSocketClientPlatformInit
|
||||||
|
#error "Define networkSocketClientPlatformInit"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
NETWORK_SOCKET_CLIENT_STATE_DISCONNECTED,
|
||||||
|
NETWORK_SOCKET_CLIENT_STATE_CONNECTING,
|
||||||
|
} networksocketclientstate_t;
|
||||||
|
|
||||||
|
typedef struct networksocketclient_s {
|
||||||
|
void *user;
|
||||||
|
void (*onConnect)(void *user);
|
||||||
|
void (*onError)(errorret_t error, void *user);
|
||||||
|
void (*onDisconnect)(void *user);
|
||||||
|
networksocketclientstate_t state;
|
||||||
|
networksocketclientplatform_t platform;
|
||||||
|
errorstate_t errorState;
|
||||||
|
} networksocketclient_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes a network socket client connection.
|
||||||
|
*
|
||||||
|
* @param client The client struct to initialize.
|
||||||
|
* @param host The hostname or IP address to connect to.
|
||||||
|
* @param port The port number to connect to.
|
||||||
|
* @param user User data to pass to callbacks.
|
||||||
|
* @param onConnect Callback for when the connection is established.
|
||||||
|
* @param onError Callback for when an error occurs.
|
||||||
|
* @param onDisconnect Callback for when the connection is disconnected.
|
||||||
|
*/
|
||||||
|
void networkSocketClientInit(
|
||||||
|
networksocketclient_t *client,
|
||||||
|
const char_t *host,
|
||||||
|
uint16_t port,
|
||||||
|
void *user,
|
||||||
|
void (*onConnect)(void *user),
|
||||||
|
void (*onError)(errorret_t error, void *user),
|
||||||
|
void (*onDisconnect)(void *user)
|
||||||
|
);
|
||||||
@@ -1,288 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "networktls.h"
|
|
||||||
#include "util/memory.h"
|
|
||||||
#include "assert/assert.h"
|
|
||||||
|
|
||||||
// Define missing mbedtls net error codes if not present
|
|
||||||
#ifndef MBEDTLS_ERR_NET_SEND_FAILED
|
|
||||||
#define MBEDTLS_ERR_NET_SEND_FAILED -0x004E
|
|
||||||
#endif
|
|
||||||
#ifndef MBEDTLS_ERR_NET_RECV_FAILED
|
|
||||||
#define MBEDTLS_ERR_NET_RECV_FAILED -0x004C
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* ---- mbedTLS bio callbacks -------------------------------------------- */
|
|
||||||
|
|
||||||
#ifdef DUSK_DOLPHIN
|
|
||||||
#include <ogc/lwp_watchdog.h>
|
|
||||||
#include <mbedtls/platform_time.h>
|
|
||||||
mbedtls_ms_time_t mbedtls_ms_time(void) {
|
|
||||||
return (mbedtls_ms_time_t)ticks_to_millisecs(gettime());
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
int mbedtls_platform_get_entropy(uint8_t *output, size_t len) {
|
|
||||||
for (size_t i = 0; i < len; i++) {
|
|
||||||
output[i] = rand() & 0xFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int networktlsBioSend(
|
|
||||||
void *ctx,
|
|
||||||
const unsigned char *buf,
|
|
||||||
size_t len
|
|
||||||
) {
|
|
||||||
networktls_t *tls = (networktls_t *)ctx;
|
|
||||||
errorret_t err = networksocketWrite(&tls->socket, (const uint8_t *)buf, len);
|
|
||||||
if(err.code != ERROR_OK) return MBEDTLS_ERR_NET_SEND_FAILED;
|
|
||||||
return (int)len;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int networktlsBioRecv(void *ctx, unsigned char *buf, size_t len) {
|
|
||||||
networktls_t *tls = (networktls_t *)ctx;
|
|
||||||
size_t outLen = 0;
|
|
||||||
errorret_t err = networksocketRead(
|
|
||||||
&tls->socket, (uint8_t *)buf, len, &outLen
|
|
||||||
);
|
|
||||||
if(err.code != ERROR_OK) return MBEDTLS_ERR_NET_RECV_FAILED;
|
|
||||||
if(outLen == NETWORKSOCKET_RECV_CLOSED) return 0;
|
|
||||||
if(outLen == 0) return MBEDTLS_ERR_SSL_WANT_READ;
|
|
||||||
return (int)outLen;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---- socket callbacks (run in socket thread) --------------------------- */
|
|
||||||
|
|
||||||
static void networktlsSocketOnConnect(networksocket_t *socket, void *user) {
|
|
||||||
networktls_t *tls = (networktls_t *)user;
|
|
||||||
errorret_t errret;
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
if(psa_crypto_init() != PSA_SUCCESS) {
|
|
||||||
errret = errorThrowImpl(
|
|
||||||
&tls->errorState, ERROR_NOT_OK,
|
|
||||||
__FILE__, __func__, __LINE__,
|
|
||||||
"psa_crypto_init failed"
|
|
||||||
);
|
|
||||||
if(tls->onError) tls->onError(tls, errret, tls->user);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = mbedtls_ssl_config_defaults(
|
|
||||||
&tls->conf,
|
|
||||||
MBEDTLS_SSL_IS_CLIENT,
|
|
||||||
MBEDTLS_SSL_TRANSPORT_STREAM,
|
|
||||||
MBEDTLS_SSL_PRESET_DEFAULT
|
|
||||||
);
|
|
||||||
if(ret != 0) {
|
|
||||||
errret = errorThrowImpl(
|
|
||||||
&tls->errorState, ERROR_NOT_OK,
|
|
||||||
__FILE__, __func__, __LINE__,
|
|
||||||
"mbedtls_ssl_config_defaults failed: -0x%04x", -ret
|
|
||||||
);
|
|
||||||
if(tls->onError) tls->onError(tls, errret, tls->user);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
mbedtls_ssl_conf_authmode(
|
|
||||||
&tls->conf,
|
|
||||||
tls->verifyPeer ? MBEDTLS_SSL_VERIFY_REQUIRED : MBEDTLS_SSL_VERIFY_NONE
|
|
||||||
);
|
|
||||||
mbedtls_ssl_conf_ca_chain(&tls->conf, &tls->cacert, NULL);
|
|
||||||
|
|
||||||
ret = mbedtls_ssl_setup(&tls->ssl, &tls->conf);
|
|
||||||
if(ret != 0) {
|
|
||||||
errret = errorThrowImpl(
|
|
||||||
&tls->errorState, ERROR_NOT_OK,
|
|
||||||
__FILE__, __func__, __LINE__,
|
|
||||||
"mbedtls_ssl_setup failed: -0x%04x", -ret
|
|
||||||
);
|
|
||||||
if(tls->onError) tls->onError(tls, errret, tls->user);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = mbedtls_ssl_set_hostname(&tls->ssl, tls->socket.host);
|
|
||||||
if(ret != 0) {
|
|
||||||
errret = errorThrowImpl(
|
|
||||||
&tls->errorState, ERROR_NOT_OK,
|
|
||||||
__FILE__, __func__, __LINE__,
|
|
||||||
"mbedtls_ssl_set_hostname failed: -0x%04x", -ret
|
|
||||||
);
|
|
||||||
if(tls->onError) tls->onError(tls, errret, tls->user);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
mbedtls_ssl_set_bio(
|
|
||||||
&tls->ssl, tls,
|
|
||||||
networktlsBioSend, networktlsBioRecv, NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
/* TLS handshake loop — WANT_READ/WANT_WRITE are non-fatal retry signals */
|
|
||||||
do {
|
|
||||||
ret = mbedtls_ssl_handshake(&tls->ssl);
|
|
||||||
} while(
|
|
||||||
ret == MBEDTLS_ERR_SSL_WANT_READ ||
|
|
||||||
ret == MBEDTLS_ERR_SSL_WANT_WRITE
|
|
||||||
);
|
|
||||||
|
|
||||||
if(ret != 0) {
|
|
||||||
errret = errorThrowImpl(
|
|
||||||
&tls->errorState, ERROR_NOT_OK,
|
|
||||||
__FILE__, __func__, __LINE__,
|
|
||||||
"TLS handshake failed: -0x%04x", -ret
|
|
||||||
);
|
|
||||||
if(tls->onError) tls->onError(tls, errret, tls->user);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Handshake succeeded — let the user drive I/O from this thread */
|
|
||||||
if(tls->onConnect) tls->onConnect(tls, tls->user);
|
|
||||||
|
|
||||||
mbedtls_ssl_close_notify(&tls->ssl);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void networktlsSocketOnError(
|
|
||||||
networksocket_t *socket,
|
|
||||||
errorret_t err,
|
|
||||||
void *user
|
|
||||||
) {
|
|
||||||
networktls_t *tls = (networktls_t *)user;
|
|
||||||
if(tls->onError) tls->onError(tls, err, tls->user);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void networktlsSocketOnDisconnect(
|
|
||||||
networksocket_t *socket,
|
|
||||||
void *user
|
|
||||||
) {
|
|
||||||
networktls_t *tls = (networktls_t *)user;
|
|
||||||
if(tls->onDisconnect) tls->onDisconnect(tls, tls->user);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---- public API -------------------------------------------------------- */
|
|
||||||
|
|
||||||
errorret_t networktlsInit(networktls_t *tls) {
|
|
||||||
memoryZero(tls, sizeof(networktls_t));
|
|
||||||
errorChain(networksocketInit(&tls->socket));
|
|
||||||
mbedtls_ssl_init(&tls->ssl);
|
|
||||||
mbedtls_ssl_config_init(&tls->conf);
|
|
||||||
mbedtls_x509_crt_init(&tls->cacert);
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t networktlsAddCACert(
|
|
||||||
networktls_t *tls,
|
|
||||||
const uint8_t *certPem,
|
|
||||||
size_t len
|
|
||||||
) {
|
|
||||||
int ret = mbedtls_x509_crt_parse(&tls->cacert, certPem, len);
|
|
||||||
if(ret != 0) {
|
|
||||||
errorThrowState(
|
|
||||||
&tls->errorState,
|
|
||||||
"Failed to parse CA certificate: -0x%04x", -ret
|
|
||||||
);
|
|
||||||
}
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
void networktlsConnect(
|
|
||||||
networktls_t *tls,
|
|
||||||
const char_t *host,
|
|
||||||
uint16_t port,
|
|
||||||
bool_t verifyPeer,
|
|
||||||
networktlscallback_t onConnect,
|
|
||||||
networktlserrorcallback_t onError,
|
|
||||||
networktlscallback_t onDisconnect,
|
|
||||||
void *user
|
|
||||||
) {
|
|
||||||
tls->verifyPeer = verifyPeer;
|
|
||||||
tls->onConnect = onConnect;
|
|
||||||
tls->onError = onError;
|
|
||||||
tls->onDisconnect = onDisconnect;
|
|
||||||
tls->user = user;
|
|
||||||
|
|
||||||
networksocketConnect(
|
|
||||||
&tls->socket,
|
|
||||||
host, port,
|
|
||||||
networktlsSocketOnConnect,
|
|
||||||
networktlsSocketOnError,
|
|
||||||
networktlsSocketOnDisconnect,
|
|
||||||
NULL, /* no recv loop — TLS drives all I/O from onConnect */
|
|
||||||
tls
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t networktlsWrite(
|
|
||||||
networktls_t *tls,
|
|
||||||
const uint8_t *data,
|
|
||||||
size_t len
|
|
||||||
) {
|
|
||||||
size_t sent = 0;
|
|
||||||
int ret;
|
|
||||||
while(sent < len) {
|
|
||||||
ret = mbedtls_ssl_write(
|
|
||||||
&tls->ssl,
|
|
||||||
(const unsigned char *)(data + sent),
|
|
||||||
len - sent
|
|
||||||
);
|
|
||||||
if(ret == MBEDTLS_ERR_SSL_WANT_WRITE || ret == MBEDTLS_ERR_SSL_WANT_READ) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if(ret < 0) {
|
|
||||||
errorThrowState(
|
|
||||||
&tls->errorState,
|
|
||||||
"TLS write failed: -0x%04x", -ret
|
|
||||||
);
|
|
||||||
}
|
|
||||||
sent += (size_t)ret;
|
|
||||||
}
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t networktlsRead(
|
|
||||||
networktls_t *tls,
|
|
||||||
uint8_t *buf,
|
|
||||||
size_t maxLen,
|
|
||||||
size_t *outLen
|
|
||||||
) {
|
|
||||||
int ret;
|
|
||||||
do {
|
|
||||||
ret = mbedtls_ssl_read(&tls->ssl, (unsigned char *)buf, maxLen);
|
|
||||||
} while(
|
|
||||||
ret == MBEDTLS_ERR_SSL_WANT_READ ||
|
|
||||||
ret == MBEDTLS_ERR_SSL_WANT_WRITE
|
|
||||||
);
|
|
||||||
|
|
||||||
if(ret == 0 || ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) {
|
|
||||||
*outLen = NETWORKSOCKET_RECV_CLOSED;
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
if(ret < 0) {
|
|
||||||
errorThrowState(
|
|
||||||
&tls->errorState,
|
|
||||||
"TLS read failed: -0x%04x", -ret
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
*outLen = (size_t)ret;
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
void networktlsDisconnect(networktls_t *tls) {
|
|
||||||
networksocketDisconnect(&tls->socket);
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t networktlsDispose(networktls_t *tls) {
|
|
||||||
errorChain(networksocketDispose(&tls->socket));
|
|
||||||
mbedtls_ssl_free(&tls->ssl);
|
|
||||||
mbedtls_ssl_config_free(&tls->conf);
|
|
||||||
mbedtls_x509_crt_free(&tls->cacert);
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "network/networksocket.h"
|
|
||||||
|
|
||||||
#include <mbedtls/ssl.h>
|
|
||||||
#include <mbedtls/x509_crt.h>
|
|
||||||
#include <psa/crypto.h>
|
|
||||||
|
|
||||||
typedef struct networktls_s networktls_t;
|
|
||||||
|
|
||||||
typedef void (*networktlscallback_t)(networktls_t *tls, void *user);
|
|
||||||
typedef void (*networktlserrorcallback_t)(
|
|
||||||
networktls_t *tls, errorret_t err, void *user
|
|
||||||
);
|
|
||||||
|
|
||||||
typedef struct networktls_s {
|
|
||||||
networksocket_t socket;
|
|
||||||
errorstate_t errorState;
|
|
||||||
bool_t verifyPeer;
|
|
||||||
|
|
||||||
mbedtls_ssl_context ssl;
|
|
||||||
mbedtls_ssl_config conf;
|
|
||||||
mbedtls_x509_crt cacert;
|
|
||||||
|
|
||||||
networktlscallback_t onConnect;
|
|
||||||
networktlserrorcallback_t onError;
|
|
||||||
networktlscallback_t onDisconnect;
|
|
||||||
void *user;
|
|
||||||
} networktls_t;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the TLS context and the underlying socket.
|
|
||||||
*/
|
|
||||||
errorret_t networktlsInit(networktls_t *tls);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a PEM-encoded CA certificate used to verify the server. Must be called
|
|
||||||
* before networktlsConnect when verifyPeer is true.
|
|
||||||
*/
|
|
||||||
errorret_t networktlsAddCACert(
|
|
||||||
networktls_t *tls,
|
|
||||||
const uint8_t *certPem,
|
|
||||||
size_t len
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts an async TCP+TLS connection in the socket's own thread. All callbacks
|
|
||||||
* fire from that thread, not the main thread.
|
|
||||||
*
|
|
||||||
* When onConnect fires, the TLS handshake is complete and the connection is
|
|
||||||
* ready for networktlsWrite / networktlsRead. The connection is kept alive
|
|
||||||
* until onConnect returns, so all I/O should be performed synchronously within
|
|
||||||
* that callback.
|
|
||||||
*
|
|
||||||
* @param verifyPeer When true, the server certificate is verified against the
|
|
||||||
* CA certificates added via networktlsAddCACert. When false,
|
|
||||||
* certificate verification is skipped (useful for development
|
|
||||||
* or self-signed certificates).
|
|
||||||
*/
|
|
||||||
void networktlsConnect(
|
|
||||||
networktls_t *tls,
|
|
||||||
const char_t *host,
|
|
||||||
uint16_t port,
|
|
||||||
bool_t verifyPeer,
|
|
||||||
networktlscallback_t onConnect,
|
|
||||||
networktlserrorcallback_t onError,
|
|
||||||
networktlscallback_t onDisconnect,
|
|
||||||
void *user
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes data through the TLS layer. Must be called from within the
|
|
||||||
* onConnect callback (i.e., from the socket thread).
|
|
||||||
*/
|
|
||||||
errorret_t networktlsWrite(
|
|
||||||
networktls_t *tls,
|
|
||||||
const uint8_t *data,
|
|
||||||
size_t len
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads up to maxLen bytes from the TLS layer. Blocks until data arrives or
|
|
||||||
* the connection closes. Sets *outLen to bytes received, or
|
|
||||||
* NETWORKSOCKET_RECV_CLOSED when the peer has closed the connection.
|
|
||||||
* Must be called from within the onConnect callback.
|
|
||||||
*/
|
|
||||||
errorret_t networktlsRead(
|
|
||||||
networktls_t *tls,
|
|
||||||
uint8_t *buf,
|
|
||||||
size_t maxLen,
|
|
||||||
size_t *outLen
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Requests disconnect. Non-blocking; onDisconnect fires once complete.
|
|
||||||
*/
|
|
||||||
void networktlsDisconnect(networktls_t *tls);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Blocks until the connection thread stops, then releases all resources.
|
|
||||||
*/
|
|
||||||
errorret_t networktlsDispose(networktls_t *tls);
|
|
||||||
+1
-77
@@ -94,83 +94,7 @@ float_t endianLittleToHostFloat(float_t value) {
|
|||||||
memoryCopy(&temp, &value, sizeof(uint32_t));
|
memoryCopy(&temp, &value, sizeof(uint32_t));
|
||||||
temp = endianLittleToHost32(temp);
|
temp = endianLittleToHost32(temp);
|
||||||
memoryCopy(&result, &temp, sizeof(uint32_t));
|
memoryCopy(&result, &temp, sizeof(uint32_t));
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t endianBigToHost16(uint16_t value) {
|
|
||||||
#if defined(DUSK_PLATFORM_ENDIAN_BIG)
|
|
||||||
return value;
|
|
||||||
#elif defined(DUSK_PLATFORM_ENDIAN_LITTLE)
|
|
||||||
// Perform conversion below
|
|
||||||
#else
|
|
||||||
if(!isHostLittleEndian()) return value;
|
|
||||||
#endif
|
|
||||||
return (uint16_t)(((value & 0x00FFu) << 8) | ((value & 0xFF00u) >> 8));
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t endianBigToHost32(uint32_t value) {
|
|
||||||
#if defined(DUSK_PLATFORM_ENDIAN_BIG)
|
|
||||||
return value;
|
|
||||||
#elif defined(DUSK_PLATFORM_ENDIAN_LITTLE)
|
|
||||||
// Perform conversion below
|
|
||||||
#else
|
|
||||||
if(!isHostLittleEndian()) return value;
|
|
||||||
#endif
|
|
||||||
return (
|
|
||||||
((value & 0x000000FFu) << 24) |
|
|
||||||
((value & 0x0000FF00u) << 8) |
|
|
||||||
((value & 0x00FF0000u) >> 8) |
|
|
||||||
((value & 0xFF000000u) >> 24)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t endianBigToHost64(uint64_t value) {
|
|
||||||
#if defined(DUSK_PLATFORM_ENDIAN_BIG)
|
|
||||||
return value;
|
|
||||||
#elif defined(DUSK_PLATFORM_ENDIAN_LITTLE)
|
|
||||||
// Perform conversion below
|
|
||||||
#else
|
|
||||||
if(!isHostLittleEndian()) return value;
|
|
||||||
#endif
|
|
||||||
return (
|
|
||||||
((value & 0x00000000000000FFull) << 56) |
|
|
||||||
((value & 0x000000000000FF00ull) << 40) |
|
|
||||||
((value & 0x0000000000FF0000ull) << 24) |
|
|
||||||
((value & 0x00000000FF000000ull) << 8) |
|
|
||||||
((value & 0x000000FF00000000ull) >> 8) |
|
|
||||||
((value & 0x0000FF0000000000ull) >> 24) |
|
|
||||||
((value & 0x00FF000000000000ull) >> 40) |
|
|
||||||
((value & 0xFF00000000000000ull) >> 56)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t endianReadBE16(const uint8_t *buf, size_t offset) {
|
|
||||||
return endianBigToHost16(
|
|
||||||
(uint16_t)((uint16_t)buf[offset] << 8 | (uint16_t)buf[offset + 1])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t endianReadBE32(const uint8_t *buf, size_t offset) {
|
|
||||||
return endianBigToHost32(
|
|
||||||
((uint32_t)buf[offset ] << 24) |
|
|
||||||
((uint32_t)buf[offset + 1] << 16) |
|
|
||||||
((uint32_t)buf[offset + 2] << 8) |
|
|
||||||
((uint32_t)buf[offset + 3] )
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t endianReadLE16(const uint8_t *buf, size_t offset) {
|
|
||||||
return endianLittleToHost16(
|
|
||||||
(uint16_t)((uint16_t)buf[offset + 1] << 8 | (uint16_t)buf[offset])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t endianReadLE32(const uint8_t *buf, size_t offset) {
|
|
||||||
return endianLittleToHost32(
|
|
||||||
((uint32_t)buf[offset + 3] << 24) |
|
|
||||||
((uint32_t)buf[offset + 2] << 16) |
|
|
||||||
((uint32_t)buf[offset + 1] << 8) |
|
|
||||||
((uint32_t)buf[offset ] )
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
+2
-56
@@ -46,62 +46,8 @@ uint64_t endianLittleToHost64(uint64_t value);
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts a float from little-endian to host byte order.
|
* Converts a float from little-endian to host byte order.
|
||||||
*
|
*
|
||||||
* @param value The little-endian value to convert.
|
* @param value The little-endian value to convert.
|
||||||
* @return The value in host byte order.
|
* @return The value in host byte order.
|
||||||
*/
|
*/
|
||||||
float_t endianLittleToHostFloat(float_t value);
|
float_t endianLittleToHostFloat(float_t value);
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a 16-bit integer from big-endian (network byte order) to host
|
|
||||||
* byte order. Use this for binary data received over network protocols.
|
|
||||||
*
|
|
||||||
* @param value The big-endian value to convert.
|
|
||||||
* @return The value in host byte order.
|
|
||||||
*/
|
|
||||||
uint16_t endianBigToHost16(uint16_t value);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a 32-bit integer from big-endian (network byte order) to host
|
|
||||||
* byte order.
|
|
||||||
*
|
|
||||||
* @param value The big-endian value to convert.
|
|
||||||
* @return The value in host byte order.
|
|
||||||
*/
|
|
||||||
uint32_t endianBigToHost32(uint32_t value);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a 64-bit integer from big-endian (network byte order) to host
|
|
||||||
* byte order.
|
|
||||||
*
|
|
||||||
* @param value The big-endian value to convert.
|
|
||||||
* @return The value in host byte order.
|
|
||||||
*/
|
|
||||||
uint64_t endianBigToHost64(uint64_t value);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads a big-endian uint16 from a byte buffer at the given offset. Safe on
|
|
||||||
* all alignment and endianness combinations. Suitable for binary HTTP response
|
|
||||||
* body parsing where the format specifies network (big-endian) byte order.
|
|
||||||
*
|
|
||||||
* @param buf Source byte buffer.
|
|
||||||
* @param offset Byte offset within buf.
|
|
||||||
* @return Host-order uint16.
|
|
||||||
*/
|
|
||||||
uint16_t endianReadBE16(const uint8_t *buf, size_t offset);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads a big-endian uint32 from a byte buffer at the given offset.
|
|
||||||
*/
|
|
||||||
uint32_t endianReadBE32(const uint8_t *buf, size_t offset);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads a little-endian uint16 from a byte buffer at the given offset. Use
|
|
||||||
* for binary payloads that specify little-endian byte order.
|
|
||||||
*/
|
|
||||||
uint16_t endianReadLE16(const uint8_t *buf, size_t offset);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads a little-endian uint32 from a byte buffer at the given offset.
|
|
||||||
*/
|
|
||||||
uint32_t endianReadLE32(const uint8_t *buf, size_t offset);
|
|
||||||
@@ -6,5 +6,4 @@
|
|||||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||||
PUBLIC
|
PUBLIC
|
||||||
networkdolphin.c
|
networkdolphin.c
|
||||||
networksocketdolphin.c
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,162 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "network/networksocket.h"
|
|
||||||
#include "assert/assert.h"
|
|
||||||
#include <network.h>
|
|
||||||
#include <netdb.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
/** Poll timeout in microseconds — short enough to check the stop flag often. */
|
|
||||||
#define NETWORKSOCKET_DOLPHIN_RECV_TIMEOUT_US 100000
|
|
||||||
|
|
||||||
errorret_t networksocketDolphinInit(networksocket_t *socket) {
|
|
||||||
socket->platform.fd = -1;
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t networksocketDolphinConnect(networksocket_t *nsocket) {
|
|
||||||
struct hostent *he;
|
|
||||||
struct sockaddr_in addr;
|
|
||||||
s32 fd;
|
|
||||||
s32 ret;
|
|
||||||
|
|
||||||
he = net_gethostbyname(nsocket->host);
|
|
||||||
if (!he) {
|
|
||||||
errorThrowState(
|
|
||||||
&nsocket->errorState,
|
|
||||||
"net_gethostbyname(%s) failed",
|
|
||||||
nsocket->host
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fd = net_socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
|
|
||||||
if(fd == INVALID_SOCKET) {
|
|
||||||
errorThrowState(
|
|
||||||
&nsocket->errorState,
|
|
||||||
"net_socket failed with invalid socket"
|
|
||||||
);
|
|
||||||
} else if (fd < 0) {
|
|
||||||
errorThrowState(
|
|
||||||
&nsocket->errorState,
|
|
||||||
"net_socket failed: %d",
|
|
||||||
(int)fd
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
memset(&addr, 0, sizeof(addr));
|
|
||||||
addr.sin_family = AF_INET;
|
|
||||||
addr.sin_port = htons(nsocket->port);
|
|
||||||
memcpy(&addr.sin_addr, he->h_addr_list[0], (size_t)he->h_length);
|
|
||||||
|
|
||||||
ret = net_connect(fd, (struct sockaddr *)&addr, sizeof(addr));
|
|
||||||
if (ret < 0) {
|
|
||||||
net_close(fd);
|
|
||||||
errorThrowState(
|
|
||||||
&nsocket->errorState,
|
|
||||||
"net_connect to %s:%u failed: %d",
|
|
||||||
nsocket->host,
|
|
||||||
(unsigned int)nsocket->port,
|
|
||||||
(int)ret
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
nsocket->platform.fd = fd;
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t networksocketDolphinSend(
|
|
||||||
networksocket_t *socket,
|
|
||||||
const uint8_t *data,
|
|
||||||
size_t len
|
|
||||||
) {
|
|
||||||
size_t total = 0;
|
|
||||||
s32 sent;
|
|
||||||
|
|
||||||
while (total < len) {
|
|
||||||
sent = net_send(
|
|
||||||
socket->platform.fd,
|
|
||||||
(const void *)(data + total),
|
|
||||||
len - total,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
if (sent <= 0) {
|
|
||||||
errorThrowState(
|
|
||||||
&socket->errorState,
|
|
||||||
"net_send failed: %d",
|
|
||||||
(int)sent
|
|
||||||
);
|
|
||||||
}
|
|
||||||
total += (size_t)sent;
|
|
||||||
}
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t networksocketDolphinRecv(
|
|
||||||
networksocket_t *socket,
|
|
||||||
uint8_t *buf,
|
|
||||||
size_t maxLen,
|
|
||||||
size_t *outLen
|
|
||||||
) {
|
|
||||||
struct timeval tv;
|
|
||||||
fd_set readset;
|
|
||||||
s32 ready;
|
|
||||||
s32 received;
|
|
||||||
|
|
||||||
tv.tv_sec = 0;
|
|
||||||
tv.tv_usec = NETWORKSOCKET_DOLPHIN_RECV_TIMEOUT_US;
|
|
||||||
|
|
||||||
FD_ZERO(&readset);
|
|
||||||
FD_SET(socket->platform.fd, &readset);
|
|
||||||
|
|
||||||
ready = net_select(socket->platform.fd + 1, &readset, NULL, NULL, &tv);
|
|
||||||
if (ready < 0) {
|
|
||||||
errorThrowState(
|
|
||||||
&socket->errorState,
|
|
||||||
"net_select failed: %d",
|
|
||||||
(int)ready
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ready == 0) {
|
|
||||||
*outLen = 0;
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
received = net_recv(socket->platform.fd, buf, maxLen, 0);
|
|
||||||
if (received < 0) {
|
|
||||||
errorThrowState(
|
|
||||||
&socket->errorState,
|
|
||||||
"net_recv failed: %d",
|
|
||||||
(int)received
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (received == 0) {
|
|
||||||
*outLen = NETWORKSOCKET_RECV_CLOSED;
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
*outLen = (size_t)received;
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t networksocketDolphinDisconnect(networksocket_t *socket) {
|
|
||||||
if (socket->platform.fd >= 0) {
|
|
||||||
net_close(socket->platform.fd);
|
|
||||||
socket->platform.fd = -1;
|
|
||||||
}
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t networksocketDolphinDispose(networksocket_t *socket) {
|
|
||||||
if (socket->platform.fd >= 0) {
|
|
||||||
net_close(socket->platform.fd);
|
|
||||||
socket->platform.fd = -1;
|
|
||||||
}
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "dusk.h"
|
|
||||||
#include "error/error.h"
|
|
||||||
#include <network.h>
|
|
||||||
|
|
||||||
typedef struct networksocket_s networksocket_t;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
s32 fd;
|
|
||||||
} networksocketdolphin_t;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes platform state (sets fd to -1).
|
|
||||||
*/
|
|
||||||
errorret_t networksocketDolphinInit(networksocket_t *socket);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolves host/port via net_gethostbyname and establishes a TCP connection.
|
|
||||||
*/
|
|
||||||
errorret_t networksocketDolphinConnect(networksocket_t *socket);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes all bytes in data to the socket. Loops on partial sends.
|
|
||||||
*/
|
|
||||||
errorret_t networksocketDolphinSend(
|
|
||||||
networksocket_t *socket,
|
|
||||||
const uint8_t *data,
|
|
||||||
size_t len
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Polls for incoming data with a short timeout (via net_select) so the
|
|
||||||
* socket thread can periodically check its stop flag. Sets *outLen to bytes
|
|
||||||
* received, 0 on timeout, or NETWORKSOCKET_RECV_CLOSED when peer closed.
|
|
||||||
*/
|
|
||||||
errorret_t networksocketDolphinRecv(
|
|
||||||
networksocket_t *socket,
|
|
||||||
uint8_t *buf,
|
|
||||||
size_t maxLen,
|
|
||||||
size_t *outLen
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Closes the socket file descriptor.
|
|
||||||
*/
|
|
||||||
errorret_t networksocketDolphinDisconnect(networksocket_t *socket);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Closes the file descriptor if still open (idempotent).
|
|
||||||
*/
|
|
||||||
errorret_t networksocketDolphinDispose(networksocket_t *socket);
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "networksocketdolphin.h"
|
|
||||||
|
|
||||||
#define networksocketPlatformInit networksocketDolphinInit
|
|
||||||
#define networksocketPlatformConnect networksocketDolphinConnect
|
|
||||||
#define networksocketPlatformSend networksocketDolphinSend
|
|
||||||
#define networksocketPlatformRecv networksocketDolphinRecv
|
|
||||||
#define networksocketPlatformDisconnect networksocketDolphinDisconnect
|
|
||||||
#define networksocketPlatformDispose networksocketDolphinDispose
|
|
||||||
|
|
||||||
typedef networksocketdolphin_t networksocketplatform_t;
|
|
||||||
@@ -6,5 +6,5 @@
|
|||||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||||
PUBLIC
|
PUBLIC
|
||||||
networklinux.c
|
networklinux.c
|
||||||
networksocketlinux.c
|
networksocketclientlinux.c
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "network/networksocketclient.h"
|
||||||
|
#include "assert/assert.h"
|
||||||
|
#include "util/memory.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <netdb.h>
|
||||||
|
|
||||||
|
void networkSocketClientLinuxInit(
|
||||||
|
networksocketclient_t *client,
|
||||||
|
const char_t *host,
|
||||||
|
uint16_t port,
|
||||||
|
void *user,
|
||||||
|
void (*onConnect)(void *user),
|
||||||
|
void (*onError)(errorret_t error, void *user),
|
||||||
|
void (*onDisconnect)(void *user)
|
||||||
|
) {
|
||||||
|
assertTrue(client->user == user, "Net client init mismatch?");
|
||||||
|
assertTrue(client->onConnect == onConnect, "Net client init mismatch?");
|
||||||
|
assertTrue(client->onError == onError, "Net client init mismatch?");
|
||||||
|
assertTrue(client->onDisconnect == onDisconnect, "Net client init mismatch?");
|
||||||
|
|
||||||
|
// Create the thread.
|
||||||
|
threadInit(&client->platform.thread, networkSocketClientLinuxThread);
|
||||||
|
client->platform.thread.data = client;
|
||||||
|
|
||||||
|
threadStart(&client->platform.thread);
|
||||||
|
}
|
||||||
|
|
||||||
|
void networkSocketClientLinuxThread(thread_t *thread) {
|
||||||
|
assertNotNull(thread, "Thread cannot be NULL.");
|
||||||
|
assertNotNull(thread->data, "Thread data cannot be NULL.");
|
||||||
|
|
||||||
|
networksocketclient_t *client = (networksocketclient_t *)thread->data;
|
||||||
|
|
||||||
|
// Setup socket FD
|
||||||
|
client->platform.sockfd = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
|
if(client->platform.sockfd < 0) {
|
||||||
|
errorret_t ret = errorThrowImpl(
|
||||||
|
&client->errorState, ERROR_NOT_OK,
|
||||||
|
__FILE__, __func__, __LINE__,
|
||||||
|
"Failed to create socket."
|
||||||
|
);
|
||||||
|
client->onError(ret, client->user);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure server address.
|
||||||
|
char_t *host = "google.com";
|
||||||
|
int port = 443;
|
||||||
|
|
||||||
|
struct sockaddr_in serverAddr;
|
||||||
|
serverAddr.sin_family = AF_INET;
|
||||||
|
serverAddr.sin_port = htons(port);
|
||||||
|
struct hostent *server = gethostbyname(host);
|
||||||
|
if(server == NULL) {
|
||||||
|
errorret_t ret = errorThrowImpl(
|
||||||
|
&client->errorState, ERROR_NOT_OK,
|
||||||
|
__FILE__, __func__, __LINE__,
|
||||||
|
"Failed to resolve host."
|
||||||
|
);
|
||||||
|
client->onError(ret, client->user);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
memoryCopy(&serverAddr.sin_addr.s_addr, server->h_addr, server->h_length);
|
||||||
|
|
||||||
|
// Connect to server.
|
||||||
|
if(connect(
|
||||||
|
client->platform.sockfd,
|
||||||
|
(struct sockaddr *)&serverAddr,
|
||||||
|
sizeof(serverAddr)
|
||||||
|
) < 0) {
|
||||||
|
errorret_t ret = errorThrowImpl(
|
||||||
|
&client->errorState, ERROR_NOT_OK,
|
||||||
|
__FILE__, __func__, __LINE__,
|
||||||
|
"Failed to connect to server."
|
||||||
|
);
|
||||||
|
client->onError(ret, client->user);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point we are connected.
|
||||||
|
client->onConnect(client->user);
|
||||||
|
|
||||||
|
// Hangup for now.
|
||||||
|
close(client->platform.sockfd);
|
||||||
|
client->onDisconnect(client->user);
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "error/error.h"
|
||||||
|
#include "thread/thread.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int sockfd;
|
||||||
|
thread_t thread;
|
||||||
|
} networksocketclientlinux_t;
|
||||||
|
|
||||||
|
typedef struct networksocketclient_s networksocketclient_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refer to networkSocketClientInit for documentation.
|
||||||
|
*/
|
||||||
|
void networkSocketClientLinuxInit(
|
||||||
|
networksocketclient_t *client,
|
||||||
|
const char_t *host,
|
||||||
|
uint16_t port,
|
||||||
|
void *user,
|
||||||
|
void (*onConnect)(void *user),
|
||||||
|
void (*onError)(errorret_t error, void *user),
|
||||||
|
void (*onDisconnect)(void *user)
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Async thread for handling the socket connection.
|
||||||
|
*
|
||||||
|
* @param thread Thread structure passed.
|
||||||
|
*/
|
||||||
|
void networkSocketClientLinuxThread(thread_t *thread);
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "networksocketclientlinux.h"
|
||||||
|
|
||||||
|
#define networkSocketClientPlatformInit networkSocketClientLinuxInit
|
||||||
|
|
||||||
|
typedef networksocketclientlinux_t networksocketclientplatform_t;
|
||||||
@@ -1,142 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "network/networksocket.h"
|
|
||||||
#include "assert/assert.h"
|
|
||||||
|
|
||||||
/** Recv timeout: short enough to check the thread stop flag regularly. */
|
|
||||||
#define NETWORKSOCKET_LINUX_RECV_TIMEOUT_US 100000
|
|
||||||
|
|
||||||
errorret_t networksocketLinuxInit(networksocket_t *socket) {
|
|
||||||
socket->platform.fd = -1;
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t networksocketLinuxConnect(networksocket_t *nsocket) {
|
|
||||||
struct addrinfo hints;
|
|
||||||
struct addrinfo *result;
|
|
||||||
struct addrinfo *rp;
|
|
||||||
struct timeval tv;
|
|
||||||
char_t portStr[8];
|
|
||||||
int ret;
|
|
||||||
int fd;
|
|
||||||
|
|
||||||
memset(&hints, 0, sizeof(hints));
|
|
||||||
hints.ai_family = AF_UNSPEC;
|
|
||||||
hints.ai_socktype = SOCK_STREAM;
|
|
||||||
|
|
||||||
snprintf(portStr, sizeof(portStr), "%u", (unsigned int)nsocket->port);
|
|
||||||
|
|
||||||
ret = getaddrinfo(nsocket->host, portStr, &hints, &result);
|
|
||||||
if (ret != 0) {
|
|
||||||
errorThrowState(
|
|
||||||
&nsocket->errorState,
|
|
||||||
"getaddrinfo(%s): %s",
|
|
||||||
nsocket->host,
|
|
||||||
gai_strerror(ret)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fd = -1;
|
|
||||||
for (rp = result; rp != NULL; rp = rp->ai_next) {
|
|
||||||
fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
|
|
||||||
if (fd == -1) continue;
|
|
||||||
|
|
||||||
if (connect(fd, rp->ai_addr, rp->ai_addrlen) == 0) break;
|
|
||||||
|
|
||||||
close(fd);
|
|
||||||
fd = -1;
|
|
||||||
}
|
|
||||||
freeaddrinfo(result);
|
|
||||||
|
|
||||||
if (fd == -1) {
|
|
||||||
errorThrowState(
|
|
||||||
&nsocket->errorState,
|
|
||||||
"Failed to connect to %s:%u",
|
|
||||||
nsocket->host,
|
|
||||||
(unsigned int)nsocket->port
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
tv.tv_sec = 0;
|
|
||||||
tv.tv_usec = NETWORKSOCKET_LINUX_RECV_TIMEOUT_US;
|
|
||||||
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
|
|
||||||
|
|
||||||
nsocket->platform.fd = fd;
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t networksocketLinuxSend(
|
|
||||||
networksocket_t *socket,
|
|
||||||
const uint8_t *data,
|
|
||||||
size_t len
|
|
||||||
) {
|
|
||||||
ssize_t sent;
|
|
||||||
size_t total;
|
|
||||||
|
|
||||||
total = 0;
|
|
||||||
while (total < len) {
|
|
||||||
sent = send(socket->platform.fd, data + total, len - total, MSG_NOSIGNAL);
|
|
||||||
if (sent <= 0) {
|
|
||||||
errorThrowState(
|
|
||||||
&socket->errorState,
|
|
||||||
"send failed: %s",
|
|
||||||
strerror(errno)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
total += (size_t)sent;
|
|
||||||
}
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t networksocketLinuxRecv(
|
|
||||||
networksocket_t *socket,
|
|
||||||
uint8_t *buf,
|
|
||||||
size_t maxLen,
|
|
||||||
size_t *outLen
|
|
||||||
) {
|
|
||||||
ssize_t received;
|
|
||||||
|
|
||||||
received = recv(socket->platform.fd, buf, maxLen, 0);
|
|
||||||
|
|
||||||
if (received < 0) {
|
|
||||||
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
|
||||||
*outLen = 0;
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
errorThrowState(
|
|
||||||
&socket->errorState,
|
|
||||||
"recv failed: %s",
|
|
||||||
strerror(errno)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (received == 0) {
|
|
||||||
*outLen = NETWORKSOCKET_RECV_CLOSED;
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
*outLen = (size_t)received;
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t networksocketLinuxDisconnect(networksocket_t *socket) {
|
|
||||||
if (socket->platform.fd != -1) {
|
|
||||||
shutdown(socket->platform.fd, SHUT_RDWR);
|
|
||||||
close(socket->platform.fd);
|
|
||||||
socket->platform.fd = -1;
|
|
||||||
}
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
|
|
||||||
errorret_t networksocketLinuxDispose(networksocket_t *socket) {
|
|
||||||
if (socket->platform.fd != -1) {
|
|
||||||
close(socket->platform.fd);
|
|
||||||
socket->platform.fd = -1;
|
|
||||||
}
|
|
||||||
errorOk();
|
|
||||||
}
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "dusk.h"
|
|
||||||
#include "error/error.h"
|
|
||||||
#include <sys/socket.h>
|
|
||||||
#include <netdb.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <sys/time.h>
|
|
||||||
|
|
||||||
typedef struct networksocket_s networksocket_t;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
int fd;
|
|
||||||
} networksocketlinux_t;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes platform state for the socket (sets fd to -1).
|
|
||||||
*/
|
|
||||||
errorret_t networksocketLinuxInit(networksocket_t *socket);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolves host/port and establishes a TCP connection. Sets SO_RCVTIMEO
|
|
||||||
* on the resulting file descriptor so recv calls return periodically even
|
|
||||||
* when no data arrives, allowing the thread to check its stop flag.
|
|
||||||
*/
|
|
||||||
errorret_t networksocketLinuxConnect(networksocket_t *socket);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes all bytes in data to the socket. Loops on partial sends.
|
|
||||||
*/
|
|
||||||
errorret_t networksocketLinuxSend(
|
|
||||||
networksocket_t *socket,
|
|
||||||
const uint8_t *data,
|
|
||||||
size_t len
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempts to read up to maxLen bytes into buf. Sets *outLen to the number
|
|
||||||
* of bytes received. Returns 0 in *outLen on timeout (EAGAIN/EWOULDBLOCK).
|
|
||||||
* Returns NETWORKSOCKET_RECV_CLOSED in *outLen when the peer closed the
|
|
||||||
* connection.
|
|
||||||
*/
|
|
||||||
errorret_t networksocketLinuxRecv(
|
|
||||||
networksocket_t *socket,
|
|
||||||
uint8_t *buf,
|
|
||||||
size_t maxLen,
|
|
||||||
size_t *outLen
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shuts down and closes the socket file descriptor.
|
|
||||||
*/
|
|
||||||
errorret_t networksocketLinuxDisconnect(networksocket_t *socket);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Closes the file descriptor if still open (idempotent).
|
|
||||||
*/
|
|
||||||
errorret_t networksocketLinuxDispose(networksocket_t *socket);
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2026 Dominic Masters
|
|
||||||
*
|
|
||||||
* This software is released under the MIT License.
|
|
||||||
* https://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include "networksocketlinux.h"
|
|
||||||
|
|
||||||
#define networksocketPlatformInit networksocketLinuxInit
|
|
||||||
#define networksocketPlatformConnect networksocketLinuxConnect
|
|
||||||
#define networksocketPlatformSend networksocketLinuxSend
|
|
||||||
#define networksocketPlatformRecv networksocketLinuxRecv
|
|
||||||
#define networksocketPlatformDisconnect networksocketLinuxDisconnect
|
|
||||||
#define networksocketPlatformDispose networksocketLinuxDispose
|
|
||||||
|
|
||||||
typedef networksocketlinux_t networksocketplatform_t;
|
|
||||||
Reference in New Issue
Block a user