Trace public nRF52 advertising payloads for discovery diagnosis

This commit is contained in:
mikecarper committed 2026-10-19 13:47:38 -07:00
1 parent 042d5b7f0d
commit 016a69ea10
3 files changed
+106

No files matched your search

+6
View File
@@ -31,6 +31,7 @@ struct MeshBleTraceBuffer {
extern "C" { extern MeshBleTraceBuffer mesh_ble_trace; }
void meshBleTraceState(char* reply, size_t capacity);
void meshBleTraceIdentity(char* reply, size_t capacity);
void meshBleTraceAdvertising(char* reply, size_t capacity);
static inline void meshBleTraceMax(std::atomic<uint32_t>& value, uint32_t sample) {
uint32_t old = value.load(std::memory_order_relaxed);
while (sample > old && !value.compare_exchange_weak(old, sample,
@@ -78,6 +79,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.advertising")
|| !strcmp(command, "get ble.trace.advertising")) {
meshBleTraceAdvertising(reply, capacity);
return true;
}
if (!strcmp(command, "get bluetooth.trace.identity")
|| !strcmp(command, "get ble.trace.identity")) {
meshBleTraceIdentity(reply, capacity);
+27
View File
@@ -94,6 +94,33 @@ void meshBleTraceIdentity(char* reply, size_t capacity) {
name_hex, (unsigned)Bluefruit.Advertising.count(),
(unsigned)Bluefruit.ScanResponse.count());
}
void meshBleTraceAdvertising(char* reply, size_t capacity) {
if (!instance) {
snprintf(reply, capacity, "> Bluetooth not initialized");
return;
}
// Legacy advertising/scan-response bytes are public. Read the configured
// payloads themselves: the GAP name getter cannot prove what addName copied.
// Retain a hard 31-byte bound even if an invalid count is ever observed.
const auto encode = [](BLEAdvertisingData& payload, char* output) -> unsigned {
static const char hex[] = "0123456789abcdef";
const unsigned count = payload.count();
const uint8_t* bytes = payload.getData();
const unsigned copied = bytes ? (count < 31u ? count : 31u) : 0u;
for (unsigned i = 0; i < copied; ++i) {
output[2 * i] = hex[bytes[i] >> 4];
output[2 * i + 1] = hex[bytes[i] & 15];
}
output[2 * copied] = 0;
return count;
};
char advertising_hex[63], scan_hex[63];
const unsigned advertising_length = encode(Bluefruit.Advertising, advertising_hex);
const unsigned scan_length = encode(Bluefruit.ScanResponse, scan_hex);
snprintf(reply, capacity, "> adv_len=%u,adv_hex=%s,scan_len=%u,scan_hex=%s",
advertising_length, advertising_hex, scan_length, scan_hex);
}
#endif
static bool formatDeviceName(char* dest, size_t capacity,
+73
View File
@@ -27,6 +27,9 @@ void meshBleTraceState(char* reply, size_t capacity) {
void meshBleTraceIdentity(char* reply, size_t capacity) {
snprintf(reply,capacity,"> identity");
}
void meshBleTraceAdvertising(char* reply, size_t capacity) {
snprintf(reply,capacity,"> advertising");
}
int main() {
char reply[160];
assert(!meshBleTraceCommand("get radio",reply,sizeof(reply)));
@@ -49,6 +52,11 @@ int main() {
assert(meshBleTraceCommand(command,reply,sizeof(reply)));
assert(!strcmp(reply,"> identity"));
}
for (const char* command : {"get bluetooth.trace.advertising", "get ble.trace.advertising"}) {
assert(meshBleTraceCommand(command,reply,sizeof(reply)));
assert(!strcmp(reply,"> advertising"));
}
assert(!meshBleTraceCommand("get ble.trace.advertising extra",reply,sizeof(reply)));
meshBleTraceDispatchEnter(0x10,0);
assert(mesh_ble_trace.dispatch_event.load()==0x10);
meshBleTraceDispatchExit(0x10,0);
@@ -150,6 +158,71 @@ int main() {
result = subprocess.run([str(binary)], capture_output=True, text=True)
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
def test_real_advertising_query_reads_public_payloads_with_31_byte_bounds(self):
source = (ROOT / "src/helpers/nrf52/SerialBLEInterface.cpp").read_text()
start = source.index("void meshBleTraceAdvertising(")
implementation = source[start:source.index("\n}\n", start) + 3]
with tempfile.TemporaryDirectory() as directory:
work = Path(directory)
cpp = work / "advertising.cpp"
cpp.write_text(r'''
#include <cassert>
#include <cstdint>
#include <cstdio>
#include <cstring>
static void* instance;
struct BLEAdvertisingData {
uint8_t length = 0;
uint8_t bytes[31] = {};
uint8_t count() { return length; }
uint8_t* getData() { return bytes; }
};
static struct { BLEAdvertisingData Advertising, ScanResponse; } Bluefruit;
@IMPLEMENTATION@
int main() {
char reply[172];
meshBleTraceAdvertising(reply, sizeof(reply));
assert(!strcmp(reply, "> Bluetooth not initialized"));
instance = reply;
const uint8_t advertising[] = {2, 1, 6, 2, 10, 4};
memcpy(Bluefruit.Advertising.bytes, advertising, sizeof(advertising));
Bluefruit.Advertising.length = sizeof(advertising);
// This is the copied advertising payload, independent of the GAP getter.
const uint8_t scan[] = {5, 9, 'R', 0, 0xc3, 0xa9};
memcpy(Bluefruit.ScanResponse.bytes, scan, sizeof(scan));
Bluefruit.ScanResponse.length = sizeof(scan);
meshBleTraceAdvertising(reply, sizeof(reply));
assert(!strcmp(reply, "> adv_len=6,adv_hex=020106020a04,scan_len=6,scan_hex=05095200c3a9"));
for (unsigned i = 0; i < 31; ++i) {
Bluefruit.Advertising.bytes[i] = i;
Bluefruit.ScanResponse.bytes[i] = 255 - i;
}
Bluefruit.Advertising.length = Bluefruit.ScanResponse.length = 255;
meshBleTraceAdvertising(reply, sizeof(reply));
assert(strlen(reply) < sizeof(reply) - 1);
const char* adv_hex = strstr(reply, "adv_hex=") + 8;
const char* scan_hex = strstr(reply, "scan_hex=") + 9;
assert(strchr(adv_hex, ',') - adv_hex == 62 && strlen(scan_hex) == 62);
assert(!strncmp(reply, "> adv_len=255,", 14));
assert(strstr(reply, ",scan_len=255,"));
char tiny[3] = {};
meshBleTraceAdvertising(tiny, sizeof(tiny));
assert(tiny[2] == 0);
char untouched = 'X';
meshBleTraceAdvertising(&untouched, 0);
assert(untouched == 'X');
}
'''.replace("@IMPLEMENTATION@", implementation))
binary = work / "advertising"
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)