Skip to content

Commit 0fef83d

Browse files
authored
Add configurable event mode hop limit (#11275)
* feat: resolve event mode hop limit * feat: bake event mode hop limit * fix: honor event mode hop cap in routing * docs: expose event mode hop limit preference * fix: enforce event hop defaults across routing * docs: clarify event hop override behavior * refactor: simplify event mode hop preference * fix: cap equal event hop limit
1 parent b4ff1df commit 0fef83d

8 files changed

Lines changed: 126 additions & 13 deletions

File tree

src/mesh/Default.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ uint32_t Default::getConfiguredOrMinimumValue(uint32_t configured, uint32_t minV
9292
uint8_t Default::getConfiguredOrDefaultHopLimit(uint8_t configured)
9393
{
9494
#if USERPREFS_EVENT_MODE
95-
return (configured > HOP_RELIABLE) ? HOP_RELIABLE : config.lora.hop_limit;
95+
return (configured >= eventModeHopLimit) ? eventModeHopLimit : config.lora.hop_limit;
9696
#else
9797
return (configured >= HOP_MAX) ? HOP_MAX : config.lora.hop_limit;
9898
#endif

src/mesh/Default.h

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include <cmath>
66
#include <cstdint>
77
#include <meshUtils.h>
8+
#include <type_traits>
89
#define ONE_DAY 24 * 60 * 60
910
#define ONE_MINUTE_MS 60 * 1000
1011
#define THIRTY_SECONDS_MS 30 * 1000
@@ -75,7 +76,20 @@ enum class TrafficType { POSITION, TELEMETRY };
7576

7677
class Default
7778
{
79+
#if USERPREFS_EVENT_MODE && defined(USERPREFS_EVENT_MODE_HOP_LIMIT)
80+
static constexpr auto eventModeHopLimitSetting = USERPREFS_EVENT_MODE_HOP_LIMIT;
81+
#else
82+
static constexpr auto eventModeHopLimitSetting = HOP_RELIABLE;
83+
#endif
84+
using EventModeHopLimitType = typename std::remove_cv<decltype(eventModeHopLimitSetting)>::type;
85+
static_assert(std::is_integral<EventModeHopLimitType>::value && !std::is_same<EventModeHopLimitType, bool>::value &&
86+
eventModeHopLimitSetting >= 0 && eventModeHopLimitSetting <= HOP_MAX,
87+
"USERPREFS_EVENT_MODE_HOP_LIMIT must be an integer between 0 and 7");
88+
7889
public:
90+
static constexpr uint8_t eventModeHopLimit = static_cast<uint8_t>(eventModeHopLimitSetting);
91+
static constexpr uint8_t eventModeRelayHopLimit = eventModeHopLimit > 0 ? eventModeHopLimit - 1 : 0;
92+
7993
static uint32_t getConfiguredOrDefaultMs(uint32_t configuredInterval);
8094
static uint32_t getConfiguredOrDefaultMs(uint32_t configuredInterval, uint32_t defaultInterval);
8195
static uint32_t getConfiguredOrDefault(uint32_t configured, uint32_t defaultValue);
@@ -129,4 +143,4 @@ class Default
129143
return 1.0 + (nodesOverForty * throttlingFactor); // Each number of online node scales by throttle factor
130144
}
131145
}
132-
};
146+
};

src/mesh/NextHopRouter.cpp

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include "NextHopRouter.h"
2+
#include "Default.h"
23
#include "MeshTypes.h"
34
#include "meshUtils.h"
45
#if !MESHTASTIC_EXCLUDE_TRACEROUTE
@@ -9,6 +10,18 @@
910
#endif
1011
#include "NodeDB.h"
1112

13+
#if USERPREFS_EVENT_MODE
14+
static void capEventRelayHops(meshtastic_MeshPacket *packet)
15+
{
16+
if (packet->hop_limit <= Default::eventModeRelayHopLimit)
17+
return;
18+
19+
const uint8_t reduction = packet->hop_limit - Default::eventModeRelayHopLimit;
20+
packet->hop_start = reduction <= packet->hop_start ? packet->hop_start - reduction : 0;
21+
packet->hop_limit = Default::eventModeRelayHopLimit;
22+
}
23+
#endif
24+
1225
NextHopRouter::NextHopRouter() {}
1326

