forge: add patch 14 — stabilize bot response tokens

This commit is contained in:
cj-vana
2026-05-17 12:11:02 -06:00
parent ee1b95cc9e
commit f00355c100
15 changed files with 392 additions and 13 deletions

View File

@@ -1,7 +1,7 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: cj-vana <cj@depth23.online> From: cj-vana <cj@depth23.online>
Date: Fri, 15 May 2026 11:44:21 -0600 Date: Fri, 15 May 2026 11:44:21 -0600
Subject: [PATCH 01/13] Add companion radio firmware bot command parity Subject: [PATCH 01/14] Add companion radio firmware bot command parity
--- ---
.../companion_radio/BotCommandRegistry.cpp | 121 ++ .../companion_radio/BotCommandRegistry.cpp | 121 ++

View File

@@ -1,7 +1,7 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: cj-vana <cj@depth23.online> From: cj-vana <cj@depth23.online>
Date: Fri, 15 May 2026 19:58:42 -0600 Date: Fri, 15 May 2026 19:58:42 -0600
Subject: [PATCH 02/13] Harden firmware bot response coordination Subject: [PATCH 02/14] Harden firmware bot response coordination
--- ---
examples/companion_radio/BotCommands.cpp | 2 +- examples/companion_radio/BotCommands.cpp | 2 +-

View File

@@ -1,7 +1,7 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: cj-vana <cj@depth23.online> From: cj-vana <cj@depth23.online>
Date: Fri, 15 May 2026 20:09:58 -0600 Date: Fri, 15 May 2026 20:09:58 -0600
Subject: [PATCH 03/13] Allow prefixless firmware bot DMs Subject: [PATCH 03/14] Allow prefixless firmware bot DMs
--- ---
examples/companion_radio/BotPolicy.cpp | 2 +- examples/companion_radio/BotPolicy.cpp | 2 +-

View File

@@ -1,7 +1,7 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: cj-vana <cj@depth23.online> From: cj-vana <cj@depth23.online>
Date: Sat, 16 May 2026 04:12:02 +0000 Date: Sat, 16 May 2026 04:12:02 +0000
Subject: [PATCH 04/13] Align firmware bot commands with upstream behavior Subject: [PATCH 04/14] Align firmware bot commands with upstream behavior
--- ---
.../companion_radio/BotCommandRegistry.cpp | 25 +- .../companion_radio/BotCommandRegistry.cpp | 25 +-

View File

@@ -1,7 +1,7 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: cj-vana <cj@depth23.online> From: cj-vana <cj@depth23.online>
Date: Sat, 16 May 2026 12:38:13 -0600 Date: Sat, 16 May 2026 12:38:13 -0600
Subject: [PATCH 05/13] Add hop-aware bot coordination, utility commands, and Subject: [PATCH 05/14] Add hop-aware bot coordination, utility commands, and
bounded delays bounded delays
Adds new utility commands (TIME, LORA, ID, NEIGHBORS), neighbor tracking, Adds new utility commands (TIME, LORA, ID, NEIGHBORS), neighbor tracking,

View File

@@ -1,7 +1,7 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: cj-vana <cj@depth23.online> From: cj-vana <cj@depth23.online>
Date: Sat, 16 May 2026 18:58:28 -0600 Date: Sat, 16 May 2026 18:58:28 -0600
Subject: [PATCH 06/13] Mark bot adverts, speed up initial advert, default Subject: [PATCH 06/14] Mark bot adverts, speed up initial advert, default
contacts auto-overwrite contacts auto-overwrite
Three coordination/discoverability fixes: Three coordination/discoverability fixes:

View File

@@ -1,7 +1,7 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: cj-vana <cj@depth23.online> From: cj-vana <cj@depth23.online>
Date: Sat, 16 May 2026 19:33:04 -0600 Date: Sat, 16 May 2026 19:33:04 -0600
Subject: [PATCH 07/13] Prefix bot responses with request token for inter-bot Subject: [PATCH 07/14] Prefix bot responses with request token for inter-bot
suppression suppression
Adds a 4-hex request token (low 16 bits of the request fingerprint) as a Adds a 4-hex request token (low 16 bits of the request fingerprint) as a

