From b4b3a5601978048afeea3c3dad9f12ee4495f99a Mon Sep 17 00:00:00 2001 From: Peter Woolery Date: Thu, 16 Apr 2026 17:41:22 -0700 Subject: [PATCH] fix(reporter): accept int JSON for max_move + reject malformed config fields - max_move now accepts bare int JSON literals (server may emit 12 vs 12.0) - Present-but-wrong-type fields reject the whole update with a log, preventing cfg_version from advancing on malformed payloads - Zero-init CVTuning& out in cv_get_tuning before mutex take for safety --- firmware/src/main.cpp | 1 + firmware/src/reporter.cpp | 37 +++++++++++++++++++++++++++++++------ 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/firmware/src/main.cpp b/firmware/src/main.cpp index 7dfe4c9..b8310a0 100644 --- a/firmware/src/main.cpp +++ b/firmware/src/main.cpp @@ -37,6 +37,7 @@ void cv_apply_tuning(const CVTuning& incoming) { } void cv_get_tuning(CVTuning& out) { + out = CVTuning{}; if (!s_cv_mutex) return; if (xSemaphoreTake(s_cv_mutex, pdMS_TO_TICKS(500)) == pdTRUE) { out = g_cv.tuning; diff --git a/firmware/src/reporter.cpp b/firmware/src/reporter.cpp index 3d7934d..de325b9 100644 --- a/firmware/src/reporter.cpp +++ b/firmware/src/reporter.cpp @@ -199,12 +199,37 @@ void reporter_heartbeat(const DeviceConfig& cfg, uint32_t uptime_s, int wifi_rss CVTuning candidate = current; candidate.cfg_version = new_ver; - if (obj["diff_thresh"].is()) candidate.diff_thresh = (uint8_t)obj["diff_thresh"].as(); - if (obj["min_blob_px"].is()) candidate.min_blob_px = obj["min_blob_px"].as(); - if (obj["max_move"].is() || obj["max_move"].is()) - candidate.max_move = obj["max_move"].as(); - if (obj["max_missed"].is()) candidate.max_missed = obj["max_missed"].as(); - if (obj["line_offset"].is()) candidate.line_offset = (uint8_t)obj["line_offset"].as(); + + // Present-but-wrong-type fields reject the whole update. Absent fields + // (isNull == true) fall back to the current value. This prevents + // cfg_version bumping while silently dropping malformed fields. + bool bad_type = false; + if (!obj["diff_thresh"].isNull()) { + if (!obj["diff_thresh"].is()) bad_type = true; + else candidate.diff_thresh = (uint8_t)obj["diff_thresh"].as(); + } + if (!obj["min_blob_px"].isNull()) { + if (!obj["min_blob_px"].is()) bad_type = true; + else candidate.min_blob_px = obj["min_blob_px"].as(); + } + if (!obj["max_move"].isNull()) { + // max_move is float — accept int literal too (JSON 12 vs 12.0) + if (!(obj["max_move"].is() || obj["max_move"].is() + || obj["max_move"].is())) bad_type = true; + else candidate.max_move = obj["max_move"].as(); + } + if (!obj["max_missed"].isNull()) { + if (!obj["max_missed"].is()) bad_type = true; + else candidate.max_missed = obj["max_missed"].as(); + } + if (!obj["line_offset"].isNull()) { + if (!obj["line_offset"].is()) bad_type = true; + else candidate.line_offset = (uint8_t)obj["line_offset"].as(); + } + if (bad_type) { + Serial.printf("[CFG] rejected malformed config v=%u\n", (unsigned)new_ver); + return; + } if (!cv_tuning_validate(candidate)) { Serial.printf("[CFG] rejected invalid config v=%u\n", (unsigned)new_ver);