From 3af25495bb4e2eaf17de4a916ce9355e62e71f31 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Sat, 3 Jan 2026 12:02:15 +1100 Subject: [PATCH 1/9] * Repeater: new anon request sub-type: ANON_REQ_TYPE_REGIONS (rate limited to max 4 every 3 mins) * Companion: new CMD_SEND_ANON_REQ command (reply with existing RESP_CODE_SENT frame) --- examples/companion_radio/MyMesh.cpp | 22 ++++++++++++++++++ examples/simple_repeater/MyMesh.cpp | 22 +++++++++++++++--- examples/simple_repeater/MyMesh.h | 3 ++- src/helpers/BaseChatMesh.cpp | 25 ++++++++++++++++++++ src/helpers/BaseChatMesh.h | 1 + src/helpers/RegionMap.cpp | 36 ++++++++++++++++++++++++----- src/helpers/RegionMap.h | 7 ++++-- 7 files changed, 104 insertions(+), 12 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 7689708c..59a0078f 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -53,6 +53,7 @@ #define CMD_SET_FLOOD_SCOPE 54 // v8+ #define CMD_SEND_CONTROL_DATA 55 // v8+ #define CMD_GET_STATS 56 // v8+, second byte is stats type +#define CMD_SEND_ANON_REQ 57 // Stats sub-types for CMD_GET_STATS #define STATS_TYPE_CORE 0 @@ -1286,6 +1287,27 @@ void MyMesh::handleCmdFrame(size_t len) { } else { writeErrFrame(ERR_CODE_NOT_FOUND); // contact not found } + } else if (cmd_frame[0] == CMD_SEND_ANON_REQ && len > 1 + PUB_KEY_SIZE) { + uint8_t *pub_key = &cmd_frame[1]; + ContactInfo *recipient = lookupContactByPubKey(pub_key, PUB_KEY_SIZE); + uint8_t *data = &cmd_frame[1 + PUB_KEY_SIZE]; + if (recipient) { + uint32_t tag, est_timeout; + int result = sendAnonReq(*recipient, data, len - (1 + PUB_KEY_SIZE), tag, est_timeout); + if (result == MSG_SEND_FAILED) { + writeErrFrame(ERR_CODE_TABLE_FULL); + } else { + clearPendingReqs(); + pending_req = tag; // match this to onContactResponse() + out_frame[0] = RESP_CODE_SENT; + out_frame[1] = (result == MSG_SEND_SENT_FLOOD) ? 1 : 0; + memcpy(&out_frame[2], &tag, 4); + memcpy(&out_frame[6], &est_timeout, 4); + _serial->writeFrame(out_frame, 10); + } + } else { + writeErrFrame(ERR_CODE_NOT_FOUND); // contact not found + } } else if (cmd_frame[0] == CMD_SEND_STATUS_REQ && len >= 1 + PUB_KEY_SIZE) { uint8_t *pub_key = &cmd_frame[1]; ContactInfo *recipient = lookupContactByPubKey(pub_key, PUB_KEY_SIZE); diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 33e32a68..253fa701 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -51,6 +51,8 @@ #define RESP_SERVER_LOGIN_OK 0 // response to ANON_REQ +#define ANON_REQ_TYPE_REGIONS 0x01 + #define CLI_REPLY_DELAY_MILLIS 600 #define LAZY_CONTACTS_WRITE_DELAY 5000 @@ -139,6 +141,19 @@ uint8_t MyMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secr return 13; // reply length } +uint8_t MyMesh::handleAnonRegionsReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data) { + // REVISIT: should there be params like 'since' in request data[] ? + if (regions_limiter.allow(rtc_clock.getCurrentTime())) { + memcpy(reply_data, &sender_timestamp, 4); // prefix with sender_timestamp, like a tag + + uint32_t now = getRTCClock()->getCurrentTime(); + memcpy(&reply_data[4], &now, 4); // include our clock (for easy clock sync, if this is a trusted node) + + return 8 + region_map.exportNamesTo((char *) &reply_data[8], sizeof(reply_data) - 12, REGION_DENY_FLOOD); // reply length + } + return 0; +} + int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t *payload, size_t payload_len) { // uint32_t now = getRTCClock()->getCurrentTimeUnique(); // memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp @@ -450,8 +465,8 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m uint8_t reply_len; if (data[4] == 0 || data[4] >= ' ') { // is password, ie. a login request reply_len = handleLoginReq(sender, secret, timestamp, &data[4], packet->isRouteFlood()); - //} else if (data[4] == ANON_REQ_TYPE_*) { // future type codes - // TODO + } else if (data[4] == ANON_REQ_TYPE_REGIONS) { + reply_len = handleAnonRegionsReq(sender, timestamp, &data[5]); } else { reply_len = 0; // unknown request type } @@ -668,7 +683,8 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc mesh::RTCClock &rtc, mesh::MeshTables &tables) : mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables), _cli(board, rtc, sensors, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4), region_map(key_store), temp_map(key_store), - discover_limiter(4, 120) // max 4 every 2 minutes + discover_limiter(4, 120), // max 4 every 2 minutes + regions_limiter(4, 180) // max 4 every 3 minutes #if defined(WITH_RS232_BRIDGE) , bridge(&_prefs, WITH_RS232_BRIDGE, _mgr, &rtc) #endif diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 343aa44f..172c756b 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -93,7 +93,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { RegionMap region_map, temp_map; RegionEntry* load_stack[8]; RegionEntry* recv_pkt_region; - RateLimiter discover_limiter; + RateLimiter discover_limiter, regions_limiter; bool region_load_active; unsigned long dirty_contacts_expiry; #if MAX_NEIGHBOURS @@ -114,6 +114,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { void putNeighbour(const mesh::Identity& id, uint32_t timestamp, float snr); uint8_t handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data, bool is_flood); + uint8_t handleAnonRegionsReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data); int handleRequest(ClientInfo* sender, uint32_t sender_timestamp, uint8_t* payload, size_t payload_len); mesh::Packet* createSelfAdvert(); diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index 597444fa..08185628 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -477,6 +477,31 @@ int BaseChatMesh::sendLogin(const ContactInfo& recipient, const char* password, return MSG_SEND_FAILED; } +int BaseChatMesh::sendAnonReq(const ContactInfo& recipient, const uint8_t* data, uint8_t len, uint32_t& tag, uint32_t& est_timeout) { + mesh::Packet* pkt; + { + uint8_t temp[MAX_PACKET_PAYLOAD]; + tag = getRTCClock()->getCurrentTimeUnique(); + memcpy(temp, &tag, 4); // tag to match later (also extra blob to help make packet_hash unique) + memcpy(&temp[4], data, len); + + pkt = createAnonDatagram(PAYLOAD_TYPE_ANON_REQ, self_id, recipient.id, recipient.getSharedSecret(self_id), temp, 4 + len); + } + if (pkt) { + uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength()); + if (recipient.out_path_len < 0) { + sendFloodScoped(recipient, pkt); + est_timeout = calcFloodTimeoutMillisFor(t); + return MSG_SEND_SENT_FLOOD; + } else { + sendDirect(pkt, recipient.out_path, recipient.out_path_len); + est_timeout = calcDirectTimeoutMillisFor(t, recipient.out_path_len); + return MSG_SEND_SENT_DIRECT; + } + } + return MSG_SEND_FAILED; +} + int BaseChatMesh::sendRequest(const ContactInfo& recipient, const uint8_t* req_data, uint8_t data_len, uint32_t& tag, uint32_t& est_timeout) { if (data_len > MAX_PACKET_PAYLOAD - 16) return MSG_SEND_FAILED; diff --git a/src/helpers/BaseChatMesh.h b/src/helpers/BaseChatMesh.h index 76b0dd1c..40818fed 100644 --- a/src/helpers/BaseChatMesh.h +++ b/src/helpers/BaseChatMesh.h @@ -141,6 +141,7 @@ public: int sendCommandData(const ContactInfo& recipient, uint32_t timestamp, uint8_t attempt, const char* text, uint32_t& est_timeout); bool sendGroupMessage(uint32_t timestamp, mesh::GroupChannel& channel, const char* sender_name, const char* text, int text_len); int sendLogin(const ContactInfo& recipient, const char* password, uint32_t& est_timeout); + int sendAnonReq(const ContactInfo& recipient, const uint8_t* data, uint8_t len, uint32_t& tag, uint32_t& est_timeout); int sendRequest(const ContactInfo& recipient, uint8_t req_type, uint32_t& tag, uint32_t& est_timeout); int sendRequest(const ContactInfo& recipient, const uint8_t* req_data, uint8_t data_len, uint32_t& tag, uint32_t& est_timeout); bool shareContactZeroHop(const ContactInfo& contact); diff --git a/src/helpers/RegionMap.cpp b/src/helpers/RegionMap.cpp index 36844615..e227532a 100644 --- a/src/helpers/RegionMap.cpp +++ b/src/helpers/RegionMap.cpp @@ -24,12 +24,12 @@ static File openWrite(FILESYSTEM* _fs, const char* filename) { #endif } -bool RegionMap::load(FILESYSTEM* _fs) { - if (_fs->exists("/regions2")) { +bool RegionMap::load(FILESYSTEM* _fs, const char* path) { + if (_fs->exists(path ? path : "/regions2")) { #if defined(RP2040_PLATFORM) - File file = _fs->open("/regions2", "r"); + File file = _fs->open(path ? path : "/regions2", "r"); #else - File file = _fs->open("/regions2"); + File file = _fs->open(path ? path : "/regions2"); #endif if (file) { @@ -67,8 +67,8 @@ bool RegionMap::load(FILESYSTEM* _fs) { return false; // failed } -bool RegionMap::save(FILESYSTEM* _fs) { - File file = openWrite(_fs, "/regions2"); +bool RegionMap::save(FILESYSTEM* _fs, const char* path) { + File file = openWrite(_fs, path ? path : "/regions2"); if (file) { uint8_t pad[128]; memset(pad, 0, sizeof(pad)); @@ -235,3 +235,27 @@ void RegionMap::printChildRegions(int indent, const RegionEntry* parent, Stream& void RegionMap::exportTo(Stream& out) const { printChildRegions(0, &wildcard, out); // recursive } + +int RegionMap::exportNamesTo(char *dest, int max_len, uint8_t mask) { + char *dp = dest; + if ((wildcard.flags & mask) == 0) { + *dp++ = '*'; + *dp++ = ','; + } + + for (int i = 0; i < num_regions; i++) { + auto region = ®ions[i]; + if ((region->flags & mask) == 0) { // region allowed? (per 'mask' param) + int len = strlen(region->name); + if ((dp - dest) + len + 2 < max_len) { // only append if name will fit + memcpy(dp, region->name, len); + dp += len; + *dp++ = ','; + } + } + } + if (dp > dest) { dp--; } // don't include trailing comma + + *dp = 0; // set null terminator + return dp - dest; // return length +} diff --git a/src/helpers/RegionMap.h b/src/helpers/RegionMap.h index 50513be1..dcd9a774 100644 --- a/src/helpers/RegionMap.h +++ b/src/helpers/RegionMap.h @@ -32,8 +32,8 @@ public: static bool is_name_char(char c); - bool load(FILESYSTEM* _fs); - bool save(FILESYSTEM* _fs); + bool load(FILESYSTEM* _fs, const char* path=NULL); + bool save(FILESYSTEM* _fs, const char* path=NULL); RegionEntry* putRegion(const char* name, uint16_t parent_id, uint16_t id = 0); RegionEntry* findMatch(mesh::Packet* packet, uint8_t mask); @@ -47,6 +47,9 @@ public: bool clear(); void resetFrom(const RegionMap& src) { num_regions = 0; next_id = src.next_id; } int getCount() const { return num_regions; } + const RegionEntry* getByIdx(int i) const { return ®ions[i]; } + const RegionEntry* getRoot() const { return &wildcard; } + int exportNamesTo(char *dest, int max_len, uint8_t mask); void exportTo(Stream& out) const; }; From 5cc44dd802b07a4f4180e8ba694fabcb7de96174 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Thu, 8 Jan 2026 13:20:52 +1100 Subject: [PATCH 2/9] * ANON_REQ_TYPE_REGIONS now direct only, with reply_path encoded in request --- examples/simple_repeater/MyMesh.cpp | 18 ++++++++++++++---- examples/simple_repeater/MyMesh.h | 2 ++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 253fa701..261184c5 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -142,8 +142,13 @@ uint8_t MyMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secr } uint8_t MyMesh::handleAnonRegionsReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data) { - // REVISIT: should there be params like 'since' in request data[] ? if (regions_limiter.allow(rtc_clock.getCurrentTime())) { + // request data has: {reply-path-len}{reply-path} + reply_path_len = *data++ & 0x3F; + memcpy(reply_path, data, reply_path_len); + // data += reply_path_len; + // other params?? + memcpy(reply_data, &sender_timestamp, 4); // prefix with sender_timestamp, like a tag uint32_t now = getRTCClock()->getCurrentTime(); @@ -463,12 +468,14 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m data[len] = 0; // ensure null terminator uint8_t reply_len; + + reply_path_len = -1; if (data[4] == 0 || data[4] >= ' ') { // is password, ie. a login request reply_len = handleLoginReq(sender, secret, timestamp, &data[4], packet->isRouteFlood()); - } else if (data[4] == ANON_REQ_TYPE_REGIONS) { + } else if (data[4] == ANON_REQ_TYPE_REGIONS && packet->isRouteDirect()) { reply_len = handleAnonRegionsReq(sender, timestamp, &data[5]); } else { - reply_len = 0; // unknown request type + reply_len = 0; // unknown/invalid request type } if (reply_len == 0) return; // invalid request @@ -478,9 +485,12 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m mesh::Packet* path = createPathReturn(sender, secret, packet->path, packet->path_len, PAYLOAD_TYPE_RESPONSE, reply_data, reply_len); if (path) sendFlood(path, SERVER_RESPONSE_DELAY); - } else { + } else if (reply_path_len < 0) { mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, secret, reply_data, reply_len); if (reply) sendFlood(reply, SERVER_RESPONSE_DELAY); + } else { + mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, secret, reply_data, reply_len); + if (reply) sendDirect(reply, reply_path, reply_path_len, SERVER_RESPONSE_DELAY); } } } diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 172c756b..49952770 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -88,6 +88,8 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { NodePrefs _prefs; CommonCLI _cli; uint8_t reply_data[MAX_PACKET_PAYLOAD]; + uint8_t reply_path[MAX_PATH_SIZE]; + int8_t reply_path_len; ClientACL acl; TransportKeyStore key_store; RegionMap region_map, temp_map; From 5475043083756b4bed07da6e6dec683f5d5f99a8 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Fri, 9 Jan 2026 11:07:31 +1100 Subject: [PATCH 3/9] * new ANON_REQ_TYPE_VER_OWNER * CommonCLI: new "get/set owner.info ..." --- examples/simple_repeater/MyMesh.cpp | 27 +++++++++++++++++++++++++-- examples/simple_repeater/MyMesh.h | 3 ++- src/helpers/CommonCLI.cpp | 25 +++++++++++++++++++++++-- src/helpers/CommonCLI.h | 1 + 4 files changed, 51 insertions(+), 5 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 261184c5..eb561939 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -52,6 +52,7 @@ #define RESP_SERVER_LOGIN_OK 0 // response to ANON_REQ #define ANON_REQ_TYPE_REGIONS 0x01 +#define ANON_REQ_TYPE_VER_OWNER 0x02 #define CLI_REPLY_DELAY_MILLIS 600 @@ -142,7 +143,7 @@ uint8_t MyMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secr } uint8_t MyMesh::handleAnonRegionsReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data) { - if (regions_limiter.allow(rtc_clock.getCurrentTime())) { + if (anon_limiter.allow(rtc_clock.getCurrentTime())) { // request data has: {reply-path-len}{reply-path} reply_path_len = *data++ & 0x3F; memcpy(reply_path, data, reply_path_len); @@ -159,6 +160,26 @@ uint8_t MyMesh::handleAnonRegionsReq(const mesh::Identity& sender, uint32_t send return 0; } +uint8_t MyMesh::handleAnonVerOwnerReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data) { + if (anon_limiter.allow(rtc_clock.getCurrentTime())) { + // request data has: {reply-path-len}{reply-path} + reply_path_len = *data++ & 0x3F; + memcpy(reply_path, data, reply_path_len); + // data += reply_path_len; + // other params?? + + memcpy(reply_data, &sender_timestamp, 4); // prefix with sender_timestamp, like a tag + + uint32_t now = getRTCClock()->getCurrentTime(); + memcpy(&reply_data[4], &now, 4); // include our clock (for easy clock sync, if this is a trusted node) + + sprintf((char *) &reply_data[8], "%s,%s", FIRMWARE_VERSION, _prefs.owner_info); + + return 8 + strlen((char *) &reply_data[8]); // reply length + } + return 0; +} + int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t *payload, size_t payload_len) { // uint32_t now = getRTCClock()->getCurrentTimeUnique(); // memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp @@ -474,6 +495,8 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m reply_len = handleLoginReq(sender, secret, timestamp, &data[4], packet->isRouteFlood()); } else if (data[4] == ANON_REQ_TYPE_REGIONS && packet->isRouteDirect()) { reply_len = handleAnonRegionsReq(sender, timestamp, &data[5]); + } else if (data[4] == ANON_REQ_TYPE_VER_OWNER && packet->isRouteDirect()) { + reply_len = handleAnonVerOwnerReq(sender, timestamp, &data[5]); } else { reply_len = 0; // unknown/invalid request type } @@ -694,7 +717,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc : mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables), _cli(board, rtc, sensors, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4), region_map(key_store), temp_map(key_store), discover_limiter(4, 120), // max 4 every 2 minutes - regions_limiter(4, 180) // max 4 every 3 minutes + anon_limiter(4, 180) // max 4 every 3 minutes #if defined(WITH_RS232_BRIDGE) , bridge(&_prefs, WITH_RS232_BRIDGE, _mgr, &rtc) #endif diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 49952770..f63aee24 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -95,7 +95,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { RegionMap region_map, temp_map; RegionEntry* load_stack[8]; RegionEntry* recv_pkt_region; - RateLimiter discover_limiter, regions_limiter; + RateLimiter discover_limiter, anon_limiter; bool region_load_active; unsigned long dirty_contacts_expiry; #if MAX_NEIGHBOURS @@ -117,6 +117,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { void putNeighbour(const mesh::Identity& id, uint32_t timestamp, float snr); uint8_t handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data, bool is_flood); uint8_t handleAnonRegionsReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data); + uint8_t handleAnonVerOwnerReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data); int handleRequest(ClientInfo* sender, uint32_t sender_timestamp, uint8_t* payload, size_t payload_len); mesh::Packet* createSelfAdvert(); diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 78e1b5e0..14b67b39 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -72,7 +72,8 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { file.read((uint8_t *)&_prefs->advert_loc_policy, sizeof (_prefs->advert_loc_policy)); // 161 file.read((uint8_t *)&_prefs->discovery_mod_timestamp, sizeof(_prefs->discovery_mod_timestamp)); // 162 file.read((uint8_t *)&_prefs->adc_multiplier, sizeof(_prefs->adc_multiplier)); // 166 - // 170 + file.read((uint8_t *)_prefs->owner_info, sizeof(_prefs->owner_info)); // 170 + // 290 // sanitise bad pref values _prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0, 20.0f); @@ -155,7 +156,8 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) { file.write((uint8_t *)&_prefs->advert_loc_policy, sizeof(_prefs->advert_loc_policy)); // 161 file.write((uint8_t *)&_prefs->discovery_mod_timestamp, sizeof(_prefs->discovery_mod_timestamp)); // 162 file.write((uint8_t *)&_prefs->adc_multiplier, sizeof(_prefs->adc_multiplier)); // 166 - // 170 + file.write((uint8_t *)_prefs->owner_info, sizeof(_prefs->owner_info)); // 170 + // 290 file.close(); } @@ -301,6 +303,15 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch sprintf(reply, "> %d", (uint32_t)_prefs->flood_max); } else if (memcmp(config, "direct.txdelay", 14) == 0) { sprintf(reply, "> %s", StrHelper::ftoa(_prefs->direct_tx_delay_factor)); + } else if (memcmp(config, "owner.info", 10) == 0) { + *reply++ = '>'; + *reply++ = ' '; + const char* sp = _prefs->owner_info; + while (*sp) { + *reply++ = (*sp == '\n') ? '|' : *sp; // translate newline back to orig '|' + sp++; + } + *reply = 0; // set null terminator } else if (memcmp(config, "tx", 2) == 0 && (config[2] == 0 || config[2] == ' ')) { sprintf(reply, "> %d", (uint32_t) _prefs->tx_power_dbm); } else if (memcmp(config, "freq", 4) == 0) { @@ -479,6 +490,16 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch } else { strcpy(reply, "Error, cannot be negative"); } + } else if (memcmp(config, "owner.info ", 11) == 0) { + config += 11; + char *dp = _prefs->owner_info; + while (*config && dp - _prefs->owner_info < sizeof(_prefs->owner_info)-1) { + *dp++ = (*config == '|') ? '\n' : *config; // translate '|' to newline chars + config++; + } + *dp = 0; + savePrefs(); + strcpy(reply, "OK"); } else if (memcmp(config, "tx ", 3) == 0) { _prefs->tx_power_dbm = atoi(&config[3]); savePrefs(); diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index 642a4cce..3b1d05f9 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -50,6 +50,7 @@ struct NodePrefs { // persisted to file uint8_t advert_loc_policy; uint32_t discovery_mod_timestamp; float adc_multiplier; + char owner_info[120]; }; class CommonCLICallbacks { From 2a035ad8167f03901ff064d637b7ae714c57bcd2 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Fri, 9 Jan 2026 13:20:20 +1100 Subject: [PATCH 4/9] * ANON_REQ_TYPE_VER_OWNER, now includes node_name --- examples/simple_repeater/MyMesh.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index eb561939..d86e97bc 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -173,7 +173,7 @@ uint8_t MyMesh::handleAnonVerOwnerReq(const mesh::Identity& sender, uint32_t sen uint32_t now = getRTCClock()->getCurrentTime(); memcpy(&reply_data[4], &now, 4); // include our clock (for easy clock sync, if this is a trusted node) - sprintf((char *) &reply_data[8], "%s,%s", FIRMWARE_VERSION, _prefs.owner_info); + sprintf((char *) &reply_data[8], "%s,%s,%s", FIRMWARE_VERSION, _prefs.node_name, _prefs.owner_info); return 8 + strlen((char *) &reply_data[8]); // reply length } From fd69acb4212fef2ad955860de4a9955e309ce4d2 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Fri, 9 Jan 2026 13:54:18 +1100 Subject: [PATCH 5/9] * new ANON_REQ_TYPE_VER (for just simple clock + ver info) --- examples/simple_repeater/MyMesh.cpp | 29 ++++++++++++++++++++++------- examples/simple_repeater/MyMesh.h | 1 + 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index d86e97bc..38f81ff9 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -53,6 +53,7 @@ #define ANON_REQ_TYPE_REGIONS 0x01 #define ANON_REQ_TYPE_VER_OWNER 0x02 +#define ANON_REQ_TYPE_VER 0x03 #define CLI_REPLY_DELAY_MILLIS 600 @@ -148,12 +149,10 @@ uint8_t MyMesh::handleAnonRegionsReq(const mesh::Identity& sender, uint32_t send reply_path_len = *data++ & 0x3F; memcpy(reply_path, data, reply_path_len); // data += reply_path_len; - // other params?? memcpy(reply_data, &sender_timestamp, 4); // prefix with sender_timestamp, like a tag - uint32_t now = getRTCClock()->getCurrentTime(); - memcpy(&reply_data[4], &now, 4); // include our clock (for easy clock sync, if this is a trusted node) + memcpy(&reply_data[4], &now, 4); // include our clock (for easy clock sync, and packet hash uniqueness) return 8 + region_map.exportNamesTo((char *) &reply_data[8], sizeof(reply_data) - 12, REGION_DENY_FLOOD); // reply length } @@ -166,13 +165,10 @@ uint8_t MyMesh::handleAnonVerOwnerReq(const mesh::Identity& sender, uint32_t sen reply_path_len = *data++ & 0x3F; memcpy(reply_path, data, reply_path_len); // data += reply_path_len; - // other params?? memcpy(reply_data, &sender_timestamp, 4); // prefix with sender_timestamp, like a tag - uint32_t now = getRTCClock()->getCurrentTime(); - memcpy(&reply_data[4], &now, 4); // include our clock (for easy clock sync, if this is a trusted node) - + memcpy(&reply_data[4], &now, 4); // include our clock (for easy clock sync, and packet hash uniqueness) sprintf((char *) &reply_data[8], "%s,%s,%s", FIRMWARE_VERSION, _prefs.node_name, _prefs.owner_info); return 8 + strlen((char *) &reply_data[8]); // reply length @@ -180,6 +176,23 @@ uint8_t MyMesh::handleAnonVerOwnerReq(const mesh::Identity& sender, uint32_t sen return 0; } +uint8_t MyMesh::handleAnonVerReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data) { + if (anon_limiter.allow(rtc_clock.getCurrentTime())) { + // request data has: {reply-path-len}{reply-path} + reply_path_len = *data++ & 0x3F; + memcpy(reply_path, data, reply_path_len); + // data += reply_path_len; + + memcpy(reply_data, &sender_timestamp, 4); // prefix with sender_timestamp, like a tag + uint32_t now = getRTCClock()->getCurrentTime(); + memcpy(&reply_data[4], &now, 4); // include our clock (for easy clock sync, and packet hash uniqueness) + strcpy((char *) &reply_data[8], FIRMWARE_VERSION); + + return 8 + strlen((char *) &reply_data[8]); // reply length + } + return 0; +} + int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t *payload, size_t payload_len) { // uint32_t now = getRTCClock()->getCurrentTimeUnique(); // memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp @@ -497,6 +510,8 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m reply_len = handleAnonRegionsReq(sender, timestamp, &data[5]); } else if (data[4] == ANON_REQ_TYPE_VER_OWNER && packet->isRouteDirect()) { reply_len = handleAnonVerOwnerReq(sender, timestamp, &data[5]); + } else if (data[4] == ANON_REQ_TYPE_VER && packet->isRouteDirect()) { + reply_len = handleAnonVerReq(sender, timestamp, &data[5]); } else { reply_len = 0; // unknown/invalid request type } diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index f63aee24..96a51da8 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -118,6 +118,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { uint8_t handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data, bool is_flood); uint8_t handleAnonRegionsReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data); uint8_t handleAnonVerOwnerReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data); + uint8_t handleAnonVerReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data); int handleRequest(ClientInfo* sender, uint32_t sender_timestamp, uint8_t* payload, size_t payload_len); mesh::Packet* createSelfAdvert(); From 65796c8f2043dd2a3bc4327dfe091304ccbe6945 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Fri, 9 Jan 2026 16:28:08 +1100 Subject: [PATCH 6/9] * CommonCLI: added "set name ..." validation * ANON_REQ_TYPE_VER_OWNER, now removes commas from node_name --- examples/simple_repeater/MyMesh.cpp | 13 ++++++++++++- src/helpers/CommonCLI.cpp | 18 +++++++++++++++--- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 38f81ff9..c50d860f 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -159,6 +159,14 @@ uint8_t MyMesh::handleAnonRegionsReq(const mesh::Identity& sender, uint32_t send return 0; } +static void sanitiseName(char* dest, const char* src) { + while (*src) { + *dest++ = (*src == ',') ? ' ' : *src; + src++; + } + *dest = 0; +} + uint8_t MyMesh::handleAnonVerOwnerReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data) { if (anon_limiter.allow(rtc_clock.getCurrentTime())) { // request data has: {reply-path-len}{reply-path} @@ -166,10 +174,13 @@ uint8_t MyMesh::handleAnonVerOwnerReq(const mesh::Identity& sender, uint32_t sen memcpy(reply_path, data, reply_path_len); // data += reply_path_len; + char tmp[sizeof(_prefs.node_name)]; + sanitiseName(tmp, _prefs.node_name); + memcpy(reply_data, &sender_timestamp, 4); // prefix with sender_timestamp, like a tag uint32_t now = getRTCClock()->getCurrentTime(); memcpy(&reply_data[4], &now, 4); // include our clock (for easy clock sync, and packet hash uniqueness) - sprintf((char *) &reply_data[8], "%s,%s,%s", FIRMWARE_VERSION, _prefs.node_name, _prefs.owner_info); + sprintf((char *) &reply_data[8], "%s,%s,%s", FIRMWARE_VERSION, tmp, _prefs.owner_info); return 8 + strlen((char *) &reply_data[8]); // reply length } diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 14b67b39..2fc93006 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -14,6 +14,14 @@ static uint32_t _atoi(const char* sp) { return n; } +static bool isValidName(const char *n) { + while (*n) { + if (*n == '[' || *n == ']' || *n == '/' || *n == '\\' || *n == ':' || *n == ',' || *n == '?' || *n == '*') return false; + n++; + } + return true; +} + void CommonCLI::loadPrefs(FILESYSTEM* fs) { if (fs->exists("/com_prefs")) { loadPrefsInt(fs, "/com_prefs"); // new filename @@ -421,9 +429,13 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch strcpy(reply, "Error, invalid key"); } } else if (memcmp(config, "name ", 5) == 0) { - StrHelper::strncpy(_prefs->node_name, &config[5], sizeof(_prefs->node_name)); - savePrefs(); - strcpy(reply, "OK"); + if (isValidName(&config[5])) { + StrHelper::strncpy(_prefs->node_name, &config[5], sizeof(_prefs->node_name)); + savePrefs(); + strcpy(reply, "OK"); + } else { + strcpy(reply, "Error, bad chars"); + } } else if (memcmp(config, "repeat ", 7) == 0) { _prefs->disable_fwd = memcmp(&config[7], "off", 3) == 0; savePrefs(); From 4e4f6d92a0fced540cded8fd7866a4fe4e1b2557 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Fri, 9 Jan 2026 16:32:08 +1100 Subject: [PATCH 7/9] * ANON_REQ_TYPE_VER_OWNER now delimited by newline chars --- examples/simple_repeater/MyMesh.cpp | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index c50d860f..d504fec9 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -159,14 +159,6 @@ uint8_t MyMesh::handleAnonRegionsReq(const mesh::Identity& sender, uint32_t send return 0; } -static void sanitiseName(char* dest, const char* src) { - while (*src) { - *dest++ = (*src == ',') ? ' ' : *src; - src++; - } - *dest = 0; -} - uint8_t MyMesh::handleAnonVerOwnerReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data) { if (anon_limiter.allow(rtc_clock.getCurrentTime())) { // request data has: {reply-path-len}{reply-path} @@ -174,13 +166,10 @@ uint8_t MyMesh::handleAnonVerOwnerReq(const mesh::Identity& sender, uint32_t sen memcpy(reply_path, data, reply_path_len); // data += reply_path_len; - char tmp[sizeof(_prefs.node_name)]; - sanitiseName(tmp, _prefs.node_name); - memcpy(reply_data, &sender_timestamp, 4); // prefix with sender_timestamp, like a tag uint32_t now = getRTCClock()->getCurrentTime(); memcpy(&reply_data[4], &now, 4); // include our clock (for easy clock sync, and packet hash uniqueness) - sprintf((char *) &reply_data[8], "%s,%s,%s", FIRMWARE_VERSION, tmp, _prefs.owner_info); + sprintf((char *) &reply_data[8], "%s\n%s\n%s", FIRMWARE_VERSION, _prefs.node_name, _prefs.owner_info); return 8 + strlen((char *) &reply_data[8]); // reply length } From b6110eee38b2c7b80b36f54186095430e975bc83 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Mon, 12 Jan 2026 16:58:35 +1100 Subject: [PATCH 8/9] * new req/resp (after login): REQ_TYPE_GET_OWNER_INFO (includes firmware-ver) * ANON_REQ_TYPE_OWNER, firmware-ver removed (security exploit) * ANON_REQ_TYPE_BASIC, formware-ver removed, just remote clock + some 'feature' bits * CTL_TYPE_NODE_DISCOVER_REQ now ingored if 'repeat off' has been set --- examples/simple_repeater/MyMesh.cpp | 39 +++++++++++++++++++---------- examples/simple_repeater/MyMesh.h | 4 +-- src/helpers/RegionMap.cpp | 5 ++-- src/helpers/RegionMap.h | 2 +- 4 files changed, 32 insertions(+), 18 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index d504fec9..54f3ef79 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -48,12 +48,13 @@ #define REQ_TYPE_GET_TELEMETRY_DATA 0x03 #define REQ_TYPE_GET_ACCESS_LIST 0x05 #define REQ_TYPE_GET_NEIGHBOURS 0x06 +#define REQ_TYPE_GET_OWNER_INFO 0x07 #define RESP_SERVER_LOGIN_OK 0 // response to ANON_REQ #define ANON_REQ_TYPE_REGIONS 0x01 -#define ANON_REQ_TYPE_VER_OWNER 0x02 -#define ANON_REQ_TYPE_VER 0x03 +#define ANON_REQ_TYPE_OWNER 0x02 +#define ANON_REQ_TYPE_BASIC 0x03 // just remote clock #define CLI_REPLY_DELAY_MILLIS 600 @@ -159,7 +160,7 @@ uint8_t MyMesh::handleAnonRegionsReq(const mesh::Identity& sender, uint32_t send return 0; } -uint8_t MyMesh::handleAnonVerOwnerReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data) { +uint8_t MyMesh::handleAnonOwnerReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data) { if (anon_limiter.allow(rtc_clock.getCurrentTime())) { // request data has: {reply-path-len}{reply-path} reply_path_len = *data++ & 0x3F; @@ -169,14 +170,14 @@ uint8_t MyMesh::handleAnonVerOwnerReq(const mesh::Identity& sender, uint32_t sen memcpy(reply_data, &sender_timestamp, 4); // prefix with sender_timestamp, like a tag uint32_t now = getRTCClock()->getCurrentTime(); memcpy(&reply_data[4], &now, 4); // include our clock (for easy clock sync, and packet hash uniqueness) - sprintf((char *) &reply_data[8], "%s\n%s\n%s", FIRMWARE_VERSION, _prefs.node_name, _prefs.owner_info); + sprintf((char *) &reply_data[8], "%s\n%s", _prefs.node_name, _prefs.owner_info); return 8 + strlen((char *) &reply_data[8]); // reply length } return 0; } -uint8_t MyMesh::handleAnonVerReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data) { +uint8_t MyMesh::handleAnonClockReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data) { if (anon_limiter.allow(rtc_clock.getCurrentTime())) { // request data has: {reply-path-len}{reply-path} reply_path_len = *data++ & 0x3F; @@ -186,9 +187,16 @@ uint8_t MyMesh::handleAnonVerReq(const mesh::Identity& sender, uint32_t sender_t memcpy(reply_data, &sender_timestamp, 4); // prefix with sender_timestamp, like a tag uint32_t now = getRTCClock()->getCurrentTime(); memcpy(&reply_data[4], &now, 4); // include our clock (for easy clock sync, and packet hash uniqueness) - strcpy((char *) &reply_data[8], FIRMWARE_VERSION); - - return 8 + strlen((char *) &reply_data[8]); // reply length + reply_data[8] = 0; // features +#ifdef WITH_RS232_BRIDGE + reply_data[8] |= 0x01; // is bridge, type UART +#elif WITH_ESPNOW_BRIDGE + reply_data[8] |= 0x03; // is bridge, type ESP-NOW +#endif + if (_prefs.disable_fwd) { // is this repeater currently disabled + reply_data[8] |= 0x80; // is disabled + } + return 9; // reply length } return 0; } @@ -350,6 +358,9 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t return reply_offset; } + } else if (payload[0] == REQ_TYPE_GET_OWNER_INFO) { + sprintf((char *) &reply_data[4], "%s\n%s", FIRMWARE_VERSION, _prefs.owner_info); + return 4 + strlen((char *) &reply_data[4]); } return 0; // unknown command } @@ -508,10 +519,10 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m reply_len = handleLoginReq(sender, secret, timestamp, &data[4], packet->isRouteFlood()); } else if (data[4] == ANON_REQ_TYPE_REGIONS && packet->isRouteDirect()) { reply_len = handleAnonRegionsReq(sender, timestamp, &data[5]); - } else if (data[4] == ANON_REQ_TYPE_VER_OWNER && packet->isRouteDirect()) { - reply_len = handleAnonVerOwnerReq(sender, timestamp, &data[5]); - } else if (data[4] == ANON_REQ_TYPE_VER && packet->isRouteDirect()) { - reply_len = handleAnonVerReq(sender, timestamp, &data[5]); + } else if (data[4] == ANON_REQ_TYPE_OWNER && packet->isRouteDirect()) { + reply_len = handleAnonOwnerReq(sender, timestamp, &data[5]); + } else if (data[4] == ANON_REQ_TYPE_BASIC && packet->isRouteDirect()) { + reply_len = handleAnonClockReq(sender, timestamp, &data[5]); } else { reply_len = 0; // unknown/invalid request type } @@ -700,7 +711,9 @@ bool MyMesh::onPeerPathRecv(mesh::Packet *packet, int sender_idx, const uint8_t void MyMesh::onControlDataRecv(mesh::Packet* packet) { uint8_t type = packet->payload[0] & 0xF0; // just test upper 4 bits - if (type == CTL_TYPE_NODE_DISCOVER_REQ && packet->payload_len >= 6 && discover_limiter.allow(rtc_clock.getCurrentTime())) { + if (type == CTL_TYPE_NODE_DISCOVER_REQ && packet->payload_len >= 6 + && !_prefs.disable_fwd && discover_limiter.allow(rtc_clock.getCurrentTime()) + ) { int i = 1; uint8_t filter = packet->payload[i++]; uint32_t tag; diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 96a51da8..f930ee7e 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -117,8 +117,8 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { void putNeighbour(const mesh::Identity& id, uint32_t timestamp, float snr); uint8_t handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data, bool is_flood); uint8_t handleAnonRegionsReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data); - uint8_t handleAnonVerOwnerReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data); - uint8_t handleAnonVerReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data); + uint8_t handleAnonOwnerReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data); + uint8_t handleAnonClockReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data); int handleRequest(ClientInfo* sender, uint32_t sender_timestamp, uint8_t* payload, size_t payload_len); mesh::Packet* createSelfAdvert(); diff --git a/src/helpers/RegionMap.cpp b/src/helpers/RegionMap.cpp index e227532a..fbc5f017 100644 --- a/src/helpers/RegionMap.cpp +++ b/src/helpers/RegionMap.cpp @@ -9,8 +9,9 @@ RegionMap::RegionMap(TransportKeyStore& store) : _store(&store) { strcpy(wildcard.name, "*"); } -bool RegionMap::is_name_char(char c) { - return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '.' || c == '_' || c == '#'; +bool RegionMap::is_name_char(uint8_t c) { + // accept all alpha-num or accented characters, but exclude most punctuation chars + return c == '-' || c == '#' || (c >= '0' && c <= '9') || c >= 'A'; } static File openWrite(FILESYSTEM* _fs, const char* filename) { diff --git a/src/helpers/RegionMap.h b/src/helpers/RegionMap.h index dcd9a774..01174d09 100644 --- a/src/helpers/RegionMap.h +++ b/src/helpers/RegionMap.h @@ -30,7 +30,7 @@ class RegionMap { public: RegionMap(TransportKeyStore& store); - static bool is_name_char(char c); + static bool is_name_char(uint8_t c); bool load(FILESYSTEM* _fs, const char* path=NULL); bool save(FILESYSTEM* _fs, const char* path=NULL); From 69a71d0e25b956e70d33ada889ff0580d6dbb060 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Mon, 12 Jan 2026 17:47:51 +1100 Subject: [PATCH 9/9] * repeater login response, FIRMWARE_VER_LEVEL now bumped to 2 --- docs/payloads.md | 53 ++++++++++++++++++++++++++++- examples/simple_repeater/MyMesh.cpp | 7 ++-- 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/docs/payloads.md b/docs/payloads.md index 5a41e69c..4742bfbb 100644 --- a/docs/payloads.md +++ b/docs/payloads.md @@ -103,7 +103,9 @@ Request type | `0x02` | keepalive | (deprecated) | | `0x03` | get telemetry data | TODO | | `0x04` | get min,max,avg data | sensor nodes - get min, max, average for given time span | -| `0x05` | get access list | get node's approved access list | +| `0x05` | get access list | get node's approved access list | +| `0x06` | get neighbors | get repeater node's neighbors | +| `0x07` | get owner info | get repeater firmware-ver/name/owner info | ### Get stats @@ -132,6 +134,27 @@ Gets information about the node, possibly including the following: Request data about sensors on the node, including battery level. +### Get Telemetry + +TODO + +### Get Min/Max/Ave (Sensor nodes) + +TODO + +### Get Access List + +TODO + +### Get Neighors + +TODO + +### Get Owner Info + +TODO + + ## Response | Field | Size (bytes) | Description | @@ -179,6 +202,34 @@ txt_type | timestamp | 4 | sender time (unix timestamp) | | password | rest of message | password for repeater/sensor | +## Repeater - Regions request + +| Field | Size (bytes) | Description | +|----------------|-----------------|-------------------------------------------------------------------------------| +| timestamp | 4 | sender time (unix timestamp) | +| req type | 1 | 0x01 (request sub type) | +| reply path len | 1 | path len for reply | +| reply path | (variable) | reply path | + +## Repeater - Owner info request + +| Field | Size (bytes) | Description | +|----------------|-----------------|-------------------------------------------------------------------------------| +| timestamp | 4 | sender time (unix timestamp) | +| req type | 1 | 0x02 (request sub type) | +| reply path len | 1 | path len for reply | +| reply path | (variable) | reply path | + +## Repeater - Clock and status request + +| Field | Size (bytes) | Description | +|----------------|-----------------|-------------------------------------------------------------------------------| +| timestamp | 4 | sender time (unix timestamp) | +| req type | 1 | 0x03 (request sub type) | +| reply path len | 1 | path len for reply | +| reply path | (variable) | reply path | + + # Group text message / datagram | Field | Size (bytes) | Description | diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 54f3ef79..d926148d 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -41,14 +41,14 @@ #define TXT_ACK_DELAY 200 #endif -#define FIRMWARE_VER_LEVEL 1 +#define FIRMWARE_VER_LEVEL 2 #define REQ_TYPE_GET_STATUS 0x01 // same as _GET_STATS #define REQ_TYPE_KEEP_ALIVE 0x02 #define REQ_TYPE_GET_TELEMETRY_DATA 0x03 #define REQ_TYPE_GET_ACCESS_LIST 0x05 #define REQ_TYPE_GET_NEIGHBOURS 0x06 -#define REQ_TYPE_GET_OWNER_INFO 0x07 +#define REQ_TYPE_GET_OWNER_INFO 0x07 // FIRMWARE_VER_LEVEL >= 2 #define RESP_SERVER_LOGIN_OK 0 // response to ANON_REQ @@ -196,6 +196,7 @@ uint8_t MyMesh::handleAnonClockReq(const mesh::Identity& sender, uint32_t sender if (_prefs.disable_fwd) { // is this repeater currently disabled reply_data[8] |= 0x80; // is disabled } + // TODO: add some kind of moving-window utilisation metric, so can query 'how busy' is this repeater return 9; // reply length } return 0; @@ -359,7 +360,7 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t return reply_offset; } } else if (payload[0] == REQ_TYPE_GET_OWNER_INFO) { - sprintf((char *) &reply_data[4], "%s\n%s", FIRMWARE_VERSION, _prefs.owner_info); + sprintf((char *) &reply_data[4], "%s\n%s\n%s", FIRMWARE_VERSION, _prefs.node_name, _prefs.owner_info); return 4 + strlen((char *) &reply_data[4]); } return 0; // unknown command