mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-09-01 20:28:19 +00:00
Harden ESP32 identity entropy handling
This commit is contained in:
@@ -2,9 +2,14 @@
|
||||
|
||||
#include <Arduino.h> // needed for PlatformIO
|
||||
#include <Mesh.h>
|
||||
#include <helpers/IdentityGeneration.h>
|
||||
#include "helpers/radiolib/RXPowerSaving.h"
|
||||
#include "helpers/radiolib/RxBoostedGainDefaults.h"
|
||||
|
||||
#if defined(ESP32_PLATFORM)
|
||||
#include <helpers/ESP32TrueRandom.h>
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_USB_INTERFACE
|
||||
#include <helpers/CLICommandUtils.h>
|
||||
#include <helpers/TracePathHelpers.h>
|
||||
@@ -1382,15 +1387,21 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe
|
||||
void MyMesh::begin(bool has_display) {
|
||||
BaseChatMesh::begin();
|
||||
|
||||
const bool is_new_install = !_store->loadMainIdentity(self_id);
|
||||
const bool is_new_install = !_store->loadMainIdentity(self_id)
|
||||
|| mesh::hasReservedIdentityPrefix(self_id);
|
||||
bool identity_ready = true;
|
||||
if (is_new_install) {
|
||||
self_id = radio_new_identity(); // create new random identity
|
||||
int count = 0;
|
||||
while (count < 10 && (self_id.pub_key[0] == 0x00 || self_id.pub_key[0] == 0xFF)) { // reserved id hashes
|
||||
self_id = radio_new_identity();
|
||||
count++;
|
||||
}
|
||||
_store->saveMainIdentity(self_id);
|
||||
identity_ready = mesh::generateUsableLocalIdentity(self_id, radio_new_identity);
|
||||
if (identity_ready) _store->saveMainIdentity(self_id);
|
||||
}
|
||||
|
||||
#if defined(ESP32_PLATFORM)
|
||||
mesh::discardESP32TrueRandom();
|
||||
#endif
|
||||
if (!identity_ready) {
|
||||
MESH_DEBUG_PRINTLN("Identity generation exhausted all attempts; rebooting");
|
||||
board.reboot();
|
||||
return;
|
||||
}
|
||||
|
||||
// if name is provided as a build flag, use that as default node name instead
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
#include <Arduino.h>
|
||||
#include <target.h>
|
||||
#include <helpers/ArduinoHelpers.h>
|
||||
#include <helpers/IdentityGeneration.h>
|
||||
#include <helpers/IdentityStore.h>
|
||||
#include "KissModem.h"
|
||||
|
||||
#if defined(ESP32_PLATFORM)
|
||||
#include <helpers/ESP32TrueRandom.h>
|
||||
#endif
|
||||
|
||||
#if defined(NRF52_PLATFORM)
|
||||
#include <InternalFileSystem.h>
|
||||
#elif defined(RP2040_PLATFORM)
|
||||
@@ -48,12 +53,20 @@ void loadOrCreateIdentity() {
|
||||
#error "Filesystem not defined"
|
||||
#endif
|
||||
|
||||
if (!store.load("_main", identity)) {
|
||||
identity = radio_new_identity();
|
||||
while (identity.pub_key[0] == 0x00 || identity.pub_key[0] == 0xFF) {
|
||||
identity = radio_new_identity();
|
||||
}
|
||||
store.save("_main", identity);
|
||||
const bool needs_identity = !store.load("_main", identity)
|
||||
|| mesh::hasReservedIdentityPrefix(identity);
|
||||
bool identity_ready = true;
|
||||
if (needs_identity) {
|
||||
identity_ready = mesh::generateUsableLocalIdentity(identity, radio_new_identity);
|
||||
if (identity_ready) store.save("_main", identity);
|
||||
}
|
||||
|
||||
#if defined(ESP32_PLATFORM)
|
||||
mesh::discardESP32TrueRandom();
|
||||
#endif
|
||||
if (!identity_ready) {
|
||||
MESH_DEBUG_PRINTLN("Identity generation exhausted all attempts; rebooting");
|
||||
board.reboot();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
#include <Arduino.h> // needed for PlatformIO
|
||||
#include <Mesh.h>
|
||||
#include <helpers/IdentityGeneration.h>
|
||||
|
||||
#include "MyMesh.h"
|
||||
#if defined(ESP32_PLATFORM)
|
||||
#include <helpers/ESP32TrueRandom.h>
|
||||
#endif
|
||||
#if defined(ESP32) && MAX_RECENT_REPEATERS > 0
|
||||
#include <new>
|
||||
#endif
|
||||
@@ -110,14 +114,22 @@ void setup() {
|
||||
#else
|
||||
#error "need to define filesystem"
|
||||
#endif
|
||||
if (!store.load("_main", the_mesh.self_id)) {
|
||||
const bool needs_identity = !store.load("_main", the_mesh.self_id)
|
||||
|| mesh::hasReservedIdentityPrefix(the_mesh.self_id);
|
||||
bool identity_ready = true;
|
||||
if (needs_identity) {
|
||||
MESH_DEBUG_PRINTLN("Generating new keypair");
|
||||
the_mesh.self_id = radio_new_identity(); // create new random identity
|
||||
int count = 0;
|
||||
while (count < 10 && (the_mesh.self_id.pub_key[0] == 0x00 || the_mesh.self_id.pub_key[0] == 0xFF)) { // reserved id hashes
|
||||
the_mesh.self_id = radio_new_identity(); count++;
|
||||
}
|
||||
store.save("_main", the_mesh.self_id);
|
||||
identity_ready = mesh::generateUsableLocalIdentity(the_mesh.self_id, radio_new_identity);
|
||||
if (identity_ready) store.save("_main", the_mesh.self_id);
|
||||
}
|
||||
|
||||
#if defined(ESP32_PLATFORM)
|
||||
mesh::discardESP32TrueRandom();
|
||||
#endif
|
||||
if (!identity_ready) {
|
||||
MESH_DEBUG_PRINTLN("Identity generation exhausted all attempts; rebooting");
|
||||
board.reboot();
|
||||
return;
|
||||
}
|
||||
|
||||
// Print the running firmware version at boot so it's visible after an OTA
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
#include <Arduino.h> // needed for PlatformIO
|
||||
#include <Mesh.h>
|
||||
#include <helpers/IdentityGeneration.h>
|
||||
|
||||
#include "MyMesh.h"
|
||||
|
||||
#if defined(ESP32_PLATFORM)
|
||||
#include <helpers/ESP32TrueRandom.h>
|
||||
#endif
|
||||
|
||||
#ifdef ETHERNET_ENABLED
|
||||
#define ETHERNET_CLI_BANNER "MeshCore Room Server CLI"
|
||||
#include <helpers/nrf52/EthernetCLI.h>
|
||||
@@ -78,13 +83,21 @@ void setup() {
|
||||
#else
|
||||
#error "need to define filesystem"
|
||||
#endif
|
||||
if (!store.load("_main", the_mesh.self_id)) {
|
||||
the_mesh.self_id = radio_new_identity(); // create new random identity
|
||||
int count = 0;
|
||||
while (count < 10 && (the_mesh.self_id.pub_key[0] == 0x00 || the_mesh.self_id.pub_key[0] == 0xFF)) { // reserved id hashes
|
||||
the_mesh.self_id = radio_new_identity(); count++;
|
||||
}
|
||||
store.save("_main", the_mesh.self_id);
|
||||
const bool needs_identity = !store.load("_main", the_mesh.self_id)
|
||||
|| mesh::hasReservedIdentityPrefix(the_mesh.self_id);
|
||||
bool identity_ready = true;
|
||||
if (needs_identity) {
|
||||
identity_ready = mesh::generateUsableLocalIdentity(the_mesh.self_id, radio_new_identity);
|
||||
if (identity_ready) store.save("_main", the_mesh.self_id);
|
||||
}
|
||||
|
||||
#if defined(ESP32_PLATFORM)
|
||||
mesh::discardESP32TrueRandom();
|
||||
#endif
|
||||
if (!identity_ready) {
|
||||
MESH_DEBUG_PRINTLN("Identity generation exhausted all attempts; rebooting");
|
||||
board.reboot();
|
||||
return;
|
||||
}
|
||||
|
||||
Serial.print("Room ID: ");
|
||||
|
||||
@@ -10,9 +10,13 @@
|
||||
#endif
|
||||
|
||||
#include <helpers/ArduinoHelpers.h>
|
||||
#include <helpers/IdentityGeneration.h>
|
||||
#include <helpers/StaticPoolPacketManager.h>
|
||||
#include <helpers/SimpleMeshTables.h>
|
||||
#include <helpers/IdentityStore.h>
|
||||
#if defined(ESP32_PLATFORM)
|
||||
#include <helpers/ESP32TrueRandom.h>
|
||||
#endif
|
||||
#include <RTClib.h>
|
||||
#include <target.h>
|
||||
|
||||
@@ -310,7 +314,10 @@ public:
|
||||
#else
|
||||
IdentityStore store(fs, "/identity");
|
||||
#endif
|
||||
if (!store.load("_main", self_id, _prefs.node_name, sizeof(_prefs.node_name))) { // legacy: node_name was from identity file
|
||||
const bool needs_identity = !store.load("_main", self_id, _prefs.node_name, sizeof(_prefs.node_name))
|
||||
|| mesh::hasReservedIdentityPrefix(self_id); // legacy: node_name was from identity file
|
||||
bool identity_ready = true;
|
||||
if (needs_identity) {
|
||||
// Need way to get some entropy to seed RNG
|
||||
Serial.println("Press ENTER to generate key:");
|
||||
char c = 0;
|
||||
@@ -319,12 +326,22 @@ public:
|
||||
}
|
||||
((StdRNG *)getRNG())->begin(millis());
|
||||
|
||||
self_id = mesh::LocalIdentity(getRNG()); // create new random identity
|
||||
int count = 0;
|
||||
while (count < 10 && (self_id.pub_key[0] == 0x00 || self_id.pub_key[0] == 0xFF)) { // reserved id hashes
|
||||
self_id = mesh::LocalIdentity(getRNG()); count++;
|
||||
}
|
||||
store.save("_main", self_id);
|
||||
#if defined(ESP32_PLATFORM)
|
||||
identity_ready = mesh::generateUsableLocalIdentity(self_id, radio_new_identity);
|
||||
#else
|
||||
identity_ready = mesh::generateUsableLocalIdentity(
|
||||
self_id, [this]() { return mesh::LocalIdentity(getRNG()); });
|
||||
#endif
|
||||
if (identity_ready) store.save("_main", self_id);
|
||||
}
|
||||
|
||||
#if defined(ESP32_PLATFORM)
|
||||
mesh::discardESP32TrueRandom();
|
||||
#endif
|
||||
if (!identity_ready) {
|
||||
MESH_DEBUG_PRINTLN("Identity generation exhausted all attempts; rebooting");
|
||||
board.reboot();
|
||||
return;
|
||||
}
|
||||
|
||||
// load persisted prefs
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
#include "SensorMesh.h"
|
||||
#include <helpers/IdentityGeneration.h>
|
||||
|
||||
#if defined(ESP32_PLATFORM)
|
||||
#include <helpers/ESP32TrueRandom.h>
|
||||
#endif
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
#include "UITask.h"
|
||||
@@ -100,14 +105,22 @@ void setup() {
|
||||
#else
|
||||
#error "need to define filesystem"
|
||||
#endif
|
||||
if (!store.load("_main", the_mesh.self_id)) {
|
||||
const bool needs_identity = !store.load("_main", the_mesh.self_id)
|
||||
|| mesh::hasReservedIdentityPrefix(the_mesh.self_id);
|
||||
bool identity_ready = true;
|
||||
if (needs_identity) {
|
||||
MESH_DEBUG_PRINTLN("Generating new keypair");
|
||||
the_mesh.self_id = radio_new_identity(); // create new random identity
|
||||
int count = 0;
|
||||
while (count < 10 && (the_mesh.self_id.pub_key[0] == 0x00 || the_mesh.self_id.pub_key[0] == 0xFF)) { // reserved id hashes
|
||||
the_mesh.self_id = radio_new_identity(); count++;
|
||||
}
|
||||
store.save("_main", the_mesh.self_id);
|
||||
identity_ready = mesh::generateUsableLocalIdentity(the_mesh.self_id, radio_new_identity);
|
||||
if (identity_ready) store.save("_main", the_mesh.self_id);
|
||||
}
|
||||
|
||||
#if defined(ESP32_PLATFORM)
|
||||
mesh::discardESP32TrueRandom();
|
||||
#endif
|
||||
if (!identity_ready) {
|
||||
MESH_DEBUG_PRINTLN("Identity generation exhausted all attempts; rebooting");
|
||||
board.reboot();
|
||||
return;
|
||||
}
|
||||
|
||||
Serial.print("Sensor ID: ");
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "soc/rtc.h"
|
||||
#include "esp_system.h"
|
||||
#include <driver/rtc_io.h>
|
||||
#include "ESP32TrueRandom.h"
|
||||
|
||||
#if defined(ARDUINO_USB_CDC_ON_BOOT) && ARDUINO_USB_CDC_ON_BOOT && \
|
||||
(!defined(ARDUINO_USB_MODE) || !ARDUINO_USB_MODE)
|
||||
@@ -38,6 +39,10 @@ protected:
|
||||
|
||||
public:
|
||||
void begin() {
|
||||
// Arduino's early init hook normally captured this before initVariant().
|
||||
// Keep this idempotent fallback before this class touches ADC peripherals.
|
||||
mesh::initializeESP32TrueRandom();
|
||||
|
||||
// for future use, sub-classes SHOULD call this from their begin()
|
||||
startup_reason = BD_STARTUP_NORMAL;
|
||||
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
#include "ESP32TrueRandom.h"
|
||||
|
||||
#if defined(ESP32_PLATFORM)
|
||||
|
||||
#include <bootloader_random.h>
|
||||
#include <esp_random.h>
|
||||
#include <esp_system.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/semphr.h>
|
||||
#include <mbedtls/platform_util.h>
|
||||
|
||||
#include "IdentityGeneration.h"
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr size_t TRUE_RANDOM_POOL_SIZE =
|
||||
SEED_SIZE * mesh::MAX_LOCAL_IDENTITY_GENERATION_ATTEMPTS;
|
||||
|
||||
enum TrueRandomState : uint8_t {
|
||||
TRUE_RANDOM_UNINITIALIZED,
|
||||
TRUE_RANDOM_READY,
|
||||
TRUE_RANDOM_DISCARDED
|
||||
};
|
||||
|
||||
uint8_t true_random_pool[TRUE_RANDOM_POOL_SIZE];
|
||||
size_t true_random_offset = 0;
|
||||
TrueRandomState true_random_state = TRUE_RANDOM_UNINITIALIZED;
|
||||
|
||||
StaticSemaphore_t true_random_mutex_storage;
|
||||
SemaphoreHandle_t true_random_mutex = NULL;
|
||||
portMUX_TYPE true_random_mutex_init_mux = portMUX_INITIALIZER_UNLOCKED;
|
||||
|
||||
SemaphoreHandle_t getTrueRandomMutex() {
|
||||
// Always enter the cross-core critical section: an unlocked preliminary
|
||||
// pointer read would itself race a first caller creating the mutex.
|
||||
portENTER_CRITICAL(&true_random_mutex_init_mux);
|
||||
if (true_random_mutex == NULL) {
|
||||
true_random_mutex = xSemaphoreCreateMutexStatic(&true_random_mutex_storage);
|
||||
}
|
||||
SemaphoreHandle_t mutex = true_random_mutex;
|
||||
portEXIT_CRITICAL(&true_random_mutex_init_mux);
|
||||
return mutex;
|
||||
}
|
||||
|
||||
bool tryMixESP32TrueRandom(uint8_t* dest, size_t size) {
|
||||
if (size == 0) return true;
|
||||
if (dest == NULL) return false;
|
||||
|
||||
SemaphoreHandle_t mutex = getTrueRandomMutex();
|
||||
if (mutex == NULL || xSemaphoreTake(mutex, portMAX_DELAY) != pdTRUE) return false;
|
||||
|
||||
const bool valid_offset = true_random_offset <= sizeof(true_random_pool);
|
||||
const size_t available = valid_offset
|
||||
? sizeof(true_random_pool) - true_random_offset
|
||||
: 0;
|
||||
if (true_random_state != TRUE_RANDOM_READY || size > available) {
|
||||
xSemaphoreGive(mutex);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
dest[i] ^= true_random_pool[true_random_offset + i];
|
||||
}
|
||||
mbedtls_platform_zeroize(&true_random_pool[true_random_offset], size);
|
||||
true_random_offset += size;
|
||||
|
||||
xSemaphoreGive(mutex);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace mesh {
|
||||
|
||||
void initializeESP32TrueRandom() {
|
||||
SemaphoreHandle_t mutex = getTrueRandomMutex();
|
||||
if (mutex == NULL || xSemaphoreTake(mutex, portMAX_DELAY) != pdTRUE) return;
|
||||
|
||||
if (true_random_state != TRUE_RANDOM_UNINITIALIZED) {
|
||||
xSemaphoreGive(mutex);
|
||||
return;
|
||||
}
|
||||
|
||||
// ESP-IDF guarantees true RNG output while this SAR ADC entropy source is
|
||||
// enabled. Arduino's init hook below runs before variant and board setup.
|
||||
bootloader_random_enable();
|
||||
esp_fill_random(true_random_pool, sizeof(true_random_pool));
|
||||
bootloader_random_disable();
|
||||
|
||||
true_random_offset = 0;
|
||||
true_random_state = TRUE_RANDOM_READY;
|
||||
xSemaphoreGive(mutex);
|
||||
}
|
||||
|
||||
void mixESP32TrueRandom(uint8_t* dest, size_t size) {
|
||||
if (tryMixESP32TrueRandom(dest, size)) return;
|
||||
|
||||
// A software or radio source may already have populated dest. Never let
|
||||
// those bytes escape as pseudo-only identity material.
|
||||
if (dest != NULL && size > 0) mbedtls_platform_zeroize(dest, size);
|
||||
discardESP32TrueRandom();
|
||||
esp_restart();
|
||||
}
|
||||
|
||||
void discardESP32TrueRandom() {
|
||||
SemaphoreHandle_t mutex = getTrueRandomMutex();
|
||||
if (mutex == NULL || xSemaphoreTake(mutex, portMAX_DELAY) != pdTRUE) {
|
||||
mbedtls_platform_zeroize(true_random_pool, sizeof(true_random_pool));
|
||||
true_random_offset = sizeof(true_random_pool);
|
||||
true_random_state = TRUE_RANDOM_DISCARDED;
|
||||
return;
|
||||
}
|
||||
|
||||
mbedtls_platform_zeroize(true_random_pool, sizeof(true_random_pool));
|
||||
true_random_offset = sizeof(true_random_pool);
|
||||
true_random_state = TRUE_RANDOM_DISCARDED;
|
||||
xSemaphoreGive(mutex);
|
||||
}
|
||||
|
||||
} // namespace mesh
|
||||
|
||||
// Arduino-ESP32 calls this weak hook before initVariant() and setup(). Defining
|
||||
// it here makes entropy capture independent of every board subclass's begin()
|
||||
// ordering, including future variants which initialize ADC or RF early.
|
||||
extern "C" void init() {
|
||||
mesh::initializeESP32TrueRandom();
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#if defined(ESP32_PLATFORM)
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace mesh {
|
||||
|
||||
// Capture true hardware entropy during Arduino's early initialization, before
|
||||
// variant, board, ADC, Wi-Fi, or Bluetooth setup. ESP32Board::begin() calls
|
||||
// this again as an idempotent fallback.
|
||||
void initializeESP32TrueRandom();
|
||||
|
||||
// XOR previously captured true hardware entropy into every requested byte.
|
||||
// This is fail-closed: if a complete hardware block is unavailable, dest is
|
||||
// securely erased and the ESP32 restarts instead of returning pseudo-only
|
||||
// output.
|
||||
void mixESP32TrueRandom(uint8_t* dest, size_t size);
|
||||
|
||||
// Securely erase and permanently close the startup pool once the persisted or
|
||||
// newly generated identity is ready. It cannot be reopened later because ADC
|
||||
// or RF peripherals may have started by then.
|
||||
void discardESP32TrueRandom();
|
||||
|
||||
} // namespace mesh
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include <Identity.h>
|
||||
#include <stddef.h>
|
||||
|
||||
namespace mesh {
|
||||
|
||||
// Keep identity generation bounded so platforms which cache startup entropy
|
||||
// can provision an exact amount. Eleven attempts makes exhaustion vanishingly
|
||||
// unlikely while still allowing callers to fail closed.
|
||||
constexpr size_t MAX_LOCAL_IDENTITY_GENERATION_ATTEMPTS = 11;
|
||||
|
||||
inline bool hasReservedIdentityPrefix(const Identity& identity) {
|
||||
return identity.pub_key[0] == 0x00 || identity.pub_key[0] == 0xFF;
|
||||
}
|
||||
|
||||
template <typename Generator>
|
||||
bool generateUsableLocalIdentity(LocalIdentity& identity, Generator generator) {
|
||||
for (size_t attempt = 0;
|
||||
attempt < MAX_LOCAL_IDENTITY_GENERATION_ATTEMPTS;
|
||||
++attempt) {
|
||||
identity = generator();
|
||||
if (!hasReservedIdentityPrefix(identity)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace mesh
|
||||
@@ -15,6 +15,9 @@
|
||||
#ifdef USE_CC310_HW_CRYPTO
|
||||
#include "../NRF52Crypto.h"
|
||||
#endif
|
||||
#ifdef ESP32_PLATFORM
|
||||
#include "../ESP32TrueRandom.h"
|
||||
#endif
|
||||
struct PacketMillis {
|
||||
uint32_t preambleMillis; // preamble-detect -> header-valid deadline
|
||||
uint32_t payloadMillis; // header-valid -> rx-done deadline
|
||||
@@ -217,14 +220,16 @@ public:
|
||||
RadioNoiseListener(PhysicalLayer& radio): _radio(&radio) { }
|
||||
|
||||
void random(uint8_t* dest, size_t sz) override {
|
||||
// Preserve the existing radio/PRNG entropy on every platform. On nRF52840,
|
||||
// independently generated CC310 bytes are mixed in without becoming the
|
||||
// sole source of randomness.
|
||||
// Preserve the existing radio/PRNG entropy on every platform. Independent
|
||||
// hardware entropy is mixed in without becoming the sole source.
|
||||
for (size_t i = 0; i < sz; i++) {
|
||||
dest[i] = _radio->randomByte() ^ (::random(0, 256) & 0xFF);
|
||||
}
|
||||
#ifdef USE_CC310_HW_CRYPTO
|
||||
mesh::mixCC310Random(dest, sz);
|
||||
#endif
|
||||
#ifdef ESP32_PLATFORM
|
||||
mesh::mixESP32TrueRandom(dest, sz);
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
@@ -43,6 +43,7 @@ does not reflect the GoogleTest count -- run the built binary directly
|
||||
| `test_flood_filter_policy` | `src/helpers/FloodFilterPolicy.h` | unordered blacklist matching; ordered 1/2/3-byte pbyte rule prefixes; original incoming scope classes and canonical region-name identity; channel-authentication cache key comparison; priority ordering and terminal stop masks; bridge-bucket and regionless channel-target selector encoding; `require=region` and per-channel scope-gate truth tables; fast/slow timing; adding, replacing, and preserving packet scope |
|
||||
| `test_logical_message_cache` | `src/helpers/LogicalMessageCache.h` | bounded logical-message mapping; stable retry timestamps; exact older retries after newer messages; stale and same-timestamp mismatch rejection |
|
||||
| `test_cli_command_utils` | `src/helpers/CLICommandUtils.h`, `src/helpers/ContactListOrder.h`, `src/helpers/TerminalCommandTracker.h`, `src/helpers/TerminalDisplayFilter.h` | terminal verb/argument/path parsing; routed receive labels; quiet display defaults and independent emergency filtering; favorite-first contact ordering; single-command reply matching, round-trip timing, and rollover-safe expiration |
|
||||
| `test_identity_generation` | `src/helpers/IdentityGeneration.h` | reserved-prefix rejection; bounded retries; final provisioned attempt; fail-closed exhaustion |
|
||||
| `test_remote_cli_reply_cache` | `src/helpers/RemoteCliReplyCache.h`, `src/helpers/RemoteCliRequest.h`, `src/helpers/RemoteCliTimeout.h` | authenticated logical-request matching; bounded recent-reply history; backward-compatible retry identity; 300% response timeout; empty-response completion; on-air truncation and clearing |
|
||||
| `test_companion_frame_queue` | `src/helpers/CompanionFrameQueue.h` | response/required/best-effort classification; reserved capacity; stable priority; safe eviction; message-waiting coalescing |
|
||||
| `test_serial_mode_switch` | `src/helpers/ArduinoSerialInterface.cpp`, `src/helpers/MultiSerialInterface.h` | independent terminal/seeder control-sequence recognition across reads and binary-frame boundaries; passthrough ownership of USB input and suppression of binary output; Bluetooth-only connection and pairing-request routing |
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "helpers/IdentityGeneration.h"
|
||||
|
||||
namespace {
|
||||
|
||||
mesh::LocalIdentity identityWithPrefix(uint8_t prefix) {
|
||||
mesh::LocalIdentity identity;
|
||||
memset(identity.pub_key, 0x5A, sizeof(identity.pub_key));
|
||||
identity.pub_key[0] = prefix;
|
||||
return identity;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(IdentityGeneration, AcceptsFirstUsableIdentity) {
|
||||
mesh::LocalIdentity identity;
|
||||
size_t calls = 0;
|
||||
|
||||
const bool generated = mesh::generateUsableLocalIdentity(
|
||||
identity, [&calls]() {
|
||||
++calls;
|
||||
return identityWithPrefix(0x42);
|
||||
});
|
||||
|
||||
EXPECT_TRUE(generated);
|
||||
EXPECT_EQ(1U, calls);
|
||||
EXPECT_EQ(0x42, identity.pub_key[0]);
|
||||
}
|
||||
|
||||
TEST(IdentityGeneration, RetriesBothReservedPrefixes) {
|
||||
mesh::LocalIdentity identity;
|
||||
size_t calls = 0;
|
||||
|
||||
const bool generated = mesh::generateUsableLocalIdentity(
|
||||
identity, [&calls]() {
|
||||
const uint8_t prefixes[] = {0x00, 0xFF, 0x7E};
|
||||
return identityWithPrefix(prefixes[calls++]);
|
||||
});
|
||||
|
||||
EXPECT_TRUE(generated);
|
||||
EXPECT_EQ(3U, calls);
|
||||
EXPECT_EQ(0x7E, identity.pub_key[0]);
|
||||
}
|
||||
|
||||
TEST(IdentityGeneration, AcceptsFinalProvisionedAttempt) {
|
||||
mesh::LocalIdentity identity;
|
||||
size_t calls = 0;
|
||||
|
||||
const bool generated = mesh::generateUsableLocalIdentity(
|
||||
identity, [&calls]() {
|
||||
++calls;
|
||||
return identityWithPrefix(
|
||||
calls == mesh::MAX_LOCAL_IDENTITY_GENERATION_ATTEMPTS ? 0x23 : 0x00);
|
||||
});
|
||||
|
||||
EXPECT_TRUE(generated);
|
||||
EXPECT_EQ(mesh::MAX_LOCAL_IDENTITY_GENERATION_ATTEMPTS, calls);
|
||||
EXPECT_EQ(0x23, identity.pub_key[0]);
|
||||
}
|
||||
|
||||
TEST(IdentityGeneration, FailsClosedAfterProvisionedAttempts) {
|
||||
mesh::LocalIdentity identity;
|
||||
size_t calls = 0;
|
||||
|
||||
const bool generated = mesh::generateUsableLocalIdentity(
|
||||
identity, [&calls]() {
|
||||
++calls;
|
||||
return identityWithPrefix(0xFF);
|
||||
});
|
||||
|
||||
EXPECT_FALSE(generated);
|
||||
EXPECT_EQ(mesh::MAX_LOCAL_IDENTITY_GENERATION_ATTEMPTS, calls);
|
||||
EXPECT_TRUE(mesh::hasReservedIdentityPrefix(identity));
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <Arduino.h>
|
||||
#include "target.h"
|
||||
#include <helpers/ArduinoHelpers.h>
|
||||
#include <helpers/ESP32TrueRandom.h>
|
||||
|
||||
ESP32Board board;
|
||||
|
||||
@@ -17,12 +18,15 @@ bool radio_init() {
|
||||
return true; // success
|
||||
}
|
||||
|
||||
// NOTE: as we are using the WiFi radio, the ESP_IDF will have enabled hardware RNG:
|
||||
// https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/random.html
|
||||
// Combine the normal software source with true entropy captured before RF/ADC
|
||||
// initialization. The hardware source is never read in pseudo-random-only mode.
|
||||
class ESP_RNG : public mesh::RNG {
|
||||
public:
|
||||
void random(uint8_t* dest, size_t sz) override {
|
||||
esp_fill_random(dest, sz);
|
||||
for (size_t i = 0; i < sz; ++i) {
|
||||
dest[i] = (::random(0, 256) & 0xFF);
|
||||
}
|
||||
mesh::mixESP32TrueRandom(dest, sz);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <Arduino.h>
|
||||
#include "target.h"
|
||||
#include <helpers/ArduinoHelpers.h>
|
||||
#include <helpers/ESP32TrueRandom.h>
|
||||
|
||||
ESP32Board board;
|
||||
|
||||
@@ -29,12 +30,15 @@ bool radio_init() {
|
||||
return true; // success
|
||||
}
|
||||
|
||||
// NOTE: as we are using the WiFi radio, the ESP_IDF will have enabled hardware RNG:
|
||||
// https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/random.html
|
||||
// Combine the normal software source with true entropy captured before RF/ADC
|
||||
// initialization. The hardware source is never read in pseudo-random-only mode.
|
||||
class ESP_RNG : public mesh::RNG {
|
||||
public:
|
||||
void random(uint8_t* dest, size_t sz) override {
|
||||
esp_fill_random(dest, sz);
|
||||
for (size_t i = 0; i < sz; ++i) {
|
||||
dest[i] = (::random(0, 256) & 0xFF);
|
||||
}
|
||||
mesh::mixESP32TrueRandom(dest, sz);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user