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
This commit is contained in:
2026-04-16 17:41:22 -07:00
parent 21f3bc77d1
commit b4b3a56019
2 changed files with 32 additions and 6 deletions

View File

@@ -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;

View File

@@ -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<int>()) candidate.diff_thresh = (uint8_t)obj["diff_thresh"].as<int>();
if (obj["min_blob_px"].is<int>()) candidate.min_blob_px = obj["min_blob_px"].as<int>();
if (obj["max_move"].is<float>() || obj["max_move"].is<double>())
candidate.max_move = obj["max_move"].as<float>();
if (obj["max_missed"].is<int>()) candidate.max_missed = obj["max_missed"].as<int>();
if (obj["line_offset"].is<int>()) candidate.line_offset = (uint8_t)obj["line_offset"].as<int>();
// 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<int>()) bad_type = true;
else candidate.diff_thresh = (uint8_t)obj["diff_thresh"].as<int>();
}
if (!obj["min_blob_px"].isNull()) {
if (!obj["min_blob_px"].is<int>()) bad_type = true;
else candidate.min_blob_px = obj["min_blob_px"].as<int>();
}
if (!obj["max_move"].isNull()) {
// max_move is float — accept int literal too (JSON 12 vs 12.0)
if (!(obj["max_move"].is<float>() || obj["max_move"].is<double>()
|| obj["max_move"].is<int>())) bad_type = true;
else candidate.max_move = obj["max_move"].as<float>();
}
if (!obj["max_missed"].isNull()) {
if (!obj["max_missed"].is<int>()) bad_type = true;
else candidate.max_missed = obj["max_missed"].as<int>();
}
if (!obj["line_offset"].isNull()) {
if (!obj["line_offset"].is<int>()) bad_type = true;
else candidate.line_offset = (uint8_t)obj["line_offset"].as<int>();
}
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);