diff --git a/examples/companion_radio/MeshtextCompanion.cpp b/examples/companion_radio/MeshtextCompanion.cpp index 9306ce440..7393577f8 100644 --- a/examples/companion_radio/MeshtextCompanion.cpp +++ b/examples/companion_radio/MeshtextCompanion.cpp @@ -775,11 +775,11 @@ bool MyMesh::handleMeshtextCommand(const char* command, char* reply, size_t capa ChannelDetails channel; if (!nextToken(nodeinfo_args, index_text, sizeof(index_text)) || !endTokens(nodeinfo_args) || !mesh::cli::parseUnsignedIntegerStrict(index_text, index) || index >= MAX_GROUP_CHANNELS) { - snprintf(reply, capacity, "Error: use send mt.nodeinfo "); + mesh::cli::formatError(reply, capacity, "use send mt.nodeinfo "); } else if (!getChannelForSave(index, channel) || !isMeshtextChannel(channel)) { - snprintf(reply, capacity, "Error: configured Meshtext channel required"); + mesh::cli::formatError(reply, capacity, "configured Meshtext channel required"); } else if (!sendMeshtextNodeInfo(channel)) { - snprintf(reply, capacity, "Error: NodeInfo not queued; check get mt.status, TX mode or pending TX"); + mesh::cli::formatError(reply, capacity, "NodeInfo not queued; check get mt.status, TX mode or pending TX"); } else { snprintf(reply, capacity, "OK - NodeInfo queued on Meshtext channel %lu; no GPS", (unsigned long)index); @@ -794,19 +794,19 @@ bool MyMesh::handleMeshtextCommand(const char* command, char* reply, size_t capa char index_text[12]; uint32_t index = 0; if (!nextToken(cursor, index_text, sizeof(index_text)) || !mesh::cli::parseUnsignedIntegerStrict(index_text, index) || index >= MAX_GROUP_CHANNELS) { - snprintf(reply, capacity, "Error: invalid channel slot"); return true; + mesh::cli::formatError(reply, capacity, "invalid channel slot"); return true; } ChannelDetails previous{}, channel{}; if (!getChannelForSave(index, previous)) { - snprintf(reply, capacity, "Error: channel slot unavailable"); return true; + mesh::cli::formatError(reply, capacity, "channel slot unavailable"); return true; } if (channel_get || channel_delete) { if (!endTokens(cursor) || !previous.name[0] || !isMeshtextChannel(previous)) { - snprintf(reply, capacity, "Error: Meshtext channel not found or extra arguments"); return true; + mesh::cli::formatError(reply, capacity, "Meshtext channel not found or extra arguments"); return true; } if (channel_get) { mesh::mt::Channel wire; - if (!describe(previous, wire)) snprintf(reply, capacity, "Error: invalid Meshtext channel"); + if (!describe(previous, wire)) mesh::cli::formatError(reply, capacity, "invalid Meshtext channel"); else snprintf(reply, capacity, "> %lu %s; wire=%s; AES-%u; key not displayed", (unsigned long)index, previous.name, wire.name, unsigned(wire.key_length * 8)); return true; @@ -816,24 +816,24 @@ bool MyMesh::handleMeshtextCommand(const char* command, char* reply, size_t capa if (!nextToken(cursor, channel.name, sizeof(channel.name)) || !nextToken(cursor, key_text, sizeof(key_text)) || !endTokens(cursor) || !isMeshtextChannel(channel)) { - snprintf(reply, capacity, "Error: use set mt.channel <32/64 hex digits|AQ==>"); return true; + mesh::cli::formatError(reply, capacity, "use set mt.channel <32/64 hex digits|AQ==>"); return true; } if (!strcmp(key_text, "AQ==")) memcpy(channel.channel.secret, mesh::mt::DefaultPsk, 16); else { const size_t digits = strlen(key_text); if ((digits != 32 && digits != 64) || !mesh::Utils::fromHex(channel.channel.secret, digits / 2, key_text)) { - snprintf(reply, capacity, "Error: key must be 32/64 hex digits or AQ=="); return true; + mesh::cli::formatError(reply, capacity, "key must be 32/64 hex digits or AQ=="); return true; } } if (!validateMeshtextChannelChange(index, channel)) { - snprintf(reply, capacity, "Error: conflicting Meshtext preset, region, manual radio2 or busy owner"); return true; + mesh::cli::formatError(reply, capacity, "conflicting Meshtext preset, region, manual radio2 or busy owner"); return true; } } - if (!setChannel(index, channel)) snprintf(reply, capacity, "Error: invalid Meshtext channel"); + if (!setChannel(index, channel)) mesh::cli::formatError(reply, capacity, "invalid Meshtext channel"); else if (!saveChannels()) { setChannel(index, previous); - snprintf(reply, capacity, "Error: channel not saved; previous channel restored"); + mesh::cli::formatError(reply, capacity, "channel not saved; previous channel restored"); } else { _mt_seen.clear(); _mt_profile_dirty = true; refreshMeshtextProfile(); @@ -848,7 +848,7 @@ bool MyMesh::handleMeshtextCommand(const char* command, char* reply, size_t capa if (!mesh::mt::parsePreset(preset_value, preset) || !mesh::mt::modemConfig(meshtextRegion(), preset, requested, _mt_slot) || !_radio->supportsMeshtext() || !_radio->validateProfile(requested)) { - snprintf(reply, capacity, "Error: invalid Meshtext preset/region/slot; use set mt.region first"); return true; + mesh::cli::formatError(reply, capacity, "invalid Meshtext preset/region/slot; use set mt.region first"); return true; } // Explicit provisioning for custom private names. This is a manual saved // radio2 choice, not an automatic alias owner, so deletion cannot undo it. @@ -896,16 +896,16 @@ bool MyMesh::handleMeshtextCommand(const char* command, char* reply, size_t capa if (rebroadcast_value) { if (!strcmp(rebroadcast_value, "on")) rebroadcast = 1; else if (!strcmp(rebroadcast_value, "off")) rebroadcast = 0; - else { snprintf(reply, capacity, "Error: use set mt.rebroadcast on|off"); return true; } + else { mesh::cli::formatError(reply, capacity, "use set mt.rebroadcast on|off"); return true; } } if (region_value && !mesh::mt::parseRegion(region_value, region)) { - snprintf(reply, capacity, "Error: use set mt.region auto|us|eu868|eu433|anz"); return true; + mesh::cli::formatError(reply, capacity, "use set mt.region auto|us|eu868|eu433|anz"); return true; } if (slot_value && (!mesh::cli::parseUnsignedIntegerStrict(slot_value, slot) || slot > 1024)) { - snprintf(reply, capacity, "Error: mt.slot must be 0 (preset default) or 1..1024"); return true; + mesh::cli::formatError(reply, capacity, "mt.slot must be 0 (preset default) or 1..1024"); return true; } if (hops_value && (!mesh::cli::parseUnsignedIntegerStrict(hops_value, hops) || hops > 7)) { - snprintf(reply, capacity, "Error: mt.hops must be 0..7"); return true; + mesh::cli::formatError(reply, capacity, "mt.hops must be 0..7"); return true; } if (node_value) { if (!strcmp(node_value, "default")) node_override = 0; @@ -913,23 +913,23 @@ bool MyMesh::handleMeshtextCommand(const char* command, char* reply, size_t capa if (*node_value == '!') ++node_value; uint8_t bytes[4]; if (strlen(node_value) != 8 || !mesh::Utils::fromHex(bytes, sizeof(bytes), node_value)) { - snprintf(reply, capacity, "Error: use set mt.node !<8 hex digits>|default"); return true; + mesh::cli::formatError(reply, capacity, "use set mt.node !<8 hex digits>|default"); return true; } node_override = (uint32_t(bytes[0]) << 24) | (uint32_t(bytes[1]) << 16) | (uint32_t(bytes[2]) << 8) | uint32_t(bytes[3]); if (!node_override || !validNodeOverride(node_override)) { - snprintf(reply, capacity, "Error: mt.node IDs 0..3 and ffffffff are reserved; use default to reset"); return true; + mesh::cli::formatError(reply, capacity, "mt.node IDs 0..3 and ffffffff are reserved; use default to reset"); return true; } } } if (short_name_value) { short_name = !strcmp(short_name_value, "default") ? "" : short_name_value; if ((!*short_name && strcmp(short_name_value, "default")) || !validShortName(short_name)) { - snprintf(reply, capacity, "Error: mt.shortname must be 1..4 UTF-8 characters (no spaces/control/colon), or default"); return true; + mesh::cli::formatError(reply, capacity, "mt.shortname must be 1..4 UTF-8 characters (no spaces/control/colon), or default"); return true; } } if (!saveMeshtextSettings(region, slot, hops, short_name, node_override, rebroadcast)) - snprintf(reply, capacity, "Error: Meshtext settings not saved (pending TX or storage error)"); + mesh::cli::formatError(reply, capacity, "Meshtext settings not saved (pending TX or storage error)"); else if (rebroadcast_value) snprintf(reply, capacity, "OK - mt.rebroadcast=%s; extra wait=5 packet airtimes", rebroadcast ? "on" : "off"); else if (node_value) diff --git a/src/helpers/SimpleMeshTables.h b/src/helpers/SimpleMeshTables.h index 0e1914bc8..937c631ac 100644 --- a/src/helpers/SimpleMeshTables.h +++ b/src/helpers/SimpleMeshTables.h @@ -199,6 +199,16 @@ private: } } + void clearHash(const uint8_t* hash) { + uint8_t* sp = _hashes; + for (int i = 0; i < MAX_PACKET_HASHES; i++, sp += MAX_HASH_SIZE) { + if (memcmp(hash, sp, MAX_HASH_SIZE) == 0) { + memset(sp, 0, MAX_HASH_SIZE); + break; + } + } + } + int8_t weightedSnrX4RoundUp(int8_t curr_snr_x4, int8_t new_snr_x4) const { // Keep existing SNR heavier than a single new sample: 75% existing + 25% new. int32_t weighted_sum = ((int32_t)curr_snr_x4 * 3) + (int32_t)new_snr_x4; @@ -433,17 +443,22 @@ public: return; } - uint8_t* sp = _hashes; - for (int i = 0; i < MAX_PACKET_HASHES; i++, sp += MAX_HASH_SIZE) { - if (memcmp(key.hash, sp, MAX_HASH_SIZE) == 0) { - memset(sp, 0, MAX_HASH_SIZE); - break; - } - } + clearHash(key.hash); } void clear(const mesh::Packet* packet) override { - clearSeenKey(seenKey(*packet)); + // Packet callers do not need a retained token. Keep this path direct so + // small targets avoid its initialization and return-value overhead. + if (isDedicatedAckPacket(packet)) { + uint8_t hash[MAX_HASH_SIZE]; + packet->calculatePacketHash(hash); + clearAckHash(hash); + return; + } + + uint8_t hash[MAX_HASH_SIZE]; + packet->calculatePacketHash(hash); + clearHash(hash); } uint32_t getNumDirectDups() const { return _direct_dups; }