Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/mesh/Default.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ uint32_t Default::getConfiguredOrMinimumValue(uint32_t configured, uint32_t minV
uint8_t Default::getConfiguredOrDefaultHopLimit(uint8_t configured)
{
#if USERPREFS_EVENT_MODE
return (configured > HOP_RELIABLE) ? HOP_RELIABLE : config.lora.hop_limit;
return (configured >= eventModeHopLimit) ? eventModeHopLimit : config.lora.hop_limit;
#else
return (configured >= HOP_MAX) ? HOP_MAX : config.lora.hop_limit;
#endif
Expand Down
16 changes: 15 additions & 1 deletion src/mesh/Default.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <cmath>
#include <cstdint>
#include <meshUtils.h>
#include <type_traits>
#define ONE_DAY 24 * 60 * 60
#define ONE_MINUTE_MS 60 * 1000
#define THIRTY_SECONDS_MS 30 * 1000
Expand Down Expand Up @@ -75,7 +76,20 @@ enum class TrafficType { POSITION, TELEMETRY };

class Default
{
#if USERPREFS_EVENT_MODE && defined(USERPREFS_EVENT_MODE_HOP_LIMIT)
static constexpr auto eventModeHopLimitSetting = USERPREFS_EVENT_MODE_HOP_LIMIT;
#else
static constexpr auto eventModeHopLimitSetting = HOP_RELIABLE;
#endif
using EventModeHopLimitType = typename std::remove_cv<decltype(eventModeHopLimitSetting)>::type;
static_assert(std::is_integral<EventModeHopLimitType>::value && !std::is_same<EventModeHopLimitType, bool>::value &&
eventModeHopLimitSetting >= 0 && eventModeHopLimitSetting <= HOP_MAX,
"USERPREFS_EVENT_MODE_HOP_LIMIT must be an integer between 0 and 7");

public:
static constexpr uint8_t eventModeHopLimit = static_cast<uint8_t>(eventModeHopLimitSetting);
static constexpr uint8_t eventModeRelayHopLimit = eventModeHopLimit > 0 ? eventModeHopLimit - 1 : 0;

static uint32_t getConfiguredOrDefaultMs(uint32_t configuredInterval);
static uint32_t getConfiguredOrDefaultMs(uint32_t configuredInterval, uint32_t defaultInterval);
static uint32_t getConfiguredOrDefault(uint32_t configured, uint32_t defaultValue);
Expand Down Expand Up @@ -129,4 +143,4 @@ class Default
return 1.0 + (nodesOverForty * throttlingFactor); // Each number of online node scales by throttle factor
}
}
};
};
22 changes: 17 additions & 5 deletions src/mesh/NextHopRouter.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "NextHopRouter.h"
#include "Default.h"
#include "MeshTypes.h"
#include "meshUtils.h"
#if !MESHTASTIC_EXCLUDE_TRACEROUTE
Expand All @@ -9,6 +10,18 @@
#endif
#include "NodeDB.h"

#if USERPREFS_EVENT_MODE
static void capEventRelayHops(meshtastic_MeshPacket *packet)
{
if (packet->hop_limit <= Default::eventModeRelayHopLimit)
return;

const uint8_t reduction = packet->hop_limit - Default::eventModeRelayHopLimit;
packet->hop_start = reduction <= packet->hop_start ? packet->hop_start - reduction : 0;
packet->hop_limit = Default::eventModeRelayHopLimit;
}
#endif

NextHopRouter::NextHopRouter() {}

bool NextHopRouter::relayOpaquePacket(const meshtastic_MeshPacket *p)
Expand All @@ -26,6 +39,9 @@ bool NextHopRouter::relayOpaquePacket(const meshtastic_MeshPacket *p)
if (!relay)
return false;
relay->hop_limit--;
#if USERPREFS_EVENT_MODE
capEventRelayHops(relay);
#endif
relay->relay_node = nodeDB->getLastByteOfNodeNum(getNodeNum());
// The interface declines some packets (NODENUM_BROADCAST_NO_LORA) with ERRNO_SHOULD_RELEASE,
// which leaves the copy ours to free. Dropping it here would leak a pool slot per opaque frame.
Expand Down Expand Up @@ -211,11 +227,7 @@ bool NextHopRouter::perhapsRebroadcast(const meshtastic_MeshPacket *p)
LOG_INFO("favorite-ROUTER/CLIENT_BASE-to-ROUTER/CLIENT_BASE rebroadcast: preserving hop_limit");
}
#if USERPREFS_EVENT_MODE
if (tosend->hop_limit > 2) {
// if we are "correcting" the hop_limit, "correct" the hop_start by the same amount to preserve hops away.
tosend->hop_start -= (tosend->hop_limit - 2);
tosend->hop_limit = 2;
}
capEventRelayHops(tosend);
#endif

