From 1e0f21e8f81b62de4b14d138aa3cdb728984c17c Mon Sep 17 00:00:00 2001 From: Noah Pendleton <2538614+noahp@users.noreply.github.com> Date: Wed, 12 Aug 2026 13:23:04 -0400 Subject: [PATCH 1/2] Remove pairing/bonding from the app Adds unnecessary friction to the quickstart flow, remove it with warnings sprinkled about why this is bad practice for real apps. --- CLAUDE.md | 12 +++-- app/prj.conf | 7 ++- app/src/main.c | 97 ++++++++--------------------------------- test/gateway/README.md | 16 +++---- test/gateway/gateway.js | 5 +-- 5 files changed, 41 insertions(+), 96 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index eb94eb1..9771f37 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -61,11 +61,15 @@ merged/signed hex, no DFU zip (OTA is explicitly out of scope). ## App behavior to preserve +- **No pairing/bonding:** `CONFIG_BT_SMP=n` — this sample has no encryption or bonding. A + real product handling sensitive data should set `CONFIG_BT_SMP=y` and use bonding + (`CONFIG_BT_BONDABLE=y`, the default) instead. - **MDS access control:** register `bt_mds_cb` with an `access_enable` callback that gates - MDS access to the secured/connected gateway link (`CONFIG_BT_SMP=y` is required). -- **Heartbeat-on-connect:** in `security_changed`, once the link is secured, call - `memfault_metrics_heartbeat_debug_trigger()` once so the device shows up in Memfault - immediately instead of waiting for the periodic timer. + MDS access to the first connected gateway link (tracked via `mds_conn` in `connected()`, + since there is no security level to check without `CONFIG_BT_SMP`). +- **Heartbeat-on-connect:** in `connected()`, once the gateway link is captured as + `mds_conn`, call `memfault_metrics_heartbeat_debug_trigger()` once so the device shows up + in Memfault immediately instead of waiting for the periodic timer. - **Crash button (demo-only):** map an LBS button to a forced fault (e.g. `k_oops`) to demonstrate a coredump. Comment it clearly as demo-only. - Keep the LBS LED/button behavior so it remains a recognizable LBS device for the guide — diff --git a/app/prj.conf b/app/prj.conf index f72a352..6423867 100644 --- a/app/prj.conf +++ b/app/prj.conf @@ -6,8 +6,11 @@ CONFIG_BT=y CONFIG_BT_PERIPHERAL=y CONFIG_BT_SHELL=y -# MDS access control needs a secured link -CONFIG_BT_SMP=y +# Encryption/pairing/bonding disabled for this sample. MDS access is instead +# gated to the first connected gateway link (see mds_access_enable() in +# main.c). A real product handling sensitive data should set CONFIG_BT_SMP=y +# and use bonding (CONFIG_BT_BONDABLE=y, the default) instead. +CONFIG_BT_SMP=n CONFIG_BT_DEVICE_NAME="Quickstart_Bluetooth" CONFIG_NCS_APPLICATION_BOOT_BANNER_STRING="Quickstart Bluetooth" CONFIG_BT_LBS=y diff --git a/app/src/main.c b/app/src/main.c index 62d4e95..00269b3 100644 --- a/app/src/main.c +++ b/app/src/main.c @@ -8,9 +8,10 @@ * quickstart-bluetooth application. * * The peripheral_lbs button/LED base with Memfault observability over the - * Memfault Diagnostic Service (MDS). The device serves Memfault chunks to a - * secured gateway connection; the gateway performs the HTTPS upload. The device - * never does on-device HTTP/TLS. + * Memfault Diagnostic Service (MDS). The device serves Memfault chunks to the + * first connected gateway; the gateway performs the HTTPS upload. The device + * never does on-device HTTP/TLS. No pairing/encryption (CONFIG_BT_SMP=n). A + * real product handling sensitive data should enable BT_SMP and bonding. * * Controls (nRF54L15 DK): * Button 0 LBS button characteristic (standard LBS — central sees the press) @@ -107,6 +108,17 @@ static void connected(struct bt_conn *conn, uint8_t err) printk("Connected\n"); dk_set_led_on(CON_STATUS_LED); + + /* No pairing/encryption in this sample (CONFIG_BT_SMP=n). The first + * connected central is treated as the MDS-authorized gateway. Capture a + * heartbeat immediately so the device (with its software/hardware + * version and serial) appears in Memfault within seconds instead of + * waiting for the periodic heartbeat timer. + */ + if (!mds_conn) { + mds_conn = conn; + memfault_metrics_heartbeat_debug_trigger(); + } } static void disconnected(struct bt_conn *conn, uint8_t reason) @@ -120,31 +132,6 @@ static void disconnected(struct bt_conn *conn, uint8_t reason) } } -static void security_changed(struct bt_conn *conn, bt_security_t level, enum bt_security_err err) -{ - char addr[BT_ADDR_LE_STR_LEN]; - - bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr)); - - if (err) { - printk("Security failed: %s level %u err %d %s\n", addr, level, err, - bt_security_err_to_str(err)); - return; - } - - printk("Security changed: %s level %u\n", addr, level); - - /* Once the gateway link is secured, mark it as the MDS-authorized - * connection and capture a heartbeat immediately so the device (with its - * software/hardware version and serial) appears in Memfault within - * seconds instead of waiting for the periodic heartbeat timer. - */ - if (level >= BT_SECURITY_L2 && !mds_conn) { - mds_conn = conn; - memfault_metrics_heartbeat_debug_trigger(); - } -} - static void recycled_cb(void) { advertising_start(); @@ -153,46 +140,10 @@ static void recycled_cb(void) BT_CONN_CB_DEFINE(conn_callbacks) = { .connected = connected, .disconnected = disconnected, - .security_changed = security_changed, .recycled = recycled_cb, }; -static void pairing_complete(struct bt_conn *conn, bool bonded) -{ - char addr[BT_ADDR_LE_STR_LEN]; - - bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr)); - printk("Pairing completed: %s, bonded: %d\n", addr, bonded); -} - -static void pairing_failed(struct bt_conn *conn, enum bt_security_err reason) -{ - char addr[BT_ADDR_LE_STR_LEN]; - - bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr)); - printk("Pairing failed conn: %s, reason %d %s\n", addr, reason, - bt_security_err_to_str(reason)); -} - -static void auth_cancel(struct bt_conn *conn) -{ - char addr[BT_ADDR_LE_STR_LEN]; - - bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr)); - printk("Pairing cancelled: %s\n", addr); -} - -/* Just-works pairing (no passkey entry) — the gateway initiates security. */ -static struct bt_conn_auth_cb conn_auth_callbacks = { - .cancel = auth_cancel, -}; - -static struct bt_conn_auth_info_cb conn_auth_info_callbacks = { - .pairing_complete = pairing_complete, - .pairing_failed = pairing_failed, -}; - -/* Gate MDS access to the secured gateway connection only. */ +/* Gate MDS access to the first connected gateway link only. */ static bool mds_access_enable(struct bt_conn *conn) { return (mds_conn && conn == mds_conn); @@ -264,18 +215,6 @@ int main(void) return 0; } - err = bt_conn_auth_cb_register(&conn_auth_callbacks); - if (err) { - printk("Failed to register authorization callbacks (err %d)\n", err); - return 0; - } - - err = bt_conn_auth_info_cb_register(&conn_auth_info_callbacks); - if (err) { - printk("Failed to register authorization info callbacks (err %d)\n", err); - return 0; - } - err = bt_enable(NULL); if (err) { printk("Bluetooth init failed (err %d)\n", err); @@ -285,8 +224,8 @@ int main(void) printk("Bluetooth initialized\n"); #if defined(CONFIG_SETTINGS) - /* Loads the stored Memfault project key (memfault/project_key) and BT - * bonds. The runtime key is applied here at boot, not live. + /* Loads the stored Memfault project key (memfault/project_key). The + * runtime key is applied here at boot, not live. */ settings_load(); diff --git a/test/gateway/README.md b/test/gateway/README.md index 18707a8..10733fc 100644 --- a/test/gateway/README.md +++ b/test/gateway/README.md @@ -3,9 +3,9 @@ A small Node.js gateway that acts as the phone/desktop side of the Memfault Diagnostic Service (MDS) path, so you can verify the `quickstart-bluetooth` firmware end-to-end **without the mobile app**. It connects to the DK over -Bluetooth Low Energy, secures the link, drains Memfault chunks from the MDS -data-export characteristic, and (optionally) forwards them to the Memfault -cloud — exactly what the *nRF Connect for Desktop Quick Start* gateway does. +Bluetooth Low Energy, drains Memfault chunks from the MDS data-export +characteristic, and (optionally) forwards them to the Memfault cloud — +exactly what the *nRF Connect for Desktop Quick Start* gateway does. It is a Node/[`@abandonware/noble`](https://github.com/abandonware/noble) port of Memfault's Web Bluetooth example, . @@ -16,8 +16,8 @@ Memfault's Web Bluetooth example, . - **Node.js 18+** (uses the built-in `fetch`). - A `nrf54l15dk` flashed with this firmware and advertising as `Quickstart_Bluetooth`. - On first run, macOS will ask to grant your terminal **Bluetooth** permission - (System Settings → Privacy & Security → Bluetooth). The just-works pairing the - firmware uses is handled automatically by CoreBluetooth — no manual dialog. + (System Settings → Privacy & Security → Bluetooth). The firmware does not use + pairing/encryption, so there's no pairing dialog to handle. ## Install @@ -37,8 +37,8 @@ npm run discover # or: node discover.js ## Run the gateway -**Dry run (default) — BLE only, no upload.** Connects, secures, and hexdumps the -drained chunks so you can confirm streaming works without touching the cloud: +**Dry run (default) — BLE only, no upload.** Connects and hexdumps the drained +chunks so you can confirm streaming works without touching the cloud: ```sh npm run gateway # or: node gateway.js @@ -62,7 +62,7 @@ node gateway.js --upload ## Exercising a coredump -1. Run the gateway once so the device is known/paired. +1. Run the gateway once to confirm connectivity. 2. Press **Button 2** on the DK (the demo crash → `k_oops`; on a `0`-labelled DK silkscreen this is the button labelled **`1`**). The device captures a RAM-backed coredump and reboots. Don't power-cycle — a RAM-backed coredump diff --git a/test/gateway/gateway.js b/test/gateway/gateway.js index bc0a53f..888b379 100644 --- a/test/gateway/gateway.js +++ b/test/gateway/gateway.js @@ -9,7 +9,7 @@ * Node port of memfault/web-ble-example's mds.js, using @abandonware/noble. * Upstream reference: https://github.com/memfault/web-ble-example * - * Default: BLE-only dry run (connect, secure, drain chunks, hexdump) — NO upload. + * Default: BLE-only dry run (connect, drain chunks, hexdump) — NO upload. * Pass --upload to actually POST chunks to the Memfault data URI read from the device. * * Usage: node gateway.js [--upload] [--name Quickstart_Bluetooth] [--seconds 30] @@ -126,8 +126,7 @@ async function run(peripheral) { ); const byUuid = Object.fromEntries(characteristics.map((c) => [c.uuid, c])); - // First encrypted read triggers CoreBluetooth pairing/security. - log("Reading supported features (this triggers pairing if not yet secured)…"); + log("Reading supported features…"); const feat = await byUuid[CHAR.supportedFeatures].readAsync(); log(` SupportedFeatures: 0x${feat[0].toString(16)}`); From a5b3875f3e7af32d0f901ca08564cae2d407b1ed Mon Sep 17 00:00:00 2001 From: Noah Pendleton <2538614+noahp@users.noreply.github.com> Date: Thu, 13 Aug 2026 09:26:31 -0400 Subject: [PATCH 2/2] review feedback --- CLAUDE.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index 9771f37..6ca6237 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -63,7 +63,11 @@ merged/signed hex, no DFU zip (OTA is explicitly out of scope). - **No pairing/bonding:** `CONFIG_BT_SMP=n` — this sample has no encryption or bonding. A real product handling sensitive data should set `CONFIG_BT_SMP=y` and use bonding - (`CONFIG_BT_BONDABLE=y`, the default) instead. + (`CONFIG_BT_BONDABLE=y`, the default) instead. Because of this, we can't use the + `CONFIG_BT_MDS_PERM_RW_ENCRYPT` option described in [Restricting Access to + MDS](https://docs.memfault.com/docs/mcu/mds#restricting-access-to-mds) — that requires a + bonded, encrypted link — so this app falls back to the custom `access_enable` callback + from that same doc (see MDS access control below). - **MDS access control:** register `bt_mds_cb` with an `access_enable` callback that gates MDS access to the first connected gateway link (tracked via `mds_conn` in `connected()`, since there is no security level to check without `CONFIG_BT_SMP`).