View File

@@ -1,7 +1,7 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: cj-vana <cj@depth23.online> From: cj-vana <cj@depth23.online>
Date: Sat, 16 May 2026 19:55:36 -0600 Date: Sat, 16 May 2026 19:55:36 -0600
Subject: [PATCH 08/13] Drop [MCBOT] advert suffix and auto-learn known-bot Subject: [PATCH 08/14] Drop [MCBOT] advert suffix and auto-learn known-bot
path path
Removes BOT_ADVERT_MARKER / writeBotAdvertName / isBotAdvertName from Removes BOT_ADVERT_MARKER / writeBotAdvertName / isBotAdvertName from

View File

@@ -1,7 +1,7 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: cj-vana <cj@depth23.online> From: cj-vana <cj@depth23.online>
Date: Sat, 16 May 2026 20:05:19 -0600 Date: Sat, 16 May 2026 20:05:19 -0600
Subject: [PATCH 09/13] Widen hop-step + drop "ack " prefix from test response Subject: [PATCH 09/14] Widen hop-step + drop "ack " prefix from test response
Two changes: Two changes:

View File

@@ -1,7 +1,7 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: cj-vana <cj@depth23.online> From: cj-vana <cj@depth23.online>
Date: Sat, 16 May 2026 20:31:12 -0600 Date: Sat, 16 May 2026 20:31:12 -0600
Subject: [PATCH 10/13] Widen coordinator delays a tiny bit more Subject: [PATCH 10/14] Widen coordinator delays a tiny bit more
~25% bump across the main coordination knobs: ~25% bump across the main coordination knobs:
- BOT_RESPONSE_DELAY_BASE_MILLIS 1200 -> 1500 - BOT_RESPONSE_DELAY_BASE_MILLIS 1200 -> 1500

View File

@@ -1,7 +1,7 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: cj-vana <cj@depth23.online> From: cj-vana <cj@depth23.online>
Date: Sat, 16 May 2026 20:49:51 -0600 Date: Sat, 16 May 2026 20:49:51 -0600
Subject: [PATCH 11/13] Auto-advert on startup (initial flood at 5s instead of Subject: [PATCH 11/14] Auto-advert on startup (initial flood at 5s instead of
120s) 120s)
BOT_PREFS_INITIAL_FLOOD_ADVERT_MILLIS 120000 -> 5000. The bot now sends BOT_PREFS_INITIAL_FLOOD_ADVERT_MILLIS 120000 -> 5000. The bot now sends

View File

@@ -1,7 +1,7 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: cj-vana <cj@depth23.online> From: cj-vana <cj@depth23.online>
Date: Sat, 16 May 2026 21:32:04 -0600 Date: Sat, 16 May 2026 21:32:04 -0600
Subject: [PATCH 12/13] Spell out hops/byte in test+path; drop channel name Subject: [PATCH 12/14] Spell out hops/byte in test+path; drop channel name
from test ack from test ack
Test response (writeAckResponse): Test response (writeAckResponse):

View File

@@ -1,7 +1,7 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: cj-vana <cj@depth23.online> From: cj-vana <cj@depth23.online>
Date: Sat, 16 May 2026 22:18:02 -0600 Date: Sat, 16 May 2026 22:18:02 -0600
Subject: [PATCH 13/13] =?UTF-8?q?Hop=20delay=20grows=20quadratically=20?= Subject: [PATCH 13/14] =?UTF-8?q?Hop=20delay=20grows=20quadratically=20?=
=?UTF-8?q?=E2=80=94=20wider=20gaps=20at=20higher=20hop=20counts?= =?UTF-8?q?=E2=80=94=20wider=20gaps=20at=20higher=20hop=20counts?=
MIME-Version: 1.0 MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8 Content-Type: text/plain; charset=UTF-8

View File

