From c69ffb6c4e9ccf7cfc84e1fd513190db1a8a2ebf Mon Sep 17 00:00:00 2001 From: mr-u0b0dy <63730630+mr-u0b0dy@users.noreply.github.com> Date: Wed, 17 Sep 2025 14:29:53 +0530 Subject: [PATCH 1/3] Checkpoint from VS Code for coding agent session --- .github/copilot-instructions.md | 59 +++++++++++++++++++++++++++++++++ app/app.overlay | 9 +++++ app/prj.conf | 7 ++++ app/src/main.c | 45 +++++++++++++++---------- 4 files changed, 102 insertions(+), 18 deletions(-) create mode 100644 .github/copilot-instructions.md diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..e28fd53 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,59 @@ +## ninjaUSB – AI Coding Assistant Instructions + +Goal: Firmware for an nRF52840 dongle acting as a BLE peripheral that receives commands/keystrokes and emits USB HID keyboard reports (future BadUSB extensions). Keep changes minimal, Zephyr‑aligned, and reproducible. + +### Architecture & Flow +1. Entry: `app/src/main.c` – initializes GPIO LED, USB HID device, Bluetooth stack, advertising, event loop. +2. USB: `app/src/usb_init.c` builds descriptors & config via Zephyr NEXT USB stack (`CONFIG_USB_DEVICE_STACK_NEXT`, HID enabled). Placeholders (VID/PID, manufacturer) still marked TODO. +3. HID Device Node: `app/app.overlay` defines `hid_dev_0` (keyboard protocol, 64‑byte IN report, 1 ms poll). Retrieved with `DEVICE_DT_GET_ONE(zephyr_hid_device)`. +4. Reports: Static buffer `report[]` sized by `KB_REPORT_COUNT` indexes (mod key + 6 keycodes). Submission via `hid_device_submit_report(hid_dev, KB_REPORT_COUNT, report)` after readiness flag set in `.iface_ready` callback. +5. Input Path: Zephyr input subsystem callback (`input_cb`) enqueues events into `kb_msgq`; main loop dequeues and mutates `report` based on `INPUT_KEY_*` codes. +6. BLE: Custom 128‑bit service + writable characteristic (`write_command`) in `main.c`; writes copied to `command_buf`. Example: first byte == 1 triggers NumLock key press. +7. Event Loop: Blocks on `k_msgq_get`, updates report, checks `kb_ready`, then sends HID report. + +### Build / Flash Workflow +Build (from repo root inside Zephyr workspace): +``` +west build -b nrf52840dongle/nrf52840 app +``` +Artifacts: `build/zephyr/zephyr.hex`, signed package generated manually. +Flash via serial DFU (adjust `/dev/ttyACM0` as needed): +``` +nrfutil pkg generate --hw-version 52 --sd-req=0x00 \ + --application build/zephyr/zephyr.hex \ + --application-version 1 build/firmware.zip +nrfutil dfu usb-serial -pkg build/firmware.zip -p /dev/ttyACM0 +``` +If USB VBUS detection unsupported, device enabling is forced (`!usbd_can_detect_vbus`). + +### Conventions & Patterns +- CMake: `app/CMakeLists.txt` glob‑adds `src/*.c`; keep new sources in `app/src/` or include headers via `app/inc/` + `zephyr_include_directories(inc)`. +- Configuration split: Feature toggles & stack settings in `app/prj.conf`; only add Zephyr Kconfig symbols actually used. +- Versioning: `app/VERSION` (Zephyr format) – bump when changing externally observable behavior. +- Logging: Use `LOG_MODULE_REGISTER(, LOG_LEVEL_*)`; prefer existing modules (`main`, `usbd_app_config`); avoid printk except for very early BLE messages (some still present). +- HID report editing: Mutate indices defined by `enum kb_report_idx`; always clear released keys (set to 0) to avoid stuck modifiers. +- BLE characteristic writes: Validate length (`len <= sizeof(command_buf)`), return proper ATT error on overflow. Extend action dispatch using first command byte (add `switch` instead of chained `if`). +- USB descriptors: Update constants in `usb_init.c` (`USB_DEVICE_MANUFACTURER`, VID/PID) before distributing hardware; ensure uniqueness. +- Avoid blocking outside of main loop; use Zephyr work (`k_work`) for deferred operations like restarting advertising (`adv_work`). + +### Extending Functionality (Examples) +- Add a new command (e.g., send string): extend `write_command` with command byte map; enqueue synthesized `kb_event`s instead of directly editing `report` for uniformity. +- Add more input sources: register additional `INPUT_CALLBACK_DEFINE` handlers; ensure they produce `INPUT_KEY_*` codes the switch handles. +- Additional keys: Expand switch in main loop; maintain mutual exclusivity if needed (clear prior codes). + +### Gotchas +- Do not exceed `KB_REPORT_COUNT`; HID keyboard standard supports 6 simultaneous keys + modifiers. +- `hid_device_submit_report` requires device readiness (`kb_ready` flag set in `.iface_ready`). Submissions earlier silently fail or log errors. +- VID/PID placeholders must not ship; Nordic examples’ values are not production legal. +- Message queue depth is 2 (`K_MSGQ_DEFINE(kb_msgq, ..., 2, ...)`); burst inputs beyond this are dropped; enlarge if adding rapid input sources. + +### Safe Change Checklist +1. Build succeeds (`west build ...`). +2. USB enumeration intact (keyboard recognized) – changing descriptors can break host detection. +3. BLE advertising still starts after disconnect (`recycled_cb` triggers `advertising_start`). +4. No stuck keys (press/release path clears report indices). + +### Areas Lacking Automation +No unit / integration tests present; validation is manual (USB enumeration + BLE write -> HID action). Keep additions deterministic and log‑rich. + +Feedback Wanted: Clarify more on BLE command protocol structure? Add guidance for future BadUSB scripting layer? Indicate and I’ll refine. diff --git a/app/app.overlay b/app/app.overlay index f65906e..6cfc4ef 100644 --- a/app/app.overlay +++ b/app/app.overlay @@ -1,4 +1,13 @@ / { + chosen { + /* Route Zephyr console/shell over physical UART0 instead of USB CDC ACM */ + zephyr,console = &uart0; + zephyr,shell-uart = &uart0; + zephyr,uart-mcumgr = &uart0; + zephyr,bt-mon-uart = &uart0; + zephyr,bt-c2h-uart = &uart0; + }; + hid_dev_0: hid_dev_0 { compatible = "zephyr,hid-device"; label = "HID0"; diff --git a/app/prj.conf b/app/prj.conf index 38d4e5e..3379176 100644 --- a/app/prj.conf +++ b/app/prj.conf @@ -16,3 +16,10 @@ CONFIG_BT=y CONFIG_BT_PERIPHERAL=y CONFIG_BT_DEVICE_NAME="NinjaUSB" +# Route console and logging to UART +CONFIG_SERIAL=y +CONFIG_CONSOLE=y +CONFIG_UART_CONSOLE=y +CONFIG_LOG_BACKEND_UART=y +CONFIG_LOG_MODE_IMMEDIATE=n + diff --git a/app/src/main.c b/app/src/main.c index a32cdb8..e9fe324 100644 --- a/app/src/main.c +++ b/app/src/main.c @@ -156,43 +156,52 @@ static void msg_cb(struct usbd_context *const usbd_ctx, /* BLE */ /************************************************************************/ -#define BT_UUID_CUSTOM_SERVICE_VAL \ - BT_UUID_128_ENCODE(0x12345678, 0x1234, 0x5678, 0x1234, 0x56789abcdef0) - +/* Switch to standard HID Service UUID (0x1812) for the primary service. */ #define BT_UUID_CMD_CHAR_VAL \ BT_UUID_128_ENCODE(0xabcdef01, 0x2345, 0x6789, 0x2345, 0x6789abcdef01) - -static struct bt_uuid_128 custom_service_uuid = - BT_UUID_INIT_128(BT_UUID_CUSTOM_SERVICE_VAL); static struct bt_uuid_128 cmd_char_uuid = BT_UUID_INIT_128(BT_UUID_CMD_CHAR_VAL); -static uint8_t command_buf[20]; +/* BLE write payload will directly overwrite the HID report buffer ("raw pass-through"). */ ssize_t write_command(struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, uint16_t len, uint16_t offset, uint8_t flags) { - if (len > sizeof(command_buf)) { + ARG_UNUSED(conn); + ARG_UNUSED(attr); + ARG_UNUSED(offset); + ARG_UNUSED(flags); + + /* Expect raw HID keyboard report bytes from BLE. The standard keyboard + * report is KB_REPORT_COUNT bytes (modifier, reserved, 6 keycodes). If + * fewer bytes are provided we zero-fill the remainder. If more are sent + * we reject to avoid accidental overflow / unintended state. + */ + if (len > KB_REPORT_COUNT) { return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); } - memcpy(command_buf, buf, len); - printk("Received command: "); - for (int i = 0; i < len; i++) { - printk("%02x ", command_buf[i]); - } - printk("\n"); + /* Clear existing report then copy provided bytes */ + memset(report, 0, KB_REPORT_COUNT); + memcpy(report, buf, len); - if (command_buf[0] == 1) { - report[KB_KEY_CODE1] = HID_KEY_NUMLOCK; - hid_device_submit_report(hid_dev, KB_REPORT_COUNT, report); + LOG_INF("BLE raw HID report write (%u bytes)", len); + LOG_HEXDUMP_DBG(report, KB_REPORT_COUNT, "hid-rx"); + + if (!kb_ready) { + LOG_WRN("HID interface not ready; dropping report"); + return len; /* Return len so GATT write appears successful */ } + int ret = hid_device_submit_report(hid_dev, KB_REPORT_COUNT, report); + if (ret) { + LOG_ERR("Failed to submit HID report (%d)", ret); + } return len; } BT_GATT_SERVICE_DEFINE( - custom_svc, BT_GATT_PRIMARY_SERVICE(&custom_service_uuid), + custom_svc, BT_GATT_PRIMARY_SERVICE(BT_UUID_HIDS), BT_GATT_CHARACTERISTIC(&cmd_char_uuid.uuid, BT_GATT_CHRC_WRITE, BT_GATT_PERM_WRITE, NULL, write_command, NULL), ); From e0ebe0ce238d72dd35508ee1c5e5f10de4d73a1f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 17 Sep 2025 09:01:38 +0000 Subject: [PATCH 2/3] Initial plan From 3b458404ded67be18640d935f3e3e11d03bd35bb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 17 Sep 2025 09:05:24 +0000 Subject: [PATCH 3/3] Add bonding and pairing support with standard HID UUID advertising Co-authored-by: mr-u0b0dy <63730630+mr-u0b0dy@users.noreply.github.com> --- app/VERSION | 2 +- app/prj.conf | 9 +++++++++ app/src/main.c | 37 +++++++++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/app/VERSION b/app/VERSION index 1457d6a..445af30 100644 --- a/app/VERSION +++ b/app/VERSION @@ -1,5 +1,5 @@ VERSION_MAJOR = 0 -VERSION_MINOR = 1 +VERSION_MINOR = 2 PATCHLEVEL = 0 VERSION_TWEAK = 0 EXTRAVERSION = diff --git a/app/prj.conf b/app/prj.conf index 3379176..43e46ba 100644 --- a/app/prj.conf +++ b/app/prj.conf @@ -16,6 +16,15 @@ CONFIG_BT=y CONFIG_BT_PERIPHERAL=y CONFIG_BT_DEVICE_NAME="NinjaUSB" +# Enable SMP (Security Manager Protocol) for bonding and pairing +CONFIG_BT_SMP=y +CONFIG_BT_BONDABLE=y +CONFIG_BT_KEYS_OVERWRITE_OLDEST=y + +# Enable settings subsystem for persistent bonding storage +CONFIG_SETTINGS=y +CONFIG_BT_SETTINGS=y + # Route console and logging to UART CONFIG_SERIAL=y CONFIG_CONSOLE=y diff --git a/app/src/main.c b/app/src/main.c index e9fe324..f7cb840 100644 --- a/app/src/main.c +++ b/app/src/main.c @@ -17,6 +17,8 @@ #include #include +#include + #include #include @@ -215,6 +217,7 @@ static const struct bt_data ad[] = { BT_DATA_BYTES( BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)), /* Set the advertising flags */ + BT_DATA_BYTES(BT_DATA_UUID16_ALL, 0x12, 0x18), /* Advertise HID Service UUID */ BT_DATA(BT_DATA_NAME_COMPLETE, CONFIG_BT_DEVICE_NAME, sizeof(CONFIG_BT_DEVICE_NAME) - 1), /* Set the advertising packet data */}; @@ -269,6 +272,28 @@ BT_CONN_CB_DEFINE(connection_callbacks) = { .recycled = recycled_cb, }; +/* Authentication callbacks for bonding support */ +static void auth_cancel(struct bt_conn *conn) +{ + LOG_INF("Pairing cancelled"); +} + +static void auth_pairing_complete(struct bt_conn *conn, bool bonded) +{ + LOG_INF("Pairing completed %s", bonded ? "and bonded" : "but not bonded"); +} + +static void auth_pairing_failed(struct bt_conn *conn, enum bt_security_err reason) +{ + LOG_ERR("Pairing failed (reason %d)", reason); +} + +static struct bt_conn_auth_cb auth_cb = { + .cancel = auth_cancel, + .pairing_complete = auth_pairing_complete, + .pairing_failed = auth_pairing_failed, +}; + int main(void) { struct usbd_context *app_usbd; int ret; @@ -335,6 +360,18 @@ int main(void) { return -1; } + /* Load stored settings for bonding */ + ret = settings_load(); + if (ret) { + LOG_WRN("Settings load failed (err %d)", ret); + } + + /* Register authentication callbacks for bonding support */ + ret = bt_conn_auth_cb_register(&auth_cb); + if (ret) { + LOG_ERR("Failed to register auth callbacks (err %d)", ret); + } + /* Start connectable advertising */ k_work_init(&adv_work, adv_work_handler); advertising_start();