1427
bool NextHopRouter::relayOpaquePacket(const meshtastic_MeshPacket *p)
@@ -26,6 +39,9 @@ bool NextHopRouter::relayOpaquePacket(const meshtastic_MeshPacket *p)
2639
if (!relay)
2740
return false;
2841
relay->hop_limit--;
42+
#if USERPREFS_EVENT_MODE
43+
capEventRelayHops(relay);
44+
#endif
2945
relay->relay_node = nodeDB->getLastByteOfNodeNum(getNodeNum());
3046
// The interface declines some packets (NODENUM_BROADCAST_NO_LORA) with ERRNO_SHOULD_RELEASE,
3147
// which leaves the copy ours to free. Dropping it here would leak a pool slot per opaque frame.
@@ -211,11 +227,7 @@ bool NextHopRouter::perhapsRebroadcast(const meshtastic_MeshPacket *p)
211227
LOG_INFO("favorite-ROUTER/CLIENT_BASE-to-ROUTER/CLIENT_BASE rebroadcast: preserving hop_limit");
212228
}
213229
#if USERPREFS_EVENT_MODE
214-
if (tosend->hop_limit > 2) {
215-
// if we are "correcting" the hop_limit, "correct" the hop_start by the same amount to preserve hops away.
216-
tosend->hop_start -= (tosend->hop_limit - 2);
217-
tosend->hop_limit = 2;
218-
}
230+
capEventRelayHops(tosend);
219231
#endif
220232

221233
ErrorCode res =

src/mesh/NodeDB.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -927,7 +927,11 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
927927
config.lora.override_frequency = USERPREFS_LORACONFIG_OVERRIDE_FREQUENCY;
928928
#endif
929929

930+
#if USERPREFS_EVENT_MODE
931+
config.lora.hop_limit = Default::eventModeHopLimit;
932+
#else
930933
config.lora.hop_limit = HOP_RELIABLE;
934+
#endif
931935
#ifdef USERPREFS_CONFIG_LORA_IGNORE_MQTT
932936
config.lora.ignore_mqtt = USERPREFS_CONFIG_LORA_IGNORE_MQTT;
933937
#else

src/modules/RoutingModule.cpp

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -64,19 +64,20 @@ void RoutingModule::sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketI
6464
uint8_t RoutingModule::getHopLimitForResponse(const meshtastic_MeshPacket &mp)
6565
{
6666
const int8_t hopsUsed = getHopsAway(mp);
67+
const uint8_t responseHopLimit = Default::getConfiguredOrDefaultHopLimit(config.lora.hop_limit);
6768
if (hopsUsed >= 0) {
68-
if (hopsUsed > (int32_t)(config.lora.hop_limit)) {
69-
// In event mode, we never want to send packets with more than our default 3 hops.
70-
#if !(EVENTMODE) // This falls through to the default.
69+
if (hopsUsed > static_cast<int32_t>(responseHopLimit)) {
70+
// In event mode, never exceed the configured event hop limit.
71+
#if !USERPREFS_EVENT_MODE // This falls through to the default.
7172
return hopsUsed; // If the request used more hops than the limit, use the same amount of hops
7273
#endif
7374
} else if (mp.hop_start == 0) {
7475
return 0; // The requesting node wanted 0 hops, so the response also uses a direct/local path.
75-
} else if ((uint8_t)(hopsUsed + 2) < config.lora.hop_limit) {
76+
} else if (static_cast<uint8_t>(hopsUsed + 2) < responseHopLimit) {
7677
return hopsUsed + 2; // Use only the amount of hops needed with some margin as the way back may be different
7778
}
7879
}
79-
return Default::getConfiguredOrDefaultHopLimit(config.lora.hop_limit); // Use the default hop limit
80+
return responseHopLimit;
8081
}
8182

8283
meshtastic_MeshPacket *RoutingModule::allocAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex,
@@ -93,4 +94,4 @@ RoutingModule::RoutingModule() : ProtobufModule("routing", meshtastic_PortNum_RO
9394
// LocalOnly requires either the from or to to be a known node
9495
// knownOnly specifically requires the from to be a known node.
9596
encryptedOk = true;
96-
}
97+
}