@@ -0,0 +1,342 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: cj-vana <cj@depth23.online>
Date: Sun, 17 May 2026 12:06:00 -0600
Subject: [PATCH 14/14] Stabilize firmware bot response tokens
---
examples/companion_radio/FirmwareBot.cpp | 52 ++++++++++++------
examples/companion_radio/FirmwareBot.h | 1 +
examples/companion_radio/MyMesh.cpp | 67 +++++++++++++++++++++---
examples/companion_radio/MyMesh.h | 1 +
4 files changed, 96 insertions(+), 25 deletions(-)
diff --git a/examples/companion_radio/FirmwareBot.cpp b/examples/companion_radio/FirmwareBot.cpp
index afd44d41..21312c55 100644
--- a/examples/companion_radio/FirmwareBot.cpp
+++ b/examples/companion_radio/FirmwareBot.cpp
@@ -366,8 +366,17 @@ BotWriteResult writeAckResponse(const BotMessage& message, const BotCommand& com
}
BotFingerprint fingerprintFor(const BotMessage& message) {
+ return fingerprintFor(message, NULL, 0);
+}
+
+BotFingerprint fingerprintFor(const BotMessage& message, const uint8_t* channel_id, size_t channel_id_len) {
uint64_t hash = 1469598103934665603ULL;
- hash = fnv1aUpdateChannel(hash, message);
+ if (message.channel_kind == BOT_CHANNEL_DM || !channel_id || channel_id_len == 0) {
+ hash = fnv1aUpdateChannel(hash, message);
+ } else {
+ hash = fnv1aUpdate(hash, 0xC7);
+ hash = fnv1aUpdateBytes(hash, channel_id, channel_id_len);
+ }
hash = fnv1aUpdateBytes(hash, message.sender_key_prefix, sizeof(message.sender_key_prefix));
hash = fnv1aUpdateTextLower(hash, message.sender_name, boundedStrLen(message.sender_name, sizeof(message.sender_name)));
hash = fnv1aUpdateU32(hash, message.sender_timestamp);
@@ -381,6 +390,24 @@ BotFingerprint fingerprintFor(const BotMessage& message) {
return fingerprint;
}
+static int hexNibble(char c);
+
+bool parseRequestTokenPrefix(const char* text, size_t text_len, uint16_t* token, size_t* prefix_len) {
+ if (token) *token = 0;
+ if (prefix_len) *prefix_len = 0;
+ if (!text || text_len < 7) return false;
+ if (text[0] != '[' || text[5] != ']' || text[6] != ' ') return false;
+ uint16_t value = 0;
+ for (int i = 0; i < 4; i++) {
+ int n = hexNibble(text[1 + i]);
+ if (n < 0) return false;
+ value = (uint16_t)((value << 4) | (uint16_t)n);
+ }
+ if (token) *token = value;
+ if (prefix_len) *prefix_len = 7;
+ return true;
+}
+
BotFingerprint responseFingerprintFor(const BotMessage& message, const char* response_text, size_t response_text_len) {
uint64_t hash = 1469598103934665603ULL;
hash = fnv1aUpdateChannel(hash, message);
@@ -389,6 +416,13 @@ BotFingerprint responseFingerprintFor(const BotMessage& message, const char* res
hash = fnv1aUpdateBytes(hash, message.sender_key_prefix, message.sender_key_prefix_len);
}
+ uint16_t token = 0;
+ size_t prefix_len = 0;
+ if (parseRequestTokenPrefix(response_text, response_text_len, &token, &prefix_len)) {
+ response_text += prefix_len;
+ response_text_len -= prefix_len;
+ }
+
char normalized[BOT_MAX_RESPONSE_LEN + 1];
size_t normalized_len = 0;
normalizeText(response_text, response_text_len, normalized, sizeof(normalized), &normalized_len);
@@ -423,22 +457,6 @@ static int hexNibble(char c) {
return -1;
}
-bool parseRequestTokenPrefix(const char* text, size_t text_len, uint16_t* token, size_t* prefix_len) {
- if (token) *token = 0;
- if (prefix_len) *prefix_len = 0;
- if (!text || text_len < 7) return false; // "[XXXX] " = 7 chars minimum
- if (text[0] != '[' || text[5] != ']' || text[6] != ' ') return false;
- uint16_t value = 0;
- for (int i = 0; i < 4; i++) {
- int n = hexNibble(text[1 + i]);
- if (n < 0) return false;
- value = (uint16_t)((value << 4) | (uint16_t)n);
- }
- if (token) *token = value;
- if (prefix_len) *prefix_len = 7;
- return true;
-}
-
BotWriteResult prependRequestToken(BotFingerprint request_fingerprint, char* text, size_t text_len, size_t buf_len,
size_t* new_len) {
if (new_len) *new_len = text_len;
diff --git a/examples/companion_radio/FirmwareBot.h b/examples/companion_radio/FirmwareBot.h
index 708432a2..f1726712 100644
--- a/examples/companion_radio/FirmwareBot.h
+++ b/examples/companion_radio/FirmwareBot.h
@@ -17,6 +17,7 @@ BotWriteResult writeResponseForChannel(BotChannelKind channel_kind, bool allow_p
BotWriteResult writeAckResponse(const BotMessage& message, const BotCommand& command, char* output, size_t output_len,
size_t* written);
BotFingerprint fingerprintFor(const BotMessage& message);
+BotFingerprint fingerprintFor(const BotMessage& message, const uint8_t* channel_id, size_t channel_id_len);
BotFingerprint responseFingerprintFor(const BotMessage& message, const char* response_text, size_t response_text_len);
uint16_t requestToken(BotFingerprint request_fingerprint);
void formatRequestToken(uint16_t token, char out[5]);
diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp
index 9121d42c..1a7ddb7a 100644
--- a/examples/companion_radio/MyMesh.cpp
+++ b/examples/companion_radio/MyMesh.cpp
@@ -121,6 +121,16 @@
#define LAZY_CONTACTS_WRITE_DELAY 5000
#if CMESH_BOT_ENABLED
+#ifndef CMESH_BOT_DEBUG
+#define CMESH_BOT_DEBUG 0
+#endif
+#if CMESH_BOT_DEBUG
+#define BOT_DEBUG_PRINTF(...) Serial.printf(__VA_ARGS__)
+#define BOT_DEBUG_PRINTLN(value) Serial.println(value)
+#else
+#define BOT_DEBUG_PRINTF(...)
+#define BOT_DEBUG_PRINTLN(value)
+#endif
#endif
#define PUBLIC_GROUP_PSK "izOH6cXN6mrJ5e26oRXNcg=="
@@ -1055,6 +1065,7 @@ bool MyMesh::handleBotCLI(const char *args) {
void MyMesh::observeBotDirectMessage(const ContactInfo &from, uint32_t sender_timestamp, const uint8_t *sender_prefix,
size_t sender_prefix_len, const char *text, const mesh::Packet *packet) {
+ BOT_DEBUG_PRINTF("CBOT-DBG: DM rx from=%s text=%.40s enabled=%d\r\n", from.name, text, bot_prefs.enabled);
BotMessage message;
memset(&message, 0, sizeof(message));
message.channel_kind = BotPolicy::classifyChannel(NULL, 0, true, bot_prefs);
@@ -1090,6 +1101,7 @@ void MyMesh::observeBotDirectMessage(const ContactInfo &from, uint32_t sender_ti
void MyMesh::observeBotChannelMessage(uint8_t channel_idx, const char *channel_name, const char *text,
uint32_t sender_timestamp, const mesh::Packet *packet) {
+ BOT_DEBUG_PRINTF("CBOT-DBG: CH rx ch=%s text=%.40s enabled=%d\r\n", channel_name ? channel_name : "?", text, bot_prefs.enabled);
BotMessage message;
memset(&message, 0, sizeof(message));
size_t channel_len = botBoundedStrLen(channel_name, BOT_MAX_CHANNEL_NAME_LEN);
@@ -1429,7 +1441,7 @@ bool MyMesh::dispatchBotTraceDirectLink(const BotMessage &message, const Contact
return true;
}
- BotFingerprint request_fingerprint = FirmwareBot::fingerprintFor(message);
+ BotFingerprint request_fingerprint = botRequestFingerprintFor(message, channel_idx);
BotFingerprint response_fingerprint =
FirmwareBot::responseFingerprintFor(message, final_response, final_response_len);
BotFingerprint fingerprint;
@@ -1503,7 +1515,7 @@ bool MyMesh::handleBotTraceCommand(const BotMessage &message, const ContactInfo
}
if (!have_path || !botTracePathShapeValid(path_len, flags)) return false;
- BotFingerprint request_fingerprint = FirmwareBot::fingerprintFor(message);
+ BotFingerprint request_fingerprint = botRequestFingerprintFor(message, channel_idx);
char response[BOT_MAX_RESPONSE_LEN + 1];
size_t response_len = botFormatTraceSent(response, sizeof(response), (uint8_t)(path_len / hash_size));
char final_response[BOT_MAX_RESPONSE_LEN + 1];
@@ -1737,6 +1749,16 @@ bool MyMesh::observeKnownBotResponse(const BotMessage &message, bool authoritati
return false;
}
+BotFingerprint MyMesh::botRequestFingerprintFor(const BotMessage &message, uint8_t channel_idx) {
+ if (message.channel_kind != BOT_CHANNEL_DM && channel_idx != 0xFF) {
+ ChannelDetails channel;
+ if (getChannel(channel_idx, channel)) {
+ return FirmwareBot::fingerprintFor(message, channel.channel.hash, sizeof(channel.channel.hash));
+ }
+ }
+ return FirmwareBot::fingerprintFor(message);
+}
+
bool MyMesh::observeBotGroupResponse(const BotMessage &message) {
if (!BotPolicy::isPrefixlessCommandAllowed(message.channel_kind)) return false;
@@ -1749,6 +1771,9 @@ bool MyMesh::observeBotGroupResponse(const BotMessage &message) {
if (FirmwareBot::parseRequestTokenPrefix(message.text, message.text_len, &token, &token_prefix_len)) {
if (ResponseCoordinator::suppressByRequestToken(bot_coordinator_pending, BOT_COORDINATOR_PENDING_SLOTS, token)) {
token_suppressed = true;
+ BOT_DEBUG_PRINTF("CBOT-DBG: token suppress %04x\r\n", (unsigned)token);
+ } else {
+ BOT_DEBUG_PRINTF("CBOT-DBG: token miss %04x\r\n", (unsigned)token);
}
}
@@ -1763,9 +1788,12 @@ bool MyMesh::observeBotGroupResponse(const BotMessage &message) {
}
void MyMesh::recordBotObservation(const BotMessage &message, const ContactInfo *direct_recipient, uint8_t channel_idx) {
+ BOT_DEBUG_PRINTF("CBOT-DBG: record kind=%d direct=%d enabled=%d\r\n", (int)message.channel_kind, direct_recipient != NULL, bot_prefs.enabled);
bot_stats.observed_messages++;
BotPolicyDecision decision = BotPolicy::decide(message.channel_kind);
+ BOT_DEBUG_PRINTF("CBOT-DBG: policy decision=%d\r\n", (int)decision);
if (decision == BOT_POLICY_IGNORE) {
+ BOT_DEBUG_PRINTLN("CBOT-DBG: IGNORE -> drop");
bot_stats.ignored_messages++;
return;
}
@@ -1776,24 +1804,28 @@ void MyMesh::recordBotObservation(const BotMessage &message, const ContactInfo *
}
if (!bot_prefs.enabled) {
+ BOT_DEBUG_PRINTLN("CBOT-DBG: bot disabled -> drop");
bot_stats.ignored_messages++;
return;
}
- if (observeKnownBotResponse(message, direct_recipient != NULL)) return;
- if (!direct_recipient && observeBotGroupResponse(message)) return;
+ if (observeKnownBotResponse(message, direct_recipient != NULL)) { BOT_DEBUG_PRINTLN("CBOT-DBG: known-bot suppress"); return; }
+ if (!direct_recipient && observeBotGroupResponse(message)) { BOT_DEBUG_PRINTLN("CBOT-DBG: group-response suppress"); return; }
sendQueuedBotResponses();
BotCommand command;
if (!FirmwareBot::parseCommand(message.text, message.text_len, &command,
BotPolicy::isPrefixlessCommandAllowed(message.channel_kind))) {
+ BOT_DEBUG_PRINTF("CBOT-DBG: parseCommand FAILED text=%.40s prefixless=%d\r\n", message.text, BotPolicy::isPrefixlessCommandAllowed(message.channel_kind));
if (message.text_len > 0 && (message.text[0] == '!' || message.text[0] == '/')) bot_stats.parse_errors++;
return;
}
+ BOT_DEBUG_PRINTF("CBOT-DBG: parsed cmd.id=%d name=%s args=%.30s\r\n", (int)command.id, command.name, command.args);
if ((command.id != BOT_COMMAND_UNKNOWN && command.id != BOT_COMMAND_UNSUPPORTED &&
!BotPrefsCodec::commandEnabled(bot_prefs, command.id)) ||
FirmwareBot::isCommandOnCooldown(bot_command_cooldowns, BOT_COMMAND_COOLDOWN_SLOTS, command.id, _ms->getMillis())) {
+ BOT_DEBUG_PRINTLN("CBOT-DBG: cmd disabled or on cooldown -> drop");
bot_stats.ignored_messages++;
return;
}
@@ -1868,7 +1900,7 @@ void MyMesh::recordBotObservation(const BotMessage &message, const ContactInfo *
return;
}
- BotFingerprint request_fingerprint = FirmwareBot::fingerprintFor(message);
+ BotFingerprint request_fingerprint = botRequestFingerprintFor(message, channel_idx);
BotFingerprint response_fingerprint = FirmwareBot::responseFingerprintFor(message, final_response, final_response_len);
BotFingerprint fingerprint;
uint32_t due_at_millis = 0;
@@ -1884,7 +1916,11 @@ void MyMesh::recordBotObservation(const BotMessage &message, const ContactInfo *
queue_depth, bot_prefs.normal_delay_ms,
bot_prefs.normal_jitter_ms, bot_prefs.hop_step_ms,
&fingerprint, &due_at_millis);
+ BOT_DEBUG_PRINTF("CBOT-DBG: schedule result=%d due=%lu now=%lu reqfp=%08lx%08lx\r\n",
+ (int)schedule, (unsigned long)due_at_millis, (unsigned long)_ms->getMillis(),
+ (unsigned long)(request_fingerprint.value >> 32), (unsigned long)(request_fingerprint.value & 0xFFFFFFFFul));
if (schedule == BOT_COORDINATOR_NO_SPACE || schedule == BOT_COORDINATOR_NOT_NORMAL) {
+ BOT_DEBUG_PRINTLN("CBOT-DBG: schedule FAILED -> drop");
bot_stats.send_failures++;
return;
}
@@ -1894,8 +1930,11 @@ void MyMesh::recordBotObservation(const BotMessage &message, const ContactInfo *
FirmwareBot::recordCommandCooldown(bot_command_cooldowns, BOT_COMMAND_COOLDOWN_SLOTS, command.id, _ms->getMillis(),
BOT_COMMAND_COOLDOWN_MILLIS);
if (!enqueueBotResponse(message, direct_recipient, channel_idx, final_response, final_response_len, fingerprint, response_fingerprint)) {
+ BOT_DEBUG_PRINTLN("CBOT-DBG: enqueueBotResponse FAILED -> drop");
ResponseCoordinator::cancel(bot_coordinator_pending, BOT_COORDINATOR_PENDING_SLOTS, fingerprint);
bot_stats.send_failures++;
+ } else {
+ BOT_DEBUG_PRINTF("CBOT-DBG: queued response, text=%.40s\r\n", final_response);
}
}
@@ -1904,6 +1943,8 @@ void MyMesh::sendQueuedBotResponses() {
while (true) {
BotCoordinatorReady ready = ResponseCoordinator::poll(bot_coordinator_pending, BOT_COORDINATOR_PENDING_SLOTS, now);
if (ready.result == BOT_COORDINATOR_READY_NONE) return;
+ BOT_DEBUG_PRINTF("CBOT-DBG: poll result=%d reqfp=%08lx\r\n", (int)ready.result,
+ (unsigned long)(ready.request_fingerprint.value & 0xFFFFFFFFul));
if (ready.result == BOT_COORDINATOR_READY_SUPPRESSED) {
bot_stats.suppressed_responses++;
@@ -1945,12 +1986,14 @@ void MyMesh::sendQueuedBotResponses() {
bool success = false;
if (pending->direct) {
ContactInfo *recipient = lookupContactByPubKey(pending->recipient_pub_key, PUB_KEY_SIZE);
+ BOT_DEBUG_PRINTF("CBOT-DBG: send DM lookup=%s text=%.40s\r\n", recipient ? "FOUND" : "NULL", pending->text);
if (recipient) {
uint32_t expected_ack = 0;
uint32_t est_timeout = 0;
uint32_t timestamp = getRTCClock()->getCurrentTimeUnique();
int result = sendMessage(*recipient, timestamp, 0, pending->text, expected_ack, est_timeout);
success = result != MSG_SEND_FAILED;
+ BOT_DEBUG_PRINTF("CBOT-DBG: sendMessage result=%d success=%d\r\n", result, success);
if (success && expected_ack) {
expected_ack_table[next_ack_idx].msg_sent = _ms->getMillis();
expected_ack_table[next_ack_idx].ack = expected_ack;
@@ -1960,9 +2003,11 @@ void MyMesh::sendQueuedBotResponses() {
}
} else if (pending->channel_idx != 0xFF) {
ChannelDetails channel;
+ bool have_ch = getChannel(pending->channel_idx, channel);
uint32_t timestamp = getRTCClock()->getCurrentTimeUnique();
- success = getChannel(pending->channel_idx, channel) &&
- sendGroupMessage(timestamp, channel.channel, _prefs.node_name, pending->text, pending->text_len);
+ success = have_ch && sendGroupMessage(timestamp, channel.channel, _prefs.node_name, pending->text, pending->text_len);
+ BOT_DEBUG_PRINTF("CBOT-DBG: send CH idx=%u have=%d success=%d text=%.40s\r\n",
+ (unsigned)pending->channel_idx, have_ch, success, pending->text);
}
if (success) {
@@ -1970,6 +2015,7 @@ void MyMesh::sendQueuedBotResponses() {
ResponseCoordinator::recordRecent(bot_coordinator_recent, BOT_COORDINATOR_RECENT_SLOTS,
pending->response_fingerprint, now);
} else {
+ BOT_DEBUG_PRINTLN("CBOT-DBG: send FAILED -> bot stays silent");
bot_stats.send_failures++;
}
pending->active = false;
@@ -2521,9 +2567,14 @@ void MyMesh::begin(bool has_display) {
MESH_DEBUG_PRINTLN("RX Boosted Gain Mode: %s",
radio_driver.getRxBoostedGainMode() ? "Enabled" : "Disabled");
#if CMESH_BOT_ENABLED
- if (!_store->loadBotPrefs(bot_prefs)) bot_prefs.prefs_load_failures++;
+ bool loaded = _store->loadBotPrefs(bot_prefs);
+ if (!loaded) bot_prefs.prefs_load_failures++;
BotPrefsCodec::validate(bot_prefs);
applyBotPrefs();
+ BOT_DEBUG_PRINTF("CBOT-DBG: boot: loaded=%d enabled=%d bot_ch=%s testing_ch=%s mask=0x%08lx delay=%u jitter=%u hop_step=%u\r\n",
+ loaded, bot_prefs.enabled, bot_prefs.bot_channel, bot_prefs.testing_channel,
+ (unsigned long)bot_prefs.command_mask, bot_prefs.normal_delay_ms, bot_prefs.normal_jitter_ms,
+ bot_prefs.hop_step_ms);
#endif
}
diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h
index 46c3ff3c..cfd402df 100644
--- a/examples/companion_radio/MyMesh.h
+++ b/examples/companion_radio/MyMesh.h
@@ -215,6 +215,7 @@ private:
void recordBotObservation(const BotMessage &message, const ContactInfo *direct_recipient, uint8_t channel_idx);
bool observeKnownBotResponse(const BotMessage &message, bool authoritative_sender);
bool observeBotGroupResponse(const BotMessage &message);
+ BotFingerprint botRequestFingerprintFor(const BotMessage &message, uint8_t channel_idx);
void buildBotCommandContext(BotCommandContext &context, BotCommandId command_id);
bool enqueueBotResponse(const BotMessage &message, const ContactInfo *direct_recipient, uint8_t channel_idx,
const char *text, size_t text_len, BotFingerprint request_fingerprint,

View File

@@ -1138,6 +1138,26 @@ static void test_fingerprint() {
assert(fa.value != FirmwareBot::fingerprintFor(b).value); assert(fa.value != FirmwareBot::fingerprintFor(b).value);
} }
static void test_channel_id_fingerprint() {
uint8_t channel_id_a[4] = { 0x10, 0x20, 0x30, 0x40 };
uint8_t channel_id_b[4] = { 0x10, 0x20, 0x30, 0x41 };
BotMessage a = make_message("#bot", "!PING");
BotMessage b = make_message("#testing", "!ping");
BotMessage c = make_message("#ops", "!ping");
c.channel_kind = BOT_CHANNEL_BOT;
BotFingerprint fa = FirmwareBot::fingerprintFor(a, channel_id_a, sizeof(channel_id_a));
BotFingerprint fb = FirmwareBot::fingerprintFor(b, channel_id_a, sizeof(channel_id_a));
BotFingerprint fc = FirmwareBot::fingerprintFor(c, channel_id_a, sizeof(channel_id_a));
assert(fa.value == fb.value);
assert(fa.value == fc.value);
assert(fa.value != FirmwareBot::fingerprintFor(a, channel_id_b, sizeof(channel_id_b)).value);
BotMessage dm = a;
dm.channel_kind = BOT_CHANNEL_DM;
dm.channel_name[0] = 0;
assert(FirmwareBot::fingerprintFor(dm, channel_id_a, sizeof(channel_id_a)).value == FirmwareBot::fingerprintFor(dm).value);
}
static BotFingerprint final_response_fingerprint_for(const BotMessage& message, const char* text) { static BotFingerprint final_response_fingerprint_for(const BotMessage& message, const char* text) {
char response[BOT_MAX_RESPONSE_LEN + 1]; char response[BOT_MAX_RESPONSE_LEN + 1];
size_t written = 0; size_t written = 0;
@@ -1171,6 +1191,21 @@ static void test_response_fingerprint() {
FirmwareBot::responseFingerprintFor(other_dm, "pong!", 5).value); FirmwareBot::responseFingerprintFor(other_dm, "pong!", 5).value);
} }
static void test_response_fingerprint_ignores_request_token() {
BotMessage message = make_message("#bot", "ping");
BotFingerprint bare = FirmwareBot::responseFingerprintFor(message, "Pong!", 5);
assert(bare.value == FirmwareBot::responseFingerprintFor(message, "[1a2b] Pong!", 12).value);
assert(bare.value == FirmwareBot::responseFingerprintFor(message, "[1A2B] Pong!", 12).value);
assert(bare.value != FirmwareBot::responseFingerprintFor(message, "[bad] Pong!", 11).value);
BotMessage dm = message;
dm.channel_kind = BOT_CHANNEL_DM;
dm.channel_name[0] = 0;
dm.sender_key_prefix_len = BOT_SENDER_KEY_PREFIX_LEN;
assert(FirmwareBot::responseFingerprintFor(dm, "Pong!", 5).value ==
FirmwareBot::responseFingerprintFor(dm, "[1a2b] Pong!", 12).value);
}
static void test_authoritative_suppression_flow() { static void test_authoritative_suppression_flow() {
BotKnownBotEntry entries[1]; BotKnownBotEntry entries[1];
uint8_t known_key[BOT_SENDER_KEY_PREFIX_LEN] = { 2, 4, 6, 8, 10, 12 }; uint8_t known_key[BOT_SENDER_KEY_PREFIX_LEN] = { 2, 4, 6, 8, 10, 12 };
@@ -1665,7 +1700,9 @@ int main() {
test_known_bot_registry(); test_known_bot_registry();
test_known_bot_registry_ambiguous_short_prefix(); test_known_bot_registry_ambiguous_short_prefix();
test_fingerprint(); test_fingerprint();
test_channel_id_fingerprint();
test_response_fingerprint(); test_response_fingerprint();
test_response_fingerprint_ignores_request_token();
test_authoritative_suppression_flow(); test_authoritative_suppression_flow();
test_response_coordinator_schedule_poll(); test_response_coordinator_schedule_poll();
test_response_coordinator_distinct_requests_same_response(); test_response_coordinator_distinct_requests_same_response();