Files
HaloKeymind/test/test_mesh_tables/test_simple_mesh_tables.cpp
T
mikecarper 4b3e419eb0 Reduce shared RAM usage without lowering firmware capacities
Specialize repeater ACL session state, compact recent and flood records, stream Merkle proofs, and release cold OTA buffers safely. Preserve saved formats, stack sizes, table capacities, and OTA source limits. Account for lazy buffers when active and add compatibility, ARM alignment, allocation failure, and queued-response tests.

Validated six representative board/role builds, 1736 native test cases, sanitizer fixtures, and strict documentation. Document the preexisting Wio-E5 Mini flash overflow separately.
2026-10-08 14:41:16 -07:00

528 lines
20 KiB
C++

#include <gtest/gtest.h>
#include <cstring>
#include <new>
#include <type_traits>
#define MAX_RECENT_REPEATERS 8
#include "helpers/SimpleMeshTables.h"
using namespace mesh;
TEST(SimpleMeshTables, CompactRecentRowsPreserveFullMillisAtEveryByteAlignment) {
using Info = SimpleMeshTables::RecentRepeaterInfo;
static_assert(sizeof(Info) == 9 && alignof(Info) == 1, "compact row layout");
static_assert(std::is_trivially_copyable<Info>::value, "memset/cursor copy support");
constexpr uint32_t values[] = {0, 1, 0x0000ffff, 0x12345678, 0x80000000,
0xfffffffe, 0xffffffff};
for (size_t offset = 0; offset < alignof(uint32_t); ++offset) {
alignas(uint32_t) unsigned char bytes[sizeof(Info) + alignof(uint32_t)] = {};
Info* info = new (bytes + offset) Info{};
info->prefix[0] = 0x86;
info->prefix[1] = 0x0c;
info->prefix[2] = 0xca;
info->prefix_len = 3;
info->snr_x4 = -128;
for (uint32_t value : values) {
info->last_heard_millis = value;
EXPECT_EQ(value, uint32_t(info->last_heard_millis));
Info cursor;
SimpleMeshTables::copyRecentRepeaterInfo(cursor, *info);
EXPECT_EQ(value, uint32_t(cursor.last_heard_millis));
EXPECT_EQ(0x86, info->prefix[0]);
EXPECT_EQ(0x0c, info->prefix[1]);
EXPECT_EQ(0xca, info->prefix[2]);
EXPECT_EQ(3, info->prefix_len);
EXPECT_EQ(-128, info->snr_x4);
}
SimpleMeshTables::clearRecentRepeaterInfo(*info);
EXPECT_EQ(0u, uint32_t(info->last_heard_millis));
}
}
TEST(SimpleMeshTables, CompactRecentPrefixCopiesStayWithinThreeBytes) {
using Info = SimpleMeshTables::RecentRepeaterInfo;
alignas(uint32_t) unsigned char bytes[sizeof(Info) + alignof(uint32_t)] = {};
for (size_t offset = 0; offset < alignof(uint32_t); ++offset) {
Info* info = new (bytes + offset) Info{};
info->prefix[0] = 0x86;
info->prefix[1] = 0x0c;
info->prefix[2] = 0xca;
info->prefix_len = 3;
unsigned char out[] = {0xa5, 0xa5, 0xa5, 0xa5, 0xa5};
SimpleMeshTables::copyRecentRepeaterPrefix(out + 1, *info);
EXPECT_EQ(0xa5, out[0]);
EXPECT_EQ(0x86, out[1]);
EXPECT_EQ(0x0c, out[2]);
EXPECT_EQ(0xca, out[3]);
EXPECT_EQ(0xa5, out[4]);
EXPECT_EQ(0, routeHashPrefixBytesCompare(info->prefix, out + 1, 3));
out[3] = 0xc9;
EXPECT_GT(routeHashPrefixBytesCompare(info->prefix, out + 1, 3), 0);
EXPECT_LT(routeHashPrefixBytesCompare(out + 1, info->prefix, 3), 0);
}
}
TEST(SimpleMeshTables, CompactRecentExpiryPreservesUnsignedMillisRollover) {
SimpleMeshTables::RecentRepeaterInfo storage[3];
SimpleMeshTables table(storage, 3);
const uint8_t older[] = {0x10, 0x20, 0x30};
const uint8_t boundary[] = {0x11, 0x21, 0x31};
const uint8_t fresh[] = {0x12, 0x22, 0x32};
ASSERT_TRUE(table.setRecentRepeater(older, 3, 12));
ASSERT_TRUE(table.setRecentRepeater(boundary, 3, 8));
ASSERT_TRUE(table.setRecentRepeater(fresh, 3, 4));
storage[0].last_heard_millis = 0xffffffe0;
storage[1].last_heard_millis = 0xfffffff0;
storage[2].last_heard_millis = 8;
EXPECT_EQ(1, table.expireRecentRepeaters(16, 32));
EXPECT_EQ(nullptr, table.findRecentRepeaterByHash(older, 3));
ASSERT_NE(nullptr, table.findRecentRepeaterByHash(boundary, 3));
ASSERT_NE(nullptr, table.findRecentRepeaterByHash(fresh, 3));
EXPECT_EQ(0xfffffff0u, uint32_t(table.findRecentRepeaterByHash(boundary, 3)->last_heard_millis));
EXPECT_EQ(8u, uint32_t(table.findRecentRepeaterByHash(fresh, 3)->last_heard_millis));
EXPECT_EQ(2, table.getRecentRepeaterCount());
EXPECT_EQ(0u, uint32_t(storage[2].last_heard_millis));
}
// Build a packet that calculatePacketHash() distinguishes by payload content.
// header selects ROUTE_TYPE_FLOOD so isRouteDirect() returns false.
static Packet makeFloodPacket(uint8_t seed) {
Packet p;
p.header = ROUTE_TYPE_FLOOD | (PAYLOAD_TYPE_RAW_CUSTOM << PH_TYPE_SHIFT);
p.payload[0] = seed;
p.payload_len = 1;
p.path_len = 0;
return p;
}
static Packet makeDirectPacket(uint8_t seed) {
Packet p;
p.header = ROUTE_TYPE_DIRECT | (PAYLOAD_TYPE_RAW_CUSTOM << PH_TYPE_SHIFT);
p.payload[0] = seed;
p.payload_len = 1;
p.path_len = 0;
return p;
}
static Packet makeAckPacket(uint32_t crc, bool direct = true) {
Packet p;
p.header = (direct ? ROUTE_TYPE_DIRECT : ROUTE_TYPE_FLOOD)
| (PAYLOAD_TYPE_ACK << PH_TYPE_SHIFT);
memcpy(p.payload, &crc, sizeof(crc));
p.payload_len = sizeof(crc);
p.path_len = 0;
return p;
}
static Packet makeMultipartAckPacket(uint32_t crc, uint8_t remaining = 1, bool direct = true) {
Packet p;
p.header = (direct ? ROUTE_TYPE_DIRECT : ROUTE_TYPE_FLOOD)
| (PAYLOAD_TYPE_MULTIPART << PH_TYPE_SHIFT);
p.payload[0] = (remaining << 4) | PAYLOAD_TYPE_ACK;
memcpy(&p.payload[1], &crc, sizeof(crc));
p.payload_len = sizeof(crc) + 1;
p.path_len = 0;
return p;
}
// -- wasSeen: pure query -------------------------------------------------------
TEST(SimpleMeshTables, WasSeen_ReturnsFalseForUnseen) {
SimpleMeshTables t;
Packet p = makeFloodPacket(0x01);
EXPECT_FALSE(t.wasSeen(&p));
}
// wasSeen shouldn't change state
TEST(SimpleMeshTables, WasSeen_IsPureQuery_DoesNotInsert) {
SimpleMeshTables t;
Packet p = makeFloodPacket(0x01);
EXPECT_FALSE(t.wasSeen(&p));
EXPECT_FALSE(t.wasSeen(&p));
}
// -- markSeen + wasSeen -------------------------------------------------------
TEST(SimpleMeshTables, MarkSeen_MakesWasSeenReturnTrue) {
SimpleMeshTables t;
Packet p = makeFloodPacket(0x01);
t.markSeen(&p);
EXPECT_TRUE(t.wasSeen(&p));
}
TEST(SimpleMeshTables, MarkSeen_DoesNotAffectOtherPackets) {
SimpleMeshTables t;
Packet p1 = makeFloodPacket(0x01);
Packet p2 = makeFloodPacket(0x02);
t.markSeen(&p1);
EXPECT_FALSE(t.wasSeen(&p2));
}
TEST(SimpleMeshTables, TransportScopeDoesNotChangeDuplicateIdentity) {
SimpleMeshTables t;
Packet unscoped = makeFloodPacket(0x35);
Packet scoped = unscoped;
scoped.header = (scoped.header & (uint8_t)~PH_ROUTE_MASK) | ROUTE_TYPE_TRANSPORT_FLOOD;
scoped.transport_codes[0] = 0x1234;
scoped.transport_codes[1] = 0x5678;
t.markSeen(&unscoped);
EXPECT_TRUE(t.wasSeen(&scoped));
}
// Canonical pattern used at every onRecvPacket call site:
// if (!wasSeen(pkt)) { markSeen(pkt); process(pkt); }
TEST(SimpleMeshTables, QueryThenMark_WorksCorrectly) {
SimpleMeshTables t;
Packet p = makeFloodPacket(0x01);
EXPECT_FALSE(t.wasSeen(&p));
t.markSeen(&p);
EXPECT_TRUE(t.wasSeen(&p));
}
// -- dup stats ----------------------------------------------------------------
TEST(SimpleMeshTables, WasSeen_IncrementsFloodDupStat) {
SimpleMeshTables t;
Packet p = makeFloodPacket(0x01);
t.markSeen(&p);
t.wasSeen(&p);
EXPECT_EQ(1u, t.getNumFloodDups());
EXPECT_EQ(0u, t.getNumDirectDups());
}
TEST(SimpleMeshTables, WasSeen_IncrementsDirectDupStat) {
SimpleMeshTables t;
Packet p = makeDirectPacket(0x01);
t.markSeen(&p);
t.wasSeen(&p);
EXPECT_EQ(0u, t.getNumFloodDups());
EXPECT_EQ(1u, t.getNumDirectDups());
}
// -- clear --------------------------------------------------------------------
TEST(SimpleMeshTables, Clear_RemovesSeenPacket) {
SimpleMeshTables t;
Packet p = makeFloodPacket(0x01);
t.markSeen(&p);
ASSERT_TRUE(t.wasSeen(&p));
t.clear(&p);
EXPECT_FALSE(t.wasSeen(&p));
}
TEST(SimpleMeshTables, AckCrcZeroIsAValidUnseenValue) {
SimpleMeshTables t;
Packet p = makeAckPacket(0);
EXPECT_FALSE(t.wasSeen(&p));
t.markSeen(&p);
EXPECT_TRUE(t.wasSeen(&p));
}
TEST(SimpleMeshTables, AckDedupUsesTheCompletePayloadIdentity) {
SimpleMeshTables t;
Packet first = makeAckPacket(0xC3B2A141);
Packet repeated = first;
Packet different_attempt = first;
first.payload[4] = 0x10;
first.payload[5] = 0x20;
first.payload_len = 6;
repeated.payload[4] = 0x10;
repeated.payload[5] = 0x20;
repeated.payload_len = 6;
different_attempt.payload[4] = 0x14;
different_attempt.payload[5] = 0x20;
different_attempt.payload_len = 6;
t.markSeen(&first);
EXPECT_TRUE(t.wasSeen(&repeated));
EXPECT_FALSE(t.wasSeen(&different_attempt));
}
TEST(SimpleMeshTables, AckTrafficDoesNotEvictGeneralPacketHashes) {
SimpleMeshTables t;
Packet retained = makeFloodPacket(0x5A);
t.markSeen(&retained);
for (int i = 0; i < MAX_PACKET_HASHES + 1; i++) {
Packet ack = makeAckPacket((uint32_t)i);
t.markSeen(&ack);
}
EXPECT_TRUE(t.wasSeen(&retained));
}
TEST(SimpleMeshTables, MultipartAckTrafficDoesNotEvictGeneralPacketHashes) {
SimpleMeshTables t;
Packet retained = makeFloodPacket(0x5B);
t.markSeen(&retained);
for (int i = 0; i < MAX_PACKET_HASHES + 1; i++) {
Packet ack = makeMultipartAckPacket((uint32_t)i, (uint8_t)(i & 0x0F));
t.markSeen(&ack);
}
EXPECT_TRUE(t.wasSeen(&retained));
}
TEST(SimpleMeshTables, NormalAndMultipartAckTransmissionsAreDistinct) {
SimpleMeshTables t;
Packet normal = makeAckPacket(0x10203040);
Packet multipart = makeMultipartAckPacket(0x10203040);
t.markSeen(&normal);
EXPECT_FALSE(t.wasSeen(&multipart));
t.markSeen(&multipart);
EXPECT_TRUE(t.wasSeen(&multipart));
EXPECT_TRUE(t.wasSeen(&normal));
}
TEST(SimpleMeshTables, MultipartRemainingCountIsPartOfItsIdentity) {
SimpleMeshTables t;
Packet first = makeMultipartAckPacket(0x55667788, 1);
Packet second = makeMultipartAckPacket(0x55667788, 0);
t.markSeen(&first);
EXPECT_FALSE(t.wasSeen(&second));
}
TEST(SimpleMeshTables, ShortAckPayloadFallsBackToSafeGeneralDedup) {
SimpleMeshTables t;
Packet p = makeAckPacket(0x7B);
p.payload_len = 1;
EXPECT_FALSE(t.wasSeen(&p));
t.markSeen(&p);
EXPECT_TRUE(t.wasSeen(&p));
t.clear(&p);
EXPECT_FALSE(t.wasSeen(&p));
}
TEST(RouteHashPrefixes, MatchesSharedOneTwoOrThreeBytes) {
const uint8_t configured[] = {0x86, 0x0c, 0xca};
const uint8_t one_byte[] = {0x86};
const uint8_t two_bytes[] = {0x86, 0x0c};
const uint8_t three_bytes[] = {0x86, 0x0c, 0xca};
const uint8_t mismatch[] = {0x86, 0x0d};
EXPECT_TRUE(routeHashPrefixesOverlap(configured, 3, one_byte, 1));
EXPECT_TRUE(routeHashPrefixesOverlap(configured, 3, two_bytes, 2));
EXPECT_TRUE(routeHashPrefixesOverlap(configured, 3, three_bytes, 3));
EXPECT_TRUE(routeHashPrefixesOverlap(one_byte, 1, configured, 3));
EXPECT_FALSE(routeHashPrefixesOverlap(configured, 3, mismatch, 2));
}
TEST(SimpleMeshTables, RecentRepeaterSearchReturnsAllOverlappingPrefixesInDisplayOrder) {
SimpleMeshTables::RecentRepeaterInfo storage[6];
SimpleMeshTables t(storage, 6);
const uint8_t one_byte[] = {0x86};
const uint8_t two_bytes[] = {0x86, 0x0c};
const uint8_t three_bytes[] = {0x86, 0x0c, 0xca};
const uint8_t other_branch[] = {0x86, 0xd0};
const uint8_t unrelated[] = {0x71, 0xce, 0x82};
ASSERT_TRUE(t.setRecentRepeater(one_byte, 1, 20));
ASSERT_TRUE(t.setRecentRepeater(two_bytes, 2, 4));
ASSERT_TRUE(t.setRecentRepeater(three_bytes, 3, 8));
ASSERT_TRUE(t.setRecentRepeater(other_branch, 2, 12));
ASSERT_TRUE(t.setRecentRepeater(unrelated, 3, 16));
EXPECT_EQ(3, t.getRecentRepeaterMatchingCount(two_bytes, 2));
const auto* first = t.getRecentRepeaterMatchingBySortedIdx(two_bytes, 2, 0);
const auto* second = t.getRecentRepeaterMatchingBySortedIdx(two_bytes, 2, 1);
const auto* third = t.getRecentRepeaterMatchingBySortedIdx(two_bytes, 2, 2);
ASSERT_NE(nullptr, first);
ASSERT_NE(nullptr, second);
ASSERT_NE(nullptr, third);
EXPECT_EQ(3, first->prefix_len);
EXPECT_EQ(0, memcmp(three_bytes, first->prefix, 3));
EXPECT_EQ(2, second->prefix_len);
EXPECT_EQ(0, memcmp(two_bytes, second->prefix, 2));
EXPECT_EQ(1, third->prefix_len);
EXPECT_EQ(0, memcmp(one_byte, third->prefix, 1));
EXPECT_EQ(nullptr,
t.getRecentRepeaterMatchingBySortedIdx(two_bytes, 2, 3));
// A one-byte search also includes both two-byte branches sharing it.
EXPECT_EQ(4, t.getRecentRepeaterMatchingCount(one_byte, 1));
const auto* branch =
t.getRecentRepeaterMatchingBySortedIdx(one_byte, 1, 1);
ASSERT_NE(nullptr, branch);
EXPECT_EQ(0, memcmp(other_branch, branch->prefix, 2));
}
TEST(SimpleMeshTables, ShortFailurePrefixUpdatesOverlappingLongEntry) {
SimpleMeshTables::RecentRepeaterInfo storage[MAX_RECENT_REPEATERS];
SimpleMeshTables t(storage, MAX_RECENT_REPEATERS);
const uint8_t configured[] = {0x86, 0x0c, 0xca};
const uint8_t observed[] = {0x86};
ASSERT_TRUE(t.setRecentRepeater(configured, 3, 12));
ASSERT_TRUE(t.decrementRecentRepeaterSnrX4(observed, 1, 1));
const auto* info = t.findRecentRepeaterByHash(configured, 3);
ASSERT_NE(nullptr, info);
EXPECT_EQ(11, info->snr_x4);
EXPECT_EQ(1, t.getRecentRepeaterCount());
}
TEST(SimpleMeshTables, RecentRepeatersExpireOnlyAfterTwentyFourHours) {
SimpleMeshTables::RecentRepeaterInfo storage[MAX_RECENT_REPEATERS];
SimpleMeshTables t(storage, MAX_RECENT_REPEATERS);
const uint8_t first[] = {0x86, 0x0c, 0xca};
const uint8_t second[] = {0x71, 0xce, 0x82};
constexpr uint32_t twenty_four_hours = 24UL * 60UL * 60UL * 1000UL;
ASSERT_TRUE(t.setRecentRepeater(first, 3, 12));
ASSERT_TRUE(t.setRecentRepeater(second, 3, 8));
EXPECT_EQ(0, t.expireRecentRepeaters(twenty_four_hours, twenty_four_hours));
EXPECT_EQ(2, t.getRecentRepeaterCount());
EXPECT_EQ(2, t.expireRecentRepeaters(twenty_four_hours + 1, twenty_four_hours));
EXPECT_EQ(0, t.getRecentRepeaterCount());
EXPECT_EQ(nullptr, t.findRecentRepeaterByHash(first, 3));
}
TEST(SimpleMeshTables, FullRecentRepeaterTableStillEvictsDeterministically) {
SimpleMeshTables::RecentRepeaterInfo storage[2];
SimpleMeshTables t(storage, 2);
const uint8_t first[] = {0x10};
const uint8_t second[] = {0x20};
const uint8_t replacement[] = {0x30};
ASSERT_TRUE(t.setRecentRepeater(first, 1, 4));
ASSERT_TRUE(t.setRecentRepeater(second, 1, 8));
ASSERT_TRUE(t.setRecentRepeater(replacement, 1, 12));
EXPECT_EQ(nullptr, t.findRecentRepeaterByHash(first, 1));
EXPECT_NE(nullptr, t.findRecentRepeaterByHash(second, 1));
EXPECT_NE(nullptr, t.findRecentRepeaterByHash(replacement, 1));
EXPECT_EQ(2, t.getRecentRepeaterCount());
}
TEST(SimpleMeshTables, ExpirationKeepsOccupiedEntriesPacked) {
SimpleMeshTables::RecentRepeaterInfo storage[4];
SimpleMeshTables t(storage, 4);
const uint8_t first[] = {0x10};
const uint8_t expired[] = {0x20};
const uint8_t third[] = {0x30};
const uint8_t added[] = {0x40};
ASSERT_TRUE(t.setRecentRepeater(first, 1, 4));
ASSERT_TRUE(t.setRecentRepeater(expired, 1, 8));
ASSERT_TRUE(t.setRecentRepeater(third, 1, 12));
storage[0].last_heard_millis = 100;
storage[1].last_heard_millis = 0;
storage[2].last_heard_millis = 100;
EXPECT_EQ(1, t.expireRecentRepeaters(101, 50));
EXPECT_EQ(2, t.getRecentRepeaterCount());
EXPECT_NE(nullptr, t.findRecentRepeaterByHash(first, 1));
EXPECT_EQ(nullptr, t.findRecentRepeaterByHash(expired, 1));
EXPECT_NE(nullptr, t.findRecentRepeaterByHash(third, 1));
ASSERT_TRUE(t.setRecentRepeater(added, 1, 16));
EXPECT_EQ(3, t.getRecentRepeaterCount());
EXPECT_NE(nullptr, t.findRecentRepeaterByHash(added, 1));
EXPECT_EQ(0, storage[3].prefix_len);
}
TEST(SimpleMeshTables, CooperativeRecentCursorMatchesUnchangedRankOrder) {
SimpleMeshTables::RecentRepeaterInfo storage[8];
SimpleMeshTables t(storage, 8);
for (int i = 0; i < 8; ++i) {
const uint8_t prefix[] = {static_cast<uint8_t>(0x10 + i), 0x22, 0x33};
ASSERT_TRUE(t.setRecentRepeater(prefix, 1 + i % 3, (i / 2) * 4));
}
SimpleMeshTables::RecentRepeaterInfo cursor{};
int cursor_index = -1;
for (int rank = 0; rank < 8; ++rank) {
int found_index = -1;
const auto* found = t.getNextRecentRepeaterBySortKey(
rank == 0 ? nullptr : &cursor, cursor_index, found_index);
ASSERT_NE(nullptr, found);
EXPECT_EQ(t.getRecentRepeaterBySortedIdx(rank), found);
SimpleMeshTables::copyRecentRepeaterInfo(cursor, *found);
cursor_index = found_index;
}
int found_index = 123;
EXPECT_EQ(nullptr, t.getNextRecentRepeaterBySortKey(&cursor, cursor_index, found_index));
EXPECT_EQ(-1, found_index);
}
TEST(SimpleMeshTables, CooperativeRecentCursorSurvivesLiveStorageCompaction) {
SimpleMeshTables::RecentRepeaterInfo storage[3];
SimpleMeshTables t(storage, 3);
const uint8_t first[] = {0x10};
const uint8_t second[] = {0x20};
const uint8_t third[] = {0x30};
ASSERT_TRUE(t.setRecentRepeater(first, 1, 12));
ASSERT_TRUE(t.setRecentRepeater(second, 1, 8));
ASSERT_TRUE(t.setRecentRepeater(third, 1, 4));
int cursor_index = -1;
const auto* first_row = t.getNextRecentRepeaterBySortKey(nullptr, -1, cursor_index);
ASSERT_NE(nullptr, first_row);
SimpleMeshTables::RecentRepeaterInfo cursor;
SimpleMeshTables::copyRecentRepeaterInfo(cursor, *first_row);
storage[0].last_heard_millis = 0;
storage[1].last_heard_millis = 100;
storage[2].last_heard_millis = 100;
ASSERT_EQ(1, t.expireRecentRepeaters(101, 50));
int next_index = -1;
const auto* next = t.getNextRecentRepeaterBySortKey(&cursor, cursor_index, next_index);
ASSERT_NE(nullptr, next);
EXPECT_EQ(0x20, next->prefix[0]);
EXPECT_EQ(0x10, cursor.prefix[0]);
}
TEST(SimpleMeshTables, CooperativeRecentCursorTraverses2048Rows) {
SimpleMeshTables::RecentRepeaterInfo storage[2048];
SimpleMeshTables t(storage, 2048);
for (int i = 0; i < 2048; ++i) {
const uint8_t prefix[] = {0x80, static_cast<uint8_t>(i >> 8),
static_cast<uint8_t>(i)};
ASSERT_TRUE(t.setRecentRepeater(prefix, 3, 4));
}
SimpleMeshTables::RecentRepeaterInfo cursor{};
int cursor_index = -1;
for (int i = 0; i < 2048; ++i) {
int found_index = -1;
const auto* found = t.getNextRecentRepeaterBySortKey(
i == 0 ? nullptr : &cursor, cursor_index, found_index);
ASSERT_NE(nullptr, found);
EXPECT_EQ(static_cast<uint8_t>(i >> 8), found->prefix[1]);
EXPECT_EQ(static_cast<uint8_t>(i), found->prefix[2]);
SimpleMeshTables::copyRecentRepeaterInfo(cursor, *found);
cursor_index = found_index;
}
EXPECT_EQ(nullptr, t.getNextRecentRepeaterBySortKey(&cursor, cursor_index, cursor_index));
}
TEST(SimpleMeshTables, LegacyOtaDirectCacheTurnoverEvictsStaleHashWithoutFlooding) {
SimpleMeshTables table;
auto stale = makeFloodPacket(0x44);
stale.header = ROUTE_TYPE_FLOOD | (PAYLOAD_TYPE_OTA << PH_TYPE_SHIFT);
table.markSeen(&stale);
ASSERT_TRUE(table.wasSeen(&stale));
int received = 0;
for (unsigned i = 0; i < 192; ++i) {
if (i % 6 == 0) continue; // 32 lost frames; remaining 160 replace the FIFO
const uint8_t raw[] = {0x32, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8,
(uint8_t)i, (uint8_t)(i >> 8)};
Packet packet;
ASSERT_TRUE(packet.readFrom(raw, sizeof(raw)));
ASSERT_TRUE(packet.isRouteDirect());
ASSERT_EQ(PAYLOAD_TYPE_OTA, packet.getPayloadType());
ASSERT_EQ(0, packet.getPathHashCount());
ASSERT_EQ(0, packet.payload[0]); // unknown opcode; no manager state mutation
table.markSeen(&packet);
++received;
}
EXPECT_EQ(MAX_PACKET_HASHES, received);
EXPECT_FALSE(table.wasSeen(&stale));
}
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}