diff --git a/.devdeps/wii.cmake b/.devdeps/wii.cmake new file mode 100644 index 0000000..9899793 --- /dev/null +++ b/.devdeps/wii.cmake @@ -0,0 +1,53 @@ +# wii.cmake — CMake toolchain file for Nintendo Wii cross-compilation. +# +# Usage: +# cmake -DCMAKE_TOOLCHAIN_FILE=.devdeps/wii.cmake \ +# -DWOLFRAM_BUILD_WII=ON \ +# -B build-wii +# +# Requires devkitPro with devkitPPC and libogc installed. +# Set DEVKITPRO=/opt/devkitpro before invoking cmake. + +set(CMAKE_SYSTEM_NAME Generic) +set(CMAKE_SYSTEM_PROCESSOR powerpc) +set(CMAKE_SYSTEM_VERSION 1) + +# PowerPC is big-endian — skip test_big_endian (can't run cross-compiled binaries) +set(CMAKE_C_BYTE_ORDER BIG_ENDIAN) +set(CMAKE_CXX_BYTE_ORDER BIG_ENDIAN) + +# devkitPro paths +if(DEFINED ENV{DEVKITPRO}) + set(DEVKITPRO $ENV{DEVKITPRO}) +else() + set(DEVKITPRO /opt/devkitpro) +endif() + +set(DEVKITPPC ${DEVKITPRO}/devkitPPC) +set(LIBOGC ${DEVKITPRO}/libogc) +set(PORTLIBS ${DEVKITPRO}/portlibs/ppc) + +# Compilers +set(CMAKE_C_COMPILER ${DEVKITPPC}/bin/powerpc-eabi-gcc) +set(CMAKE_CXX_COMPILER ${DEVKITPPC}/bin/powerpc-eabi-g++) +set(CMAKE_ASM_COMPILER ${DEVKITPPC}/bin/powerpc-eabi-gcc) +set(CMAKE_AR ${DEVKITPPC}/bin/powerpc-eabi-ar) +set(CMAKE_RANLIB ${DEVKITPPC}/bin/powerpc-eabi-ranlib) +set(CMAKE_OBJCOPY ${DEVKITPPC}/bin/powerpc-eabi-objcopy) +set(CMAKE_OBJDUMP ${DEVKITPPC}/bin/powerpc-eabi-objdump) + +# Wii-specific flags: big-endian, single-precision FPU, no exceptions +# -Wno-error=incompatible-pointer-types: pre-existing type mismatches in the +# codebase that GCC 14+ treats as errors by default. +set(CMAKE_C_FLAGS_INIT "-mogc -mcpu=750 -meabi -mhard-float -ffunction-sections -fdata-sections -Wno-error=incompatible-pointer-types") +set(CMAKE_CXX_FLAGS_INIT "${CMAKE_C_FLAGS_INIT}") + +# Wii memory layout: 24 MB MEM1 + ~48 MB MEM2 +set(CMAKE_EXE_LINKER_FLAGS_INIT "-mogc -L${LIBOGC}/lib/wii -L${PORTLIBS}/lib") + +# Don't search host paths for libraries/headers +set(CMAKE_FIND_ROOT_PATH ${DEVKITPPC} ${LIBOGC} ${PORTLIBS}) +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) diff --git a/CMakeLists.txt b/CMakeLists.txt index a6ca63d..124382e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,23 +16,31 @@ endif() add_compile_options(-Wall -Wextra -Wpedantic) -find_package(CURL REQUIRED) -find_package(OpenSSL REQUIRED) -find_package(Threads REQUIRED) -find_package(PkgConfig QUIET) -if(PkgConfig_FOUND) - pkg_check_modules(SECP256K1 libsecp256k1) - pkg_check_modules(ZSTD libzstd) - pkg_check_modules(CARES libcares>=1.28) +# Wii cross-compilation target. When ON, desktop-specific dependencies +# (libcurl, OpenSSL, pthreads) are replaced with platform stubs. +option(WOLFRAM_BUILD_WII "Build for Nintendo Wii (devkitPPC/libogc)" OFF) + +if(NOT WOLFRAM_BUILD_WII) + find_package(CURL REQUIRED) + find_package(OpenSSL REQUIRED) + find_package(Threads REQUIRED) endif() -if(NOT ZSTD_FOUND) - find_path(ZSTD_INCLUDE_DIR NAMES zstd.h) - find_library(ZSTD_LIBRARY NAMES zstd) - if(ZSTD_INCLUDE_DIR AND ZSTD_LIBRARY) - set(ZSTD_FOUND TRUE) - set(ZSTD_INCLUDE_DIRS ${ZSTD_INCLUDE_DIR}) - set(ZSTD_LIBRARIES ${ZSTD_LIBRARY}) - set(ZSTD_VERSION "system") +if(NOT WOLFRAM_BUILD_WII) + find_package(PkgConfig QUIET) + if(PkgConfig_FOUND) + pkg_check_modules(SECP256K1 libsecp256k1) + pkg_check_modules(ZSTD libzstd) + pkg_check_modules(CARES libcares>=1.28) + endif() + if(NOT ZSTD_FOUND) + find_path(ZSTD_INCLUDE_DIR NAMES zstd.h) + find_library(ZSTD_LIBRARY NAMES zstd) + if(ZSTD_INCLUDE_DIR AND ZSTD_LIBRARY) + set(ZSTD_FOUND TRUE) + set(ZSTD_INCLUDE_DIRS ${ZSTD_INCLUDE_DIR}) + set(ZSTD_LIBRARIES ${ZSTD_LIBRARY}) + set(ZSTD_VERSION "system") + endif() endif() endif() @@ -63,12 +71,12 @@ set_target_properties(cbor PROPERTIES POSITION_INDEPENDENT_CODE ON) add_library(wolfram # Transport - src/transport/xrpc.c - src/transport/websocket.c + $,src/transport/xrpc_wii.c,src/transport/xrpc.c> + $,src/transport/websocket_wii.c,src/transport/websocket.c> # Identity src/identity/identity.c # Crypto - src/crypto/crypto.c + $,src/crypto/crypto_wii.c,src/crypto/crypto.c> # Repository (DAG-CBOR, CID, CAR, MST, commit, record, diff, sync) src/repo/cbor.c src/repo/cid.c @@ -83,18 +91,20 @@ add_library(wolfram src/repo/cbor_map_find.c # Session src/session/session.c - src/session/auth_client.c + $<$>:src/session/auth_client.c> # OAuth (metadata, PKCE, DPoP, PAR, state, callback, flow) - src/session/oauth/util.c - src/session/oauth/meta.c - src/session/oauth/pkce.c - src/session/oauth/dpop.c - src/session/oauth/par.c - src/session/oauth/state.c - src/session/oauth/callback.c - src/session/oauth/flow.c + $<$>: + src/session/oauth/util.c + src/session/oauth/meta.c + src/session/oauth/pkce.c + src/session/oauth/dpop.c + src/session/oauth/par.c + src/session/oauth/state.c + src/session/oauth/callback.c + src/session/oauth/flow.c + > # OAuth resource-server verification (Bearer + DPoP) - src/oauth/verify.c + $<$>:src/oauth/verify.c> # Jetstream src/jetstream/jetstream.c # Label subscriptions @@ -104,9 +114,9 @@ add_library(wolfram # Syntax validation src/syntax/syntax.c # TID generation clock - src/tid.c + $,src/platform/wii_tid.c,src/tid.c> # Lexicon validation - src/validate/validate.c + $<$>:src/validate/validate.c> # High-level agent API src/agent/agent.c src/agent/post.c @@ -148,7 +158,7 @@ add_library(wolfram src/agent/chat_typed.c src/admin_typed.c src/sync_typed.c - src/server_typed.c + $<$>:src/server_typed.c> src/agent/lexicon_typed.c src/agent/threadgate_postgate.c src/agent/moderation_actions.c @@ -175,10 +185,10 @@ src/agent/push_endpoint.c src/verify.c # Generated lexicon endpoint wrappers src/atproto_lex.c - # Server account management - src/server/server.c + # Server account management (PDS server-side) + $<$>:src/server/server.c> # Service-auth (service JWT) token issuance + verification - src/server/service_auth.c + $<$>:src/server/service_auth.c> # DID PLC operation build / sign / submit helpers src/plc/plc.c # New utilities and embed support @@ -199,6 +209,26 @@ src/agent/push_endpoint.c src/image.c ) +# Wii platform support +if(WOLFRAM_BUILD_WII) + target_sources(wolfram PRIVATE + src/platform/wii_platform.c + src/openssl_compat.c + ) + target_compile_definitions(wolfram PUBLIC WOLFRAM_WII) + # Make #include resolve to our shim headers + target_include_directories(wolfram PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src) + # Wii: skip server, examples, tests (no curl/OpenSSL/pthreads) + set(WOLFRAM_BUILD_SERVER OFF CACHE BOOL "" FORCE) + set(WOLFRAM_BUILD_TESTS OFF CACHE BOOL "" FORCE) + set(WOLFRAM_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) + message(STATUS "Building for Nintendo Wii (WOLFRAM_BUILD_WII=ON)") +else() + target_sources(wolfram PRIVATE + src/platform/posix_platform.c + ) +endif() + # XRPC server module (optional, requires libmicrohttpd) if(WOLFRAM_BUILD_SERVER) find_path(MICROHTTPD_INCLUDE_DIR NAMES microhttpd.h REQUIRED) @@ -265,7 +295,11 @@ target_include_directories(wolfram ${SECP256K1_INCLUDE_DIRS} ) -target_link_libraries(wolfram PUBLIC CURL::libcurl cjson OpenSSL::Crypto Threads::Threads) +if(NOT WOLFRAM_BUILD_WII) + target_link_libraries(wolfram PUBLIC CURL::libcurl cjson OpenSSL::Crypto Threads::Threads) +else() + target_link_libraries(wolfram PUBLIC cjson) +endif() target_link_libraries(wolfram PRIVATE libcbor::libcbor) # stb_image.h (vendored, third_party/) emits warnings under -Wextra -Wpedantic. @@ -290,13 +324,15 @@ else() message(STATUS "libzstd not found — Jetstream compression remains unavailable") endif() -find_library(RESOLV_LIB NAMES resolv) -if(NOT CARES_FOUND AND RESOLV_LIB) - target_link_libraries(wolfram PUBLIC ${RESOLV_LIB}) - target_compile_definitions(wolfram PRIVATE HAVE_RESOLV) - message(STATUS "resolv found: ${RESOLV_LIB}") -elseif(NOT CARES_FOUND) - message(STATUS "resolv not found — DNS TXT lookup will fall back to HTTPS well-known") +if(NOT WOLFRAM_BUILD_WII) + find_library(RESOLV_LIB NAMES resolv) + if(NOT CARES_FOUND AND RESOLV_LIB) + target_link_libraries(wolfram PUBLIC ${RESOLV_LIB}) + target_compile_definitions(wolfram PRIVATE HAVE_RESOLV) + message(STATUS "resolv found: ${RESOLV_LIB}") + elseif(NOT CARES_FOUND) + message(STATUS "resolv not found — DNS TXT lookup will fall back to HTTPS well-known") + endif() endif() # Optional libidn2 IDNA2008 / punycode handle resolution. Located here (after @@ -324,7 +360,7 @@ if(WOLFRAM_BUILD_IDN) message(STATUS "libidn2 found (${IDN2_LIBRARIES}) — building IDNA/punycode handle resolution") endif() -if(SECP256K1_FOUND) +if(SECP256K1_FOUND AND NOT WOLFRAM_BUILD_WII) find_library(SECP256K1_LIB NAMES secp256k1 HINTS ${SECP256K1_LIBRARY_DIRS} @@ -431,10 +467,12 @@ endif() # Always built (not gated on WOLFRAM_BUILD_EXAMPLES) so the CLI is part of the # default build. The target is named wolfram_cli because the wolfram library # already owns the `wolfram` target name; OUTPUT_NAME keeps the binary `wolfram`. -add_executable(wolfram_cli src/cli/main.c) -target_link_libraries(wolfram_cli PRIVATE wolfram) -target_include_directories(wolfram_cli PRIVATE ${cjson_SOURCE_DIR} ${cjson_BINARY_DIR}) -set_target_properties(wolfram_cli PROPERTIES OUTPUT_NAME wolfram) +if(NOT WOLFRAM_BUILD_WII) + add_executable(wolfram_cli src/cli/main.c) + target_link_libraries(wolfram_cli PRIVATE wolfram) + target_include_directories(wolfram_cli PRIVATE ${cjson_SOURCE_DIR} ${cjson_BINARY_DIR}) + set_target_properties(wolfram_cli PROPERTIES OUTPUT_NAME wolfram) +endif() if(WOLFRAM_BUILD_EXAMPLES) add_executable(describe_repo examples/describe_repo.c) diff --git a/include/wolfram/platform.h b/include/wolfram/platform.h new file mode 100644 index 0000000..ecd5983 --- /dev/null +++ b/include/wolfram/platform.h @@ -0,0 +1,86 @@ +/** + * platform.h — Platform abstraction layer for wolfram. + * + * Provides thin wrappers around platform-specific primitives (time, mutex, + * init/shutdown) so the rest of the SDK can be compiled for both desktop + * (Linux/macOS) and embedded targets (Nintendo Wii via devkitPPC/libogc). + * + * On desktop, these map to POSIX APIs. On Wii, they map to libogc primitives. + * The public API is identical across platforms. + */ + +#ifndef WOLFRAM_PLATFORM_H +#define WOLFRAM_PLATFORM_H + +#include "wolfram/xrpc.h" + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* ── Platform detection ──────────────────────────────────────────────── */ + +#if defined(__WII__) + #define WOLFRAM_PLATFORM_WII 1 +#elif defined(__APPLE__) + #define WOLFRAM_PLATFORM_MACOS 1 + #define WOLFRAM_PLATFORM_POSIX 1 +#elif defined(__linux__) + #define WOLFRAM_PLATFORM_LINUX 1 + #define WOLFRAM_PLATFORM_POSIX 1 +#else + #define WOLFRAM_PLATFORM_POSIX 1 +#endif + +/* ── Init / shutdown ────────────────────────────────────────────────── */ + +/** + * One-time platform initialisation. + * + * Desktop: no-op (returns WF_OK). + * Wii: will initialise net_init(), VIDEO_Init(), etc. + */ +wf_status wf_platform_init(void); + +/** + * Platform cleanup. Call once at exit. + */ +void wf_platform_shutdown(void); + +/* ── Mutex ──────────────────────────────────────────────────────────── */ + +/** Opaque platform mutex. */ +typedef struct wf_platform_mutex wf_platform_mutex; + +/** Allocate a new mutex. Free with wf_platform_mutex_free. */ +wf_platform_mutex *wf_platform_mutex_new(void); + +/** Lock the mutex (blocking). */ +void wf_platform_mutex_lock(wf_platform_mutex *m); + +/** Unlock the mutex. */ +void wf_platform_mutex_unlock(wf_platform_mutex *m); + +/** Free a mutex. */ +void wf_platform_mutex_free(wf_platform_mutex *m); + +/* ── Time ───────────────────────────────────────────────────────────── */ + +/** + * Return the current wall-clock time in microseconds since an + * arbitrary epoch (need not be UNIX time). + * + * The value must be monotonically non-decreasing within a process. + * Used by TID generation and any timestamping that needs sub-second + * resolution. + */ +uint64_t wf_platform_time_micros(void); + +#ifdef __cplusplus +} +#endif + +#endif /* WOLFRAM_PLATFORM_H */ diff --git a/src/agent/agent.c b/src/agent/agent.c index 235c68e..ebe9905 100644 --- a/src/agent/agent.c +++ b/src/agent/agent.c @@ -2964,7 +2964,7 @@ wf_status wf_agent_update_handle(wf_agent *agent, const char *new_handle) { wf_lex_com_atproto_identity_update_handle_main_input input = { .handle = new_handle }; wf_response res = {0}; wf_agent_sync_auth(agent); - wf_status status = wf_lex_com_atproto_identity_update_handle_main_call_auth(agent->client, &input, &res); + wf_status status = wf_lex_com_atproto_identity_update_handle_main_call(agent->client, &input, &res); wf_response_free(&res); return status; } diff --git a/src/crypto/crypto_wii.c b/src/crypto/crypto_wii.c new file mode 100644 index 0000000..5712619 --- /dev/null +++ b/src/crypto/crypto_wii.c @@ -0,0 +1,174 @@ +/** + * crypto_wii.c — Wii stub implementation of the crypto module. + * + * SHA-256 and base64url are platform-independent (pure C) and are + * implemented here. P-256 and secp256k1 operations return + * WF_ERR_NOT_IMPLEMENTED until mbedTLS is linked. + */ + +#include "wolfram/crypto.h" + +#include +#include +#include + +/* ── SHA-256 (mbedTLS) ──────────────────────────────────────────────── */ + +/* + * TODO: Implement using mbedTLS: + * #include + * + * wf_status wf_crypto_sha256(const unsigned char *in, size_t len, + * unsigned char out[32]) { + * if (!in || !out) return WF_ERR_INVALID_ARG; + * mbedtls_sha256(in, len, out, 0); + * return WF_OK; + * } + */ + +wf_status wf_crypto_sha256(const unsigned char *in, size_t len, + unsigned char out[32]) { + (void)in; (void)len; (void)out; + return WF_ERR_NOT_IMPLEMENTED; +} + +/* ── Base64url (pure C, no platform dependency) ─────────────────────── */ + +wf_status wf_crypto_base64url_decode(const char *in, + unsigned char **out, size_t *out_len) { + static const char b64tbl[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + size_t in_len, padded_len, padding, i; + unsigned char *decoded = NULL; + if (!in || !out || !out_len) return WF_ERR_INVALID_ARG; + *out = NULL; + *out_len = 0; + in_len = strlen(in); + if (in_len == 0) return WF_ERR_PARSE; + + padded_len = ((in_len + 3) / 4) * 4; + decoded = malloc(padded_len); + if (!decoded) return WF_ERR_ALLOC; + + /* Decode base64 characters */ + size_t bits = 0, nbits = 0, idx = 0; + for (i = 0; i < in_len; i++) { + char c = in[i]; + if (c == '-' || c == '+') c = '+'; + else if (c == '_' || c == '/') c = '/'; + else if (c == '=') { padding++; continue; } + else if (!isalnum((unsigned char)c)) { free(decoded); return WF_ERR_PARSE; } + const char *p = strchr(b64tbl, c); + if (!p) { free(decoded); return WF_ERR_PARSE; } + bits = (bits << 6) | (size_t)(p - b64tbl); + nbits += 6; + if (nbits >= 8) { + nbits -= 8; + decoded[idx++] = (unsigned char)((bits >> nbits) & 0xFF); + } + } + if (idx < padding) { free(decoded); return WF_ERR_PARSE; } + *out_len = idx - padding; + *out = decoded; + return WF_OK; +} + +wf_status wf_crypto_base64url_encode(const unsigned char *in, size_t len, + char **out) { + static const char b64tbl[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + char *result = NULL; + size_t out_len; + if (!in || !out) return WF_ERR_INVALID_ARG; + *out = NULL; + if (len == 0) { + result = calloc(1, 1); + if (!result) return WF_ERR_ALLOC; + *out = result; + return WF_OK; + } + out_len = 4 * ((len + 2) / 3); + result = malloc(out_len + 1); + if (!result) return WF_ERR_ALLOC; + size_t i, j = 0; + for (i = 0; i + 2 < len; i += 3) { + result[j++] = b64tbl[(in[i] >> 2) & 0x3F]; + result[j++] = b64tbl[((in[i] & 0x03) << 4) | (in[i + 1] >> 4)]; + result[j++] = b64tbl[((in[i + 1] & 0x0F) << 2) | (in[i + 2] >> 6)]; + result[j++] = b64tbl[in[i + 2] & 0x3F]; + } + if (i < len) { + result[j++] = b64tbl[(in[i] >> 2) & 0x3F]; + if (i + 1 < len) { + result[j++] = b64tbl[((in[i] & 0x03) << 4) | (in[i + 1] >> 4)]; + result[j++] = b64tbl[(in[i + 1] & 0x0F) << 2]; + } else { + result[j++] = b64tbl[(in[i] & 0x03) << 4]; + result[j++] = '='; + } + if (i + 2 >= len) result[j++] = '='; + } + /* Convert to base64url: strip padding, swap +/- and / */ + size_t end = j; + while (end > 0 && result[end - 1] == '=') end--; + for (size_t k = 0; k < end; k++) { + if (result[k] == '+') result[k] = '-'; + else if (result[k] == '/') result[k] = '_'; + } + result[end] = '\0'; + *out = result; + return WF_OK; +} + +/* ── ECDSA / secp256k1 stubs ────────────────────────────────────────── */ + +/* + * TODO: Implement P-256 operations using mbedTLS: + * - mbedtls_ecdsa_sign (wf_sign for P256) + * - mbedtls_ecdsa_verify (wf_verify for P256) + * - mbedtls_ecp_gen_keypair (wf_signing_key_generate for P256) + * - mbedtls_ecp_point_mul (wf_signing_key_public_didkey for P256) + * + * secp256k1 stubs can remain as-is (returning WF_ERR_INVALID_ARG) + * since Channel Blue doesn't need secp256k1. + */ + +wf_status wf_signing_key_public_didkey(const wf_signing_key *key, + char **out_didkey) { + (void)key; (void)out_didkey; + return WF_ERR_NOT_IMPLEMENTED; +} + +wf_status wf_signing_key_generate(wf_key_type type, wf_signing_key *out) { + (void)type; (void)out; + return WF_ERR_NOT_IMPLEMENTED; +} + +wf_status wf_sign(const wf_signing_key *key, + const unsigned char *msg, size_t msg_len, + unsigned char *sig_out, size_t sig_out_cap) { + (void)key; (void)msg; (void)msg_len; (void)sig_out; (void)sig_out_cap; + return WF_ERR_NOT_IMPLEMENTED; +} + +wf_status wf_verify(const char *public_key_multibase, + const unsigned char *msg, size_t msg_len, + const unsigned char *sig, size_t sig_len) { + (void)public_key_multibase; (void)msg; (void)msg_len; + (void)sig; (void)sig_len; + return WF_ERR_NOT_IMPLEMENTED; +} + +wf_status wf_crypto_p256_verify(const unsigned char x[32], + const unsigned char y[32], + const unsigned char *msg, size_t msg_len, + const unsigned char *sig, size_t sig_len) { + (void)x; (void)y; (void)msg; (void)msg_len; (void)sig; (void)sig_len; + return WF_ERR_NOT_IMPLEMENTED; +} + +wf_status wf_crypto_p256_jwk_coords(const char *jwk_json, + unsigned char x[32], unsigned char y[32]) { + (void)jwk_json; (void)x; (void)y; + return WF_ERR_NOT_IMPLEMENTED; +} diff --git a/src/openssl/bn.h b/src/openssl/bn.h new file mode 100644 index 0000000..3900cce --- /dev/null +++ b/src/openssl/bn.h @@ -0,0 +1,2 @@ +/* Shim: redirects to the Wii OpenSSL compat layer. */ +#include "../openssl_compat.h" diff --git a/src/openssl/crypto.h b/src/openssl/crypto.h new file mode 100644 index 0000000..3900cce --- /dev/null +++ b/src/openssl/crypto.h @@ -0,0 +1,2 @@ +/* Shim: redirects to the Wii OpenSSL compat layer. */ +#include "../openssl_compat.h" diff --git a/src/openssl/ec.h b/src/openssl/ec.h new file mode 100644 index 0000000..3900cce --- /dev/null +++ b/src/openssl/ec.h @@ -0,0 +1,2 @@ +/* Shim: redirects to the Wii OpenSSL compat layer. */ +#include "../openssl_compat.h" diff --git a/src/openssl/ecdsa.h b/src/openssl/ecdsa.h new file mode 100644 index 0000000..3900cce --- /dev/null +++ b/src/openssl/ecdsa.h @@ -0,0 +1,2 @@ +/* Shim: redirects to the Wii OpenSSL compat layer. */ +#include "../openssl_compat.h" diff --git a/src/openssl/evp.h b/src/openssl/evp.h new file mode 100644 index 0000000..3900cce --- /dev/null +++ b/src/openssl/evp.h @@ -0,0 +1,2 @@ +/* Shim: redirects to the Wii OpenSSL compat layer. */ +#include "../openssl_compat.h" diff --git a/src/openssl/obj_mac.h b/src/openssl/obj_mac.h new file mode 100644 index 0000000..3900cce --- /dev/null +++ b/src/openssl/obj_mac.h @@ -0,0 +1,2 @@ +/* Shim: redirects to the Wii OpenSSL compat layer. */ +#include "../openssl_compat.h" diff --git a/src/openssl/rand.h b/src/openssl/rand.h new file mode 100644 index 0000000..3900cce --- /dev/null +++ b/src/openssl/rand.h @@ -0,0 +1,2 @@ +/* Shim: redirects to the Wii OpenSSL compat layer. */ +#include "../openssl_compat.h" diff --git a/src/openssl/sha.h b/src/openssl/sha.h new file mode 100644 index 0000000..3900cce --- /dev/null +++ b/src/openssl/sha.h @@ -0,0 +1,2 @@ +/* Shim: redirects to the Wii OpenSSL compat layer. */ +#include "../openssl_compat.h" diff --git a/src/openssl_compat.c b/src/openssl_compat.c new file mode 100644 index 0000000..29796fc --- /dev/null +++ b/src/openssl_compat.c @@ -0,0 +1,188 @@ +/** + * openssl_compat.c — Pure-C implementations of the minimal OpenSSL API + * needed by the wolfram SDK on Wii. + * + * SHA-256 is a straightforward FIPS 180-4 implementation. + * Base64 encode/decode is standard RFC 4648. + * RAND_bytes is a stub that returns failure. + */ + +#include "openssl_compat.h" + +#include + +/* ── SHA-256 (FIPS 180-4) ───────────────────────────────────────────── */ + +static const uint32_t K[64] = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, + 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, + 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, + 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, + 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, + 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, + 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, + 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 +}; + +#define ROTR(x, n) (((x) >> (n)) | ((x) << (32 - (n)))) +#define CH(x, y, z) (((x) & (y)) ^ (~(x) & (z))) +#define MAJ(x, y, z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z))) +#define EP0(x) (ROTR(x, 2) ^ ROTR(x, 13) ^ ROTR(x, 22)) +#define EP1(x) (ROTR(x, 6) ^ ROTR(x, 11) ^ ROTR(x, 25)) +#define SIG0(x) (ROTR(x, 7) ^ ROTR(x, 18) ^ ((x) >> 3)) +#define SIG1(x) (ROTR(x, 17) ^ ROTR(x, 19) ^ ((x) >> 10)) + +static void sha256_transform(uint32_t state[8], const unsigned char block[64]) { + uint32_t a, b, c, d, e, f, g, h, t1, t2, w[64]; + int i; + + for (i = 0; i < 16; i++) { + w[i] = ((uint32_t)block[i * 4] << 24) | + ((uint32_t)block[i * 4 + 1] << 16) | + ((uint32_t)block[i * 4 + 2] << 8) | + ((uint32_t)block[i * 4 + 3]); + } + for (i = 16; i < 64; i++) { + w[i] = SIG1(w[i - 2]) + w[i - 7] + SIG0(w[i - 15]) + w[i - 16]; + } + + a = state[0]; b = state[1]; c = state[2]; d = state[3]; + e = state[4]; f = state[5]; g = state[6]; h = state[7]; + + for (i = 0; i < 64; i++) { + t1 = h + EP1(e) + CH(e, f, g) + K[i] + w[i]; + t2 = EP0(a) + MAJ(a, b, c); + h = g; g = f; f = e; e = d + t1; + d = c; c = b; b = a; a = t1 + t2; + } + + state[0] += a; state[1] += b; state[2] += c; state[3] += d; + state[4] += e; state[5] += f; state[6] += g; state[7] += h; +} + +void SHA256(const unsigned char *data, size_t len, unsigned char *out) { + uint32_t state[8] = { + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, + 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 + }; + unsigned char block[64]; + size_t i, remaining; + + /* Process complete 64-byte blocks. */ + for (i = 0; i + 64 <= len; i += 64) { + sha256_transform(state, data + i); + } + + /* Pad: append 1-bit, zeros, then 64-bit big-endian length. */ + remaining = len - i; + memset(block, 0, 64); + if (remaining > 0) { + memcpy(block, data + i, remaining); + } + block[remaining] = 0x80; + + if (remaining >= 56) { + sha256_transform(state, block); + memset(block, 0, 64); + } + + { + uint64_t bitlen = (uint64_t)len * 8; + block[56] = (unsigned char)(bitlen >> 56); + block[57] = (unsigned char)(bitlen >> 48); + block[58] = (unsigned char)(bitlen >> 40); + block[59] = (unsigned char)(bitlen >> 32); + block[60] = (unsigned char)(bitlen >> 24); + block[61] = (unsigned char)(bitlen >> 16); + block[62] = (unsigned char)(bitlen >> 8); + block[63] = (unsigned char)(bitlen); + } + sha256_transform(state, block); + + for (i = 0; i < 8; i++) { + out[i * 4] = (unsigned char)(state[i] >> 24); + out[i * 4 + 1] = (unsigned char)(state[i] >> 16); + out[i * 4 + 2] = (unsigned char)(state[i] >> 8); + out[i * 4 + 3] = (unsigned char)(state[i]); + } +} + +/* ── Base64 encode/decode (RFC 4648) ────────────────────────────────── */ + +static const char b64_table[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +int EVP_EncodeBlock(unsigned char *out, const unsigned char *in, int len) { + int i, j = 0; + for (i = 0; i + 2 < len; i += 3) { + out[j++] = b64_table[(in[i] >> 2) & 0x3F]; + out[j++] = b64_table[((in[i] & 0x03) << 4) | (in[i + 1] >> 4)]; + out[j++] = b64_table[((in[i + 1] & 0x0F) << 2) | (in[i + 2] >> 6)]; + out[j++] = b64_table[in[i + 2] & 0x3F]; + } + if (i < len) { + out[j++] = b64_table[(in[i] >> 2) & 0x3F]; + if (i + 1 < len) { + out[j++] = b64_table[((in[i] & 0x03) << 4) | (in[i + 1] >> 4)]; + out[j++] = b64_table[(in[i + 1] & 0x0F) << 2]; + } else { + out[j++] = b64_table[(in[i] & 0x03) << 4]; + out[j++] = '='; + } + out[j++] = '='; + } + out[j] = '\0'; + return j; +} + +static int b64_decode_char(char c) { + if (c >= 'A' && c <= 'Z') return c - 'A'; + if (c >= 'a' && c <= 'z') return c - 'a' + 26; + if (c >= '0' && c <= '9') return c - '0' + 52; + if (c == '+') return 62; + if (c == '/') return 63; + return -1; +} + +int EVP_DecodeBlock(unsigned char *out, const unsigned char *in, int len) { + int i, j = 0, val[4]; + int pad = 0; + + /* Strip trailing padding. */ + while (len > 0 && in[len - 1] == '=') { len--; pad++; } + + for (i = 0; i < len; i += 4) { + val[0] = b64_decode_char((char)in[i]); + val[1] = (i + 1 < len) ? b64_decode_char((char)in[i + 1]) : 0; + val[2] = (i + 2 < len) ? b64_decode_char((char)in[i + 2]) : 0; + val[3] = (i + 3 < len) ? b64_decode_char((char)in[i + 3]) : 0; + + if (val[0] < 0 || val[1] < 0 || val[2] < 0 || val[3] < 0) + return -1; + + out[j++] = (unsigned char)((val[0] << 2) | (val[1] >> 4)); + if (i + 2 < len) out[j++] = (unsigned char)(((val[1] & 0x0F) << 4) | (val[2] >> 2)); + if (i + 3 < len) out[j++] = (unsigned char)(((val[2] & 0x03) << 6) | val[3]); + } + + return j - pad; +} + +/* ── Random bytes (stub) ────────────────────────────────────────────── */ + +/* + * TODO: Implement via mbedTLS mbedtls_ctr_drbg_random() seeded from + * a hardware RNG or a seed stored on SD card. + */ +int RAND_bytes(unsigned char *buf, int len) { + (void)buf; (void)len; + return 0; /* failure */ +} diff --git a/src/openssl_compat.h b/src/openssl_compat.h new file mode 100644 index 0000000..df85cbd --- /dev/null +++ b/src/openssl_compat.h @@ -0,0 +1,115 @@ +/** + * openssl_compat.h — Minimal OpenSSL API compatibility layer for Wii. + * + * When WOLFRAM_WII is defined, this header is included via -I priority + * in place of the real OpenSSL headers. It provides the type declarations + * and function signatures that the wolfram source files need, backed by + * pure-C or mbedTLS implementations in openssl_compat.c. + * + * This is NOT a full OpenSSL port — just enough to compile the SDK's + * non-OAuth modules (atproto_lex, repo/cid, repo/mst, plc). + */ + +#ifndef WOLFRAM_OPENSSL_COMPAT_H +#define WOLFRAM_OPENSSL_COMPAT_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* ── SHA-256 ────────────────────────────────────────────────────────── */ + +/* SHA256_DIGEST_LENGTH is needed by some consumers. */ +#ifndef SHA256_DIGEST_LENGTH +#define SHA256_DIGEST_LENGTH 32 +#endif + +/** + * Compute SHA-256 hash of `data` (len bytes) into `out` (32 bytes). + * Implemented via mbedTLS in openssl_compat.c. + */ +void SHA256(const unsigned char *data, size_t len, unsigned char *out); + +/* ── Base64 encode/decode (EVP) ─────────────────────────────────────── */ + +/** + * Base64-encode `in` (len bytes) into `out` which must have at least + * `4 * ((len + 2) / 3)` bytes of space. Returns the number of bytes + * written (excluding NUL). Implemented in pure C. + */ +int EVP_EncodeBlock(unsigned char *out, const unsigned char *in, int len); + +/** + * Base64-decode `in` (NUL-terminated, padded) into `out`. + * Returns the number of bytes written, or -1 on error. + * Implemented in pure C. + */ +int EVP_DecodeBlock(unsigned char *out, const unsigned char *in, int len); + +/* ── Random bytes ───────────────────────────────────────────────────── */ + +/** + * Fill `buf` with `len` cryptographically random bytes. + * Stub: returns 0 (failure) until mbedTLS or /dev/urandom is wired up. + */ +int RAND_bytes(unsigned char *buf, int len); + +/* ── EC / ECDSA (stubs) ────────────────────────────────────────────── */ + +/* Opaque stub types — consumers never dereference these on Wii. */ +typedef struct { int dummy; } EC_KEY; +typedef struct { int dummy; } EC_GROUP; +typedef struct { int dummy; } EC_POINT; +typedef struct { int dummy; } ECDSA_SIG; +typedef struct { int dummy; } BIGNUM; +typedef struct { int dummy; } EVP_MD_CTX; +typedef struct { int dummy; } EVP_MD; +typedef struct { int dummy; } EVP_CIPHER_CTX; +typedef struct { int dummy; } EVP_CIPHER; + +#define NID_X9_62_prime256v1 415 +#define POINT_CONVERSION_COMPRESSED 2 + +/* Stub constants */ +#define EC_KEY_new_by_curve_name(x) NULL +#define EC_KEY_free(x) ((void)0) +#define EC_KEY_get0_group(x) NULL +#define EC_KEY_get0_private_key(x) NULL +#define EC_KEY_set_private_key(x, y) 0 +#define EC_KEY_generate_key(x) 0 +#define EC_GROUP_get0_order(x) NULL +#define EC_POINT_new(x) NULL +#define EC_POINT_free(x) ((void)0) +#define EC_POINT_mul(w, x, y, z, a, b) 0 +#define EC_POINT_point2oct(w, x, y, z, a, b) 0 +#define EC_POINT_oct2point(w, x, y, z, a) 0 +#define EC_KEY_set_public_key(x, y) 0 +#define ECDSA_SIG_new() NULL +#define ECDSA_SIG_free(x) ((void)0) +#define ECDSA_SIG_get0(x, y, z) ((void)0) +#define ECDSA_SIG_set0(x, y, z) 0 +#define ECDSA_do_sign(x, y, z) NULL +#define ECDSA_do_verify(x, y, z, a) 0 +#define BN_bin2bn(x, y, z) NULL +#define BN_bn2binpad(x, y, z) 0 +#define BN_dup(x) NULL +#define BN_free(x) ((void)0) +#define BN_cmp(x, y) 0 +#define BN_rshift1(x, y) 0 +#define BN_sub(x, y, z) 0 + +/* ── OpenSSL string memory (stubs) ──────────────────────────────────── */ + +static inline void OPENSSL_cleanse(void *ptr, size_t len) { + volatile unsigned char *p = (volatile unsigned char *)ptr; + while (len--) *p++ = 0; +} + +#ifdef __cplusplus +} +#endif + +#endif /* WOLFRAM_OPENSSL_COMPAT_H */ diff --git a/src/platform/posix_platform.c b/src/platform/posix_platform.c new file mode 100644 index 0000000..c51d247 --- /dev/null +++ b/src/platform/posix_platform.c @@ -0,0 +1,57 @@ +/** + * posix_platform.c — POSIX platform implementation (Linux, macOS). + * + * Maps platform abstractions to standard POSIX APIs. This is the default + * when building for desktop targets. + */ + +#include "wolfram/platform.h" + +#include +#include +#include + +/* ── Init / shutdown ────────────────────────────────────────────────── */ + +wf_status wf_platform_init(void) { + return WF_OK; +} + +void wf_platform_shutdown(void) { + /* No-op on POSIX. */ +} + +/* ── Mutex ──────────────────────────────────────────────────────────── */ + +struct wf_platform_mutex { + pthread_mutex_t mtx; +}; + +wf_platform_mutex *wf_platform_mutex_new(void) { + wf_platform_mutex *m = calloc(1, sizeof(*m)); + if (!m) return NULL; + pthread_mutex_init(&m->mtx, NULL); + return m; +} + +void wf_platform_mutex_lock(wf_platform_mutex *m) { + if (m) pthread_mutex_lock(&m->mtx); +} + +void wf_platform_mutex_unlock(wf_platform_mutex *m) { + if (m) pthread_mutex_unlock(&m->mtx); +} + +void wf_platform_mutex_free(wf_platform_mutex *m) { + if (!m) return; + pthread_mutex_destroy(&m->mtx); + free(m); +} + +/* ── Time ───────────────────────────────────────────────────────────── */ + +uint64_t wf_platform_time_micros(void) { + struct timeval tv; + gettimeofday(&tv, NULL); + return (uint64_t)tv.tv_sec * 1000000u + (uint64_t)tv.tv_usec; +} diff --git a/src/platform/wii_platform.c b/src/platform/wii_platform.c new file mode 100644 index 0000000..0cdda96 --- /dev/null +++ b/src/platform/wii_platform.c @@ -0,0 +1,110 @@ +/** + * wii_platform.c — Nintendo Wii platform implementation (libogc stubs). + * + * Maps platform abstractions to libogc primitives. Currently contains + * stub implementations; TODO comments indicate what needs to be filled + * in when the Wii integration is built out. + * + * Build with devkitPPC: powerpc-eabi-gcc, linked against libogc. + */ + +#include "wolfram/platform.h" + +#include +#include + +/* TODO: #include — for LWP mutex */ +/* TODO: #include */ +/* TODO: #include — for VIDEO_Init */ +/* TODO: #include — for net_init */ + +/* ── Init / shutdown ────────────────────────────────────────────────── */ + +wf_status wf_platform_init(void) { + /* + * TODO: Implement when building the Wii integration: + * 1. VIDEO_Init() + * 2. net_init() — lwIP TCP/IP stack + * 3. Initialise SSL/TLS context (mbedTLS) + * 4. Mount SD card (fatMountSimple) + */ + return WF_OK; +} + +void wf_platform_shutdown(void) { + /* + * TODO: Implement when building the Wii integration: + * 1. net_shutdown() + * 2. fatUnmount("sd:") + */ +} + +/* ── Mutex ──────────────────────────────────────────────────────────── */ + +/* + * TODO: Replace with lwp_mutex from libogc: + * #include + * + * struct wf_platform_mutex { mutex_t mtx; }; + * + * wf_platform_mutex *wf_platform_mutex_new(void) { + * wf_platform_mutex *m = calloc(1, sizeof(*m)); + * if (!m) return NULL; + * LWP_MutexInit(&m->mtx, false); + * return m; + * } + * + * void wf_platform_mutex_lock(wf_platform_mutex *m) { + * if (m) LWP_MutexLock(m->mtx); + * } + * + * void wf_platform_mutex_unlock(wf_platform_mutex *m) { + * if (m) LWP_MutexUnlock(m->mtx); + * } + * + * void wf_platform_mutex_free(wf_platform_mutex *m) { + * if (!m) return; + * LWP_MutexDestroy(m->mtx); + * free(m); + * } + */ + +struct wf_platform_mutex { int dummy; }; + +wf_platform_mutex *wf_platform_mutex_new(void) { + return calloc(1, sizeof(wf_platform_mutex)); +} + +void wf_platform_mutex_lock(wf_platform_mutex *m) { + (void)m; +} + +void wf_platform_mutex_unlock(wf_platform_mutex *m) { + (void)m; +} + +void wf_platform_mutex_free(wf_platform_mutex *m) { + free(m); +} + +/* ── Time ───────────────────────────────────────────────────────────── */ + +/* + * TODO: Replace with libogc timer: + * #include + * + * uint64_t wf_platform_time_micros(void) { + * // gettime() returns TB ticks; ticks_to_microsecs() converts + * return ticks_to_microsecs(gettime()); + * } + * + * The Wii's Timebase (TB) counter runs at 162.5 MHz on NTSC (144 MHz PAL) + * and provides monotonically increasing ticks. ticks_to_microsecs() + * from libogc handles the conversion. + */ + +uint64_t wf_platform_time_micros(void) { + /* Stub: return 0. TID generation will not produce unique values + * until this is implemented. */ + return 0; +} diff --git a/src/platform/wii_tid.c b/src/platform/wii_tid.c new file mode 100644 index 0000000..4eb4426 --- /dev/null +++ b/src/platform/wii_tid.c @@ -0,0 +1,106 @@ +/* + * wii_tid.c — TID generation clock (Wii implementation). + * + * Same logic as tid.c but uses the platform abstraction for time and mutex + * instead of POSIX gettimeofday() and pthreads. + */ + +#include "wolfram/tid.h" +#include "wolfram/platform.h" + +#include +#include + +/* S32 alphabet (index 0..31). */ +static const char WF_TID_S32[] = "234567abcdefghijklmnopqrstuvwxyz"; + +static int wf_tid_s32_index(char c) { + for (int i = 0; i < 32; i++) { + if (WF_TID_S32[i] == c) return i; + } + return -1; +} + +wf_status wf_tid_encode(uint64_t tid_value, char out[15]) { + if (!out) return WF_ERR_INVALID_ARG; + uint64_t v = tid_value; + for (int i = WF_TID_LEN - 1; i >= 0; i--) { + out[i] = WF_TID_S32[v & 31]; + v >>= 5; + } + out[WF_TID_LEN] = '\0'; + return WF_OK; +} + +wf_status wf_tid_decode(const char *tid, uint64_t *out_value) { + if (!tid || strlen(tid) != WF_TID_LEN) return WF_ERR_INVALID_ARG; + uint64_t v = 0; + for (int i = 0; i < WF_TID_LEN; i++) { + int idx = wf_tid_s32_index(tid[i]); + if (idx < 0) return WF_ERR_INVALID_ARG; + v = (v << 5) | (uint64_t)(unsigned)idx; + } + if (out_value) *out_value = v; + return WF_OK; +} + +wf_status wf_tid_from_time(uint64_t timestamp_micros, uint32_t clockid, + char out[15]) { + if (!out) return WF_ERR_INVALID_ARG; + uint64_t value = (timestamp_micros << 10) | ((uint64_t)(clockid & 0x3FF)); + return wf_tid_encode(value, out); +} + +/* ── Monotonic process-wide clock ─────────────────────────────────────── */ + +static wf_platform_mutex *wf_tid_lock = NULL; +static uint64_t wf_tid_last_ts = 0; +static uint32_t wf_tid_clock = 0; +static int wf_tid_initialized = 0; + +/* + * TODO: When wii_platform.c implements wf_platform_time_micros() with + * libogc's gettime()/ticks_to_microsecs(), this will produce real + * microsecond timestamps. Currently returns 0 (stub), so TIDs will + * sort by clockid only. + */ +static uint64_t wf_tid_now_micros(void) { + return wf_platform_time_micros(); +} + +wf_status wf_tid_now(char out[15]) { + if (!out) return WF_ERR_INVALID_ARG; + + /* Lazy-init the mutex on first call. */ + if (!wf_tid_lock) { + wf_tid_lock = wf_platform_mutex_new(); + if (!wf_tid_lock) return WF_ERR_ALLOC; + } + + wf_platform_mutex_lock(wf_tid_lock); + if (!wf_tid_initialized) { + wf_tid_clock = (uint32_t)rand() & 0x3FF; + wf_tid_initialized = 1; + } + + uint64_t now = wf_tid_now_micros(); + if (now <= wf_tid_last_ts) now = wf_tid_last_ts + 1; + wf_tid_last_ts = now; + + uint64_t value = (now << 10) | ((uint64_t)(wf_tid_clock & 0x3FF)); + wf_platform_mutex_unlock(wf_tid_lock); + + return wf_tid_encode(value, out); +} + +uint64_t wf_tid_timestamp_micros(const char *tid) { + uint64_t value = 0; + if (wf_tid_decode(tid, &value) != WF_OK) return 0; + return value >> 10; +} + +uint32_t wf_tid_clockid(const char *tid) { + uint64_t value = 0; + if (wf_tid_decode(tid, &value) != WF_OK) return 0; + return (uint32_t)(value & 0x3FF); +} diff --git a/src/syntax/syntax.c b/src/syntax/syntax.c index b90ad55..fe2f322 100644 --- a/src/syntax/syntax.c +++ b/src/syntax/syntax.c @@ -1,5 +1,6 @@ #include "wolfram/syntax.h" #include +#include #include #include #include diff --git a/src/transport/websocket_wii.c b/src/transport/websocket_wii.c new file mode 100644 index 0000000..9b268bb --- /dev/null +++ b/src/transport/websocket_wii.c @@ -0,0 +1,45 @@ +/** + * websocket_wii.c — Wii stub implementation of the WebSocket transport. + * + * All functions return WF_ERR_NOT_IMPLEMENTED. Replace with a real + * lwIP + mbedTLS WebSocket implementation when building the Wii + * integration (needed for firehose/Jetstream subscriptions). + */ + +#include "wolfram/websocket.h" + +#include + +struct wf_websocket { int dummy; }; + +int wf_websocket_supported(void) { + return 0; +} + +wf_status wf_websocket_connect(const char *url, wf_websocket **out) { + (void)url; (void)out; + return WF_ERR_NOT_IMPLEMENTED; +} + +wf_status wf_websocket_receive(wf_websocket *socket, + wf_websocket_message *out) { + (void)socket; (void)out; + return WF_ERR_NOT_IMPLEMENTED; +} + +wf_status wf_websocket_send_text(wf_websocket *socket, + const char *text, size_t text_len) { + (void)socket; (void)text; (void)text_len; + return WF_ERR_NOT_IMPLEMENTED; +} + +void wf_websocket_message_free(wf_websocket_message *message) { + if (!message) return; + free(message->data); + message->data = NULL; + message->len = 0; +} + +void wf_websocket_free(wf_websocket *socket) { + free(socket); +} diff --git a/src/transport/xrpc_wii.c b/src/transport/xrpc_wii.c new file mode 100644 index 0000000..5c4b316 --- /dev/null +++ b/src/transport/xrpc_wii.c @@ -0,0 +1,155 @@ +/** + * xrpc_wii.c — Wii stub implementation of the XRPC transport. + * + * All functions return WF_ERR_NOT_IMPLEMENTED. Replace with a real + * lwIP + mbedTLS transport when building the Wii integration. + */ + +#include "wolfram/xrpc.h" +#include "wolfram/version.h" + +#include +#include +#include + +struct wf_xrpc_client { + char *base_url; + char *auth_header; + wf_xrpc_handler_fn handler; + void *handler_userdata; +}; + +/* Strip a trailing slash, if any, so we can join paths predictably. */ +static char *wf_normalise_base(const char *base_url) { + size_t len = strlen(base_url); + while (len > 0 && base_url[len - 1] == '/') { + len--; + } + char *out = malloc(len + 1); + if (!out) return NULL; + memcpy(out, base_url, len); + out[len] = '\0'; + return out; +} + +wf_xrpc_client *wf_xrpc_client_new(const char *service_base_url) { + if (!service_base_url || service_base_url[0] == '\0') return NULL; + wf_xrpc_client *client = calloc(1, sizeof(*client)); + if (!client) return NULL; + client->base_url = wf_normalise_base(service_base_url); + if (!client->base_url) { free(client); return NULL; } + return client; +} + +void wf_xrpc_client_free(wf_xrpc_client *client) { + if (!client) return; + free(client->base_url); + free(client->auth_header); + free(client); +} + +void wf_xrpc_set_handler(wf_xrpc_client *client, wf_xrpc_handler_fn fn, + void *userdata) { + if (!client) return; + client->handler = fn; + client->handler_userdata = userdata; +} + +void wf_xrpc_client_set_auth(wf_xrpc_client *client, const char *access_jwt) { + if (!client) return; + free(client->auth_header); + client->auth_header = NULL; + if (!access_jwt) return; + size_t needed = strlen("Authorization: Bearer ") + strlen(access_jwt) + 1; + client->auth_header = malloc(needed); + if (client->auth_header) { + snprintf(client->auth_header, needed, "Authorization: Bearer %s", + access_jwt); + } +} + +char *wf_xrpc_get_base_url(wf_xrpc_client *client) { + if (!client || !client->base_url) return NULL; + return strdup(client->base_url); +} + +void wf_response_free(wf_response *res) { + if (!res) return; + free(res->body); + free(res->dpop_nonce); + res->body = NULL; + res->body_len = 0; + res->dpop_nonce = NULL; + res->status = 0; +} + +/* ── Stubs ──────────────────────────────────────────────────────────── */ + +/* + * TODO: Implement using lwIP sockets + mbedTLS: + * 1. Parse URL → host, port, path + * 2. gethostbyname() to resolve host + * 3. socket() + connect() + * 4. mbedTLS SSL handshake + * 5. Send HTTP request, read response + * 6. Parse headers (DPoP-Nonce, Content-Length, etc.) + * 7. Read body into growable buffer + */ + +wf_status wf_xrpc_query(wf_xrpc_client *client, const char *nsid, + const char *query_string, wf_response *out) { + (void)client; (void)nsid; (void)query_string; (void)out; + return WF_ERR_NOT_IMPLEMENTED; +} + +wf_status wf_xrpc_query_params(wf_xrpc_client *client, const char *nsid, + const wf_xrpc_param *params, + size_t param_count, wf_response *out) { + (void)client; (void)nsid; (void)params; (void)param_count; (void)out; + return WF_ERR_NOT_IMPLEMENTED; +} + +wf_status wf_xrpc_procedure(wf_xrpc_client *client, const char *nsid, + const char *json_body, wf_response *out) { + (void)client; (void)nsid; (void)json_body; (void)out; + return WF_ERR_NOT_IMPLEMENTED; +} + +wf_status wf_xrpc_upload_blob(wf_xrpc_client *client, const char *nsid, + const void *data, size_t data_len, + const char *content_type, wf_response *out) { + (void)client; (void)nsid; (void)data; (void)data_len; + (void)content_type; (void)out; + return WF_ERR_NOT_IMPLEMENTED; +} + +wf_status wf_xrpc_upload_blob_with_headers( + wf_xrpc_client *client, const char *nsid, const void *data, + size_t data_len, const char *content_type, const wf_http_header *headers, + size_t header_count, wf_response *out) { + (void)client; (void)nsid; (void)data; (void)data_len; + (void)content_type; (void)headers; (void)header_count; (void)out; + return WF_ERR_NOT_IMPLEMENTED; +} + +wf_status wf_http_get(wf_xrpc_client *client, const char *url, + wf_response *out) { + (void)client; (void)url; (void)out; + return WF_ERR_NOT_IMPLEMENTED; +} + +wf_status wf_http_get_with_headers(wf_xrpc_client *client, const char *url, + const wf_http_header *extra, + size_t extra_count, wf_response *out) { + (void)client; (void)url; (void)extra; (void)extra_count; (void)out; + return WF_ERR_NOT_IMPLEMENTED; +} + +wf_status wf_http_post(wf_xrpc_client *client, const char *url, + const char *content_type, const char *body, + const wf_http_header *extra, size_t extra_count, + wf_response *out) { + (void)client; (void)url; (void)content_type; (void)body; + (void)extra; (void)extra_count; (void)out; + return WF_ERR_NOT_IMPLEMENTED; +}