Remove TMM relayed-position precision clamp (#12039)

The clamp rewrote the payload of relayed position broadcasts. The XEdDSA
signature covers Data.payload, so receivers that know the sender's key
dropped the rewritten packet. Relays now forward position payloads
unmodified.

Removes USERPREFS_TMM_APPLY_TO_PRIVATE_CHANNELS, which only gated the clamp.


Claude-Session: https://claude.ai/code/session_01NmhtcXBe3B3NvS9RttzwHW

Co-authored-by: Jonathan Bennett <jonathan@meshtastic.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
authored and GitHub committed 2026-10-02 01:50:43 +00:00
1 parent dee945cd6a
commit 34c9457c69
5 files changed
+16 -83

No files matched your search

-1
View File
@@ -49,7 +49,6 @@ enum class TrafficType { POSITION, TELEMETRY };
#define default_traffic_mgmt_tracker_position_min_interval_secs (60 * 60) // 1 hour
// Role cap: lost-and-found origins may refresh a duplicate position this often, so a lost
// device updates frequently without flooding. (Quantised to the dedup tick: ~2 ticks.)
// Unlike before, lost-and-found is NOT exempt from the relayed precision clamp.
#define default_traffic_mgmt_lost_and_found_position_min_interval_secs (15 * 60) // 15 minutes
// Hop scaling defaults
+2 -48
View File
@@ -1069,14 +1069,14 @@ uint8_t TrafficManagementModule::computePositionFingerprint(int32_t lat_truncate
/// Runs BEFORE RoutingModule in callModules(): STOP fully consumes the packet (no rebroadcast),
/// ignoreRequest suppresses the default NAK for want_response packets, and exhaustRequested
/// (set by alterReceived) makes perhapsRebroadcast() force hop_limit=0 on the relayed copy.
/// makes perhapsRebroadcast() force hop_limit=0 on the relayed copy.
ProcessMessage TrafficManagementModule::handleReceived(const meshtastic_MeshPacket &mp)
{
if (!moduleConfig.has_traffic_management)
return ProcessMessage::CONTINUE;
ignoreRequest = false;
exhaustRequested = false; // Reset per-packet; may be set by alterReceived() below
exhaustRequested = false; // Reset per-packet
exhaustRequestedFrom = 0;
exhaustRequestedId = 0;
incrementStat(&stats.packets_inspected);
@@ -1182,52 +1182,6 @@ ProcessMessage TrafficManagementModule::handleReceived(const meshtastic_MeshPack
return ProcessMessage::CONTINUE;
}
void TrafficManagementModule::alterReceived(meshtastic_MeshPacket &mp)
{
if (!moduleConfig.has_traffic_management)
return;
if (mp.which_payload_variant != meshtastic_MeshPacket_decoded_tag)
return;
if (isFromUs(&mp))
return;
// exhaust_hop_telemetry / exhaust_hop_position / router_preserve_hops: shelved until the
// right heuristics are clearer; exhaustRequested stays false and rebroadcast is normal.
const bool isPosition = mp.decoded.portnum == meshtastic_PortNum_POSITION_APP;
// -------------------------------------------------------------------------
// Relayed Position Precision Clamp
// -------------------------------------------------------------------------
// Never forward more-precise coordinates than the channel is configured to carry
// (chanPrec==0 = sharing disabled on channel: skip). Ham mode is exempt; lost-and-found
// is not. Compile USERPREFS_TMM_APPLY_TO_PRIVATE_CHANNELS to extend to private channels.
if (!owner.is_licensed && isPosition && isBroadcast(mp.to)) {
#ifdef USERPREFS_TMM_APPLY_TO_PRIVATE_CHANNELS
const bool shouldClamp = true;
#else
const bool shouldClamp = channels.isWellKnownChannel(mp.channel);
#endif
if (shouldClamp) {
const uint32_t chanPrec = getPositionPrecisionForChannel(mp.channel);
if (chanPrec > 0) {
meshtastic_Position pos = meshtastic_Position_init_default;
if (pb_decode_from_bytes(mp.decoded.payload.bytes, mp.decoded.payload.size, &meshtastic_Position_msg, &pos)) {
const uint32_t packetPrec = pos.precision_bits > 0 ? pos.precision_bits : 32u;
if (packetPrec > chanPrec) {
applyPositionPrecision(pos, chanPrec);
mp.decoded.payload.size = pb_encode_to_bytes(mp.decoded.payload.bytes, sizeof(mp.decoded.payload.bytes),
&meshtastic_Position_msg, &pos);
logAction("clamp", &mp, "precision");
}
}
}
}
}
}
// =============================================================================
// Periodic Maintenance
// =============================================================================
+3 -5
View File
@@ -95,7 +95,7 @@ class TrafficManagementModule : public MeshModule, private concurrency::OSThread
void purgeAll();
/// True when perhapsRebroadcast() must force hop_limit=0 for this packet, regardless of
/// router_preserve_hops or favorite-node logic (set by alterReceived()).
/// router_preserve_hops or favorite-node logic.
bool shouldExhaustHops(const meshtastic_MeshPacket &mp) const
{
return exhaustRequested && exhaustRequestedFrom == getFrom(&mp) && exhaustRequestedId == mp.id;
@@ -116,8 +116,6 @@ class TrafficManagementModule : public MeshModule, private concurrency::OSThread
ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override;
/// Promiscuous: this module inspects every packet.
bool wantPacket(const meshtastic_MeshPacket *p) override { return true; }
/// Mutate relayed packets in place (position precision clamp).
void alterReceived(meshtastic_MeshPacket &mp) override;
/// 60 s maintenance sweep: expire timed state, saturate tick stamps, reconcile with NodeDB.
int32_t runOnce() override;
/// Clear all per-node traffic state (protected for test shims).
@@ -299,8 +297,8 @@ class TrafficManagementModule : public MeshModule, private concurrency::OSThread
meshtastic_TrafficManagementStats stats;
// Set during alterReceived() when the packet's hops should be exhausted; checked by
// perhapsRebroadcast() for the matching packet key. Reset at start of handleReceived().
// Set when the packet's hops should be exhausted; checked by perhapsRebroadcast() for the
// matching packet key. Reset at start of handleReceived().
bool exhaustRequested = false;
NodeNum exhaustRequestedFrom = 0;
PacketId exhaustRequestedId = 0;
+11 -28
View File
@@ -292,12 +292,6 @@ static meshtastic_MeshPacket makePositionPacketWithPrecision(NodeNum from, int32
return packet;
}
static bool decodePositionPayload(const meshtastic_MeshPacket &packet, meshtastic_Position &out)
{
out = meshtastic_Position_init_zero;
return pb_decode_from_bytes(packet.decoded.payload.bytes, packet.decoded.payload.size, &meshtastic_Position_msg, &out);
}
// Primary channel with a well-known single-byte PSK and the (empty -> preset)
// default name, so Channels::isWellKnownChannel(0) is true.
static void installWellKnownPrimaryChannel()
@@ -2322,8 +2316,6 @@ static void test_tm_alterReceived_telemetryBroadcast_hopLimitUnchanged(void)
/**
* Verify alterReceived does not modify unicast or local-origin packets.
* The precision clamp (the only active alterReceived path) only fires for
* broadcast position packets from remote nodes - these should be untouched.
*/
static void test_tm_alterReceived_skipsLocalAndUnicast(void)
{
@@ -3163,36 +3155,27 @@ static void test_tm_unknownRole_noUserBit_appliesFullInterval(void)
}
/**
* Verify a LOST_AND_FOUND origin now GETS the relayed precision clamp - the
* anti-dox exemption was removed, so a relayed position more precise than the
* channel setting is clamped down to the channel ceiling like any other node's.
* Verify a relayed position broadcast more precise than the channel setting passes through
* handleReceived/alterReceived unmodified. Relays must forward position payloads byte-for-byte
* so the sender's XEdDSA signature still verifies downstream.
*/
static void test_tm_lostAndFoundRole_getsAlterReceivedPrecisionClamp(void)
static void test_tm_relayedPosition_payloadUnmodified(void)
{
// Set channel precision ceiling to 13 bits. Must be <= MAX_POSITION_PRECISION_PUBLIC_KEY
// (15) - well-known channels have a public PSK (size==1), so getPositionPrecisionForChannel
// clamps any value above 15 via usesPublicKey().
installWellKnownPrimaryChannelWithPrecision(13);
mockNodeDB->setCachedNode(kRemoteNode);
mockNodeDB->setCachedNodeRole(meshtastic_Config_DeviceConfig_Role_LOST_AND_FOUND);
TrafficManagementModuleTestShim module;
// Full-precision packet - 32 bits - exceeds the channel cap.
const uint32_t fullPrecision = 32;
meshtastic_MeshPacket packet = makePositionPacketWithPrecision(kRemoteNode, 374221234, -1220845678, fullPrecision);
meshtastic_MeshPacket packet = makePositionPacketWithPrecision(kRemoteNode, 374221234, -1220845678, 32);
packet.hop_start = 3;
packet.hop_limit = 2; // relayed (hop_limit < hop_start) so clamp logic applies
packet.hop_limit = 2;
const meshtastic_Data_payload_t original = packet.decoded.payload;
TEST_ASSERT_EQUAL_INT(static_cast<int>(ProcessMessage::CONTINUE), static_cast<int>(module.handleReceived(packet)));
module.alterReceived(packet);
meshtastic_Position out;
TEST_ASSERT_TRUE(decodePositionPayload(packet, out));
// Clamped to channel ceiling (13 bits) - lost-and-found is no longer exempt.
// Note: precision must be <= MAX_POSITION_PRECISION_PUBLIC_KEY (15); well-known
// channels always have a public PSK so getPositionPrecisionForChannel caps at 15.
TEST_ASSERT_EQUAL_UINT32(13, out.precision_bits);
TEST_ASSERT_EQUAL_UINT32(original.size, packet.decoded.payload.size);
TEST_ASSERT_EQUAL_MEMORY(original.bytes, packet.decoded.payload.bytes, original.size);
}
// ---------------------------------------------------------------------------
@@ -3405,7 +3388,7 @@ TM_TEST_ENTRY void setup()
RUN_TEST(test_tm_specialRole_evictedLastUnderPressure);
RUN_TEST(test_tm_trackerRole_doesNotLengthenShorterOperatorInterval);
RUN_TEST(test_tm_lostAndFoundRole_capsDedupAtFifteenMinutes);
RUN_TEST(test_tm_lostAndFoundRole_getsAlterReceivedPrecisionClamp);
RUN_TEST(test_tm_relayedPosition_payloadUnmodified);
RUN_TEST(test_tm_unknownRole_noDbEntry_appliesFullInterval);
RUN_TEST(test_tm_unknownRole_noUserBit_appliesFullInterval);
RUN_TEST(test_tm_fuzz_nodenum_blitz);
-1
View File
@@ -47,7 +47,6 @@
// "USERPREFS_EVENT_MODE": "1",
// "USERPREFS_EVENT_MODE_HOP_LIMIT": "3", // Event-mode default and firmware-generated/relay hop cap (0-7; default 3)
// "USERPREFS_BLOCK_POSITION_ON_EVENT_CHANNEL": "1", // Block location TX + discard inbound location on channels keyed with USERPREFS_CHANNEL_0_PSK. Defaults off, and must be set explicitly.
// "USERPREFS_TMM_APPLY_TO_PRIVATE_CHANNELS": "1", // Extend TMM position dedup and precision clamping to private/custom-key channels (default: well-known channels only)
// "USERPREFS_FIRMWARE_EDITION": "meshtastic_FirmwareEdition_BURNING_MAN",
// "USERPREFS_FIXED_BLUETOOTH": "121212",
// "USERPREFS_FIXED_GPS": "",