From ea614f511deaf6cacbfda8a09e87e55ebe914bf5 Mon Sep 17 00:00:00 2001 From: Annaqi Date: Wed, 19 Nov 2025 21:49:07 +0800 Subject: [PATCH 1/4] fix: Optimized HTML (Stagger API call), ensure no overflow for bearertoken --- index.html | 285 ++++++++++++++++++++++------------------------ src/net/api/api.c | 12 +- 2 files changed, 142 insertions(+), 155 deletions(-) diff --git a/index.html b/index.html index 000ee75..86831cd 100644 --- a/index.html +++ b/index.html @@ -782,32 +782,27 @@

🔐 MASTR Dashboard

diff --git a/src/net/api/api.c b/src/net/api/api.c index 9b52777..1e8850d 100644 --- a/src/net/api/api.c +++ b/src/net/api/api.c @@ -81,11 +81,17 @@ static void generate_random_psk(char *out, size_t out_len) { // Generate a random bearer token (256-bit = 64 hex chars) static void generate_bearer_token(char *out, size_t out_len) { + static const char HEX[] = "0123456789abcdef"; if (out_len < 65) return; // Need at least 65 bytes (64 hex + NUL) - - for (int i = 0; i < 32; i++) { + + for (int chunk = 0; chunk < 8; ++chunk) { uint32_t r = get_rand_32(); - sprintf(&out[i*2], "%08x", (unsigned)r); + for (int byte = 0; byte < 4; ++byte) { + uint8_t b = (uint8_t)((r >> (byte * 8)) & 0xFF); + int idx = chunk * 8 + byte * 2; + out[idx] = HEX[(b >> 4) & 0x0F]; + out[idx + 1] = HEX[b & 0x0F]; + } } out[64] = '\0'; } From 3298e95811ac28e02ca64687010ff507f468e87b Mon Sep 17 00:00:00 2001 From: Annaqi Date: Thu, 20 Nov 2025 00:19:13 +0800 Subject: [PATCH 2/4] fix: Regeneration of token for debug environment --- src/net/api/api.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/net/api/api.c b/src/net/api/api.c index 1e8850d..5df0f10 100644 --- a/src/net/api/api.c +++ b/src/net/api/api.c @@ -170,11 +170,17 @@ static void generate_token_handler(struct tcp_pcb *pcb, const char *request) { (void)request; API_DBG("[API] generate_token_handler called\n"); +#ifndef DEBUG if (g_bearer_token_generated) { // Token already generated - return 409 Conflict http_send_json(pcb, 409, "{\"error\":\"token_already_generated\",\"message\":\"Bearer token has already been issued for this device session\"}"); return; } +#else + if (g_bearer_token_generated) { + API_DBG("[API] DEBUG build: regenerating bearer token for testing\n"); + } +#endif // Generate new bearer token generate_bearer_token(g_bearer_token, sizeof(g_bearer_token)); From ffafbad8df62cf093ac5231dfbe80c4ee0e06e89 Mon Sep 17 00:00:00 2001 From: Annaqi Date: Thu, 20 Nov 2025 02:21:32 +0800 Subject: [PATCH 3/4] fix: fixed submmision bug for provisioning to allow multiple resubmition for testing --- index.html | 52 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/index.html b/index.html index 86831cd..5b6916b 100644 --- a/index.html +++ b/index.html @@ -1000,8 +1000,20 @@

🔐 Bearer Token Setup

// Reset all steps document.getElementById('hostPublicKeyInput').value = ''; document.getElementById('goldenHashInput').value = ''; - document.getElementById('hostKeyStatus').style.display = 'none'; - document.getElementById('hashStatus').style.display = 'none'; + const hostKeyStatus = document.getElementById('hostKeyStatus'); + const hashStatus = document.getElementById('hashStatus'); + hostKeyStatus.style.display = 'none'; + hostKeyStatus.className = 'provision-status'; + document.getElementById('hostKeyStatusText').textContent = ''; + hashStatus.style.display = 'none'; + hashStatus.className = 'provision-status'; + document.getElementById('hashStatusText').textContent = ''; + const step2Btn = document.getElementById('step2SubmitBtn'); + step2Btn.disabled = false; + step2Btn.textContent = 'Submit & Verify'; + const step3Btn = document.getElementById('step3SubmitBtn'); + step3Btn.disabled = false; + step3Btn.textContent = 'Complete Provisioning'; const modal = document.getElementById('provisioningModal'); modal.classList.add('show'); @@ -1261,21 +1273,27 @@

🔐 Bearer Token Setup

// Still pending, retry with increasing delay const delay = Math.min(100 + (attempts * 50), 500); console.log(`Host key still pending, retrying in ${delay}ms...`); - return new Promise(resolve => { - setTimeout(() => poll().then(resolve), delay); + return new Promise((resolve, reject) => { + setTimeout(() => { + poll().then(resolve).catch(reject); + }, delay); }); } else { // Unknown status, retry - return new Promise(resolve => { - setTimeout(() => poll().then(resolve), 200); + return new Promise((resolve, reject) => { + setTimeout(() => { + poll().then(resolve).catch(reject); + }, 200); }); } }) .catch(error => { if (attempts < maxAttempts) { console.log(`Poll error (attempt ${attempts}):`, error.message); - return new Promise(resolve => { - setTimeout(() => poll().then(resolve), 200); + return new Promise((resolve, reject) => { + setTimeout(() => { + poll().then(resolve).catch(reject); + }, 200); }); } else { throw error; @@ -1347,21 +1365,27 @@

🔐 Bearer Token Setup

// Still pending, retry with increasing delay const delay = Math.min(100 + (attempts * 50), 500); console.log(`Golden hash still processing, retrying in ${delay}ms...`); - return new Promise(resolve => { - setTimeout(() => poll().then(resolve), delay); + return new Promise((resolve, reject) => { + setTimeout(() => { + poll().then(resolve).catch(reject); + }, delay); }); } else { // Unknown status, retry - return new Promise(resolve => { - setTimeout(() => poll().then(resolve), 200); + return new Promise((resolve, reject) => { + setTimeout(() => { + poll().then(resolve).catch(reject); + }, 200); }); } }) .catch(error => { if (attempts < maxAttempts) { console.log(`Poll error (attempt ${attempts}):`, error.message); - return new Promise(resolve => { - setTimeout(() => poll().then(resolve), 200); + return new Promise((resolve, reject) => { + setTimeout(() => { + poll().then(resolve).catch(reject); + }, 200); }); } else { throw error; From 6e0bfc7619ca4a29a9e13573af6dc0bfeec2a16d Mon Sep 17 00:00:00 2001 From: Annaqi Date: Sat, 22 Nov 2025 18:45:01 +0800 Subject: [PATCH 4/4] chore: cleaned up comments --- src/cpu_monitor.c | 40 ++--- src/net/api/api.c | 339 +++++++++++++++++-------------------- src/net/dhcp/dhcpserver.c | 147 +++++++++++----- src/net/http/http_server.c | 82 +++++++-- src/net/wifi_ap.c | 135 ++++++--------- 5 files changed, 386 insertions(+), 357 deletions(-) diff --git a/src/cpu_monitor.c b/src/cpu_monitor.c index a5d5640..8a22c1a 100644 --- a/src/cpu_monitor.c +++ b/src/cpu_monitor.c @@ -1,10 +1,3 @@ -/** - * CPU Utilization Monitoring - * - * Tracks idle task execution time to calculate real CPU utilization. - * Uses FreeRTOS idle hook to increment idle tick counter. - */ - #include #include #include "FreeRTOS.h" @@ -15,10 +8,9 @@ #include #include "pico/time.h" #include "serial.h" -// Forward declaration for cpu_get_percent (optional header could be added later) - - -// Tick-based idle accounting reinstated for fallback stability. +/******************************************************************************* + * @brief Idle hook counter used for fallback CPU accounting. + ******************************************************************************/ volatile uint32_t g_idleTicks = 0; void vApplicationIdleHook(void) { static uint32_t lastTick = 0; @@ -29,9 +21,10 @@ void vApplicationIdleHook(void) { } } -// Return CPU usage percent (integer 0-100) using FreeRTOS run-time stats. -// Accurate path: sum per-task run time counters and derive busy = total - idle. -// Uses a minimum delta window to avoid noisy tiny samples. +/******************************************************************************* + * @brief Compute CPU utilization percentage using FreeRTOS runtime stats. + * @return CPU usage percent (0-100), last sampled value if insufficient data. + ******************************************************************************/ uint32_t cpu_get_percent(void) { #define CPU_MON_MAX_TASKS 48 @@ -42,13 +35,12 @@ uint32_t cpu_get_percent(void) static uint32_t acc_idle = 0; static uint32_t last_percent = 0; - // If scheduler not running yet, just return last cached percent. if (xTaskGetSchedulerState() != taskSCHEDULER_RUNNING) { return last_percent; } UBaseType_t numTasks = uxTaskGetNumberOfTasks(); - if (numTasks < 2) { // Need at least Idle + one other + if (numTasks < 2) { return last_percent; } @@ -71,7 +63,7 @@ uint32_t cpu_get_percent(void) } } - if (last_total == 0) { // establish baseline + if (last_total == 0) { last_total = sumAll; last_idle = sumIdle; return last_percent; @@ -86,12 +78,11 @@ uint32_t cpu_get_percent(void) return last_percent; } - // Accumulate to reach ~100ms window at 10kHz runtime counter (threshold = 1000 ticks) acc_total += dTotal; acc_idle += dIdle; - const uint32_t MIN_RT_DELTA = 1000; // ~100ms + const uint32_t MIN_RT_DELTA = 1000; if (acc_total < MIN_RT_DELTA) { - return last_percent; // wait for sufficient window + return last_percent; } dTotal = acc_total; @@ -104,15 +95,16 @@ uint32_t cpu_get_percent(void) } uint32_t busy = dTotal - dIdle; - uint32_t percent = (busy * 100U + (dTotal / 2U)) / dTotal; // rounded + uint32_t percent = (busy * 100U + (dTotal / 2U)) / dTotal; last_percent = percent; return percent; } -// Provide the runtime counter for FreeRTOS stats without including Pico headers in FreeRTOSConfig.h +/******************************************************************************* + * @brief FreeRTOS runtime counter hook (~10kHz) for stats. + * @return Runtime counter value. + ******************************************************************************/ uint32_t freertos_runtime_counter_get(void) { - // Scale microsecond hardware timer to ~10kHz resolution (100 microseconds per tick) - // Improves stability of short-window measurements and extends wrap period. return (uint32_t)(time_us_64() / 100ULL); } diff --git a/src/net/api/api.c b/src/net/api/api.c index 5df0f10..68721cd 100644 --- a/src/net/api/api.c +++ b/src/net/api/api.c @@ -18,38 +18,30 @@ #include "cpu_monitor.h" #include "wifi_ap.h" -// Forward-declared timer for deferred AP password rotation after claim #include "FreeRTOS.h" #include "task.h" #include "timers.h" #include "semphr.h" -// Throttled API logging: set to 1 to enable verbose API logs to serial -#ifndef API_DEBUG -#define API_DEBUG 0 -#endif -#if API_DEBUG -#define API_DBG(...) ////print_dbg(__VA_ARGS__) +#ifdef DEBUG +#define API_DBG(...) print_dbg(__VA_ARGS__) #else #define API_DBG(...) do { } while (0) #endif -// State: has the device been claimed already? static bool g_claimed = false; -// Cache the last generated password so we can optionally re-display or debug -static char g_last_psk[33] = ""; // 32 chars + NUL +static char g_last_psk[33] = ""; -// Bearer token authentication state -static char g_bearer_token[65] = ""; // 64 hex chars + NUL (256-bit token) +static char g_bearer_token[65] = ""; static bool g_bearer_token_generated = false; -// Token pubkey caching/prefetch handled by crypt.c now - -// One-shot timer callback to restart AP with new password -// Worker task to perform AP restart in a normal task context (not timer task) +/******************************************************************************* + * @brief Restart the AP with the latest password after claim. + * @param arg Unused task argument. + * @return void + ******************************************************************************/ static void ap_restart_task(void *arg) { (void)arg; - // Small delay to ensure response left the device stack vTaskDelay(pdMS_TO_TICKS(50)); if (g_last_psk[0] != '\0') { wifi_ap_rotate_password(g_last_psk); @@ -57,32 +49,45 @@ static void ap_restart_task(void *arg) { vTaskDelete(NULL); } +/******************************************************************************* + * @brief Timer callback that schedules AP rotation work. + * @param xTimer Unused timer handle. + * @return void + ******************************************************************************/ static void ap_rotate_timer_cb(TimerHandle_t xTimer) { (void)xTimer; - // Create detached worker to do the heavy lifting (deinit/init may block) xTaskCreate(ap_restart_task, "ap_rst", 1024, NULL, tskIDLE_PRIORITY + 2, NULL); } -// Generate a random WPA2 passphrase (length between 16 and 24) using get_rand_32() +/******************************************************************************* + * @brief Generate a random WPA2 passphrase. + * @param out Destination buffer for the passphrase. + * @param out_len Size of the destination buffer. + * @return void + ******************************************************************************/ static void generate_random_psk(char *out, size_t out_len) { static const char charset[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; size_t charset_len = sizeof(charset) - 1; if (out_len == 0) return; uint32_t seed = get_rand_32(); - size_t target = 16; // fixed length for now + size_t target = 16; if (target > out_len - 1) target = out_len - 1; for (size_t i = 0; i < target; i++) { - // Mix hardware random each iteration for unpredictability uint32_t r = get_rand_32() ^ (seed + i * 0x9E3779B1u); out[i] = charset[r % charset_len]; } out[target] = '\0'; } -// Generate a random bearer token (256-bit = 64 hex chars) +/******************************************************************************* + * @brief Generate a random 256-bit bearer token as hex. + * @param out Destination buffer for the hex token. + * @param out_len Size of the destination buffer. + * @return void + ******************************************************************************/ static void generate_bearer_token(char *out, size_t out_len) { static const char HEX[] = "0123456789abcdef"; - if (out_len < 65) return; // Need at least 65 bytes (64 hex + NUL) + if (out_len < 65) return; for (int chunk = 0; chunk < 8; ++chunk) { uint32_t r = get_rand_32(); @@ -96,25 +101,24 @@ static void generate_bearer_token(char *out, size_t out_len) { out[64] = '\0'; } -// Check if request has valid bearer token in Authorization header -// Returns true if token is valid, false otherwise +/******************************************************************************* + * @brief Validate bearer token presence and match. + * @param request Raw HTTP request buffer. + * @return true if the bearer token matches the generated token; false otherwise. + ******************************************************************************/ bool http_validate_bearer_token(const char *request) { if (!g_bearer_token_generated || g_bearer_token[0] == '\0') { - // No token has been generated yet - reject all auth'd endpoints return false; } - // Look for "Authorization: Bearer " header const char *auth_header = strstr(request, "Authorization: Bearer "); if (!auth_header) { return false; } - // Extract the token from the header const char *token_start = auth_header + strlen("Authorization: Bearer "); char request_token[65]; - // Read up to 64 chars or until we hit whitespace/newline int i = 0; while (i < 64 && token_start[i] != '\r' && token_start[i] != '\n' && token_start[i] != ' ' && token_start[i] != '\0') { request_token[i] = token_start[i]; @@ -122,7 +126,6 @@ bool http_validate_bearer_token(const char *request) { } request_token[i] = '\0'; - // Compare tokens (constant-time to prevent timing attacks) int match = 1; for (int j = 0; j < 64; j++) { if (g_bearer_token[j] != request_token[j]) { @@ -134,7 +137,12 @@ bool http_validate_bearer_token(const char *request) { } -// Claim handler: if not claimed, generate password, respond, then schedule AP restart. +/******************************************************************************* + * @brief Handle initial claim flow and rotate the AP password. + * @param pcb TCP control block for the client connection. + * @param request Incoming HTTP request. + * @return void + ******************************************************************************/ static void claim_handler(struct tcp_pcb *pcb, const char *request) { (void)request; API_DBG("[API] claim_handler called\n"); @@ -145,13 +153,11 @@ static void claim_handler(struct tcp_pcb *pcb, const char *request) { generate_random_psk(g_last_psk, sizeof(g_last_psk)); g_claimed = true; - // Create one-shot timer to rotate AP after grace period so response is delivered first. - const uint32_t GRACE_MS = 750; // client sees password then AP restarts + const uint32_t GRACE_MS = 750; TimerHandle_t t = xTimerCreate("ap_rot", pdMS_TO_TICKS(GRACE_MS), pdFALSE, NULL, ap_rotate_timer_cb); if (t) { xTimerStart(t, 0); } else { - // Fallback: rotate immediately ap_rotate_timer_cb(NULL); } @@ -163,16 +169,18 @@ static void claim_handler(struct tcp_pcb *pcb, const char *request) { http_send_json(pcb, 200, body); } -// Generate bearer token handler - POST /api/auth/generate-token -// Returns a bearer token to be used for authenticating subsequent API requests -// Can only be called once per device startup (when token is not yet generated) +/******************************************************************************* + * @brief Issue a bearer token for authenticating future API requests. + * @param pcb TCP control block for the client connection. + * @param request Incoming HTTP request. + * @return void + ******************************************************************************/ static void generate_token_handler(struct tcp_pcb *pcb, const char *request) { (void)request; API_DBG("[API] generate_token_handler called\n"); #ifndef DEBUG if (g_bearer_token_generated) { - // Token already generated - return 409 Conflict http_send_json(pcb, 409, "{\"error\":\"token_already_generated\",\"message\":\"Bearer token has already been issued for this device session\"}"); return; } @@ -182,13 +190,11 @@ static void generate_token_handler(struct tcp_pcb *pcb, const char *request) { } #endif - // Generate new bearer token generate_bearer_token(g_bearer_token, sizeof(g_bearer_token)); g_bearer_token_generated = true; API_DBG("[API] Bearer token generated: %s\n", g_bearer_token); - // Return the token to the client char body[200]; int n = snprintf(body, sizeof(body), "{\"status\":\"ok\",\"bearer_token\":\"%s\",\"message\":\"Store this token securely - it is your device password\"}", @@ -198,22 +204,34 @@ static void generate_token_handler(struct tcp_pcb *pcb, const char *request) { http_send_json(pcb, 200, body); } -// CPU utilization tracking handled inside cpu_monitor (runtime-only) - +/******************************************************************************* + * @brief Respond to ping health checks. + * @param pcb TCP control block for the client connection. + * @param request Incoming HTTP request. + * @return void + ******************************************************************************/ static void ping_handler(struct tcp_pcb *pcb, const char *request) { (void)request; http_send_json(pcb, 200, "{\"message\":\"pong\"}"); } -// Lightweight health endpoint for quick connectivity checks +/******************************************************************************* + * @brief Lightweight health endpoint for quick connectivity checks. + * @param pcb TCP control block for the client connection. + * @param request Incoming HTTP request. + * @return void + ******************************************************************************/ static void health_handler(struct tcp_pcb *pcb, const char *request) { (void)request; http_send_json(pcb, 200, "{\"ok\":true}"); } -/* -Status handler - returns system status information, including attecc status and uptime -*/ +/******************************************************************************* + * @brief Return system status including provisioning state and uptime. + * @param pcb TCP control block for the client connection. + * @param request Incoming HTTP request. + * @return void + ******************************************************************************/ static void status_handler(struct tcp_pcb *pcb, const char *request) { (void)request; API_DBG("[API] status_handler called\n"); @@ -242,50 +260,38 @@ static void status_handler(struct tcp_pcb *pcb, const char *request) { API_DBG("[API] response sent\n"); } -/** - * Network info handler - returns AP SSID, client IPs, and MAC addresses - * - * Requirements: - * R-4.6.1: Display SSID and WPA2-PSK status - * R-4.6.2: Display connected client IP addresses - * R-4.6.3: Display MAC address + IP for each client - * R-4.6.4: Refresh every 5 seconds (handled by client-side) - */ +/******************************************************************************* + * @brief Return AP network info including SSID and connected clients. + * @param pcb TCP control block for the client connection. + * @param request Incoming HTTP request. + * @return void + ******************************************************************************/ static void network_handler(struct tcp_pcb *pcb, const char *request) { (void)request; API_DBG("[API] network_handler called\n"); - // Get AP IP const ip4_addr_t *ap_ip = netif_ip4_addr(&cyw43_state.netif[CYW43_ITF_AP]); const char *ap_ip_str = ap_ip ? ip4addr_ntoa(ap_ip) : "192.168.4.1"; - // Get DHCP server to query connected clients const dhcp_server_t *dhcp = get_dhcp_server(); - // Build JSON response with network info and connected clients - // Maximum size: headers + 8 clients * (MAC 17 bytes + IP 15 bytes + JSON overhead) char body[768]; int pos = 0; - // Start JSON header pos += snprintf(body + pos, sizeof(body) - pos, "{\"ssid\":\"MASTR-Token\",\"security\":\"WPA2-PSK\",\"ap_ip\":\"%s\",\"clients\":[", ap_ip_str); - // Add connected clients from DHCP leases #define DHCPS_MAX_IP 8 #define DHCPS_BASE_IP 16 int client_count = 0; for (int i = 0; i < DHCPS_MAX_IP; i++) { - // Check if lease is active (expiry is non-zero and not expired) if (dhcp->lease[i].expiry != 0) { - // Only add comma if not first client if (client_count > 0) { pos += snprintf(body + pos, sizeof(body) - pos, ","); } - // Format MAC address from lease const uint8_t *mac = dhcp->lease[i].mac; uint8_t ip_octet = DHCPS_BASE_IP + i; @@ -300,7 +306,6 @@ static void network_handler(struct tcp_pcb *pcb, const char *request) { } } - // Close JSON pos += snprintf(body + pos, sizeof(body) - pos, "]}"); API_DBG("[API] network info: SSID=MASTR-Token, AP_IP=%s, clients=%d\n", ap_ip_str, client_count); @@ -308,17 +313,16 @@ static void network_handler(struct tcp_pcb *pcb, const char *request) { API_DBG("[API] network response sent\n"); } -/** - * RAM info handler - returns RAM usage - * - * Requirements: - * R-4.5.2: Display RAM usage (total, used, free) - */ +/******************************************************************************* + * @brief Report RAM usage totals and percentages. + * @param pcb TCP control block for the client connection. + * @param request Incoming HTTP request. + * @return void + ******************************************************************************/ static void ram_handler(struct tcp_pcb *pcb, const char *request) { (void)request; API_DBG("[API] ram_handler called\n"); - // Get heap info size_t free_heap = xPortGetFreeHeapSize(); size_t total_heap = configTOTAL_HEAP_SIZE; size_t used_heap = total_heap - free_heap; @@ -326,7 +330,6 @@ static void ram_handler(struct tcp_pcb *pcb, const char *request) { API_DBG("[API] heap - total: %u, used: %u, free: %u\n", (unsigned)total_heap, (unsigned)used_heap, (unsigned)free_heap); - // Build JSON response char body[256]; int n = snprintf(body, sizeof(body), "{\"ram_total_kb\":%u,\"ram_used_kb\":%u,\"ram_free_kb\":%u,\"ram_used_percent\":%u}", @@ -341,15 +344,11 @@ static void ram_handler(struct tcp_pcb *pcb, const char *request) { API_DBG("[API] ram response sent\n"); } -/** - * Temperature handler - returns internal MCU temperature (°C) - * - * Safe, low-overhead read using Pico SDK ADC driver. We enable the - * internal sensor, discard the first sample, average a few readings, - * then convert to degrees Celsius using the standard formula. - */ +/******************************************************************************* + * @brief Read the internal MCU temperature in Celsius. + * @return Measured temperature in degrees Celsius. + ******************************************************************************/ static float read_internal_temperature_c(void) { - // One-time ADC init (idempotent) static bool adc_ready = false; if (!adc_ready) { adc_init(); @@ -357,13 +356,10 @@ static float read_internal_temperature_c(void) { adc_ready = true; } - // Select internal temperature sensor channel (ADC input 4) adc_select_input(4); - // Throw away the first reading after switching channels/sensor enable (void)adc_read(); - // Average several samples for stability const int SAMPLES = 8; uint32_t acc = 0; for (int i = 0; i < SAMPLES; i++) { @@ -371,21 +367,24 @@ static float read_internal_temperature_c(void) { } float raw = acc / (float)SAMPLES; - // Convert raw 12-bit ADC reading to voltage (assumes 3.3V reference) const float VREF = 3.3f; float v = raw * VREF / 4095.0f; - // Pico formula: 27°C at 0.706V, slope 1.721 mV/°C float temp_c = 27.0f - (v - 0.706f) / 0.001721f; return temp_c; } +/******************************************************************************* + * @brief Serve current MCU temperature. + * @param pcb TCP control block for the client connection. + * @param request Incoming HTTP request. + * @return void + ******************************************************************************/ static void temperature_handler(struct tcp_pcb *pcb, const char *request) { (void)request; API_DBG("[API] temperature_handler called\n"); float t = read_internal_temperature_c(); - // Clamp to a sane range in case of transient anomalies if (t < -40.0f) t = -40.0f; if (t > 125.0f) t = 125.0f; @@ -396,17 +395,12 @@ static void temperature_handler(struct tcp_pcb *pcb, const char *request) { http_send_json(pcb, 200, body); } -/** - * CPU utilization handler - returns CPU usage percentage - * - * Requirements: - * R-4.5.1: Display CPU utilization - * - * Formula: CPU% = (TotalDelta - IdleDelta) / TotalDelta * 100 - * - * Uses FreeRTOS idle hook to accurately measure idle vs busy time. - * g_idleTicks is incremented by vApplicationIdleHook() each time idle task runs. - */ +/******************************************************************************* + * @brief Serve current CPU utilization percentage. + * @param pcb TCP control block for the client connection. + * @param request Incoming HTTP request. + * @return void + ******************************************************************************/ static void cpu_handler(struct tcp_pcb *pcb, const char *request) { (void)request; API_DBG("[API] cpu_handler called\n"); @@ -418,11 +412,12 @@ static void cpu_handler(struct tcp_pcb *pcb, const char *request) { http_send_json(pcb, 200, body); } -/** - * Token info handler - returns token's public key for provisioning - * GET /api/provision/token_info - * Returns: {"token_pubkey":""} - */ +/******************************************************************************* + * @brief Serve the token public key for provisioning. + * @param pcb TCP control block for the client connection. + * @param request Incoming HTTP request. + * @return void + ******************************************************************************/ static void token_info_handler(struct tcp_pcb *pcb, const char *request){ (void)request; bool ready = false; @@ -443,83 +438,65 @@ static void token_info_handler(struct tcp_pcb *pcb, const char *request){ http_send_json(pcb, 200, body); } -/** - * Set host public key handler (non-blocking) - * POST /api/provision/host_pubkey - * Expects: 64-byte hex string in request body (128 hex chars) - * Returns: {"status":"accepted"} or {"error":"..."} - */ +/******************************************************************************* + * @brief Accept and validate host public key submission. + * @param pcb TCP control block for the client connection. + * @param request Incoming HTTP request. + * @return void + ******************************************************************************/ static void set_host_pubkey_handler(struct tcp_pcb *pcb, const char *request) { - ////print_dbg("API: set_host_pubkey_handler called (non-blocking)\n"); - - // Find the request body (after double CRLF) const char *body_start = strstr(request, "\r\n\r\n"); if (!body_start) { - ////print_dbg("API: missing request body\n"); http_send_json(pcb, 400, "{\"error\":\"missing_body\"}"); return; } - body_start += 4; // Skip past "\r\n\r\n" + body_start += 4; - // Trim whitespace and newlines from the end const char *body_end = body_start + strlen(body_start); while (body_end > body_start && (body_end[-1] == '\r' || body_end[-1] == '\n' || body_end[-1] == ' ')) { body_end--; } - // Create null-terminated string for the hex data size_t hex_len = body_end - body_start; - ////print_dbg("API: received hex data length: %zu\n", hex_len); if (hex_len != 128) { - ////print_dbg("API: invalid hex length, expected 128, got %zu\n", hex_len); char error_msg[100]; snprintf(error_msg, sizeof(error_msg), "{\"error\":\"invalid_length\",\"expected\":128,\"got\":%zu}", hex_len); http_send_json(pcb, 400, error_msg); return; } - // Validate hex format before proceeding for (size_t i = 0; i < 128; i++) { char c = body_start[i]; if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) { - ////print_dbg("API: invalid hex character at position %zu: '%c'\n", i, c); http_send_json(pcb, 400, "{\"error\":\"invalid_hex_format\"}"); return; } } - ////print_dbg("API: hex validation passed, creating null-terminated string\n"); - - // Create null-terminated string for the crypto function char hex_str[129]; memcpy(hex_str, body_start, 128); hex_str[128] = '\0'; - ////print_dbg("API: requesting non-blocking host pubkey write\n"); - - // Use the new non-blocking crypto function bool write_ready, write_failed; bool accepted = crypto_request_host_pubkey_write(hex_str, &write_ready, &write_failed); if (!accepted) { - ////print_dbg("API: Host pubkey write request rejected (already pending)\n"); http_send_json(pcb, 409, "{\"error\":\"write_pending\",\"retry_ms\":100}"); return; } - ////print_dbg("API: Host pubkey write request accepted\n"); http_send_json(pcb, 202, "{\"status\":\"accepted\",\"message\":\"write_queued\"}"); } -/** - * Get host public key handler (non-blocking) - * GET /api/provision/host_pubkey/get - * Returns: {"host_pubkey":""} or {"status":"reading"} or {"error":"..."} - */ +/******************************************************************************* + * @brief Return the host public key if available. + * @param pcb TCP control block for the client connection. + * @param request Incoming HTTP request. + * @return void + ******************************************************************************/ static void get_host_pubkey_handler(struct tcp_pcb *pcb, const char *request) { (void)request; - ////print_dbg("API: get_host_pubkey_handler called (non-blocking)\n"); const char *hex_pubkey = NULL; bool ready = false; @@ -528,34 +505,29 @@ static void get_host_pubkey_handler(struct tcp_pcb *pcb, const char *request) { crypto_get_cached_host_pubkey_hex(&hex_pubkey, &ready, &failed); if (failed) { - ////print_dbg("API: Host pubkey read failed\n"); http_send_json(pcb, 500, "{\"error\":\"read_failed\"}"); return; } if (ready && hex_pubkey && hex_pubkey[0] != '\0') { - // Return cached result char body[180]; int n = snprintf(body, sizeof(body), "{\"host_pubkey\":\"%s\",\"cached\":true}", hex_pubkey); (void)n; - ////print_dbg("API: Returning cached host pubkey\n"); http_send_json(pcb, 200, body); return; } - // Still reading - ////print_dbg("API: Host pubkey not ready yet\n"); http_send_json(pcb, 503, "{\"status\":\"reading\",\"retry_ms\":100}"); } -/** - * Get host public key write status handler (non-blocking) - * GET /api/provision/host_pubkey/status - * Returns: {"status":"ready|pending|failed"} - */ +/******************************************************************************* + * @brief Report status of the host public key write operation. + * @param pcb TCP control block for the client connection. + * @param request Incoming HTTP request. + * @return void + ******************************************************************************/ static void host_pubkey_status_handler(struct tcp_pcb *pcb, const char *request) { (void)request; - ////print_dbg("API: host_pubkey_status_handler called\n"); bool write_ready = false; bool write_failed = false; @@ -571,12 +543,13 @@ static void host_pubkey_status_handler(struct tcp_pcb *pcb, const char *request) } } -/** - * Set golden hash handler - POST /api/provision/golden_hash - * Non-blocking version: validates input then triggers async crypto operation - */ +/******************************************************************************* + * @brief Accept a golden hash value and trigger async processing. + * @param pcb TCP control block for the client connection. + * @param request Incoming HTTP request. + * @return void + ******************************************************************************/ static void set_golden_hash_handler(struct tcp_pcb *pcb, const char *request) { - // Extract hex data from request body const char *body_start = strstr(request, "\r\n\r\n"); if (!body_start) { http_send_json(pcb, 400, "{\"error\":\"missing_body\"}"); @@ -584,7 +557,6 @@ static void set_golden_hash_handler(struct tcp_pcb *pcb, const char *request) { } body_start += 4; - // Trim whitespace const char *body_end = body_start + strlen(body_start); while (body_end > body_start && (body_end[-1] == '\r' || body_end[-1] == '\n' || body_end[-1] == ' ')) { body_end--; @@ -596,7 +568,6 @@ static void set_golden_hash_handler(struct tcp_pcb *pcb, const char *request) { return; } - // Validate hex format for (size_t i = 0; i < 64; i++) { char c = body_start[i]; if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) { @@ -605,30 +576,29 @@ static void set_golden_hash_handler(struct tcp_pcb *pcb, const char *request) { } } - // Convert hex to bytes uint8_t golden_hash[32]; for (int i = 0; i < 32; i++) { char hex_pair[3] = {body_start[i*2], body_start[i*2 + 1], '\0'}; golden_hash[i] = (uint8_t)strtol(hex_pair, NULL, 16); } - // Trigger async golden hash operation (non-blocking) bool queued = crypto_spawn_golden_hash_task_with_data(golden_hash); if (!queued) { http_send_json(pcb, 503, "{\"error\":\"task_busy\"}"); return; } - // Return immediate response - client should poll status http_send_json(pcb, 202, "{\"status\":\"accepted\",\"message\":\"golden_hash_operation_queued\"}"); } -/** - * Get golden hash status handler - GET /api/provision/golden_hash/status - * Returns the result of the most recent golden hash operation - */ +/******************************************************************************* + * @brief Report the status of the golden hash operation. + * @param pcb TCP control block for the client connection. + * @param request Incoming HTTP request. + * @return void + ******************************************************************************/ static void golden_hash_status_handler(struct tcp_pcb *pcb, const char *request) { - (void)request; // Unused parameter + (void)request; bool write_ready = false; bool write_failed = false; @@ -637,7 +607,6 @@ static void golden_hash_status_handler(struct tcp_pcb *pcb, const char *request) crypto_get_golden_hash_write_status(&write_ready, &write_failed, golden_hash_result); if (write_ready) { - // Convert result hash to hex char hex_response[65]; for (int i = 0; i < 32; i++) { sprintf(&hex_response[i*2], "%02x", golden_hash_result[i]); @@ -649,14 +618,19 @@ static void golden_hash_status_handler(struct tcp_pcb *pcb, const char *request) } else if (write_failed) { http_send_json(pcb, 200, "{\"status\":\"error\",\"error\":\"crypto_operation_failed\"}"); } else { - // Still processing or idle http_send_json(pcb, 200, "{\"status\":\"processing\"}"); } } #ifdef DEBUG +/******************************************************************************* + * @brief Debug endpoint to reset provisioning state. + * @param pcb TCP control block for the client connection. + * @param request Incoming HTTP request. + * @return void + ******************************************************************************/ static void reset_api_handler(struct tcp_pcb *pcb, const char *request) { - (void)request; // Unused parameter + (void)request; protocol_unprovision(); @@ -666,13 +640,15 @@ static void reset_api_handler(struct tcp_pcb *pcb, const char *request) { } #endif +/******************************************************************************* + * @brief Register all API routes with the HTTP server. + * @return void + ******************************************************************************/ void api_register_routes(void) { - // Public endpoints (no auth required) http_register("/api/ping", ping_handler); http_register("/api/health", health_handler); - http_register("/api/auth/generate-token", generate_token_handler); // Generate bearer token (one-time) + http_register("/api/auth/generate-token", generate_token_handler); - // Protected endpoints (require bearer token in Authorization header) http_register_auth("/api/status", status_handler, true); http_register_auth("/api/network", network_handler, true); http_register_auth("/api/ram", ram_handler, true); @@ -682,40 +658,29 @@ void api_register_routes(void) { print_dbg("protocol state is currently at: 0x%02X", g_protocol_state.current_state); if(g_protocol_state.current_state == PROTOCOL_STATE_UNPROVISIONED){ - // Provisioning token public key endpoint (single canonical path) - protected http_register_auth("/api/provision/token_info", token_info_handler, true); - // Provisioning host public key endpoints (non-blocking versions) - protected - http_register_auth("/api/provision/host_pubkey", set_host_pubkey_handler, true); // POST to set - http_register_auth("/api/provision/host_pubkey/get", get_host_pubkey_handler, true); // GET to read - http_register_auth("/api/provision/host_pubkey/status", host_pubkey_status_handler, true); // GET write status - // Provisioning golden hash endpoints (non-blocking versions) - protected - http_register_auth("/api/provision/golden_hash", set_golden_hash_handler, true); // POST to set - http_register_auth("/api/provision/golden_hash/status", golden_hash_status_handler, true); // GET status + http_register_auth("/api/provision/host_pubkey", set_host_pubkey_handler, true); + http_register_auth("/api/provision/host_pubkey/get", get_host_pubkey_handler, true); + http_register_auth("/api/provision/host_pubkey/status", host_pubkey_status_handler, true); + http_register_auth("/api/provision/golden_hash", set_golden_hash_handler, true); + http_register_auth("/api/provision/golden_hash/status", golden_hash_status_handler, true); }else{ print_dbg("protocol has already been provisioned, skipping provisioning endpoints..."); } #ifdef DEBUG if(g_protocol_state.current_state != PROTOCOL_STATE_UNPROVISIONED){ - // when in debug, register endpoints anyway. http_register_auth("/api/provision/token_info", token_info_handler, true); - http_register_auth("/api/provision/host_pubkey", set_host_pubkey_handler, true); // POST to set - http_register_auth("/api/provision/host_pubkey/get", get_host_pubkey_handler, true); // GET to read - http_register_auth("/api/provision/host_pubkey/status", host_pubkey_status_handler, true); // GET write status - http_register_auth("/api/provision/golden_hash", set_golden_hash_handler, true); // POST to set - http_register_auth("/api/provision/golden_hash/status", golden_hash_status_handler, true); // GET status - - // also register an additional viewpoint for token reset. + http_register_auth("/api/provision/host_pubkey", set_host_pubkey_handler, true); + http_register_auth("/api/provision/host_pubkey/get", get_host_pubkey_handler, true); + http_register_auth("/api/provision/host_pubkey/status", host_pubkey_status_handler, true); + http_register_auth("/api/provision/golden_hash", set_golden_hash_handler, true); + http_register_auth("/api/provision/golden_hash/status", golden_hash_status_handler, true); http_register_auth("/api/provision/reset", reset_api_handler, true); } #endif - // Ask crypt layer to spawn prefetch task (low priority) crypto_spawn_pubkey_prefetch(); - // Start background task for host pubkey operations (non-blocking) crypto_spawn_host_pubkey_task(); - // Start background task for golden hash operations (non-blocking) crypto_spawn_golden_hash_task(); - - ////print_dbg("API routes registered\n"); } diff --git a/src/net/dhcp/dhcpserver.c b/src/net/dhcp/dhcpserver.c index 529695b..e65e16c 100644 --- a/src/net/dhcp/dhcpserver.c +++ b/src/net/dhcp/dhcpserver.c @@ -24,10 +24,6 @@ * THE SOFTWARE. */ -// For DHCP specs see: -// https://www.ietf.org/rfc/rfc2131.txt -// https://tools.ietf.org/html/rfc2132 -- DHCP Options and BOOTP Vendor Extensions - #include #include #include @@ -63,33 +59,35 @@ #define PORT_DHCP_SERVER (67) #define PORT_DHCP_CLIENT (68) -#define DEFAULT_LEASE_TIME_S (24 * 60 * 60) // in seconds +#define DEFAULT_LEASE_TIME_S (24 * 60 * 60) #define MAC_LEN (6) #define MAKE_IP4(a, b, c, d) ((a) << 24 | (b) << 16 | (c) << 8 | (d)) typedef struct { - uint8_t op; // message opcode - uint8_t htype; // hardware address type - uint8_t hlen; // hardware address length + uint8_t op; + uint8_t htype; + uint8_t hlen; uint8_t hops; - uint32_t xid; // transaction id, chosen by client - uint16_t secs; // client seconds elapsed + uint32_t xid; + uint16_t secs; uint16_t flags; - uint8_t ciaddr[4]; // client IP address - uint8_t yiaddr[4]; // your IP address - uint8_t siaddr[4]; // next server IP address - uint8_t giaddr[4]; // relay agent IP address - uint8_t chaddr[16]; // client hardware address - uint8_t sname[64]; // server host name - uint8_t file[128]; // boot file name - uint8_t options[312]; // optional parameters, variable, starts with magic + uint8_t ciaddr[4]; + uint8_t yiaddr[4]; + uint8_t siaddr[4]; + uint8_t giaddr[4]; + uint8_t chaddr[16]; + uint8_t sname[64]; + uint8_t file[128]; + uint8_t options[312]; } dhcp_msg_t; -// Convert lwIP ip_addr_t to 4 bytes in dotted order (a.b.c.d) into buf[0..3]. -// Use the ip4_addrX() helpers to avoid endianness issues (ip4_addr_get_u32 -// can be in host byte order on little-endian platforms). Using the helpers -// guarantees we extract the octets in the correct network (dotted) order. +/******************************************************************************* + * @brief Convert lwIP IPv4 address to dotted-order bytes. + * @param a Source IP address. + * @param buf Destination buffer for four octets. + * @return void + ******************************************************************************/ static void ipaddr_to_bytes(const ip_addr_t *a, uint8_t *buf) { const ip4_addr_t *ip4 = ip_2_ip4((ip_addr_t *)a); buf[0] = ip4_addr1(ip4); @@ -98,21 +96,29 @@ static void ipaddr_to_bytes(const ip_addr_t *a, uint8_t *buf) { buf[3] = ip4_addr4(ip4); } +/******************************************************************************* + * @brief Allocate a UDP socket for DHCP. + * @param udp Output pointer for the PCB. + * @param cb_data User data passed to the callback. + * @param cb_udp_recv Receive callback. + * @return 0 on success, negative errno on failure. + ******************************************************************************/ static int dhcp_socket_new_dgram(struct udp_pcb **udp, void *cb_data, udp_recv_fn cb_udp_recv) { - // family is AF_INET - // type is SOCK_DGRAM - *udp = udp_new(); if (*udp == NULL) { return -ENOMEM; } - // Register callback udp_recv(*udp, cb_udp_recv, (void *)cb_data); - return 0; // success + return 0; } +/******************************************************************************* + * @brief Free a DHCP UDP socket. + * @param udp PCB pointer to clear. + * @return void + ******************************************************************************/ static void dhcp_socket_free(struct udp_pcb **udp) { if (*udp != NULL) { udp_remove(*udp); @@ -120,11 +126,26 @@ static void dhcp_socket_free(struct udp_pcb **udp) { } } +/******************************************************************************* + * @brief Bind a DHCP UDP socket to a port. + * @param udp PCB pointer. + * @param port Port number. + * @return lwIP status code. + ******************************************************************************/ static int dhcp_socket_bind(struct udp_pcb **udp, uint16_t port) { - // TODO convert lwIP errors to errno return udp_bind(*udp, IP_ANY_TYPE, port); } +/******************************************************************************* + * @brief Send a DHCP UDP datagram. + * @param udp PCB pointer. + * @param nif Network interface to send on (optional). + * @param buf Payload buffer. + * @param len Payload length. + * @param ip Destination IPv4 address. + * @param port Destination port. + * @return Bytes sent or error code. + ******************************************************************************/ static int dhcp_socket_sendto(struct udp_pcb **udp, struct netif *nif, const void *buf, size_t len, uint32_t ip, uint16_t port) { if (len > 0xffff) { len = 0xffff; @@ -155,6 +176,12 @@ static int dhcp_socket_sendto(struct udp_pcb **udp, struct netif *nif, const voi return len; } +/******************************************************************************* + * @brief Find a DHCP option within an options buffer. + * @param opt Options buffer start. + * @param cmd Option code to search for. + * @return Pointer to matching option or NULL. + ******************************************************************************/ static uint8_t *opt_find(uint8_t *opt, uint8_t cmd) { for (int i = 0; i < 308 && opt[i] != DHCP_OPT_END;) { if (opt[i] == cmd) { @@ -165,6 +192,14 @@ static uint8_t *opt_find(uint8_t *opt, uint8_t cmd) { return NULL; } +/******************************************************************************* + * @brief Write a DHCP option with arbitrary length. + * @param opt Cursor pointer updated on return. + * @param cmd Option code. + * @param n Length of data. + * @param data Pointer to data bytes. + * @return void + ******************************************************************************/ static void opt_write_n(uint8_t **opt, uint8_t cmd, size_t n, const void *data) { uint8_t *o = *opt; *o++ = cmd; @@ -173,6 +208,13 @@ static void opt_write_n(uint8_t **opt, uint8_t cmd, size_t n, const void *data) *opt = o + n; } +/******************************************************************************* + * @brief Write a one-byte DHCP option. + * @param opt Cursor pointer updated on return. + * @param cmd Option code. + * @param val Option value. + * @return void + ******************************************************************************/ static void opt_write_u8(uint8_t **opt, uint8_t cmd, uint8_t val) { uint8_t *o = *opt; *o++ = cmd; @@ -181,6 +223,13 @@ static void opt_write_u8(uint8_t **opt, uint8_t cmd, uint8_t val) { *opt = o; } +/******************************************************************************* + * @brief Write a four-byte DHCP option. + * @param opt Cursor pointer updated on return. + * @param cmd Option code. + * @param val Option value. + * @return void + ******************************************************************************/ static void opt_write_u32(uint8_t **opt, uint8_t cmd, uint32_t val) { uint8_t *o = *opt; *o++ = cmd; @@ -192,13 +241,21 @@ static void opt_write_u32(uint8_t **opt, uint8_t cmd, uint32_t val) { *opt = o; } +/******************************************************************************* + * @brief Process inbound DHCP packets and respond appropriately. + * @param arg DHCP server context. + * @param upcb UDP PCB (unused). + * @param p Incoming pbuf. + * @param src_addr Source address (unused). + * @param src_port Source port (unused). + * @return void + ******************************************************************************/ static void dhcp_server_process(void *arg, struct udp_pcb *upcb, struct pbuf *p, const ip_addr_t *src_addr, u16_t src_port) { dhcp_server_t *d = arg; (void)upcb; (void)src_addr; (void)src_port; - // This is around 548 bytes dhcp_msg_t dhcp_msg; #define DHCP_MIN_SIZE (240 + 3) @@ -212,15 +269,13 @@ static void dhcp_server_process(void *arg, struct udp_pcb *upcb, struct pbuf *p, } dhcp_msg.op = DHCPOFFER; - // Copy server IP into yiaddr as four network-order bytes ipaddr_to_bytes(&d->ip, dhcp_msg.yiaddr); uint8_t *opt = (uint8_t *)&dhcp_msg.options; - opt += 4; // assume magic cookie: 99, 130, 83, 99 + opt += 4; uint8_t *msgtype = opt_find(opt, DHCP_OPT_MSG_TYPE); if (msgtype == NULL) { - // A DHCP package without MSG_TYPE? goto ignore_request; } @@ -229,26 +284,21 @@ static void dhcp_server_process(void *arg, struct udp_pcb *upcb, struct pbuf *p, int yi = DHCPS_MAX_IP; for (int i = 0; i < DHCPS_MAX_IP; ++i) { if (memcmp(d->lease[i].mac, dhcp_msg.chaddr, MAC_LEN) == 0) { - // MAC match, use this IP address yi = i; break; } if (yi == DHCPS_MAX_IP) { - // Look for a free IP address if (memcmp(d->lease[i].mac, "\x00\x00\x00\x00\x00\x00", MAC_LEN) == 0) { - // IP available yi = i; } uint32_t expiry = d->lease[i].expiry << 16 | 0xffff; if ((int32_t)(expiry - cyw43_hal_ticks_ms()) < 0) { - // IP expired, reuse it memset(d->lease[i].mac, 0, MAC_LEN); yi = i; } } } if (yi == DHCPS_MAX_IP) { - // No more IP addresses left goto ignore_request; } dhcp_msg.yiaddr[3] = DHCPS_BASE_IP + yi; @@ -259,28 +309,21 @@ static void dhcp_server_process(void *arg, struct udp_pcb *upcb, struct pbuf *p, case DHCPREQUEST: { uint8_t *o = opt_find(opt, DHCP_OPT_REQUESTED_IP); if (o == NULL) { - // Should be NACK goto ignore_request; } uint8_t server_id_bytes[4]; ipaddr_to_bytes(&d->ip, server_id_bytes); if (memcmp(o + 2, server_id_bytes, 3) != 0) { - // Should be NACK goto ignore_request; } uint8_t yi = o[5] - DHCPS_BASE_IP; if (yi >= DHCPS_MAX_IP) { - // Should be NACK goto ignore_request; } if (memcmp(d->lease[yi].mac, dhcp_msg.chaddr, MAC_LEN) == 0) { - // MAC match, ok to use this IP address } else if (memcmp(d->lease[yi].mac, "\x00\x00\x00\x00\x00\x00", MAC_LEN) == 0) { - // IP unused, ok to use this IP address memcpy(d->lease[yi].mac, dhcp_msg.chaddr, MAC_LEN); } else { - // IP already in use - // Should be NACK goto ignore_request; } d->lease[yi].expiry = (cyw43_hal_ticks_ms() + DEFAULT_LEASE_TIME_S * 1000) >> 16; @@ -299,9 +342,9 @@ static void dhcp_server_process(void *arg, struct udp_pcb *upcb, struct pbuf *p, ipaddr_to_bytes(&d->nm, tmp_ip); opt_write_n(&opt, DHCP_OPT_SUBNET_MASK, 4, tmp_ip); ipaddr_to_bytes(&d->ip, tmp_ip); - opt_write_n(&opt, DHCP_OPT_ROUTER, 4, tmp_ip); // aka gateway; can have multiple addresses + opt_write_n(&opt, DHCP_OPT_ROUTER, 4, tmp_ip); ipaddr_to_bytes(&d->ip, tmp_ip); - opt_write_n(&opt, DHCP_OPT_DNS, 4, tmp_ip); // this server is the dns + opt_write_n(&opt, DHCP_OPT_DNS, 4, tmp_ip); opt_write_u32(&opt, DHCP_OPT_IP_LEASE_TIME, DEFAULT_LEASE_TIME_S); *opt++ = DHCP_OPT_END; struct netif *nif = ip_current_input_netif(); @@ -311,6 +354,13 @@ static void dhcp_server_process(void *arg, struct udp_pcb *upcb, struct pbuf *p, pbuf_free(p); } +/******************************************************************************* + * @brief Initialize and bind the DHCP server. + * @param d DHCP server context. + * @param ip Server IP address. + * @param nm Netmask. + * @return void + ******************************************************************************/ void dhcp_server_init(dhcp_server_t *d, ip_addr_t *ip, ip_addr_t *nm) { ip_addr_copy(d->ip, *ip); ip_addr_copy(d->nm, *nm); @@ -321,6 +371,11 @@ void dhcp_server_init(dhcp_server_t *d, ip_addr_t *ip, ip_addr_t *nm) { dhcp_socket_bind(&d->udp, PORT_DHCP_SERVER); } +/******************************************************************************* + * @brief Deinitialize the DHCP server and free resources. + * @param d DHCP server context. + * @return void + ******************************************************************************/ void dhcp_server_deinit(dhcp_server_t *d) { dhcp_socket_free(&d->udp); } diff --git a/src/net/http/http_server.c b/src/net/http/http_server.c index 4b5a40a..62f4511 100644 --- a/src/net/http/http_server.c +++ b/src/net/http/http_server.c @@ -1,6 +1,3 @@ -// Rebuilt HTTP server file: cleaned duplicates, minimal single-connection server -// with deferred close via tcp_sent to reduce intermittent curl failures. - #include "http_server.h" #include "lwip/pbuf.h" #include "lwip/tcp.h" @@ -10,17 +7,30 @@ #include "serial.h" -struct route_entry { - const char *path; +struct route_entry { + const char *path; http_handler_fn handler; - bool requires_auth; // Does this route require bearer token auth? + bool requires_auth; }; static struct route_entry routes[MAX_ROUTES]; +/******************************************************************************* + * @brief Register a public HTTP route. + * @param path Route path. + * @param handler Handler function. + * @return 0 on success, -1 if table is full. + ******************************************************************************/ int http_register(const char *path, http_handler_fn handler) { - return http_register_auth(path, handler, false); // Public by default + return http_register_auth(path, handler, false); } +/******************************************************************************* + * @brief Register an HTTP route with optional auth requirement. + * @param path Route path. + * @param handler Handler function. + * @param requires_auth Whether bearer auth is required. + * @return 0 on success, -1 if table is full. + ******************************************************************************/ int http_register_auth(const char *path, http_handler_fn handler, bool requires_auth) { for (int i = 0; i < MAX_ROUTES; ++i) { if (routes[i].path == NULL) { @@ -38,16 +48,27 @@ int http_register_auth(const char *path, http_handler_fn handler, bool requires_ static http_state_t g_state; +/******************************************************************************* + * @brief Reset HTTP connection state. + * @return void + ******************************************************************************/ static void reset_state(void) { g_state.request_len = 0; g_state.request[0] = '\0'; g_state.in_use = false; g_state.close_when_sent = false; - // Track connection closing http_connection_closed(); } +/******************************************************************************* + * @brief Send an HTTP response with headers and body. + * @param pcb TCP PCB. + * @param status Status line text. + * @param content_type Content-Type header value. + * @param body Response body. + * @return void + ******************************************************************************/ static void send_response(struct tcp_pcb *pcb, const char *status, const char *content_type, const char *body) { char header[512]; int header_len = snprintf(header, sizeof(header), @@ -68,6 +89,13 @@ static void send_response(struct tcp_pcb *pcb, const char *status, const char *c g_state.close_when_sent = true; } +/******************************************************************************* + * @brief Convenience for sending JSON responses. + * @param pcb TCP PCB. + * @param status_code HTTP status code. + * @param json_body JSON body string. + * @return void + ******************************************************************************/ void http_send_json(struct tcp_pcb *pcb, int status_code, const char *json_body) { char status[32]; switch (status_code) { @@ -83,7 +111,6 @@ static void handle_request(struct tcp_pcb *pcb, char *request) { char method[8], path[64]; sscanf(request, "%7s %63s", method, path); - // Handle CORS preflight OPTIONS requests if (strcmp(method, "OPTIONS") == 0) { const char *cors_response = "HTTP/1.1 204 No Content\r\n" @@ -100,7 +127,6 @@ static void handle_request(struct tcp_pcb *pcb, char *request) { for (int i = 0; i < MAX_ROUTES; ++i) { if (routes[i].path && strcmp(path, routes[i].path) == 0) { - // Check if this route requires authentication if (routes[i].requires_auth && !http_validate_bearer_token(request)) { send_response(pcb, "401 Unauthorized", "application/json", "{\"error\":\"unauthorized\",\"message\":\"Bearer token required. Call POST /api/auth/generate-token first.\"}"); @@ -127,6 +153,14 @@ static err_t http_sent(void *arg, struct tcp_pcb *pcb, u16_t len) { return ERR_OK; } +/******************************************************************************* + * @brief Receive callback for HTTP TCP connections. + * @param arg Unused arg pointer. + * @param pcb TCP PCB. + * @param p Incoming pbuf. + * @param err lwIP error code. + * @return lwIP status. + ******************************************************************************/ static err_t http_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err) { (void)arg; (void)err; if (!p) return http_close(pcb); @@ -148,6 +182,13 @@ static err_t http_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err return ERR_OK; } +/******************************************************************************* + * @brief Accept callback for incoming HTTP connections. + * @param arg Unused arg pointer. + * @param client_pcb Client TCP PCB. + * @param err lwIP error code. + * @return lwIP status. + ******************************************************************************/ static err_t http_accept(void *arg, struct tcp_pcb *client_pcb, err_t err) { (void)arg; (void)err; if (g_state.in_use) { @@ -155,7 +196,6 @@ static err_t http_accept(void *arg, struct tcp_pcb *client_pcb, err_t err) { return ERR_OK; } - // Track new connection http_connection_opened(); g_state.in_use = true; g_state.request_len = 0; g_state.close_when_sent = false; g_state.request[0] = '\0'; @@ -166,6 +206,10 @@ static err_t http_accept(void *arg, struct tcp_pcb *client_pcb, err_t err) { return ERR_OK; } +/******************************************************************************* + * @brief Initialize the HTTP server and start listening on port 80. + * @return void + ******************************************************************************/ void http_server_init(void) { struct tcp_pcb *pcb = tcp_new_ip_type(IPADDR_TYPE_ANY); if (!pcb) { print_dbg("HTTP: tcp_new failed\n"); return; } @@ -175,22 +219,30 @@ void http_server_init(void) { print_dbg("HTTP server initialized on port 80\n"); } -// ============================================================================ -// HTTP Server Monitoring Functions -// ============================================================================ - static uint32_t g_active_connections = 0; +/******************************************************************************* + * @brief Track a newly opened HTTP connection. + * @return void + ******************************************************************************/ static void http_connection_opened(void) { g_active_connections++; } +/******************************************************************************* + * @brief Track a closed HTTP connection. + * @return void + ******************************************************************************/ static void http_connection_closed(void) { if (g_active_connections > 0) { g_active_connections--; } } +/******************************************************************************* + * @brief Get the number of active HTTP connections. + * @return Current active connection count. + ******************************************************************************/ uint32_t http_get_active_connections(void) { return g_active_connections; } diff --git a/src/net/wifi_ap.c b/src/net/wifi_ap.c index 104fbfe..0df4adc 100644 --- a/src/net/wifi_ap.c +++ b/src/net/wifi_ap.c @@ -9,36 +9,33 @@ #include "task.h" #endif -// Persistent password storage so runtime password rotations remain valid. -// Start passwordless (empty string) so AP initially is OPEN for claim flow. -static char wifi_pass_storage[65] = ""; // will be filled on claim +static char wifi_pass_storage[65] = ""; static wifi_ap_config_t wifi_config = { .ssid = "MASTR-Token", - .password = wifi_pass_storage, // pointer always kept to storage - .ip_address = 0xC0A80401, // 192.168.4.1 + .password = wifi_pass_storage, + .ip_address = 0xC0A80401, .is_running = false }; -/** - * Initialize WiFi hardware (lightweight - just prepares config) - * Actual CYW43 initialization happens in wifi_ap_init_task after FreeRTOS starts - */ +/******************************************************************************* + * @brief Prepare WiFi subsystem configuration (pre-FreeRTOS). + * @return true on success. + ******************************************************************************/ bool wifi_ap_init(void) { - // Just mark that we're ready to initialize WiFi - // The actual cyw43_arch_init() must happen in a FreeRTOS task print_dbg("WiFi subsystem ready for initialization\n"); return true; } -/** - * Start WiFi AP with configuration - */ +/******************************************************************************* + * @brief Start the WiFi AP with the given configuration. + * @param config Configuration to apply. + * @return true on success, false on failure. + ******************************************************************************/ bool wifi_ap_start(const wifi_ap_config_t *config) { if (config == NULL) { return false; } - // Copy fundamental fields but deep-copy password text into persistent storage. - wifi_config.ssid = config->ssid; // assume lifetime static/const or managed by caller + wifi_config.ssid = config->ssid; if (config->password) { size_t len = strlen(config->password); if (len >= sizeof(wifi_pass_storage)) len = sizeof(wifi_pass_storage) - 1; @@ -64,9 +61,10 @@ bool wifi_ap_start(const wifi_ap_config_t *config) { return true; } -/** - * Stop WiFi AP - */ +/******************************************************************************* + * @brief Stop the WiFi AP. + * @return void + ******************************************************************************/ void wifi_ap_stop(void) { #ifndef UNIT_TEST stop_access_point(); @@ -75,24 +73,27 @@ void wifi_ap_stop(void) { #endif } -/** - * Get WiFi configuration - */ +/******************************************************************************* + * @brief Get the current WiFi AP configuration. + * @return Pointer to configuration. + ******************************************************************************/ wifi_ap_config_t* wifi_ap_get_config(void) { return &wifi_config; } -// Rotate password and restart AP (synchronous). Returns false on failure; AP left stopped if restart fails. +/******************************************************************************* + * @brief Rotate AP password and apply live without full restart. + * @param new_pass New passphrase. + * @return true on success, false on failure. + ******************************************************************************/ bool wifi_ap_rotate_password(const char *new_pass) { if (new_pass == NULL) return false; - // Update stored password text size_t len = strlen(new_pass); if (len >= sizeof(wifi_pass_storage)) len = sizeof(wifi_pass_storage) - 1; memcpy(wifi_pass_storage, new_pass, len); wifi_pass_storage[len] = '\0'; wifi_config.password = wifi_pass_storage; - // Reconfigure AP credentials without full deinit (smoother, safer) if (reconfigure_access_point(wifi_config.ssid, wifi_config.password) != 0) { print_dbg("ERROR: AP reconfiguration failed, attempting OPEN fallback\n"); wifi_pass_storage[0] = '\0'; @@ -104,19 +105,11 @@ bool wifi_ap_rotate_password(const char *new_pass) { return true; } -/** - * WiFi background task (runs frequently to process network events) - * - * This task is CRITICAL for CYW43 driver and lwIP stack operation. - * It must run regularly (every 50-100ms) to: - * - Process WiFi driver events - * - Handle network timeouts - * - Manage DHCP state - * - Process incoming packets - * - * Priority: 25 (just below serial task at 26) - * Sleep interval: 50ms (allows lwIP to process events regularly) - */ +/******************************************************************************* + * @brief FreeRTOS background task for WiFi driver upkeep. + * @param params Unused task parameter. + * @return void + ******************************************************************************/ void wifi_background_task(void *params) { (void)params; @@ -124,9 +117,6 @@ void wifi_background_task(void *params) { while (true) { #ifndef UNIT_TEST - // Sleep briefly to allow CYW43 driver and lwIP to process events - // CYW43_ARCH_THREADSAFE_BACKGROUND automatically handles the background work - // This sleep allows task switching and prevents blocking vTaskDelay(pdMS_TO_TICKS(50)); #else vTaskDelay(pdMS_TO_TICKS(50)); @@ -134,32 +124,24 @@ void wifi_background_task(void *params) { } } -/** - * HTTP server task (FreeRTOS task function) - * - * Handles HTTP server monitoring and API request processing - * - * Recommended Priority: 10 - * Recommended Stack: 2048 bytes - * - * @param params Unused task parameters - */ +/******************************************************************************* + * @brief FreeRTOS task shell for HTTP server monitoring. + * @param params Unused task parameter. + * @return void + ******************************************************************************/ void http_server_task(void *params) { (void)params; print_dbg("HTTP server task started (priority 5, 100ms interval)\n"); - // Only run if AP is configured if (!wifi_config.is_running) { print_dbg("HTTP server: AP not running, task exiting\n"); - vTaskDelete(NULL); // Delete self + vTaskDelete(NULL); return; } while (true) { #ifndef UNIT_TEST - // lwIP httpd is interrupt/callback-driven through recv callbacks - // This task primarily monitors server health and manages long-running requests vTaskDelay(pdMS_TO_TICKS(100)); #else vTaskDelay(pdMS_TO_TICKS(100)); @@ -167,14 +149,11 @@ void http_server_task(void *params) { } } -/** - * WiFi AP initialization task - * - * Starts the WiFi AP after the scheduler is running. - * This runs once and then exits. - * - * @param params Pointer to wifi_ap_config_t (or NULL to use default) - */ +/******************************************************************************* + * @brief FreeRTOS task to start the WiFi AP once scheduler is running. + * @param params Optional wifi_ap_config_t pointer. + * @return void + ******************************************************************************/ void wifi_ap_init_task(void *params) { print_dbg("WiFi AP init task started\n"); @@ -189,52 +168,38 @@ void wifi_ap_init_task(void *params) { print_dbg("ERROR: WiFi AP initialization failed\n"); } - // Task completes after starting AP vTaskDelete(NULL); } -// ============================================================================ -// AP Stability and Monitoring Functions -// ============================================================================ - -/** - * Check if WiFi AP is currently active and operational - * @return true if AP is running and healthy, false otherwise - */ +/******************************************************************************* + * @brief Report whether the WiFi AP is active. + * @return true if running and healthy. + ******************************************************************************/ bool wifi_ap_is_active(void) { #ifndef UNIT_TEST - // Check basic config state if (!wifi_config.is_running) { return false; } - // Could add more sophisticated health checks here: - // - Check CYW43 link status - // - Verify DHCP server is responding - // - Check for recent client activity - return true; #else return wifi_config.is_running; #endif } -/** - * Attempt to restart the WiFi AP - * @return true if restart was successful, false otherwise - */ +/******************************************************************************* + * @brief Restart the WiFi AP using current configuration. + * @return true on success, false on failure. + ******************************************************************************/ bool wifi_ap_restart(void) { print_dbg("WiFi AP: Attempting restart...\n"); - // Stop current AP wifi_ap_stop(); - // Brief delay to ensure clean shutdown #ifndef UNIT_TEST vTaskDelay(pdMS_TO_TICKS(1000)); #endif - // Restart with current configuration bool success = wifi_ap_start(&wifi_config); if (success) { print_dbg("WiFi AP: Restart successful\n");