PSP net code first pass

This commit is contained in:
2026-04-15 15:50:43 -05:00
parent 133685ea37
commit e51cdc8992
12 changed files with 611 additions and 16 deletions
+390
View File
@@ -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 <psphttp.h>
#include <pspnet.h>
#include <pspnet_inet.h>
#include <pspnet_apctl.h>
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);
}