ErrorCode res =
Expand Down
4 changes: 4 additions & 0 deletions src/mesh/NodeDB.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -927,7 +927,11 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
config.lora.override_frequency = USERPREFS_LORACONFIG_OVERRIDE_FREQUENCY;
#endif

#if USERPREFS_EVENT_MODE
config.lora.hop_limit = Default::eventModeHopLimit;
#else
config.lora.hop_limit = HOP_RELIABLE;
#endif
#ifdef USERPREFS_CONFIG_LORA_IGNORE_MQTT
config.lora.ignore_mqtt = USERPREFS_CONFIG_LORA_IGNORE_MQTT;
#else
Expand Down
13 changes: 7 additions & 6 deletions src/modules/RoutingModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,19 +64,20 @@ void RoutingModule::sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketI
uint8_t RoutingModule::getHopLimitForResponse(const meshtastic_MeshPacket &mp)
{
const int8_t hopsUsed = getHopsAway(mp);
const uint8_t responseHopLimit = Default::getConfiguredOrDefaultHopLimit(config.lora.hop_limit);
if (hopsUsed >= 0) {
if (hopsUsed > (int32_t)(config.lora.hop_limit)) {
// In event mode, we never want to send packets with more than our default 3 hops.
#if !(EVENTMODE) // This falls through to the default.
if (hopsUsed > static_cast<int32_t>(responseHopLimit)) {
// In event mode, never exceed the configured event hop limit.
#if !USERPREFS_EVENT_MODE // This falls through to the default.
return hopsUsed; // If the request used more hops than the limit, use the same amount of hops
#endif
} else if (mp.hop_start == 0) {
return 0; // The requesting node wanted 0 hops, so the response also uses a direct/local path.
} else if ((uint8_t)(hopsUsed + 2) < config.lora.hop_limit) {
} else if (static_cast<uint8_t>(hopsUsed + 2) < responseHopLimit) {
return hopsUsed + 2; // Use only the amount of hops needed with some margin as the way back may be different
}
}
return Default::getConfiguredOrDefaultHopLimit(config.lora.hop_limit); // Use the default hop limit
return responseHopLimit;
}

meshtastic_MeshPacket *RoutingModule::allocAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex,
Expand All @@ -93,4 +94,4 @@ RoutingModule::RoutingModule() : ProtobufModule("routing", meshtastic_PortNum_RO
// LocalOnly requires either the from or to to be a known node
// knownOnly specifically requires the from to be a known node.
encryptedOk = true;
}
}
32 changes: 32 additions & 0 deletions test/test_default/test_main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
#include "MeshRadio.h"
#include "TestUtil.h"
#include "meshUtils.h"
#include "modules/RoutingModule.h"
#include <algorithm>
#include <unity.h>

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

void test_configured_or_default_hop_limit()
{
config.lora.hop_limit = HOP_MAX;
const uint8_t result = Default::getConfiguredOrDefaultHopLimit(config.lora.hop_limit);

#if USERPREFS_EVENT_MODE
TEST_ASSERT_EQUAL_UINT8(Default::eventModeHopLimit, result);
TEST_ASSERT_EQUAL_UINT8(Default::eventModeHopLimit, Default::getConfiguredOrDefaultHopLimit(Default::eventModeHopLimit));
#else
TEST_ASSERT_EQUAL_UINT8(HOP_MAX, result);
#endif
}

#if USERPREFS_EVENT_MODE
void test_event_mode_caps_optimized_response()
{
config.lora.hop_limit = HOP_MAX;
meshtastic_MeshPacket request = meshtastic_MeshPacket_init_zero;
request.hop_start = HOP_MAX;
request.hop_limit = HOP_MAX - 4;

RoutingModule module;
TEST_ASSERT_EQUAL_UINT8(std::min<uint8_t>(6, Default::eventModeHopLimit), module.getHopLimitForResponse(request));
}
#endif

void setup()
{
// Small delay to match other test mains
Expand All @@ -201,6 +229,10 @@ void setup()
RUN_TEST(test_ms_default_clamps);
RUN_TEST(test_ms_result_is_int32_safe);
RUN_TEST(test_scaled_overflow_saturates);
RUN_TEST(test_configured_or_default_hop_limit);
#if USERPREFS_EVENT_MODE
RUN_TEST(test_event_mode_caps_optimized_response);
#endif
exit(UNITY_END());
}

Expand Down
49 changes: 49 additions & 0 deletions test/test_nexthop_routing/test_main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

