Compact flood filter persistence

This commit is contained in:
mikecarper committed 2026-08-21 00:55:05 -07:00
1 parent 177eb06745
commit 9e4d246ac6
3 files changed
+61 -7

No files matched your search

+7 -7
View File
@@ -5001,7 +5001,8 @@ bool MyMesh::loadFloodPacketFilters() {
bool version_7 = success && memcmp(magic, "FPF7", sizeof(magic)) == 0;
success = (version_6 || version_7)
&& file.read(&count, sizeof(count)) == sizeof(count)
&& count <= FLOOD_PACKET_FILTER_SLOTS;
&& FloodFilterPolicy::forwardPersistenceCountSupported(
count, FLOOD_PACKET_FILTER_SLOTS);
for (int i = 0; success && i < count; i++) {
uint8_t active = 0;
@@ -5762,14 +5763,13 @@ bool MyMesh::saveFloodPacketFilters(bool empty_scope_phase,
};
const uint8_t magic[4] = {'F', 'P', 'F', '7'};
uint8_t count = FLOOD_PACKET_FILTER_SLOTS;
const uint8_t count = FloodFilterPolicy::forwardPersistenceCount(
flood_packet_filters, FLOOD_PACKET_FILTER_SLOTS,
empty_forward_phase);
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 = empty_forward_phase
? empty_entry : flood_packet_filters[i];
for (int i = 0; success && i < count; i++) {
const auto& 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;
+19
View File
@@ -40,6 +40,25 @@ enum ChannelScopeGate {
CHANNEL_SCOPE_REQUIRED_REJECTED,
};
// Persist only through the highest occupied forward-rule slot. The on-disk
// formats are dense, so inactive holes before that slot must remain present,
// while the usually-large inactive tail need not consume LittleFS space.
// `empty_forward_phase` deliberately wins over the live table for atomic
// phase-clearing transactions.
template<typename Entry>
inline uint8_t forwardPersistenceCount(const Entry entries[],
uint8_t slot_count,
bool empty_forward_phase) {
if (empty_forward_phase || entries == NULL) return 0;
while (slot_count > 0 && !entries[slot_count - 1].active) slot_count--;
return slot_count;
}
inline bool forwardPersistenceCountSupported(uint8_t count,
uint8_t slot_count) {
return count <= slot_count;
}
inline ChannelScopeGate channelScopeGate(bool table_active,
bool is_group_channel_packet,
bool channel_requires_scope,
@@ -3,6 +3,41 @@
#include <helpers/FloodFilterPolicy.h>
#include <helpers/RegionNameUtils.h>
struct FloodPersistenceRow {
bool active;
};
TEST(FloodFilterPersistence, DefaultRowsTrimTheInactiveTail) {
FloodPersistenceRow rows[63] = {};
rows[0].active = true;
rows[1].active = true;
EXPECT_EQ(2, FloodFilterPolicy::forwardPersistenceCount(
rows, 63, false));
}
TEST(FloodFilterPersistence, SparseHighSlotPreservesEveryEarlierPosition) {
FloodPersistenceRow rows[63] = {};
rows[0].active = true;
rows[47].active = true;
EXPECT_EQ(48, FloodFilterPolicy::forwardPersistenceCount(
rows, 63, false));
}
TEST(FloodFilterPersistence, EmptyForwardPhaseSerializesNoRows) {
FloodPersistenceRow rows[63] = {};
rows[62].active = true;
EXPECT_EQ(0, FloodFilterPolicy::forwardPersistenceCount(
rows, 63, true));
}
TEST(FloodFilterPersistence, OldFullCountRemainsLoadCompatible) {
EXPECT_TRUE(FloodFilterPolicy::forwardPersistenceCountSupported(63, 63));
EXPECT_FALSE(FloodFilterPolicy::forwardPersistenceCountSupported(64, 63));
}
static mesh::Packet makeFloodPacket(uint8_t hash_size,
const uint8_t* path,
uint8_t path_hops) {