From e51cdc8992958f3246b749d2ebcc9616e7fa4df2 Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Wed, 15 Apr 2026 15:50:43 -0500 Subject: [PATCH] PSP net code first pass --- cmake/targets/psp.cmake | 6 + src/dusk/engine/engine.c | 27 +- src/dusk/network/networkmanager.c | 12 + src/dusk/network/networkmanager.h | 39 +++ src/dusklinux/network/networklinux.c | 14 + src/dusklinux/network/networklinux.h | 24 +- src/dusklinux/network/networkplatform.h | 6 +- src/duskpsp/CMakeLists.txt | 3 +- src/duskpsp/network/CMakeLists.txt | 9 + src/duskpsp/network/networkplatform.h | 16 + src/duskpsp/network/networkpsp.c | 390 ++++++++++++++++++++++++ src/duskpsp/network/networkpsp.h | 81 +++++ 12 files changed, 611 insertions(+), 16 deletions(-) create mode 100644 src/duskpsp/network/CMakeLists.txt create mode 100644 src/duskpsp/network/networkplatform.h create mode 100644 src/duskpsp/network/networkpsp.c create mode 100644 src/duskpsp/network/networkpsp.h diff --git a/cmake/targets/psp.cmake b/cmake/targets/psp.cmake index 4f3a6b03..6beec75e 100644 --- a/cmake/targets/psp.cmake +++ b/cmake/targets/psp.cmake @@ -24,6 +24,11 @@ target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC pspvfpu pspvram psphprm + pspnet + pspnet_inet + pspnet_apctl + psphttp + pspssl ) target_include_directories(${DUSK_LIBRARY_TARGET_NAME} PRIVATE @@ -39,6 +44,7 @@ target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC DUSK_OPENGL_LEGACY DUSK_DISPLAY_WIDTH=480 DUSK_DISPLAY_HEIGHT=272 + THREAD_PTHREAD=1 ) # Postbuild, create .pbp file for PSP. diff --git a/src/dusk/engine/engine.c b/src/dusk/engine/engine.c index 1b05b03a..cfe887e2 100644 --- a/src/dusk/engine/engine.c +++ b/src/dusk/engine/engine.c @@ -28,12 +28,9 @@ engine_t ENGINE; -/* Kept module-level only because engineUpdate needs them for the reset. */ static entityid_t phBoxEnt; static componentid_t phBoxPhys; -/* ---- test network callbacks ---- */ - static void onTestResponse( const uint16_t status, const char_t *body, @@ -48,6 +45,21 @@ static void onTestError(errorret_t error, void *user) { printf("HTTP error: %s\n", error.state->message); } +static void onNetworkConnected(void *user) { + printf("Network connected.\n"); + networkHTTPRequest( + "http://10.0.0.94:3000/test", + NETWORK_HTTP_REQUEST_METHOD_GET, + NULL, NULL, 0, NULL, + onTestResponse, + onTestError + ); +} + +static void onNetworkFailed(errorret_t error, void *user) { + printf("Network connection failed: %s\n", error.state->message); +} + errorret_t engineInit(const int32_t argc, const char_t **argv) { memoryZero(&ENGINE, sizeof(engine_t)); ENGINE.running = true; @@ -66,16 +78,9 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) { entityManagerInit(); physicsManagerInit(); errorChain(networkManagerInit()); + networkManagerRequestConnection(onNetworkConnected, onNetworkFailed, NULL); errorChain(gameInit()); - networkHTTPRequest( - "http://localhost:3000/test", - NETWORK_HTTP_REQUEST_METHOD_GET, - NULL, NULL, 0, NULL, - onTestResponse, - onTestError - ); - /* ---- Camera ---- */ entityid_t cam = entityManagerAdd(); componentid_t camPos = entityAddComponent(cam, COMPONENT_TYPE_POSITION); diff --git a/src/dusk/network/networkmanager.c b/src/dusk/network/networkmanager.c index 3dad0bb0..5405027d 100644 --- a/src/dusk/network/networkmanager.c +++ b/src/dusk/network/networkmanager.c @@ -22,6 +22,18 @@ errorret_t networkManagerUpdate() { errorOk(); } +bool_t networkManagerIsConnected() { + return networkPlatformIsConnected(); +} + +void networkManagerRequestConnection( + void (*onConnected)(void *user), + void (*onFailed)(errorret_t error, void *user), + void *user +) { + networkPlatformRequestConnection(onConnected, onFailed, user); +} + errorret_t networkManagerDispose() { errorChain(networkPlatformDispose()); errorOk(); diff --git a/src/dusk/network/networkmanager.h b/src/dusk/network/networkmanager.h index 419950c1..5a2ff03d 100644 --- a/src/dusk/network/networkmanager.h +++ b/src/dusk/network/networkmanager.h @@ -29,6 +29,45 @@ errorret_t networkManagerInit(); */ errorret_t networkManagerUpdate(); +/** + * Returns true if the system is connected to AN network, this doesn't mean that + * requests will succeed, doesn't mean there's internet, just that we could + * possibly make network requests. If this is false, this usually means + * something like; + * - A network cable is not connnected + * - No Wi-Fi Connection has been established + * - No IP Address has been assigned + * + * That kinda stuff. + * + * In future I will probably have REASONS for why it's not connected, for + * example; + * - On PSP you need to "request" network access + * - On GameCube, network settings need to be defined, including DHCP, etc. + * - On Windows, this may require additional permissions + * + * @return True if some network connection is detected. + */ +bool_t networkManagerIsConnected(); + +/** + * See networkManagerIsConnected for a bit more info, but this is for some + * platforms (mainly PSP) to request the system to start doing networking. + * + * You should only call this once and assume that it is "pending" until either + * onComplete or onFailed is invoked. If you call this twice it is undefined + * behavior. + * + * @param onConnected Callback to invoke when the network is connected. + * @param onFailed Callback to invoke if the network connection fails. + * @param user User data to pass to the callbacks. + */ +void networkManagerRequestConnection( + void (*onConnected)(void *user), + void (*onFailed)(errorret_t error, void *user), + void *user +); + /** * Disposes of the network manager. This will NOT disconnect from the network. * diff --git a/src/dusklinux/network/networklinux.c b/src/dusklinux/network/networklinux.c index 21c17edb..7349518e 100644 --- a/src/dusklinux/network/networklinux.c +++ b/src/dusklinux/network/networklinux.c @@ -190,6 +190,20 @@ errorret_t networkLinuxUpdate() { errorOk(); } +bool_t networkLinuxIsConnected() { + return true; +} + +void networkLinuxRequestConnection( + void (*onConnected)(void *user), + void (*onFailed)(errorret_t error, void *user), + void *user +) { + assertNotNull(onConnected, "onConnected must not be NULL"); + assertNotNull(onFailed, "onFailed must not be NULL"); + onConnected(user); +} + errorret_t networkLinuxDispose() { curl_global_cleanup(); threadMutexDispose(&NETWORK_LINUX.resultsMutex); diff --git a/src/dusklinux/network/networklinux.h b/src/dusklinux/network/networklinux.h index cea4fe9e..18e44327 100644 --- a/src/dusklinux/network/networklinux.h +++ b/src/dusklinux/network/networklinux.h @@ -59,14 +59,34 @@ errorret_t networkLinuxInit(); /** * Updates the network manager, called once per frame to handle completed * HTTP requests. - * + * * @return Any error that occurs. */ errorret_t networkLinuxUpdate(); +/** + * Returns true — Linux uses the OS network stack which is always available. + * + * @return true + */ +bool_t networkLinuxIsConnected(); + +/** + * No-op — Linux connection is managed by the OS. Calls onConnected immediately. + * + * @param onConnected Callback invoked immediately. + * @param onFailed Unused. + * @param user Passed to onConnected. + */ +void networkLinuxRequestConnection( + void (*onConnected)(void *user), + void (*onFailed)(errorret_t error, void *user), + void *user +); + /** * Disposes the network manager. - * + * * @return Any error that occurs. */ errorret_t networkLinuxDispose(); diff --git a/src/dusklinux/network/networkplatform.h b/src/dusklinux/network/networkplatform.h index 15645b10..5c1ad617 100644 --- a/src/dusklinux/network/networkplatform.h +++ b/src/dusklinux/network/networkplatform.h @@ -8,7 +8,9 @@ #pragma once #include "networklinux.h" -#define networkPlatformInit networkLinuxInit -#define networkPlatformUpdate networkLinuxUpdate +#define networkPlatformInit networkLinuxInit +#define networkPlatformUpdate networkLinuxUpdate #define networkPlatformDispose networkLinuxDispose +#define networkPlatformIsConnected networkLinuxIsConnected +#define networkPlatformRequestConnection networkLinuxRequestConnection #define networkPlatformHTTPRequest networkLinuxHTTPRequest \ No newline at end of file diff --git a/src/duskpsp/CMakeLists.txt b/src/duskpsp/CMakeLists.txt index 793ece42..a7021af8 100644 --- a/src/duskpsp/CMakeLists.txt +++ b/src/duskpsp/CMakeLists.txt @@ -17,4 +17,5 @@ target_sources(${DUSK_BINARY_TARGET_NAME} # Subdirs add_subdirectory(asset) add_subdirectory(input) -add_subdirectory(log) \ No newline at end of file +add_subdirectory(log) +add_subdirectory(network) \ No newline at end of file diff --git a/src/duskpsp/network/CMakeLists.txt b/src/duskpsp/network/CMakeLists.txt new file mode 100644 index 00000000..c302baf6 --- /dev/null +++ b/src/duskpsp/network/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright (c) 2026 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +target_sources(${DUSK_LIBRARY_TARGET_NAME} + PUBLIC + networkpsp.c +) diff --git a/src/duskpsp/network/networkplatform.h b/src/duskpsp/network/networkplatform.h new file mode 100644 index 00000000..ddd0a061 --- /dev/null +++ b/src/duskpsp/network/networkplatform.h @@ -0,0 +1,16 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "networkpsp.h" + +#define networkPlatformInit networkPspInit +#define networkPlatformUpdate networkPspUpdate +#define networkPlatformDispose networkPspDispose +#define networkPlatformIsConnected networkPspIsConnected +#define networkPlatformRequestConnection networkPspRequestConnection +#define networkPlatformHTTPRequest networkPspHTTPRequest diff --git a/src/duskpsp/network/networkpsp.c b/src/duskpsp/network/networkpsp.c new file mode 100644 index 00000000..54f22719 --- /dev/null +++ b/src/duskpsp/network/networkpsp.c @@ -0,0 +1,390 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "networkpsp.h" +#include "util/memory.h" +#include "util/string.h" +#include "assert/assert.h" +#include +#include +#include +#include + +static networkpsp_t NETWORK_PSP; + +/* ---- HTTP thread ---- */ + +static int networkPspMapMethod(const networkhttprequestmethod_t method) { + switch(method) { + case NETWORK_HTTP_REQUEST_METHOD_POST: return PSP_HTTP_METHOD_POST; + case NETWORK_HTTP_REQUEST_METHOD_HEAD: return PSP_HTTP_METHOD_HEAD; + case NETWORK_HTTP_REQUEST_METHOD_GET: + default: + return PSP_HTTP_METHOD_GET; + } +} + +static void networkPspHTTPThread(thread_t *thread) { + assertNotNull(thread, "Thread must not be NULL"); + assertNotNull(thread->data, "Thread data must not be NULL"); + + networkhttppendingitem_t *item = (networkhttppendingitem_t *)thread->data; + int templateId = -1; + int connId = -1; + int reqId = -1; + + templateId = sceHttpCreateTemplate(NETWORK_PSP_AGENT, PSP_HTTP_VERSION_1_1, 1); + if(templateId < 0) { + stringCopy(item->errorMessage, "sceHttpCreateTemplate failed", + NETWORK_ERROR_MESSAGE_MAX - 1); + item->isError = true; + goto done; + } + + connId = sceHttpCreateConnectionWithURL(templateId, item->url, 0); + if(connId < 0) { + stringCopy(item->errorMessage, "sceHttpCreateConnectionWithURL failed", + NETWORK_ERROR_MESSAGE_MAX - 1); + item->isError = true; + goto done; + } + + unsigned int contentLength = item->hasBody ? (unsigned int)strlen(item->body) : 0; + reqId = sceHttpCreateRequestWithURL( + connId, networkPspMapMethod(item->method), item->url, + (SceULong64)contentLength + ); + if(reqId < 0) { + stringCopy(item->errorMessage, "sceHttpCreateRequestWithURL failed", + NETWORK_ERROR_MESSAGE_MAX - 1); + item->isError = true; + goto done; + } + + for(uint32_t i = 0; i < item->headerCount; i++) { + sceHttpAddExtraHeader(reqId, item->headerKeys[i], item->headerVals[i], 0); + } + + int ret; + if(item->hasBody) { + ret = sceHttpSendRequest(reqId, item->body, contentLength); + } else { + ret = sceHttpSendRequest(reqId, NULL, 0); + } + if(ret < 0) { + stringCopy(item->errorMessage, "sceHttpSendRequest failed", + NETWORK_ERROR_MESSAGE_MAX - 1); + item->isError = true; + goto done; + } + + int statusCode = 0; + ret = sceHttpGetStatusCode(reqId, &statusCode); + if(ret < 0) { + stringCopy(item->errorMessage, "sceHttpGetStatusCode failed", + NETWORK_ERROR_MESSAGE_MAX - 1); + item->isError = true; + goto done; + } + item->status = (uint16_t)statusCode; + + /* Read response body in chunks into the fixed buffer. */ + size_t written = 0; + char_t buf[512]; + while(1) { + int read = sceHttpReadData(reqId, buf, sizeof(buf)); + if(read <= 0) break; + + size_t space = NETWORK_HTTP_RESPONSE_MAX - 1 - written; + size_t copy = (size_t)read; + if(copy > space) copy = space; + if(copy > 0) { + memcpy(item->responseBody + written, buf, copy); + written += copy; + item->responseBody[written] = '\0'; + } + if(space == 0) break; + } + + item->isError = false; + +done: + if(reqId >= 0) sceHttpDeleteRequest(reqId); + if(connId >= 0) sceHttpDeleteConnection(connId); + if(templateId >= 0) sceHttpDeleteTemplate(templateId); + + threadMutexLock(&NETWORK_PSP.resultsMutex); + item->resultReady = true; + threadMutexUnlock(&NETWORK_PSP.resultsMutex); +} + +/* ---- Dialog state machine (pumped every frame) ---- */ + +static void networkPspPumpDialog() { + switch(sceUtilityNetconfGetStatus()) { + case PSP_UTILITY_DIALOG_VISIBLE: + sceUtilityNetconfUpdate(1); + break; + + case PSP_UTILITY_DIALOG_QUIT: + sceUtilityNetconfShutdownStart(); + break; + + case PSP_UTILITY_DIALOG_FINISHED: { + int apState = 0; + sceNetApctlGetState(&apState); + if(apState == PSP_NET_APCTL_STATE_GOT_IP) { + sceUtilityLoadNetModule(PSP_NET_MODULE_HTTP); + sceUtilityLoadNetModule(PSP_NET_MODULE_SSL); + sceHttpInit(NETWORK_PSP_HTTP_HEAP_SIZE); + NETWORK_PSP.state = NETWORK_PSP_STATE_CONNECTED; + if(NETWORK_PSP.onConnected) NETWORK_PSP.onConnected(NETWORK_PSP.connectionUser); + } else { + NETWORK_PSP.state = NETWORK_PSP_STATE_FAILED; + if(NETWORK_PSP.onFailed) { + errorstate_t errState; + errState.code = ERROR_NOT_OK; + errState.message = (char_t *)"WiFi connection failed or cancelled"; + errState.lines = (char_t *)""; + errorret_t err; + err.code = ERROR_NOT_OK; + err.state = &errState; + NETWORK_PSP.onFailed(err, NETWORK_PSP.connectionUser); + } + } + break; + } + + default: + break; + } +} + +/* ---- Public API ---- */ + +errorret_t networkPspInit() { + memoryZero(&NETWORK_PSP, sizeof(networkpsp_t)); + threadMutexInit(&NETWORK_PSP.resultsMutex); + NETWORK_PSP.state = NETWORK_PSP_STATE_DISCONNECTED; + + /* Load base net modules (FW 2.00+ style). HTTP/SSL loaded after connection. */ + int ret; + ret = sceUtilityLoadNetModule(PSP_NET_MODULE_COMMON); + if(ret < 0) errorThrow("sceUtilityLoadNetModule(COMMON) failed: 0x%08X", ret); + + ret = sceUtilityLoadNetModule(PSP_NET_MODULE_INET); + if(ret < 0) errorThrow("sceUtilityLoadNetModule(INET) failed: 0x%08X", ret); + + /* Init the network stack. */ + ret = sceNetInit(0x20000, 0x20, 0x1000, 0x20, 0x1000); + if(ret < 0) errorThrow("sceNetInit failed: 0x%08X", ret); + + ret = sceNetInetInit(); + if(ret < 0) errorThrow("sceNetInetInit failed: 0x%08X", ret); + + ret = sceNetApctlInit(0x1800, 0x30); + if(ret < 0) errorThrow("sceNetApctlInit failed: 0x%08X", ret); + + errorOk(); +} + +bool_t networkPspIsConnected() { + return NETWORK_PSP.state == NETWORK_PSP_STATE_CONNECTED; +} + +void networkPspRequestConnection( + void (*onConnected)(void *user), + void (*onFailed)(errorret_t error, void *user), + void *user +) { + assertNotNull(onConnected, "onConnected must not be NULL"); + assertNotNull(onFailed, "onFailed must not be NULL"); + + NETWORK_PSP.onConnected = onConnected; + NETWORK_PSP.onFailed = onFailed; + NETWORK_PSP.connectionUser = user; + NETWORK_PSP.state = NETWORK_PSP_STATE_DIALOG; + + /* Configure and launch the netconf WiFi dialog. + * The dialog is pumped each frame in networkPspUpdate; it must not block. */ + memoryZero(&NETWORK_PSP.dialogAdhoc, sizeof(NETWORK_PSP.dialogAdhoc)); + memoryZero(&NETWORK_PSP.dialogData, sizeof(NETWORK_PSP.dialogData)); + NETWORK_PSP.dialogData.base.size = sizeof(pspUtilityNetconfData); + NETWORK_PSP.dialogData.base.language = PSP_SYSTEMPARAM_LANGUAGE_ENGLISH; + NETWORK_PSP.dialogData.base.buttonSwap = PSP_UTILITY_ACCEPT_CROSS; + NETWORK_PSP.dialogData.base.graphicsThread = 17; + NETWORK_PSP.dialogData.base.accessThread = 19; + NETWORK_PSP.dialogData.base.fontThread = 18; + NETWORK_PSP.dialogData.base.soundThread = 16; + NETWORK_PSP.dialogData.action = PSP_NETCONF_ACTION_CONNECTAP; + NETWORK_PSP.dialogData.adhocparam = &NETWORK_PSP.dialogAdhoc; + + int ret = sceUtilityNetconfInitStart(&NETWORK_PSP.dialogData); + if(ret < 0) { + NETWORK_PSP.state = NETWORK_PSP_STATE_FAILED; + errorstate_t errState; + errState.code = ERROR_NOT_OK; + errState.message = (char_t *)"sceUtilityNetconfInitStart failed"; + errState.lines = (char_t *)""; + errorret_t err; + err.code = ERROR_NOT_OK; + err.state = &errState; + onFailed(err, user); + } +} + +errorret_t networkPspUpdate() { + if(NETWORK_PSP.state == NETWORK_PSP_STATE_DIALOG) { + networkPspPumpDialog(); + errorOk(); + } + + if(NETWORK_PSP.state == NETWORK_PSP_STATE_FAILED) { + errorOk(); + } + + /* Drain completed HTTP requests on the main thread. */ + for(int32_t i = 0; i < NETWORK_HTTP_PENDING_MAX; i++) { + threadMutexLock(&NETWORK_PSP.resultsMutex); + + networkhttppendingitem_t *item = &NETWORK_PSP.requests[i]; + if(!item->used || !item->resultReady) { + threadMutexUnlock(&NETWORK_PSP.resultsMutex); + continue; + } + + bool_t isError = item->isError; + uint16_t status = item->status; + networkhttpcallback_t cb = item->callback; + networkhttperrorcallback_t errCb = item->errorCallback; + void *user = item->user; + const char_t *responseBody = item->responseBody; + const char_t *errorMessage = item->errorMessage; + + threadMutexUnlock(&NETWORK_PSP.resultsMutex); + + if(isError) { + errorstate_t errState; + errState.code = ERROR_NOT_OK; + errState.message = (char_t *)errorMessage; + errState.lines = (char_t *)""; + errorret_t err; + err.code = ERROR_NOT_OK; + err.state = &errState; + errCb(err, user); + } else { + cb(status, responseBody, NULL, 0, user); + } + + threadMutexLock(&NETWORK_PSP.resultsMutex); + item->used = false; + item->resultReady = false; + threadMutexUnlock(&NETWORK_PSP.resultsMutex); + } + errorOk(); +} + +errorret_t networkPspDispose() { + if(NETWORK_PSP.state == NETWORK_PSP_STATE_CONNECTED) { + sceHttpEnd(); + sceUtilityUnloadNetModule(PSP_NET_MODULE_SSL); + sceUtilityUnloadNetModule(PSP_NET_MODULE_HTTP); + } + + sceNetApctlTerm(); + sceNetInetTerm(); + sceNetTerm(); + sceUtilityUnloadNetModule(PSP_NET_MODULE_INET); + sceUtilityUnloadNetModule(PSP_NET_MODULE_COMMON); + + threadMutexDispose(&NETWORK_PSP.resultsMutex); + errorOk(); +} + +void networkPspHTTPRequest( + const char_t *url, + const networkhttprequestmethod_t method, + const char_t *bodyOrNull, + const networkhttpheader_t *headers, + const uint32_t headerCount, + void *user, + networkhttpcallback_t callback, + networkhttperrorcallback_t errorCallback +) { + assertStrLenMin(url, 1, "URL must be non-empty"); + assertStrLenMax(url, NETWORK_HTTP_URL_MAX, "URL exceeds maximum length"); + assertNotNull(callback, "Callback must be non-NULL"); + assertNotNull(errorCallback, "Error callback must be non-NULL"); + assertTrue(headerCount == 0 || headers != NULL, "Headers must not be NULL when headerCount > 0"); + + if(NETWORK_PSP.state != NETWORK_PSP_STATE_CONNECTED) { + errorstate_t errState; + errState.code = ERROR_NOT_OK; + errState.message = (char_t *)"Network not connected"; + errState.lines = (char_t *)""; + errorret_t err; + err.code = ERROR_NOT_OK; + err.state = &errState; + errorCallback(err, user); + return; + } + + threadMutexLock(&NETWORK_PSP.resultsMutex); + + networkhttppendingitem_t *item = NULL; + for(int32_t i = 0; i < NETWORK_HTTP_PENDING_MAX; i++) { + if(!NETWORK_PSP.requests[i].used) { + item = &NETWORK_PSP.requests[i]; + break; + } + } + + if(!item) { + threadMutexUnlock(&NETWORK_PSP.resultsMutex); + errorstate_t errState; + errState.code = ERROR_NOT_OK; + errState.message = (char_t *)"No free HTTP request slots"; + errState.lines = (char_t *)""; + errorret_t err; + err.code = ERROR_NOT_OK; + err.state = &errState; + errorCallback(err, user); + return; + } + + memoryZero(item, sizeof(networkhttppendingitem_t)); + item->used = true; + + threadMutexUnlock(&NETWORK_PSP.resultsMutex); + + stringCopy(item->url, url, NETWORK_HTTP_URL_MAX - 1); + item->method = method; + + if(bodyOrNull != NULL) { + assertStrLenMax(bodyOrNull, NETWORK_HTTP_BODY_MAX, "Body exceeds maximum length"); + stringCopy(item->body, bodyOrNull, NETWORK_HTTP_BODY_MAX - 1); + item->hasBody = true; + } + + uint32_t hdrCount = headerCount; + if(hdrCount > NETWORK_HTTP_HEADER_MAX) hdrCount = NETWORK_HTTP_HEADER_MAX; + item->headerCount = hdrCount; + for(uint32_t i = 0; i < hdrCount; i++) { + assertStrLenMax(headers[i].key, NETWORK_HTTP_HEADER_KEY_MAX, "Header key exceeds maximum length"); + assertStrLenMax(headers[i].value, NETWORK_HTTP_HEADER_VAL_MAX, "Header value exceeds maximum length"); + stringCopy(item->headerKeys[i], headers[i].key, NETWORK_HTTP_HEADER_KEY_MAX - 1); + stringCopy(item->headerVals[i], headers[i].value, NETWORK_HTTP_HEADER_VAL_MAX - 1); + } + + item->callback = callback; + item->errorCallback = errorCallback; + item->user = user; + + threadInit(&item->thread, networkPspHTTPThread); + item->thread.data = item; + threadStartRequest(&item->thread); +} diff --git a/src/duskpsp/network/networkpsp.h b/src/duskpsp/network/networkpsp.h new file mode 100644 index 00000000..439ce58d --- /dev/null +++ b/src/duskpsp/network/networkpsp.h @@ -0,0 +1,81 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "dusk.h" +#include "network/networkhttprequest.h" +#include "thread/thread.h" +#include + +#define NETWORK_HTTP_PENDING_MAX 4 +#define NETWORK_HTTP_URL_MAX 512 +#define NETWORK_HTTP_BODY_MAX 2048 +#define NETWORK_HTTP_RESPONSE_MAX 16384 +#define NETWORK_HTTP_HEADER_MAX 8 +#define NETWORK_HTTP_HEADER_KEY_MAX 64 +#define NETWORK_HTTP_HEADER_VAL_MAX 256 +#define NETWORK_ERROR_MESSAGE_MAX 256 +#define NETWORK_PSP_AGENT "DuskEngine/1.0" +#define NETWORK_PSP_HTTP_HEAP_SIZE 0x25800 + +typedef enum { + NETWORK_PSP_STATE_DISCONNECTED, /* Init done, no connection requested yet */ + NETWORK_PSP_STATE_DIALOG, /* WiFi netconf dialog is active */ + NETWORK_PSP_STATE_CONNECTED, /* AP connected, HTTP ready */ + NETWORK_PSP_STATE_FAILED, /* Connection attempt failed or cancelled */ +} networkpspstate_t; + +typedef struct { + bool_t used; + volatile bool_t resultReady; + bool_t isError; + char_t url[NETWORK_HTTP_URL_MAX]; + networkhttprequestmethod_t method; + char_t body[NETWORK_HTTP_BODY_MAX]; + bool_t hasBody; + char_t headerKeys[NETWORK_HTTP_HEADER_MAX][NETWORK_HTTP_HEADER_KEY_MAX]; + char_t headerVals[NETWORK_HTTP_HEADER_MAX][NETWORK_HTTP_HEADER_VAL_MAX]; + uint32_t headerCount; + networkhttpcallback_t callback; + networkhttperrorcallback_t errorCallback; + void *user; + uint16_t status; + char_t responseBody[NETWORK_HTTP_RESPONSE_MAX]; + char_t errorMessage[NETWORK_ERROR_MESSAGE_MAX]; + thread_t thread; +} networkhttppendingitem_t; + +typedef struct { + networkpspstate_t state; + pspUtilityNetconfData dialogData; + struct pspUtilityNetconfAdhoc dialogAdhoc; + void (*onConnected)(void *user); + void (*onFailed)(errorret_t error, void *user); + void *connectionUser; + networkhttppendingitem_t requests[NETWORK_HTTP_PENDING_MAX]; + threadmutex_t resultsMutex; +} networkpsp_t; + +errorret_t networkPspInit(); +errorret_t networkPspUpdate(); +errorret_t networkPspDispose(); +bool_t networkPspIsConnected(); +void networkPspRequestConnection( + void (*onConnected)(void *user), + void (*onFailed)(errorret_t error, void *user), + void *user +); +void networkPspHTTPRequest( + const char_t *url, + const networkhttprequestmethod_t method, + const char_t *bodyOrNull, + const networkhttpheader_t *headers, + const uint32_t headerCount, + void *user, + networkhttpcallback_t callback, + networkhttperrorcallback_t errorCallback +);