feat(box3): POST /game/file — write assets to SD over HTTP (WiFi-direct)
CI / platformio (pull_request) Failing after 4m26s

This commit is contained in:
2026-06-14 12:58:45 +02:00
parent 29fa8eed34
commit 935f86d844
+104 -2
View File
@@ -22,6 +22,9 @@
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
/* BSP SD card support (bsp_sdcard_mount, BSP_SD_MOUNT_POINT) */
#include "bsp/esp-bsp.h"
#define TAG "scenario_srv"
#define MAX_SCENARIO_BYTES (64 * 1024)
@@ -216,6 +219,100 @@ static esp_err_t handle_scenario_post(httpd_req_t *req) {
return send_json(req, "200 OK", buf);
}
// ---------- POST /game/file ----------
//
// Write binary assets directly to the SD card over HTTP.
// Query param: ?path=sd/<relative/path> (only "sd/" prefix accepted)
// Route: sd/<…> → /sdcard/<…> (mkdir -p, up to 8 MiB, anti-traversal).
// Returns 503 if SD is not mounted, 403 on bad path, 200 JSON on success.
#define MAX_FILE_BYTES (8L * 1024L * 1024L)
static bool s_sd_mounted = false;
static esp_err_t ensure_sd_mounted(void) {
if (s_sd_mounted) return ESP_OK;
esp_err_t ret = bsp_sdcard_mount();
if (ret == ESP_OK || ret == ESP_ERR_INVALID_STATE /* already mounted */) {
s_sd_mounted = true;
ESP_LOGI(TAG, "file: SD mounted at %s", BSP_SD_MOUNT_POINT);
return ESP_OK;
}
ESP_LOGW(TAG, "file: SD mount failed: %s", esp_err_to_name(ret));
return ret;
}
static esp_err_t handle_file_post(httpd_req_t *req) {
char query[160], path_param[96];
if (httpd_req_get_url_query_str(req, query, sizeof(query)) != ESP_OK ||
httpd_query_key_value(query, "path", path_param,
sizeof(path_param)) != ESP_OK) {
return send_error(req, "400 Bad Request", "missing path param");
}
// Only accept sd/<…> prefix; reject traversal and bare directory paths.
if (strncmp(path_param, "sd/", 3) != 0 ||
strstr(path_param, "..") ||
path_param[strlen(path_param) - 1] == '/') {
return send_error(req, "403 Forbidden", "path must start with sd/ and name a file");
}
if (req->content_len <= 0 || (long)req->content_len > MAX_FILE_BYTES) {
return send_error(req, "400 Bad Request", "file too large or empty");
}
if (ensure_sd_mounted() != ESP_OK) {
return send_error(req, "503 Service Unavailable", "sd_not_mounted");
}
// Build absolute destination: /sdcard/<…> (strip the "sd/" prefix).
char full[192];
snprintf(full, sizeof(full), "%s/%s", BSP_SD_MOUNT_POINT, path_param + 3);
// mkdir -p for all intermediate directories.
const size_t root_len = strlen(BSP_SD_MOUNT_POINT) + 1; // e.g. strlen("/sdcard/")
for (char *p = full + root_len; *p; p++) {
if (*p == '/') {
*p = '\0';
mkdir(full, 0775); // EEXIST is fine
*p = '/';
}
}
FILE *f = fopen(full, "wb");
if (!f) {
ESP_LOGE(TAG, "file: fopen %s failed (errno=%d)", full, errno);
return send_error(req, "500 Internal Server Error", "open failed");
}
char buf[1024];
int remaining = (int)req->content_len;
while (remaining > 0) {
const int want = remaining < (int)sizeof(buf) ? remaining : (int)sizeof(buf);
int got = httpd_req_recv(req, buf, want);
if (got <= 0) {
if (got == HTTPD_SOCK_ERR_TIMEOUT) continue;
fclose(f);
unlink(full);
return send_error(req, "400 Bad Request", "recv failed");
}
if (fwrite(buf, 1, (size_t)got, f) != (size_t)got) {
fclose(f);
unlink(full);
return send_error(req, "500 Internal Server Error", "write failed");
}
remaining -= got;
}
fclose(f);
char resp[192];
snprintf(resp, sizeof(resp),
"{\"status\":\"ok\",\"path\":\"%s\",\"bytes\":%d}",
path_param, (int)req->content_len);
ESP_LOGI(TAG, "file: stored %s (%d B)", full, (int)req->content_len);
return send_json(req, "200 OK", resp);
}
// ---------- public init ----------
httpd_handle_t scenario_server_handle(void) {
@@ -229,7 +326,7 @@ esp_err_t scenario_server_start(void) {
}
httpd_config_t cfg = HTTPD_DEFAULT_CONFIG();
cfg.server_port = 80;
cfg.max_uri_handlers = 8;
cfg.max_uri_handlers = 10;
cfg.stack_size = 8192;
esp_err_t err = httpd_start(&s_server, &cfg);
@@ -246,9 +343,14 @@ esp_err_t scenario_server_start(void) {
.uri = "/game/scenario", .method = HTTP_POST,
.handler = handle_scenario_post, .user_ctx = NULL,
};
static const httpd_uri_t uri_file = {
.uri = "/game/file", .method = HTTP_POST,
.handler = handle_file_post, .user_ctx = NULL,
};
httpd_register_uri_handler(s_server, &uri_healthz);
httpd_register_uri_handler(s_server, &uri_scenario);
httpd_register_uri_handler(s_server, &uri_file);
ESP_LOGI(TAG, "scenario server up on :80 (GET /healthz, POST /game/scenario)");
ESP_LOGI(TAG, "scenario server up on :80 (GET /healthz, POST /game/scenario, POST /game/file)");
return ESP_OK;
}