diff --git a/docs/cli_commands.md b/docs/cli_commands.md index 920c0fd7..c838fd10 100644 --- a/docs/cli_commands.md +++ b/docs/cli_commands.md @@ -134,6 +134,7 @@ This document provides an overview of CLI commands that can be sent to MeshCore - If a full failure has no row yet, it first seeds the row at the active retry cutoff + `2.5 dB`, then applies the `0.25 dB` penalty. - Serial CLI page size is fixed at `128` rows; choose page with `get recent.repeater `. - Over LoRa remote CLI, page size is fixed at `7` rows; choose page with `get recent.repeater `. +- Repeaters can use adjacent entries in this table to short-circuit non-TRACE direct packets when this node appears later in the direct path. --- @@ -801,6 +802,8 @@ This document provides an overview of CLI commands that can be sent to MeshCore **Default:** `2` for `rooftop`, `1` for `infra` and `mobile` +**Note:** Prefixes in `flood.retry.ignore` do not count toward this path length. + --- #### View or change whether advert packets are flood-retried @@ -900,6 +903,30 @@ This document provides an overview of CLI commands that can be sent to MeshCore --- +#### View or set direct path overrides for the current remote client +**Usage:** +- `get outpath` +- `set outpath ` +- `set outpath clear` +- `set outpath flood` +- `get altpath` +- `set altpath ` +- `set altpath clear` + +**Parameters:** +- `hopN_hex`: Hop hash, `2`, `4`, or `6` hex characters. All hops must use the same width. + +**Notes:** +- These commands require remote client context; they target the caller's ACL entry. +- The path hash size is inferred from the hop hash width. +- `outpath` overrides the primary direct route used for replies to the caller. +- `clear` forgets the current direct path and allows normal path discovery to repopulate it. +- `flood` forces replies to use flood packets until the client logs in again. +- `altpath` is an optional second direct route used for duplicate response attempts. +- `set altpath clear` removes the duplicate route so only one reply is sent. + +--- + #### View or change this room server's 'read-only' flag **Usage:** - `get allow.read.only` diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index d8e72cde..5071eb8c 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -529,6 +529,64 @@ void MyMesh::sendFloodReply(mesh::Packet* packet, unsigned long delay_millis, ui } } +static bool pathsEqual(const uint8_t* a, uint8_t a_len, const uint8_t* b, uint8_t b_len) { + if (a == NULL || b == NULL || a_len != b_len || !mesh::Packet::isValidPathLen(a_len)) return false; + + uint8_t hash_count = a_len & 63; + uint8_t hash_size = (a_len >> 6) + 1; + return memcmp(a, b, hash_count * hash_size) == 0; +} + +static bool hasUsablePath(const uint8_t* path, uint8_t path_len) { + return path != NULL && mesh::Packet::isValidPathLen(path_len) && (path_len & 63) > 0; +} + +mesh::Packet* MyMesh::createPacketCopy(const mesh::Packet* packet, const char* caller) { + if (packet == NULL) return NULL; + + mesh::Packet* copy = obtainNewPacket(); + if (copy == NULL) { + MESH_DEBUG_PRINTLN("%s %s: error, packet pool empty", getLogDateTime(), caller); + return NULL; + } + *copy = *packet; + return copy; +} + +mesh::Packet* MyMesh::createAltPathCopy(const mesh::Packet* packet, + const uint8_t* primary_path, uint8_t primary_path_len, + const uint8_t* alt_path, uint8_t alt_path_len) { + if (!hasUsablePath(alt_path, alt_path_len)) return NULL; + if (hasUsablePath(primary_path, primary_path_len) && pathsEqual(primary_path, primary_path_len, alt_path, alt_path_len)) { + return NULL; + } + return createPacketCopy(packet, "MyMesh::createAltPathCopy()"); +} + +void MyMesh::sendFloodReplyWithAltPath(mesh::Packet* packet, + const uint8_t* direct_path, uint8_t direct_path_len, + const uint8_t* alt_path, uint8_t alt_path_len, + unsigned long delay_millis, uint8_t path_hash_size) { + mesh::Packet* direct = hasUsablePath(direct_path, direct_path_len) + ? createPacketCopy(packet, "MyMesh::sendFloodReplyWithAltPath(direct)") + : NULL; + mesh::Packet* alt = createAltPathCopy(packet, direct_path, direct_path_len, alt_path, alt_path_len); + + sendFloodReply(packet, delay_millis, path_hash_size); + if (direct) sendDirect(direct, direct_path, direct_path_len, delay_millis); + if (alt) sendDirect(alt, alt_path, alt_path_len, delay_millis); +} + +void MyMesh::sendDirectWithAltPath(mesh::Packet* packet, + const uint8_t* path, uint8_t path_len, + const uint8_t* alt_path, uint8_t alt_path_len, + uint32_t delay_millis) { + mesh::Packet* alt = createAltPathCopy(packet, path, path_len, alt_path, alt_path_len); + + sendDirect(packet, path, path_len, delay_millis); + if (alt) sendDirect(alt, alt_path, alt_path_len, delay_millis); +} + bool MyMesh::allowPacketForward(const mesh::Packet *packet) { if (_prefs.disable_fwd) return false; @@ -849,6 +907,54 @@ bool MyMesh::allowDirectRetry(const mesh::Packet* packet, const uint8_t* next_ho return true; } +bool MyMesh::maybeShortCircuitDirect(mesh::Packet* packet) { + if (packet == NULL || !packet->isRouteDirect() || packet->getPayloadType() == PAYLOAD_TYPE_TRACE) { + return false; + } + + uint8_t hash_count = packet->getPathHashCount(); + uint8_t hash_size = packet->getPathHashSize(); + if (hash_count < 2 || hash_size == 0 || hash_size > MAX_ROUTE_HASH_BYTES) { + return false; + } + + // Normal direct forwarding handles the first path entry. Short-circuit only when we are later in the route. + int self_idx = -1; + for (uint8_t i = 1; i < hash_count; i++) { + if (self_id.isHashMatch(&packet->path[i * hash_size], hash_size)) { + self_idx = i; + break; + } + } + if (self_idx < 0) { + return false; + } + + const SimpleMeshTables* tables = (const SimpleMeshTables*)getTables(); + bool adjacent_recent = false; + if (self_idx > 0 + && tables->findRecentRepeaterByHash(&packet->path[(self_idx - 1) * hash_size], hash_size) != NULL) { + adjacent_recent = true; + } + if (!adjacent_recent && self_idx + 1 < hash_count + && tables->findRecentRepeaterByHash(&packet->path[(self_idx + 1) * hash_size], hash_size) != NULL) { + adjacent_recent = true; + } + if (!adjacent_recent) { + return false; + } + + uint8_t remaining = hash_count - (uint8_t)self_idx; + memmove(packet->path, &packet->path[self_idx * hash_size], remaining * hash_size); + packet->setPathHashCount(remaining); + + MESH_DEBUG_PRINTLN("%s direct short-circuit (type=%d, original_hop=%d, remaining_hops=%d)", + getLogDateTime(), + (uint32_t)packet->getPayloadType(), + self_idx + 1, + (uint32_t)remaining); + return true; +} uint8_t MyMesh::getDirectRetryCodingRateForSNR(int8_t snr_x4) const { if (_prefs.direct_retry_cr4_snr_x4 == 0 && _prefs.direct_retry_cr5_snr_x4 == 0 @@ -992,6 +1098,31 @@ bool MyMesh::floodRetryPrefixIgnored(const uint8_t* prefix, uint8_t prefix_len) } return false; } +uint8_t MyMesh::floodRetryEffectivePathLength(const mesh::Packet* packet, uint8_t max_hops) const { + if (packet == NULL || !packet->isRouteFlood() || packet->getPathHashCount() == 0) { + return 0; + } + + uint8_t hash_size = packet->getPathHashSize(); + if (hash_size == 0 || hash_size > MAX_ROUTE_HASH_BYTES) { + return packet->getPathHashCount(); + } + + uint8_t hop_count = packet->getPathHashCount(); + if (max_hops < hop_count) { + hop_count = max_hops; + } + + uint8_t effective_len = 0; + const uint8_t* path = packet->path; + for (uint8_t hop = 0; hop < hop_count; hop++) { + if (!floodRetryPrefixIgnored(path, hash_size)) { + effective_len++; + } + path += hash_size; + } + return effective_len; +} bool MyMesh::floodRetryPrefixFresh(const uint8_t* prefix, uint8_t prefix_len) const { const auto* recent = ((const SimpleMeshTables *)getTables())->findRecentRepeaterByHash(prefix, prefix_len); if (recent == NULL || recent->last_heard_millis == 0) { @@ -1413,7 +1544,15 @@ uint8_t MyMesh::getFloodRetryMaxPathLength(const mesh::Packet* packet) const { if (gate == FLOOD_RETRY_PATH_GATE_DISABLED) { return FLOOD_RETRY_PATH_GATE_DISABLED; } - return gate <= 63 ? gate : 2; + if (gate > 63) { + gate = 2; + } + + 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 MyMesh::getFloodRetryMaxAttempts(const mesh::Packet* packet) const { if (_prefs.disable_fwd) { @@ -1447,7 +1586,7 @@ bool MyMesh::isFloodRetryEchoTarget(const mesh::Packet* packet, uint8_t progress if (hasFloodRetryPrefixes()) { return floodRetryLastHopMatches(packet); } - if (packet->getPathHashCount() <= progress_marker) { + if (floodRetryEffectivePathLength(packet) <= floodRetryEffectivePathLength(packet, progress_marker)) { return false; } return true; @@ -1607,13 +1746,22 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx, // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response mesh::Packet *path = createPathReturn(client->id, secret, packet->path, packet->path_len, PAYLOAD_TYPE_RESPONSE, reply_data, reply_len); - if (path) sendFloodReply(path, SERVER_RESPONSE_DELAY, packet->getPathHashSize()); + if (path) { + if (hasUsablePath(client->out_path, client->out_path_len)) { + sendFloodReplyWithAltPath(path, client->out_path, client->out_path_len, + client->alt_path, client->alt_path_len, + SERVER_RESPONSE_DELAY, packet->getPathHashSize()); + } else { + sendFloodReply(path, SERVER_RESPONSE_DELAY, packet->getPathHashSize()); + } + } } else { mesh::Packet *reply = createDatagram(PAYLOAD_TYPE_RESPONSE, client->id, secret, reply_data, reply_len); if (reply) { - if (client->out_path_len != OUT_PATH_UNKNOWN) { // we have an out_path, so send DIRECT - sendDirect(reply, client->out_path, client->out_path_len, SERVER_RESPONSE_DELAY); + if (hasUsablePath(client->out_path, client->out_path_len)) { // we have an out_path, so send DIRECT + sendDirectWithAltPath(reply, client->out_path, client->out_path_len, + client->alt_path, client->alt_path_len, SERVER_RESPONSE_DELAY); } else { sendFloodReply(reply, SERVER_RESPONSE_DELAY, packet->getPathHashSize()); } @@ -1645,10 +1793,11 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx, mesh::Packet *ack = createAck(ack_hash); if (ack) { - if (client->out_path_len == OUT_PATH_UNKNOWN) { - sendFloodReply(ack, TXT_ACK_DELAY, packet->getPathHashSize()); + if (hasUsablePath(client->out_path, client->out_path_len)) { + sendDirectWithAltPath(ack, client->out_path, client->out_path_len, + client->alt_path, client->alt_path_len, TXT_ACK_DELAY); } else { - sendDirect(ack, client->out_path, client->out_path_len, TXT_ACK_DELAY); + sendFloodReply(ack, TXT_ACK_DELAY, packet->getPathHashSize()); } } } @@ -1659,7 +1808,7 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx, if (is_retry) { *reply = 0; } else { - handleCommand(sender_timestamp, command, reply); + handleCommand(sender_timestamp, client, command, reply); } int text_len = strlen(reply); if (text_len > 0) { @@ -1673,10 +1822,11 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx, auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, client->id, secret, temp, 5 + text_len); if (reply) { - if (client->out_path_len == OUT_PATH_UNKNOWN) { - sendFloodReply(reply, CLI_REPLY_DELAY_MILLIS, packet->getPathHashSize()); + if (hasUsablePath(client->out_path, client->out_path_len)) { + sendDirectWithAltPath(reply, client->out_path, client->out_path_len, + client->alt_path, client->alt_path_len, CLI_REPLY_DELAY_MILLIS); } else { - sendDirect(reply, client->out_path, client->out_path_len, CLI_REPLY_DELAY_MILLIS); + sendFloodReply(reply, CLI_REPLY_DELAY_MILLIS, packet->getPathHashSize()); } } } @@ -1696,7 +1846,9 @@ bool MyMesh::onPeerPathRecv(mesh::Packet *packet, int sender_idx, const uint8_t auto client = acl.getClientByIdx(i); // store a copy of path, for sendDirect() - client->out_path_len = mesh::Packet::copyPath(client->out_path, path, path_len); + if (client->out_path_len != OUT_PATH_FORCE_FLOOD) { + client->out_path_len = mesh::Packet::copyPath(client->out_path, path, path_len); + } client->last_activity = getRTCClock()->getCurrentTime(); } else { MESH_DEBUG_PRINTLN("onPeerPathRecv: invalid peer idx: %d", i); @@ -2127,7 +2279,100 @@ void MyMesh::clearStats() { ((SimpleMeshTables *)getTables())->resetStats(); } -void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply) { +static char* trimSpaces(char* s) { + while (*s == ' ') s++; + char* end = s + strlen(s); + while (end > s && end[-1] == ' ') end--; + *end = 0; + return s; +} + +static bool parsePathCommand(char* raw, uint8_t* out_path, uint8_t& out_path_len, const char*& err) { + if (raw == NULL || out_path == NULL) { + err = "Err - bad params"; + return false; + } + + char* spec = trimSpaces(raw); + if (*spec == 0) { + err = "Err - missing path"; + return false; + } + if (strcmp(spec, "clear") == 0 || strcmp(spec, "-") == 0 || strcmp(spec, "none") == 0) { + out_path_len = OUT_PATH_UNKNOWN; + return true; + } + if (strcmp(spec, "flood") == 0) { + out_path_len = OUT_PATH_FORCE_FLOOD; + return true; + } + + uint8_t hash_size = 0; + uint8_t hop_count = 0; + char* token = spec; + while (token && *token) { + char* comma = strchr(token, ','); + if (comma) *comma = 0; + token = trimSpaces(token); + + int hex_len = strlen(token); + if (!(hex_len == 2 || hex_len == 4 || hex_len == 6)) { + err = "Err - each hop must be 1/2/3 bytes hex"; + return false; + } + + uint8_t hop_hash_size = (uint8_t)(hex_len / 2); + if (hash_size == 0) { + hash_size = hop_hash_size; + } else if (hash_size != hop_hash_size) { + err = "Err - mixed hash sizes in path"; + return false; + } + + if (hop_count >= 63 || (hop_count + 1) * hash_size > MAX_PATH_SIZE) { + err = "Err - path too long"; + return false; + } + if (!mesh::Utils::fromHex(&out_path[hop_count * hash_size], hash_size, token)) { + err = "Err - bad hex"; + return false; + } + + hop_count++; + token = comma ? comma + 1 : NULL; + } + + if (hash_size == 0 || hop_count == 0) { + err = "Err - missing path"; + return false; + } + out_path_len = ((hash_size - 1) << 6) | (hop_count & 63); + return true; +} + +static void formatPathReply(const uint8_t* path, uint8_t path_len, char* out, size_t out_len) { + if (path_len == OUT_PATH_FORCE_FLOOD) { + snprintf(out, out_len, "> flood"); + return; + } + if (path_len == OUT_PATH_UNKNOWN) { + snprintf(out, out_len, "> unknown"); + return; + } + if (!mesh::Packet::isValidPathLen(path_len)) { + snprintf(out, out_len, "> invalid"); + return; + } + + uint8_t hash_size = (path_len >> 6) + 1; + uint8_t hop_count = path_len & 63; + uint8_t byte_len = hop_count * hash_size; + char hex[(MAX_PATH_SIZE * 2) + 1]; + mesh::Utils::toHex(hex, path, byte_len); + snprintf(out, out_len, "> hs=%u hops=%u hex=%s", (uint32_t)hash_size, (uint32_t)hop_count, hex); +} + +void MyMesh::handleCommand(uint32_t sender_timestamp, ClientInfo* sender, char *command, char *reply) { if (region_load_active) { if (StrHelper::isBlank(command)) { // empty/blank line, signal to terminate 'load' operation region_map = temp_map; // copy over the temp instance as new current map @@ -2204,6 +2449,53 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply Serial.printf("\n"); } reply[0] = 0; + } else if (strcmp(command, "get outpath") == 0 + || strcmp(command, "set outpath") == 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 = strstr(command, "altpath") != NULL; + if (sender == NULL) { + strcpy(reply, "Err - command needs remote client context"); + } else if (is_get) { + formatPathReply(is_alt ? sender->alt_path : sender->out_path, + is_alt ? sender->alt_path_len : sender->out_path_len, + reply, 160); + } else { + char* spec = command + 11; // length of "set outpath"/"set altpath" + if (*spec == ' ') spec++; + + uint8_t path[MAX_PATH_SIZE]; + uint8_t path_len = OUT_PATH_UNKNOWN; + const char* err = NULL; + if (!parsePathCommand(spec, path, path_len, err)) { + strcpy(reply, err ? err : "Err - invalid path"); + } else if (is_alt && path_len == OUT_PATH_FORCE_FLOOD) { + strcpy(reply, "Err - bad params"); + } else { + if (is_alt) { + if (path_len == OUT_PATH_UNKNOWN) { + memset(sender->alt_path, 0, sizeof(sender->alt_path)); + sender->alt_path_len = OUT_PATH_UNKNOWN; + } else { + sender->alt_path_len = mesh::Packet::copyPath(sender->alt_path, path, path_len); + } + } 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; + } else { + sender->out_path_len = mesh::Packet::copyPath(sender->out_path, path, path_len); + } + } + dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); + formatPathReply(is_alt ? sender->alt_path : sender->out_path, + is_alt ? sender->alt_path_len : sender->out_path_len, + reply, 160); + } + } } else if (strncmp(command, "get recent.repeater", 19) == 0 || strncmp(command, "set recent.repeater", 19) == 0 || strncmp(command, "clear recent.repeater", 21) == 0 diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 73f80556..0e6c5f70 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -141,6 +141,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { bool floodRetryPrefixMatches(const mesh::Packet* packet) const; bool floodRetryLastHopMatches(const mesh::Packet* packet) const; 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, bool include_other) const; @@ -184,6 +185,7 @@ protected: uint32_t getDirectRetransmitDelay(const mesh::Packet* packet) override; uint8_t getDefaultTxCodingRate() const override { return active_cr; } bool allowDirectRetry(const mesh::Packet* packet, const uint8_t* next_hop_hash, uint8_t next_hop_hash_len) const override; + bool maybeShortCircuitDirect(mesh::Packet* packet) override; void configureDirectRetryPacket(mesh::Packet* retry, const mesh::Packet* original, uint8_t retry_attempt) override; uint32_t getDirectRetryEchoDelay(const mesh::Packet* packet) const override; uint8_t getDirectRetryMaxAttempts(const mesh::Packet* packet) const override; @@ -223,6 +225,18 @@ protected: void onControlDataRecv(mesh::Packet* packet) override; void sendFloodReply(mesh::Packet* packet, unsigned long delay_millis, uint8_t path_hash_size); + mesh::Packet* createPacketCopy(const mesh::Packet* packet, const char* caller); + mesh::Packet* createAltPathCopy(const mesh::Packet* packet, + const uint8_t* primary_path, uint8_t primary_path_len, + const uint8_t* alt_path, uint8_t alt_path_len); + void sendFloodReplyWithAltPath(mesh::Packet* packet, + const uint8_t* direct_path, uint8_t direct_path_len, + const uint8_t* alt_path, uint8_t alt_path_len, + unsigned long delay_millis, uint8_t path_hash_size); + void sendDirectWithAltPath(mesh::Packet* packet, + const uint8_t* path, uint8_t path_len, + const uint8_t* alt_path, uint8_t alt_path_len, + uint32_t delay_millis); public: MyMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::MeshTables& tables); @@ -272,7 +286,10 @@ public: void saveIdentity(const mesh::LocalIdentity& new_id) override; void clearStats() override; - void handleCommand(uint32_t sender_timestamp, char* command, char* reply); + void handleCommand(uint32_t sender_timestamp, ClientInfo* sender, char* command, char* reply); + void handleCommand(uint32_t sender_timestamp, char* command, char* reply) { + handleCommand(sender_timestamp, NULL, command, reply); + } void loop(); #if defined(WITH_BRIDGE) diff --git a/src/Mesh.cpp b/src/Mesh.cpp index 941c71a1..8b088b31 100644 --- a/src/Mesh.cpp +++ b/src/Mesh.cpp @@ -254,7 +254,7 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { } } - if (self_id.isHashMatch(pkt->path, pkt->getPathHashSize()) && allowPacketForward(pkt)) { + if ((self_id.isHashMatch(pkt->path, pkt->getPathHashSize()) || maybeShortCircuitDirect(pkt)) && allowPacketForward(pkt)) { if (pkt->getPayloadType() == PAYLOAD_TYPE_MULTIPART) { return forwardMultipartDirect(pkt); } else if (pkt->getPayloadType() == PAYLOAD_TYPE_ACK) { diff --git a/src/Mesh.h b/src/Mesh.h index 3f86653e..483d4826 100644 --- a/src/Mesh.h +++ b/src/Mesh.h @@ -135,6 +135,11 @@ protected: */ virtual bool allowDirectRetry(const Packet* packet, const uint8_t* next_hop_hash, uint8_t next_hop_hash_len) const; + /** + * \brief Allow subclasses to rewrite a non-TRACE DIRECT packet path when this node can safely skip ahead. + */ + virtual bool maybeShortCircuitDirect(Packet* packet) { return false; } + /** * \returns milliseconds to wait for the next-hop echo before queueing one retry of the DIRECT packet. */ diff --git a/src/helpers/ClientACL.cpp b/src/helpers/ClientACL.cpp index 12823827..1d880823 100644 --- a/src/helpers/ClientACL.cpp +++ b/src/helpers/ClientACL.cpp @@ -1,5 +1,7 @@ #include "ClientACL.h" +static const uint8_t CONTACT_RECORD_VERSION_ALT_PATH = 1; + static File openWrite(FILESYSTEM* _fs, const char* filename) { #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) _fs->remove(filename); @@ -28,6 +30,7 @@ void ClientACL::load(FILESYSTEM* fs, const mesh::LocalIdentity& self_id) { uint8_t unused[2]; memset(&c, 0, sizeof(c)); + c.alt_path_len = OUT_PATH_UNKNOWN; bool success = (file.read(pub_key, 32) == 32); success = success && (file.read((uint8_t *) &c.permissions, 1) == 1); @@ -36,6 +39,10 @@ void ClientACL::load(FILESYSTEM* fs, const mesh::LocalIdentity& self_id) { success = success && (file.read((uint8_t *)&c.out_path_len, 1) == 1); success = success && (file.read(c.out_path, 64) == 64); success = success && (file.read(c.shared_secret, PUB_KEY_SIZE) == PUB_KEY_SIZE); // will be recalculated below + if (success && unused[0] >= CONTACT_RECORD_VERSION_ALT_PATH) { + success = success && (file.read((uint8_t *)&c.alt_path_len, 1) == 1); + success = success && (file.read(c.alt_path, 64) == 64); + } if (!success) break; // EOF @@ -57,7 +64,8 @@ void ClientACL::save(FILESYSTEM* fs, bool (*filter)(ClientInfo*)) { File file = openWrite(_fs, "/s_contacts"); if (file) { uint8_t unused[2]; - memset(unused, 0, sizeof(unused)); + unused[0] = CONTACT_RECORD_VERSION_ALT_PATH; + unused[1] = 0; for (int i = 0; i < num_clients; i++) { auto c = &clients[i]; @@ -70,6 +78,8 @@ void ClientACL::save(FILESYSTEM* fs, bool (*filter)(ClientInfo*)) { success = success && (file.write((uint8_t *)&c->out_path_len, 1) == 1); success = success && (file.write(c->out_path, 64) == 64); success = success && (file.write(c->shared_secret, PUB_KEY_SIZE) == PUB_KEY_SIZE); + success = success && (file.write((uint8_t *)&c->alt_path_len, 1) == 1); + success = success && (file.write(c->alt_path, 64) == 64); if (!success) break; // write failed } @@ -115,6 +125,7 @@ ClientInfo* ClientACL::putClient(const mesh::Identity& id, uint8_t init_perms) { c->permissions = init_perms; c->id = id; c->out_path_len = OUT_PATH_UNKNOWN; + c->alt_path_len = OUT_PATH_UNKNOWN; return c; } diff --git a/src/helpers/ClientACL.h b/src/helpers/ClientACL.h index b758f706..356574de 100644 --- a/src/helpers/ClientACL.h +++ b/src/helpers/ClientACL.h @@ -10,13 +10,16 @@ #define PERM_ACL_READ_WRITE 2 #define PERM_ACL_ADMIN 3 -#define OUT_PATH_UNKNOWN 0xFF +#define OUT_PATH_FORCE_FLOOD 0xFE +#define OUT_PATH_UNKNOWN 0xFF struct ClientInfo { mesh::Identity id; uint8_t permissions; uint8_t out_path_len; uint8_t out_path[MAX_PATH_SIZE]; + uint8_t alt_path_len; + uint8_t alt_path[MAX_PATH_SIZE]; uint8_t shared_secret[PUB_KEY_SIZE]; uint32_t last_timestamp; // by THEIR clock (transient) uint32_t last_activity; // by OUR clock (transient)