Limit flood advert forwarding and persistent abuse
This commit is contained in:
1 parent
27ff4b9063
commit
23fe7c8a9d
15 files changed
+985
-6
No files matched your search
+45
-6
@@ -192,6 +192,7 @@ uint8_t Mesh::getOtaHopLimit() const { return ota::ota_hop_limit(); }
|
||||
#endif
|
||||
|
||||
void Mesh::begin() {
|
||||
if (auto* limiter = getFloodAdvertLimiter()) limiter->reset();
|
||||
_active_direct_retry_count = 0;
|
||||
_active_flood_retry_count = 0;
|
||||
_waiting_direct_retry_count = 0;
|
||||
@@ -285,6 +286,7 @@ bool Mesh::hasPendingOtaApply() const {
|
||||
}
|
||||
|
||||
void __attribute__((noinline)) Mesh::serviceLoopMaintenance() {
|
||||
if (auto* limiter = getFloodAdvertLimiter()) limiter->tick(_ms->getMillis());
|
||||
if (_waiting_direct_retry_count != 0
|
||||
&& millisHasNowPassed(_next_direct_retry_timeout)) {
|
||||
for (int i = 0; i < MAX_DIRECT_RETRY_SLOTS; i++) {
|
||||
@@ -1006,6 +1008,13 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
|
||||
MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): incomplete advertisement packet", getLogDateTime());
|
||||
break;
|
||||
}
|
||||
// Do not accept unsigned trailing data by truncating it for signature
|
||||
// verification. An attacker could vary that tail to manufacture distinct
|
||||
// packet hashes and falsely accumulate abuse against an authentic key.
|
||||
if (pkt->payload_len > min_advert_len + MAX_ADVERT_DATA_SIZE) {
|
||||
MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): oversized advertisement packet", getLogDateTime());
|
||||
break;
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
Identity id;
|
||||
@@ -1017,11 +1026,21 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
|
||||
|
||||
if (self_id.matches(id.pub_key)) {
|
||||
MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): receiving SELF advert packet", getLogDateTime());
|
||||
} else if (!_tables->wasSeen(pkt)) {
|
||||
_tables->markSeen(pkt);
|
||||
} else {
|
||||
const bool seen = _tables->wasSeen(pkt);
|
||||
auto* limiter = pkt->isRouteFlood() ? getFloodAdvertLimiter() : nullptr;
|
||||
const uint32_t now = _ms->getMillis();
|
||||
if (seen && limiter) {
|
||||
uint8_t hash[MAX_HASH_SIZE];
|
||||
pkt->calculatePacketHash(hash);
|
||||
limiter->noteKnownCopy(id.pub_key, hash, now);
|
||||
}
|
||||
// Even a duplicate can reveal a shorter route. Re-verify its signature
|
||||
// before using that evidence, without delivering or relaying it twice.
|
||||
if (seen && !(limiter && limiter->needsShorterPath(id.pub_key, pkt->getPathHashCount(), now))) break;
|
||||
if (!seen) _tables->markSeen(pkt);
|
||||
uint8_t* app_data = &pkt->payload[i];
|
||||
int app_data_len = pkt->payload_len - i;
|
||||
if (app_data_len > MAX_ADVERT_DATA_SIZE) { app_data_len = MAX_ADVERT_DATA_SIZE; }
|
||||
|
||||
// check that signature is valid
|
||||
bool is_ok;
|
||||
@@ -1036,8 +1055,15 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
|
||||
}
|
||||
if (is_ok) {
|
||||
MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): valid advertisement received!", getLogDateTime());
|
||||
onAdvertRecv(pkt, id, timestamp, app_data, app_data_len);
|
||||
action = routeRecvPacket(pkt);
|
||||
if (limiter) {
|
||||
uint8_t hash[MAX_HASH_SIZE];
|
||||
pkt->calculatePacketHash(hash);
|
||||
limiter->observe(id.pub_key, hash, pkt->getPathHashCount(), now, seen);
|
||||
}
|
||||
if (!seen) {
|
||||
onAdvertRecv(pkt, id, timestamp, app_data, app_data_len);
|
||||
action = routeRecvPacket(pkt);
|
||||
}
|
||||
} else {
|
||||
MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): received advertisement with forged signature! (app_data_len=%d)", getLogDateTime(), app_data_len);
|
||||
}
|
||||
@@ -1158,7 +1184,20 @@ DispatcherAction Mesh::routeRecvPacket(Packet* packet) {
|
||||
|
||||
uint8_t n = packet->getPathHashCount();
|
||||
if (packet->isRouteFlood() && !packet->isMarkedDoNotRetransmit()
|
||||
&& (n + 1)*packet->getPathHashSize() <= MAX_PATH_SIZE && allowPacketForward(packet)) {
|
||||
&& (n + 1)*packet->getPathHashSize() <= MAX_PATH_SIZE) {
|
||||
auto* limiter = packet->getPayloadType() == PAYLOAD_TYPE_ADVERT ? getFloodAdvertLimiter() : nullptr;
|
||||
uint8_t hash[MAX_HASH_SIZE];
|
||||
const uint32_t now = _ms->getMillis();
|
||||
if (limiter) {
|
||||
packet->calculatePacketHash(hash);
|
||||
auto decision = limiter->check(packet->payload, hash, now);
|
||||
if (decision != FloodAdvertLimiter::Decision::Allow) {
|
||||
MESH_DEBUG_PRINTLN("%s Mesh::routeRecvPacket(): advert limited (reason=%u)", getLogDateTime(), unsigned(decision));
|
||||
return ACTION_RELEASE;
|
||||
}
|
||||
}
|
||||
if (!allowPacketForward(packet)) return ACTION_RELEASE;
|
||||
if (limiter) limiter->commit(packet->payload, hash, now);
|
||||
// append this node's hash to 'path'
|
||||
self_id.copyHashTo(&packet->path[n * packet->getPathHashSize()], packet->getPathHashSize());
|
||||
packet->setPathHashCount(n + 1);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <Dispatcher.h>
|
||||
#include <helpers/FloodAdvertLimiter.h>
|
||||
|
||||
// OTA-over-LoRa transport is understood by every Mesh role even when the OTA manager/installer is not
|
||||
// compiled in. Repeaters can therefore relay PAYLOAD_TYPE_OTA opaquely while they are on TempRadio.
|
||||
@@ -228,6 +229,9 @@ protected:
|
||||
*/
|
||||
virtual bool allowPacketForward(const Packet* packet);
|
||||
|
||||
// Forwarding roles own the RAM; companions need no advert-limiter table.
|
||||
virtual FloodAdvertLimiter* getFloodAdvertLimiter() { return nullptr; }
|
||||
|
||||
/**
|
||||
* \returns number of milliseconds delay to apply to retransmitting the given packet.
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <Arduino.h>
|
||||
#include "CommonCLI.h"
|
||||
#include "CLICommandUtils.h"
|
||||
#include "FloodAdvertCLI.h"
|
||||
#include "StorageLayout.h"
|
||||
#include "radiolib/RadioPowerLimits.h"
|
||||
#include "radiolib/LR2021SideDetectorConfig.h"
|
||||
@@ -2460,6 +2461,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* re
|
||||
// Observer-only top-level commands (ota check/update, tls.bundletest, alert test)
|
||||
// live in CommonCLI_Observer.cpp.
|
||||
if (handleObserverCommand(sender_timestamp, command, reply)) return;
|
||||
if (mesh::cli::handleFloodAdvertClear(_callbacks->getFloodAdvertLimiter(), command, reply)) return;
|
||||
if (memcmp(command, "poweroff", 8) == 0 || memcmp(command, "shutdown", 8) == 0) {
|
||||
_board->powerOff(); // doesn't return
|
||||
} else if (memcmp(command, "reboot", 6) == 0) {
|
||||
|
||||
@@ -446,6 +446,7 @@ struct LegacyObserverTail {
|
||||
|
||||
class CommonCLICallbacks {
|
||||
public:
|
||||
virtual mesh::FloodAdvertLimiter* getFloodAdvertLimiter() { return nullptr; }
|
||||
// Ordinary CommonCLI setters mutate NodePrefs and therefore save only the
|
||||
// common image. Observer setters explicitly request Observer; only
|
||||
// cross-file migration and mixed-owner setters request Both.
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#include "FloodAdvertLimiter.h"
|
||||
#include <Utils.h>
|
||||
|
||||
namespace mesh {
|
||||
namespace cli {
|
||||
|
||||
// Dispatch only after the role's ordinary CLI authorization checks. Requiring
|
||||
// an explicit "all" avoids treating missing/malformed keys as a global reset.
|
||||
inline bool handleFloodAdvertClear(FloodAdvertLimiter* limiter, const char* command, char* reply) {
|
||||
static const char prefix[] = "clear flood.advert";
|
||||
const size_t prefix_len = sizeof(prefix) - 1;
|
||||
if (strncmp(command, prefix, prefix_len) != 0) return false;
|
||||
const char* arg = command + prefix_len;
|
||||
if (*arg && *arg != ' ' && *arg != '\t') return false;
|
||||
while (*arg == ' ' || *arg == '\t') ++arg;
|
||||
const char* end = arg;
|
||||
while (*end && *end != ' ' && *end != '\t' && *end != '\r' && *end != '\n') ++end;
|
||||
const size_t len = end - arg;
|
||||
while (*end == ' ' || *end == '\t' || *end == '\r' || *end == '\n') ++end;
|
||||
uint8_t key[PUB_KEY_SIZE];
|
||||
const bool all = len == 3 && strncmp(arg, "all", 3) == 0;
|
||||
bool valid = all;
|
||||
if (len == PUB_KEY_SIZE * 2) {
|
||||
char hex[PUB_KEY_SIZE * 2 + 1];
|
||||
memcpy(hex, arg, len);
|
||||
hex[len] = 0;
|
||||
valid = Utils::fromHex(key, PUB_KEY_SIZE, hex);
|
||||
}
|
||||
if (*end || !valid) {
|
||||
strcpy(reply, "ERR: clear flood.advert all|<64-hex-key>");
|
||||
} else if (!limiter) {
|
||||
strcpy(reply, "ERR: advert limiter unavailable on this role");
|
||||
} else {
|
||||
if (all) limiter->reset();
|
||||
else limiter->clear(key);
|
||||
strcpy(reply, all ? "OK - all advert limit history cleared" : "OK - key advert limit history cleared");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace cli
|
||||
} // namespace mesh
|
||||
@@ -0,0 +1,186 @@
|
||||
#include "FloodAdvertLimiter.h"
|
||||
|
||||
namespace mesh {
|
||||
|
||||
uint8_t FloodAdvertLimiter::allowance(uint8_t hops) {
|
||||
static const uint8_t limits[] = {10, 10, 9, 8, 6, 5, 4, 3, 2};
|
||||
return limits[hops < 8 ? hops : 8];
|
||||
}
|
||||
|
||||
void FloodAdvertLimiter::reset() {
|
||||
for (size_t i = 0; i < _capacity; ++i) _entries[i].flags = 0;
|
||||
_last_sweep = 0;
|
||||
}
|
||||
|
||||
FloodAdvertLimiter::Entry* FloodAdvertLimiter::find(const uint8_t* key) {
|
||||
for (size_t i = 0; i < _capacity; ++i) {
|
||||
if ((_entries[i].flags & ACTIVE) && memcmp(_entries[i].key, key, PUB_KEY_SIZE) == 0) {
|
||||
return &_entries[i];
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void FloodAdvertLimiter::clear(const uint8_t* full_key) {
|
||||
Entry* entry = find(full_key);
|
||||
if (entry) entry->flags = 0;
|
||||
}
|
||||
|
||||
int FloodAdvertLimiter::hashIndex(const Entry& entry, const uint8_t* hash) {
|
||||
for (uint8_t i = 0; i < entry.count; ++i) {
|
||||
if (memcmp(entry.hashes[i], hash, MAX_HASH_SIZE) == 0) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void FloodAdvertLimiter::advance(Entry& entry, uint32_t now) {
|
||||
if (!(entry.flags & ACTIVE)) return;
|
||||
if ((entry.flags & HAS_FORWARDED) && uint32_t(now - entry.last_forwarded) >= BAD_INTERVAL_MS) {
|
||||
entry.flags &= ~HAS_FORWARDED;
|
||||
}
|
||||
uint32_t elapsed = now - entry.window_start;
|
||||
if (elapsed >= WINDOW_MS) {
|
||||
const bool over = entry.count > allowance(entry.min_hops);
|
||||
if (over) {
|
||||
// Recovery starts after the last over-limit window, not after the last
|
||||
// forwarded packet. Suppressed RX traffic still counts as abuse.
|
||||
entry.last_violation = entry.window_start + WINDOW_MS;
|
||||
}
|
||||
entry.flags &= ~PREVIOUS_OVER;
|
||||
if (over && elapsed < 2 * WINDOW_MS) entry.flags |= PREVIOUS_OVER;
|
||||
entry.window_start += (elapsed / WINDOW_MS) * WINDOW_MS;
|
||||
entry.count = 0;
|
||||
entry.forwarded = 0;
|
||||
entry.min_hops = 255;
|
||||
// Quiet ordinary sources need no history beyond their three-hour window.
|
||||
// Keep a first strike through the next window; never evict a bad entry.
|
||||
if (!(entry.flags & (BAD | PREVIOUS_OVER))) entry.flags = 0;
|
||||
}
|
||||
if ((entry.flags & BAD) && uint32_t(now - entry.last_violation) >= RECOVERY_MS) {
|
||||
entry.flags &= ~BAD;
|
||||
if (entry.count == 0 && !(entry.flags & PREVIOUS_OVER)) entry.flags = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void FloodAdvertLimiter::refresh(uint32_t now) {
|
||||
for (size_t i = 0; i < _capacity; ++i) advance(_entries[i], now);
|
||||
_last_sweep = now;
|
||||
}
|
||||
|
||||
void FloodAdvertLimiter::tick(uint32_t now) {
|
||||
// Periodic expiry prevents stale history resurrecting after a millis wrap,
|
||||
// even when no adverts arrive for weeks. Receive operations also expire it
|
||||
// immediately at the exact window/recovery boundary.
|
||||
if (uint32_t(now - _last_sweep) >= 60000UL) refresh(now);
|
||||
}
|
||||
|
||||
bool FloodAdvertLimiter::needsShorterPath(const uint8_t* key, uint8_t hops, uint32_t now) {
|
||||
refresh(now);
|
||||
Entry* entry = find(key);
|
||||
return entry && hops < entry->min_hops;
|
||||
}
|
||||
|
||||
void FloodAdvertLimiter::noteKnownCopy(const uint8_t* key, const uint8_t* hash, uint32_t now) {
|
||||
refresh(now);
|
||||
Entry* entry = find(key);
|
||||
// Only the exact retained hash of a previously verified payload can refresh
|
||||
// last-heard without another signature check. Never trust just the key/path.
|
||||
if (entry && hashIndex(*entry, hash) >= 0) entry->last_heard = now;
|
||||
}
|
||||
|
||||
void FloodAdvertLimiter::observe(const uint8_t* key, const uint8_t* hash, uint8_t hops,
|
||||
uint32_t now, bool previously_seen) {
|
||||
refresh(now);
|
||||
Entry* entry = find(key);
|
||||
if (!entry) {
|
||||
if (previously_seen) return;
|
||||
uint32_t start = now;
|
||||
// Keys sharing the first 12 hex characters share a normal quota AND its epoch.
|
||||
for (size_t i = 0; i < _capacity; ++i) {
|
||||
if ((_entries[i].flags & ACTIVE) && memcmp(_entries[i].key, key, PREFIX_BYTES) == 0) {
|
||||
start = _entries[i].window_start;
|
||||
if (_entries[i].min_hops < hops) hops = _entries[i].min_hops;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Entry* oldest = nullptr;
|
||||
for (size_t i = 0; i < _capacity; ++i) {
|
||||
if (!(_entries[i].flags & ACTIVE)) {
|
||||
entry = &_entries[i];
|
||||
break;
|
||||
}
|
||||
// Preserve both bad-list entries and first-strike evidence. Evict only
|
||||
// normal entries, least recently heard first, using rollover-safe ages.
|
||||
const Entry& candidate = _entries[i];
|
||||
if (!(candidate.flags & (BAD | PREVIOUS_OVER))
|
||||
&& candidate.count <= allowance(candidate.min_hops)
|
||||
&& (!oldest || uint32_t(now - candidate.last_heard) > uint32_t(now - oldest->last_heard))) {
|
||||
oldest = &_entries[i];
|
||||
}
|
||||
}
|
||||
if (!entry) entry = oldest;
|
||||
if (!entry) return; // all slots protect abuse history: fail closed
|
||||
memset(entry, 0, sizeof(*entry));
|
||||
memcpy(entry->key, key, PUB_KEY_SIZE);
|
||||
entry->window_start = start;
|
||||
entry->min_hops = hops;
|
||||
entry->flags = ACTIVE;
|
||||
}
|
||||
entry->last_heard = now;
|
||||
// The normal level belongs to the prefix, including its shortest RX path.
|
||||
// Keep that minimum on every colliding full key for consistent window-close
|
||||
// abuse accounting, but never combine their received counts into a strike.
|
||||
if (hops < entry->min_hops) entry->min_hops = hops;
|
||||
for (size_t i = 0; i < _capacity; ++i) {
|
||||
if ((_entries[i].flags & ACTIVE) && memcmp(_entries[i].key, key, PREFIX_BYTES) == 0
|
||||
&& hops < _entries[i].min_hops) _entries[i].min_hops = hops;
|
||||
}
|
||||
if (previously_seen || hashIndex(*entry, hash) >= 0 || entry->count == HASH_SLOTS) return;
|
||||
memcpy(entry->hashes[entry->count++], hash, MAX_HASH_SIZE);
|
||||
if (entry->count > allowance(entry->min_hops)) {
|
||||
entry->last_violation = now;
|
||||
if (entry->flags & PREVIOUS_OVER) entry->flags |= BAD;
|
||||
}
|
||||
}
|
||||
|
||||
FloodAdvertLimiter::Decision FloodAdvertLimiter::check(const uint8_t* key, const uint8_t* hash,
|
||||
uint32_t now) {
|
||||
refresh(now);
|
||||
Entry* entry = find(key);
|
||||
if (!entry) return Decision::Capacity;
|
||||
int idx = hashIndex(*entry, hash);
|
||||
if (idx < 0) return Decision::Quota; // saturated receive history is fail-closed
|
||||
if (entry->forwarded & (1U << idx)) return Decision::Duplicate;
|
||||
if ((entry->flags & (BAD | HAS_FORWARDED)) == (BAD | HAS_FORWARDED)
|
||||
&& uint32_t(now - entry->last_forwarded) < BAD_INTERVAL_MS) return Decision::BadList;
|
||||
|
||||
uint8_t hops = entry->min_hops;
|
||||
unsigned forwarded = 0;
|
||||
for (size_t i = 0; i < _capacity; ++i) {
|
||||
const Entry& other = _entries[i];
|
||||
if (!(other.flags & ACTIVE) || memcmp(other.key, key, PREFIX_BYTES) != 0) continue;
|
||||
if (other.min_hops < hops) hops = other.min_hops;
|
||||
for (uint16_t bits = other.forwarded; bits; bits &= bits - 1) ++forwarded;
|
||||
}
|
||||
return forwarded < allowance(hops) ? Decision::Allow : Decision::Quota;
|
||||
}
|
||||
|
||||
void FloodAdvertLimiter::commit(const uint8_t* key, const uint8_t* hash, uint32_t now) {
|
||||
// Caller has passed check() and ALL other forwarding gates, before appending
|
||||
// its own path hop. Retries of this same advert are not new distinct adverts.
|
||||
Entry* entry = find(key);
|
||||
if (!entry) return;
|
||||
int idx = hashIndex(*entry, hash);
|
||||
if (idx < 0) return;
|
||||
entry->forwarded |= uint16_t(1U << idx);
|
||||
entry->last_forwarded = now;
|
||||
entry->flags |= HAS_FORWARDED;
|
||||
}
|
||||
|
||||
bool FloodAdvertLimiter::isBad(const uint8_t* key, uint32_t now) {
|
||||
refresh(now);
|
||||
Entry* entry = find(key);
|
||||
return entry && (entry->flags & BAD);
|
||||
}
|
||||
|
||||
} // namespace mesh
|
||||
@@ -0,0 +1,82 @@
|
||||
#pragma once
|
||||
|
||||
#include <MeshCore.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifndef FLOOD_ADVERT_SOURCE_SLOTS
|
||||
#if defined(STM32_PLATFORM)
|
||||
#define FLOOD_ADVERT_SOURCE_SLOTS 8
|
||||
#elif defined(NRF52_PLATFORM)
|
||||
// Preserve the runtime heap reserve alongside the fixed 64 KiB mOTA arena.
|
||||
#define FLOOD_ADVERT_SOURCE_SLOTS 96
|
||||
#else
|
||||
#define FLOOD_ADVERT_SOURCE_SLOTS 128
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace mesh {
|
||||
|
||||
// Volatile, bounded receive history. Only forwarding roles own this table.
|
||||
// All times are uint32_t uptime milliseconds, never advert timestamps or RTC.
|
||||
class FloodAdvertLimiter {
|
||||
public:
|
||||
static constexpr uint32_t WINDOW_MS = 180UL * 60UL * 1000UL;
|
||||
static constexpr uint32_t BAD_INTERVAL_MS = 12UL * 60UL * 60UL * 1000UL;
|
||||
static constexpr uint32_t RECOVERY_MS = 7UL * 24UL * 60UL * 60UL * 1000UL;
|
||||
static constexpr uint8_t HASH_SLOTS = 11; // maximum normal allowance + evidence of excess
|
||||
static constexpr uint8_t PREFIX_BYTES = 6; // first 12 public-key hex characters
|
||||
|
||||
enum class Decision : uint8_t { Allow, Duplicate, Quota, BadList, Capacity };
|
||||
|
||||
struct Entry {
|
||||
uint8_t key[PUB_KEY_SIZE];
|
||||
uint8_t hashes[HASH_SLOTS][MAX_HASH_SIZE];
|
||||
uint32_t window_start;
|
||||
uint32_t last_violation;
|
||||
uint32_t last_forwarded;
|
||||
uint32_t last_heard;
|
||||
uint16_t forwarded;
|
||||
uint8_t min_hops;
|
||||
uint8_t count;
|
||||
uint8_t flags;
|
||||
};
|
||||
|
||||
void reset();
|
||||
void clear(const uint8_t* full_key);
|
||||
void tick(uint32_t now);
|
||||
static uint8_t allowance(uint8_t hops);
|
||||
bool needsShorterPath(const uint8_t* key, uint8_t hops, uint32_t now);
|
||||
void noteKnownCopy(const uint8_t* key, const uint8_t* hash, uint32_t now);
|
||||
// Call ONLY after signature validation. Previously seen copies may improve
|
||||
// the minimum path, but cannot add evidence or consume forwarding quota.
|
||||
void observe(const uint8_t* key, const uint8_t* hash, uint8_t hops,
|
||||
uint32_t now, bool previously_seen = false);
|
||||
Decision check(const uint8_t* key, const uint8_t* hash, uint32_t now);
|
||||
void commit(const uint8_t* key, const uint8_t* hash, uint32_t now);
|
||||
bool isBad(const uint8_t* key, uint32_t now);
|
||||
|
||||
protected:
|
||||
FloodAdvertLimiter(Entry* entries, size_t capacity) : _entries(entries), _capacity(capacity) {}
|
||||
FloodAdvertLimiter(const FloodAdvertLimiter&) = delete;
|
||||
FloodAdvertLimiter& operator=(const FloodAdvertLimiter&) = delete;
|
||||
|
||||
private:
|
||||
enum : uint8_t { ACTIVE = 1, PREVIOUS_OVER = 2, BAD = 4, HAS_FORWARDED = 8 };
|
||||
Entry* _entries;
|
||||
size_t _capacity;
|
||||
uint32_t _last_sweep = 0;
|
||||
Entry* find(const uint8_t* key);
|
||||
static int hashIndex(const Entry& entry, const uint8_t* hash);
|
||||
static void advance(Entry& entry, uint32_t now);
|
||||
void refresh(uint32_t now);
|
||||
};
|
||||
|
||||
template<size_t Capacity = FLOOD_ADVERT_SOURCE_SLOTS>
|
||||
class StaticFloodAdvertLimiter : public FloodAdvertLimiter {
|
||||
static_assert(Capacity > 0, "Advert limiter needs at least one source slot");
|
||||
Entry _storage[Capacity];
|
||||
public:
|
||||
StaticFloodAdvertLimiter() : FloodAdvertLimiter(_storage, Capacity) { reset(); }
|
||||
};
|
||||
|
||||
} // namespace mesh
|
||||
Reference in new issue
Block a user