Enable direct-route echo retries for telemetry snapshots
This commit is contained in:
1 parent
437afbdda1
commit
ea4b53ceee
4 files changed
+349
-87
No files matched your search
+16
-5
@@ -865,8 +865,16 @@ history is still sent. It uses all currently available base
|
||||
positions up to 165 and all available I2C positions up to 192 per populated
|
||||
channel, plus the latest 255 positions per noise profile (63 hours 45 minutes).
|
||||
It works while the schedule is off and does not move the next
|
||||
scheduled send time. RAW_CUSTOM direct packets do not enter the normal
|
||||
encrypted direct-message retry mechanism.
|
||||
scheduled send time.
|
||||
|
||||
With a non-empty hop path, `TTB1`, `TVB1`, `IVB1`, `N1B1`, and `N2B1` telemetry
|
||||
use the existing direct-message echo retries. They follow `direct.retry`,
|
||||
`direct.retry.count`, `direct.retry.heard`, and the direct-retry coding-rate
|
||||
policy. A matching downstream forwarding echo stops retries. The default
|
||||
zero-hop `direct` route has no forwarding echo and does not use these retries.
|
||||
Updated relays can retry their own forwarding hop; stock relays remain compatible
|
||||
and their forwarded packets can provide the echo. The packet format is unchanged:
|
||||
there is no new ACK or confirmation that the final observer received the data.
|
||||
|
||||
Full temperature and battery snapshots are 184-byte RAW_CUSTOM payloads.
|
||||
Manual tests can be shorter while history fills. Both formats are binary,
|
||||
@@ -5038,7 +5046,10 @@ region save
|
||||
---
|
||||
### Direct Retry
|
||||
|
||||
Direct retry resends direct-routed packets when the downstream echo is not heard. It applies to direct messages, ACK packets, multipart packets carrying ACK payloads, and TRACE packets.
|
||||
Direct retry resends direct-routed packets when the downstream echo is not heard.
|
||||
It applies to direct messages, ACK packets, multipart packets carrying ACK
|
||||
payloads, TRACE packets, and recognized RAW_CUSTOM telemetry (`TTB1`, `TVB1`,
|
||||
`IVB1`, `N1B1`, `N2B1`) with a non-empty hop path.
|
||||
|
||||
The shared state, count, base, and step controls work on repeater, room-server,
|
||||
and sensor firmware. Recent-repeater/SNR controls are repeater-only because the
|
||||
@@ -5396,8 +5407,8 @@ set flood.retry.bucket 2 none
|
||||
**Default:** `15` with the `rooftop` preset
|
||||
|
||||
**Note:** This setting limits retries for eligible direct-routed packets such as
|
||||
traces, requests, responses, and ACKs. Direct-routed **text messages (type 2)**
|
||||
are an exception: they allow **up to 21 retries after the original send**,
|
||||
traces, requests, responses, ACKs, and recognized telemetry. Direct-routed
|
||||
**text messages (type 2)** are an exception: they allow **up to 21 retries after the original send**,
|
||||
regardless of `direct.retry.count` or the repeater short-path cap. For example,
|
||||
`set direct.retry.count 1` limits eligible non-text packets to one retry, but
|
||||
does not reduce the text-message limit.
|
||||
|
||||
@@ -38,6 +38,15 @@ set data.tx path A1B2,C3D4
|
||||
send telemetry.tx now
|
||||
```
|
||||
|
||||
Telemetry with a non-empty hop path uses the existing direct-message echo
|
||||
retries, following `direct.retry`, `direct.retry.count`, `direct.retry.heard`,
|
||||
and the direct-retry coding-rate policy. Retries stop when a matching downstream
|
||||
forwarding echo is heard. The default zero-hop `direct` route does not use echo retries.
|
||||
Updated relays can retry their own forwarding hop; stock relays remain compatible
|
||||
and their forwarding can provide the echo. This adds no packet format or ACK:
|
||||
an echo confirms forwarding, not final delivery to the observer. A queue failure
|
||||
still retries only the packet that failed to queue, after 30 minutes.
|
||||
|
||||
The shared route does not enable telemetry. Automatic telemetry is off on a
|
||||
fresh install; to explicitly keep the route manual-only before sending:
|
||||
|
||||
|
||||
+54
-82
@@ -58,6 +58,29 @@ static bool hasValidEncryptedPayloadLength(uint16_t payload_len, uint16_t clear_
|
||||
return ((payload_len - overhead) % CIPHER_BLOCK_SIZE) == 0;
|
||||
}
|
||||
|
||||
static bool isTelemetryHistoryPayload(const Packet* packet) {
|
||||
const uint8_t* data = packet->payload;
|
||||
const uint16_t length = packet->payload_len;
|
||||
if ((uint16_t)(length - 20U) > MAX_PACKET_PAYLOAD - 20U
|
||||
|| data[2] != 'B' || data[3] != '1'
|
||||
|| (data[16] | data[17]) == 0 || data[18] == 0) return false;
|
||||
|
||||
// Recognize complete B1 history envelopes, not arbitrary custom traffic
|
||||
// which happens to begin with a telemetry tag.
|
||||
const uint16_t count = data[18];
|
||||
if (data[0] == 'T' && (data[1] == 'T' || data[1] == 'V')) {
|
||||
// The payload-size bound also caps these one-byte samples at 165.
|
||||
return length == 19U + count;
|
||||
}
|
||||
if (data[0] == 'I' && data[1] == 'V') {
|
||||
const uint16_t samples = data[19];
|
||||
return samples != 0 && samples <= 64
|
||||
&& length == 20U + (samples * 15U + 7U) / 8U;
|
||||
}
|
||||
return data[0] == 'N' && (uint8_t)(data[1] - '1') <= 1
|
||||
&& length == 19U + (count * 5U + 7U) / 8U;
|
||||
}
|
||||
|
||||
static uint8_t decodeTraceHashSize(uint8_t flags, uint8_t route_bytes) {
|
||||
uint8_t code = flags & 0x03;
|
||||
uint8_t size_pow2 = (uint8_t)(1U << code); // legacy TRACE interpretation
|
||||
@@ -301,31 +324,7 @@ void Mesh::begin() {
|
||||
_next_recent_advert_echo = 0;
|
||||
_next_direct_retry_timeout = 0;
|
||||
_next_flood_retry_timeout = 0;
|
||||
for (int i = 0; i < TOTAL_DIRECT_RETRY_SLOTS; i++) {
|
||||
_direct_retries[i].packet = NULL;
|
||||
_direct_retries[i].trigger_packet = NULL;
|
||||
_direct_retries[i].retry_started_at = 0;
|
||||
_direct_retries[i].echo_wait_started_at = 0;
|
||||
_direct_retries[i].retry_at = 0;
|
||||
_direct_retries[i].retry_delay = 0;
|
||||
_direct_retries[i].message_timestamp = 0;
|
||||
_direct_retries[i].retry_attempts_sent = 0;
|
||||
memset(_direct_retries[i].retry_key, 0, sizeof(_direct_retries[i].retry_key));
|
||||
memset(_direct_retries[i].trace_replacement_key, 0, sizeof(_direct_retries[i].trace_replacement_key));
|
||||
memset(_direct_retries[i].message_replacement_key, 0,
|
||||
sizeof(_direct_retries[i].message_replacement_key));
|
||||
memset(_direct_retries[i].next_hop_hash, 0, sizeof(_direct_retries[i].next_hop_hash));
|
||||
_direct_retries[i].next_hop_hash_len = 0;
|
||||
_direct_retries[i].payload_type = 0;
|
||||
_direct_retries[i].priority = 0;
|
||||
_direct_retries[i].progress_marker = 0;
|
||||
_direct_retries[i].expect_path_growth = false;
|
||||
_direct_retries[i].final_hop_retry = false;
|
||||
_direct_retries[i].waiting_final_echo = false;
|
||||
_direct_retries[i].queued = false;
|
||||
_direct_retries[i].has_message_replacement_key = false;
|
||||
_direct_retries[i].active = false;
|
||||
}
|
||||
memset(_direct_retries, 0, sizeof(_direct_retries));
|
||||
for (int i = 0; i < TOTAL_FLOOD_RETRY_SLOTS; i++) {
|
||||
_flood_retries[i].packet = NULL;
|
||||
_flood_retries[i].trigger_packet = NULL;
|
||||
@@ -1645,29 +1644,7 @@ void Mesh::clearDirectRetrySlot(int idx) {
|
||||
if (_direct_retries[idx].active && _active_direct_retry_count > 0) {
|
||||
_active_direct_retry_count--;
|
||||
}
|
||||
_direct_retries[idx].packet = NULL;
|
||||
_direct_retries[idx].trigger_packet = NULL;
|
||||
_direct_retries[idx].retry_started_at = 0;
|
||||
_direct_retries[idx].echo_wait_started_at = 0;
|
||||
_direct_retries[idx].retry_at = 0;
|
||||
_direct_retries[idx].retry_delay = 0;
|
||||
_direct_retries[idx].message_timestamp = 0;
|
||||
_direct_retries[idx].retry_attempts_sent = 0;
|
||||
memset(_direct_retries[idx].retry_key, 0, sizeof(_direct_retries[idx].retry_key));
|
||||
memset(_direct_retries[idx].trace_replacement_key, 0, sizeof(_direct_retries[idx].trace_replacement_key));
|
||||
memset(_direct_retries[idx].message_replacement_key, 0,
|
||||
sizeof(_direct_retries[idx].message_replacement_key));
|
||||
memset(_direct_retries[idx].next_hop_hash, 0, sizeof(_direct_retries[idx].next_hop_hash));
|
||||
_direct_retries[idx].next_hop_hash_len = 0;
|
||||
_direct_retries[idx].payload_type = 0;
|
||||
_direct_retries[idx].priority = 0;
|
||||
_direct_retries[idx].progress_marker = 0;
|
||||
_direct_retries[idx].expect_path_growth = false;
|
||||
_direct_retries[idx].final_hop_retry = false;
|
||||
_direct_retries[idx].waiting_final_echo = false;
|
||||
_direct_retries[idx].queued = false;
|
||||
_direct_retries[idx].has_message_replacement_key = false;
|
||||
_direct_retries[idx].active = false;
|
||||
memset(&_direct_retries[idx], 0, sizeof(_direct_retries[idx]));
|
||||
if (rebuild_timeout) rebuildNextDirectRetryTimeout();
|
||||
}
|
||||
|
||||
@@ -2028,6 +2005,11 @@ void Mesh::clearPendingDirectRetryOnSendFail(const Packet* packet) {
|
||||
bool Mesh::getDirectRetryTarget(const Packet* packet, const uint8_t*& next_hop_hash, uint8_t& next_hop_hash_len,
|
||||
uint8_t& progress_marker, bool& expect_path_growth) const {
|
||||
switch (packet->getPayloadType()) {
|
||||
case PAYLOAD_TYPE_RAW_CUSTOM:
|
||||
if (!isTelemetryHistoryPayload(packet)) return false;
|
||||
// Telemetry uses the same shrinking-path echo as other direct packets.
|
||||
// Zero-hop sends have no forwarding echo and remain excluded below.
|
||||
// fall through
|
||||
case PAYLOAD_TYPE_ACK:
|
||||
case PAYLOAD_TYPE_PATH:
|
||||
case PAYLOAD_TYPE_REQ:
|
||||
@@ -2036,24 +2018,13 @@ bool Mesh::getDirectRetryTarget(const Packet* packet, const uint8_t*& next_hop_h
|
||||
case PAYLOAD_TYPE_ANON_REQ:
|
||||
// Allow retries even when only one downstream hop remains so fixed direct paths
|
||||
// (e.g. remote admin/login over 2-hop chains) use the same retry policy.
|
||||
if (packet->getPathHashCount() == 0) {
|
||||
return false;
|
||||
}
|
||||
next_hop_hash = packet->path;
|
||||
next_hop_hash_len = packet->getPathHashSize();
|
||||
progress_marker = packet->getPathHashCount();
|
||||
expect_path_growth = false;
|
||||
return true;
|
||||
break;
|
||||
|
||||
case PAYLOAD_TYPE_MULTIPART:
|
||||
if (packet->payload_len < 1 || (packet->payload[0] & 0x0F) != PAYLOAD_TYPE_ACK || packet->getPathHashCount() == 0) {
|
||||
if (packet->payload_len < 1 || (packet->payload[0] & 0x0F) != PAYLOAD_TYPE_ACK) {
|
||||
return false;
|
||||
}
|
||||
next_hop_hash = packet->path;
|
||||
next_hop_hash_len = packet->getPathHashSize();
|
||||
progress_marker = packet->getPathHashCount();
|
||||
expect_path_growth = false;
|
||||
return true;
|
||||
break;
|
||||
|
||||
case PAYLOAD_TYPE_TRACE: {
|
||||
if (packet->payload_len < 9) {
|
||||
@@ -2077,6 +2048,13 @@ bool Mesh::getDirectRetryTarget(const Packet* packet, const uint8_t*& next_hop_h
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
if (packet->getPathHashCount() == 0) return false;
|
||||
next_hop_hash = packet->path;
|
||||
next_hop_hash_len = packet->getPathHashSize();
|
||||
progress_marker = packet->getPathHashCount();
|
||||
expect_path_growth = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Mesh::canDecodeDirectPayloadForSelf(const Packet* packet) {
|
||||
@@ -2202,27 +2180,21 @@ void Mesh::maybeScheduleDirectRetry(const Packet* packet, uint8_t priority, bool
|
||||
|
||||
// Only store retry metadata here; allocate the retry packet after the initial TX really completes.
|
||||
uint32_t retry_delay = getDirectRetryAttemptDelay(packet, 0);
|
||||
memcpy(_direct_retries[slot_idx].retry_key, retry_key, sizeof(retry_key));
|
||||
memcpy(_direct_retries[slot_idx].trace_replacement_key, trace_replacement_key,
|
||||
DirectRetryEntry& entry = _direct_retries[slot_idx];
|
||||
memset(&entry, 0, sizeof(entry));
|
||||
memcpy(entry.retry_key, retry_key, sizeof(retry_key));
|
||||
memcpy(entry.trace_replacement_key, trace_replacement_key,
|
||||
sizeof(trace_replacement_key));
|
||||
_direct_retries[slot_idx].packet = NULL;
|
||||
_direct_retries[slot_idx].trigger_packet = const_cast<Packet*>(packet);
|
||||
_direct_retries[slot_idx].retry_started_at = 0;
|
||||
_direct_retries[slot_idx].echo_wait_started_at = 0;
|
||||
_direct_retries[slot_idx].retry_at = 0;
|
||||
_direct_retries[slot_idx].retry_delay = retry_delay;
|
||||
_direct_retries[slot_idx].retry_attempts_sent = 0;
|
||||
memset(_direct_retries[slot_idx].next_hop_hash, 0, sizeof(_direct_retries[slot_idx].next_hop_hash));
|
||||
memcpy(_direct_retries[slot_idx].next_hop_hash, next_hop_hash, next_hop_hash_len);
|
||||
_direct_retries[slot_idx].next_hop_hash_len = next_hop_hash_len;
|
||||
_direct_retries[slot_idx].payload_type = packet->getPayloadType();
|
||||
_direct_retries[slot_idx].priority = priority;
|
||||
_direct_retries[slot_idx].progress_marker = progress_marker;
|
||||
_direct_retries[slot_idx].expect_path_growth = expect_path_growth;
|
||||
_direct_retries[slot_idx].final_hop_retry = final_hop_retry;
|
||||
_direct_retries[slot_idx].waiting_final_echo = false;
|
||||
_direct_retries[slot_idx].queued = false;
|
||||
_direct_retries[slot_idx].active = true;
|
||||
entry.trigger_packet = const_cast<Packet*>(packet);
|
||||
entry.retry_delay = retry_delay;
|
||||
memcpy(entry.next_hop_hash, next_hop_hash, next_hop_hash_len);
|
||||
entry.next_hop_hash_len = next_hop_hash_len;
|
||||
entry.payload_type = packet->getPayloadType();
|
||||
entry.priority = priority;
|
||||
entry.progress_marker = progress_marker;
|
||||
entry.expect_path_growth = expect_path_growth;
|
||||
entry.final_hop_retry = final_hop_retry;
|
||||
entry.active = true;
|
||||
_active_direct_retry_count++;
|
||||
}
|
||||
|
||||
|
||||
@@ -426,6 +426,276 @@ TEST_F(RetryCodingRateTest, DirectTransmissionsKeepTheirScheduleWithFloodRetryOf
|
||||
}
|
||||
}
|
||||
|
||||
class TelemetryRetryMesh : public RetryCodingRateMesh {
|
||||
public:
|
||||
using RetryCodingRateMesh::RetryCodingRateMesh;
|
||||
bool direct_enabled = true;
|
||||
bool allowDirectRetry(const mesh::Packet*, const uint8_t*, uint8_t) const override {
|
||||
return direct_enabled;
|
||||
}
|
||||
};
|
||||
|
||||
class TelemetryDirectRetryTest : public testing::TestWithParam<const char*> {
|
||||
protected:
|
||||
TraceTestClock clock;
|
||||
TraceTestRTC rtc;
|
||||
TraceTestRNG rng;
|
||||
RetryCodingRateRadio radio;
|
||||
TraceTestTables tables;
|
||||
StaticPoolPacketManager manager{12};
|
||||
TelemetryRetryMesh node{radio, clock, rng, rtc, manager, tables};
|
||||
const uint8_t path[2] = {0x12, 0x34};
|
||||
|
||||
void SetUp() override {
|
||||
node.begin();
|
||||
node.direct_attempts = 2;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> snapshot(uint8_t count = 4) const {
|
||||
const char* magic = GetParam();
|
||||
const bool external = memcmp(magic, "IVB1", 4) == 0;
|
||||
const uint8_t bits = external ? 15 : magic[0] == 'N' ? 5 : 8;
|
||||
const size_t header = external ? 20 : 19;
|
||||
std::vector<uint8_t> payload(header + (count * bits + 7U) / 8U, 0x51);
|
||||
memcpy(payload.data(), magic, 4);
|
||||
payload[16] = magic[0] == 'N' ? 15 : 30;
|
||||
payload[17] = 0;
|
||||
payload[18] = external ? 1 : count;
|
||||
if (external) payload[19] = count;
|
||||
return payload;
|
||||
}
|
||||
|
||||
mesh::Packet* queue(const std::vector<uint8_t>& payload, uint8_t hops = 2) {
|
||||
auto* packet = node.createRawData(payload.data(), payload.size());
|
||||
EXPECT_NE(nullptr, packet);
|
||||
if (packet && !node.sendDirect(packet, path, hops)) {
|
||||
ADD_FAILURE() << "Could not queue telemetry packet";
|
||||
return nullptr;
|
||||
}
|
||||
return packet;
|
||||
}
|
||||
|
||||
void transmitNext() {
|
||||
clock.now += 10000;
|
||||
node.loop();
|
||||
ASSERT_TRUE(radio.sending);
|
||||
radio.complete = true;
|
||||
++clock.now;
|
||||
node.loop();
|
||||
ASSERT_FALSE(radio.sending);
|
||||
}
|
||||
|
||||
void expectIdle() {
|
||||
clock.now += 10000;
|
||||
node.loop();
|
||||
EXPECT_FALSE(radio.sending);
|
||||
EXPECT_EQ(0, manager.getOutboundTotal());
|
||||
EXPECT_EQ(12, manager.getFreeCount());
|
||||
}
|
||||
};
|
||||
|
||||
TEST_P(TelemetryDirectRetryTest, MissingEchoUsesTheDirectRetryBudgetAndCodingRates) {
|
||||
auto* packet = queue(snapshot());
|
||||
ASSERT_NE(nullptr, packet);
|
||||
const mesh::Packet original = *packet;
|
||||
for (uint8_t attempt = 0; attempt <= node.direct_attempts; ++attempt) {
|
||||
transmitNext();
|
||||
if (attempt == node.direct_attempts) continue;
|
||||
ASSERT_EQ(1, manager.getOutboundTotal());
|
||||
const auto* retry = manager.getOutboundByIdx(0);
|
||||
EXPECT_EQ(original.header, retry->header);
|
||||
EXPECT_EQ(original.payload_len, retry->payload_len);
|
||||
EXPECT_EQ(0, memcmp(original.payload, retry->payload, original.payload_len));
|
||||
EXPECT_EQ(original.path_len, retry->path_len);
|
||||
EXPECT_EQ(0, memcmp(path, retry->path, sizeof(path)));
|
||||
EXPECT_EQ(original.radio_profile, retry->radio_profile);
|
||||
}
|
||||
expectIdle();
|
||||
EXPECT_EQ((std::vector<uint8_t>{5, 5, 7}), radio.transmitted_crs);
|
||||
}
|
||||
|
||||
TEST_P(TelemetryDirectRetryTest, RetryIsAllocatedOnlyAfterTheRadioCompletesSending) {
|
||||
ASSERT_NE(nullptr, queue(snapshot()));
|
||||
++clock.now;
|
||||
node.loop();
|
||||
ASSERT_TRUE(radio.sending);
|
||||
EXPECT_EQ(0, manager.getOutboundTotal());
|
||||
clock.now += 10; // still within this fake radio's 15 ms TX timeout
|
||||
node.loop();
|
||||
EXPECT_TRUE(radio.sending);
|
||||
EXPECT_EQ(0, manager.getOutboundTotal());
|
||||
EXPECT_EQ(1U, radio.transmitted_crs.size());
|
||||
radio.complete = true;
|
||||
++clock.now;
|
||||
node.loop();
|
||||
EXPECT_FALSE(radio.sending);
|
||||
ASSERT_EQ(1, manager.getOutboundTotal());
|
||||
transmitNext();
|
||||
EXPECT_EQ(2U, radio.transmitted_crs.size());
|
||||
}
|
||||
|
||||
TEST_P(TelemetryDirectRetryTest, MatchingDownstreamEchoCancelsTheQueuedRetry) {
|
||||
auto* packet = queue(snapshot());
|
||||
ASSERT_NE(nullptr, packet);
|
||||
mesh::Packet echo = *packet;
|
||||
transmitNext();
|
||||
ASSERT_EQ(1, manager.getOutboundTotal());
|
||||
echo.setPathHashCount(1);
|
||||
echo.path[0] = path[1];
|
||||
node.receivePacket(&echo);
|
||||
expectIdle();
|
||||
EXPECT_EQ((std::vector<uint8_t>{5}), radio.transmitted_crs);
|
||||
}
|
||||
|
||||
TEST_P(TelemetryDirectRetryTest, RelaysRetryTheRemainingRouteForEveryHashWidth) {
|
||||
node.forwardFloods = true;
|
||||
const uint8_t route[] = {0x12, 0x13, 0x14, 0x34, 0x35, 0x36, 0x56, 0x57, 0x58};
|
||||
for (uint8_t width : {1, 2, 3}) {
|
||||
SCOPED_TRACE(width);
|
||||
const auto payload = snapshot();
|
||||
auto* incoming = node.createRawData(payload.data(), payload.size());
|
||||
ASSERT_NE(nullptr, incoming);
|
||||
incoming->header |= ROUTE_TYPE_DIRECT;
|
||||
incoming->setPathHashSizeAndCount(width, 3);
|
||||
memcpy(incoming->path, route, width * 3);
|
||||
memcpy(node.self_id.pub_key, route, width);
|
||||
ASSERT_NE(ACTION_RELEASE, node.receivePacket(incoming));
|
||||
EXPECT_TRUE(incoming->forwarded);
|
||||
EXPECT_EQ(width, incoming->getPathHashSize());
|
||||
EXPECT_EQ(2, incoming->getPathHashCount());
|
||||
EXPECT_EQ(0, memcmp(route + width, incoming->path, width * 2));
|
||||
const mesh::Packet forwarded = *incoming;
|
||||
ASSERT_TRUE(node.sendPacket(incoming, 0));
|
||||
transmitNext();
|
||||
ASSERT_EQ(1, manager.getOutboundTotal());
|
||||
const auto* retry = manager.getOutboundByIdx(0);
|
||||
EXPECT_EQ(forwarded.path_len, retry->path_len);
|
||||
EXPECT_EQ(0, memcmp(forwarded.path, retry->path, width * 2));
|
||||
transmitNext();
|
||||
auto echo = forwarded;
|
||||
echo.setPathHashCount(1);
|
||||
memcpy(echo.path, route + width * 2, width);
|
||||
node.receivePacket(&echo);
|
||||
expectIdle();
|
||||
|
||||
auto* last_hop = node.createRawData(payload.data(), payload.size());
|
||||
ASSERT_NE(nullptr, last_hop);
|
||||
last_hop->header |= ROUTE_TYPE_DIRECT;
|
||||
last_hop->setPathHashSizeAndCount(width, 1);
|
||||
memcpy(last_hop->path, route, width);
|
||||
ASSERT_NE(ACTION_RELEASE, node.receivePacket(last_hop));
|
||||
EXPECT_EQ(0, last_hop->getPathHashCount());
|
||||
ASSERT_TRUE(node.sendPacket(last_hop, 0));
|
||||
transmitNext();
|
||||
expectIdle();
|
||||
}
|
||||
EXPECT_EQ(9U, radio.transmitted_crs.size()); // initial, retry, final relay per width
|
||||
}
|
||||
|
||||
TEST_P(TelemetryDirectRetryTest, EchoMustMatchPayloadRadioAndForwardProgress) {
|
||||
auto* packet = queue(snapshot());
|
||||
ASSERT_NE(nullptr, packet);
|
||||
const mesh::Packet original = *packet;
|
||||
transmitNext();
|
||||
ASSERT_EQ(1, manager.getOutboundTotal());
|
||||
|
||||
auto wrong_payload = original;
|
||||
wrong_payload.setPathHashCount(1);
|
||||
wrong_payload.payload[wrong_payload.payload_len - 1] ^= 1;
|
||||
node.receivePacket(&wrong_payload);
|
||||
EXPECT_EQ(1, manager.getOutboundTotal());
|
||||
|
||||
auto wrong_radio = original;
|
||||
wrong_radio.setPathHashCount(1);
|
||||
wrong_radio.radio_profile = 1;
|
||||
node.receivePacket(&wrong_radio);
|
||||
EXPECT_EQ(1, manager.getOutboundTotal());
|
||||
|
||||
auto no_progress = original;
|
||||
node.receivePacket(&no_progress);
|
||||
EXPECT_EQ(1, manager.getOutboundTotal());
|
||||
|
||||
transmitNext();
|
||||
EXPECT_EQ((std::vector<uint8_t>{5, 5}), radio.transmitted_crs);
|
||||
auto valid_echo = original;
|
||||
valid_echo.setPathHashCount(0);
|
||||
node.receivePacket(&valid_echo);
|
||||
expectIdle();
|
||||
}
|
||||
|
||||
TEST_P(TelemetryDirectRetryTest, ZeroHopRemainsASingleTransmission) {
|
||||
ASSERT_NE(nullptr, queue(snapshot(), 0));
|
||||
transmitNext();
|
||||
expectIdle();
|
||||
EXPECT_EQ((std::vector<uint8_t>{5}), radio.transmitted_crs);
|
||||
}
|
||||
|
||||
TEST_P(TelemetryDirectRetryTest, DirectRetrySettingStillGatesTelemetry) {
|
||||
node.direct_enabled = false;
|
||||
ASSERT_NE(nullptr, queue(snapshot()));
|
||||
transmitNext();
|
||||
expectIdle();
|
||||
EXPECT_EQ((std::vector<uint8_t>{5}), radio.transmitted_crs);
|
||||
}
|
||||
|
||||
TEST_P(TelemetryDirectRetryTest, LargestSnapshotOfEachFamilyCanRetry) {
|
||||
const char* magic = GetParam();
|
||||
const uint8_t maximum = magic[0] == 'N' ? 255 : magic[0] == 'I' ? 64 : 165;
|
||||
auto* packet = queue(snapshot(maximum));
|
||||
ASSERT_NE(nullptr, packet);
|
||||
mesh::Packet echo = *packet;
|
||||
transmitNext();
|
||||
ASSERT_EQ(1, manager.getOutboundTotal());
|
||||
transmitNext();
|
||||
EXPECT_EQ((std::vector<uint8_t>{5, 5}), radio.transmitted_crs);
|
||||
echo.setPathHashCount(1);
|
||||
node.receivePacket(&echo);
|
||||
expectIdle();
|
||||
}
|
||||
|
||||
TEST_P(TelemetryDirectRetryTest, MalformedAndUnrelatedCustomPayloadsDoNotRetry) {
|
||||
const auto valid = snapshot();
|
||||
std::vector<std::vector<uint8_t>> invalid;
|
||||
invalid.emplace_back(valid.begin(), valid.begin() + 3);
|
||||
invalid.push_back(valid);
|
||||
invalid.back().pop_back();
|
||||
invalid.push_back(valid);
|
||||
invalid.back().push_back(0);
|
||||
invalid.push_back(valid);
|
||||
invalid.back()[16] = invalid.back()[17] = 0;
|
||||
invalid.push_back(valid);
|
||||
invalid.back()[3] = '2';
|
||||
invalid.push_back(valid);
|
||||
invalid.back()[0] = 'X';
|
||||
invalid.push_back(valid);
|
||||
invalid.back()[1] = 'X';
|
||||
invalid.push_back(valid);
|
||||
invalid.back()[2] = 'X';
|
||||
const bool external = GetParam()[0] == 'I';
|
||||
invalid.push_back(valid);
|
||||
invalid.back()[external ? 19 : 18] = 0;
|
||||
invalid.back().resize(external ? 20 : 19);
|
||||
if (external) {
|
||||
invalid.push_back(valid);
|
||||
invalid.back()[18] = 0;
|
||||
invalid.push_back(valid);
|
||||
invalid.back()[19] = 65;
|
||||
} else if (GetParam()[0] == 'T') {
|
||||
invalid.push_back(valid);
|
||||
invalid.back()[18] = 166;
|
||||
}
|
||||
for (size_t i = 0; i < invalid.size(); ++i) {
|
||||
SCOPED_TRACE(i);
|
||||
ASSERT_NE(nullptr, queue(invalid[i]));
|
||||
transmitNext();
|
||||
expectIdle();
|
||||
}
|
||||
EXPECT_EQ(invalid.size(), radio.transmitted_crs.size());
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(TelemetryFamilies, TelemetryDirectRetryTest,
|
||||
testing::Values("TTB1", "TVB1", "IVB1", "N1B1", "N2B1"));
|
||||
|
||||
static mesh::Packet makeOtaManifestFragment(uint8_t format_version) {
|
||||
mesh::Packet packet = makeFloodPacket(PAYLOAD_TYPE_OTA);
|
||||
packet.payload_len = 12;
|
||||
|
||||
Reference in new issue
Block a user