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
4 changes: 3 additions & 1 deletion src/helpers/RegionMap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,9 @@ int RegionMap::getTransportKeysFor(const RegionEntry& src, TransportKey dest[],
}

RegionEntry* RegionMap::findMatch(mesh::Packet* packet, uint8_t mask) {
if (packet->transport_codes[0] == 0) return &wildcard;
if (packet->transport_codes[0] == 0) { // code 0 = un-scoped: same rules as the wildcard (config + runtime gate)
return (wildcard.effectiveFlags() & mask) == 0 ? &wildcard : NULL;
}
for (int i = 0; i < num_regions; i++) {
auto region = &regions[i];
if ((region->effectiveFlags() & mask) == 0) { // does region allow this? (config + runtime gate)
Expand Down
13 changes: 11 additions & 2 deletions src/helpers/RoutingPolicy.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,28 @@

namespace mesh {

/**
* \brief Is this an un-scoped flood? ROUTE_TYPE_FLOOD, or a TRANSPORT_FLOOD carrying transport
* code 0, which RegionMap::findMatch() treats the same as un-scoped.
*/
inline bool isUnscopedFlood(const Packet* packet) {
if (packet->getRouteType() == ROUTE_TYPE_FLOOD) return true;
return packet->getRouteType() == ROUTE_TYPE_TRANSPORT_FLOOD && packet->transport_codes[0] == 0;
}

/**
* \brief Test a flood packet against the configured hop limits.
* \param packet inbound flood packet (caller has already checked isRouteFlood())
* \param flood_max max hops for any flood packet
* \param flood_max_unscoped max hops for ROUTE_TYPE_FLOOD (ie. un-scoped) packets
* \param flood_max_unscoped max hops for un-scoped packets (see isUnscopedFlood())
* \param flood_max_advert max hops for ADVERT packets
* \returns true if the packet has exceeded a limit, and must not be forwarded
*/
inline bool isFloodHopLimitExceeded(const Packet* packet, uint8_t flood_max,
uint8_t flood_max_unscoped, uint8_t flood_max_advert) {
uint8_t hops = packet->getPathHashCount();
if (hops >= flood_max) return true;
if (packet->getRouteType() == ROUTE_TYPE_FLOOD && hops >= flood_max_unscoped) return true;
if (isUnscopedFlood(packet) && hops >= flood_max_unscoped) return true;
if (packet->getPayloadType() == PAYLOAD_TYPE_ADVERT && hops >= flood_max_advert) return true;
return false;
}
Expand Down
50 changes: 50 additions & 0 deletions test/test_region_gating/test_region_gating.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include <gtest/gtest.h>
#include "helpers/RegionMap.h"
#include <string.h>

// Unit tests for duty-cycle region gating (issue #2747).
// These exercise the pure hierarchy/level logic; no crypto or filesystem is touched.
Expand Down Expand Up @@ -223,6 +224,55 @@ TEST_F(NlChainGating, RecoveryRestoresConfiguredBehaviour) {
EXPECT_FALSE(eu->effectiveFlags() & REGION_DENY_FLOOD); // flood allowed again
}

// ---------- transport code 0 (upstream #3568: 'treat code 0 the same as un-scoped') ----------
// Code 0 maps to the wildcard, so it must obey the wildcard's flags (config denyf and the
// runtime duty gate) exactly like a plain un-scoped flood, instead of bypassing them.

static mesh::Packet makeTransportFlood(uint16_t code0, uint16_t code1 = 0) {
mesh::Packet p;
p.header = ROUTE_TYPE_TRANSPORT_FLOOD | (PAYLOAD_TYPE_TXT_MSG << PH_TYPE_SHIFT);
p.payload_len = 4;
memcpy(p.payload, "abcd", 4);
p.transport_codes[0] = code0;
p.transport_codes[1] = code1;
return p;
}

TEST_F(NlChainGating, ZeroCodeMatchesWildcardWhenWildcardAllowsFlood) {
auto pkt = makeTransportFlood(0);
EXPECT_EQ(rm.findMatch(&pkt, REGION_DENY_FLOOD), &rm.getWildcard());
}

TEST_F(NlChainGating, ZeroCodeIsDeniedWhenWildcardDeniesFlood) {
rm.getWildcard().flags |= REGION_DENY_FLOOD; // 'region denyf *'
auto pkt = makeTransportFlood(0);
EXPECT_EQ(rm.findMatch(&pkt, REGION_DENY_FLOOD), nullptr);
}

TEST_F(NlChainGating, ZeroCodeIsDeniedWhileDutyGateHoldsWildcard) {
rm.applyDutyGate(1); // runtime gate on '*', config still allows flood
auto pkt = makeTransportFlood(0);
EXPECT_EQ(rm.findMatch(&pkt, REGION_DENY_FLOOD), nullptr);
}

TEST_F(NlChainGating, ZeroCodeWithReturnRegionHintIsStillUnscoped) {
rm.getWildcard().flags |= REGION_DENY_FLOOD;
auto pkt = makeTransportFlood(0, 0x1234); // codes[1] carries a hint, codes[0] is still 0
EXPECT_EQ(rm.findMatch(&pkt, REGION_DENY_FLOOD), nullptr);
}

TEST_F(NlChainGating, NamedRegionStillMatchesWhenWildcardDeniesFlood) {
rm.getWildcard().flags |= REGION_DENY_FLOOD;
RegionEntry* nl = rm.findByName("nl");
nl->flags &= (uint8_t)~REGION_DENY_FLOOD; // 'region allowf nl'

auto pkt = makeTransportFlood(0);
TransportKey key;
ASSERT_EQ(rm.getTransportKeysFor(*nl, &key, 1), 1);
pkt.transport_codes[0] = key.calcTransportCode(&pkt);
EXPECT_EQ(rm.findMatch(&pkt, REGION_DENY_FLOOD), nl);
}

int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
Expand Down
41 changes: 40 additions & 1 deletion test/test_routing_policy/test_routing_policy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@

using namespace mesh;

static Packet makeFlood(uint8_t route_type, uint8_t payload_type, uint8_t hops) {
// scoped packets get a real (non-zero) transport code unless the test asks for code 0
static Packet makeFlood(uint8_t route_type, uint8_t payload_type, uint8_t hops, uint16_t code0 = 0x1234) {
Packet p;
p.header = route_type | (payload_type << PH_TYPE_SHIFT);
p.setPathHashSizeAndCount(1, hops);
p.payload_len = 1;
p.transport_codes[0] = code0;
p.transport_codes[1] = 0;
return p;
}

Expand Down Expand Up @@ -50,6 +53,42 @@ TEST(FloodHopLimit, UnscopedAdvertIsAlsoDroppedAtHopZero) {
EXPECT_FALSE(isFloodHopLimitExceeded(&scoped, 64, 0, 8));
}

// transport code 0 is 'un-scoped' (upstream #3568): it must not escape flood.max.unscoped
TEST(UnscopedFlood, PlainFloodIsUnscoped) {
auto pkt = makeFlood(ROUTE_TYPE_FLOOD, PAYLOAD_TYPE_TXT_MSG, 0);
EXPECT_TRUE(isUnscopedFlood(&pkt));
}

TEST(UnscopedFlood, TransportFloodWithZeroCodeIsUnscoped) {
auto pkt = makeFlood(ROUTE_TYPE_TRANSPORT_FLOOD, PAYLOAD_TYPE_TXT_MSG, 0, 0);
EXPECT_TRUE(isUnscopedFlood(&pkt));
}

TEST(UnscopedFlood, TransportFloodWithRealCodeIsScoped) {
auto pkt = makeFlood(ROUTE_TYPE_TRANSPORT_FLOOD, PAYLOAD_TYPE_TXT_MSG, 0);
EXPECT_FALSE(isUnscopedFlood(&pkt));
}

TEST(UnscopedFlood, DirectRoutesAreNeverUnscopedFloods) {
auto direct = makeFlood(ROUTE_TYPE_DIRECT, PAYLOAD_TYPE_TXT_MSG, 0);
EXPECT_FALSE(isUnscopedFlood(&direct));
auto tdirect = makeFlood(ROUTE_TYPE_TRANSPORT_DIRECT, PAYLOAD_TYPE_TXT_MSG, 0, 0);
EXPECT_FALSE(isUnscopedFlood(&tdirect));
}

TEST(FloodHopLimit, ZeroCodeTransportFloodIsDroppedAtFirstHopWhenMaxUnscopedIsZero) {
auto pkt = makeFlood(ROUTE_TYPE_TRANSPORT_FLOOD, PAYLOAD_TYPE_TXT_MSG, 0, 0);
EXPECT_TRUE(isFloodHopLimitExceeded(&pkt, 64, 0, 8));
}

TEST(FloodHopLimit, ZeroCodeTransportFloodSurvivesUpToMaxUnscopedHops) {
auto ok = makeFlood(ROUTE_TYPE_TRANSPORT_FLOOD, PAYLOAD_TYPE_TXT_MSG, 2, 0);
EXPECT_FALSE(isFloodHopLimitExceeded(&ok, 64, 3, 8));

auto too_far = makeFlood(ROUTE_TYPE_TRANSPORT_FLOOD, PAYLOAD_TYPE_TXT_MSG, 3, 0);
EXPECT_TRUE(isFloodHopLimitExceeded(&too_far, 64, 3, 8));
}

TEST(ReplyRoute, FloodRequestGetsAPathReturn) {
EXPECT_EQ(REPLY_ROUTE_PATH_RETURN,
chooseReplyRoute(true, false, false));
Expand Down
Loading