Files
HaloKeymind/test/test_packet_manager/test_rx_reserve_packet_manager.cpp
T

1319 lines
41 KiB
C++

#include <gtest/gtest.h>
#include <helpers/RxReservePacketManager.h>
#include <helpers/RepeaterRadioTiming.h>
class TestClock : public mesh::MillisecondClock {
public:
unsigned long now = 0;
unsigned long getMillis() override { return now; }
};
class TestRadio : public mesh::Radio {
public:
int begins = 0;
uint8_t pending_rx[MAX_TRANS_UNIT];
int pending_rx_len = 0;
int send_starts = 0;
bool receiving = false;
bool in_recv_mode = true;
int soft_recoveries = 0;
int hard_recoveries = 0;
int cad_set_calls = 0;
int agc_resets = 0;
bool cad_enabled = false;
unsigned long last_irq = 0;
bool recovery_result = true;
bool send_complete = false;
bool start_success = true;
int send_finishes = 0;
uint8_t sent_raw[3][MAX_TRANS_UNIT] = {};
int sent_len[3] = {};
void begin() override { begins++; }
int recvRaw(uint8_t* dest, int max_len) override {
if (pending_rx_len == 0) return 0;
int len = pending_rx_len < max_len ? pending_rx_len : max_len;
memcpy(dest, pending_rx, len);
pending_rx_len = 0;
return len;
}
uint32_t getEstAirtimeFor(int) override { return 1; }
float packetScore(float, int) override { return 0; }
bool startSendRaw(const uint8_t* bytes, int len) override {
if (send_starts < 3) {
sent_len[send_starts] = len;
memcpy(sent_raw[send_starts], bytes, len);
}
send_starts++;
return start_success;
}
bool isSendComplete() override { return send_complete; }
void onSendFinished() override { send_finishes++; }
bool isInRecvMode() const override { return in_recv_mode; }
bool isReceiving() override { return receiving; }
void setCADEnabled(bool enable) override {
cad_set_calls++;
cad_enabled = enable;
}
void resetAGC() override { agc_resets++; }
unsigned long getLastRadioInterruptMillis() const override { return last_irq; }
bool recoverRadio(bool hard) override {
if (hard) {
hard_recoveries++;
} else {
soft_recoveries++;
}
return recovery_result;
}
void queueRx(const uint8_t* raw, int len) {
ASSERT_NE(raw, nullptr);
ASSERT_GT(len, 0);
ASSERT_LE(len, MAX_TRANS_UNIT);
memcpy(pending_rx, raw, len);
pending_rx_len = len;
}
};
class TestDispatcher : public mesh::Dispatcher {
RxReservePacketManager& manager;
protected:
mesh::DispatcherAction onRecvPacket(mesh::Packet*) override {
received_packets++;
return ACTION_RELEASE;
}
int calcRxDelay(float score, uint32_t air_time) const override {
return forced_rx_delay >= 0
? forced_rx_delay
: mesh::Dispatcher::calcRxDelay(score, air_time);
}
bool shouldBypassRxDelay(const mesh::Packet*) override {
return bypass_rx_delay;
}
bool getCADEnabled() const override {
return configured_cad_enabled;
}
void onSendFail(mesh::Packet* packet) override {
failed_packet = packet;
free_count_during_failure = manager.getFreeCount();
}
void onSendComplete(mesh::Packet* packet) override {
completed_packet = packet;
completed_packets++;
}
void logRxRaw(float, float, const uint8_t[], int) override {
raw_receive_logs++;
}
void logRx(mesh::Packet*, int, float) override {
parsed_receive_logs++;
}
public:
mesh::Packet* failed_packet = nullptr;
mesh::Packet* completed_packet = nullptr;
int completed_packets = 0;
int free_count_during_failure = -1;
int received_packets = 0;
int raw_receive_logs = 0;
int parsed_receive_logs = 0;
int forced_rx_delay = -1;
bool bypass_rx_delay = false;
bool configured_cad_enabled = false;
int configured_agc_interval = 0;
float ota_speed = 1.0f;
float getOtaSpeedFactor() const override { return ota_speed; }
int getAGCResetInterval() const override {
return configured_agc_interval;
}
TestDispatcher(TestRadio& radio, TestClock& clock, RxReservePacketManager& mgr)
: mesh::Dispatcher(radio, clock, mgr), manager(mgr) { }
bool nextQueueWakeDelay(uint32_t& delay_millis) const {
return getNextQueueWakeDelay(delay_millis);
}
bool queuedWorkDue() const { return hasQueuedWorkDue(); }
bool parse(mesh::Packet* packet, const uint8_t* raw, int len) {
return tryParsePacket(packet, raw, len);
}
void makeRadioAvailable(bool available) { setRadioAvailable(available); }
};
TEST(Dispatcher, UnavailableRadioDoesNotBlockStartupAndRejectsSends) {
RxReservePacketManager manager(4, 1);
TestClock clock;
TestRadio radio;
TestDispatcher dispatcher(radio, clock, manager);
dispatcher.makeRadioAvailable(false);
dispatcher.begin();
dispatcher.loop();
EXPECT_EQ(0, radio.begins);
EXPECT_EQ(0, radio.cad_set_calls);
mesh::Packet* packet = dispatcher.obtainNewPacket();
ASSERT_NE(packet, nullptr);
packet->header = ROUTE_TYPE_DIRECT | (PAYLOAD_TYPE_RAW_CUSTOM << PH_TYPE_SHIFT);
packet->payload[0] = 0x42;
packet->payload_len = 1;
EXPECT_FALSE(dispatcher.sendPacket(packet, 0));
EXPECT_EQ(4, manager.getFreeCount());
EXPECT_EQ(packet, dispatcher.failed_packet);
dispatcher.makeRadioAvailable(true);
EXPECT_EQ(1, radio.begins);
clock.now = 1;
dispatcher.loop();
EXPECT_EQ(1, radio.cad_set_calls);
}
TEST(Packet, ReadFromRejectsTruncatedHeadersAndPaths) {
mesh::Packet packet;
const uint8_t one_byte[] = {ROUTE_TYPE_DIRECT | (PAYLOAD_TYPE_RAW_CUSTOM << PH_TYPE_SHIFT)};
EXPECT_FALSE(packet.readFrom(one_byte, sizeof(one_byte)));
const uint8_t short_transport[] = {
ROUTE_TYPE_TRANSPORT_DIRECT | (PAYLOAD_TYPE_RAW_CUSTOM << PH_TYPE_SHIFT), 0
};
EXPECT_FALSE(packet.readFrom(short_transport, sizeof(short_transport)));
const uint8_t missing_path_byte[] = {
ROUTE_TYPE_DIRECT | (PAYLOAD_TYPE_RAW_CUSTOM << PH_TYPE_SHIFT), 1
};
EXPECT_FALSE(packet.readFrom(missing_path_byte, sizeof(missing_path_byte)));
}
TEST(Packet, ReadFromAcceptsACompletePacketWithEmptyPayload) {
mesh::Packet packet;
const uint8_t raw[] = {
ROUTE_TYPE_DIRECT | (PAYLOAD_TYPE_RAW_CUSTOM << PH_TYPE_SHIFT), 0
};
ASSERT_TRUE(packet.readFrom(raw, sizeof(raw)));
EXPECT_EQ(0U, packet.path_len);
EXPECT_EQ(0U, packet.payload_len);
}
TEST(Dispatcher, ParserRejectsTruncatedHeadersAndPaths) {
RxReservePacketManager manager(4, 1);
TestClock clock;
TestRadio radio;
TestDispatcher dispatcher(radio, clock, manager);
mesh::Packet packet;
const uint8_t one_byte[] = {ROUTE_TYPE_DIRECT | (PAYLOAD_TYPE_RAW_CUSTOM << PH_TYPE_SHIFT)};
EXPECT_FALSE(dispatcher.parse(&packet, one_byte, sizeof(one_byte)));
const uint8_t short_transport[] = {
ROUTE_TYPE_TRANSPORT_DIRECT | (PAYLOAD_TYPE_RAW_CUSTOM << PH_TYPE_SHIFT), 0
};
EXPECT_FALSE(dispatcher.parse(&packet, short_transport, sizeof(short_transport)));
const uint8_t missing_path_byte[] = {
ROUTE_TYPE_DIRECT | (PAYLOAD_TYPE_RAW_CUSTOM << PH_TYPE_SHIFT), 1
};
EXPECT_FALSE(dispatcher.parse(&packet, missing_path_byte, sizeof(missing_path_byte)));
}
TEST(Dispatcher, ParserAcceptsACompletePacketWithEmptyPayload) {
RxReservePacketManager manager(4, 1);
TestClock clock;
TestRadio radio;
TestDispatcher dispatcher(radio, clock, manager);
mesh::Packet packet;
const uint8_t raw[] = {
ROUTE_TYPE_DIRECT | (PAYLOAD_TYPE_RAW_CUSTOM << PH_TYPE_SHIFT), 0
};
ASSERT_TRUE(dispatcher.parse(&packet, raw, sizeof(raw)));
EXPECT_EQ(0U, packet.path_len);
EXPECT_EQ(0U, packet.payload_len);
}
TEST(Dispatcher, FloodPacketWaitsForConfiguredRxDelayWithoutBypass) {
RxReservePacketManager manager(4, 1);
TestClock clock;
TestRadio radio;
TestDispatcher dispatcher(radio, clock, manager);
dispatcher.forced_rx_delay = 1000;
dispatcher.begin();
const uint8_t raw[] = {
ROUTE_TYPE_FLOOD | (PAYLOAD_TYPE_GRP_DATA << PH_TYPE_SHIFT), 0, 0x42
};
clock.now = 100;
radio.queueRx(raw, sizeof(raw));
dispatcher.loop();
EXPECT_EQ(0, dispatcher.received_packets);
clock.now = 1099;
dispatcher.loop();
EXPECT_EQ(0, dispatcher.received_packets);
clock.now = 1100;
dispatcher.loop();
EXPECT_EQ(1, dispatcher.received_packets);
}
TEST(Dispatcher, FloodTraceAndControlAreDroppedBeforeLogsAndRouting) {
RxReservePacketManager manager(4, 1);
TestClock clock;
TestRadio radio;
TestDispatcher dispatcher(radio, clock, manager);
dispatcher.begin();
for (uint8_t route : {ROUTE_TYPE_FLOOD, ROUTE_TYPE_TRANSPORT_FLOOD}) {
for (uint8_t type : {PAYLOAD_TYPE_TRACE, PAYLOAD_TYPE_CONTROL}) {
uint8_t raw[7] = {};
int raw_len = 0;
raw[raw_len++] = route | (type << PH_TYPE_SHIFT);
if (route == ROUTE_TYPE_TRANSPORT_FLOOD) {
raw_len += 4; // transport codes
}
raw[raw_len++] = 0; // zero-hop path
raw[raw_len++] = 0x42;
clock.now++;
radio.queueRx(raw, raw_len);
dispatcher.loop();
}
}
EXPECT_EQ(0, dispatcher.raw_receive_logs);
EXPECT_EQ(0, dispatcher.parsed_receive_logs);
EXPECT_EQ(0, dispatcher.received_packets);
EXPECT_EQ(0U, dispatcher.getLastMeshCoreRecvMillis());
EXPECT_EQ(0U, dispatcher.getNumRecvFlood());
EXPECT_EQ(4, manager.getFreeCount());
}
TEST(Dispatcher, DirectTraceAndControlRemainValid) {
RxReservePacketManager manager(4, 1);
TestClock clock;
TestRadio radio;
TestDispatcher dispatcher(radio, clock, manager);
dispatcher.begin();
for (uint8_t type : {PAYLOAD_TYPE_TRACE, PAYLOAD_TYPE_CONTROL}) {
const uint8_t raw[] = {
static_cast<uint8_t>(ROUTE_TYPE_DIRECT | (type << PH_TYPE_SHIFT)), 0, 0x42
};
clock.now++;
radio.queueRx(raw, sizeof(raw));
dispatcher.loop();
}
EXPECT_EQ(2, dispatcher.raw_receive_logs);
EXPECT_EQ(2, dispatcher.parsed_receive_logs);
EXPECT_EQ(2, dispatcher.received_packets);
EXPECT_EQ(2U, dispatcher.getLastMeshCoreRecvMillis());
EXPECT_EQ(2U, dispatcher.getNumRecvDirect());
}
TEST(Dispatcher, FloodTraceAndControlCannotBeQueuedForTransmit) {
RxReservePacketManager manager(4, 1);
TestClock clock;
TestRadio radio;
TestDispatcher dispatcher(radio, clock, manager);
dispatcher.begin();
for (uint8_t route : {ROUTE_TYPE_FLOOD, ROUTE_TYPE_TRANSPORT_FLOOD}) {
for (uint8_t type : {PAYLOAD_TYPE_TRACE, PAYLOAD_TYPE_CONTROL}) {
mesh::Packet* packet = dispatcher.obtainNewPacket();
ASSERT_NE(packet, nullptr);
packet->header = route | (type << PH_TYPE_SHIFT);
packet->setPathHashSizeAndCount(1, 0);
packet->payload_len = 1;
packet->payload[0] = 0x42;
EXPECT_FALSE(dispatcher.sendPacket(packet, 0));
EXPECT_FALSE(dispatcher.hasOutbound());
}
}
EXPECT_EQ(4, manager.getFreeCount());
EXPECT_EQ(0, radio.send_starts);
}
TEST(Dispatcher, RejectedRadioPacketsCannotFeedMeshCoreRxWatchdog) {
RxReservePacketManager manager(4, 1);
TestClock clock;
TestRadio radio;
TestDispatcher dispatcher(radio, clock, manager);
mesh::RxInactivityWatchdog watchdog;
const uint32_t timeout = 24 * mesh::RepeaterRadioTiming::HOUR_MS;
clock.now = 1;
dispatcher.begin();
ASSERT_FALSE(watchdog.expired(clock.now, dispatcher.getLastMeshCoreRecvMillis(), timeout));
const uint8_t rejected[][3] = {
{ROUTE_TYPE_DIRECT | (PAYLOAD_VER_2 << PH_VER_SHIFT), 0, 0x42},
{ROUTE_TYPE_DIRECT, 0xC0, 0x42}, // reserved path mode
{ROUTE_TYPE_DIRECT, 2, 0x42}, // truncated path
{ROUTE_TYPE_TRANSPORT_DIRECT, 0, 0}, // truncated transport header
};
for (const auto& raw : rejected) {
clock.now += 1000;
radio.queueRx(raw, sizeof(raw));
dispatcher.loop();
EXPECT_EQ(0U, dispatcher.getLastMeshCoreRecvMillis());
}
EXPECT_EQ(0, dispatcher.received_packets);
clock.now = timeout + 1;
radio.queueRx(rejected[0], sizeof(rejected[0]));
dispatcher.loop();
EXPECT_TRUE(watchdog.expired(clock.now, dispatcher.getLastMeshCoreRecvMillis(), timeout));
// The separate radio-only watchdog can still use a successful physical read.
EXPECT_EQ(0, radio.soft_recoveries);
EXPECT_EQ(0, radio.hard_recoveries);
}
TEST(Dispatcher, MeshCorePacketAtDeadlineFeedsWatchdogBeforeForwardingDelay) {
RxReservePacketManager manager(4, 1);
TestClock clock;
TestRadio radio;
TestDispatcher dispatcher(radio, clock, manager);
mesh::RxInactivityWatchdog watchdog;
const uint32_t timeout = 24 * mesh::RepeaterRadioTiming::HOUR_MS;
dispatcher.forced_rx_delay = 32000;
clock.now = 1;
dispatcher.begin();
ASSERT_FALSE(watchdog.expired(clock.now, dispatcher.getLastMeshCoreRecvMillis(), timeout));
const uint8_t raw[] = {
ROUTE_TYPE_FLOOD | (PAYLOAD_TYPE_RAW_CUSTOM << PH_TYPE_SHIFT), 0, 0x42
};
clock.now = timeout + 1;
radio.queueRx(raw, sizeof(raw));
dispatcher.loop();
EXPECT_EQ(0, dispatcher.received_packets); // still waiting for forwarding
EXPECT_EQ(clock.now, dispatcher.getLastMeshCoreRecvMillis());
EXPECT_FALSE(watchdog.expired(clock.now, dispatcher.getLastMeshCoreRecvMillis(), timeout));
clock.now += 32000;
dispatcher.loop();
EXPECT_EQ(1, dispatcher.received_packets);
EXPECT_EQ(timeout + 1, dispatcher.getLastMeshCoreRecvMillis());
clock.now = 2 * timeout + 1;
EXPECT_TRUE(watchdog.expired(clock.now, dispatcher.getLastMeshCoreRecvMillis(), timeout));
}
TEST(Dispatcher, RepeatedMeshCorePacketsFeedClockButTxAndStatsResetDoNot) {
RxReservePacketManager manager(4, 1);
TestClock clock;
TestRadio radio;
TestDispatcher dispatcher(radio, clock, manager);
dispatcher.begin();
const uint8_t raw[] = {
ROUTE_TYPE_DIRECT | (PAYLOAD_TYPE_RAW_CUSTOM << PH_TYPE_SHIFT), 0, 0x42
};
for (unsigned long received_at : {100UL, 200UL}) {
clock.now = received_at;
radio.queueRx(raw, sizeof(raw));
dispatcher.loop();
EXPECT_EQ(received_at, dispatcher.getLastMeshCoreRecvMillis());
}
clock.now = 300;
auto* outbound = dispatcher.obtainNewPacket();
ASSERT_NE(outbound, nullptr);
ASSERT_TRUE(outbound->readFrom(raw, sizeof(raw)));
ASSERT_TRUE(dispatcher.sendPacket(outbound, 0));
dispatcher.loop();
ASSERT_EQ(1, radio.send_starts);
radio.send_complete = true;
clock.now = 301;
dispatcher.loop();
ASSERT_EQ(1, dispatcher.completed_packets);
dispatcher.resetStats();
radio.last_irq = ++clock.now;
dispatcher.loop();
EXPECT_EQ(200U, dispatcher.getLastMeshCoreRecvMillis());
}
TEST(Dispatcher, UnparsedPacketWhenPoolIsFullDoesNotFeedMeshCoreRxClock) {
RxReservePacketManager manager(1, 0);
TestClock clock;
TestRadio radio;
TestDispatcher dispatcher(radio, clock, manager);
dispatcher.begin();
auto* held = manager.allocNew();
ASSERT_NE(held, nullptr);
const uint8_t raw[] = {
ROUTE_TYPE_DIRECT | (PAYLOAD_TYPE_RAW_CUSTOM << PH_TYPE_SHIFT), 0, 0x42
};
clock.now = 100;
radio.queueRx(raw, sizeof(raw));
dispatcher.loop();
EXPECT_EQ(0U, dispatcher.getLastMeshCoreRecvMillis());
EXPECT_EQ(0, dispatcher.received_packets);
manager.free(held);
}
TEST(Dispatcher, ScopeRewriteHookBypassesConfiguredRxDelay) {
RxReservePacketManager manager(4, 1);
TestClock clock;
TestRadio radio;
TestDispatcher dispatcher(radio, clock, manager);
dispatcher.forced_rx_delay = 1000;
dispatcher.bypass_rx_delay = true;
dispatcher.begin();
const uint8_t raw[] = {
ROUTE_TYPE_FLOOD | (PAYLOAD_TYPE_GRP_DATA << PH_TYPE_SHIFT), 0, 0x42
};
clock.now = 100;
radio.queueRx(raw, sizeof(raw));
dispatcher.loop();
EXPECT_EQ(1, dispatcher.received_packets);
EXPECT_EQ(4, manager.getFreeCount());
}
TEST(Dispatcher, ConfiguredCadStateIsPropagatedToTheRadio) {
RxReservePacketManager manager(4, 1);
TestClock clock;
TestRadio radio;
TestDispatcher dispatcher(radio, clock, manager);
dispatcher.configured_cad_enabled = true;
dispatcher.begin();
clock.now = 1;
dispatcher.loop();
EXPECT_EQ(1, radio.cad_set_calls);
EXPECT_TRUE(radio.cad_enabled);
dispatcher.configured_cad_enabled = false;
clock.now = 2002;
dispatcher.loop();
EXPECT_EQ(2, radio.cad_set_calls);
EXPECT_FALSE(radio.cad_enabled);
}
TEST(Dispatcher, FirstAgcResetWaitsForConfiguredIntervalAfterStartup) {
RxReservePacketManager manager(4, 1);
TestClock clock;
clock.now = 1000;
TestRadio radio;
TestDispatcher dispatcher(radio, clock, manager);
dispatcher.begin();
// Repeater preferences are loaded after Dispatcher::begin(). The first loop
// must arm from that persisted value rather than treating a zero deadline as
// already expired.
dispatcher.configured_agc_interval = 8000;
dispatcher.loop();
EXPECT_EQ(0, radio.agc_resets);
clock.now = 8999;
dispatcher.loop();
EXPECT_EQ(0, radio.agc_resets);
clock.now = 9001;
dispatcher.loop();
EXPECT_EQ(1, radio.agc_resets);
}
TEST(Dispatcher, FirstAgcResetWaitsAfterDelayedRadioActivation) {
RxReservePacketManager manager(4, 1);
TestClock clock;
TestRadio radio;
TestDispatcher dispatcher(radio, clock, manager);
dispatcher.configured_agc_interval = 8000;
dispatcher.makeRadioAvailable(false);
dispatcher.begin();
clock.now = 2000;
dispatcher.makeRadioAvailable(true);
dispatcher.loop();
EXPECT_EQ(0, radio.agc_resets);
clock.now = 9999;
dispatcher.loop();
EXPECT_EQ(0, radio.agc_resets);
clock.now = 10001;
dispatcher.loop();
EXPECT_EQ(1, radio.agc_resets);
}
TEST(Dispatcher, EnablingAgcResetAtRuntimeArmsWithoutImmediateReset) {
RxReservePacketManager manager(4, 1);
TestClock clock;
TestRadio radio;
TestDispatcher dispatcher(radio, clock, manager);
dispatcher.begin();
dispatcher.loop();
clock.now = 5000;
dispatcher.configured_agc_interval = 8000;
dispatcher.loop();
EXPECT_EQ(0, radio.agc_resets);
clock.now = 13001;
dispatcher.loop();
EXPECT_EQ(1, radio.agc_resets);
}
TEST(Dispatcher, LengtheningPositiveAgcIntervalRearmsTheDeadline) {
RxReservePacketManager manager(4, 1);
TestClock clock;
clock.now = 1000;
TestRadio radio;
TestDispatcher dispatcher(radio, clock, manager);
dispatcher.configured_agc_interval = 8000;
dispatcher.begin();
dispatcher.loop();
clock.now = 2000;
dispatcher.configured_agc_interval = 240000;
dispatcher.loop();
// The former 8-second deadline must no longer fire.
clock.now = 9001;
dispatcher.loop();
EXPECT_EQ(0, radio.agc_resets);
clock.now = 242001;
dispatcher.loop();
EXPECT_EQ(1, radio.agc_resets);
}
TEST(Dispatcher, ShorteningPositiveAgcIntervalRearmsTheDeadline) {
RxReservePacketManager manager(4, 1);
TestClock clock;
clock.now = 1000;
TestRadio radio;
TestDispatcher dispatcher(radio, clock, manager);
dispatcher.configured_agc_interval = 240000;
dispatcher.begin();
dispatcher.loop();
clock.now = 2000;
dispatcher.configured_agc_interval = 8000;
dispatcher.loop();
clock.now = 9999;
dispatcher.loop();
EXPECT_EQ(0, radio.agc_resets);
clock.now = 10001;
dispatcher.loop();
EXPECT_EQ(1, radio.agc_resets);
}
TEST(Dispatcher, DisablingAndReenablingAgcStartsANewFullInterval) {
RxReservePacketManager manager(4, 1);
TestClock clock;
TestRadio radio;
TestDispatcher dispatcher(radio, clock, manager);
dispatcher.configured_agc_interval = 8000;
dispatcher.begin();
dispatcher.loop();
clock.now = 4000;
dispatcher.configured_agc_interval = 0;
dispatcher.loop();
clock.now = 10000;
dispatcher.configured_agc_interval = 8000;
dispatcher.loop();
clock.now = 17999;
dispatcher.loop();
EXPECT_EQ(0, radio.agc_resets);
clock.now = 18001;
dispatcher.loop();
EXPECT_EQ(1, radio.agc_resets);
}
TEST(Dispatcher, AgcResetRepeatsAtTheConfiguredInterval) {
RxReservePacketManager manager(4, 1);
TestClock clock;
TestRadio radio;
TestDispatcher dispatcher(radio, clock, manager);
dispatcher.configured_agc_interval = 100;
dispatcher.begin();
dispatcher.loop();
clock.now = 101;
dispatcher.loop();
EXPECT_EQ(1, radio.agc_resets);
clock.now = 202;
dispatcher.loop();
EXPECT_EQ(2, radio.agc_resets);
}
TEST(Dispatcher, RadioReactivationStartsANewFullAgcInterval) {
RxReservePacketManager manager(4, 1);
TestClock clock;
TestRadio radio;
TestDispatcher dispatcher(radio, clock, manager);
dispatcher.configured_agc_interval = 8000;
dispatcher.begin();
dispatcher.loop();
clock.now = 7000;
dispatcher.makeRadioAvailable(false);
clock.now = 12000;
dispatcher.makeRadioAvailable(true);
dispatcher.loop();
clock.now = 19999;
dispatcher.loop();
EXPECT_EQ(0, radio.agc_resets);
clock.now = 20001;
dispatcher.loop();
EXPECT_EQ(1, radio.agc_resets);
}
TEST(Dispatcher, AgcResetDeadlineSurvivesMillisRollover) {
RxReservePacketManager manager(4, 1);
TestClock clock;
clock.now = ~0UL - 0xFFUL;
TestRadio radio;
TestDispatcher dispatcher(radio, clock, manager);
dispatcher.configured_agc_interval = 512;
dispatcher.begin();
dispatcher.loop();
clock.now = 0xFFUL;
dispatcher.loop();
EXPECT_EQ(0, radio.agc_resets);
clock.now = 0x101UL;
dispatcher.loop();
EXPECT_EQ(1, radio.agc_resets);
}
TEST(StaticPoolPacketManager, ReportsEarliestQueueTimesWithoutDequeuing) {
StaticPoolPacketManager manager(8);
mesh::Packet* later = manager.allocNew();
mesh::Packet* earlier = manager.allocNew();
mesh::Packet* inbound = manager.allocNew();
ASSERT_NE(later, nullptr);
ASSERT_NE(earlier, nullptr);
ASSERT_NE(inbound, nullptr);
ASSERT_TRUE(manager.queueOutbound(later, 0, 500));
ASSERT_TRUE(manager.queueOutbound(earlier, 0, 300));
manager.queueInbound(inbound, 250);
uint32_t scheduled_for = 0;
ASSERT_TRUE(manager.getNextOutboundTime(100, scheduled_for));
EXPECT_EQ(300U, scheduled_for);
ASSERT_TRUE(manager.getNextInboundTime(100, scheduled_for));
EXPECT_EQ(250U, scheduled_for);
EXPECT_EQ(2, manager.getOutboundTotal());
ASSERT_TRUE(manager.getNextOutboundTime(400, scheduled_for));
EXPECT_EQ(400U, scheduled_for); // overdue work is runnable now
manager.free(manager.getNextInbound(250));
manager.free(manager.getNextOutbound(500));
manager.free(manager.getNextOutbound(500));
}
TEST(StaticPoolPacketManager, EarliestQueueTimeIsCorrectAcrossMillisRollover) {
StaticPoolPacketManager manager(4);
mesh::Packet* before_wrap = manager.allocNew();
mesh::Packet* after_wrap = manager.allocNew();
ASSERT_NE(before_wrap, nullptr);
ASSERT_NE(after_wrap, nullptr);
// Add these in reverse chronological order to exercise the cached minimum.
ASSERT_TRUE(manager.queueOutbound(after_wrap, 0, 0x00000004UL));
ASSERT_TRUE(manager.queueOutbound(before_wrap, 0, 0xFFFFFFF5UL));
uint32_t scheduled_for = 0;
ASSERT_TRUE(manager.getNextOutboundTime(0xFFFFFFF0UL, scheduled_for));
EXPECT_EQ(0xFFFFFFF5UL, scheduled_for);
EXPECT_EQ(before_wrap, manager.getNextOutbound(0xFFFFFFF5UL));
manager.free(before_wrap);
ASSERT_TRUE(manager.getNextOutboundTime(0xFFFFFFF6UL, scheduled_for));
EXPECT_EQ(0x00000004UL, scheduled_for);
EXPECT_EQ(after_wrap, manager.getNextOutbound(0x00000004UL));
manager.free(after_wrap);
}
static void setFloodIdentity(mesh::Packet* packet, uint8_t route, uint8_t seed) {
packet->header = route | (PAYLOAD_TYPE_GRP_TXT << PH_TYPE_SHIFT);
packet->path_len = 0;
packet->payload[0] = seed;
packet->payload_len = 1;
}
static uint8_t acceptFloodScope(const mesh::Packet*, void*) {
return 1;
}
TEST(StaticPoolPacketManager, ScopedRxDelayCopyRequiresLocalValidator) {
StaticPoolPacketManager manager(4);
mesh::Packet* unscoped = manager.allocNew();
mesh::Packet* scoped = manager.allocNew();
ASSERT_NE(unscoped, nullptr);
ASSERT_NE(scoped, nullptr);
setFloodIdentity(unscoped, ROUTE_TYPE_FLOOD, 0x41);
setFloodIdentity(scoped, ROUTE_TYPE_TRANSPORT_FLOOD, 0x41);
scoped->transport_codes[0] = 0x1234;
manager.queueInbound(unscoped, 100);
manager.queueInbound(scoped, 200);
EXPECT_EQ(unscoped, manager.getNextInbound(100));
EXPECT_EQ(ROUTE_TYPE_FLOOD, unscoped->getRouteType());
manager.free(unscoped);
manager.free(manager.getNextInbound(200));
}
TEST(StaticPoolPacketManager, ScopedRxDelayCopyUpgradesQueuedUnscopedCopy) {
StaticPoolPacketManager manager(4);
manager.setFloodScopePreference(acceptFloodScope, NULL);
mesh::Packet* unscoped = manager.allocNew();
mesh::Packet* scoped = manager.allocNew();
ASSERT_NE(unscoped, nullptr);
ASSERT_NE(scoped, nullptr);
setFloodIdentity(unscoped, ROUTE_TYPE_FLOOD, 0x42);
setFloodIdentity(scoped, ROUTE_TYPE_TRANSPORT_FLOOD, 0x42);
scoped->transport_codes[0] = 0x1234;
scoped->transport_codes[1] = 0x5678;
manager.queueInbound(unscoped, 100);
manager.queueInbound(scoped, 200);
EXPECT_EQ(ROUTE_TYPE_FLOOD, unscoped->getRouteType());
EXPECT_EQ(unscoped, manager.getNextInbound(100));
EXPECT_EQ(ROUTE_TYPE_TRANSPORT_FLOOD, unscoped->getRouteType());
EXPECT_EQ(0x1234, unscoped->transport_codes[0]);
EXPECT_EQ(0x5678, unscoped->transport_codes[1]);
manager.free(unscoped);
manager.free(manager.getNextInbound(200));
}
TEST(StaticPoolPacketManager, QueuedScopedCopyUpgradesNewEarlierUnscopedCopy) {
StaticPoolPacketManager manager(4);
manager.setFloodScopePreference(acceptFloodScope, NULL);
mesh::Packet* scoped = manager.allocNew();
mesh::Packet* unscoped = manager.allocNew();
ASSERT_NE(scoped, nullptr);
ASSERT_NE(unscoped, nullptr);
setFloodIdentity(scoped, ROUTE_TYPE_TRANSPORT_FLOOD, 0x24);
setFloodIdentity(unscoped, ROUTE_TYPE_FLOOD, 0x24);
scoped->transport_codes[0] = 0xABCD;
scoped->transport_codes[1] = 0;
manager.queueInbound(scoped, 200);
manager.queueInbound(unscoped, 100);
EXPECT_EQ(ROUTE_TYPE_FLOOD, unscoped->getRouteType());
EXPECT_EQ(unscoped, manager.getNextInbound(100));
EXPECT_EQ(ROUTE_TYPE_TRANSPORT_FLOOD, unscoped->getRouteType());
EXPECT_EQ(0xABCD, unscoped->transport_codes[0]);
manager.free(unscoped);
manager.free(manager.getNextInbound(200));
}
TEST(StaticPoolPacketManager, RxDelayScopeDoesNotCrossPacketIdentity) {
StaticPoolPacketManager manager(4);
mesh::Packet* scoped = manager.allocNew();
mesh::Packet* different = manager.allocNew();
ASSERT_NE(scoped, nullptr);
ASSERT_NE(different, nullptr);
setFloodIdentity(scoped, ROUTE_TYPE_TRANSPORT_FLOOD, 0x11);
setFloodIdentity(different, ROUTE_TYPE_FLOOD, 0x12);
scoped->transport_codes[0] = 0xCAFE;
manager.queueInbound(scoped, 200);
manager.queueInbound(different, 100);
EXPECT_EQ(ROUTE_TYPE_FLOOD, different->getRouteType());
EXPECT_EQ(different, manager.getNextInbound(100));
manager.free(different);
manager.free(manager.getNextInbound(200));
}
TEST(StaticPoolPacketManager, RxDelayNeverAddsScopeToFloodTrace) {
StaticPoolPacketManager manager(4);
manager.setFloodScopePreference(acceptFloodScope, NULL);
mesh::Packet* unscoped = manager.allocNew();
mesh::Packet* scoped = manager.allocNew();
ASSERT_NE(unscoped, nullptr);
ASSERT_NE(scoped, nullptr);
setFloodIdentity(unscoped, ROUTE_TYPE_FLOOD, 0x13);
setFloodIdentity(scoped, ROUTE_TYPE_TRANSPORT_FLOOD, 0x13);
unscoped->header = ROUTE_TYPE_FLOOD | (PAYLOAD_TYPE_TRACE << PH_TYPE_SHIFT);
scoped->header = ROUTE_TYPE_TRANSPORT_FLOOD | (PAYLOAD_TYPE_TRACE << PH_TYPE_SHIFT);
scoped->transport_codes[0] = 0xCAFE;
manager.queueInbound(unscoped, 100);
manager.queueInbound(scoped, 200);
EXPECT_EQ(unscoped, manager.getNextInbound(100));
EXPECT_EQ(ROUTE_TYPE_FLOOD, unscoped->getRouteType());
manager.free(unscoped);
manager.free(manager.getNextInbound(200));
}
static uint8_t testFloodScopePreference(const mesh::Packet* packet, void*) {
return (uint8_t)(packet->transport_codes[0] & 0xFF);
}
TEST(StaticPoolPacketManager, RxDelayRejectsUnknownShorterScope) {
StaticPoolPacketManager manager(4);
manager.setFloodScopePreference(testFloodScopePreference, NULL);
mesh::Packet* known = manager.allocNew();
mesh::Packet* unknown = manager.allocNew();
ASSERT_NE(known, nullptr);
ASSERT_NE(unknown, nullptr);
setFloodIdentity(known, ROUTE_TYPE_TRANSPORT_FLOOD, 0x30);
setFloodIdentity(unknown, ROUTE_TYPE_TRANSPORT_FLOOD, 0x30);
known->setPathHashSizeAndCount(1, 4);
unknown->setPathHashSizeAndCount(1, 1);
known->transport_codes[0] = 3;
unknown->transport_codes[0] = 0; // rejected by the local validator
manager.queueInbound(known, 100);
manager.queueInbound(unknown, 200);
EXPECT_EQ(known, manager.getNextInbound(100));
EXPECT_EQ(3, known->transport_codes[0]);
manager.free(known);
manager.free(manager.getNextInbound(200));
}
TEST(StaticPoolPacketManager, RxDelayShorterScopedPathBeatsNarrowerScope) {
StaticPoolPacketManager manager(4);
manager.setFloodScopePreference(testFloodScopePreference, NULL);
mesh::Packet* narrower = manager.allocNew();
mesh::Packet* shorter = manager.allocNew();
ASSERT_NE(narrower, nullptr);
ASSERT_NE(shorter, nullptr);
setFloodIdentity(narrower, ROUTE_TYPE_TRANSPORT_FLOOD, 0x31);
setFloodIdentity(shorter, ROUTE_TYPE_TRANSPORT_FLOOD, 0x31);
narrower->setPathHashSizeAndCount(1, 4);
shorter->setPathHashSizeAndCount(1, 2);
narrower->transport_codes[0] = 9; // higher local preference
shorter->transport_codes[0] = 1;
manager.queueInbound(narrower, 100);
manager.queueInbound(shorter, 200);
EXPECT_EQ(narrower, manager.getNextInbound(100));
EXPECT_EQ(1, narrower->transport_codes[0]);
manager.free(narrower);
manager.free(manager.getNextInbound(200));
}
TEST(StaticPoolPacketManager, RxDelayEqualPathsPreferNarrowerLocalScope) {
StaticPoolPacketManager manager(4);
manager.setFloodScopePreference(testFloodScopePreference, NULL);
mesh::Packet* shallow = manager.allocNew();
mesh::Packet* narrower = manager.allocNew();
ASSERT_NE(shallow, nullptr);
ASSERT_NE(narrower, nullptr);
setFloodIdentity(shallow, ROUTE_TYPE_TRANSPORT_FLOOD, 0x32);
setFloodIdentity(narrower, ROUTE_TYPE_TRANSPORT_FLOOD, 0x32);
shallow->setPathHashSizeAndCount(1, 3);
narrower->setPathHashSizeAndCount(1, 3);
shallow->transport_codes[0] = 1;
narrower->transport_codes[0] = 9; // higher local preference
manager.queueInbound(shallow, 100);
manager.queueInbound(narrower, 200);
EXPECT_EQ(shallow, manager.getNextInbound(100));
EXPECT_EQ(9, shallow->transport_codes[0]);
manager.free(shallow);
manager.free(manager.getNextInbound(200));
}
TEST(Dispatcher, QueueWakeDelayIncludesSchedulesAndAdaptiveChannelBackoff) {
RxReservePacketManager manager(8, 4);
TestClock clock;
clock.now = 100;
TestRadio radio;
TestDispatcher dispatcher(radio, clock, manager);
dispatcher.begin();
mesh::Packet* packet = dispatcher.obtainNewPacket();
ASSERT_NE(packet, nullptr);
packet->header = ROUTE_TYPE_DIRECT | (PAYLOAD_TYPE_RAW_CUSTOM << PH_TYPE_SHIFT);
packet->payload[0] = 0x42;
packet->payload_len = 1;
ASSERT_TRUE(dispatcher.sendPacket(packet, 0, 500));
uint32_t delay_millis = 0;
ASSERT_TRUE(dispatcher.nextQueueWakeDelay(delay_millis));
EXPECT_EQ(500U, delay_millis);
EXPECT_FALSE(dispatcher.queuedWorkDue());
clock.now = 600;
radio.receiving = true;
dispatcher.loop();
ASSERT_TRUE(dispatcher.nextQueueWakeDelay(delay_millis));
EXPECT_EQ(200U, delay_millis);
EXPECT_FALSE(dispatcher.queuedWorkDue());
mesh::Packet* second = dispatcher.obtainNewPacket();
ASSERT_NE(second, nullptr);
second->header = ROUTE_TYPE_DIRECT | (PAYLOAD_TYPE_RAW_CUSTOM << PH_TYPE_SHIFT);
second->payload[0] = 0x43;
second->payload_len = 1;
ASSERT_TRUE(dispatcher.sendPacket(second, 0));
clock.now = 801;
dispatcher.loop();
ASSERT_TRUE(dispatcher.nextQueueWakeDelay(delay_millis));
EXPECT_EQ(100U, delay_millis);
EXPECT_FALSE(dispatcher.queuedWorkDue());
clock.now = 901;
radio.receiving = false;
EXPECT_TRUE(dispatcher.queuedWorkDue());
}
TEST(Dispatcher, GrowingQueueShortensCADBusyCeiling) {
RxReservePacketManager manager(8, 1);
TestClock clock;
clock.now = 100;
TestRadio radio;
radio.receiving = true;
TestDispatcher dispatcher(radio, clock, manager);
dispatcher.begin();
mesh::Packet* first = dispatcher.obtainNewPacket();
ASSERT_NE(first, nullptr);
first->header = ROUTE_TYPE_DIRECT | (PAYLOAD_TYPE_RAW_CUSTOM << PH_TYPE_SHIFT);
first->payload[0] = 0x40;
first->payload_len = 1;
ASSERT_TRUE(dispatcher.sendPacket(first, 0));
clock.now = 101;
dispatcher.loop();
EXPECT_EQ(0, radio.send_starts);
for (uint8_t i = 1; i < 4; i++) {
mesh::Packet* packet = dispatcher.obtainNewPacket();
ASSERT_NE(packet, nullptr);
packet->header = ROUTE_TYPE_DIRECT | (PAYLOAD_TYPE_RAW_CUSTOM << PH_TYPE_SHIFT);
packet->payload[0] = (uint8_t)(0x40 + i);
packet->payload_len = 1;
ASSERT_TRUE(dispatcher.sendPacket(packet, 0));
}
EXPECT_EQ(4, manager.getOutboundCount(clock.now));
// Four ready packets reduce the normal four-second CAD ceiling to one
// second, measured from the original busy observation.
clock.now = 1102;
dispatcher.loop();
EXPECT_EQ(1, radio.send_starts);
EXPECT_EQ(3, manager.getOutboundCount(clock.now));
EXPECT_TRUE(dispatcher.getErrFlags() & ERR_EVENT_CAD_TIMEOUT);
}
TEST(Dispatcher, SilentRadioEscalatesFromSoftToHardRecovery) {
RxReservePacketManager manager(8, 4);
TestClock clock;
TestRadio radio;
TestDispatcher dispatcher(radio, clock, manager);
dispatcher.begin();
clock.now = 30UL * 60UL * 1000UL;
dispatcher.loop();
EXPECT_EQ(1, radio.soft_recoveries);
EXPECT_EQ(0, radio.hard_recoveries);
clock.now = 12UL * 60UL * 60UL * 1000UL;
dispatcher.loop();
EXPECT_EQ(1, radio.soft_recoveries);
EXPECT_EQ(1, radio.hard_recoveries);
}
TEST(Dispatcher, RadioOutsideReceiveModeEscalatesOnSecondAttempt) {
RxReservePacketManager manager(8, 4);
TestClock clock;
TestRadio radio;
radio.in_recv_mode = false;
TestDispatcher dispatcher(radio, clock, manager);
dispatcher.begin();
clock.now = 8001;
dispatcher.loop();
EXPECT_EQ(1, radio.soft_recoveries);
EXPECT_EQ(0, radio.hard_recoveries);
clock.now = 16002;
dispatcher.loop();
EXPECT_EQ(1, radio.soft_recoveries);
EXPECT_EQ(1, radio.hard_recoveries);
}
TEST(Dispatcher, TimedOutTransmitRecoversAndRetriesSamePacket) {
RxReservePacketManager manager(8, 4);
TestClock clock;
clock.now = 100;
TestRadio radio;
TestDispatcher dispatcher(radio, clock, manager);
dispatcher.begin();
mesh::Packet* packet = dispatcher.obtainNewPacket();
ASSERT_NE(packet, nullptr);
packet->header = ROUTE_TYPE_DIRECT | (PAYLOAD_TYPE_RAW_CUSTOM << PH_TYPE_SHIFT);
packet->payload[0] = 0x42;
packet->payload_len = 1;
ASSERT_TRUE(dispatcher.sendPacket(packet, 0));
clock.now = 101;
dispatcher.loop();
ASSERT_EQ(1, radio.send_starts);
ASSERT_EQ(0, radio.hard_recoveries);
clock.now = 103;
dispatcher.loop();
EXPECT_EQ(1, radio.send_finishes);
EXPECT_EQ(1, radio.hard_recoveries);
EXPECT_EQ(nullptr, dispatcher.failed_packet);
EXPECT_EQ(7, manager.getFreeCount());
EXPECT_TRUE(dispatcher.getErrFlags() & ERR_EVENT_RADIO_WATCHDOG);
uint32_t wake_delay = 0;
ASSERT_TRUE(dispatcher.nextQueueWakeDelay(wake_delay));
EXPECT_EQ(200U, wake_delay);
clock.now = 304;
dispatcher.loop();
ASSERT_EQ(2, radio.send_starts);
ASSERT_EQ(radio.sent_len[0], radio.sent_len[1]);
EXPECT_EQ(0, memcmp(radio.sent_raw[0], radio.sent_raw[1], radio.sent_len[0]));
radio.send_complete = true;
clock.now = 305;
dispatcher.loop();
EXPECT_EQ(2, radio.send_finishes);
EXPECT_EQ(packet, dispatcher.completed_packet);
EXPECT_EQ(1, dispatcher.completed_packets);
EXPECT_EQ(nullptr, dispatcher.failed_packet);
EXPECT_EQ(8, manager.getFreeCount());
}
TEST(Dispatcher, SecondTimedOutTransmitFailsWithoutThirdAttempt) {
RxReservePacketManager manager(8, 4);
TestClock clock;
clock.now = 100;
TestRadio radio;
TestDispatcher dispatcher(radio, clock, manager);
dispatcher.begin();
mesh::Packet* packet = dispatcher.obtainNewPacket();
ASSERT_NE(packet, nullptr);
packet->header = ROUTE_TYPE_DIRECT | (PAYLOAD_TYPE_RAW_CUSTOM << PH_TYPE_SHIFT);
packet->payload[0] = 0x43;
packet->payload_len = 1;
ASSERT_TRUE(dispatcher.sendPacket(packet, 0));
clock.now = 101;
dispatcher.loop();
ASSERT_EQ(1, radio.send_starts);
clock.now = 103;
dispatcher.loop();
ASSERT_EQ(1, radio.hard_recoveries);
clock.now = 304;
dispatcher.loop();
ASSERT_EQ(2, radio.send_starts);
clock.now = 306;
dispatcher.loop();
EXPECT_EQ(2, radio.send_finishes);
EXPECT_EQ(2, radio.hard_recoveries);
EXPECT_EQ(packet, dispatcher.failed_packet);
EXPECT_EQ(8, manager.getFreeCount());
clock.now = 1000;
dispatcher.loop();
EXPECT_EQ(2, radio.send_starts);
}
TEST(Dispatcher, StartFailureRetriesSamePacketWithoutApp) {
RxReservePacketManager manager(8, 4);
TestClock clock;
clock.now = 100;
TestRadio radio;
radio.start_success = false;
TestDispatcher dispatcher(radio, clock, manager);
dispatcher.begin();
mesh::Packet* packet = dispatcher.obtainNewPacket();
ASSERT_NE(packet, nullptr);
packet->header = ROUTE_TYPE_DIRECT | (PAYLOAD_TYPE_RAW_CUSTOM << PH_TYPE_SHIFT);
packet->payload[0] = 0x44;
packet->payload_len = 1;
ASSERT_TRUE(dispatcher.sendPacket(packet, 0));
clock.now = 101;
dispatcher.loop();
ASSERT_EQ(1, radio.send_starts);
EXPECT_EQ(nullptr, dispatcher.failed_packet);
EXPECT_EQ(7, manager.getFreeCount());
radio.start_success = true;
clock.now = 302;
dispatcher.loop();
ASSERT_EQ(2, radio.send_starts);
ASSERT_EQ(radio.sent_len[0], radio.sent_len[1]);
EXPECT_EQ(0, memcmp(radio.sent_raw[0], radio.sent_raw[1], radio.sent_len[0]));
radio.send_complete = true;
clock.now = 303;
dispatcher.loop();
EXPECT_EQ(packet, dispatcher.completed_packet);
EXPECT_EQ(1, dispatcher.completed_packets);
EXPECT_EQ(nullptr, dispatcher.failed_packet);
EXPECT_EQ(8, manager.getFreeCount());
}
TEST(RxReservePacketManager, RejectedOutboundRemainsOwnedByCaller) {
RxReservePacketManager manager(8, 4);
mesh::Packet* held[6];
for (int i = 0; i < 6; i++) {
held[i] = manager.allocNew();
ASSERT_NE(held[i], nullptr);
}
ASSERT_EQ(manager.getFreeCount(), 2);
EXPECT_FALSE(manager.queueOutbound(held[5], 2, 0));
EXPECT_EQ(manager.getFreeCount(), 2);
manager.free(held[5]);
EXPECT_EQ(manager.getFreeCount(), 3);
for (int i = 0; i < 5; i++) {
manager.free(held[i]);
}
EXPECT_EQ(manager.getFreeCount(), 8);
}
TEST(RxReservePacketManager, PeekMatchesDequeueWithoutRemovingPacket) {
RxReservePacketManager manager(8, 4);
mesh::Packet* low = manager.allocNew();
mesh::Packet* high = manager.allocNew();
ASSERT_NE(low, nullptr);
ASSERT_NE(high, nullptr);
ASSERT_TRUE(manager.queueOutbound(low, 2, 100));
ASSERT_TRUE(manager.queueOutbound(high, 0, 100));
EXPECT_EQ(high, manager.peekNextOutbound(100));
EXPECT_EQ(2, manager.getOutboundTotal());
EXPECT_EQ(high, manager.getNextOutbound(100));
manager.free(high);
EXPECT_EQ(low, manager.getNextOutbound(100));
manager.free(low);
}
TEST(RxReservePacketManager, PeekExpiresStalePacketBeforeChannelSelection) {
RxReservePacketManager manager(8, 4);
mesh::Packet* stale = manager.allocNew();
ASSERT_NE(stale, nullptr);
ASSERT_TRUE(manager.queueOutbound(stale, 0, 0));
ASSERT_EQ(7, manager.getFreeCount());
EXPECT_EQ(nullptr, manager.peekNextOutbound(30001));
EXPECT_EQ(0, manager.getOutboundTotal());
// The manager must not silently free a packet while Dispatcher/application
// retry state can still refer to it.
EXPECT_EQ(7, manager.getFreeCount());
EXPECT_EQ(stale, manager.getNextDroppedOutbound());
EXPECT_EQ(nullptr, manager.getNextDroppedOutbound());
manager.free(stale);
EXPECT_EQ(8, manager.getFreeCount());
}
TEST(RxReservePacketManager, DispatcherNotifiesBeforeReleasingStaleOutbound) {
RxReservePacketManager manager(8, 4);
TestClock clock;
TestRadio radio;
TestDispatcher dispatcher(radio, clock, manager);
dispatcher.begin();
mesh::Packet* stale = dispatcher.obtainNewPacket();
ASSERT_NE(stale, nullptr);
stale->header = ROUTE_TYPE_DIRECT | (PAYLOAD_TYPE_RAW_CUSTOM << PH_TYPE_SHIFT);
stale->payload[0] = 0x42;
stale->payload_len = 1;
ASSERT_TRUE(dispatcher.sendPacket(stale, 0));
clock.now = 30001;
dispatcher.loop();
EXPECT_EQ(stale, dispatcher.failed_packet);
EXPECT_EQ(7, dispatcher.free_count_during_failure);
EXPECT_EQ(8, manager.getFreeCount());
EXPECT_EQ(0, radio.send_starts);
}
TEST(RxReservePacketManager, PacingPausesExpiryWithoutErasingPriorCongestion) {
for (uint32_t start : {0u, UINT32_MAX - 20000}) {
RxReservePacketManager manager(8, 4);
auto* paced = manager.allocNew();
auto* stale = manager.allocNew();
ASSERT_TRUE(manager.queueOutbound(paced, 0, start));
ASSERT_TRUE(manager.queueOutbound(stale, 1, start));
// Already spent 29 seconds waiting. One minute of deliberate pacing must
// not expire this packet, or grant another full 30 seconds afterwards.
for (uint32_t elapsed = 29000; elapsed < 89000; elapsed += 100) {
ASSERT_EQ(manager.peekNextOutbound(start + elapsed), paced);
ASSERT_TRUE(manager.deferOutboundForPacing(paced, start + elapsed, start + elapsed + 100));
}
EXPECT_EQ(manager.getNextDroppedOutbound(), stale);
manager.free(stale);
EXPECT_EQ(manager.peekNextOutbound(start + 90000), paced);
EXPECT_EQ(manager.peekNextOutbound(start + 90001), nullptr);
EXPECT_EQ(manager.getNextDroppedOutbound(), paced);
manager.free(paced);
EXPECT_EQ(manager.getFreeCount(), 8);
}
}
TEST(RxReservePacketManager, DispatcherKeepsOtaQueuedAcrossSlowQuietWindow) {
RxReservePacketManager manager(8, 4);
TestClock clock;
TestRadio radio;
TestDispatcher dispatcher(radio, clock, manager);
dispatcher.begin();
dispatcher.ota_speed = 0.05f;
for (unsigned i = 0; i < 2; ++i) {
auto* packet = dispatcher.obtainNewPacket();
ASSERT_NE(packet, nullptr);
packet->header = ROUTE_TYPE_FLOOD | (PAYLOAD_TYPE_OTA << PH_TYPE_SHIFT);
packet->payload_len = 1; packet->payload[0] = i;
ASSERT_TRUE(dispatcher.sendPacket(packet, 0));
}
clock.now = 1; dispatcher.loop();
ASSERT_EQ(radio.send_starts, 1);
clock.now = 1801; radio.send_complete = true; dispatcher.loop();
radio.send_complete = false;
for (clock.now = 1901; clock.now < 36001; clock.now += 100) {
dispatcher.loop();
ASSERT_EQ(radio.send_starts, 1);
ASSERT_EQ(dispatcher.failed_packet, nullptr);
}
clock.now = 36001; dispatcher.loop(); // 34.2 seconds of intentional quiet
EXPECT_EQ(radio.send_starts, 2);
EXPECT_EQ(dispatcher.failed_packet, nullptr);
clock.now++; radio.send_complete = true; dispatcher.loop();
EXPECT_EQ(manager.getFreeCount(), 8);
}
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}