diff --git a/zephcore/app/RepeaterMesh.cpp b/zephcore/app/RepeaterMesh.cpp index b9fb723..bdb7130 100644 --- a/zephcore/app/RepeaterMesh.cpp +++ b/zephcore/app/RepeaterMesh.cpp @@ -1371,11 +1371,17 @@ void RepeaterMesh::handleCommand(uint32_t sender_timestamp, char* command, char* while (*command == ' ') command++; + uint8_t hdr_used = 0; if (strlen(command) > 4 && command[2] == '|') { memcpy(reply, command, 3); reply += 3; command += 3; + hdr_used = 3; } + /* Tell the shared CLI how much of the caller's buffer is already spent, so + * the self-limiting handlers do not write past temp[5 + CLI_REMOTE_REPLY_SIZE]. + * Set unconditionally: a stale value would shrink the next reply. */ + _cli.setReplyHeaderUsed(hdr_used); #if IS_ENABLED(CONFIG_ZEPHCORE_REPEATER_UPLINK) && IS_ENABLED(CONFIG_MQTT_LIB) if (handleUplinkCommand(command, reply)) { diff --git a/zephcore/app/RoomServerMesh.cpp b/zephcore/app/RoomServerMesh.cpp index d045d77..7e6fe2e 100644 --- a/zephcore/app/RoomServerMesh.cpp +++ b/zephcore/app/RoomServerMesh.cpp @@ -1030,11 +1030,17 @@ void RoomServerMesh::handleCommand(uint32_t sender_timestamp, char* command, cha while (*command == ' ') command++; + uint8_t hdr_used = 0; if (strlen(command) > 4 && command[2] == '|') { memcpy(reply, command, 3); reply += 3; command += 3; + hdr_used = 3; } + /* Tell the shared CLI how much of the caller's buffer is already spent, so + * the self-limiting handlers do not write past temp[5 + CLI_REMOTE_REPLY_SIZE]. + * Set unconditionally: a stale value would shrink the next reply. */ + _cli.setReplyHeaderUsed(hdr_used); // ACL commands - supports BOTH formats for app compatibility: // Old Arduino: setperm {pubkey-hex} {permissions} (pubkey is long, perms is short) diff --git a/zephcore/helpers/CommonCLI.cpp b/zephcore/helpers/CommonCLI.cpp index c61a0fc..529a562 100644 --- a/zephcore/helpers/CommonCLI.cpp +++ b/zephcore/helpers/CommonCLI.cpp @@ -683,9 +683,15 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch } else if (memcmp(config, "tz.offset", 9) == 0) { snprintf(reply, CLI_REPLY_SIZE, "> %d", (int)_prefs->tz_offset); } else if (memcmp(config, "gps diag", 8) == 0) { - // What the last module-configuration attempt actually did. + /* What the last module-configuration attempt actually did. + * Self-limits on the remote path like cad.stats/meshtimesync: + * gps_get_diag_report() bounds itself by the length it is + * handed, and the full report (long module version + wide + * counters + the no-GPS_SAT_DIAG tail) can exceed the + * caller's packet buffer. */ + size_t cap = replyCap(sender_timestamp); reply[0] = '>'; reply[1] = ' '; - gps_get_diag_report(reply + 2, CLI_REPLY_SIZE - 2); + gps_get_diag_report(reply + 2, cap - 2); } else if (memcmp(config, "gps duty", 8) == 0) { uint32_t s = gps_get_poll_interval_sec(); // now-effective value if (s == 0) strcpy(reply, "> always on (0)"); @@ -706,8 +712,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch /* Runtime state + per-level probe stats live in the radio. * ZephCore-only; must stay ahead of the "cad" prefix match below. * Remote replies get the truncated buffer like meshtimesync. */ - size_t cap = (sender_timestamp == 0) ? CLI_REPLY_SIZE - : CLI_REMOTE_REPLY_SIZE; + size_t cap = replyCap(sender_timestamp); int n = snprintf(reply, cap, "> "); if (_callbacks->formatCadStatus(reply + n, (int)cap - n) == 0) { strcpy(reply, "not available"); @@ -758,8 +763,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch /* Only the local USB CLI (sender_timestamp == 0) gets the * full evidence table; remote replies are truncated to the * packet buffer. */ - size_t cap = (sender_timestamp == 0) ? CLI_REPLY_SIZE - : CLI_REMOTE_REPLY_SIZE; + size_t cap = replyCap(sender_timestamp); ts->formatStatus(reply, cap, getRTCClock()->getCurrentTime(), (uint32_t)(k_uptime_get() / 1000), _prefs->meshtimesync != 0); diff --git a/zephcore/helpers/CommonCLI.h b/zephcore/helpers/CommonCLI.h index 5fb4057..62a9388 100644 --- a/zephcore/helpers/CommonCLI.h +++ b/zephcore/helpers/CommonCLI.h @@ -134,6 +134,18 @@ class CommonCLI { int64_t _reboot_deadline_ms; static void rebootWorkHandler(struct k_work *work); + /* Bytes the caller already consumed at the head of the reply buffer before + * handing it to us — see setReplyHeaderUsed(). */ + uint8_t _reply_hdr_used; + + /* Space a handler may write at `reply`, terminator included. Local USB + * gets the full buffer; a remote reply rides in the caller's packet buffer + * and must also leave room for whatever the caller wrote ahead of us. */ + size_t replyCap(uint32_t sender_timestamp) const { + return (sender_timestamp == 0) ? CLI_REPLY_SIZE + : (CLI_REMOTE_REPLY_SIZE - _reply_hdr_used); + } + mesh::RTCClock* getRTCClock() { return _rtc; } void savePrefs(); void scheduleReboot(uint8_t type); @@ -142,11 +154,18 @@ public: CommonCLI(mesh::MainBoard& board, mesh::RTCClock& rtc, ClientACL& acl, NodePrefs* prefs, CommonCLICallbacks* callbacks) : _board(&board), _rtc(&rtc), _acl(&acl), _prefs(prefs), _callbacks(callbacks), - _pending_reboot(REBOOT_NONE), _reboot_deadline_ms(0) + _pending_reboot(REBOOT_NONE), _reboot_deadline_ms(0), _reply_hdr_used(0) { k_work_init_delayable(&_reboot_work, rebootWorkHandler); } + /* Roles that strip the `XX|` request-tag prefix echo those 3 bytes into the + * head of the reply buffer and advance `reply` past them before calling us. + * Their buffer is still only 5 + CLI_REMOTE_REPLY_SIZE, so the self-limiting + * handlers have to subtract what was already written or they overrun it. + * Call this on EVERY dispatch, with 0 when no prefix was stripped. */ + void setReplyHeaderUsed(uint8_t n) { _reply_hdr_used = n; } + void handleCommand(uint32_t sender_timestamp, const char* command, char* reply); uint8_t buildAdvertData(uint8_t node_type, uint8_t* app_data);