From f4e07716c42eef6b0af8739a306e1da02b439f70 Mon Sep 17 00:00:00 2001 From: Robert Ekl Date: Tue, 3 Mar 2026 11:24:59 -0600 Subject: [PATCH] fix(cli): parse tempradio args without mutable copy and document timeout cap --- docs/cli_commands.md | 2 +- src/helpers/CommonCLI.cpp | 71 ++++++++++++++++++++++++++++++++++----- 2 files changed, 63 insertions(+), 10 deletions(-) diff --git a/docs/cli_commands.md b/docs/cli_commands.md index c316bd6c7..fab886a5d 100644 --- a/docs/cli_commands.md +++ b/docs/cli_commands.md @@ -211,7 +211,7 @@ - `bw`: Bandwidth in kHz (7.8-500) - `sf`: Spreading factor (5-12) - `cr`: Coding rate (5-8) -- `timeout_mins`: Duration in minutes (must be > 0) +- `timeout_mins`: Duration in minutes (must be 1-32767) **Note:** This is not saved to preferences and will clear on reboot diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index fd6312734..093281dbe 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -3,6 +3,7 @@ #include "TxtDataHelpers.h" #include "AdvertDataHelpers.h" #include +#include // Believe it or not, this std C function is busted on some platforms! static uint32_t _atoi(const char* sp) { @@ -22,6 +23,58 @@ static bool isValidName(const char *n) { return true; } +static bool parseTempradioFloatField(const char*& sp, float& out_val, bool is_last = false) { + while (*sp == ' ' || *sp == '\t') sp++; + if (*sp == 0) return false; + + char* ep = nullptr; + out_val = strtof(sp, &ep); + if (ep == sp) return false; + + sp = ep; + while (*sp == ' ' || *sp == '\t') sp++; + if (is_last) return *sp == 0; + if (*sp != ',') return false; + sp++; + return true; +} + +static bool parseTempradioIntField(const char*& sp, long& out_val, bool is_last = false) { + while (*sp == ' ' || *sp == '\t') sp++; + if (*sp == 0) return false; + + char* ep = nullptr; + out_val = strtol(sp, &ep, 10); + if (ep == sp) return false; + + sp = ep; + while (*sp == ' ' || *sp == '\t') sp++; + if (is_last) return *sp == 0; + if (*sp != ',') return false; + sp++; + return true; +} + +static bool parseTempradioArgs(const char* args, float& freq, float& bw, uint8_t& sf, uint8_t& cr, int& timeout_mins) { + long sf_l = 0; + long cr_l = 0; + long timeout_l = 0; + const char* sp = args; + + if (!parseTempradioFloatField(sp, freq)) return false; + if (!parseTempradioFloatField(sp, bw)) return false; + if (!parseTempradioIntField(sp, sf_l)) return false; + if (!parseTempradioIntField(sp, cr_l)) return false; + if (!parseTempradioIntField(sp, timeout_l, true)) return false; + if (sf_l < 0 || sf_l > 255 || cr_l < 0 || cr_l > 255) return false; + if (timeout_l < -32768 || timeout_l > 32767) return false; + + sf = (uint8_t)sf_l; + cr = (uint8_t)cr_l; + timeout_mins = (int)timeout_l; + return true; +} + void CommonCLI::loadPrefs(FILESYSTEM* fs) { if (fs->exists("/com_prefs")) { loadPrefsInt(fs, "/com_prefs"); // new filename @@ -250,15 +303,15 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch strcpy(reply, "ERR: bad pubkey"); } } else if (memcmp(command, "tempradio ", 10) == 0) { - strcpy(tmp, &command[10]); - const char *parts[5]; - int num = mesh::Utils::parseTextParts(tmp, parts, 5); - float freq = num > 0 ? strtof(parts[0], nullptr) : 0.0f; - float bw = num > 1 ? strtof(parts[1], nullptr) : 0.0f; - uint8_t sf = num > 2 ? atoi(parts[2]) : 0; - uint8_t cr = num > 3 ? atoi(parts[3]) : 0; - int temp_timeout_mins = num > 4 ? atoi(parts[4]) : 0; - if (freq >= 300.0f && freq <= 2500.0f && sf >= 5 && sf <= 12 && cr >= 5 && cr <= 8 && bw >= 7.0f && bw <= 500.0f && temp_timeout_mins > 0) { + float freq = 0.0f; + float bw = 0.0f; + uint8_t sf = 0; + uint8_t cr = 0; + int temp_timeout_mins = 0; + bool parsed = parseTempradioArgs(&command[10], freq, bw, sf, cr, temp_timeout_mins); + + if (parsed && freq >= 300.0f && freq <= 2500.0f && sf >= 5 && sf <= 12 && cr >= 5 && cr <= 8 && + bw >= 7.0f && bw <= 500.0f && temp_timeout_mins > 0) { _callbacks->applyTempRadioParams(freq, bw, sf, cr, temp_timeout_mins); sprintf(reply, "OK - temp params for %d mins", temp_timeout_mins); } else {