test/test_default/test_main.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
#include "MeshRadio.h"
44
#include "TestUtil.h"
55
#include "meshUtils.h"
6+
#include "modules/RoutingModule.h"
7+
#include <algorithm>
68
#include <unity.h>
79

810
// Helper to compute expected ms using same logic as Default::congestionScalingCoefficient
@@ -181,6 +183,32 @@ void test_scaled_overflow_saturates()
181183
TEST_ASSERT_EQUAL_UINT32(static_cast<uint32_t>(INT32_MAX), res);
182184
}
183185

186+
void test_configured_or_default_hop_limit()
187+
{
188+
config.lora.hop_limit = HOP_MAX;
189+
const uint8_t result = Default::getConfiguredOrDefaultHopLimit(config.lora.hop_limit);
190+
191+
#if USERPREFS_EVENT_MODE
192+
TEST_ASSERT_EQUAL_UINT8(Default::eventModeHopLimit, result);
193+
TEST_ASSERT_EQUAL_UINT8(Default::eventModeHopLimit, Default::getConfiguredOrDefaultHopLimit(Default::eventModeHopLimit));
194+
#else
195+
TEST_ASSERT_EQUAL_UINT8(HOP_MAX, result);
196+
#endif
197+
}
198+
199+
#if USERPREFS_EVENT_MODE
200+
void test_event_mode_caps_optimized_response()
201+
{
202+
config.lora.hop_limit = HOP_MAX;
203+
meshtastic_MeshPacket request = meshtastic_MeshPacket_init_zero;
204+
request.hop_start = HOP_MAX;
205+
request.hop_limit = HOP_MAX - 4;
206+
207+
RoutingModule module;
208+
TEST_ASSERT_EQUAL_UINT8(std::min<uint8_t>(6, Default::eventModeHopLimit), module.getHopLimitForResponse(request));
209+
}
210+
#endif
211+
184212
void setup()
185213
{
186214
// Small delay to match other test mains
@@ -201,6 +229,10 @@ void setup()
201229
RUN_TEST(test_ms_default_clamps);
202230
RUN_TEST(test_ms_result_is_int32_safe);
203231
RUN_TEST(test_scaled_overflow_saturates);
232+
RUN_TEST(test_configured_or_default_hop_limit);
233+
#if USERPREFS_EVENT_MODE
234+
RUN_TEST(test_event_mode_caps_optimized_response);
235+
#endif
204236
exit(UNITY_END());
205237
}
206238

test/test_nexthop_routing/test_main.cpp

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
#include "configuration.h"
1515
#include "gps/RTC.h"
16+
#include "mesh/Default.h"
1617
#include "mesh/NextHopRouter.h"
1718
#include "mesh/NodeDB.h"
1819
#include "mesh/RadioInterface.h"
@@ -89,6 +90,7 @@ class NextHopRouterTestShim : public NextHopRouter
8990
using NextHopRouter::noteRouteLearned;
9091
using NextHopRouter::noteRouteSuccess;
9192
using NextHopRouter::perhapsRebroadcast;
93+
using NextHopRouter::relayOpaquePacket;
9294
using Router::shouldDecrementHopLimit; // protected in Router
9395

9496
void resetRouteHealthForTest()
@@ -108,6 +110,8 @@ class MockRadioInterface : public RadioInterface
108110
ErrorCode send(meshtastic_MeshPacket *p) override
109111
{
110112
sendCount++;
113+
lastHopLimit = p->hop_limit;
114+
lastHopStart = p->hop_start;
111115
if (declineAll || p->to == NODENUM_BROADCAST_NO_LORA)
112116
return ERRNO_SHOULD_RELEASE;
113117

@@ -124,6 +128,8 @@ class MockRadioInterface : public RadioInterface
124128

125129
int sendCount = 0;
126130
bool declineAll = false;
131+
uint8_t lastHopLimit = 0;
132+
uint8_t lastHopStart = 0;
127133
};
128134

129135
static MockNodeDB *mockNodeDB = nullptr;
@@ -498,6 +504,46 @@ void test_rebroadcast_declined_send_releases_packet(void)
498504
TEST_ASSERT_EQUAL_MESSAGE(1, mockIface->sendCount, "the copy must have reached the mock radio");
499505
}
500506

