mirror of
https://github.com/agessaman/MeshCore.git
synced 2026-08-07 01:39:46 +00:00
Add altpath replies and flood channel hop gates
This commit is contained in:
+39
-9
@@ -781,9 +781,11 @@ send text.flood checking ridge link
|
||||
|
||||
**Forwarding behavior:** Repeater firmware only. The repeater still receives and
|
||||
logs the packet when logging is enabled; this only blocks retransmission.
|
||||
This is checked before `flood.channel.block` and applies to every flood
|
||||
`GRP_DATA` packet regardless of channel key. Flood group text (`GRP_TXT`) is
|
||||
unaffected.
|
||||
This is checked before `flood.channel.block` and applies to flood `GRP_DATA`
|
||||
packets regardless of channel key, subject to `flood.channel.block.hops`.
|
||||
Flood group text (`GRP_TXT`) is unaffected by this setting.
|
||||
|
||||
`get flood.channel.data` includes the active hop gate as `h=all` or `h>N`.
|
||||
|
||||
---
|
||||
|
||||
@@ -792,10 +794,12 @@ unaffected.
|
||||
- `get flood.channel.block`
|
||||
- `get flood.channel.block.<n>`
|
||||
- `get flood.channel.block <name|8_hex_prefix>`
|
||||
- `set flood.channel.block <key> <name>`
|
||||
- `set flood.channel.block.<n> <key> <name>`
|
||||
- `set flood.channel.block #channel`
|
||||
- `set flood.channel.block.<n> #channel`
|
||||
- `get flood.channel.block.hops`
|
||||
- `set flood.channel.block <key> <name> [h=<all|1-7|default>]`
|
||||
- `set flood.channel.block.<n> <key> <name> [h=<all|1-7|default>]`
|
||||
- `set flood.channel.block #channel [h=<all|1-7|default>]`
|
||||
- `set flood.channel.block.<n> #channel [h=<all|1-7|default>]`
|
||||
- `set flood.channel.block.hops <all|1-7>`
|
||||
- `del flood.channel.block.<n>`
|
||||
- `del flood.channel.block <name|8_hex_prefix>`
|
||||
|
||||
@@ -803,8 +807,11 @@ unaffected.
|
||||
- `n`: Slot number from `1` to `15`.
|
||||
- `key`: 128-bit or 256-bit channel key as hex.
|
||||
- `#channel`: Public hashtag channel name; derives the 128-bit channel key from the hashtag and is stored as the row name.
|
||||
- `name`: Local label for hex-key rows. Not needed for `#channel`; any extra text after `#channel` is ignored.
|
||||
- `name`: Local label for hex-key rows. Not needed for `#channel`; extra text after `#channel` is ignored unless it is a hop setting.
|
||||
- `8_hex_prefix`: First 4 bytes of the derived channel hash, shown by single-entry `get`.
|
||||
- `all`: Block matching flood channel packets at any received flood hop count.
|
||||
- `1-7`: Maximum received flood path hash count to repeat. Matching packets over this hop count are blocked.
|
||||
- `default`: Row inherits the global `flood.channel.block.hops` setting.
|
||||
|
||||
**Slot behavior:** Without `.n`, `set flood.channel.block` updates an existing
|
||||
row with the same derived channel prefix or name, otherwise it uses the next
|
||||
@@ -815,7 +822,26 @@ writes that slot.
|
||||
flood `GRP_TXT` and `GRP_DATA` channel packets. The repeater still receives and
|
||||
logs the packet, but it does not retransmit it when a configured block entry can
|
||||
validate/decode it. If `flood.channel.data` is `off`, all flood `GRP_DATA`
|
||||
packets are blocked before this per-channel check runs.
|
||||
packets over the hop gate are blocked before this per-channel check runs.
|
||||
|
||||
**Hop gate:** `flood.channel.block.hops` defaults to `all`, which preserves the
|
||||
original behavior. When set to `N` from `1` to `7`, both `flood.channel.data off`
|
||||
and block rows that inherit the global setting only block packets whose received
|
||||
flood path hash count is greater than `N`; packets at `N` hops or lower can
|
||||
still repeat. For example, `set flood.channel.block.hops 1` repeats zero-hop and
|
||||
one-hop matches but blocks two-hop and longer matches.
|
||||
|
||||
Each block row can override the global hop gate with `h=<all|1-7|default>`.
|
||||
For example, `set flood.channel.block #wardriving h=3` blocks `#wardriving`
|
||||
matches above three hops, while `set flood.channel.block #bot h=7` blocks
|
||||
`#bot` matches above seven hops. Use `h=default` to make the row inherit the
|
||||
global setting again.
|
||||
|
||||
`get flood.channel.block` includes the global default first, then adds per-row
|
||||
overrides as `/h>N` or `/h=all`; inherited rows do not show a suffix. Single-row
|
||||
`get` replies include that row's stored hop mode as `h=def`, `h=all`, or `h>N`.
|
||||
List replies truncate displayed row names only when the full list would exceed
|
||||
the remote-management response limit.
|
||||
|
||||
**Matching behavior:** Each block entry stores the first 4 bytes of the derived
|
||||
channel hash for display and lookup. Current group packets carry only the first
|
||||
@@ -829,7 +855,11 @@ MAC/decrypt.
|
||||
```
|
||||
set flood.channel.block #test
|
||||
set flood.channel.block.2 9cd8fcf22a47333b591d96a2b848b73f #test
|
||||
set flood.channel.block.hops 3
|
||||
set flood.channel.block #wardriving h=3
|
||||
set flood.channel.block #bot h=7
|
||||
get flood.channel.block
|
||||
get flood.channel.block.hops
|
||||
get flood.channel.block #test
|
||||
del flood.channel.block.2
|
||||
```
|
||||
|
||||
@@ -77,7 +77,11 @@ set flood.retry.ignore none
|
||||
| `battery.alert.low` | Warning threshold percentage. Must be greater than `battery.alert.critical`. | `get battery.alert.low`, `set battery.alert.low <1-100>` | `set battery.alert.low 20` |
|
||||
| `battery.alert.critical` | Critical threshold percentage. Critical warnings repeat more often. | `get battery.alert.critical`, `set battery.alert.critical <0-99>` | `set battery.alert.critical 10` |
|
||||
| `recent.repeater` | Shows, seeds, or clears the recent repeater prefix/SNR table used by direct retry and bridge freshness checks. | `get recent.repeater`, `get recent.repeater <page>`, `set recent.repeater <prefix> <snr_db>`, `clear recent.repeater` | `set recent.repeater A1B2C3 -8.5` |
|
||||
| `flood.channel.data` | Turns forwarding of flood `GRP_DATA` channel packets on or off, subject to the channel block hop gate. | `get flood.channel.data`, `set flood.channel.data on/off` | `set flood.channel.data off` |
|
||||
| `flood.channel.block` | Blocks selected flood `GRP_TXT`/`GRP_DATA` channels when the key validates the packet. Add `h=<all|1-7|default>` for a per-channel hop override. | `get flood.channel.block`, `set flood.channel.block[.n] <key|#channel> [name] [h=...]`, `del flood.channel.block[.n]` | `set flood.channel.block #wardriving h=3` |
|
||||
| `flood.channel.block.hops` | Limits channel/data forwarding to short flood paths. `all` blocks matching packets at any hop count; `1`-`7` repeats packets at that hop count or lower and blocks longer matches. | `get flood.channel.block.hops`, `set flood.channel.block.hops <all|1-7>` | `set flood.channel.block.hops 3` |
|
||||
| `outpath` | Overrides the primary direct route used for replies to the current remote client. | `get outpath`, `set outpath <hops>`, `set outpath direct`, `set outpath clear`, `set outpath flood` | `set outpath A1B2C3,D4E5F6` |
|
||||
| `altpath` | Adds a secondary direct route for repeater replies to the current remote client. | `get altpath`, `set altpath <hops>`, `set altpath direct`, `set altpath clear`, `set altpath flood` | `set altpath 71CE82,BA09F0` |
|
||||
|
||||
## Other Keymind Commands
|
||||
|
||||
@@ -146,8 +150,8 @@ Serial CLI pages contain up to `128` rows. Remote LoRa CLI pages contain up to
|
||||
|
||||
## Direct Path Overrides
|
||||
|
||||
`outpath` applies to the current remote client ACL entry. It needs remote
|
||||
client context, so it is not useful from the local serial CLI.
|
||||
`outpath` and `altpath` apply to the current remote client ACL entry. They need
|
||||
remote client context, so they are not useful from the local serial CLI.
|
||||
|
||||
Set paths with comma-separated hop hashes. Each hop must be `2`, `4`, or `6`
|
||||
hex characters, and all hops in one path must use the same width.
|
||||
@@ -158,6 +162,9 @@ set outpath A1B2C3,D4E5F6
|
||||
set outpath direct
|
||||
set outpath clear
|
||||
set outpath flood
|
||||
get altpath
|
||||
set altpath 71CE82,BA09F0
|
||||
set altpath clear
|
||||
```
|
||||
|
||||
`set outpath direct` sets a zero-hop direct route for a client reachable without
|
||||
@@ -165,6 +172,14 @@ repeaters. `set outpath clear` forgets the override and lets normal path
|
||||
discovery fill it again. `set outpath flood` forces replies to use flood packets
|
||||
until the client logs in again.
|
||||
|
||||
When `outpath` is a valid direct path and `altpath` is also a valid, different
|
||||
direct path, repeater DM replies send two packets: one on `outpath` and one on
|
||||
`altpath`. The secondary `altpath` copy does not create its own direct-retry
|
||||
state, so retry tracking stays attached to the primary `outpath` packet.
|
||||
`altpath clear` disables the secondary direct reply. `altpath flood` is accepted
|
||||
for command symmetry, but it does not create a second flood reply; only a valid
|
||||
direct `altpath` sends the second packet.
|
||||
|
||||
## Direct Retry Settings
|
||||
|
||||
Direct retry applies to direct-routed packets. A queued resend is canceled when the next-hop echo is heard.
|
||||
|
||||
@@ -624,10 +624,64 @@ void MyMesh::sendFloodReply(mesh::Packet* packet, unsigned long delay_millis, ui
|
||||
}
|
||||
}
|
||||
|
||||
static bool directPathsEqual(const uint8_t* a_path, uint8_t a_len, const uint8_t* b_path, uint8_t b_len) {
|
||||
if (!mesh::Packet::isValidPathLen(a_len) || !mesh::Packet::isValidPathLen(b_len) || a_len != b_len) {
|
||||
return false;
|
||||
}
|
||||
uint8_t hash_count = a_len & 63;
|
||||
uint8_t hash_size = (a_len >> 6) + 1;
|
||||
uint8_t byte_len = hash_count * hash_size;
|
||||
return byte_len == 0 || memcmp(a_path, b_path, byte_len) == 0;
|
||||
}
|
||||
|
||||
void MyMesh::sendClientReply(ClientInfo* client, mesh::Packet* packet, unsigned long delay_millis, uint8_t path_hash_size) {
|
||||
if (packet == NULL) {
|
||||
return;
|
||||
}
|
||||
if (client == NULL || !mesh::Packet::isValidPathLen(client->out_path_len)) {
|
||||
sendFloodReply(packet, delay_millis, path_hash_size);
|
||||
return;
|
||||
}
|
||||
|
||||
mesh::Packet* alt = NULL;
|
||||
if (mesh::Packet::isValidPathLen(client->alt_path_len)
|
||||
&& !directPathsEqual(client->out_path, client->out_path_len, client->alt_path, client->alt_path_len)) {
|
||||
alt = obtainNewPacket();
|
||||
if (alt != NULL) {
|
||||
*alt = *packet;
|
||||
} else {
|
||||
MESH_DEBUG_PRINTLN("sendClientReply: altpath packet pool empty");
|
||||
}
|
||||
}
|
||||
|
||||
sendDirect(packet, client->out_path, client->out_path_len, delay_millis);
|
||||
if (alt != NULL) {
|
||||
uint8_t direct_retry_enabled = _prefs.direct_retry_enabled;
|
||||
_prefs.direct_retry_enabled = 0;
|
||||
sendDirect(alt, client->alt_path, client->alt_path_len, delay_millis);
|
||||
_prefs.direct_retry_enabled = direct_retry_enabled;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t MyMesh::resolveFloodChannelBlockHops(uint8_t max_hops) const {
|
||||
return max_hops == FLOOD_CHANNEL_BLOCK_HOPS_INHERIT ? _prefs.flood_channel_block_max_hops : max_hops;
|
||||
}
|
||||
|
||||
bool MyMesh::floodChannelBlockHopApplies(const mesh::Packet* packet, uint8_t max_hops) const {
|
||||
if (packet == NULL) {
|
||||
return false;
|
||||
}
|
||||
max_hops = resolveFloodChannelBlockHops(max_hops);
|
||||
return max_hops == FLOOD_CHANNEL_BLOCK_HOPS_ALL || packet->getPathHashCount() > max_hops;
|
||||
}
|
||||
|
||||
bool MyMesh::floodChannelBlockMatches(const FloodChannelBlockEntry& entry, const mesh::Packet* packet) const {
|
||||
if (!entry.active || packet == NULL || !packet->isRouteFlood()) {
|
||||
return false;
|
||||
}
|
||||
if (!floodChannelBlockHopApplies(packet, entry.max_hops)) {
|
||||
return false;
|
||||
}
|
||||
uint8_t type = packet->getPayloadType();
|
||||
if (type != PAYLOAD_TYPE_GRP_TXT && type != PAYLOAD_TYPE_GRP_DATA) {
|
||||
return false;
|
||||
@@ -645,8 +699,8 @@ bool MyMesh::floodChannelBlockMatches(const FloodChannelBlockEntry& entry, const
|
||||
bool MyMesh::shouldBlockFloodChannelForward(const mesh::Packet* packet) const {
|
||||
for (int i = 0; i < FLOOD_CHANNEL_BLOCK_SLOTS; i++) {
|
||||
if (floodChannelBlockMatches(flood_channel_blocks[i], packet)) {
|
||||
MESH_DEBUG_PRINTLN("allowPacketForward: flood.channel.block matched slot=%d name=%s",
|
||||
i + 1, flood_channel_blocks[i].name);
|
||||
MESH_DEBUG_PRINTLN("allowPacketForward: flood.channel.block matched slot=%d name=%s hops=%d",
|
||||
i + 1, flood_channel_blocks[i].name, packet->getPathHashCount());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -659,8 +713,11 @@ bool MyMesh::allowPacketForward(const mesh::Packet *packet) {
|
||||
if (packet->getPathHashCount() >= _prefs.flood_max) return false;
|
||||
if (packet->getRouteType() == ROUTE_TYPE_FLOOD && packet->getPathHashCount() >= _prefs.flood_max_unscoped) return false;
|
||||
if (packet->getPayloadType() == PAYLOAD_TYPE_ADVERT && packet->getPathHashCount() >= _prefs.flood_max_advert) return false;
|
||||
if (!_prefs.flood_channel_data_enabled && packet->getPayloadType() == PAYLOAD_TYPE_GRP_DATA) {
|
||||
MESH_DEBUG_PRINTLN("allowPacketForward: flood.channel.data off, blocking GRP_DATA");
|
||||
if (!_prefs.flood_channel_data_enabled
|
||||
&& packet->getPayloadType() == PAYLOAD_TYPE_GRP_DATA
|
||||
&& floodChannelBlockHopApplies(packet, _prefs.flood_channel_block_max_hops)) {
|
||||
MESH_DEBUG_PRINTLN("allowPacketForward: flood.channel.data off, blocking GRP_DATA hops=%d",
|
||||
packet->getPathHashCount());
|
||||
return false;
|
||||
}
|
||||
if (shouldBlockFloodChannelForward(packet)) return false;
|
||||
@@ -1919,13 +1976,7 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx,
|
||||
} else {
|
||||
mesh::Packet *reply =
|
||||
createDatagram(PAYLOAD_TYPE_RESPONSE, client->id, secret, reply_data, reply_len);
|
||||
if (reply) {
|
||||
if (mesh::Packet::isValidPathLen(client->out_path_len)) { // we have an out_path, so send DIRECT
|
||||
sendDirect(reply, client->out_path, client->out_path_len, SERVER_RESPONSE_DELAY);
|
||||
} else {
|
||||
sendFloodReply(reply, SERVER_RESPONSE_DELAY, packet->getPathHashSize());
|
||||
}
|
||||
}
|
||||
sendClientReply(client, reply, SERVER_RESPONSE_DELAY, packet->getPathHashSize());
|
||||
}
|
||||
} else {
|
||||
MESH_DEBUG_PRINTLN("onPeerDataRecv: possible replay attack detected");
|
||||
@@ -1952,13 +2003,7 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx,
|
||||
PUB_KEY_SIZE);
|
||||
|
||||
mesh::Packet *ack = createAck(ack_hash);
|
||||
if (ack) {
|
||||
if (mesh::Packet::isValidPathLen(client->out_path_len)) {
|
||||
sendDirect(ack, client->out_path, client->out_path_len, TXT_ACK_DELAY);
|
||||
} else {
|
||||
sendFloodReply(ack, TXT_ACK_DELAY, packet->getPathHashSize());
|
||||
}
|
||||
}
|
||||
sendClientReply(client, ack, TXT_ACK_DELAY, packet->getPathHashSize());
|
||||
}
|
||||
|
||||
uint8_t temp[166];
|
||||
@@ -1980,13 +2025,7 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx,
|
||||
temp[4] = (TXT_TYPE_CLI_DATA << 2); // NOTE: legacy was: TXT_TYPE_PLAIN
|
||||
|
||||
auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, client->id, secret, temp, 5 + text_len);
|
||||
if (reply) {
|
||||
if (mesh::Packet::isValidPathLen(client->out_path_len)) {
|
||||
sendDirect(reply, client->out_path, client->out_path_len, CLI_REPLY_DELAY_MILLIS);
|
||||
} else {
|
||||
sendFloodReply(reply, CLI_REPLY_DELAY_MILLIS, packet->getPathHashSize());
|
||||
}
|
||||
}
|
||||
sendClientReply(client, reply, CLI_REPLY_DELAY_MILLIS, packet->getPathHashSize());
|
||||
}
|
||||
} else {
|
||||
MESH_DEBUG_PRINTLN("onPeerDataRecv: possible replay attack detected");
|
||||
@@ -2172,6 +2211,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
|
||||
_prefs.flood_retry_bridge_enabled = 0;
|
||||
_prefs.flood_retry_advert_enabled = FLOOD_RETRY_ADVERT_DEFAULT;
|
||||
_prefs.flood_channel_data_enabled = 1;
|
||||
_prefs.flood_channel_block_max_hops = FLOOD_CHANNEL_BLOCK_HOPS_ALL;
|
||||
_prefs.battery_alert_enabled = 0;
|
||||
_prefs.battery_alert_low_percent = BATTERY_ALERT_LOW_PERCENT_DEFAULT;
|
||||
_prefs.battery_alert_critical_percent = BATTERY_ALERT_CRITICAL_PERCENT_DEFAULT;
|
||||
@@ -3011,18 +3051,20 @@ void MyMesh::loadFloodChannelBlocks() {
|
||||
uint8_t magic[4];
|
||||
uint8_t count = 0;
|
||||
bool success = file.read(magic, sizeof(magic)) == sizeof(magic)
|
||||
&& memcmp(magic, "FCB1", sizeof(magic)) == 0
|
||||
&& memcmp(magic, "FCB2", sizeof(magic)) == 0
|
||||
&& file.read(&count, sizeof(count)) == sizeof(count);
|
||||
|
||||
for (int i = 0; success && i < count && i < FLOOD_CHANNEL_BLOCK_SLOTS; i++) {
|
||||
uint8_t active = 0;
|
||||
uint8_t key_len = 0;
|
||||
uint8_t max_hops = FLOOD_CHANNEL_BLOCK_HOPS_INHERIT;
|
||||
uint8_t hash_prefix[FLOOD_CHANNEL_BLOCK_PREFIX_LEN];
|
||||
uint8_t secret[PUB_KEY_SIZE];
|
||||
char name[FLOOD_CHANNEL_BLOCK_NAME_LEN];
|
||||
|
||||
success = file.read(&active, sizeof(active)) == sizeof(active);
|
||||
success = success && file.read(&key_len, sizeof(key_len)) == sizeof(key_len);
|
||||
success = success && file.read(&max_hops, sizeof(max_hops)) == sizeof(max_hops);
|
||||
success = success && file.read(hash_prefix, sizeof(hash_prefix)) == sizeof(hash_prefix);
|
||||
success = success && file.read(secret, sizeof(secret)) == sizeof(secret);
|
||||
success = success && file.read((uint8_t*)name, sizeof(name)) == sizeof(name);
|
||||
@@ -3035,6 +3077,9 @@ void MyMesh::loadFloodChannelBlocks() {
|
||||
auto& entry = flood_channel_blocks[i];
|
||||
entry.active = true;
|
||||
entry.key_len = key_len;
|
||||
entry.max_hops = (max_hops == FLOOD_CHANNEL_BLOCK_HOPS_ALL
|
||||
|| max_hops == FLOOD_CHANNEL_BLOCK_HOPS_INHERIT
|
||||
|| (max_hops >= 1 && max_hops <= 7)) ? max_hops : FLOOD_CHANNEL_BLOCK_HOPS_INHERIT;
|
||||
memcpy(entry.secret, secret, sizeof(entry.secret));
|
||||
if (entry.key_len == CIPHER_KEY_SIZE) {
|
||||
memset(&entry.secret[CIPHER_KEY_SIZE], 0, PUB_KEY_SIZE - CIPHER_KEY_SIZE);
|
||||
@@ -3057,7 +3102,7 @@ bool MyMesh::saveFloodChannelBlocks() {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint8_t magic[4] = {'F', 'C', 'B', '1'};
|
||||
const uint8_t magic[4] = {'F', 'C', 'B', '2'};
|
||||
uint8_t count = FLOOD_CHANNEL_BLOCK_SLOTS;
|
||||
bool success = file.write(magic, sizeof(magic)) == sizeof(magic);
|
||||
success = success && file.write(&count, sizeof(count)) == sizeof(count);
|
||||
@@ -3065,8 +3110,10 @@ bool MyMesh::saveFloodChannelBlocks() {
|
||||
for (int i = 0; success && i < FLOOD_CHANNEL_BLOCK_SLOTS; i++) {
|
||||
const auto& entry = flood_channel_blocks[i];
|
||||
uint8_t active = entry.active ? 1 : 0;
|
||||
uint8_t max_hops = entry.active ? entry.max_hops : FLOOD_CHANNEL_BLOCK_HOPS_INHERIT;
|
||||
success = file.write(&active, sizeof(active)) == sizeof(active);
|
||||
success = success && file.write(&entry.key_len, sizeof(entry.key_len)) == sizeof(entry.key_len);
|
||||
success = success && file.write(&max_hops, sizeof(max_hops)) == sizeof(max_hops);
|
||||
success = success && file.write(entry.hash_prefix, sizeof(entry.hash_prefix)) == sizeof(entry.hash_prefix);
|
||||
success = success && file.write(entry.secret, sizeof(entry.secret)) == sizeof(entry.secret);
|
||||
success = success && file.write((const uint8_t*)entry.name, sizeof(entry.name)) == sizeof(entry.name);
|
||||
@@ -3100,6 +3147,16 @@ static bool parseFloodChannelBlockPrefixSelector(const char* selector,
|
||||
return mesh::Utils::fromHex(prefix, FLOOD_CHANNEL_BLOCK_PREFIX_LEN, text);
|
||||
}
|
||||
|
||||
static void formatFloodChannelBlockHops(char* dest, uint8_t max_hops) {
|
||||
if (max_hops == FLOOD_CHANNEL_BLOCK_HOPS_ALL) {
|
||||
strcpy(dest, "h=all");
|
||||
} else if (max_hops == FLOOD_CHANNEL_BLOCK_HOPS_INHERIT) {
|
||||
strcpy(dest, "h=def");
|
||||
} else {
|
||||
sprintf(dest, "h>%u", (unsigned int)max_hops);
|
||||
}
|
||||
}
|
||||
|
||||
int MyMesh::findFloodChannelBlockBySelector(const char* selector) const {
|
||||
uint8_t prefix[FLOOD_CHANNEL_BLOCK_PREFIX_LEN];
|
||||
if (parseFloodChannelBlockPrefixSelector(selector, prefix)) {
|
||||
@@ -3154,17 +3211,19 @@ void MyMesh::formatFloodChannelBlockDetail(char* reply, int idx) const {
|
||||
|
||||
const auto& entry = flood_channel_blocks[idx];
|
||||
if (!entry.active) {
|
||||
snprintf(reply, 160, "> %d empty", idx + 1);
|
||||
snprintf(reply, 150, "> %d empty", idx + 1);
|
||||
return;
|
||||
}
|
||||
|
||||
char prefix[FLOOD_CHANNEL_BLOCK_PREFIX_LEN * 2 + 1];
|
||||
char hops[8];
|
||||
mesh::Utils::toHex(prefix, entry.hash_prefix, FLOOD_CHANNEL_BLOCK_PREFIX_LEN);
|
||||
snprintf(reply, 160, "> %d %s %u %s", idx + 1, prefix, (unsigned int)entry.key_len * 8, entry.name);
|
||||
formatFloodChannelBlockHops(hops, entry.max_hops);
|
||||
snprintf(reply, 150, "> %d %s %u %s %s", idx + 1, prefix, (unsigned int)entry.key_len * 8, hops, entry.name);
|
||||
}
|
||||
|
||||
void MyMesh::setFloodChannelBlock(int index, const uint8_t* secret, uint8_t key_len,
|
||||
const char* name, char* reply) {
|
||||
const char* name, uint8_t max_hops, char* reply) {
|
||||
if ((key_len != CIPHER_KEY_SIZE && key_len != PUB_KEY_SIZE) || secret == NULL || name == NULL || name[0] == 0) {
|
||||
strcpy(reply, "Err - bad params");
|
||||
return;
|
||||
@@ -3173,6 +3232,12 @@ void MyMesh::setFloodChannelBlock(int index, const uint8_t* secret, uint8_t key_
|
||||
snprintf(reply, 160, "Err - index 1-%d", FLOOD_CHANNEL_BLOCK_SLOTS);
|
||||
return;
|
||||
}
|
||||
if (max_hops != FLOOD_CHANNEL_BLOCK_HOPS_ALL
|
||||
&& max_hops != FLOOD_CHANNEL_BLOCK_HOPS_INHERIT
|
||||
&& (max_hops < 1 || max_hops > 7)) {
|
||||
strcpy(reply, "Err - bad hops");
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t prefix[FLOOD_CHANNEL_BLOCK_PREFIX_LEN];
|
||||
deriveFloodChannelBlockPrefix(secret, key_len, prefix);
|
||||
@@ -3186,6 +3251,7 @@ void MyMesh::setFloodChannelBlock(int index, const uint8_t* secret, uint8_t key_
|
||||
clearFloodChannelBlockEntry(entry);
|
||||
entry.active = true;
|
||||
entry.key_len = key_len;
|
||||
entry.max_hops = max_hops;
|
||||
memcpy(entry.secret, secret, PUB_KEY_SIZE);
|
||||
if (entry.key_len == CIPHER_KEY_SIZE) {
|
||||
memset(&entry.secret[CIPHER_KEY_SIZE], 0, PUB_KEY_SIZE - CIPHER_KEY_SIZE);
|
||||
@@ -3212,8 +3278,24 @@ void MyMesh::formatFloodChannelBlocks(const char* selector, char* reply) {
|
||||
}
|
||||
|
||||
char* out = reply;
|
||||
size_t remaining = 160;
|
||||
int written = snprintf(out, remaining, ">");
|
||||
const size_t reply_limit = 150;
|
||||
size_t remaining = reply_limit;
|
||||
char hops[8];
|
||||
formatFloodChannelBlockHops(hops, _prefs.flood_channel_block_max_hops);
|
||||
size_t full_len = 2 + strlen(hops);
|
||||
for (int i = 0; i < FLOOD_CHANNEL_BLOCK_SLOTS; i++) {
|
||||
const auto& entry = flood_channel_blocks[i];
|
||||
size_t display_len = entry.active ? strlen(entry.name) : 1;
|
||||
full_len += 1 + (i + 1 >= 10 ? 2 : 1) + 1 + display_len;
|
||||
if (entry.active && entry.max_hops != FLOOD_CHANNEL_BLOCK_HOPS_INHERIT) {
|
||||
char row_hops[8];
|
||||
formatFloodChannelBlockHops(row_hops, entry.max_hops);
|
||||
full_len += 1 + strlen(row_hops);
|
||||
}
|
||||
}
|
||||
bool trim_names = full_len >= reply_limit;
|
||||
|
||||
int written = snprintf(out, remaining, "> %s", hops);
|
||||
if (written < 0 || (size_t)written >= remaining) {
|
||||
reply[0] = 0;
|
||||
return;
|
||||
@@ -3222,18 +3304,30 @@ void MyMesh::formatFloodChannelBlocks(const char* selector, char* reply) {
|
||||
remaining -= written;
|
||||
|
||||
for (int i = 0; i < FLOOD_CHANNEL_BLOCK_SLOTS && remaining > 1; i++) {
|
||||
char display[8];
|
||||
const char* display = "-";
|
||||
char short_display[6];
|
||||
const auto& entry = flood_channel_blocks[i];
|
||||
if (!entry.active) {
|
||||
strcpy(display, "-");
|
||||
} else {
|
||||
StrHelper::strncpy(display, entry.name, sizeof(display));
|
||||
if (strlen(entry.name) >= sizeof(display)) {
|
||||
display[sizeof(display) - 2] = '~';
|
||||
display[sizeof(display) - 1] = 0;
|
||||
if (entry.active) {
|
||||
char row_hops[8];
|
||||
row_hops[0] = 0;
|
||||
if (entry.max_hops != FLOOD_CHANNEL_BLOCK_HOPS_INHERIT) {
|
||||
formatFloodChannelBlockHops(row_hops, entry.max_hops);
|
||||
}
|
||||
if (trim_names) {
|
||||
StrHelper::strncpy(short_display, entry.name, sizeof(short_display));
|
||||
if (strlen(entry.name) >= sizeof(short_display)) {
|
||||
short_display[sizeof(short_display) - 2] = '~';
|
||||
short_display[sizeof(short_display) - 1] = 0;
|
||||
}
|
||||
display = short_display;
|
||||
} else {
|
||||
display = entry.name;
|
||||
}
|
||||
written = snprintf(out, remaining, " %d:%s%s%s", i + 1, display,
|
||||
row_hops[0] ? "/" : "", row_hops);
|
||||
} else {
|
||||
written = snprintf(out, remaining, " %d:%s", i + 1, display);
|
||||
}
|
||||
written = snprintf(out, remaining, " %d:%s", i + 1, display);
|
||||
if (written < 0 || (size_t)written >= remaining) {
|
||||
out[remaining - 1] = 0;
|
||||
break;
|
||||
@@ -3478,14 +3572,23 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, ClientInfo* sender, char *
|
||||
reply[0] = 0;
|
||||
} else if (strcmp(command, "get outpath") == 0
|
||||
|| strcmp(command, "set outpath") == 0
|
||||
|| strncmp(command, "set outpath ", 12) == 0) {
|
||||
|| strncmp(command, "set outpath ", 12) == 0
|
||||
|| strcmp(command, "get altpath") == 0
|
||||
|| strcmp(command, "set altpath") == 0
|
||||
|| strncmp(command, "set altpath ", 12) == 0) {
|
||||
bool is_get = strncmp(command, "get ", 4) == 0;
|
||||
bool is_alt = strncmp(command + 4, "altpath", 7) == 0;
|
||||
if (sender == NULL) {
|
||||
strcpy(reply, "Err - command needs remote client context");
|
||||
} else if (is_get) {
|
||||
formatPathReply(sender->out_path, sender->out_path_len, reply, 160);
|
||||
} else {
|
||||
char* spec = command + 11; // length of "set outpath"
|
||||
uint8_t* stored_path = is_alt ? sender->alt_path : sender->out_path;
|
||||
uint8_t* stored_path_len = is_alt ? &sender->alt_path_len : &sender->out_path_len;
|
||||
if (is_get) {
|
||||
formatPathReply(stored_path, *stored_path_len, reply, 160);
|
||||
return;
|
||||
}
|
||||
|
||||
char* spec = command + 11; // length of "set outpath" or "set altpath"
|
||||
if (*spec == ' ') spec++;
|
||||
|
||||
uint8_t path[MAX_PATH_SIZE];
|
||||
@@ -3495,13 +3598,13 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, ClientInfo* sender, char *
|
||||
strcpy(reply, err ? err : "Err - invalid path");
|
||||
} else {
|
||||
if (path_len == OUT_PATH_UNKNOWN || path_len == OUT_PATH_FORCE_FLOOD) {
|
||||
memset(sender->out_path, 0, sizeof(sender->out_path));
|
||||
sender->out_path_len = path_len;
|
||||
memset(stored_path, 0, MAX_PATH_SIZE);
|
||||
*stored_path_len = path_len;
|
||||
} else {
|
||||
sender->out_path_len = mesh::Packet::copyPath(sender->out_path, path, path_len);
|
||||
*stored_path_len = mesh::Packet::copyPath(stored_path, path, path_len);
|
||||
}
|
||||
dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY);
|
||||
formatPathReply(sender->out_path, sender->out_path_len, reply, 160);
|
||||
formatPathReply(stored_path, *stored_path_len, reply, 160);
|
||||
}
|
||||
}
|
||||
} else if (strncmp(command, "send text.flood ", 16) == 0) {
|
||||
|
||||
@@ -135,6 +135,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
|
||||
struct FloodChannelBlockEntry {
|
||||
bool active;
|
||||
uint8_t key_len;
|
||||
uint8_t max_hops;
|
||||
uint8_t hash_prefix[FLOOD_CHANNEL_BLOCK_PREFIX_LEN];
|
||||
uint8_t secret[PUB_KEY_SIZE];
|
||||
char name[FLOOD_CHANNEL_BLOCK_NAME_LEN];
|
||||
@@ -206,6 +207,8 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
|
||||
void clearFloodChannelBlockEntry(FloodChannelBlockEntry& entry);
|
||||
void deriveFloodChannelBlockPrefix(const uint8_t* secret, uint8_t key_len,
|
||||
uint8_t prefix[FLOOD_CHANNEL_BLOCK_PREFIX_LEN]) const;
|
||||
uint8_t resolveFloodChannelBlockHops(uint8_t max_hops) const;
|
||||
bool floodChannelBlockHopApplies(const mesh::Packet* packet, uint8_t max_hops) const;
|
||||
bool floodChannelBlockMatches(const FloodChannelBlockEntry& entry, const mesh::Packet* packet) const;
|
||||
bool shouldBlockFloodChannelForward(const mesh::Packet* packet) const;
|
||||
int findFloodChannelBlockBySelector(const char* selector) const;
|
||||
@@ -291,6 +294,7 @@ protected:
|
||||
void onControlDataRecv(mesh::Packet* packet) override;
|
||||
|
||||
void sendFloodReply(mesh::Packet* packet, unsigned long delay_millis, uint8_t path_hash_size);
|
||||
void sendClientReply(ClientInfo* client, mesh::Packet* packet, unsigned long delay_millis, uint8_t path_hash_size);
|
||||
|
||||
public:
|
||||
MyMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::MeshTables& tables);
|
||||
@@ -342,7 +346,7 @@ public:
|
||||
bool saveRegions() override;
|
||||
void onDefaultRegionChanged(const RegionEntry* r) override;
|
||||
void setFloodChannelBlock(int index, const uint8_t* secret, uint8_t key_len,
|
||||
const char* name, char* reply) override;
|
||||
const char* name, uint8_t max_hops, char* reply) override;
|
||||
void formatFloodChannelBlocks(const char* selector, char* reply) override;
|
||||
void deleteFloodChannelBlock(const char* selector, char* reply) override;
|
||||
|
||||
|
||||
+131
-4
@@ -325,6 +325,98 @@ static void formatFloodRetryPathGate(char* dest, uint8_t path_gate) {
|
||||
}
|
||||
}
|
||||
|
||||
static bool parseFloodChannelBlockHops(const char* value, uint8_t& max_hops) {
|
||||
if (value == NULL) {
|
||||
return false;
|
||||
}
|
||||
value = skipSpacesConst(value);
|
||||
if (strcmp(value, "all") == 0) {
|
||||
max_hops = FLOOD_CHANNEL_BLOCK_HOPS_ALL;
|
||||
return true;
|
||||
}
|
||||
return parseUint8Strict(value, 1, 7, max_hops);
|
||||
}
|
||||
|
||||
static bool parseFloodChannelBlockRowHops(const char* value, uint8_t& max_hops) {
|
||||
if (value == NULL) {
|
||||
return false;
|
||||
}
|
||||
value = skipSpacesConst(value);
|
||||
if (strcmp(value, "default") == 0 || strcmp(value, "def") == 0 || strcmp(value, "inherit") == 0) {
|
||||
max_hops = FLOOD_CHANNEL_BLOCK_HOPS_INHERIT;
|
||||
return true;
|
||||
}
|
||||
return parseFloodChannelBlockHops(value, max_hops);
|
||||
}
|
||||
|
||||
static bool parseFloodChannelBlockHopAssignment(const char* text, bool allow_bare, uint8_t& max_hops) {
|
||||
char token[16];
|
||||
text = skipSpacesConst(text);
|
||||
if (text == NULL || *text == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t len = 0;
|
||||
while (text[len] && text[len] != ' ' && len + 1 < sizeof(token)) {
|
||||
token[len] = text[len];
|
||||
len++;
|
||||
}
|
||||
token[len] = 0;
|
||||
|
||||
const char* value = NULL;
|
||||
if (strncmp(token, "h=", 2) == 0) {
|
||||
value = token + 2;
|
||||
} else if (strncmp(token, "hops=", 5) == 0) {
|
||||
value = token + 5;
|
||||
} else if (allow_bare) {
|
||||
value = token;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return parseFloodChannelBlockRowHops(value, max_hops);
|
||||
}
|
||||
|
||||
static bool looksFloodChannelBlockHopAssignment(const char* text) {
|
||||
text = skipSpacesConst(text);
|
||||
if (text == NULL || *text == 0) {
|
||||
return false;
|
||||
}
|
||||
return (*text >= '0' && *text <= '9')
|
||||
|| strncmp(text, "h=", 2) == 0
|
||||
|| strncmp(text, "hops=", 5) == 0
|
||||
|| strncmp(text, "all", 3) == 0
|
||||
|| strncmp(text, "def", 3) == 0
|
||||
|| strncmp(text, "default", 7) == 0
|
||||
|| strncmp(text, "inherit", 7) == 0;
|
||||
}
|
||||
|
||||
static bool trimFloodChannelBlockHopSuffix(char* name, uint8_t& max_hops) {
|
||||
size_t len = strlen(name);
|
||||
while (len > 0 && name[len - 1] == ' ') {
|
||||
name[--len] = 0;
|
||||
}
|
||||
char* token = strrchr(name, ' ');
|
||||
if (token == NULL) {
|
||||
return true;
|
||||
}
|
||||
if (strncmp(token + 1, "h=", 2) != 0 && strncmp(token + 1, "hops=", 5) != 0) {
|
||||
return true;
|
||||
}
|
||||
if (!parseFloodChannelBlockHopAssignment(token + 1, false, max_hops)) {
|
||||
return false;
|
||||
}
|
||||
*token = 0;
|
||||
return strlen(name) > 0;
|
||||
}
|
||||
|
||||
static void formatFloodChannelBlockHops(char* dest, uint8_t max_hops) {
|
||||
if (max_hops == FLOOD_CHANNEL_BLOCK_HOPS_ALL) {
|
||||
strcpy(dest, "h=all");
|
||||
} else {
|
||||
sprintf(dest, "h>%u", (unsigned int)max_hops);
|
||||
}
|
||||
}
|
||||
|
||||
static void formatFloodRetryPrefixList(char* dest, const uint8_t prefixes[][FLOOD_RETRY_PREFIX_LEN],
|
||||
uint8_t max_prefixes) {
|
||||
char* out = dest;
|
||||
@@ -611,6 +703,7 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
|
||||
_prefs->battery_alert_critical_percent = BATTERY_ALERT_CRITICAL_PERCENT_DEFAULT;
|
||||
_prefs->direct_retry_recent_enabled = DIRECT_RETRY_RECENT_DEFAULT;
|
||||
_prefs->flood_channel_data_enabled = 1;
|
||||
_prefs->flood_channel_block_max_hops = FLOOD_CHANNEL_BLOCK_HOPS_ALL;
|
||||
bool has_flood_retry_prefs = file.available() >= 2;
|
||||
if (has_flood_retry_prefs) {
|
||||
file.read((uint8_t *)&_prefs->flood_retry_attempts, sizeof(_prefs->flood_retry_attempts)); // 311
|
||||
@@ -645,8 +738,11 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
|
||||
if (file.available() >= (int)sizeof(_prefs->flood_channel_data_enabled)) {
|
||||
file.read((uint8_t *)&_prefs->flood_channel_data_enabled, sizeof(_prefs->flood_channel_data_enabled));
|
||||
}
|
||||
if (file.available() >= (int)sizeof(_prefs->flood_channel_block_max_hops)) {
|
||||
file.read((uint8_t *)&_prefs->flood_channel_block_max_hops, sizeof(_prefs->flood_channel_block_max_hops));
|
||||
}
|
||||
}
|
||||
// next: 673
|
||||
// next: 674
|
||||
|
||||
// sanitise bad pref values
|
||||
_prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0, 20.0f);
|
||||
@@ -707,6 +803,10 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
|
||||
_prefs->battery_alert_enabled = constrain(_prefs->battery_alert_enabled, 0, 1);
|
||||
_prefs->direct_retry_recent_enabled = constrain(_prefs->direct_retry_recent_enabled, 0, 1);
|
||||
_prefs->flood_channel_data_enabled = constrain(_prefs->flood_channel_data_enabled, 0, 1);
|
||||
if (_prefs->flood_channel_block_max_hops != FLOOD_CHANNEL_BLOCK_HOPS_ALL
|
||||
&& (_prefs->flood_channel_block_max_hops < 1 || _prefs->flood_channel_block_max_hops > 7)) {
|
||||
_prefs->flood_channel_block_max_hops = FLOOD_CHANNEL_BLOCK_HOPS_ALL;
|
||||
}
|
||||
if (_prefs->battery_alert_low_percent < 1
|
||||
|| _prefs->battery_alert_low_percent > 100
|
||||
|| _prefs->battery_alert_critical_percent >= _prefs->battery_alert_low_percent) {
|
||||
@@ -805,7 +905,8 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) {
|
||||
file.write((uint8_t *)&_prefs->battery_alert_critical_percent, sizeof(_prefs->battery_alert_critical_percent));
|
||||
file.write((uint8_t *)&_prefs->direct_retry_recent_enabled, sizeof(_prefs->direct_retry_recent_enabled));
|
||||
file.write((uint8_t *)&_prefs->flood_channel_data_enabled, sizeof(_prefs->flood_channel_data_enabled));
|
||||
// next: 673
|
||||
file.write((uint8_t *)&_prefs->flood_channel_block_max_hops, sizeof(_prefs->flood_channel_block_max_hops));
|
||||
// next: 674
|
||||
|
||||
file.close();
|
||||
}
|
||||
@@ -1461,6 +1562,15 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep
|
||||
} else {
|
||||
strcpy(reply, "Error, must be on or off");
|
||||
}
|
||||
} else if (memcmp(config, "flood.channel.block.hops ", 25) == 0) {
|
||||
uint8_t max_hops;
|
||||
if (parseFloodChannelBlockHops(&config[25], max_hops)) {
|
||||
_prefs->flood_channel_block_max_hops = max_hops;
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
} else {
|
||||
strcpy(reply, "Error, must be all or 1-7");
|
||||
}
|
||||
} else if (memcmp(config, "flood.channel.block", 19) == 0
|
||||
&& (config[19] == ' ' || config[19] == '.')) {
|
||||
const char* cursor = &config[19];
|
||||
@@ -1483,10 +1593,21 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep
|
||||
key_text[key_len_text] = 0;
|
||||
|
||||
char name[FLOOD_CHANNEL_BLOCK_NAME_LEN];
|
||||
uint8_t block_hops = FLOOD_CHANNEL_BLOCK_HOPS_INHERIT;
|
||||
if (key_text[0] == '#') {
|
||||
StrHelper::strncpy(name, key_text, sizeof(name));
|
||||
const char* extra = skipSpacesConst(cursor);
|
||||
if (*extra && looksFloodChannelBlockHopAssignment(extra)
|
||||
&& !parseFloodChannelBlockHopAssignment(extra, true, block_hops)) {
|
||||
strcpy(reply, "Error, hops must be all, default, or 1-7");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
copyTrimmedFloodChannelBlockName(name, sizeof(name), cursor);
|
||||
if (!trimFloodChannelBlockHopSuffix(name, block_hops)) {
|
||||
strcpy(reply, "Error, bad name or hops");
|
||||
return;
|
||||
}
|
||||
}
|
||||
uint8_t secret[PUB_KEY_SIZE];
|
||||
uint8_t decoded_key_len = 0;
|
||||
@@ -1495,7 +1616,7 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep
|
||||
} else if (name[0] == 0 || !isValidName(name)) {
|
||||
strcpy(reply, "Error, bad name");
|
||||
} else {
|
||||
_callbacks->setFloodChannelBlock(index, secret, decoded_key_len, name, reply);
|
||||
_callbacks->setFloodChannelBlock(index, secret, decoded_key_len, name, block_hops, reply);
|
||||
}
|
||||
} else if (memcmp(config, "flood.retry.count ", 18) == 0) {
|
||||
int attempts = looksUnsignedInteger(&config[18]) ? _atoi(&config[18]) : -1;
|
||||
@@ -1820,7 +1941,13 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep
|
||||
} else if (memcmp(config, "txdelay", 7) == 0) {
|
||||
sprintf(reply, "> %s", StrHelper::ftoa(_prefs->tx_delay_factor));
|
||||
} else if (memcmp(config, "flood.channel.data", 18) == 0) {
|
||||
sprintf(reply, "> %s", _prefs->flood_channel_data_enabled ? "on" : "off");
|
||||
char hops[8];
|
||||
formatFloodChannelBlockHops(hops, _prefs->flood_channel_block_max_hops);
|
||||
sprintf(reply, "> %s %s", _prefs->flood_channel_data_enabled ? "on" : "off", hops);
|
||||
} else if (memcmp(config, "flood.channel.block.hops", 24) == 0) {
|
||||
char hops[8];
|
||||
formatFloodChannelBlockHops(hops, _prefs->flood_channel_block_max_hops);
|
||||
sprintf(reply, "> %s", hops);
|
||||
} else if (memcmp(config, "flood.channel.block", 19) == 0
|
||||
&& (config[19] == 0 || config[19] == ' ' || config[19] == '.')) {
|
||||
const char* cursor = &config[19];
|
||||
|
||||
@@ -87,6 +87,8 @@
|
||||
#ifndef FLOOD_CHANNEL_BLOCK_PREFIX_LEN
|
||||
#define FLOOD_CHANNEL_BLOCK_PREFIX_LEN 4
|
||||
#endif
|
||||
#define FLOOD_CHANNEL_BLOCK_HOPS_ALL 0xFF
|
||||
#define FLOOD_CHANNEL_BLOCK_HOPS_INHERIT 0xFE
|
||||
|
||||
#define DIRECT_RETRY_CR4_MIN_SNR_X4_DEFAULT 40
|
||||
#define DIRECT_RETRY_CR5_MIN_SNR_X4_DEFAULT 30
|
||||
@@ -167,6 +169,7 @@ struct NodePrefs { // persisted to file
|
||||
uint8_t battery_alert_critical_percent;
|
||||
uint8_t direct_retry_recent_enabled;
|
||||
uint8_t flood_channel_data_enabled;
|
||||
uint8_t flood_channel_block_max_hops;
|
||||
};
|
||||
|
||||
class CommonCLICallbacks {
|
||||
@@ -228,11 +231,12 @@ public:
|
||||
strcpy(reply, "Error: unsupported");
|
||||
}
|
||||
virtual void setFloodChannelBlock(int index, const uint8_t* secret, uint8_t key_len,
|
||||
const char* name, char* reply) {
|
||||
const char* name, uint8_t max_hops, char* reply) {
|
||||
(void)index;
|
||||
(void)secret;
|
||||
(void)key_len;
|
||||
(void)name;
|
||||
(void)max_hops;
|
||||
strcpy(reply, "Error: unsupported");
|
||||
}
|
||||
virtual void formatFloodChannelBlocks(const char* selector, char* reply) {
|
||||
|
||||
Reference in New Issue
Block a user