mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-07-24 00:50:58 +00:00
Refine trace and group data retries
This commit is contained in:
+35
-15
@@ -1261,7 +1261,7 @@ set direct.retry.heard off
|
||||
- `infra`: fewer, slower retries for stable fixed infrastructure.
|
||||
- `rooftop`: default long retry window for weak rooftop links.
|
||||
- `mobile`: long retry count with shorter spacing for moving or changing links; flood retry count is `15`.
|
||||
- Changing `direct.retry.count`, `direct.retry.base`, `direct.retry.step`, `direct.retry.margin`, `flood.retry.count`, or `flood.retry.path` makes the preset report as `custom`.
|
||||
- Changing `direct.retry.count`, `direct.retry.base`, `direct.retry.step`, `direct.retry.margin`, `flood.retry.count`, `flood.retry.path`, or `flood.retry.group.path` makes the preset report as `custom`.
|
||||
|
||||
**Examples:**
|
||||
```
|
||||
@@ -1278,7 +1278,7 @@ set retry.preset mobile
|
||||
Flood retry resends flood-routed packets when the same packet is not heard from
|
||||
another qualifying repeater.
|
||||
|
||||
The count, path, and advert controls work on repeater, room-server, and sensor
|
||||
The count, path, group-data path, and advert controls work on repeater, room-server, and sensor
|
||||
firmware. Flood forwarding must also be enabled for retries to run. Prefix,
|
||||
ignore, bridge, and bucket controls are repeater-only.
|
||||
|
||||
@@ -1329,6 +1329,30 @@ set flood.retry.path off
|
||||
|
||||
---
|
||||
|
||||
#### View or change the group-data flood retry path gate
|
||||
**Usage:**
|
||||
- `get flood.retry.group.path`
|
||||
- `set flood.retry.group.path <count|off>`
|
||||
|
||||
**Parameters:**
|
||||
- `count`: Maximum flood path hash count eligible for retry for group data packets (`PAYLOAD_TYPE_GRP_DATA`/type 6), from `0` to `63`.
|
||||
- `off`: Disable only the group-data-specific gate. The general `flood.retry.path` gate still applies.
|
||||
|
||||
**Default:** `1` for `infra`, `rooftop`, and `mobile` presets.
|
||||
|
||||
**Note:** The stricter of `flood.retry.path` and `flood.retry.group.path` is used. A value of `1` allows retry sequences at path counts `0` and `1`; group data at path count `2` or higher is still forwarded normally but does not start a flood retry sequence. A value of `0` allows retries only at the originating sender.
|
||||
|
||||
Setting `flood.retry.path` to `0` also sets `flood.retry.group.path` to `off` because the general zero-hop gate is already stricter. While the general gate remains `0`, attempts to set the group-data gate keep it `off`. Applying a named retry preset restores the group-data default of `1`.
|
||||
|
||||
**Examples:**
|
||||
```
|
||||
get flood.retry.group.path
|
||||
set flood.retry.group.path 1
|
||||
set flood.retry.group.path off
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### View or change flood retry advert handling
|
||||
**Usage:**
|
||||
- `get flood.retry.advert`
|
||||
@@ -1466,15 +1490,13 @@ set direct.retry.count 15
|
||||
**Default:** `175` with the `rooftop` preset
|
||||
|
||||
**Explanation:**
|
||||
- The first retry waits `base` milliseconds after the failed echo window.
|
||||
- The failed echo window includes a packet-length add-on. TRACE and
|
||||
ANON_REQ/type 7 packets keep the existing 4x line-time add-on. TXT_MSG/type 2
|
||||
- The first retry waits for `base + packet-length add-on + random forwarding jitter`
|
||||
after the preceding transmission completes.
|
||||
- TRACE and
|
||||
ANON_REQ/type 7 packets use a 3x line-time add-on. TXT_MSG/type 2
|
||||
packets use 7x. Other direct retry packets use 6x.
|
||||
- Room-server and sensor firmware use this configured base with the same
|
||||
packet-type add-ons.
|
||||
- For non-TRACE direct paths shorter than 6 remaining hops, the effective wait is scaled by `hops / 6`.
|
||||
- Non-TRACE direct paths with 6 or more remaining hops use the configured value unchanged.
|
||||
- TRACE retries shorter than 16 remaining hops use `hops / 16`; 16 or more remaining hops use the configured value unchanged.
|
||||
- Larger values reduce channel pressure and give slow repeaters more time.
|
||||
- Smaller values recover faster but create tighter retry bursts.
|
||||
|
||||
@@ -1499,16 +1521,14 @@ set direct.retry.base 500
|
||||
**Default:** `100` with the `rooftop` preset
|
||||
|
||||
**Explanation:**
|
||||
- Retry delay is `base + attempt_index * step`.
|
||||
- This is added after the failed echo window. TRACE and ANON_REQ/type 7 packets
|
||||
keep the existing 4x packet-length add-on. TXT_MSG/type 2 packets use 7x.
|
||||
- Retry delay is `base + packet-length add-on + random forwarding jitter + attempt_index * step`.
|
||||
- TRACE and ANON_REQ/type 7 packets
|
||||
use a 3x packet-length add-on. TXT_MSG/type 2 packets use 7x.
|
||||
Other direct retry packets use 6x.
|
||||
- Room-server and sensor firmware use this configured step with the same
|
||||
packet-type add-ons.
|
||||
- For non-TRACE direct paths shorter than 6 remaining hops, that computed delay is scaled by `hops / 6`.
|
||||
- Non-TRACE direct paths with 6 or more remaining hops use the computed delay unchanged.
|
||||
- TRACE retries shorter than 16 remaining hops use `hops / 16`; 16 or more remaining hops use the computed delay unchanged.
|
||||
- With `base=175` and `step=100`, non-TRACE paths with 6 or more remaining hops wait about `175`, `275`, `375`, `475` ms, and so on.
|
||||
- With `base=175` and `step=100`, the fixed portion is `175`, `275`, `375`,
|
||||
`475` ms, and so on, before the packet-length add-on and random jitter.
|
||||
- `step=0` keeps every retry at the same delay.
|
||||
- Larger steps spread retries over time and are safer on busy channels.
|
||||
|
||||
|
||||
@@ -210,8 +210,8 @@ Direct retry applies to direct-routed packets. A queued resend is canceled when
|
||||
| `direct.retry.heard` | Uses the recent repeater table as the direct retry eligibility gate. | `get direct.retry.heard`, `set direct.retry.heard on/off` | `set direct.retry.heard on` |
|
||||
| `direct.retry.margin` | SNR margin in dB above the SF-specific receive floor. | `get direct.retry.margin`, `set direct.retry.margin <0-40>` | `set direct.retry.margin 5` |
|
||||
| `direct.retry.count` | Maximum direct retry attempts after initial TX. Direct-routed type 2 text packets always use 21 attempts regardless of this setting or the short-path cap. | `get direct.retry.count`, `set direct.retry.count <1-15>` | `set direct.retry.count 15` |
|
||||
| `direct.retry.base` | Base wait in milliseconds before retry; packet-length add-on is 4x for TRACE and ANON_REQ/type 7, 7x for TXT_MSG/type 2, and 6x for other direct retry packets. Non-TRACE paths under 6 remaining hops scale by `hops / 6`, TRACE paths under 16 by `hops / 16`. | `get direct.retry.base`, `set direct.retry.base <10-5000>` | `set direct.retry.base 175` |
|
||||
| `direct.retry.step` | Milliseconds added per retry attempt before the same short-path scaling. | `get direct.retry.step`, `set direct.retry.step <0-5000>` | `set direct.retry.step 100` |
|
||||
| `direct.retry.base` | Base wait in milliseconds before retry; packet-length add-on is 3x for TRACE and ANON_REQ/type 7, 7x for TXT_MSG/type 2, and 6x for other direct retry packets. | `get direct.retry.base`, `set direct.retry.base <10-5000>` | `set direct.retry.base 175` |
|
||||
| `direct.retry.step` | Milliseconds added per retry attempt after the base, packet-length add-on, and random forwarding jitter. | `get direct.retry.step`, `set direct.retry.step <0-5000>` | `set direct.retry.step 100` |
|
||||
| `direct.retry.cr` | Adaptive coding-rate thresholds for repeater direct retry packets. Repeaters use `CR4`, `CR5`, `CR7`, or `CR8`, then escalate by attempt: CR4, CR5, CR7, CR7, then CR8 from a CR4 start; CR5, CR7, CR7, then CR8 from a CR5 start. Non-repeaters start at the current radio CR and follow the same escalation pattern, clamped at CR8. | `get direct.retry.cr`, `set direct.retry.cr <cr4_min>,<cr5_min>,<cr7_min>,<cr8_max>`, `set direct.retry.cr off` | `set direct.retry.cr 10.0,7.5,2.5,0` |
|
||||
|
||||
The default adaptive coding-rate profile is `10.0,7.5,2.5,2.5`.
|
||||
@@ -257,6 +257,7 @@ the bucket rules below instead.
|
||||
| --- | --- | --- | --- |
|
||||
| `flood.retry.count` | Base flood retry attempts after initial TX. Path count 0 doubles it, path count 1 uses 1.5x rounded up, path count 2+ uses the base, and actual attempts cap at `15`; `0` disables flood retry. | `get flood.retry.count`, `set flood.retry.count <0-15>` | `set flood.retry.count 7` |
|
||||
| `flood.retry.path` | Maximum path hash count eligible for flood retry, or `off` to disable the gate. | `get flood.retry.path`, `set flood.retry.path <0-63/off>` | `set flood.retry.path 1` |
|
||||
| `flood.retry.group.path` | Additional path gate for group data (`type=6`) flood retries. The stricter of this and `flood.retry.path` applies; `off` disables only this additional gate. Setting the general path gate to `0` forces this setting to `off`; a named preset restores the default of `1`. | `get flood.retry.group.path`, `set flood.retry.group.path <0-63/off>` | `set flood.retry.group.path 1` |
|
||||
| `flood.retry.advert` | Allows or blocks retry for node advert packets (`type=4`). Default is `off`. | `get flood.retry.advert`, `set flood.retry.advert on/off` | `set flood.retry.advert off` |
|
||||
| `flood.retry.prefixes` | Target prefixes. If set, only same-packet echoes from matching last-hop prefixes cancel a retry. | `get flood.retry.prefixes`, `set flood.retry.prefixes <prefixes/none/off>` | `set flood.retry.prefixes BEEBB0,425E5C` |
|
||||
| `flood.retry.ignore` | Ignored prefixes. In non-bridge retry, ignored last-hop echoes do not cancel retry. | `get flood.retry.ignore`, `set flood.retry.ignore <prefixes/none/off>` | `set flood.retry.ignore 71CE82,C7618C` |
|
||||
@@ -266,17 +267,18 @@ the bucket rules below instead.
|
||||
|
||||
The shared retry preset sets these flood defaults:
|
||||
|
||||
| Preset | Retry count | Path gate |
|
||||
| --- | ---: | ---: |
|
||||
| `infra` | `1` | `1` |
|
||||
| `rooftop` | `3` | `2` |
|
||||
| `mobile` | `15` | `1` |
|
||||
| Preset | Retry count | Path gate | Group-data path gate |
|
||||
| --- | ---: | ---: | ---: |
|
||||
| `infra` | `1` | `1` | `1` |
|
||||
| `rooftop` | `3` | `2` | `1` |
|
||||
| `mobile` | `15` | `1` | `1` |
|
||||
|
||||
Example for path-gated retry:
|
||||
|
||||
```text
|
||||
set retry.preset rooftop
|
||||
set flood.retry.path 1
|
||||
set flood.retry.group.path 1
|
||||
set flood.retry.advert off
|
||||
set flood.retry.ignore 71CE82,C7618C
|
||||
```
|
||||
|
||||
@@ -1827,18 +1827,22 @@ bool MyMesh::hasFloodRetryTargetPrefix(const mesh::Packet* packet) const {
|
||||
|
||||
uint8_t MyMesh::getFloodRetryMaxPathLength(const mesh::Packet* packet) const {
|
||||
uint8_t gate = _prefs.flood_retry_max_path;
|
||||
if (gate == FLOOD_RETRY_PATH_GATE_DISABLED) {
|
||||
return FLOOD_RETRY_PATH_GATE_DISABLED;
|
||||
}
|
||||
if (gate > 63) {
|
||||
gate = FLOOD_RETRY_ROOFTOP_MAX_PATH;
|
||||
if (gate != FLOOD_RETRY_PATH_GATE_DISABLED) {
|
||||
if (gate > 63) {
|
||||
gate = FLOOD_RETRY_ROOFTOP_MAX_PATH;
|
||||
}
|
||||
|
||||
uint8_t raw_hops = packet != NULL ? packet->getPathHashCount() : 0;
|
||||
uint8_t effective_hops = floodRetryEffectivePathLength(packet);
|
||||
uint8_t ignored_hops = raw_hops > effective_hops ? raw_hops - effective_hops : 0;
|
||||
uint16_t adjusted_gate = (uint16_t)gate + ignored_hops;
|
||||
gate = adjusted_gate > 63 ? 63 : (uint8_t)adjusted_gate;
|
||||
}
|
||||
|
||||
uint8_t raw_hops = packet != NULL ? packet->getPathHashCount() : 0;
|
||||
uint8_t effective_hops = floodRetryEffectivePathLength(packet);
|
||||
uint8_t ignored_hops = raw_hops > effective_hops ? raw_hops - effective_hops : 0;
|
||||
uint16_t adjusted_gate = (uint16_t)gate + ignored_hops;
|
||||
return adjusted_gate > 63 ? 63 : (uint8_t)adjusted_gate;
|
||||
uint8_t group_data_gate = _prefs.flood_retry_group_max_path == FLOOD_RETRY_PATH_GATE_DISABLED
|
||||
? FLOOD_RETRY_PATH_GATE_DISABLED
|
||||
: constrain(_prefs.flood_retry_group_max_path, 0, 63);
|
||||
return applyGroupDataFloodRetryPathGate(packet, gate, group_data_gate);
|
||||
}
|
||||
|
||||
uint8_t MyMesh::getFloodRetryMaxAttempts(const mesh::Packet* packet) const {
|
||||
@@ -2388,6 +2392,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
|
||||
_prefs.direct_retry_recent_enabled = DIRECT_RETRY_RECENT_DEFAULT;
|
||||
_prefs.flood_retry_attempts = FLOOD_RETRY_ROOFTOP_COUNT;
|
||||
_prefs.flood_retry_max_path = FLOOD_RETRY_ROOFTOP_MAX_PATH;
|
||||
_prefs.flood_retry_group_max_path = FLOOD_RETRY_GROUP_MAX_PATH_DEFAULT;
|
||||
_prefs.flood_retry_bridge_enabled = 0;
|
||||
_prefs.flood_retry_advert_enabled = FLOOD_RETRY_ADVERT_DEFAULT;
|
||||
_prefs.flood_channel_data_enabled = 1;
|
||||
|
||||
@@ -347,10 +347,13 @@ bool MyMesh::allowFloodRetry(const mesh::Packet* packet) const {
|
||||
}
|
||||
|
||||
uint8_t MyMesh::getFloodRetryMaxPathLength(const mesh::Packet* packet) const {
|
||||
(void)packet;
|
||||
return _prefs.flood_retry_max_path == FLOOD_RETRY_PATH_GATE_DISABLED
|
||||
uint8_t general_gate = _prefs.flood_retry_max_path == FLOOD_RETRY_PATH_GATE_DISABLED
|
||||
? FLOOD_RETRY_PATH_GATE_DISABLED
|
||||
: constrain(_prefs.flood_retry_max_path, 0, 63);
|
||||
uint8_t group_data_gate = _prefs.flood_retry_group_max_path == FLOOD_RETRY_PATH_GATE_DISABLED
|
||||
? FLOOD_RETRY_PATH_GATE_DISABLED
|
||||
: constrain(_prefs.flood_retry_group_max_path, 0, 63);
|
||||
return applyGroupDataFloodRetryPathGate(packet, general_gate, group_data_gate);
|
||||
}
|
||||
|
||||
uint8_t MyMesh::getFloodRetryMaxAttempts(const mesh::Packet* packet) const {
|
||||
@@ -753,6 +756,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
|
||||
_prefs.flood_max_unscoped = 64;
|
||||
_prefs.flood_max_advert = 8;
|
||||
_prefs.flood_channel_data_enabled = 1;
|
||||
_prefs.flood_retry_group_max_path = FLOOD_RETRY_GROUP_MAX_PATH_DEFAULT;
|
||||
_prefs.interference_threshold = 0; // disabled
|
||||
_prefs.radio_fem_rxgain = 1; // LoRa FEM RX gain on by default (FEM boards)
|
||||
_prefs.cad_enabled = 0; // hardware CAD before TX (off by default; 'set cad on')
|
||||
|
||||
@@ -405,10 +405,13 @@ bool SensorMesh::allowFloodRetry(const mesh::Packet* packet) const {
|
||||
}
|
||||
|
||||
uint8_t SensorMesh::getFloodRetryMaxPathLength(const mesh::Packet* packet) const {
|
||||
(void)packet;
|
||||
return _prefs.flood_retry_max_path == FLOOD_RETRY_PATH_GATE_DISABLED
|
||||
uint8_t general_gate = _prefs.flood_retry_max_path == FLOOD_RETRY_PATH_GATE_DISABLED
|
||||
? FLOOD_RETRY_PATH_GATE_DISABLED
|
||||
: constrain(_prefs.flood_retry_max_path, 0, 63);
|
||||
uint8_t group_data_gate = _prefs.flood_retry_group_max_path == FLOOD_RETRY_PATH_GATE_DISABLED
|
||||
? FLOOD_RETRY_PATH_GATE_DISABLED
|
||||
: constrain(_prefs.flood_retry_group_max_path, 0, 63);
|
||||
return applyGroupDataFloodRetryPathGate(packet, general_gate, group_data_gate);
|
||||
}
|
||||
|
||||
uint8_t SensorMesh::getFloodRetryMaxAttempts(const mesh::Packet* packet) const {
|
||||
@@ -849,6 +852,7 @@ SensorMesh::SensorMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::Millise
|
||||
_prefs.disable_fwd = true;
|
||||
_prefs.flood_max = 64;
|
||||
_prefs.flood_channel_data_enabled = 1;
|
||||
_prefs.flood_retry_group_max_path = FLOOD_RETRY_GROUP_MAX_PATH_DEFAULT;
|
||||
_prefs.interference_threshold = 0; // disabled
|
||||
_prefs.radio_fem_rxgain = 1; // LoRa FEM RX gain on by default (FEM boards)
|
||||
_prefs.cad_enabled = 0; // hardware CAD before TX (off by default; 'set cad on')
|
||||
|
||||
@@ -200,6 +200,8 @@ build_src_filter =
|
||||
+<../src/Utils.cpp>
|
||||
+<../src/Packet.cpp>
|
||||
+<../src/Dispatcher.cpp>
|
||||
+<../src/Identity.cpp>
|
||||
+<../src/Mesh.cpp>
|
||||
+<../src/helpers/StaticPoolPacketManager.cpp>
|
||||
+<../src/helpers/ota/MerkleTree.cpp>
|
||||
+<../src/helpers/ota/MotaContainer.cpp>
|
||||
|
||||
@@ -393,6 +393,8 @@ void Dispatcher::processRecvPacket(Packet* pkt) {
|
||||
if (!queueOutboundPacket(pkt, priority, _delay)) {
|
||||
onSendFail(pkt);
|
||||
releasePacket(pkt);
|
||||
} else if (pkt->isRouteDirect() && pkt->getPayloadType() == PAYLOAD_TYPE_TRACE) {
|
||||
onTracePacketQueuedForSend(pkt);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -554,6 +556,9 @@ bool Dispatcher::sendPacket(Packet* packet, uint8_t priority, uint32_t delay_mil
|
||||
releasePacket(packet);
|
||||
return false;
|
||||
}
|
||||
if (packet->isRouteDirect() && packet->getPayloadType() == PAYLOAD_TYPE_TRACE) {
|
||||
onTracePacketQueuedForSend(packet);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -232,6 +232,7 @@ protected:
|
||||
virtual void logRx(Packet* packet, int len, float score) { } // hooks for custom logging
|
||||
virtual void logTx(Packet* packet, int len) { }
|
||||
virtual void logTxFail(Packet* packet, int len) { }
|
||||
virtual void onTracePacketQueuedForSend(Packet* packet) { }
|
||||
virtual void onSendComplete(Packet* packet) { }
|
||||
virtual void onSendFail(Packet* packet) { }
|
||||
virtual const char* getLogDateTime() { return ""; }
|
||||
|
||||
+112
-29
@@ -161,6 +161,7 @@ void Mesh::begin() {
|
||||
_direct_retries[i].retry_delay = 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].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;
|
||||
@@ -359,7 +360,7 @@ uint8_t Mesh::getDirectRetryPacketAirtimeFactor(const Packet* packet) const {
|
||||
|
||||
uint8_t payload_type = packet->getPayloadType();
|
||||
if (payload_type == PAYLOAD_TYPE_TRACE || payload_type == PAYLOAD_TYPE_ANON_REQ) {
|
||||
return 4;
|
||||
return 3;
|
||||
}
|
||||
if (payload_type == PAYLOAD_TYPE_TXT_MSG) {
|
||||
return 7;
|
||||
@@ -399,6 +400,18 @@ uint8_t Mesh::getFloodRetryMaxPathLength(const Packet* packet) const {
|
||||
(void)packet;
|
||||
return FLOOD_RETRY_MAX_PATH_DEFAULT;
|
||||
}
|
||||
uint8_t Mesh::applyGroupDataFloodRetryPathGate(const Packet* packet,
|
||||
uint8_t general_gate,
|
||||
uint8_t group_data_gate) {
|
||||
if (packet == NULL || packet->getPayloadType() != PAYLOAD_TYPE_GRP_DATA
|
||||
|| group_data_gate == FLOOD_RETRY_PATH_GATE_DISABLED) {
|
||||
return general_gate;
|
||||
}
|
||||
if (general_gate == FLOOD_RETRY_PATH_GATE_DISABLED || group_data_gate < general_gate) {
|
||||
return group_data_gate;
|
||||
}
|
||||
return general_gate;
|
||||
}
|
||||
uint8_t Mesh::getFloodRetryMaxAttempts(const Packet* packet) const {
|
||||
(void)packet;
|
||||
return FLOOD_RETRY_MAX_ATTEMPTS_DEFAULT;
|
||||
@@ -424,6 +437,10 @@ void Mesh::onSendComplete(Packet* packet) {
|
||||
armFloodRetryOnSendComplete(packet);
|
||||
}
|
||||
|
||||
void Mesh::onTracePacketQueuedForSend(Packet* packet) {
|
||||
replaceQueuedTraceRetries(packet);
|
||||
}
|
||||
|
||||
void Mesh::onSendFail(Packet* packet) {
|
||||
clearPendingDirectRetryOnSendFail(packet);
|
||||
clearPendingFloodRetryOnSendFail(packet);
|
||||
@@ -913,6 +930,7 @@ void Mesh::clearDirectRetrySlot(int idx) {
|
||||
_direct_retries[idx].retry_delay = 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].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;
|
||||
@@ -926,6 +944,26 @@ void Mesh::clearDirectRetrySlot(int idx) {
|
||||
if (rebuild_timeout) rebuildNextDirectRetryTimeout();
|
||||
}
|
||||
|
||||
void Mesh::retireDirectRetrySlot(int idx) {
|
||||
if (idx < 0 || idx >= MAX_DIRECT_RETRY_SLOTS || !_direct_retries[idx].active) {
|
||||
return;
|
||||
}
|
||||
|
||||
Packet* retry = _direct_retries[idx].queued ? _direct_retries[idx].packet : NULL;
|
||||
if (retry != NULL && retry != getOutboundInFlight()) {
|
||||
for (int j = 0; j < _mgr->getOutboundTotal(); j++) {
|
||||
if (_mgr->getOutboundByIdx(j) != retry) continue;
|
||||
Packet* pending = _mgr->removeOutboundByIdx(j);
|
||||
if (pending != NULL) {
|
||||
_direct_retries[idx].packet = NULL;
|
||||
releasePacket(pending);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
clearDirectRetrySlot(idx);
|
||||
}
|
||||
|
||||
void Mesh::rebuildNextDirectRetryTimeout() {
|
||||
bool found = false;
|
||||
uint32_t shortest_delay = 0;
|
||||
@@ -996,6 +1034,62 @@ void Mesh::calculateDirectRetryKey(const Packet* packet, uint8_t* dest_key) cons
|
||||
Utils::sha256(dest_key, MAX_HASH_SIZE, &type, 1, packet->payload, packet->payload_len);
|
||||
}
|
||||
|
||||
bool Mesh::calculateTraceReplacementKey(const Packet* packet, uint8_t* dest_key) const {
|
||||
if (packet == NULL || dest_key == NULL || !packet->isRouteDirect()
|
||||
|| packet->getPayloadType() != PAYLOAD_TYPE_TRACE || packet->payload_len < 9) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t prefix[3] = {
|
||||
PAYLOAD_TYPE_TRACE,
|
||||
(uint8_t)(packet->path_len & 0xFF),
|
||||
(uint8_t)(packet->path_len >> 8)
|
||||
};
|
||||
// Ignore tag/auth (payload bytes 0..7), which change for a new request.
|
||||
// Keep flags, route, and current progress so an older trace that has already
|
||||
// advanced is not mistaken for the stale retry being replaced.
|
||||
Utils::sha256(dest_key, MAX_HASH_SIZE, prefix, sizeof(prefix),
|
||||
&packet->payload[8], packet->payload_len - 8);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Mesh::replaceQueuedTraceRetries(const Packet* packet) {
|
||||
uint8_t replacement_key[MAX_HASH_SIZE];
|
||||
if (!calculateTraceReplacementKey(packet, replacement_key)) return;
|
||||
|
||||
int replacement_slot = -1;
|
||||
bool found_prior = false;
|
||||
for (int i = 0; i < MAX_DIRECT_RETRY_SLOTS; i++) {
|
||||
if (!_direct_retries[i].active || _direct_retries[i].payload_type != PAYLOAD_TYPE_TRACE
|
||||
|| memcmp(replacement_key, _direct_retries[i].trace_replacement_key, MAX_HASH_SIZE) != 0) {
|
||||
continue;
|
||||
}
|
||||
if (_direct_retries[i].trigger_packet == packet) {
|
||||
replacement_slot = i;
|
||||
} else {
|
||||
found_prior = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found_prior) return;
|
||||
|
||||
for (int i = 0; i < MAX_DIRECT_RETRY_SLOTS; i++) {
|
||||
if (i == replacement_slot || !_direct_retries[i].active
|
||||
|| _direct_retries[i].payload_type != PAYLOAD_TYPE_TRACE
|
||||
|| memcmp(replacement_key, _direct_retries[i].trace_replacement_key, MAX_HASH_SIZE) != 0) {
|
||||
continue;
|
||||
}
|
||||
retireDirectRetrySlot(i);
|
||||
}
|
||||
|
||||
// An exact duplicate retry key, or a full retry table, can prevent the new
|
||||
// packet from reserving its slot before it is queued. The prior slots are now
|
||||
// gone, so register the successfully queued packet as the retry owner.
|
||||
if (replacement_slot < 0) {
|
||||
maybeScheduleDirectRetry(packet, getTraceDirectPriority(packet));
|
||||
}
|
||||
}
|
||||
|
||||
bool Mesh::cancelDirectRetryOnEcho(const Packet* packet) {
|
||||
if (_active_direct_retry_count == 0) return false;
|
||||
|
||||
@@ -1351,6 +1445,8 @@ void Mesh::maybeScheduleDirectRetry(const Packet* packet, uint8_t priority, bool
|
||||
|
||||
uint8_t retry_key[MAX_HASH_SIZE];
|
||||
calculateDirectRetryKey(packet, retry_key);
|
||||
uint8_t trace_replacement_key[MAX_HASH_SIZE] = { 0 };
|
||||
bool has_trace_replacement_key = calculateTraceReplacementKey(packet, trace_replacement_key);
|
||||
for (int i = 0; i < MAX_DIRECT_RETRY_SLOTS; i++) {
|
||||
if (_direct_retries[i].active
|
||||
&& memcmp(retry_key, _direct_retries[i].retry_key, MAX_HASH_SIZE) == 0) {
|
||||
@@ -1366,6 +1462,17 @@ void Mesh::maybeScheduleDirectRetry(const Packet* packet, uint8_t priority, bool
|
||||
}
|
||||
}
|
||||
if (slot_idx < 0) {
|
||||
if (has_trace_replacement_key) {
|
||||
for (int i = 0; i < MAX_DIRECT_RETRY_SLOTS; i++) {
|
||||
if (_direct_retries[i].active && _direct_retries[i].payload_type == PAYLOAD_TYPE_TRACE
|
||||
&& memcmp(trace_replacement_key, _direct_retries[i].trace_replacement_key,
|
||||
MAX_HASH_SIZE) == 0) {
|
||||
// The post-queue hook will retire this older matching TRACE and use
|
||||
// the slot for the successfully queued replacement.
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
onDirectRetryEvent("dropped_no_slot", packet, 0, 0, next_hop_hash, next_hop_hash_len);
|
||||
onDirectRetryEvent("failure", packet, 0, 0, next_hop_hash, next_hop_hash_len);
|
||||
return;
|
||||
@@ -1374,6 +1481,8 @@ 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,
|
||||
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;
|
||||
@@ -1448,20 +1557,7 @@ void Mesh::cancelAllDirectRetries() {
|
||||
|
||||
for (int i = 0; i < MAX_DIRECT_RETRY_SLOTS; i++) {
|
||||
if (!_direct_retries[i].active) continue;
|
||||
|
||||
Packet* retry = _direct_retries[i].queued ? _direct_retries[i].packet : NULL;
|
||||
if (retry != NULL && retry != getOutboundInFlight()) {
|
||||
for (int j = 0; j < _mgr->getOutboundTotal(); j++) {
|
||||
if (_mgr->getOutboundByIdx(j) != retry) continue;
|
||||
Packet* pending = _mgr->removeOutboundByIdx(j);
|
||||
if (pending != NULL) {
|
||||
_direct_retries[i].packet = NULL;
|
||||
releasePacket(pending);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
clearDirectRetrySlot(i);
|
||||
retireDirectRetrySlot(i);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1501,20 +1597,7 @@ bool Mesh::cancelActiveRetries(const uint8_t retry_key[MAX_HASH_SIZE]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Packet* retry = _direct_retries[i].queued ? _direct_retries[i].packet : NULL;
|
||||
if (retry != NULL && retry != getOutboundInFlight()) {
|
||||
for (int j = 0; j < _mgr->getOutboundTotal(); j++) {
|
||||
if (_mgr->getOutboundByIdx(j) == retry) {
|
||||
Packet* pending = _mgr->removeOutboundByIdx(j);
|
||||
if (pending != NULL) {
|
||||
_direct_retries[i].packet = NULL;
|
||||
releasePacket(pending);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
clearDirectRetrySlot(i);
|
||||
retireDirectRetrySlot(i);
|
||||
cancelled = true;
|
||||
}
|
||||
|
||||
|
||||
+12
@@ -60,6 +60,7 @@ class Mesh : public Dispatcher {
|
||||
uint32_t retry_delay;
|
||||
uint8_t retry_attempts_sent;
|
||||
uint8_t retry_key[MAX_HASH_SIZE];
|
||||
uint8_t trace_replacement_key[MAX_HASH_SIZE];
|
||||
uint8_t next_hop_hash[MAX_HASH_SIZE];
|
||||
uint8_t next_hop_hash_len;
|
||||
uint8_t payload_type;
|
||||
@@ -104,7 +105,10 @@ class Mesh : public Dispatcher {
|
||||
void rebuildNextDirectRetryTimeout();
|
||||
void rebuildNextFloodRetryTimeout();
|
||||
void clearDirectRetrySlot(int idx);
|
||||
void retireDirectRetrySlot(int idx);
|
||||
void calculateDirectRetryKey(const Packet* packet, uint8_t* dest_key) const;
|
||||
bool calculateTraceReplacementKey(const Packet* packet, uint8_t* dest_key) const;
|
||||
void replaceQueuedTraceRetries(const Packet* packet);
|
||||
bool cancelDirectRetryOnEcho(const Packet* packet);
|
||||
void armDirectRetryOnSendComplete(const Packet* packet);
|
||||
void clearPendingDirectRetryOnSendFail(const Packet* packet);
|
||||
@@ -122,6 +126,7 @@ class Mesh : public Dispatcher {
|
||||
|
||||
protected:
|
||||
DispatcherAction onRecvPacket(Packet* pkt) override;
|
||||
void onTracePacketQueuedForSend(Packet* packet) override;
|
||||
void onSendComplete(Packet* packet) override;
|
||||
void onSendFail(Packet* packet) override;
|
||||
bool allowPacketTransmit(const Packet* packet) const override;
|
||||
@@ -236,6 +241,13 @@ protected:
|
||||
*/
|
||||
virtual uint8_t getFloodRetryMaxPathLength(const Packet* packet) const;
|
||||
|
||||
/**
|
||||
* \returns the stricter of the general flood retry gate and the group-data-specific gate.
|
||||
*/
|
||||
static uint8_t applyGroupDataFloodRetryPathGate(const Packet* packet,
|
||||
uint8_t general_gate,
|
||||
uint8_t group_data_gate);
|
||||
|
||||
/**
|
||||
* \returns maximum number of FLOOD retry transmissions after the initial TX.
|
||||
*/
|
||||
|
||||
@@ -169,6 +169,7 @@ static bool isBasicRetryConfig(const char* config) {
|
||||
|| configKeyMatches(config, "direct.retry")
|
||||
|| configKeyMatches(config, "flood.retry.count")
|
||||
|| configKeyMatches(config, "flood.retry.path")
|
||||
|| configKeyMatches(config, "flood.retry.group.path")
|
||||
|| configKeyMatches(config, "flood.retry.advert");
|
||||
}
|
||||
|
||||
@@ -430,6 +431,9 @@ static void applyFloodRetryPreset(NodePrefs* prefs, uint8_t preset) {
|
||||
prefs->flood_retry_attempts = FLOOD_RETRY_ROOFTOP_COUNT;
|
||||
prefs->flood_retry_max_path = FLOOD_RETRY_ROOFTOP_MAX_PATH;
|
||||
}
|
||||
prefs->flood_retry_group_max_path = prefs->flood_retry_max_path == 0
|
||||
? FLOOD_RETRY_PATH_GATE_DISABLED
|
||||
: FLOOD_RETRY_GROUP_MAX_PATH_DEFAULT;
|
||||
}
|
||||
|
||||
static bool parseFloodRetryPathGate(const char* value, uint8_t& path_gate) {
|
||||
@@ -936,6 +940,7 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
|
||||
_prefs->ota_max_hops = 3;
|
||||
#endif
|
||||
_prefs->telemetry_access = TELEMETRY_ACCESS_ALL;
|
||||
_prefs->flood_retry_group_max_path = FLOOD_RETRY_GROUP_MAX_PATH_DEFAULT;
|
||||
// A remainder larger than the smallest legacy MQTT gap (864) means an old fork
|
||||
// file with the zero-filled gap; detect and recover it below. Anything smaller
|
||||
// (upstream/flex 5-byte tails, or the ~384-byte keymind retry tail) takes the
|
||||
@@ -1183,6 +1188,10 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
|
||||
file.read((uint8_t *)_prefs->battery_alert_region, sizeof(_prefs->battery_alert_region));
|
||||
_prefs->battery_alert_region[sizeof(_prefs->battery_alert_region) - 1] = '\0';
|
||||
}
|
||||
if (file.available() >= (int)sizeof(_prefs->flood_retry_group_max_path)) {
|
||||
file.read((uint8_t *)&_prefs->flood_retry_group_max_path,
|
||||
sizeof(_prefs->flood_retry_group_max_path));
|
||||
}
|
||||
}
|
||||
|
||||
// sanitise bad pref values
|
||||
@@ -1239,6 +1248,12 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
|
||||
if (_prefs->flood_retry_max_path != FLOOD_RETRY_PATH_GATE_DISABLED) {
|
||||
_prefs->flood_retry_max_path = constrain(_prefs->flood_retry_max_path, 0, 63);
|
||||
}
|
||||
if (_prefs->flood_retry_group_max_path != FLOOD_RETRY_PATH_GATE_DISABLED) {
|
||||
_prefs->flood_retry_group_max_path = constrain(_prefs->flood_retry_group_max_path, 0, 63);
|
||||
}
|
||||
if (_prefs->flood_retry_max_path == 0) {
|
||||
_prefs->flood_retry_group_max_path = FLOOD_RETRY_PATH_GATE_DISABLED;
|
||||
}
|
||||
_prefs->flood_retry_bridge_enabled = constrain(_prefs->flood_retry_bridge_enabled, 0, 1);
|
||||
_prefs->flood_retry_advert_enabled = constrain(_prefs->flood_retry_advert_enabled, 0, 1);
|
||||
_prefs->battery_alert_enabled = constrain(_prefs->battery_alert_enabled, 0, 1);
|
||||
@@ -1402,7 +1417,9 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) {
|
||||
file.write((uint8_t *)&_prefs->rx_ps_level, sizeof(_prefs->rx_ps_level)); // 820
|
||||
file.write((uint8_t *)&_prefs->rx_ps_preamble, sizeof(_prefs->rx_ps_preamble)); // 821
|
||||
file.write((uint8_t *)_prefs->battery_alert_region, sizeof(_prefs->battery_alert_region)); // 822
|
||||
// next: 853
|
||||
file.write((uint8_t *)&_prefs->flood_retry_group_max_path,
|
||||
sizeof(_prefs->flood_retry_group_max_path)); // 853
|
||||
// next: 854
|
||||
|
||||
file.close();
|
||||
}
|
||||
@@ -2612,6 +2629,21 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep
|
||||
uint8_t path_gate;
|
||||
if (parseFloodRetryPathGate(&config[17], path_gate)) {
|
||||
_prefs->flood_retry_max_path = path_gate;
|
||||
if (path_gate == 0) {
|
||||
_prefs->flood_retry_group_max_path = FLOOD_RETRY_PATH_GATE_DISABLED;
|
||||
}
|
||||
_prefs->retry_preset = RETRY_PRESET_CUSTOM;
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
} else {
|
||||
strcpy(reply, "Error, must be 0-63 or off");
|
||||
}
|
||||
} else if (memcmp(config, "flood.retry.group.path ", 23) == 0) {
|
||||
uint8_t path_gate;
|
||||
if (parseFloodRetryPathGate(&config[23], path_gate)) {
|
||||
_prefs->flood_retry_group_max_path = _prefs->flood_retry_max_path == 0
|
||||
? FLOOD_RETRY_PATH_GATE_DISABLED
|
||||
: path_gate;
|
||||
_prefs->retry_preset = RETRY_PRESET_CUSTOM;
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
@@ -3029,6 +3061,10 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep
|
||||
char path_gate[8];
|
||||
formatFloodRetryPathGate(path_gate, _prefs->flood_retry_max_path);
|
||||
sprintf(reply, "> %s", path_gate);
|
||||
} else if (memcmp(config, "flood.retry.group.path", 22) == 0) {
|
||||
char path_gate[8];
|
||||
formatFloodRetryPathGate(path_gate, _prefs->flood_retry_group_max_path);
|
||||
sprintf(reply, "> %s", path_gate);
|
||||
} else if (memcmp(config, "flood.retry.prefixes", 20) == 0) {
|
||||
formatFloodRetryPrefixList(tmp, _prefs->flood_retry_prefixes, FLOOD_RETRY_PREFIX_SLOTS);
|
||||
sprintf(reply, "> %s", tmp[0] ? tmp : "none");
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
#define FLOOD_RETRY_MOBILE_COUNT 15
|
||||
#define FLOOD_RETRY_MOBILE_MAX_PATH 1
|
||||
#define FLOOD_RETRY_ADVERT_DEFAULT 0
|
||||
#define FLOOD_RETRY_GROUP_MAX_PATH_DEFAULT 1
|
||||
|
||||
#define BATTERY_ALERT_LOW_PERCENT_DEFAULT 20
|
||||
#define BATTERY_ALERT_CRITICAL_PERCENT_DEFAULT 10
|
||||
@@ -215,6 +216,7 @@ struct NodePrefs { // persisted to file
|
||||
uint8_t rx_ps_level; // 0 = manual/explicit us timings; 1..10 = level-derived (auto-retunes on SF/BW change)
|
||||
uint8_t rx_ps_preamble; // 0 = auto (derive from SF); else 16 or 32 = explicit override for level calc
|
||||
char battery_alert_region[31]; // named scope for low-battery floods; empty = no alert scope
|
||||
uint8_t flood_retry_group_max_path; // PAYLOAD_TYPE_GRP_DATA retry path gate; 0xFF = use only the general gate
|
||||
};
|
||||
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
class Ed25519 {
|
||||
public:
|
||||
static bool verify(const uint8_t*, const uint8_t*, const uint8_t*, int) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
@@ -1,5 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
// Mock Stream class for native testing
|
||||
// Provides minimal interface needed by Utils.h
|
||||
|
||||
@@ -7,4 +10,7 @@ class Stream {
|
||||
public:
|
||||
virtual void print(char c) {}
|
||||
virtual void print(const char* str) {}
|
||||
virtual void println() {}
|
||||
virtual size_t readBytes(uint8_t*, size_t) { return 0; }
|
||||
virtual size_t write(const uint8_t*, size_t) { return 0; }
|
||||
};
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <Ed25519.h>
|
||||
#include <Mesh.h>
|
||||
#include <helpers/StaticPoolPacketManager.h>
|
||||
|
||||
class TraceTestClock : public mesh::MillisecondClock {
|
||||
public:
|
||||
unsigned long now = 0;
|
||||
unsigned long getMillis() override { return now; }
|
||||
};
|
||||
|
||||
class TraceTestRTC : public mesh::RTCClock {
|
||||
public:
|
||||
uint32_t now = 0;
|
||||
uint32_t getCurrentTime() override { return now; }
|
||||
void setCurrentTime(uint32_t time) override { now = time; }
|
||||
};
|
||||
|
||||
class TraceTestRNG : public mesh::RNG {
|
||||
public:
|
||||
void random(uint8_t* dest, size_t sz) override { memset(dest, 0, sz); }
|
||||
};
|
||||
|
||||
class TraceTestRadio : public mesh::Radio {
|
||||
public:
|
||||
bool sending = false;
|
||||
bool complete = false;
|
||||
|
||||
int recvRaw(uint8_t*, int) override { return 0; }
|
||||
uint32_t getEstAirtimeFor(int) override { return 10; }
|
||||
float packetScore(float, int) override { return 0; }
|
||||
bool startSendRaw(const uint8_t*, int) override {
|
||||
sending = true;
|
||||
return true;
|
||||
}
|
||||
bool isSendComplete() override { return complete; }
|
||||
void onSendFinished() override {
|
||||
sending = false;
|
||||
complete = false;
|
||||
}
|
||||
bool isInRecvMode() const override { return !sending; }
|
||||
};
|
||||
|
||||
class TraceTestTables : public mesh::MeshTables {
|
||||
public:
|
||||
bool wasSeen(const mesh::Packet*) override { return false; }
|
||||
void markSeen(const mesh::Packet*) override { }
|
||||
void markSent(const mesh::Packet*) override { }
|
||||
void clear(const mesh::Packet*) override { }
|
||||
};
|
||||
|
||||
class TraceTestMesh : public mesh::Mesh {
|
||||
public:
|
||||
TraceTestMesh(mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng,
|
||||
mesh::RTCClock& rtc, mesh::PacketManager& mgr, mesh::MeshTables& tables)
|
||||
: mesh::Mesh(radio, ms, rng, rtc, mgr, tables) { }
|
||||
|
||||
uint8_t airtimeFactor(const mesh::Packet* packet) const {
|
||||
return getDirectRetryPacketAirtimeFactor(packet);
|
||||
}
|
||||
|
||||
uint8_t floodPathGate(const mesh::Packet* packet, uint8_t general_gate,
|
||||
uint8_t group_data_gate) const {
|
||||
return applyGroupDataFloodRetryPathGate(packet, general_gate, group_data_gate);
|
||||
}
|
||||
};
|
||||
|
||||
static mesh::Packet* makeTrace(TraceTestMesh& node, uint32_t tag, uint32_t auth,
|
||||
const uint8_t* route, uint8_t route_len) {
|
||||
mesh::Packet* packet = node.createTrace(tag, auth, 0);
|
||||
EXPECT_NE(packet, nullptr);
|
||||
if (packet == nullptr) return nullptr;
|
||||
EXPECT_TRUE(node.sendDirect(packet, route, route_len));
|
||||
return packet;
|
||||
}
|
||||
|
||||
TEST(TraceRetry, TraceAndAnonymousRequestsUseThreeAirtimes) {
|
||||
TraceTestClock clock;
|
||||
TraceTestRTC rtc;
|
||||
TraceTestRNG rng;
|
||||
TraceTestRadio radio;
|
||||
TraceTestTables tables;
|
||||
StaticPoolPacketManager manager(12);
|
||||
TraceTestMesh node(radio, clock, rng, rtc, manager, tables);
|
||||
|
||||
mesh::Packet trace;
|
||||
trace.header = ROUTE_TYPE_DIRECT | (PAYLOAD_TYPE_TRACE << PH_TYPE_SHIFT);
|
||||
mesh::Packet anon;
|
||||
anon.header = ROUTE_TYPE_DIRECT | (PAYLOAD_TYPE_ANON_REQ << PH_TYPE_SHIFT);
|
||||
mesh::Packet text;
|
||||
text.header = ROUTE_TYPE_DIRECT | (PAYLOAD_TYPE_TXT_MSG << PH_TYPE_SHIFT);
|
||||
mesh::Packet other;
|
||||
other.header = ROUTE_TYPE_DIRECT | (PAYLOAD_TYPE_REQ << PH_TYPE_SHIFT);
|
||||
|
||||
EXPECT_EQ(3, node.airtimeFactor(&trace));
|
||||
EXPECT_EQ(3, node.airtimeFactor(&anon));
|
||||
EXPECT_EQ(7, node.airtimeFactor(&text));
|
||||
EXPECT_EQ(6, node.airtimeFactor(&other));
|
||||
}
|
||||
|
||||
TEST(FloodRetry, GroupDataUsesTheStricterPathGate) {
|
||||
TraceTestClock clock;
|
||||
TraceTestRTC rtc;
|
||||
TraceTestRNG rng;
|
||||
TraceTestRadio radio;
|
||||
TraceTestTables tables;
|
||||
StaticPoolPacketManager manager(12);
|
||||
TraceTestMesh node(radio, clock, rng, rtc, manager, tables);
|
||||
|
||||
mesh::Packet group_data;
|
||||
group_data.header = ROUTE_TYPE_FLOOD | (PAYLOAD_TYPE_GRP_DATA << PH_TYPE_SHIFT);
|
||||
mesh::Packet group_text;
|
||||
group_text.header = ROUTE_TYPE_FLOOD | (PAYLOAD_TYPE_GRP_TXT << PH_TYPE_SHIFT);
|
||||
|
||||
EXPECT_EQ(1, node.floodPathGate(&group_data, 2, 1));
|
||||
EXPECT_EQ(1, node.floodPathGate(&group_data, FLOOD_RETRY_PATH_GATE_DISABLED, 1));
|
||||
EXPECT_EQ(1, node.floodPathGate(&group_data, 1, 3));
|
||||
EXPECT_EQ(2, node.floodPathGate(&group_data, 2, FLOOD_RETRY_PATH_GATE_DISABLED));
|
||||
EXPECT_EQ(0, node.floodPathGate(&group_data, 0, FLOOD_RETRY_PATH_GATE_DISABLED));
|
||||
EXPECT_EQ(2, node.floodPathGate(&group_text, 2, 1));
|
||||
}
|
||||
|
||||
TEST(TraceRetry, NewTraceReplacesQueuedRetryButAdvancedOldTraceStillQueues) {
|
||||
TraceTestClock clock;
|
||||
TraceTestRTC rtc;
|
||||
TraceTestRNG rng;
|
||||
TraceTestRadio radio;
|
||||
TraceTestTables tables;
|
||||
StaticPoolPacketManager manager(12);
|
||||
TraceTestMesh node(radio, clock, rng, rtc, manager, tables);
|
||||
node.begin();
|
||||
|
||||
const uint8_t route[] = {0x11, 0x22, 0x33};
|
||||
mesh::Packet* old_trace = makeTrace(node, 0x11111111, 0xAAAAAAAA, route, sizeof(route));
|
||||
ASSERT_NE(old_trace, nullptr);
|
||||
ASSERT_EQ(1, manager.getOutboundTotal());
|
||||
|
||||
clock.now = 1;
|
||||
node.loop();
|
||||
ASSERT_TRUE(radio.sending);
|
||||
radio.complete = true;
|
||||
clock.now = 2;
|
||||
node.loop();
|
||||
ASSERT_EQ(1, manager.getOutboundTotal()); // old TRACE retry
|
||||
|
||||
mesh::Packet* new_trace = makeTrace(node, 0x22222222, 0xBBBBBBBB, route, sizeof(route));
|
||||
ASSERT_NE(new_trace, nullptr);
|
||||
ASSERT_EQ(1, manager.getOutboundTotal());
|
||||
EXPECT_EQ(new_trace, manager.getOutboundByIdx(0));
|
||||
|
||||
// A packet from the older run that has already advanced is a different
|
||||
// retry stage. It must remain queueable instead of being treated as the
|
||||
// stale same-hop retry that the newer run replaced.
|
||||
mesh::Packet* returning_old = node.createTrace(0x11111111, 0xAAAAAAAA, 0);
|
||||
ASSERT_NE(returning_old, nullptr);
|
||||
memcpy(&returning_old->payload[returning_old->payload_len], route, sizeof(route));
|
||||
returning_old->payload_len += sizeof(route);
|
||||
returning_old->header |= ROUTE_TYPE_DIRECT;
|
||||
returning_old->path_len = 1;
|
||||
returning_old->path[0] = 4;
|
||||
ASSERT_TRUE(node.sendPacket(returning_old, 1));
|
||||
EXPECT_EQ(2, manager.getOutboundTotal());
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
Reference in New Issue
Block a user