#include "configuration.h"
#include "gps/RTC.h"
#include "mesh/Default.h"
#include "mesh/NextHopRouter.h"
#include "mesh/NodeDB.h"
#include "mesh/RadioInterface.h"
Expand Down Expand Up @@ -89,6 +90,7 @@ class NextHopRouterTestShim : public NextHopRouter
using NextHopRouter::noteRouteLearned;
using NextHopRouter::noteRouteSuccess;
using NextHopRouter::perhapsRebroadcast;
using NextHopRouter::relayOpaquePacket;
using Router::shouldDecrementHopLimit; // protected in Router

void resetRouteHealthForTest()
Expand All @@ -108,6 +110,8 @@ class MockRadioInterface : public RadioInterface
ErrorCode send(meshtastic_MeshPacket *p) override
{
sendCount++;
lastHopLimit = p->hop_limit;
lastHopStart = p->hop_start;
if (declineAll || p->to == NODENUM_BROADCAST_NO_LORA)
return ERRNO_SHOULD_RELEASE;

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

int sendCount = 0;
bool declineAll = false;
uint8_t lastHopLimit = 0;
uint8_t lastHopStart = 0;
};

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

#if USERPREFS_EVENT_MODE
void test_event_mode_hop_behavior(void)
{
MockRadioInterface *mockIface = installMockIface();
meshtastic_MeshPacket p = makeRebroadcastCandidate(NODENUM_BROADCAST);
p.from = kLocalNode;
p.hop_start = 0;
p.hop_limit = HOP_MAX;

TEST_ASSERT_EQUAL(ERRNO_OK, shim->send(packetPool.allocCopy(p)));
TEST_ASSERT_EQUAL_UINT8(HOP_MAX, mockIface->lastHopLimit);
TEST_ASSERT_EQUAL_UINT8(HOP_MAX, mockIface->lastHopStart);

p = makeRebroadcastCandidate(NODENUM_BROADCAST);
p.hop_start = HOP_MAX;
p.hop_limit = HOP_MAX;

TEST_ASSERT_TRUE(shim->perhapsRebroadcast(&p));
TEST_ASSERT_EQUAL_UINT8(Default::eventModeRelayHopLimit, mockIface->lastHopLimit);
TEST_ASSERT_EQUAL_UINT8(static_cast<uint8_t>(Default::eventModeRelayHopLimit + 1), mockIface->lastHopStart);

config.device.rebroadcast_mode = meshtastic_Config_DeviceConfig_RebroadcastMode_ALL;
p = makeRebroadcastCandidate(NODENUM_BROADCAST);
p.hop_start = HOP_MAX;
p.hop_limit = HOP_MAX;

TEST_ASSERT_TRUE(shim->relayOpaquePacket(&p));
TEST_ASSERT_EQUAL_UINT8(Default::eventModeRelayHopLimit, mockIface->lastHopLimit);
TEST_ASSERT_EQUAL_UINT8(static_cast<uint8_t>(Default::eventModeRelayHopLimit + 1), mockIface->lastHopStart);

p = makeRebroadcastCandidate(NODENUM_BROADCAST);
p.hop_start = 0;
p.hop_limit = HOP_MAX;

TEST_ASSERT_TRUE(shim->relayOpaquePacket(&p));
TEST_ASSERT_EQUAL_UINT8(Default::eventModeRelayHopLimit, mockIface->lastHopLimit);
TEST_ASSERT_EQUAL_UINT8(0, mockIface->lastHopStart);
}
#endif

// ===========================================================================

void setup()
Expand Down Expand Up @@ -552,6 +598,9 @@ void setup()
RUN_TEST(test_rebroadcast_normal_broadcast_is_relayed);
RUN_TEST(test_rebroadcast_no_lora_broadcast_is_not_relayed);
RUN_TEST(test_rebroadcast_declined_send_releases_packet);
#if USERPREFS_EVENT_MODE
RUN_TEST(test_event_mode_hop_behavior);
#endif

exit(UNITY_END());
}
Expand Down
1 change: 1 addition & 0 deletions userPrefs.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
// "USERPREFS_CONFIG_OWNER_SHORT_NAME": "MLN",
// "USERPREFS_CONFIG_DEVICE_ROLE": "meshtastic_Config_DeviceConfig_Role_CLIENT", // Defaults to CLIENT. ROUTER*, and LOST AND FOUND roles are restricted.
// "USERPREFS_EVENT_MODE": "1",
// "USERPREFS_EVENT_MODE_HOP_LIMIT": "3", // Event-mode default and firmware-generated/relay hop cap (0-7; default 3)
// "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",
Expand Down
Loading