diff --git a/firmware/CMakeLists.txt b/firmware/CMakeLists.txt index 74ca731..39c8f43 100644 --- a/firmware/CMakeLists.txt +++ b/firmware/CMakeLists.txt @@ -3,5 +3,30 @@ cmake_minimum_required(VERSION 3.16) # SPAXEL ESP32-S3 Firmware # WiFi CSI-based indoor positioning node +# Get version from git tags, fallback to 0.1.0 +execute_process( + COMMAND git describe --tags --dirty 2>/dev/null + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + OUTPUT_VARIABLE GIT_VERSION + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET +) + +if(GIT_VERSION) + set(PROJECT_VERSION "${GIT_VERSION}") +else() + set(PROJECT_VERSION "0.1.0") +endif() + +message(STATUS "Building SPAXEL firmware version: ${PROJECT_VERSION}") + include($ENV{IDF_PATH}/tools/cmake/project.cmake) -project(spaxel-firmware) +project(spaxel-firmware VERSION "${PROJECT_VERSION}") + +# Generate version header +configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/main/version.h.in + ${CMAKE_CURRENT_BINARY_DIR}/spaxel-firmware/main/version.h + @ONLY +) + diff --git a/firmware/main/CMakeLists.txt b/firmware/main/CMakeLists.txt index 7798ae8..77d307e 100644 --- a/firmware/main/CMakeLists.txt +++ b/firmware/main/CMakeLists.txt @@ -5,6 +5,6 @@ idf_component_register( "csi.c" "ble.c" "provision.c" - INCLUDE_DIRS "." - REQUIRES esp_wifi esp_netif nvs_flash mdns esp_http_client esp_timer bt driver log esp_http_server + INCLUDE_DIRS "." "${CMAKE_BINARY_DIR}/spaxel-firmware/main" + REQUIRES esp_wifi esp_netif nvs_flash mdns esp_http_client esp_timer bt driver log esp_http_server mbedtls ) diff --git a/firmware/main/version.h.in b/firmware/main/version.h.in new file mode 100644 index 0000000..ba348a1 --- /dev/null +++ b/firmware/main/version.h.in @@ -0,0 +1,4 @@ +// Auto-generated at build time - do not edit +#pragma once + +#define SPAXEL_FIRMWARE_VERSION "@PROJECT_VERSION@" diff --git a/firmware/main/websocket.c b/firmware/main/websocket.c index 5e09ccf..077e785 100644 --- a/firmware/main/websocket.c +++ b/firmware/main/websocket.c @@ -7,11 +7,13 @@ #include "esp_system.h" #include "esp_ota_ops.h" #include "esp_http_client.h" +#include "mbedtls/sha256.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/semaphore.h" #include "cJSON.h" #include +#include // ESP-IDF WebSocket client #include "esp_websocket_client.h" @@ -633,15 +635,29 @@ static void ota_task(void *arg) { return; } + // Initialize SHA-256 for verification + mbedtls_sha256_context sha_ctx; + bool do_sha_verify = (strlen(s_ota_sha256) == 64); + if (do_sha_verify) { + mbedtls_sha256_init(&sha_ctx); + mbedtls_sha256_starts(&sha_ctx, 0); // 0 = SHA-256 + } + // Download and write char *buf = malloc(4096); int total_read = 0; int read; while ((read = esp_http_client_read(http, buf, 4096)) > 0) { + // Update SHA-256 hash if verifying + if (do_sha_verify) { + mbedtls_sha256_update(&sha_ctx, (unsigned char *)buf, read); + } + err = esp_ota_write(ota_handle, buf, read); if (err != ESP_OK) { free(buf); + if (do_sha_verify) mbedtls_sha256_free(&sha_ctx); esp_ota_abort(ota_handle); esp_http_client_cleanup(http); websocket_send_ota_status("failed", 0, "write_failed"); @@ -660,7 +676,29 @@ static void ota_task(void *arg) { // Verify and complete websocket_send_ota_status("verifying", 100, NULL); - // SHA256 verification would go here if s_ota_sha256 is set + // SHA-256 verification + if (do_sha_verify) { + unsigned char hash[32]; + mbedtls_sha256_finish(&sha_ctx, hash); + mbedtls_sha256_free(&sha_ctx); + + // Convert binary hash to hex string + char hash_hex[65]; + for (int i = 0; i < 32; i++) { + sprintf(hash_hex + (i * 2), "%02x", hash[i]); + } + hash_hex[64] = '\0'; + + // Compare with expected hash (case-insensitive) + if (strcasecmp(hash_hex, s_ota_sha256) != 0) { + ESP_LOGE(TAG, "SHA-256 mismatch: expected %s, got %s", s_ota_sha256, hash_hex); + esp_ota_abort(ota_handle); + websocket_send_ota_status("failed", 0, "sha256_mismatch"); + vTaskDelete(NULL); + return; + } + ESP_LOGI(TAG, "SHA-256 verified: %s", hash_hex); + } err = esp_ota_end(ota_handle); if (err != ESP_OK) { diff --git a/firmware/main/wifi.c b/firmware/main/wifi.c index 19137d6..7aa2fa6 100644 --- a/firmware/main/wifi.c +++ b/firmware/main/wifi.c @@ -10,6 +10,8 @@ #include "lwip/sockets.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" +#include +#include static const char *TAG = "wifi"; @@ -297,6 +299,26 @@ static void captive_dns_recv(void *arg, struct udp_pcb *pcb, struct pbuf *p, pbuf_free(p); } +// URL decode helper for captive portal form parsing +static void url_decode(char *dst, const char *src, size_t dst_size) { + size_t i = 0; + size_t j = 0; + + while (src[i] && j < dst_size - 1) { + if (src[i] == '+') { + dst[j++] = ' '; + i++; + } else if (src[i] == '%' && isxdigit(src[i+1]) && isxdigit(src[i+2])) { + char hex[3] = {src[i+1], src[i+2], 0}; + dst[j++] = (char)strtol(hex, NULL, 16); + i += 3; + } else { + dst[j++] = src[i++]; + } + } + dst[j] = '\0'; +} + static esp_err_t captive_root_handler(httpd_req_t *req) { const char *html = "" @@ -338,17 +360,20 @@ static esp_err_t captive_save_handler(httpd_req_t *req) { char ssid[33] = {0}; char password[65] = {0}; char ms_ip[47] = {0}; + char decoded[128]; - // Simple parsing (url-encoded) + // Parse URL-encoded form data char *p = strtok(buf, "&"); while (p) { if (strncmp(p, "ssid=", 5) == 0) { - // URL decode would go here, simplified for now - strncpy(ssid, p + 5, sizeof(ssid) - 1); + url_decode(decoded, p + 5, sizeof(decoded)); + strncpy(ssid, decoded, sizeof(ssid) - 1); } else if (strncmp(p, "password=", 9) == 0) { - strncpy(password, p + 9, sizeof(password) - 1); + url_decode(decoded, p + 9, sizeof(decoded)); + strncpy(password, decoded, sizeof(password) - 1); } else if (strncmp(p, "ms_ip=", 6) == 0) { - strncpy(ms_ip, p + 6, sizeof(ms_ip) - 1); + url_decode(decoded, p + 6, sizeof(decoded)); + strncpy(ms_ip, decoded, sizeof(ms_ip) - 1); } p = strtok(NULL, "&"); }