From 6f2a678a2d721b9659681987d5a4c90556afc317 Mon Sep 17 00:00:00 2001 From: medentem Date: Mon, 6 Jan 2025 14:34:56 -0600 Subject: [PATCH] added mechanisms to account for uninitialized coverage, stale coverage and unknown nodes --- .vscode/settings.json | 10 ++ src/mesh/CountingCoverageFilter.cpp | 267 ++++++++++++++++++++++++++++ src/mesh/CountingCoverageFilter.h | 123 +++++++++++++ src/mesh/CoverageFilter.h | 2 + src/mesh/FloodingRouter.cpp | 44 +++-- src/mesh/FloodingRouter.h | 2 +- src/mesh/MeshTypes.h | 12 +- src/mesh/NodeDB.cpp | 30 +++- src/mesh/NodeDB.h | 8 + src/mesh/RadioInterface.h | 2 +- 10 files changed, 470 insertions(+), 30 deletions(-) create mode 100644 src/mesh/CountingCoverageFilter.cpp create mode 100644 src/mesh/CountingCoverageFilter.h diff --git a/.vscode/settings.json b/.vscode/settings.json index bf9b82111..fa505b67e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,5 +7,15 @@ "cmake.configureOnOpen": false, "[cpp]": { "editor.defaultFormatter": "trunk.io" + }, + "files.associations": { + "*.tcc": "cpp", + "deque": "cpp", + "list": "cpp", + "string": "cpp", + "unordered_map": "cpp", + "unordered_set": "cpp", + "vector": "cpp", + "system_error": "cpp" } } diff --git a/src/mesh/CountingCoverageFilter.cpp b/src/mesh/CountingCoverageFilter.cpp new file mode 100644 index 000000000..90522b0aa --- /dev/null +++ b/src/mesh/CountingCoverageFilter.cpp @@ -0,0 +1,267 @@ +#pragma once + +#include "CountingCoverageFilter.h" + +CountingCoverageFilter::CountingCoverageFilter() +{ + clear(); + instantiationTime_ = getTime(); +} + +/** + * Add an item (node) to this counting bloom filter. + * Increments the counters for each hash position (up to the max for BITS_PER_COUNTER). + */ +void CountingCoverageFilter::add(NodeNum item) +{ + // We'll do BLOOM_HASH_FUNCTIONS hash functions. Typically BLOOM_HASH_FUNCTIONS=2 for simplicity. + size_t indices[BLOOM_HASH_FUNCTIONS]; + computeHashIndices(item, indices); + + for (size_t i = 0; i < BLOOM_HASH_FUNCTIONS; i++) { + incrementCounter(indices[i]); + } +} + +/** + * Remove an item (node), decrementing counters at each hash position (if >0). + */ +void CountingCoverageFilter::remove(NodeNum item) +{ + size_t indices[BLOOM_HASH_FUNCTIONS]; + computeHashIndices(item, indices); + + for (size_t i = 0; i < BLOOM_HASH_FUNCTIONS; i++) { + decrementCounter(indices[i]); + } +} + +/** + * Check if an item "might" be in the set: + * - If ALL counters at those BLOOM_HASH_FUNCTIONS positions are > 0, + * item is "possible" (false positive possible). + * - If ANY position is zero, item is definitely not in the set. + */ +bool CountingCoverageFilter::check(NodeNum item) const +{ + size_t indices[BLOOM_HASH_FUNCTIONS]; + computeHashIndices(item, indices); + + for (size_t i = 0; i < BLOOM_HASH_FUNCTIONS; i++) { + if (getCounterValue(indices[i]) == 0) { + return false; // definitely not in + } + } + return true; // might be in +} + +/** + * Approximate count of how many items are in the filter. + * The naive approach is sum(counters)/BLOOM_HASH_FUNCTIONS. Collisions can inflate this, though. + */ +float CountingCoverageFilter::approximateCount() const +{ + uint64_t sum = 0; + for (size_t i = 0; i < NUM_UNKNOWN_NODE_COUNTERS; i++) { + sum += getCounterValue(i); + } + // We do K increments per item, so a naive estimate is sum/BLOOM_HASH_FUNCTIONS + return static_cast(sum) / static_cast(BLOOM_HASH_FUNCTIONS); +} + +/** + * Merge (union) this filter with another filter of the same params. + * We'll take the max of each counter. + * (Alternatively you could add, but max is safer for a union.) + */ +void CountingCoverageFilter::merge(const CountingCoverageFilter &other) +{ + for (size_t i = 0; i < NUM_UNKNOWN_NODE_COUNTERS; i++) { + uint8_t mine = getCounterValue(i); + uint8_t theirs = other.getCounterValue(i); + uint8_t mergedVal = (mine > theirs) ? mine : theirs; + setCounterValue(i, mergedVal); + } +} + +/** + * Clear out all counters to zero. + */ +void CountingCoverageFilter::clear() +{ + storage_.fill(0); +} + +/** + * Compare a standard Bloom (bit-based, e.g., 16 bytes => 128 bits) to see how many bits + * are newly set that we do not have a nonzero counter for. + * This is purely an approximate approach for "new coverage" bits. + */ +int CountingCoverageFilter::approximateNewCoverageCount(const CoverageFilter &incoming) const +{ + if (isStale()) + return 0.0f; + + // 1) Retrieve the bits from the incoming coverage filter + const auto &bits = incoming.getBits(); // this is a std::array + size_t coverageByteCount = bits.size(); // typically 16 bytes => 128 bits + + size_t maxBitsToCheck = coverageByteCount * 8; + if (maxBitsToCheck > NUM_UNKNOWN_NODE_COUNTERS) { + maxBitsToCheck = NUM_UNKNOWN_NODE_COUNTERS; + } + + int newCoverageBits = 0; + for (size_t bitIndex = 0; bitIndex < maxBitsToCheck; bitIndex++) { + size_t byteIndex = bitIndex / 8; + uint8_t bitMask = 1 << (bitIndex % 8); + + // Was this bit set in the incoming coverage filter? + bool coverageBitSet = (bits[byteIndex] & bitMask) != 0; + if (!coverageBitSet) { + continue; + } + + // If our local counter at bitIndex is 0 => "new coverage" bit + if (getCounterValue(bitIndex) == 0) { + newCoverageBits++; + } + } + return newCoverageBits; +} + +float CountingCoverageFilter::approximateCoverageRatio(const CoverageFilter &incoming) const +{ + // 1) How many "new coverage" bits do we see? + int newBits = approximateNewCoverageCount(incoming); + + // 2) How many items do we hold, approx? + float myApproxCount = approximateCount(); + if (myApproxCount < 0.00001f) { + // Avoid division by zero; or you can return 0 or 1 as suits your logic. + return 0.0f; + } + + // newBits is a bit count, approximateCount() is an item count. + // This is a rough ratio. Decide if you want them in the same domain. + // We'll treat "newBits" ~ "new items," so ratio = newBits / myApproxCount + return static_cast(newBits) / myApproxCount; +} + +uint8_t CountingCoverageFilter::getCounterValue(size_t idx) const +{ + assert(idx < NUM_UNKNOWN_NODE_COUNTERS); + if (BITS_PER_UNKNOWN_NODE_COUNTER == 8) { + // Easiest case: 1 byte per counter + return storage_[idx]; + } else if (BITS_PER_UNKNOWN_NODE_COUNTER == 4) { + // 2 counters per byte + size_t byteIndex = idx / 2; // each byte holds 2 counters + bool second = (idx % 2) == 1; // 0 => lower nibble, 1 => upper nibble + uint8_t rawByte = storage_[byteIndex]; + if (!second) { + // lower 4 bits + return (rawByte & 0x0F); + } else { + // upper 4 bits + return (rawByte >> 4) & 0x0F; + } + } else { + // If you want to handle other bit widths (2, 3, 16, etc.), you'd do more logic here. + static_assert(BITS_PER_UNKNOWN_NODE_COUNTER == 4 || BITS_PER_UNKNOWN_NODE_COUNTER == 8, + "Only 4-bit or 8-bit counters allowed."); + return 0; + } +} + +/** + * Set the counter at position idx to val (clamped to max representable). + */ +void CountingCoverageFilter::setCounterValue(size_t idx, uint8_t val) +{ + assert(idx < NUM_UNKNOWN_NODE_COUNTERS); + // clamp val + uint8_t maxVal = (1 << BITS_PER_UNKNOWN_NODE_COUNTER) - 1; // e.g. 15 for 4 bits, 255 for 8 bits + if (val > maxVal) + val = maxVal; + + if (BITS_PER_UNKNOWN_NODE_COUNTER == 8) { + storage_[idx] = val; + } else if (BITS_PER_UNKNOWN_NODE_COUNTER == 4) { + size_t byteIndex = idx / 2; + bool second = (idx % 2) == 1; + uint8_t rawByte = storage_[byteIndex]; + + if (!second) { + // Lower nibble + // clear lower nibble, then set + rawByte = (rawByte & 0xF0) | (val & 0x0F); + } else { + // Upper nibble + // clear upper nibble, then set + rawByte = (rawByte & 0x0F) | ((val & 0x0F) << 4); + } + storage_[byteIndex] = rawByte; + } +} + +bool CountingCoverageFilter::isStale() const +{ + // How long has it been since this filter was created? + uint32_t now = getTime(); + uint32_t age = now - instantiationTime_; + return age > STALE_COVERAGE_SECONDS; +} + +/** + * Increment the counter at idx by 1 (clamped to max). + */ +void CountingCoverageFilter::incrementCounter(size_t idx) +{ + // read current + uint8_t currVal = getCounterValue(idx); + // increment + uint8_t nextVal = currVal + 1; // might overflow if at max + setCounterValue(idx, nextVal); +} + +/** + * Decrement the counter at idx by 1 (if >0). + */ +void CountingCoverageFilter::decrementCounter(size_t idx) +{ + // read current + uint8_t currVal = getCounterValue(idx); + if (currVal > 0) { + setCounterValue(idx, currVal - 1); + } + // else do nothing (can't go negative) +} + +void CountingCoverageFilter::computeHashIndices(NodeNum value, size_t outIndices[BLOOM_HASH_FUNCTIONS]) const +{ + // We can use two or more seeds for separate hashes. Here we do two seeds as an example. + // If BLOOM_HASH_FUNCTIONS > 2, you'd do more seeds or vary the combined approach. + static const uint64_t seed1 = 0xDEADBEEF; + static const uint64_t seed2 = 0xBADC0FFE; + + outIndices[0] = hashGeneric(value, seed1); + if (BLOOM_HASH_FUNCTIONS >= 2) { + outIndices[1] = hashGeneric(value, seed2); + } + // If BLOOM_HASH_FUNCTIONS were greater than 2, we'd have to update similarly for outIndices[2], + // outIndices[3], etc. with new seeds +} + +size_t CountingCoverageFilter::hashGeneric(NodeNum value, uint64_t seed) const +{ + // Just a simplistic combine of "value" and "seed" then do std::hash. + uint64_t combined = value ^ (seed + (value << 6) + (value >> 2)); + + std::hash hasher; + uint64_t hashOut = hasher(combined); + + // Then map to [0..(NUM_UNKNOWN_NODE_COUNTERS-1)] + // because each "slot" is an index from 0..NUM_UNKNOWN_NODE_COUNTERS-1 + return static_cast(hashOut % NUM_UNKNOWN_NODE_COUNTERS); +} \ No newline at end of file diff --git a/src/mesh/CountingCoverageFilter.h b/src/mesh/CountingCoverageFilter.h new file mode 100644 index 000000000..4d3b8a13c --- /dev/null +++ b/src/mesh/CountingCoverageFilter.h @@ -0,0 +1,123 @@ +#include "CoverageFilter.h" +#include "MeshTypes.h" + +#include +#include +#include +#include +#include +#include +#include + +/** + * A generic Counting Coverage (bloom) filter, which can be parameterized by: + * - NUM_UNKNOWN_NODE_COUNTERS (how many counter "slots") + * - BITS_PER_UNKNOWN_NODE_COUNTER (4 bits, 8 bits, etc.) + * - BLOOM_HASH_FUNCTIONS (number of hash functions, typically 2 or more) + * + */ + +// We have NUM_UNKNOWN_NODE_COUNTERS total "slots," and each slot is BITS_PER_UNKNOWN_NODE_COUNTER wide. +// For BITS_PER_UNKNOWN_NODE_COUNTER=4, each slot can hold 0..15. +// We store these slots in a byte array sized for the total number of bits. +// 1) Calculate how many total bits we need: +#define STORAGE_BITS NUM_UNKNOWN_NODE_COUNTERS *BITS_PER_UNKNOWN_NODE_COUNTER + +// 2) Convert that to bytes (rounding up) +#define STORAGE_BYTES ((STORAGE_BITS + 7) / 8) // integer ceiling division + +class CountingCoverageFilter +{ + public: + CountingCoverageFilter(); + + /** + * Add an item (node) to this counting bloom filter. + * Increments the counters for each hash position (up to the max for BITS_PER_COUNTER). + */ + void add(NodeNum item); + + /** + * Remove an item (node), decrementing counters at each hash position (if >0). + */ + void remove(NodeNum item); + + /** + * Check if an item "might" be in the set: + * - If ALL counters at those BLOOM_HASH_FUNCTIONS positions are > 0, + * item is "possible" (false positive possible). + * - If ANY position is zero, item is definitely not in the set. + */ + bool check(NodeNum item) const; + + /** + * Approximate count of how many items are in the filter. + * The naive approach is sum(counters)/BLOOM_HASH_FUNCTIONS. Collisions can inflate this, though. + */ + float approximateCount() const; + + /** + * Merge (union) this filter with another filter of the same params. + * We'll take the max of each counter. + * (Alternatively you could add, but max is safer for a union.) + */ + void merge(const CountingCoverageFilter &other); + + /** + * Clear out all counters to zero. + */ + void clear(); + + /** + * Compare a standard Bloom (bit-based, e.g., 16 bytes => 128 bits) to see how many bits + * are newly set that we do not have a nonzero counter for. + * This is purely an approximate approach for "new coverage" bits. + */ + int approximateNewCoverageCount(const CoverageFilter &incoming) const; + + /** + * Compare a standard Bloom (bit-based, e.g., 16 bytes => 128 bits) to see how many bits + * are newly set that we do not have a nonzero counter for, vs. total approx. input + * This is purely an approximate approach for "new coverage" ratio. + */ + float approximateCoverageRatio(const CoverageFilter &incoming) const; + + private: + uint32_t instantiationTime_; + + /** + * The storage array, sized for all counters combined. + * e.g. for NUM_UNKNOWN_NODE_COUNTERS=64, BITS_PER_UNKNOWN_NODE_COUNTER=4 => 64*4=256 bits => 256/8=32 bytes. + */ + std::array storage_; + + /** + * Retrieve the integer value of the counter at position idx + * (0 <= idx < NUM_UNKNOWN_NODE_COUNTERS). + */ + uint8_t getCounterValue(size_t idx) const; + + /** + * Set the counter at position idx to val (clamped to max representable). + */ + void setCounterValue(size_t idx, uint8_t val); + + /** + * Returns true if this instance is stale (based on instantiation time). + */ + bool isStale() const; + + /** + * Increment the counter at idx by 1 (clamped to max). + */ + void incrementCounter(size_t idx); + + /** + * Decrement the counter at idx by 1 (if >0). + */ + void decrementCounter(size_t idx); + + void computeHashIndices(NodeNum value, size_t outIndices[BLOOM_HASH_FUNCTIONS]) const; + + size_t hashGeneric(NodeNum value, uint64_t seed) const; +}; \ No newline at end of file diff --git a/src/mesh/CoverageFilter.h b/src/mesh/CoverageFilter.h index f116de762..c16b10940 100644 --- a/src/mesh/CoverageFilter.h +++ b/src/mesh/CoverageFilter.h @@ -1,3 +1,5 @@ +#pragma once + #include "MeshTypes.h" #include #include diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp index d1cc9e75c..97a993bcc 100644 --- a/src/mesh/FloodingRouter.cpp +++ b/src/mesh/FloodingRouter.cpp @@ -76,7 +76,10 @@ bool FloodingRouter::perhapsRebroadcast(const meshtastic_MeshPacket *p) CoverageFilter incomingCoverage; loadCoverageFilterFromPacket(p, incomingCoverage); - float forwardProb = calculateForwardProbability(incomingCoverage, p->from); + CoverageFilter updatedCoverage = incomingCoverage; + mergeMyCoverage(updatedCoverage); + + float forwardProb = calculateForwardProbability(incomingCoverage, updatedCoverage, p->from); float rnd = static_cast(rand()) / static_cast(RAND_MAX); if (rnd <= forwardProb) { @@ -92,8 +95,6 @@ bool FloodingRouter::perhapsRebroadcast(const meshtastic_MeshPacket *p) } #endif - CoverageFilter updatedCoverage = incomingCoverage; - mergeMyCoverage(updatedCoverage); storeCoverageFilterInPacket(updatedCoverage, tosend); LOG_INFO("Rebroadcasting packet ID=0x%x with ForwardProb=%.2f", p->id, forwardProb); @@ -155,9 +156,12 @@ void FloodingRouter::mergeMyCoverage(CoverageFilter &coverage) for (auto &nodeId : recentNeighbors) { coverage.add(nodeId); } + + // Always add ourselves to prevent a rebroadcast for a packet we've already seen + coverage.add(nodeDB->getNodeNum()); } -float FloodingRouter::calculateForwardProbability(const CoverageFilter &incoming, NodeNum from) +float FloodingRouter::calculateForwardProbability(const CoverageFilter &incoming, const CoverageFilter &updated, NodeNum from) { #ifdef USERPREFS_USE_COVERAGE_FILTER bool useCoverageFilter = USERPREFS_USE_COVERAGE_FILTER; @@ -165,8 +169,10 @@ float FloodingRouter::calculateForwardProbability(const CoverageFilter &incoming return 1.0f; #endif // If we are a router or repeater, always forward because it's assumed these are in the most advantageous locations + // or if we haven't heard from any other nodes within the stale coverage time if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER || - config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) { + config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER || + nodeDB->secondsSinceLastNodeHeard() >= STALE_COVERAGE_SECONDS) { return 1.0f; } @@ -200,31 +206,21 @@ float FloodingRouter::calculateForwardProbability(const CoverageFilter &incoming if (neighbors > 0) { coverageRatio = static_cast(uncovered) / static_cast(neighbors); } - float forwardProb = BASE_FORWARD_PROB; - /* BEGIN OPTION 1: forward probability is based on coverage ratio and scales up or down - * depending on the extent to which this node provides new coverage - */ - /* END OPTION 1 */ - forwardProb = BASE_FORWARD_PROB + (coverageRatio * COVERAGE_SCALE_FACTOR); + // Compare our unknown node coverage filter to our updated coverage filter + // We use the updated coverage filter because we don't want to double count nodes + // that have already made it into the main in memory nodedb storage mechanism + float unknownNodeCoverageRatio = nodeDB->getUnknownCoverage().approximateCoverageRatio(updated); - /* BEGIN OPTION 2: forward probability is piecewise logic: - * - Reduce to BASE_FORWARD_PROB if no new coverage is likely - * (remember false positive rate of bloom filter means a node that think its neighbor is covered when it isnt) - * - If 1 uncovered neighbor, ramp probability up significantly to 0.8 - * - If more than 1 uncovered neighbor, ramp probability up to 1.0 - * if (uncovered == 1) { - * forwardProb = 0.8f; - * } else if (uncovered > 1) { - * forwardProb = 1.0f; - * } - */ - /* END OPTION 2 */ + // unknownNodeCoverageRatio is inherently iffy so don't scale up its contribution to the probability of rebroadcast + // This essentially makes the forward probability non-zero for nodes that have a set of "unknown" neighbors + float forwardProb = BASE_FORWARD_PROB + (coverageRatio * COVERAGE_SCALE_FACTOR) + unknownNodeCoverageRatio; // Clamp probability between 0 and 1 forwardProb = std::min(std::max(forwardProb, 0.0f), 1.0f); - LOG_DEBUG("CoverageRatio=%.2f, ForwardProb=%.2f (Uncovered=%d, Total=%zu)", coverageRatio, forwardProb, uncovered, neighbors); + LOG_DEBUG("CoverageRatio=%.2f, UnknownNodeCoverageRatio=%.2f, ForwardProb=%.2f (Uncovered=%d, Total=%zu)", coverageRatio, + unknownNodeCoverageRatio, forwardProb, uncovered, neighbors); return forwardProb; } \ No newline at end of file diff --git a/src/mesh/FloodingRouter.h b/src/mesh/FloodingRouter.h index 93c0e0729..ca3bdc2cf 100644 --- a/src/mesh/FloodingRouter.h +++ b/src/mesh/FloodingRouter.h @@ -42,7 +42,7 @@ class FloodingRouter : public Router, protected PacketHistory void mergeMyCoverage(CoverageFilter &coverage); - float calculateForwardProbability(const CoverageFilter &incoming, NodeNum from); + float calculateForwardProbability(const CoverageFilter &incoming, const CoverageFilter &updated, NodeNum from); public: /** diff --git a/src/mesh/MeshTypes.h b/src/mesh/MeshTypes.h index ac32c7970..07f3eb74a 100644 --- a/src/mesh/MeshTypes.h +++ b/src/mesh/MeshTypes.h @@ -43,15 +43,15 @@ enum RxSource { // Maximum number of neighbors a node adds to the Bloom filter per hop #define MAX_NEIGHBORS_PER_HOP 20 +// Number of seconds after which coverage is considered stale +#define STALE_COVERAGE_SECONDS 60 * 60 * 0.5 // 1 hour + // Size of the Bloom filter in bytes (128 bits) #define BLOOM_FILTER_SIZE_BYTES 16 // Size of the Bloom filter in bits (128 bits) #define BLOOM_FILTER_SIZE_BITS (BLOOM_FILTER_SIZE_BYTES * 8) -// Number of hash functions to use in the Bloom filter -#define NUM_HASH_FUNCTIONS 2 - // Base forwarding probability - never drop below this value // 0.2 could be suitable because the worst case False Positive Rate of the // coverage filter is 37%. That's if its saturated with 60 unique nodes. @@ -64,6 +64,12 @@ enum RxSource { // Currently set to 1 hour because that is the minimum interval for nodeinfo broadcasts #define RECENCY_THRESHOLD_MINUTES 60 +#define NUM_UNKNOWN_NODE_COUNTERS 64 + +#define BITS_PER_UNKNOWN_NODE_COUNTER 4 + +#define BLOOM_HASH_FUNCTIONS 2 + typedef int ErrorCode; /// Alloc and free packets to our global, ISR safe pool diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index b7d567eda..68dfe2a94 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -895,6 +895,22 @@ std::vector NodeDB::getDistinctRecentDirectNeighborIds(uint32_t timeWin return recentNeighbors; } +uint32_t NodeDB::secondsSinceLastNodeHeard() +{ + // If maxLastHeard_ == 0, we have not heard from any remote node + if (maxLastHeard_ == 0) { + return UINT32_MAX; + } + + uint32_t now = getTime(); + // If the clock isn’t set or has jumped backwards, clamp to 0 + if (now < maxLastHeard_) { + return 0; + } + + return (now - maxLastHeard_); +} + void NodeDB::cleanupMeshDB() { int newPos = 0, removed = 0; @@ -907,6 +923,14 @@ void NodeDB::cleanupMeshDB() } meshNodes->at(newPos++) = meshNodes->at(i); } else { + // Check if this unknown node is a direct neighbor (hops_away == 0) + if (meshNodes->at(i).has_hops_away && meshNodes->at(i).hops_away == 0) { + // ADD for unknown coverage: + // If this node doesn't have user data, we consider it "unknown" + // and add it to the unknownCoverage_ filter: + unknownCoverage_.add(meshNodes->at(i).num); + } + removed++; } } @@ -1483,8 +1507,12 @@ void NodeDB::updateFrom(const meshtastic_MeshPacket &mp) return; } - if (mp.rx_time) // if the packet has a valid timestamp use it to update our last_heard + if (mp.rx_time) { // if the packet has a valid timestamp use it to update our last_heard info->last_heard = mp.rx_time; + if (info->last_heard > maxLastHeard_) { + maxLastHeard_ = info->last_heard; + } + } if (mp.rx_snr) info->snr = mp.rx_snr; // keep the most recent SNR we received for this node. diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h index e1444f82d..0ec6c0a68 100644 --- a/src/mesh/NodeDB.h +++ b/src/mesh/NodeDB.h @@ -6,6 +6,7 @@ #include #include +#include "CountingCoverageFilter.h" #include "MeshTypes.h" #include "NodeStatus.h" #include "configuration.h" @@ -176,8 +177,15 @@ class NodeDB */ std::vector getDistinctRecentDirectNeighborIds(uint32_t timeWindowSecs); + uint32_t secondsSinceLastNodeHeard(); + + const CountingCoverageFilter &getUnknownCoverage() const { return unknownCoverage_; } + private: uint32_t lastNodeDbSave = 0; // when we last saved our db to flash + uint32_t maxLastHeard_ = 0; // the most recent last_heard value we've seen + CountingCoverageFilter unknownCoverage_; + /// Find a node in our DB, create an empty NodeInfoLite if missing meshtastic_NodeInfoLite *getOrCreateMeshNode(NodeNum n); diff --git a/src/mesh/RadioInterface.h b/src/mesh/RadioInterface.h index 74aa00e37..6d6065b4a 100644 --- a/src/mesh/RadioInterface.h +++ b/src/mesh/RadioInterface.h @@ -45,7 +45,7 @@ typedef struct { uint8_t relay_node; // A 16-byte Bloom filter that tracks coverage of the current node. - uint8_t coverage_filter[BLOOM_FILTER_SIZE_BYTES] __attribute__((__aligned)); + uint8_t coverage_filter[BLOOM_FILTER_SIZE_BYTES] __attribute__((__aligned__)); } PacketHeader; /**