From cfdf33b7ca90a0e51093bc00e4e6d1aeb3204c7b Mon Sep 17 00:00:00 2001 From: mikecarper Date: Fri, 7 Aug 2026 17:22:21 -0700 Subject: [PATCH] Expand repeater FPF7 to 63 rules --- docs/cli_commands.md | 4 +- docs/filter_tool.md | 3 +- docs/flood_filtering.md | 10 +-- docs/halo_keymind_settings.md | 2 +- examples/simple_repeater/MyMesh.cpp | 80 ++++++++++--------- examples/simple_repeater/MyMesh.h | 19 +++-- src/helpers/FloodFilterPolicy.h | 16 ++-- .../test_flood_filter_policy.cpp | 18 ++++- 8 files changed, 85 insertions(+), 67 deletions(-) diff --git a/docs/cli_commands.md b/docs/cli_commands.md index 78044e72..d598f823 100644 --- a/docs/cli_commands.md +++ b/docs/cli_commands.md @@ -1349,7 +1349,7 @@ setting. #### Block selected flood channels with FPF7 The separate `flood.channel.block` command and 15-row table have been retired. -Generalized repeaters use the 32-row FPF7 forward phase for authenticated channel +Generalized repeaters use the 63-row FPF7 forward phase for authenticated channel blocks: ```text @@ -1659,7 +1659,7 @@ ordered `prefix=` match on a room server. Standard room-server profiles do not compile this table. **Parameters:** -- `n`: Forward-rule slot in the build's compiled table (`1-32` on generalized +- `n`: Forward-rule slot in the build's compiled table (`1-63` on generalized repeaters and `1-31` on FULL room servers; compact profiles may use fewer). - `type`: Payload type name, full `PAYLOAD_TYPE_*` name, decimal value `0-15`, hexadecimal value `0x00-0x0F`, or `any`. diff --git a/docs/filter_tool.md b/docs/filter_tool.md index 487fe9c0..46e5325b 100644 --- a/docs/filter_tool.md +++ b/docs/filter_tool.md @@ -613,7 +613,8 @@ The class column shows which broader selector also matches it. | `flood.channel.scope` | Scope-rewrite phase rows | | `flood.filter.blacklist` | One shared unordered path-ID set referenced by `path=blacklist` rows | -Generalized repeaters commit those sections together. The blacklist is useful +Generalized repeaters expose 63 forward rows and commit those sections +together. The blacklist is useful for refusing to retransmit floods associated with internet gateways dumping bulk traffic, but a path ID is truncated and unauthenticated; it identifies a routing pattern, not a person. diff --git a/docs/flood_filtering.md b/docs/flood_filtering.md index 039f854f..5a7a8d68 100644 --- a/docs/flood_filtering.md +++ b/docs/flood_filtering.md @@ -40,7 +40,7 @@ get flood.moderation ``` `flood.rule` is an alias for `flood.filter`, not another table. Generalized -repeater FPF7 has 32 forward-rule slots plus scope-rewrite and shared-blacklist +repeater FPF7 has 63 forward-rule slots plus scope-rewrite and shared-blacklist sections in the same atomic policy file. FULL room servers have 31 forward slots and empty repeater-only sections. Compact target profiles retain their separate FPF6-era controls. `flood.moderation` has 16 slots. A new @@ -290,10 +290,10 @@ open. A row saved by the extended engine uses FPF7. The former `flood.channel.block` table is now represented by ordinary FPF7 rows. On a generalized repeater, an existing FCB2 file is imported once into free FPF7 slots and then removed. For example, an old `#wardriving h=4` row -becomes `type=any channel=#wardriving hops=5+ drop`. The 32nd forward slot -guarantees room to migrate the old global `flood.channel.data` gate even when -all 31 former general/channel slots were occupied. Compact STM32WL FPF6 builds -cannot match authenticated channels and retain the older separate gate. +becomes `type=any channel=#wardriving hops=5+ drop`. The 63-row table can hold +all 31 former general filter rows, all 15 legacy channel-block rows, and the +migrated global `flood.channel.data` gate. Compact STM32WL FPF6 builds cannot +match authenticated channels and retain the older separate gate. On generalized repeaters, `flood.channel.data*` is a compatibility view over one ordinary visible FPF7 `type=grp_data ... drop` row. Turning it off creates diff --git a/docs/halo_keymind_settings.md b/docs/halo_keymind_settings.md index 6ffcbb50..4f553e62 100644 --- a/docs/halo_keymind_settings.md +++ b/docs/halo_keymind_settings.md @@ -84,7 +84,7 @@ set flood.retry.ignore none | `flood.channel.data.hops` | Sets the compatibility row's hop range: `all` maps to `hops=all`; `1`-`7` repeats through that hop count and maps to `hops=N+1+`. | `get flood.channel.data.hops`, `set flood.channel.data.hops ` | `set flood.channel.data.hops 7` | | `flood.channel.scope` | FPF7 rewrite-phase rows that add a transport scope to received unscoped floods or replace the scope of already-scoped floods. A bare target uses an existing flood-allowed region; `scope=` derives a public hashtag target directly without creating a region, exactly like `flood.filter scope=`. By default, a changed packet bypasses inbound `rxdelay` and is forwarded at the highest outbound queue priority with zero initial `txdelay`, so the selected scope can win at the next hop. `tx=slow` uses an effective inbound `rxdelay` base of `max(2, configured rxdelay * 2)`, retains normal queue priority, and forces the maximum `txdelay` factor of `2.0`; its actual randomized transmit delay ranges from zero through ten packet airtimes. `path=blacklist` and `path=bucket:<1-6>` make a row path-qualified; bridge buckets remain usable while bridge retry is off. An already-matching scope is a no-op. Exact channel keys beat `txt:*`; path-qualified rows beat the ordinary channel fallback. `login:*` covers the remote-login family, and `other:*` covers every remaining flood type, including flood-form TRACE and OTA. Direct traceroute remains outside the flood table. Generalized builds commit these rows with the forward phase and blacklist; compact FPF6 builds retain separate storage. ACL permission `4` can manage the table. | `get flood.channel.scope[.n]`, `set flood.channel.scope[.n] [path=blacklist|path=bucket:1-6] [tx=slow]`, `del flood.channel.scope.|all` | `set flood.channel.scope #rgdata scope=BlackHole86` | | `flood.channel.scope.require` | Switches group-channel region enforcement to opt-in when the table has entries. Listed authenticated `GRP_TXT`/`GRP_DATA` channels must arrive already scoped to a locally allowed region; unscoped, unknown, or denied incoming scopes are dropped before any rewrite can rescue them. Unlisted group channels bypass only the region gate and retain all other forwarding controls. An empty table preserves global region behavior; non-channel payloads are unchanged. ACL permission `4` can manage the table. | `get flood.channel.scope.require[.n]`, `set flood.channel.scope.require[.n] `, `del flood.channel.scope.require.|all` | `set flood.channel.scope.require #bot` | -| `flood.filter` | Persistent flood-route rules selected by payload type and optional hop range. Generalized repeaters have 32 FPF7 forward slots; FULL ESP32 room servers have 31. Repeaters store their scope-rewrite phase, shared unordered blacklist, and channel-data compatibility state in the same atomic FPF7 image. `path=blacklist` is intended for forwarding abuse containment, including bulk internet-to-mesh dumping, but truncated path IDs are not authenticated identities. Fixed 240 KB STM32WL repeaters retain compact FPF6 filtering and separate blacklist storage. New generalized repeater tables seed slot 1 with `ota all suspend=tempradio` and slot 2 with an authenticated `#wardriving hops=5+` drop. Direct routes and local receive/logging are unchanged. | `get flood.filter[.n]`, `set flood.filter[.n] [N|N+|N-M|all] [scope=] [require=region] [tx=slow] [suspend=tempradio]`, `del flood.filter.|all`; repeater only: `get/set/del flood.filter.blacklist[.n]` | `set flood.filter grp_txt all scope=local tx=slow` | +| `flood.filter` | Persistent flood-route rules selected by payload type and optional hop range. Generalized repeaters have 63 FPF7 forward slots; FULL ESP32 room servers have 31. Repeaters store their scope-rewrite phase, shared unordered blacklist, and channel-data compatibility state in the same atomic FPF7 image. `path=blacklist` is intended for forwarding abuse containment, including bulk internet-to-mesh dumping, but truncated path IDs are not authenticated identities. Fixed 240 KB STM32WL repeaters retain compact FPF6 filtering and separate blacklist storage. New generalized repeater tables seed slot 1 with `ota all suspend=tempradio` and slot 2 with an authenticated `#wardriving hops=5+` drop. Direct routes and local receive/logging are unchanged. | `get flood.filter[.n]`, `set flood.filter[.n] [N|N+|N-M|all] [scope=] [require=region] [tx=slow] [suspend=tempradio]`, `del flood.filter.|all`; repeater only: `get/set/del flood.filter.blacklist[.n]` | `set flood.filter grp_txt all scope=local tx=slow` | | `flood.rule` | Live alias for extended `flood.filter` on rule-engine repeaters and FULL ESP32 room servers. A row can AND packet type, hop range, optional channel authentication, ordered 1/2/3-byte pbyte source prefix, and original scope/region conditions, then drop, rewrite to a direct scope or configured region, enforce a per-row rate, and/or stop lower-priority FPF7 rules. `channel=*` means no channel condition and `type=any channel=*` matches every flood payload type. `scope=BlackHole86` directly derives a regionless sink scope; `region=BlackHole86` would require a configured flood-allowed region. All rows match the original receive-time packet; higher `priority` runs first and lower slot breaks a tie. Repeated rows with one channel key share a per-packet authentication result. Persistent FPF7 stores canonical region names, so region ID reorder or reuse cannot retarget a rule. A missing saved region makes its `region=` rewrite and paired `stop` inert until the name returns, allowing lower safety rows to run. Fixed 240 KB STM32WL profiles keep FPF6 and do not expose this alias; partition sizes are unchanged. | `get flood.rule[.n]`, `set flood.rule[.n] type= [hops=...] [channel=...] [prefix=...] [in=...] [priority=0-255]`, `del flood.rule.|all` | `set flood.rule.2 type=grp_data hops=4+ channel=#rgdata in=none scope=BlackHole86` | | `flood.moderation` | Decrypts keyed `GRP_TXT` channels and applies drop, per-username messages/minute, and maximum-hop controls, optionally matched against the first 1-3 path hashes. Supports `public`, `#channel`, and 128/256-bit channel keys. Sender names and truncated path hashes are moderation hints, not authenticated identities. | `get flood.moderation[.n]`, `set flood.moderation[.n] [path=...]`, `del flood.moderation.|all` | `set flood.moderation public "Noisy User" rate=5/min hops=4` | | `clock.sync.mesh` | Defaults on for nRF52 repeaters and off for other builds; a saved setting overrides that default. It estimates UTC as soon as the configured number of fresh signed-advert or valid Public-channel sources is collected, with a 30-minute bootstrap/retry timer when evidence is still insufficient, then repeats lazily seven days after each successful estimate. New evidence retriggers evaluation after a no-consensus result. `clock.sync.mesh now` queues an immediate LoRa-only attempt without bypassing quorum or source suppression. Only timestamps from firmware build time through build time plus ten years are recorded. Successful CLI, GPS, or WiFi/NTP clock updates suppress LoRa time collection until reboot; after reboot LoRa is the fallback if NTP cannot sync. Status reports the reason a clock was not set; its `.table` and `.1` through `.16` forms inspect collected samples. | `get clock.sync.mesh`, `set clock.sync.mesh `, `clock.sync.mesh now`, `get clock.sync.status[.table|.1-.16]` | `set clock.sync.mesh on` | diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 1b62f619..7cb0a5e7 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -1120,7 +1120,7 @@ bool MyMesh::evaluateScopeRewriteTiming(const mesh::Packet* packet, } mesh::Packet candidate = *packet; - uint32_t filter_match_mask = evaluateFloodPacketFilterMatches( + uint64_t filter_match_mask = evaluateFloodPacketFilterMatches( packet, incoming_region_allowed, incoming_region); bool channel_regionless_scope_set = false; bool scope_changed = applyFloodChannelScope( @@ -5601,7 +5601,8 @@ bool MyMesh::migrateLegacyFloodChannelBlocks() { return true; } -bool MyMesh::saveFloodPacketFilters(bool empty_scope_phase) { +bool MyMesh::saveFloodPacketFilters(bool empty_scope_phase, + bool empty_forward_phase) { if (_fs == NULL) return false; // Recovery owns transaction remnants; overwriting one could erase the only // complete image after a failed publish boundary. @@ -5627,8 +5628,11 @@ bool MyMesh::saveFloodPacketFilters(bool empty_scope_phase) { uint8_t count = FLOOD_PACKET_FILTER_SLOTS; bool success = writeExact(magic, sizeof(magic)) && writeExact(&count, sizeof(count)); + FloodPacketFilterEntry empty_entry; + memset(&empty_entry, 0, sizeof(empty_entry)); for (int i = 0; success && i < FLOOD_PACKET_FILTER_SLOTS; i++) { - const auto& entry = flood_packet_filters[i]; + const auto& entry = empty_forward_phase + ? empty_entry : flood_packet_filters[i]; uint8_t active = entry.active ? 1 : 0; uint8_t suspend_on_temp_radio = entry.suspend_on_temp_radio ? 1 : 0; uint8_t match_blacklisted_path = entry.match_blacklisted_path ? 1 : 0; @@ -5677,6 +5681,8 @@ bool MyMesh::saveFloodPacketFilters(bool empty_scope_phase) { static_assert(sizeof(FloodChannelScopeEntry) == 36, "channel scope persistence requires 36-byte entries"); const uint8_t section_magic[4] = {'F', 'P', 'S', '1'}; + const uint8_t channel_data_slot = empty_forward_phase + ? 0xFF : flood_channel_data_rule_slot; uint8_t scope_count = 0; uint8_t direct_count = 0; if (!empty_scope_phase) { @@ -5693,8 +5699,7 @@ bool MyMesh::saveFloodPacketFilters(bool empty_scope_phase) { } success = success && writeExact(section_magic, sizeof(section_magic)) - && writeExact(&flood_channel_data_rule_slot, - sizeof(flood_channel_data_rule_slot)) + && writeExact(&channel_data_slot, sizeof(channel_data_slot)) && writeExact(&flood_channel_data_rule_max_hops, sizeof(flood_channel_data_rule_max_hops)) && writeExact(&scope_count, sizeof(scope_count)); @@ -5831,7 +5836,8 @@ bool MyMesh::loadFloodPacketFilters() { return success; } -bool MyMesh::saveFloodPacketFilters(bool empty_scope_phase) { +bool MyMesh::saveFloodPacketFilters(bool empty_scope_phase, + bool empty_forward_phase) { (void)empty_scope_phase; if (_fs == NULL) return false; File file = openFloodSettingsWrite(_fs, FLOOD_PACKET_FILTER_FILE); @@ -5841,8 +5847,11 @@ bool MyMesh::saveFloodPacketFilters(bool empty_scope_phase) { uint8_t count = FLOOD_PACKET_FILTER_SLOTS; bool success = file.write(magic, sizeof(magic)) == sizeof(magic) && file.write(&count, sizeof(count)) == sizeof(count); + FloodPacketFilterEntry empty_entry; + memset(&empty_entry, 0, sizeof(empty_entry)); for (int i = 0; success && i < FLOOD_PACKET_FILTER_SLOTS; i++) { - const auto& entry = flood_packet_filters[i]; + const auto& entry = empty_forward_phase + ? empty_entry : flood_packet_filters[i]; uint8_t active = entry.active ? 1 : 0; uint8_t suspend_on_temp_radio = entry.suspend_on_temp_radio ? 1 : 0; uint8_t match_blacklisted_path = entry.match_blacklisted_path ? 1 : 0; @@ -5949,8 +5958,8 @@ bool MyMesh::authenticateFloodPacketFilterChannel( packet->payload_len - PATH_HASH_SIZE) > 0; } -int MyMesh::nextFloodPacketFilterMatch(uint32_t match_mask, - uint32_t visited_mask) const { +int MyMesh::nextFloodPacketFilterMatch(uint64_t match_mask, + uint64_t visited_mask) const { uint8_t priorities[FLOOD_PACKET_FILTER_SLOTS]; for (int i = 0; i < FLOOD_PACKET_FILTER_SLOTS; i++) { priorities[i] = flood_packet_filters[i].priority; @@ -5974,7 +5983,7 @@ bool MyMesh::resolveFloodPacketFilterTargetRegion( return true; } -uint32_t MyMesh::applyFloodPacketFilterStop(uint32_t match_mask) { +uint64_t MyMesh::applyFloodPacketFilterStop(uint64_t match_mask) { uint8_t priorities[FLOOD_PACKET_FILTER_SLOTS]; uint8_t stop_flags[FLOOD_PACKET_FILTER_SLOTS]; for (int i = 0; i < FLOOD_PACKET_FILTER_SLOTS; i++) { @@ -5995,11 +6004,11 @@ uint32_t MyMesh::applyFloodPacketFilterStop(uint32_t match_mask) { match_mask, priorities, stop_flags, FLOOD_PACKET_FILTER_SLOTS); } -uint32_t MyMesh::evaluateFloodPacketFilterMatches( +uint64_t MyMesh::evaluateFloodPacketFilterMatches( const mesh::Packet* packet, bool incoming_region_allowed, const RegionEntry* incoming_region) { - static_assert(FLOOD_PACKET_FILTER_SLOTS <= 32, - "flood filter match mask supports at most 32 slots"); + static_assert(FLOOD_PACKET_FILTER_SLOTS <= 64, + "flood filter match mask supports at most 64 slots"); if (packet == NULL || !packet->isRouteFlood()) return 0; bool incoming_is_scoped = packet->getRouteType() == ROUTE_TYPE_TRANSPORT_FLOOD; @@ -6007,7 +6016,7 @@ uint32_t MyMesh::evaluateFloodPacketFilterMatches( ? packet->transport_codes[0] : 0; bool channel_auth_checked[FLOOD_PACKET_FILTER_SLOTS] = { false }; bool channel_auth_valid[FLOOD_PACKET_FILTER_SLOTS] = { false }; - uint32_t matches = 0; + uint64_t matches = 0; for (int i = 0; i < FLOOD_PACKET_FILTER_SLOTS; i++) { const auto& entry = flood_packet_filters[i]; if (!floodPacketFilterFieldsMatch( @@ -6034,13 +6043,13 @@ uint32_t MyMesh::evaluateFloodPacketFilterMatches( channel_auth_checked[i] = true; channel_auth_valid[i] = authenticated; } - if (authenticated) matches |= (uint32_t)1U << i; + if (authenticated) matches |= (uint64_t)1U << i; } return applyFloodPacketFilterStop(matches); } bool MyMesh::applyFloodPacketFilterScope(mesh::Packet* packet, - uint32_t match_mask, + uint64_t match_mask, bool& scope_set, bool& fast_track, bool log_change) { @@ -6048,11 +6057,11 @@ bool MyMesh::applyFloodPacketFilterScope(mesh::Packet* packet, fast_track = false; if (packet == NULL || !packet->isRouteFlood()) return false; - uint32_t visited = 0; + uint64_t visited = 0; while (true) { int i = nextFloodPacketFilterMatch(match_mask, visited); if (i < 0) break; - visited |= (uint32_t)1U << i; + visited |= (uint64_t)1U << i; const auto& entry = flood_packet_filters[i]; if (entry.scope_name[0] == 0 && entry.target_region_name[0] == 0) { @@ -6092,11 +6101,11 @@ bool MyMesh::shouldBlockFloodPacketForward(const mesh::Packet* packet) const { uint8_t type = packet->getPayloadType(); uint8_t hops = packet->getPathHashCount(); - uint32_t visited = 0; + uint64_t visited = 0; while (true) { int i = nextFloodPacketFilterMatch(recv_pkt_filter_match_mask, visited); if (i < 0) break; - visited |= (uint32_t)1U << i; + visited |= (uint64_t)1U << i; const auto& entry = flood_packet_filters[i]; bool blocked = entry.drop_on_match; if (entry.rate_limit_enabled) { @@ -6121,7 +6130,7 @@ void MyMesh::commitFloodPacketFilterRates(const mesh::Packet* packet) { uint32_t now = _ms->getMillis(); for (int i = 0; i < FLOOD_PACKET_FILTER_SLOTS; i++) { auto& entry = flood_packet_filters[i]; - if ((recv_pkt_filter_match_mask & ((uint32_t)1U << i)) == 0 + if ((recv_pkt_filter_match_mask & ((uint64_t)1U << i)) == 0 || !entry.rate_limit_enabled) { continue; } @@ -6156,25 +6165,25 @@ bool MyMesh::floodPacketFilterFieldsMatch( && hops >= entry.min_hops && hops <= entry.max_hops; } -uint32_t MyMesh::evaluateFloodPacketFilterMatches( +uint64_t MyMesh::evaluateFloodPacketFilterMatches( const mesh::Packet* packet, bool incoming_region_allowed, const RegionEntry* incoming_region) { (void)incoming_region; - uint32_t matches = 0; + uint64_t matches = 0; for (int i = 0; i < FLOOD_PACKET_FILTER_SLOTS; i++) { const auto& entry = flood_packet_filters[i]; if (floodPacketFilterFieldsMatch(entry, packet, false, 0, incoming_region_allowed, NULL) && (!entry.scope_requires_region_match || incoming_region_allowed)) { - matches |= (uint32_t)1U << i; + matches |= (uint64_t)1U << i; } } return matches; } bool MyMesh::applyFloodPacketFilterScope(mesh::Packet* packet, - uint32_t match_mask, + uint64_t match_mask, bool& scope_set, bool& fast_track, bool log_change) { @@ -6183,7 +6192,7 @@ bool MyMesh::applyFloodPacketFilterScope(mesh::Packet* packet, if (packet == NULL || !packet->isRouteFlood()) return false; for (int i = 0; i < FLOOD_PACKET_FILTER_SLOTS; i++) { const auto& entry = flood_packet_filters[i]; - if ((match_mask & ((uint32_t)1U << i)) == 0 + if ((match_mask & ((uint64_t)1U << i)) == 0 || entry.scope_name[0] == 0) continue; TransportKey scope; @@ -6210,7 +6219,7 @@ bool MyMesh::shouldBlockFloodPacketForward(const mesh::Packet* packet) const { uint8_t hops = packet->getPathHashCount(); for (int i = 0; i < FLOOD_PACKET_FILTER_SLOTS; i++) { const auto& entry = flood_packet_filters[i]; - if ((recv_pkt_filter_match_mask & ((uint32_t)1U << i)) != 0 + if ((recv_pkt_filter_match_mask & ((uint64_t)1U << i)) != 0 && entry.scope_name[0] == 0) { MESH_DEBUG_PRINTLN("allowPacketForward: flood.filter matched slot=%d type=%d hops=%d range=%d-%d", i + 1, type, hops, entry.min_hops, @@ -7167,20 +7176,13 @@ void MyMesh::deleteFloodPacketFilter(const char* args, char* reply) { const char* selector = skipFloodFilterSpaces(args); if (*selector == '.') selector = skipFloodFilterSpaces(selector + 1); if (floodFilterAsciiEqual(selector, "all")) { - FloodPacketFilterEntry previous[FLOOD_PACKET_FILTER_SLOTS]; - memcpy(previous, flood_packet_filters, sizeof(previous)); -#if MESH_ENABLE_FLOOD_RULE_ENGINE - uint8_t previous_channel_data_slot = flood_channel_data_rule_slot; - flood_channel_data_rule_slot = 0xFF; -#endif - memset(flood_packet_filters, 0, sizeof(flood_packet_filters)); - if (!saveFloodPacketFilters()) { - memcpy(flood_packet_filters, previous, sizeof(flood_packet_filters)); -#if MESH_ENABLE_FLOOD_RULE_ENGINE - flood_channel_data_rule_slot = previous_channel_data_slot; -#endif + if (!saveFloodPacketFilters(false, true)) { strcpy(reply, "Err - unable to save flood filter"); } else { + memset(flood_packet_filters, 0, sizeof(flood_packet_filters)); +#if MESH_ENABLE_FLOOD_RULE_ENGINE + flood_channel_data_rule_slot = 0xFF; +#endif strcpy(reply, "OK - all flood filters removed"); } return; diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 67880bb5..78c0d2e3 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -152,9 +152,7 @@ struct NeighbourInfo { #endif #ifndef FLOOD_PACKET_FILTER_SLOTS #if MESH_ENABLE_FLOOD_RULE_ENGINE - // Keep one extra forward-phase row available when the legacy - // flood.channel.data gate is upgraded into an ordinary FPF7 rule. - #define FLOOD_PACKET_FILTER_SLOTS 32 + #define FLOOD_PACKET_FILTER_SLOTS 63 #else #define FLOOD_PACKET_FILTER_SLOTS 16 #endif @@ -395,7 +393,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks FloodChannelScopeRequireEntry flood_channel_scope_requirements[FLOOD_CHANNEL_SCOPE_REQUIRE_SLOTS]; FloodGroupModerationEntry flood_group_moderation[FLOOD_GROUP_MODERATION_SLOTS]; - uint32_t recv_pkt_filter_match_mask; + uint64_t recv_pkt_filter_match_mask; #if MESH_ENABLE_FLOOD_RULE_ENGINE bool flood_policy_has_embedded_sections; // Zero-based forward-row slot owned by the flood.channel.data compatibility @@ -592,7 +590,8 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks bool isMillisTimerDue(unsigned long timestamp) const; bool floodChannelDataHopApplies(const mesh::Packet* packet) const; bool loadFloodPacketFilters(); - bool saveFloodPacketFilters(bool empty_scope_phase = false); + bool saveFloodPacketFilters(bool empty_scope_phase = false, + bool empty_forward_phase = false); #if MESH_ENABLE_FLOOD_RULE_ENGINE bool migrateLegacyFloodChannelBlocks(); bool migrateLegacyFloodChannelData(); @@ -617,16 +616,16 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks bool authenticateFloodPacketFilterChannel( const FloodPacketFilterEntry& entry, const mesh::Packet* packet) const; - int nextFloodPacketFilterMatch(uint32_t match_mask, - uint32_t visited_mask) const; + int nextFloodPacketFilterMatch(uint64_t match_mask, + uint64_t visited_mask) const; bool resolveFloodPacketFilterTargetRegion( const char* name, TransportKey& scope, const char*& canonical_name); - uint32_t applyFloodPacketFilterStop(uint32_t match_mask); - uint32_t evaluateFloodPacketFilterMatches( + uint64_t applyFloodPacketFilterStop(uint64_t match_mask); + uint64_t evaluateFloodPacketFilterMatches( const mesh::Packet* packet, bool incoming_region_allowed, const RegionEntry* incoming_region); - bool applyFloodPacketFilterScope(mesh::Packet* packet, uint32_t match_mask, + bool applyFloodPacketFilterScope(mesh::Packet* packet, uint64_t match_mask, bool& scope_set, bool& fast_track, bool log_change = true); bool shouldBlockFloodPacketForward(const mesh::Packet* packet) const; diff --git a/src/helpers/FloodFilterPolicy.h b/src/helpers/FloodFilterPolicy.h index 2c30dcc5..c8a1875b 100644 --- a/src/helpers/FloodFilterPolicy.h +++ b/src/helpers/FloodFilterPolicy.h @@ -285,29 +285,31 @@ inline bool sameChannelKey(uint8_t left_len, const uint8_t left[], && left_len == right_len && memcmp(left, right, left_len) == 0; } -inline int nextOrderedRule(uint32_t match_mask, uint32_t visited_mask, +template +inline int nextOrderedRule(RuleMask match_mask, RuleMask visited_mask, const uint8_t priorities[], uint8_t count) { - if (priorities == NULL || count > 32) return -1; + if (priorities == NULL || count > sizeof(RuleMask) * 8U) return -1; int best = -1; for (uint8_t i = 0; i < count; i++) { - uint32_t bit = (uint32_t)1U << i; + RuleMask bit = (RuleMask)1U << i; if ((match_mask & bit) == 0 || (visited_mask & bit) != 0) continue; if (best < 0 || priorities[i] > priorities[best]) best = i; } return best; } -inline uint32_t truncateRulesAtStop(uint32_t match_mask, +template +inline RuleMask truncateRulesAtStop(RuleMask match_mask, const uint8_t priorities[], const uint8_t stop_flags[], uint8_t count) { if (stop_flags == NULL) return match_mask; - uint32_t effective = 0; - uint32_t visited = 0; + RuleMask effective = 0; + RuleMask visited = 0; while (true) { int index = nextOrderedRule(match_mask, visited, priorities, count); if (index < 0) break; - uint32_t bit = (uint32_t)1U << index; + RuleMask bit = (RuleMask)1U << index; visited |= bit; effective |= bit; if (stop_flags[index] != 0) break; diff --git a/test/test_flood_filter_policy/test_flood_filter_policy.cpp b/test/test_flood_filter_policy/test_flood_filter_policy.cpp index 37332d78..a25dafad 100644 --- a/test/test_flood_filter_policy/test_flood_filter_policy.cpp +++ b/test/test_flood_filter_policy/test_flood_filter_policy.cpp @@ -293,7 +293,7 @@ TEST(FloodRuleOrder, ThirtyOneSlotTableIncludesTheLastSlot) { const uint32_t matches = ((uint32_t)1U << 30) | 1U; EXPECT_EQ(30, FloodFilterPolicy::nextOrderedRule( - matches, 0, priorities, 31)); + matches, (uint32_t)0, priorities, 31)); EXPECT_EQ((uint32_t)1U << 30, FloodFilterPolicy::truncateRulesAtStop( matches, priorities, stop_flags, 31)); @@ -307,12 +307,26 @@ TEST(FloodRuleOrder, ThirtyTwoSlotTableIncludesTheLastSlot) { const uint32_t matches = ((uint32_t)1U << 31) | 1U; EXPECT_EQ(31, FloodFilterPolicy::nextOrderedRule( - matches, 0, priorities, 32)); + matches, (uint32_t)0, priorities, 32)); EXPECT_EQ((uint32_t)1U << 31, FloodFilterPolicy::truncateRulesAtStop( matches, priorities, stop_flags, 32)); } +TEST(FloodRuleOrder, SixtyThreeSlotTableIncludesTheLastSlot) { + uint8_t priorities[63] = {0}; + uint8_t stop_flags[63] = {0}; + priorities[62] = 200; + stop_flags[62] = 1; + const uint64_t matches = ((uint64_t)1U << 62) | 1U; + + EXPECT_EQ(62, FloodFilterPolicy::nextOrderedRule( + matches, (uint64_t)0, priorities, 63)); + EXPECT_EQ((uint64_t)1U << 62, + FloodFilterPolicy::truncateRulesAtStop( + matches, priorities, stop_flags, 63)); +} + TEST(FloodFilterScope, RegionRequirementHasTheExpectedTruthTable) { EXPECT_TRUE(FloodFilterPolicy::scopeRuleAllowed(false, false)); EXPECT_TRUE(FloodFilterPolicy::scopeRuleAllowed(false, true));