507+
#if USERPREFS_EVENT_MODE
508+
void test_event_mode_hop_behavior(void)
509+
{
510+
MockRadioInterface *mockIface = installMockIface();
511+
meshtastic_MeshPacket p = makeRebroadcastCandidate(NODENUM_BROADCAST);
512+
p.from = kLocalNode;
513+
p.hop_start = 0;
514+
p.hop_limit = HOP_MAX;
515+
516+
TEST_ASSERT_EQUAL(ERRNO_OK, shim->send(packetPool.allocCopy(p)));
517+
TEST_ASSERT_EQUAL_UINT8(HOP_MAX, mockIface->lastHopLimit);
518+
TEST_ASSERT_EQUAL_UINT8(HOP_MAX, mockIface->lastHopStart);
519+
520+
p = makeRebroadcastCandidate(NODENUM_BROADCAST);
521+
p.hop_start = HOP_MAX;
522+
p.hop_limit = HOP_MAX;
523+
524+
TEST_ASSERT_TRUE(shim->perhapsRebroadcast(&p));
525+
TEST_ASSERT_EQUAL_UINT8(Default::eventModeRelayHopLimit, mockIface->lastHopLimit);
526+
TEST_ASSERT_EQUAL_UINT8(static_cast<uint8_t>(Default::eventModeRelayHopLimit + 1), mockIface->lastHopStart);
527+
528+
config.device.rebroadcast_mode = meshtastic_Config_DeviceConfig_RebroadcastMode_ALL;
529+
p = makeRebroadcastCandidate(NODENUM_BROADCAST);
530+
p.hop_start = HOP_MAX;
531+
p.hop_limit = HOP_MAX;
532+
533+
TEST_ASSERT_TRUE(shim->relayOpaquePacket(&p));
534+
TEST_ASSERT_EQUAL_UINT8(Default::eventModeRelayHopLimit, mockIface->lastHopLimit);
535+
TEST_ASSERT_EQUAL_UINT8(static_cast<uint8_t>(Default::eventModeRelayHopLimit + 1), mockIface->lastHopStart);
536+
537+
p = makeRebroadcastCandidate(NODENUM_BROADCAST);
538+
p.hop_start = 0;
539+
p.hop_limit = HOP_MAX;
540+
541+
TEST_ASSERT_TRUE(shim->relayOpaquePacket(&p));
542+
TEST_ASSERT_EQUAL_UINT8(Default::eventModeRelayHopLimit, mockIface->lastHopLimit);
543+
TEST_ASSERT_EQUAL_UINT8(0, mockIface->lastHopStart);
544+
}
545+
#endif
546+
501547
// ===========================================================================
502548

503549
void setup()
@@ -552,6 +598,9 @@ void setup()
552598
RUN_TEST(test_rebroadcast_normal_broadcast_is_relayed);
553599
RUN_TEST(test_rebroadcast_no_lora_broadcast_is_not_relayed);
554600
RUN_TEST(test_rebroadcast_declined_send_releases_packet);
601+
#if USERPREFS_EVENT_MODE
602+
RUN_TEST(test_event_mode_hop_behavior);
603+
#endif
555604

556605
exit(UNITY_END());
557606
}

userPrefs.jsonc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
// "USERPREFS_CONFIG_OWNER_SHORT_NAME": "MLN",
2525
// "USERPREFS_CONFIG_DEVICE_ROLE": "meshtastic_Config_DeviceConfig_Role_CLIENT", // Defaults to CLIENT. ROUTER*, and LOST AND FOUND roles are restricted.
2626
// "USERPREFS_EVENT_MODE": "1",
27+
// "USERPREFS_EVENT_MODE_HOP_LIMIT": "3", // Event-mode default and firmware-generated/relay hop cap (0-7; default 3)
2728
// "USERPREFS_TMM_APPLY_TO_PRIVATE_CHANNELS": "1", // Extend TMM position dedup and precision clamping to private/custom-key channels (default: well-known channels only)
2829
// "USERPREFS_FIRMWARE_EDITION": "meshtastic_FirmwareEdition_BURNING_MAN",
2930
// "USERPREFS_FIXED_BLUETOOTH": "121212",

0 commit comments

Comments
 (0)