diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 5fb1a729f..01821f9e9 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -882,8 +882,8 @@ void MyMesh::formatPacketStatsReply(char *reply) { getNumRecvFlood(), getNumRecvDirect()); } -void MyMesh::saveIdentity(const mesh::LocalIdentity &new_id) { - self_id = new_id; +void MyMesh::saveIdentity(const mesh::LocalIdentity &new_id, bool apply_now) { + if (apply_now) self_id = new_id; #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) IdentityStore store(*_fs, ""); #elif defined(ESP32) @@ -893,7 +893,7 @@ void MyMesh::saveIdentity(const mesh::LocalIdentity &new_id) { #else #error "need to define saveIdentity()" #endif - store.save("_main", self_id); + store.save("_main", new_id); } void MyMesh::clearStats() { diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index ed9f0c5fc..9b0b2ff1f 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -201,7 +201,14 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { mesh::LocalIdentity& getSelfId() override { return self_id; } - void saveIdentity(const mesh::LocalIdentity& new_id) override; + void saveIdentity(const mesh::LocalIdentity& new_id, bool apply_now = true) override; + mesh::LocalIdentity generateNewIdentity() override { return radio_new_identity(); } + bool hasNeighborWithHash(uint8_t hash) override { + for (int i = 0; i < MAX_NEIGHBOURS; i++) { + if (neighbours[i].heard_timestamp > 0 && neighbours[i].id.pub_key[0] == hash) return true; + } + return false; + } void clearStats() override; void handleCommand(uint32_t sender_timestamp, char* command, char* reply); void loop(); diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index 60dd18407..a00382fbd 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -719,8 +719,8 @@ void MyMesh::setTxPower(uint8_t power_dbm) { radio_set_tx_power(power_dbm); } -void MyMesh::saveIdentity(const mesh::LocalIdentity &new_id) { - self_id = new_id; +void MyMesh::saveIdentity(const mesh::LocalIdentity &new_id, bool apply_now) { + if (apply_now) self_id = new_id; #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) IdentityStore store(*_fs, ""); #elif defined(ESP32) @@ -730,7 +730,7 @@ void MyMesh::saveIdentity(const mesh::LocalIdentity &new_id) { #else #error "need to define saveIdentity()" #endif - store.save("_main", self_id); + store.save("_main", new_id); } void MyMesh::clearStats() { diff --git a/examples/simple_room_server/MyMesh.h b/examples/simple_room_server/MyMesh.h index e7f1fee83..70bc414f0 100644 --- a/examples/simple_room_server/MyMesh.h +++ b/examples/simple_room_server/MyMesh.h @@ -201,7 +201,8 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { static bool saveFilter(ClientInfo* client); - void saveIdentity(const mesh::LocalIdentity& new_id) override; + void saveIdentity(const mesh::LocalIdentity& new_id, bool apply_now = true) override; + mesh::LocalIdentity generateNewIdentity() override { return radio_new_identity(); } void clearStats() override; void handleCommand(uint32_t sender_timestamp, char* command, char* reply); void loop(); diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index 4995c55fc..8430dc63b 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -764,8 +764,8 @@ bool SensorMesh::formatFileSystem() { #endif } -void SensorMesh::saveIdentity(const mesh::LocalIdentity& new_id) { - self_id = new_id; +void SensorMesh::saveIdentity(const mesh::LocalIdentity& new_id, bool apply_now) { + if (apply_now) self_id = new_id; #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) IdentityStore store(*_fs, ""); #elif defined(ESP32) @@ -775,7 +775,7 @@ void SensorMesh::saveIdentity(const mesh::LocalIdentity& new_id) { #else #error "need to define saveIdentity()" #endif - store.save("_main", self_id); + store.save("_main", new_id); } void SensorMesh::applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) { diff --git a/examples/simple_sensor/SensorMesh.h b/examples/simple_sensor/SensorMesh.h index c320eb447..e82dd75df 100644 --- a/examples/simple_sensor/SensorMesh.h +++ b/examples/simple_sensor/SensorMesh.h @@ -74,7 +74,8 @@ class SensorMesh : public mesh::Mesh, public CommonCLICallbacks { void formatRadioStatsReply(char *reply) override; void formatPacketStatsReply(char *reply) override; mesh::LocalIdentity& getSelfId() override { return self_id; } - void saveIdentity(const mesh::LocalIdentity& new_id) override; + void saveIdentity(const mesh::LocalIdentity& new_id, bool apply_now = true) override; + mesh::LocalIdentity generateNewIdentity() override { return radio_new_identity(); } void clearStats() override { } void applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) override; diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index a3de990aa..b3983b6f8 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -4,6 +4,64 @@ #include "AdvertDataHelpers.h" #include +// Platform-specific fast RNG includes +#if defined(ESP32) + #include +#elif defined(NRF52_PLATFORM) + #include +#elif defined(STM32_PLATFORM) + extern RNG_HandleTypeDef hrng; +#elif defined(RP2040_PLATFORM) + #include +#endif + +// Fast hardware RNG +class FastRNG : public mesh::RNG { +public: + void random(uint8_t* dest, size_t sz) override { +#if defined(ESP32) + esp_fill_random(dest, sz); +#elif defined(NRF52_PLATFORM) + uint8_t avail = 0; + while (sz > 0) { + sd_rand_application_bytes_available_get(&avail); + if (avail > 0) { + uint8_t chunk = (avail < sz) ? avail : sz; + sd_rand_application_vector_get(dest, chunk); + dest += chunk; + sz -= chunk; + } + } +#elif defined(STM32_PLATFORM) + while (sz >= 4) { + uint32_t r; + HAL_RNG_GenerateRandomNumber(&hrng, &r); + memcpy(dest, &r, 4); + dest += 4; + sz -= 4; + } + if (sz > 0) { + uint32_t r; + HAL_RNG_GenerateRandomNumber(&hrng, &r); + memcpy(dest, &r, sz); + } +#elif defined(RP2040_PLATFORM) + while (sz > 0) { + uint32_t r = get_rand_32(); + uint8_t chunk = (sz < 4) ? sz : 4; + memcpy(dest, &r, chunk); + dest += chunk; + sz -= chunk; + } +#else + //Fallback + for (size_t i = 0; i < sz; i++) { + dest[i] = ::random(256); + } +#endif + } +}; + // Believe it or not, this std C function is busted on some platforms! static uint32_t _atoi(const char* sp) { uint32_t n = 0; @@ -556,6 +614,77 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch sprintf(reply, "%s (Build: %s)", _callbacks->getFirmwareVer(), _callbacks->getBuildDate()); } else if (memcmp(command, "board", 5) == 0) { sprintf(reply, "%s", _board->getManufacturerName()); + } else if (memcmp(command, "regen key", 9) == 0 && (command[9] == 0 || command[9] == ' ')) { + // Parse optional target hash: "regen key" or "regen key XX" + uint8_t target_hash = 0; + bool has_target = false; + if (command[9] == ' ' && command[10] != 0) { + // Validate hex characters before parsing + if (!mesh::Utils::isHexChar(command[10]) || !mesh::Utils::isHexChar(command[11])) { + strcpy(reply, "Error: invalid hex (use 0-9, A-F)"); + return; + } + uint8_t parsed[1]; + if (mesh::Utils::fromHex(parsed, 1, &command[10])) { + target_hash = parsed[0]; + has_target = true; + if (target_hash == 0x00 || target_hash == 0xFF) { + strcpy(reply, "Error: 00 and FF are reserved hashes"); + return; + } + } else { + strcpy(reply, "Error: invalid hash (use 2 hex chars, e.g. A3)"); + return; + } + } + + Serial.println("Generating key, please wait..."); + mesh::LocalIdentity new_id; + int attempts = 0; + int max_attempts = has_target ? 1000 : 50; + bool found = false; + +#if !defined(RP2040_PLATFORM) + FastRNG fast_rng; +#endif + while (attempts < max_attempts) { +#if defined(RP2040_PLATFORM) + new_id = _callbacks->generateNewIdentity(); // use radio noise for RP2040 +#else + new_id = mesh::LocalIdentity(&fast_rng); +#endif + uint8_t hash = new_id.pub_key[0]; + attempts++; + + if (has_target) { + if (hash == target_hash) { + found = true; + break; + } + } else { + if (hash != 0x00 && hash != 0xFF && !_callbacks->hasNeighborWithHash(hash)) { + found = true; + break; + } + } + } + + if (!found) { + if (has_target) { + sprintf(reply, "Error: hash %02X not found after %d attempts", target_hash, attempts); + } else { + strcpy(reply, "Error: could not find non-colliding hash"); + } + } else { + _callbacks->saveIdentity(new_id, false); // save but don't apply until reboot + char hex[5]; + mesh::Utils::toHex(hex, new_id.pub_key, 2); + if (has_target && _callbacks->hasNeighborWithHash(new_id.pub_key[0])) { + sprintf(reply, "WARNING: collision! new ID: %s (reboot to apply)", hex); + } else { + sprintf(reply, "OK - new ID: %s (reboot to apply)", hex); + } + } } else if (memcmp(command, "sensor get ", 11) == 0) { const char* key = command + 11; const char* val = _sensors->getSettingByKey(key); diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index 068783ab1..89e1044a1 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -72,7 +72,9 @@ class CommonCLICallbacks { virtual void formatRadioStatsReply(char *reply) = 0; virtual void formatPacketStatsReply(char *reply) = 0; virtual mesh::LocalIdentity& getSelfId() = 0; - virtual void saveIdentity(const mesh::LocalIdentity& new_id) = 0; + virtual void saveIdentity(const mesh::LocalIdentity& new_id, bool apply_now = true) = 0; + virtual mesh::LocalIdentity generateNewIdentity() = 0; + virtual bool hasNeighborWithHash(uint8_t hash) { return false; } virtual void clearStats() = 0; virtual void applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) = 0;