mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-10-23 12:12:24 +00:00
Harden flood retry airtime handling
This commit is contained in:
1 parent
927d7869ff
commit
73c6472d95
9 files changed
+172
-55
No files matched your search
@@ -1267,7 +1267,7 @@ another qualifying repeater.
|
||||
**Parameters:**
|
||||
- `count`: Base retry attempts after the original send, from `0` to `15`. `0` disables flood retry.
|
||||
|
||||
**Note:** Actual attempts are capped at `15`. Hop 1 flood retries use `count * 2`; hop 2 flood retries use `count * 1.5`, rounded up.
|
||||
**Note:** Actual attempts are capped at `15`. Path count 0 flood retries use `count * 2`; path count 1 retries use `count * 1.5`, rounded up; path count 2 and higher use the configured base count.
|
||||
|
||||
**Defaults:**
|
||||
- `infra`: `1`
|
||||
@@ -1374,7 +1374,9 @@ set flood.retry.ignore none
|
||||
- `get flood.retry.bridge`
|
||||
- `set flood.retry.bridge <on|off>`
|
||||
|
||||
**Note:** Bridge mode retries until each configured fresh bucket, plus the non-source `other` bucket, has been heard or the retry count is exhausted.
|
||||
**Note:** Bridge mode retries until each configured fresh bucket, plus the non-source `other` bucket, has been heard or the retry count is exhausted. If prefixes in different buckets share their first byte, configuration commands return a warning because a 1-byte path cannot distinguish those buckets. The configuration remains valid: an ambiguous source is treated as belonging to every matching source bucket, and an ambiguous echo credits every matching target bucket so it cannot force retry exhaustion.
|
||||
|
||||
Flood retry timing retains its fixed maximum-frame plus 20 packet-airtime wait, then adds a random `0-200%` of one additional packet airtime on every attempt. This de-synchronizes repeaters that may have missed the same echo while capping the added wait at two frames.
|
||||
|
||||
**Examples:**
|
||||
```
|
||||
|
||||
@@ -236,7 +236,7 @@ the bucket rules below instead.
|
||||
|
||||
| Setting | What it does | How to use | Example |
|
||||
| --- | --- | --- | --- |
|
||||
| `flood.retry.count` | Base flood retry attempts after initial TX. Hop 1 doubles it, hop 2 uses 1.5x rounded up, 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.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.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` |
|
||||
@@ -338,6 +338,18 @@ Bridge retry stays eligible until every target bucket has been heard or
|
||||
least one of its prefixes is fresh in `recent.repeater`. Prefixes in
|
||||
`flood.retry.ignore` never count as bucket hits.
|
||||
|
||||
Configuration reports a warning when prefixes in different buckets, including
|
||||
bucket 7 (`flood.retry.prefixes`), share the same first byte. A 1-byte path cannot
|
||||
distinguish those buckets. Bridge mode therefore excludes every matching bucket
|
||||
when that short prefix is the source, and credits every matching target bucket
|
||||
when it is heard as an echo. This prevents an ambiguous short prefix from keeping
|
||||
an impossible target outstanding through every retry.
|
||||
|
||||
Each flood retry wait retains the fixed maximum-frame plus 20 packet-airtime
|
||||
delay, then adds random jitter from zero to 200 percent of one additional packet
|
||||
airtime. This keeps nearby repeaters from repeating a collision on fixed timing
|
||||
while capping the added wait at two frames.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If advert packets are still retrying:
|
||||
|
||||
@@ -1342,56 +1342,60 @@ static uint8_t floodRetryBucketMask(uint8_t bucket) {
|
||||
return (uint8_t)(1U << bucket);
|
||||
}
|
||||
|
||||
int MyMesh::floodRetryBucketForPrefix(const uint8_t* prefix, uint8_t prefix_len, bool require_fresh) const {
|
||||
uint8_t MyMesh::floodRetryBucketMaskForPrefix(const uint8_t* prefix, uint8_t prefix_len, bool require_fresh) const {
|
||||
if (prefix == NULL || prefix_len == 0 || prefix_len > MAX_ROUTE_HASH_BYTES) {
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
if (floodRetryPrefixIgnored(prefix, prefix_len)) {
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
if (require_fresh && !floodRetryPrefixFresh(prefix, prefix_len)) {
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
uint8_t mask = 0;
|
||||
for (int bucket = 0; bucket < FLOOD_RETRY_BRIDGE_BUCKETS; bucket++) {
|
||||
for (int i = 0; i < FLOOD_RETRY_BUCKET_PREFIXES; i++) {
|
||||
const uint8_t* configured = _prefs.flood_retry_bridge_buckets[bucket][i];
|
||||
if (configuredFloodRetryPrefixMatches(configured, prefix, prefix_len)) {
|
||||
return bucket;
|
||||
mask |= floodRetryBucketMask((uint8_t)bucket);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < FLOOD_RETRY_PREFIX_SLOTS; i++) {
|
||||
if (configuredFloodRetryPrefixMatches(_prefs.flood_retry_prefixes[i], prefix, prefix_len)) {
|
||||
return FLOOD_RETRY_BRIDGE_OTHER_BUCKET;
|
||||
mask |= floodRetryBucketMask(FLOOD_RETRY_BRIDGE_OTHER_BUCKET);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
return mask;
|
||||
}
|
||||
|
||||
int MyMesh::floodRetryBucketForPathHop(const uint8_t* prefix, uint8_t prefix_len, uint8_t hop,
|
||||
uint8_t progress_marker) const {
|
||||
return floodRetryBucketForPrefix(prefix, prefix_len, hop < progress_marker);
|
||||
uint8_t MyMesh::floodRetryBucketMaskForPathHop(const uint8_t* prefix, uint8_t prefix_len, uint8_t hop,
|
||||
uint8_t progress_marker) const {
|
||||
return floodRetryBucketMaskForPrefix(prefix, prefix_len, hop < progress_marker);
|
||||
}
|
||||
|
||||
int MyMesh::floodRetrySourceBucket(const mesh::Packet* packet) const {
|
||||
uint8_t MyMesh::floodRetrySourceMask(const mesh::Packet* packet) const {
|
||||
if (packet == NULL) {
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
uint8_t hash_size = packet->getPathHashSize();
|
||||
if (hash_size == 0 || hash_size > MAX_ROUTE_HASH_BYTES) {
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
if (packet->getPathHashCount() < 2) {
|
||||
return FLOOD_RETRY_BRIDGE_OTHER_BUCKET;
|
||||
return floodRetryBucketMask(FLOOD_RETRY_BRIDGE_OTHER_BUCKET);
|
||||
}
|
||||
const uint8_t* source_prefix = &packet->path[(packet->getPathHashCount() - 2) * hash_size];
|
||||
return floodRetryBucketForPrefix(source_prefix, hash_size, true);
|
||||
return floodRetryBucketMaskForPrefix(source_prefix, hash_size, true);
|
||||
}
|
||||
|
||||
uint8_t MyMesh::floodRetryBridgeTargetMask(uint8_t source_bucket) const {
|
||||
uint8_t MyMesh::floodRetryBridgeTargetMask(uint8_t source_mask) const {
|
||||
uint8_t mask = 0;
|
||||
for (int bucket = 0; bucket < FLOOD_RETRY_BRIDGE_BUCKETS; bucket++) {
|
||||
if (bucket == source_bucket) {
|
||||
uint8_t bucket_mask = floodRetryBucketMask((uint8_t)bucket);
|
||||
if ((source_mask & bucket_mask) != 0) {
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < FLOOD_RETRY_BUCKET_PREFIXES; i++) {
|
||||
@@ -1399,18 +1403,19 @@ uint8_t MyMesh::floodRetryBridgeTargetMask(uint8_t source_bucket) const {
|
||||
if ((configured[0] != 0 || configured[1] != 0 || configured[2] != 0)
|
||||
&& !floodRetryPrefixIgnored(configured, FLOOD_RETRY_PREFIX_LEN)
|
||||
&& floodRetryPrefixFresh(configured, FLOOD_RETRY_PREFIX_LEN)) {
|
||||
mask |= floodRetryBucketMask((uint8_t)bucket);
|
||||
mask |= bucket_mask;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (source_bucket != FLOOD_RETRY_BRIDGE_OTHER_BUCKET) {
|
||||
uint8_t other_mask = floodRetryBucketMask(FLOOD_RETRY_BRIDGE_OTHER_BUCKET);
|
||||
if ((source_mask & other_mask) == 0) {
|
||||
for (int i = 0; i < FLOOD_RETRY_PREFIX_SLOTS; i++) {
|
||||
const uint8_t* configured = _prefs.flood_retry_prefixes[i];
|
||||
if ((configured[0] != 0 || configured[1] != 0 || configured[2] != 0)
|
||||
&& !floodRetryPrefixIgnored(configured, FLOOD_RETRY_PREFIX_LEN)
|
||||
&& floodRetryPrefixFresh(configured, FLOOD_RETRY_PREFIX_LEN)) {
|
||||
mask |= floodRetryBucketMask(FLOOD_RETRY_BRIDGE_OTHER_BUCKET);
|
||||
mask |= other_mask;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1418,7 +1423,7 @@ uint8_t MyMesh::floodRetryBridgeTargetMask(uint8_t source_bucket) const {
|
||||
return mask;
|
||||
}
|
||||
|
||||
uint8_t MyMesh::floodRetryBridgeHeardMask(const mesh::Packet* packet, uint8_t source_bucket,
|
||||
uint8_t MyMesh::floodRetryBridgeHeardMask(const mesh::Packet* packet, uint8_t source_mask,
|
||||
uint8_t progress_marker) const {
|
||||
if (packet == NULL || packet->getPathHashCount() == 0) {
|
||||
return 0;
|
||||
@@ -1435,10 +1440,8 @@ uint8_t MyMesh::floodRetryBridgeHeardMask(const mesh::Packet* packet, uint8_t so
|
||||
path += hash_size;
|
||||
continue;
|
||||
}
|
||||
int bucket = floodRetryBucketForPathHop(path, hash_size, (uint8_t)hop, progress_marker);
|
||||
if (bucket >= 0 && bucket != source_bucket) {
|
||||
mask |= floodRetryBucketMask((uint8_t)bucket);
|
||||
}
|
||||
uint8_t bucket_mask = floodRetryBucketMaskForPathHop(path, hash_size, (uint8_t)hop, progress_marker);
|
||||
mask |= bucket_mask & (uint8_t)~source_mask;
|
||||
path += hash_size;
|
||||
}
|
||||
return mask;
|
||||
@@ -1465,25 +1468,25 @@ MyMesh::FloodRetryBridgeState* MyMesh::floodRetryBridgeStateFor(const mesh::Pack
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int source_bucket = floodRetrySourceBucket(packet);
|
||||
if (source_bucket < 0) {
|
||||
uint8_t source_mask = floodRetrySourceMask(packet);
|
||||
if (source_mask == 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint8_t target_mask = floodRetryBridgeTargetMask((uint8_t)source_bucket);
|
||||
uint8_t target_mask = floodRetryBridgeTargetMask(source_mask);
|
||||
if (target_mask == 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint8_t progress_marker = packet->getPathHashCount();
|
||||
uint8_t heard_mask = floodRetryBridgeHeardMask(packet, (uint8_t)source_bucket, progress_marker) & target_mask;
|
||||
uint8_t heard_mask = floodRetryBridgeHeardMask(packet, source_mask, progress_marker) & target_mask;
|
||||
if ((heard_mask & target_mask) == target_mask) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
memset(free_slot, 0, sizeof(*free_slot));
|
||||
memcpy(free_slot->key, key, sizeof(free_slot->key));
|
||||
free_slot->source_bucket = (uint8_t)source_bucket;
|
||||
free_slot->source_mask = source_mask;
|
||||
free_slot->target_mask = target_mask;
|
||||
free_slot->heard_mask = heard_mask;
|
||||
free_slot->progress_marker = progress_marker;
|
||||
@@ -1542,9 +1545,10 @@ void MyMesh::refreshFloodRetryHeardRecent(const mesh::Packet* packet) {
|
||||
path += hash_size;
|
||||
continue;
|
||||
}
|
||||
int bucket = floodRetryBucketForPathHop(path, hash_size, (uint8_t)hop, state->progress_marker);
|
||||
uint8_t bucket_mask = bucket >= 0 ? floodRetryBucketMask((uint8_t)bucket) : 0;
|
||||
if (bucket >= 0 && bucket != state->source_bucket && (state->target_mask & bucket_mask)) {
|
||||
uint8_t bucket_mask = floodRetryBucketMaskForPathHop(path, hash_size, (uint8_t)hop,
|
||||
state->progress_marker);
|
||||
bucket_mask &= state->target_mask & (uint8_t)~state->source_mask;
|
||||
if (bucket_mask != 0) {
|
||||
tables->setRecentRepeater(path, hash_size, packet->_snr, false, true);
|
||||
}
|
||||
path += hash_size;
|
||||
@@ -1622,11 +1626,16 @@ bool MyMesh::formatFloodRetryHeard(char* dest, size_t dest_len, const mesh::Pack
|
||||
path += hash_size;
|
||||
continue;
|
||||
}
|
||||
int bucket = floodRetryBucketForPathHop(path, hash_size, (uint8_t)hop, state->progress_marker);
|
||||
uint8_t bucket_mask = bucket >= 0 ? floodRetryBucketMask((uint8_t)bucket) : 0;
|
||||
if (bucket >= 0 && bucket != state->source_bucket && (state->target_mask & bucket_mask)) {
|
||||
uint8_t matching_mask = floodRetryBucketMaskForPathHop(path, hash_size, (uint8_t)hop,
|
||||
state->progress_marker);
|
||||
matching_mask &= state->target_mask & (uint8_t)~state->source_mask;
|
||||
for (uint8_t bucket = 0; bucket <= FLOOD_RETRY_BRIDGE_OTHER_BUCKET; bucket++) {
|
||||
uint8_t bucket_mask = floodRetryBucketMask(bucket);
|
||||
if ((matching_mask & bucket_mask) == 0) {
|
||||
continue;
|
||||
}
|
||||
char bucket_label[8];
|
||||
if ((uint8_t)bucket == FLOOD_RETRY_BRIDGE_OTHER_BUCKET) {
|
||||
if (bucket == FLOOD_RETRY_BRIDGE_OTHER_BUCKET) {
|
||||
strcpy(bucket_label, "other");
|
||||
} else {
|
||||
snprintf(bucket_label, sizeof(bucket_label), "b%d", bucket + 1);
|
||||
@@ -1776,9 +1785,9 @@ uint8_t MyMesh::getFloodRetryMaxAttempts(const mesh::Packet* packet) const {
|
||||
uint8_t attempts = constrain(_prefs.flood_retry_attempts, 0, 15);
|
||||
uint16_t scaled_attempts = attempts;
|
||||
uint8_t hops = packet != NULL ? packet->getPathHashCount() : 0;
|
||||
if (hops == 1) {
|
||||
if (hops == 0) {
|
||||
scaled_attempts = (uint16_t)attempts * 2U;
|
||||
} else if (hops == 2) {
|
||||
} else if (hops == 1) {
|
||||
scaled_attempts = (((uint16_t)attempts * 3U) + 1U) / 2U;
|
||||
}
|
||||
return scaled_attempts > 15 ? 15 : (uint8_t)scaled_attempts;
|
||||
@@ -1804,7 +1813,7 @@ bool MyMesh::isFloodRetryEchoTarget(const mesh::Packet* packet, uint8_t progress
|
||||
if (state == NULL) {
|
||||
return false;
|
||||
}
|
||||
state->heard_mask |= floodRetryBridgeHeardMask(packet, state->source_bucket, state->progress_marker) & state->target_mask;
|
||||
state->heard_mask |= floodRetryBridgeHeardMask(packet, state->source_mask, state->progress_marker) & state->target_mask;
|
||||
return (state->heard_mask & state->target_mask) == state->target_mask;
|
||||
}
|
||||
if (hasFloodRetryPrefixes()) {
|
||||
|
||||
@@ -137,7 +137,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
|
||||
RateLimiter discover_limiter, anon_limiter;
|
||||
struct FloodRetryBridgeState {
|
||||
uint8_t key[MAX_HASH_SIZE];
|
||||
uint8_t source_bucket;
|
||||
uint8_t source_mask;
|
||||
uint8_t target_mask;
|
||||
uint8_t heard_mask;
|
||||
uint8_t progress_marker;
|
||||
@@ -208,12 +208,12 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
|
||||
bool floodRetryPrefixIgnored(const uint8_t* prefix, uint8_t prefix_len) const;
|
||||
uint8_t floodRetryEffectivePathLength(const mesh::Packet* packet, uint8_t max_hops = 0xFF) const;
|
||||
bool floodRetryPrefixFresh(const uint8_t* prefix, uint8_t prefix_len) const;
|
||||
int floodRetryBucketForPrefix(const uint8_t* prefix, uint8_t prefix_len, bool require_fresh) const;
|
||||
int floodRetryBucketForPathHop(const uint8_t* prefix, uint8_t prefix_len, uint8_t hop,
|
||||
uint8_t progress_marker) const;
|
||||
int floodRetrySourceBucket(const mesh::Packet* packet) const;
|
||||
uint8_t floodRetryBridgeTargetMask(uint8_t source_bucket) const;
|
||||
uint8_t floodRetryBridgeHeardMask(const mesh::Packet* packet, uint8_t source_bucket,
|
||||
uint8_t floodRetryBucketMaskForPrefix(const uint8_t* prefix, uint8_t prefix_len, bool require_fresh) const;
|
||||
uint8_t floodRetryBucketMaskForPathHop(const uint8_t* prefix, uint8_t prefix_len, uint8_t hop,
|
||||
uint8_t progress_marker) const;
|
||||
uint8_t floodRetrySourceMask(const mesh::Packet* packet) const;
|
||||
uint8_t floodRetryBridgeTargetMask(uint8_t source_mask) const;
|
||||
uint8_t floodRetryBridgeHeardMask(const mesh::Packet* packet, uint8_t source_mask,
|
||||
uint8_t progress_marker) const;
|
||||
FloodRetryBridgeState* floodRetryBridgeStateFor(const mesh::Packet* packet, bool create) const;
|
||||
void clearFloodRetryBridgeState(const mesh::Packet* packet);
|
||||
|
||||
@@ -198,6 +198,7 @@ build_src_filter =
|
||||
-<*>
|
||||
+<../src/Utils.cpp>
|
||||
+<../src/Packet.cpp>
|
||||
+<../src/helpers/StaticPoolPacketManager.cpp>
|
||||
+<../src/helpers/ota/MerkleTree.cpp>
|
||||
+<../src/helpers/ota/MotaContainer.cpp>
|
||||
+<../src/helpers/ota/FirmwareInfo.cpp>
|
||||
|
||||
+4
-1
@@ -402,13 +402,16 @@ uint8_t Mesh::getFloodRetryMaxAttempts(const Packet* packet) const {
|
||||
return FLOOD_RETRY_MAX_ATTEMPTS_DEFAULT;
|
||||
}
|
||||
uint32_t Mesh::getFloodRetryAttemptDelay(const Packet* packet, uint8_t attempt_idx) {
|
||||
(void)attempt_idx;
|
||||
if (packet == NULL) {
|
||||
return _radio->getEstAirtimeFor(MAX_TRANS_UNIT);
|
||||
}
|
||||
|
||||
uint32_t max_packet_airtime = _radio->getEstAirtimeFor(MAX_TRANS_UNIT);
|
||||
uint32_t packet_airtime = _radio->getEstAirtimeFor(packet->getRawLength());
|
||||
return max_packet_airtime + (20UL * packet_airtime);
|
||||
uint32_t jitter_percent = _rng->nextInt(0, 201);
|
||||
uint32_t jitter = (packet_airtime * jitter_percent) / 100UL;
|
||||
return max_packet_airtime + (20UL * packet_airtime) + jitter;
|
||||
}
|
||||
uint8_t Mesh::getExtraAckTransmitCount() const {
|
||||
return 0;
|
||||
|
||||
@@ -571,6 +571,61 @@ static bool parseFloodRetryPrefixList(uint8_t dest[][FLOOD_RETRY_PREFIX_LEN], ui
|
||||
return true;
|
||||
}
|
||||
|
||||
static const uint8_t* floodRetryBucketPrefixAt(const NodePrefs* prefs, uint8_t bucket, uint8_t index) {
|
||||
if (bucket < FLOOD_RETRY_BRIDGE_BUCKETS && index < FLOOD_RETRY_BUCKET_PREFIXES) {
|
||||
return prefs->flood_retry_bridge_buckets[bucket][index];
|
||||
}
|
||||
if (bucket == FLOOD_RETRY_BRIDGE_BUCKETS && index < FLOOD_RETRY_PREFIX_SLOTS) {
|
||||
return prefs->flood_retry_prefixes[index];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static uint8_t floodRetryBucketPrefixCount(uint8_t bucket) {
|
||||
return bucket < FLOOD_RETRY_BRIDGE_BUCKETS ? FLOOD_RETRY_BUCKET_PREFIXES : FLOOD_RETRY_PREFIX_SLOTS;
|
||||
}
|
||||
|
||||
static bool findFloodRetryFirstByteCollision(const NodePrefs* prefs, uint8_t& first_byte,
|
||||
uint8_t& first_bucket, uint8_t& second_bucket) {
|
||||
for (uint8_t bucket_a = 0; bucket_a <= FLOOD_RETRY_BRIDGE_BUCKETS; bucket_a++) {
|
||||
for (uint8_t bucket_b = bucket_a + 1; bucket_b <= FLOOD_RETRY_BRIDGE_BUCKETS; bucket_b++) {
|
||||
for (uint8_t a = 0; a < floodRetryBucketPrefixCount(bucket_a); a++) {
|
||||
const uint8_t* prefix_a = floodRetryBucketPrefixAt(prefs, bucket_a, a);
|
||||
if (prefix_a == NULL || (prefix_a[0] == 0 && prefix_a[1] == 0 && prefix_a[2] == 0)) {
|
||||
continue;
|
||||
}
|
||||
for (uint8_t b = 0; b < floodRetryBucketPrefixCount(bucket_b); b++) {
|
||||
const uint8_t* prefix_b = floodRetryBucketPrefixAt(prefs, bucket_b, b);
|
||||
if (prefix_b != NULL && (prefix_b[0] != 0 || prefix_b[1] != 0 || prefix_b[2] != 0)
|
||||
&& prefix_a[0] == prefix_b[0]) {
|
||||
first_byte = prefix_a[0];
|
||||
first_bucket = bucket_a;
|
||||
second_bucket = bucket_b;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool formatFloodRetryBucketCollisionWarning(char* reply, const NodePrefs* prefs, const char* prefix) {
|
||||
uint8_t first_byte, first_bucket, second_bucket;
|
||||
if (!findFloodRetryFirstByteCollision(prefs, first_byte, first_bucket, second_bucket)) {
|
||||
return false;
|
||||
}
|
||||
if (second_bucket == FLOOD_RETRY_BRIDGE_BUCKETS) {
|
||||
snprintf(reply, 160, "%sWARNING: 1-byte %02X matches buckets %u and 7 (other)", prefix,
|
||||
(unsigned int)first_byte, (unsigned int)first_bucket + 1U);
|
||||
} else {
|
||||
snprintf(reply, 160, "%sWARNING: 1-byte %02X matches buckets %u and %u", prefix,
|
||||
(unsigned int)first_byte, (unsigned int)first_bucket + 1U,
|
||||
(unsigned int)second_bucket + 1U);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool parseFloodChannelBlockKey(const char* text, uint8_t secret[PUB_KEY_SIZE], uint8_t& key_len) {
|
||||
if (text == NULL || text[0] == 0) {
|
||||
return false;
|
||||
@@ -2509,7 +2564,9 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep
|
||||
} else if (memcmp(config, "flood.retry.prefixes ", 21) == 0) {
|
||||
if (parseFloodRetryPrefixList(_prefs->flood_retry_prefixes, FLOOD_RETRY_PREFIX_SLOTS, &config[21])) {
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
if (!formatFloodRetryBucketCollisionWarning(reply, _prefs, "OK - ")) {
|
||||
strcpy(reply, "OK");
|
||||
}
|
||||
} else {
|
||||
sprintf(reply, "Error, use up to %u comma-separated 3-byte hex prefixes",
|
||||
(unsigned int)FLOOD_RETRY_PREFIX_SLOTS);
|
||||
@@ -2539,7 +2596,9 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep
|
||||
if (strcmp(&config[19], "on") == 0) {
|
||||
_prefs->flood_retry_bridge_enabled = 1;
|
||||
savePrefs();
|
||||
strcpy(reply, "OK - flood.retry.prefixes acts as bucket 7 (other); multi-try bridge routing enabled");
|
||||
if (!formatFloodRetryBucketCollisionWarning(reply, _prefs, "OK - bucket 7=prefixes; ")) {
|
||||
strcpy(reply, "OK - flood.retry.prefixes acts as bucket 7 (other); multi-try bridge routing enabled");
|
||||
}
|
||||
} else if (strcmp(&config[19], "off") == 0) {
|
||||
_prefs->flood_retry_bridge_enabled = 0;
|
||||
savePrefs();
|
||||
@@ -2556,7 +2615,9 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep
|
||||
} else if (parseFloodRetryPrefixList(_prefs->flood_retry_bridge_buckets[bucket - 1],
|
||||
FLOOD_RETRY_BUCKET_PREFIXES, list + 1)) {
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
if (!formatFloodRetryBucketCollisionWarning(reply, _prefs, "OK - ")) {
|
||||
strcpy(reply, "OK");
|
||||
}
|
||||
} else {
|
||||
sprintf(reply, "Error, use up to %u comma-separated 3-byte hex prefixes",
|
||||
(unsigned int)FLOOD_RETRY_BUCKET_PREFIXES);
|
||||
|
||||
@@ -64,7 +64,8 @@ public:
|
||||
if (free_count < _emergency_floor
|
||||
|| (free_count < _rx_reserve && priority > MAX_PROTECTED_PRI)) {
|
||||
MESH_DEBUG_PRINTLN("RxReservePacketManager: pool below RX reserve, shedding outbound (pri %d)", (int)priority);
|
||||
free(packet);
|
||||
// queueOutbound() follows the PacketManager ownership contract: a false
|
||||
// return leaves the packet with the caller, which will release it once.
|
||||
return false;
|
||||
}
|
||||
recordAge(packet, scheduled_for);
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <helpers/RxReservePacketManager.h>
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
Reference in new issue
Block a user