diff --git a/src/helpers/nrf52/BleDebugTrace.h b/src/helpers/nrf52/BleDebugTrace.h index acfedae81..2c868a214 100644 --- a/src/helpers/nrf52/BleDebugTrace.h +++ b/src/helpers/nrf52/BleDebugTrace.h @@ -30,6 +30,7 @@ struct MeshBleTraceBuffer { }; extern "C" { extern MeshBleTraceBuffer mesh_ble_trace; } void meshBleTraceState(char* reply, size_t capacity); +void meshBleTraceIdentity(char* reply, size_t capacity); static inline void meshBleTraceMax(std::atomic& value, uint32_t sample) { uint32_t old = value.load(std::memory_order_relaxed); while (sample > old && !value.compare_exchange_weak(old, sample, @@ -77,6 +78,11 @@ static inline void meshBleTraceDispatchExit(uint32_t event, uint32_t handle) { } static inline bool meshBleTraceCommand(const char* command, char* reply, size_t capacity) { + if (!strcmp(command, "get bluetooth.trace.identity") + || !strcmp(command, "get ble.trace.identity")) { + meshBleTraceIdentity(reply, capacity); + return true; + } if (!strcmp(command, "get bluetooth.trace.state")) { meshBleTraceState(reply, capacity); return true; diff --git a/src/helpers/nrf52/SerialBLEInterface.cpp b/src/helpers/nrf52/SerialBLEInterface.cpp index 244b5a34f..971815b46 100644 --- a/src/helpers/nrf52/SerialBLEInterface.cpp +++ b/src/helpers/nrf52/SerialBLEInterface.cpp @@ -52,6 +52,48 @@ void meshBleTraceState(char* reply, size_t capacity) { if (instance) instance->formatDebugState(reply, capacity); else snprintf(reply, capacity, "> Bluetooth not initialized"); } + +void meshBleTraceIdentity(char* reply, size_t capacity) { + if (!instance) { + snprintf(reply, capacity, "> Bluetooth not initialized"); + return; + } + ble_gap_addr_t address = {}; + const uint32_t address_status = sd_ble_gap_addr_get(&address); + // Read the real GAP name rather than the application's saved preferences. + // The SDK returns a non-NUL-terminated byte string and the complete length, + // even when it only copies the supplied buffer's bounded prefix. + uint8_t name[32] = {}; + uint16_t name_length = sizeof(name); + const uint32_t name_status = sd_ble_gap_device_name_get(name, &name_length); + // The global SVC ABI guard protects these calls under LTO. Also retain the + // explicit output reload barrier used by the other diagnostic SDK queries. + __asm__ __volatile__("" ::: "memory"); + static const char hex[] = "0123456789abcdef"; + char address_hex[13] = {}; + if (address_status == NRF_SUCCESS) { + for (size_t i = 0; i < sizeof(address.addr); ++i) { + const uint8_t byte = address.addr[sizeof(address.addr) - 1 - i]; + address_hex[2 * i] = hex[byte >> 4]; + address_hex[2 * i + 1] = hex[byte & 15]; + } + } + char name_hex[2 * sizeof(name) + 1] = {}; + if (name_status == NRF_SUCCESS) { + const size_t copied = name_length < sizeof(name) ? name_length : sizeof(name); + for (size_t i = 0; i < copied; ++i) { + name_hex[2 * i] = hex[name[i] >> 4]; + name_hex[2 * i + 1] = hex[name[i] & 15]; + } + } + snprintf(reply, capacity, + "> a=%lu,t=%u,mac=%s,n=%lu,len=%u,hex=%s,adv=%u,scan=%u", + (unsigned long)address_status, + address_status == NRF_SUCCESS ? (unsigned)address.addr_type : 255u, + address_hex, (unsigned long)name_status, (unsigned)name_length, + name_hex, (unsigned)Bluefruit.Advertising.count(), + (unsigned)Bluefruit.ScanResponse.count()); +} #endif static bool formatDeviceName(char* dest, size_t capacity, diff --git a/test/test_nrf52_ble_trace.py b/test/test_nrf52_ble_trace.py index e5b3afcc7..73ccddf0d 100644 --- a/test/test_nrf52_ble_trace.py +++ b/test/test_nrf52_ble_trace.py @@ -24,6 +24,9 @@ extern "C" { MeshBleTraceBuffer mesh_ble_trace; } void meshBleTraceState(char* reply, size_t capacity) { snprintf(reply,capacity,"> state"); } +void meshBleTraceIdentity(char* reply, size_t capacity) { + snprintf(reply,capacity,"> identity"); +} int main() { char reply[160]; assert(!meshBleTraceCommand("get radio",reply,sizeof(reply))); @@ -42,6 +45,10 @@ int main() { assert(!strcmp(reply,"> 257,1234,0x0010,65535,0,0")); assert(meshBleTraceCommand("get bluetooth.trace.state",reply,sizeof(reply))); assert(!strcmp(reply,"> state")); + for (const char* command : {"get bluetooth.trace.identity", "get ble.trace.identity"}) { + assert(meshBleTraceCommand(command,reply,sizeof(reply))); + assert(!strcmp(reply,"> identity")); + } meshBleTraceDispatchEnter(0x10,0); assert(mesh_ble_trace.dispatch_event.load()==0x10); meshBleTraceDispatchExit(0x10,0); @@ -76,6 +83,73 @@ int main() { check=True, capture_output=True) subprocess.run([str(binary)],check=True,capture_output=True) + def test_real_identity_query_bounds_sdk_outputs_and_reports_failures(self): + source = (ROOT / "src/helpers/nrf52/SerialBLEInterface.cpp").read_text() + start = source.index("void meshBleTraceIdentity(") + implementation = source[start:source.index("\n}\n", start) + 3] + with tempfile.TemporaryDirectory() as directory: + work = Path(directory) + cpp = work / "identity.cpp" + cpp.write_text(r''' +#include +#include +#include +#include +static constexpr uint32_t NRF_SUCCESS = 0; +struct ble_gap_addr_t { uint8_t addr_type; uint8_t addr[6]; }; +static bool initialized; +static void* instance; +static unsigned calls; +static uint32_t address_result, name_result; +static uint16_t actual_length; +struct Data { uint8_t count() { return 255; } }; +static struct { Data Advertising, ScanResponse; } Bluefruit; +static uint32_t sd_ble_gap_addr_get(ble_gap_addr_t* address) { + assert(initialized); ++calls; + *address = {1, {0xc6, 0xbb, 0xcd, 0x86, 0xa0, 0xd1}}; + return address_result; +} +static uint32_t sd_ble_gap_device_name_get(uint8_t* name, uint16_t* length) { + assert(initialized && *length == 32); ++calls; + const uint8_t bytes[] = {0x41, 0x00, 0xc3, 0xa9}; + const size_t copied = actual_length < *length ? actual_length : *length; + for (size_t i = 0; i < copied; ++i) name[i] = bytes[i % sizeof(bytes)]; + *length = actual_length; + return name_result; +} +@IMPLEMENTATION@ +int main() { + char reply[172]; + meshBleTraceIdentity(reply, sizeof(reply)); + assert(!strcmp(reply, "> Bluetooth not initialized") && calls == 0); + initialized = true; instance = &initialized; actual_length = 4; + meshBleTraceIdentity(reply, sizeof(reply)); + assert(!strcmp(reply, "> a=0,t=1,mac=d1a086cdbbc6,n=0,len=4,hex=4100c3a9,adv=255,scan=255")); + actual_length = 65535; + meshBleTraceIdentity(reply, sizeof(reply)); + assert(strstr(reply, ",len=65535,hex=")); + assert(strstr(reply, ",adv=255,scan=255")); + assert(strlen(reply) < sizeof(reply) - 1); + const char* begin = strstr(reply, "hex=") + 4; + assert(strchr(begin, ',') - begin == 64); + address_result = name_result = UINT32_MAX; + meshBleTraceIdentity(reply, sizeof(reply)); + assert(!strcmp(reply, "> a=4294967295,t=255,mac=,n=4294967295,len=65535,hex=,adv=255,scan=255")); + char tiny[5] = {}; + meshBleTraceIdentity(tiny, sizeof(tiny)); + assert(tiny[4] == 0); +} +'''.replace("@IMPLEMENTATION@", implementation)) + binary = work / "identity" + compiled = subprocess.run([ + "c++", "-std=c++17", "-O2", "-flto", "-Wall", "-Wextra", "-Werror", + "-fsanitize=address,undefined", "-fno-omit-frame-pointer", + "-fno-pie", "-no-pie", str(cpp), "-o", str(binary), + ], capture_output=True, text=True) + self.assertEqual(compiled.returncode, 0, compiled.stderr) + result = subprocess.run([str(binary)], capture_output=True, text=True) + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + def test_concurrent_reader_never_accepts_mixed_records(self): with tempfile.TemporaryDirectory() as directory: work = Path(directory)