mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-07-28 07:09:21 +00:00
280 lines
9.2 KiB
C++
280 lines
9.2 KiB
C++
#include <gtest/gtest.h>
|
|
|
|
#include <helpers/RxReservePacketManager.h>
|
|
|
|
class TestClock : public mesh::MillisecondClock {
|
|
public:
|
|
unsigned long now = 0;
|
|
unsigned long getMillis() override { return now; }
|
|
};
|
|
|
|
class TestRadio : public mesh::Radio {
|
|
public:
|
|
int send_starts = 0;
|
|
bool receiving = false;
|
|
|
|
int recvRaw(uint8_t*, int) override { return 0; }
|
|
uint32_t getEstAirtimeFor(int) override { return 1; }
|
|
float packetScore(float, int) override { return 0; }
|
|
bool startSendRaw(const uint8_t*, int) override { send_starts++; return true; }
|
|
bool isSendComplete() override { return false; }
|
|
void onSendFinished() override { }
|
|
bool isInRecvMode() const override { return true; }
|
|
bool isReceiving() override { return receiving; }
|
|
};
|
|
|
|
class TestDispatcher : public mesh::Dispatcher {
|
|
RxReservePacketManager& manager;
|
|
|
|
protected:
|
|
mesh::DispatcherAction onRecvPacket(mesh::Packet*) override { return ACTION_RELEASE; }
|
|
void onSendFail(mesh::Packet* packet) override {
|
|
failed_packet = packet;
|
|
free_count_during_failure = manager.getFreeCount();
|
|
}
|
|
|
|
public:
|
|
mesh::Packet* failed_packet = nullptr;
|
|
int free_count_during_failure = -1;
|
|
|
|
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);
|
|
}
|
|
};
|
|
|
|
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(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);
|
|
}
|
|
|
|
TEST(Dispatcher, QueueWakeDelayIncludesSchedulesAndChannelBackoff) {
|
|
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());
|
|
|
|
clock.now = 800;
|
|
radio.receiving = false;
|
|
EXPECT_TRUE(dispatcher.queuedWorkDue());
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
int main(int argc, char** argv) {
|
|
::testing::InitGoogleTest(&argc, argv);
|
|
return RUN_ALL_TESTS();
|
|
}
|