Skip to content

Commit b4ee586

Browse files
committed
Implement LRU cache for storing hashes to filter flood
Replace the fixed-size hash table with an LRU cache using actual timestamps instead of timeout-based eviction. Some busy nodes see more than 128 packets before duplicates arrive, so LRU ordering provides better eviction behavior.
1 parent df01fd3 commit b4ee586

1 file changed

Lines changed: 43 additions & 13 deletions

File tree

src/helpers/SimpleMeshTables.h

Lines changed: 43 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,15 @@
1111

1212
class SimpleMeshTables : public mesh::MeshTables {
1313
uint8_t _hashes[MAX_PACKET_HASHES*MAX_HASH_SIZE];
14-
int _next_idx;
14+
uint32_t _last_seen[MAX_PACKET_HASHES]; // timestamp for LRU eviction
1515
uint32_t _acks[MAX_PACKET_ACKS];
1616
int _next_ack_idx;
1717
uint32_t _direct_dups, _flood_dups;
1818

1919
public:
20-
SimpleMeshTables() {
20+
SimpleMeshTables() {
2121
memset(_hashes, 0, sizeof(_hashes));
22-
_next_idx = 0;
22+
memset(_last_seen, 0, sizeof(_last_seen));
2323
memset(_acks, 0, sizeof(_acks));
2424
_next_ack_idx = 0;
2525
_direct_dups = _flood_dups = 0;
@@ -28,13 +28,26 @@ class SimpleMeshTables : public mesh::MeshTables {
2828
#ifdef ESP32
2929
void restoreFrom(File f) {
3030
f.read(_hashes, sizeof(_hashes));
31-
f.read((uint8_t *) &_next_idx, sizeof(_next_idx));
31+
int dummy_idx;
32+
f.read((uint8_t *) &dummy_idx, sizeof(dummy_idx)); // legacy, ignore
3233
f.read((uint8_t *) &_acks[0], sizeof(_acks));
3334
f.read((uint8_t *) &_next_ack_idx, sizeof(_next_ack_idx));
35+
// Treat restored hashes as just seen - give them fresh timestamps
36+
uint32_t now = millis();
37+
const uint8_t* sp = _hashes;
38+
for (int i = 0; i < MAX_PACKET_HASHES; i++, sp += MAX_HASH_SIZE) {
39+
// Check if slot has data (not all zeros)
40+
bool empty = true;
41+
for (int j = 0; j < MAX_HASH_SIZE && empty; j++) {
42+
if (sp[j] != 0) empty = false;
43+
}
44+
_last_seen[i] = empty ? 0 : now;
45+
}
3446
}
3547
void saveTo(File f) {
3648
f.write(_hashes, sizeof(_hashes));
37-
f.write((const uint8_t *) &_next_idx, sizeof(_next_idx));
49+
int dummy_idx = 0;
50+
f.write((const uint8_t *) &dummy_idx, sizeof(dummy_idx)); // legacy format
3851
f.write((const uint8_t *) &_acks[0], sizeof(_acks));
3952
f.write((const uint8_t *) &_next_ack_idx, sizeof(_next_ack_idx));
4053
}
@@ -45,7 +58,7 @@ class SimpleMeshTables : public mesh::MeshTables {
4558
uint32_t ack;
4659
memcpy(&ack, packet->payload, 4);
4760
for (int i = 0; i < MAX_PACKET_ACKS; i++) {
48-
if (ack == _acks[i]) {
61+
if (ack == _acks[i]) {
4962
if (packet->isRouteDirect()) {
5063
_direct_dups++; // keep some stats
5164
} else {
@@ -54,29 +67,45 @@ class SimpleMeshTables : public mesh::MeshTables {
5467
return true;
5568
}
5669
}
57-
70+
5871
_acks[_next_ack_idx] = ack;
59-
_next_ack_idx = (_next_ack_idx + 1) % MAX_PACKET_ACKS; // cyclic table
72+
_next_ack_idx = (_next_ack_idx + 1) % MAX_PACKET_ACKS; // cyclic table
6073
return false;
6174
}
6275

76+
uint32_t now = millis();
6377
uint8_t hash[MAX_HASH_SIZE];
6478
packet->calculatePacketHash(hash);
6579

80+
int oldest_idx = 0;
81+
uint32_t oldest_age = 0;
82+
6683
const uint8_t* sp = _hashes;
6784
for (int i = 0; i < MAX_PACKET_HASHES; i++, sp += MAX_HASH_SIZE) {
68-
if (memcmp(hash, sp, MAX_HASH_SIZE) == 0) {
85+
uint32_t age = now - _last_seen[i];
86+
87+
if (memcmp(hash, sp, MAX_HASH_SIZE) == 0 && _last_seen[i] != 0) {
88+
// Match found - refresh timestamp (LRU touch) and return true
89+
_last_seen[i] = now;
6990
if (packet->isRouteDirect()) {
7091
_direct_dups++; // keep some stats
7192
} else {
7293
_flood_dups++;
7394
}
7495
return true;
7596
}
97+
98+
// Track oldest entry for LRU eviction
99+
if (age > oldest_age) {
100+
oldest_age = age;
101+
oldest_idx = i;
102+
}
76103
}
77104

78-
memcpy(&_hashes[_next_idx*MAX_HASH_SIZE], hash, MAX_HASH_SIZE);
79-
_next_idx = (_next_idx + 1) % MAX_PACKET_HASHES; // cyclic table
105+
// Not found - evict oldest (LRU)
106+
int insert_idx = oldest_idx;
107+
memcpy(&_hashes[insert_idx*MAX_HASH_SIZE], hash, MAX_HASH_SIZE);
108+
_last_seen[insert_idx] = now;
80109
return false;
81110
}
82111

@@ -85,7 +114,7 @@ class SimpleMeshTables : public mesh::MeshTables {
85114
uint32_t ack;
86115
memcpy(&ack, packet->payload, 4);
87116
for (int i = 0; i < MAX_PACKET_ACKS; i++) {
88-
if (ack == _acks[i]) {
117+
if (ack == _acks[i]) {
89118
_acks[i] = 0;
90119
break;
91120
}
@@ -96,8 +125,9 @@ class SimpleMeshTables : public mesh::MeshTables {
96125

97126
uint8_t* sp = _hashes;
98127
for (int i = 0; i < MAX_PACKET_HASHES; i++, sp += MAX_HASH_SIZE) {
99-
if (memcmp(hash, sp, MAX_HASH_SIZE) == 0) {
128+
if (memcmp(hash, sp, MAX_HASH_SIZE) == 0) {
100129
memset(sp, 0, MAX_HASH_SIZE);
130+
_last_seen[i] = 0;
101131
break;
102132
}
103133
}

0 commit comments

Comments
 (0)