From 7f6dc104f004d4da6ada49f3a485edadd62bdb5a Mon Sep 17 00:00:00 2001 From: geeksville Date: Wed, 22 Jul 2020 09:51:57 -0700 Subject: [PATCH] nimble WIP - add advertising boilerplate --- docs/software/TODO.md | 3 +- platformio.ini | 2 +- src/BluetoothCommon.cpp | 12 ++ src/BluetoothCommon.h | 4 + src/esp32/BluetoothUtil.cpp | 351 +++++++++++++++++++++++++++++++++-- src/nimble/NimbleDefs.c | 44 +++++ src/nimble/NimbleDefs.h | 23 +++ src/nrf52/NRF52Bluetooth.cpp | 11 +- 8 files changed, 425 insertions(+), 25 deletions(-) create mode 100644 src/BluetoothCommon.cpp create mode 100644 src/nimble/NimbleDefs.c create mode 100644 src/nimble/NimbleDefs.h diff --git a/docs/software/TODO.md b/docs/software/TODO.md index 3ea06f954..7cac0626e 100644 --- a/docs/software/TODO.md +++ b/docs/software/TODO.md @@ -7,8 +7,9 @@ Nimble tasks: - Nimble getting started https://espressif-esp-idf.readthedocs-hosted.com/zh_CN/release-v3.3/api-reference/bluetooth/nimble/index.html#overview? could it work with arduino esp-idf 4.2 - implement nimble device api - setup advertising https://mynewt.apache.org/latest/tutorials/ble/bleprph/bleprph-sections/bleprph-gap-event.html -- add security +- add security (at least bonding) - test with app +- remove unsecured read/write access - restart advertising after client disconnects - make sleep work - check BLE handle stability diff --git a/platformio.ini b/platformio.ini index c1f0152e3..fe3aed87c 100644 --- a/platformio.ini +++ b/platformio.ini @@ -78,7 +78,7 @@ src_filter = upload_speed = 921600 debug_init_break = tbreak setup build_flags = - ${env.build_flags} -Wall -Wextra -Isrc/esp32 -mfix-esp32-psram-cache-issue -lnimble + ${env.build_flags} -Wall -Wextra -Isrc/esp32 -mfix-esp32-psram-cache-issue -lnimble -std=c++11 # Hmm - this doesn't work yet # board_build.ldscript = linker/esp32.extram.bss.ld lib_ignore = segger_rtt diff --git a/src/BluetoothCommon.cpp b/src/BluetoothCommon.cpp new file mode 100644 index 000000000..a12a52cf9 --- /dev/null +++ b/src/BluetoothCommon.cpp @@ -0,0 +1,12 @@ +#include "BluetoothCommon.h" + +// NRF52 wants these constants as byte arrays +// Generated here https://yupana-engineering.com/online-uuid-to-c-array-converter - but in REVERSE BYTE ORDER +const uint8_t MESH_SERVICE_UUID_16[16u] = {0xfd, 0xea, 0x73, 0xe2, 0xca, 0x5d, 0xa8, 0x9f, + 0x1f, 0x46, 0xa8, 0x15, 0x18, 0xb2, 0xa1, 0x6b}; +const uint8_t TORADIO_UUID_16[16u] = {0xe7, 0x01, 0x44, 0x12, 0x66, 0x78, 0xdd, 0xa1, + 0xad, 0x4d, 0x9e, 0x12, 0xd2, 0x76, 0x5c, 0xf7}; +const uint8_t FROMRADIO_UUID_16[16u] = {0xd5, 0x54, 0xe4, 0xc5, 0x25, 0xc5, 0x31, 0xa5, + 0x55, 0x4a, 0x02, 0xee, 0xc2, 0xbc, 0xa2, 0x8b}; +const uint8_t FROMNUM_UUID_16[16u] = {0x53, 0x44, 0xe3, 0x47, 0x75, 0xaa, 0x70, 0xa6, + 0x66, 0x4f, 0x00, 0xa8, 0x8c, 0xa1, 0x9d, 0xed}; \ No newline at end of file diff --git a/src/BluetoothCommon.h b/src/BluetoothCommon.h index 61a8fe5ea..26c7b2cef 100644 --- a/src/BluetoothCommon.h +++ b/src/BluetoothCommon.h @@ -12,5 +12,9 @@ #define FROMRADIO_UUID "8ba2bcc2-ee02-4a55-a531-c525c5e454d5" #define FROMNUM_UUID "ed9da18c-a800-4f66-a670-aa7547e34453" +// NRF52 wants these constants as byte arrays +// Generated here https://yupana-engineering.com/online-uuid-to-c-array-converter - but in REVERSE BYTE ORDER +extern const uint8_t MESH_SERVICE_UUID_16[], TORADIO_UUID_16[16u], FROMRADIO_UUID_16[], FROMNUM_UUID_16[]; + /// Given a level between 0-100, update the BLE attribute void updateBatteryLevel(uint8_t level); \ No newline at end of file diff --git a/src/esp32/BluetoothUtil.cpp b/src/esp32/BluetoothUtil.cpp index 50c36b347..ab37ceefb 100644 --- a/src/esp32/BluetoothUtil.cpp +++ b/src/esp32/BluetoothUtil.cpp @@ -345,9 +345,37 @@ void reinitBluetooth() #else -#include "esp_nimble_hci.h" -#include "nimble/nimble_port.h" -#include "nimble/nimble_port_freertos.h" +#include "host/util/util.h" +#include "nimble/NimbleDefs.h" +#include "services/gap/ble_svc_gap.h" +#include "services/gatt/ble_svc_gatt.h" + +static uint8_t own_addr_type; + +int toradio_callback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg) +{ + return BLE_ATT_ERR_UNLIKELY; // unimplemented +} + +int fromradio_callback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg) +{ + return BLE_ATT_ERR_UNLIKELY; // unimplemented +} + +int fromnum_callback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg) +{ + return BLE_ATT_ERR_UNLIKELY; // unimplemented +} + +// A C++ version of BLE_UUID128_INIT +#define BLE_UUID128_INIT_CPP(uuid128...) \ + { \ + u : { \ + type: \ + BLE_UUID_TYPE_128 \ + } \ + , value: { uuid128 } \ + } // Force arduino to keep ble data around bool btInUse() @@ -366,12 +394,6 @@ void deinitBLE() // FIXME - do we need to dealloc things? - what needs to stay alive across light sleep? auto ret = nimble_port_stop(); assert(ret == ESP_OK); - - nimble_port_deinit(); // teardown nimble datastructures - nimble_port_freertos_deinit(); // delete the task - - ret = esp_nimble_hci_and_controller_deinit(); - assert(ret == ESP_OK); } void loopBLE() @@ -379,10 +401,275 @@ void loopBLE() // FIXME } +extern "C" void ble_store_config_init(void); + +/// Print a macaddr +static void print_addr(const uint8_t *v) {} + +/** + * Logs information about a connection to the console. + */ +static void print_conn_desc(struct ble_gap_conn_desc *desc) +{ + DEBUG_MSG("handle=%d our_ota_addr_type=%d our_ota_addr=", desc->conn_handle, desc->our_ota_addr.type); + print_addr(desc->our_ota_addr.val); + DEBUG_MSG(" our_id_addr_type=%d our_id_addr=", desc->our_id_addr.type); + print_addr(desc->our_id_addr.val); + DEBUG_MSG(" peer_ota_addr_type=%d peer_ota_addr=", desc->peer_ota_addr.type); + print_addr(desc->peer_ota_addr.val); + DEBUG_MSG(" peer_id_addr_type=%d peer_id_addr=", desc->peer_id_addr.type); + print_addr(desc->peer_id_addr.val); + DEBUG_MSG(" conn_itvl=%d conn_latency=%d supervision_timeout=%d " + "encrypted=%d authenticated=%d bonded=%d\n", + desc->conn_itvl, desc->conn_latency, desc->supervision_timeout, desc->sec_state.encrypted, + desc->sec_state.authenticated, desc->sec_state.bonded); +} + +static void advertise(); + +/** + * The nimble host executes this callback when a GAP event occurs. The + * application associates a GAP event callback with each connection that forms. + * bleprph uses the same callback for all connections. + * + * @param event The type of event being signalled. + * @param ctxt Various information pertaining to the event. + * @param arg Application-specified argument; unused by + * bleprph. + * + * @return 0 if the application successfully handled the + * event; nonzero on failure. The semantics + * of the return code is specific to the + * particular GAP event being signalled. + */ +static int bleprph_gap_event(struct ble_gap_event *event, void *arg) +{ + struct ble_gap_conn_desc desc; + int rc; + + switch (event->type) { + case BLE_GAP_EVENT_CONNECT: + /* A new connection was established or a connection attempt failed. */ + DEBUG_MSG("connection %s; status=%d ", event->connect.status == 0 ? "established" : "failed", event->connect.status); + if (event->connect.status == 0) { + rc = ble_gap_conn_find(event->connect.conn_handle, &desc); + assert(rc == 0); + print_conn_desc(&desc); + } + DEBUG_MSG("\n"); + + if (event->connect.status != 0) { + /* Connection failed; resume advertising. */ + advertise(); + } + return 0; + + case BLE_GAP_EVENT_DISCONNECT: + DEBUG_MSG("disconnect; reason=%d ", event->disconnect.reason); + // bleprph_print_conn_desc(&event->disconnect.conn); + DEBUG_MSG("\n"); + + /* Connection terminated; resume advertising. */ + advertise(); + return 0; + + case BLE_GAP_EVENT_CONN_UPDATE: + /* The central has updated the connection parameters. */ + DEBUG_MSG("connection updated; status=%d ", event->conn_update.status); + rc = ble_gap_conn_find(event->conn_update.conn_handle, &desc); + assert(rc == 0); + // bleprph_print_conn_desc(&desc); + DEBUG_MSG("\n"); + return 0; + + case BLE_GAP_EVENT_ADV_COMPLETE: + DEBUG_MSG("advertise complete; reason=%d", event->adv_complete.reason); + advertise(); + return 0; + + case BLE_GAP_EVENT_ENC_CHANGE: + /* Encryption has been enabled or disabled for this connection. */ + DEBUG_MSG("encryption change event; status=%d ", event->enc_change.status); + rc = ble_gap_conn_find(event->enc_change.conn_handle, &desc); + assert(rc == 0); + // bleprph_print_conn_desc(&desc); + DEBUG_MSG("\n"); + return 0; + + case BLE_GAP_EVENT_SUBSCRIBE: + DEBUG_MSG("subscribe event; conn_handle=%d attr_handle=%d " + "reason=%d prevn=%d curn=%d previ=%d curi=%d\n", + event->subscribe.conn_handle, event->subscribe.attr_handle, event->subscribe.reason, + event->subscribe.prev_notify, event->subscribe.cur_notify, event->subscribe.prev_indicate, + event->subscribe.cur_indicate); + return 0; + + case BLE_GAP_EVENT_MTU: + DEBUG_MSG("mtu update event; conn_handle=%d cid=%d mtu=%d\n", event->mtu.conn_handle, event->mtu.channel_id, + event->mtu.value); + return 0; + + case BLE_GAP_EVENT_REPEAT_PAIRING: + /* We already have a bond with the peer, but it is attempting to + * establish a new secure link. This app sacrifices security for + * convenience: just throw away the old bond and accept the new link. + */ + + /* Delete the old bond. */ + rc = ble_gap_conn_find(event->repeat_pairing.conn_handle, &desc); + assert(rc == 0); + ble_store_util_delete_peer(&desc.peer_id_addr); + + /* Return BLE_GAP_REPEAT_PAIRING_RETRY to indicate that the host should + * continue with the pairing operation. + */ + return BLE_GAP_REPEAT_PAIRING_RETRY; + + case BLE_GAP_EVENT_PASSKEY_ACTION: + DEBUG_MSG("PASSKEY_ACTION_EVENT started \n"); + struct ble_sm_io pkey = {0}; + + if (event->passkey.params.action == BLE_SM_IOACT_DISP) { + pkey.action = event->passkey.params.action; + pkey.passkey = 123456; // This is the passkey to be entered on peer + DEBUG_MSG("Enter passkey %d on the peer side", pkey.passkey); + rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey); + DEBUG_MSG("ble_sm_inject_io result: %d\n", rc); + } else { + DEBUG_MSG("FIXME - unexpected auth type\n"); + } + return 0; + } + + return 0; +} +/** + * Enables advertising with the following parameters: + * o General discoverable mode. + * o Undirected connectable mode. + */ +static void advertise(void) +{ + struct ble_gap_adv_params adv_params; + struct ble_hs_adv_fields fields; + const char *name; + int rc; + + /** + * Set the advertisement data included in our advertisements: + * o Flags (indicates advertisement type and other general info). + * o Advertising tx power. + * o Device name. + * o 16-bit service UUIDs (alert notifications). + */ + + memset(&fields, 0, sizeof fields); + + /* Advertise two flags: + * o Discoverability in forthcoming advertisement (general) + * o BLE-only (BR/EDR unsupported). + */ + fields.flags = BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP; + + /* Indicate that the TX power level field should be included; have the + * stack fill this value automatically. This is done by assigning the + * special value BLE_HS_ADV_TX_PWR_LVL_AUTO. + */ + fields.tx_pwr_lvl_is_present = 1; + fields.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO; + + name = ble_svc_gap_device_name(); + fields.name = (uint8_t *)name; + fields.name_len = strlen(name); + fields.name_is_complete = 1; + + // fields.uuids16 = (ble_uuid16_t[]){BLE_UUID16_INIT(GATT_SVR_SVC_ALERT_UUID)}; + // fields.num_uuids16 = 1; + // fields.uuids16_is_complete = 1; + + rc = ble_gap_adv_set_fields(&fields); + if (rc != 0) { + MODLOG_DFLT(ERROR, "error setting advertisement data; rc=%d\n", rc); + return; + } + + /* Begin advertising. */ + memset(&adv_params, 0, sizeof adv_params); + adv_params.conn_mode = BLE_GAP_CONN_MODE_UND; + adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN; + // FIXME - use RPA for first parameter + rc = ble_gap_adv_start(own_addr_type, NULL, BLE_HS_FOREVER, &adv_params, bleprph_gap_event, NULL); + if (rc != 0) { + MODLOG_DFLT(ERROR, "error enabling advertisement; rc=%d\n", rc); + return; + } +} + +static void bleprph_on_reset(int reason) +{ + DEBUG_MSG("Resetting state; reason=%d\n", reason); +} + +static void bleprph_on_sync(void) +{ + int rc; + + rc = ble_hs_util_ensure_addr(0); + assert(rc == 0); + + /* Figure out address to use while advertising (no privacy for now) */ + rc = ble_hs_id_infer_auto(0, &own_addr_type); + if (rc != 0) { + DEBUG_MSG("error determining address type; rc=%d\n", rc); + return; + } + + /* Printing ADDR */ + uint8_t addr_val[6] = {0}; + rc = ble_hs_id_copy_addr(own_addr_type, addr_val, NULL); + + DEBUG_MSG("Device Address: "); + print_addr(addr_val); + DEBUG_MSG("\n"); + /* Begin advertising. */ + advertise(); +} + static void ble_host_task(void *param) { + DEBUG_MSG("BLE task running\n"); nimble_port_run(); // This function will return only when nimble_port_stop() is executed. - // nimble_port_freertos_deinit(); + + nimble_port_deinit(); // teardown nimble datastructures + nimble_port_freertos_deinit(); // delete the task + + auto ret = esp_nimble_hci_and_controller_deinit(); + assert(ret == ESP_OK); +} + +void gatt_svr_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg) +{ + char buf[BLE_UUID_STR_LEN]; + + switch (ctxt->op) { + case BLE_GATT_REGISTER_OP_SVC: + DEBUG_MSG("registered service %s with handle=%d\n", ble_uuid_to_str(ctxt->svc.svc_def->uuid, buf), ctxt->svc.handle); + break; + + case BLE_GATT_REGISTER_OP_CHR: + DEBUG_MSG("registering characteristic %s with " + "def_handle=%d val_handle=%d\n", + ble_uuid_to_str(ctxt->chr.chr_def->uuid, buf), ctxt->chr.def_handle, ctxt->chr.val_handle); + break; + + case BLE_GATT_REGISTER_OP_DSC: + DEBUG_MSG("registering descriptor %s with handle=%d\n", ble_uuid_to_str(ctxt->dsc.dsc_def->uuid, buf), ctxt->dsc.handle); + break; + + default: + assert(0); + break; + } } // This routine is called multiple times, once each time we come back from sleep @@ -394,13 +681,51 @@ void reinitBluetooth() // FIXME - why didn't this version work? // auto res = esp_nimble_hci_and_controller_init(); auto res = esp_nimble_hci_init(); - DEBUG_MSG("BLE result %d\n", res); + // DEBUG_MSG("BLE result %d\n", res); assert(res == ESP_OK); nimble_port_init(); - // FIXME Initialize the required NimBLE host configuration parameters and callbacks - // Perform application specific tasks / initialization + /* Initialize the NimBLE host configuration. */ + ble_hs_cfg.reset_cb = bleprph_on_reset; + ble_hs_cfg.sync_cb = bleprph_on_sync; + ble_hs_cfg.gatts_register_cb = gatt_svr_register_cb; + ble_hs_cfg.store_status_cb = ble_store_util_status_rr; + + ble_hs_cfg.sm_io_cap = BLE_SM_IO_CAP_DISP_ONLY; +#ifdef CONFIG_EXAMPLE_BONDING + ble_hs_cfg.sm_bonding = 1; +#endif +#ifdef CONFIG_EXAMPLE_MITM + ble_hs_cfg.sm_mitm = 1; +#endif +#ifdef CONFIG_EXAMPLE_USE_SC + ble_hs_cfg.sm_sc = 1; +#else + ble_hs_cfg.sm_sc = 0; +#ifdef CONFIG_EXAMPLE_BONDING + ble_hs_cfg.sm_our_key_dist = 1; + ble_hs_cfg.sm_their_key_dist = 1; +#endif +#endif + + // add standard GAP services + ble_svc_gap_init(); + ble_svc_gatt_init(); + + res = ble_gatts_count_cfg( + gatt_svr_svcs); // assigns handles? see docstring for note about clearing the handle list before calling SLEEP SUPPORT + assert(res == 0); + + res = ble_gatts_add_svcs(gatt_svr_svcs); + assert(res == 0); + + /* Set the default device name. */ + res = ble_svc_gap_device_name_set("nimble-bleprph"); + assert(res == 0); + + /* XXX Need to have template for store */ + ble_store_config_init(); nimble_port_freertos_init(ble_host_task); } diff --git a/src/nimble/NimbleDefs.c b/src/nimble/NimbleDefs.c new file mode 100644 index 000000000..1ce7d8e58 --- /dev/null +++ b/src/nimble/NimbleDefs.c @@ -0,0 +1,44 @@ +#include "NimbleDefs.h" + +// A C++ version of BLE_UUID128_INIT +#define BLE_UUID128_INIT_CPP(uuid128...) \ + { \ + u : { \ + type: \ + BLE_UUID_TYPE_128 \ + } \ + , value: { uuid128 } \ + } + +static const ble_uuid128_t mesh_service_uuid = + BLE_UUID128_INIT(0xfd, 0xea, 0x73, 0xe2, 0xca, 0x5d, 0xa8, 0x9f, 0x1f, 0x46, 0xa8, 0x15, 0x18, 0xb2, 0xa1, 0x6b); + +static const ble_uuid128_t toradio_uuid = + BLE_UUID128_INIT(0xe7, 0x01, 0x44, 0x12, 0x66, 0x78, 0xdd, 0xa1, 0xad, 0x4d, 0x9e, 0x12, 0xd2, 0x76, 0x5c, 0xf7); + +static const ble_uuid128_t fromradio_uuid = + BLE_UUID128_INIT(0xd5, 0x54, 0xe4, 0xc5, 0x25, 0xc5, 0x31, 0xa5, 0x55, 0x4a, 0x02, 0xee, 0xc2, 0xbc, 0xa2, 0x8b); + +static const ble_uuid128_t fromnum_uuid = + BLE_UUID128_INIT(0x53, 0x44, 0xe3, 0x47, 0x75, 0xaa, 0x70, 0xa6, 0x66, 0x4f, 0x00, 0xa8, 0x8c, 0xa1, 0x9d, 0xed); + +const struct ble_gatt_svc_def gatt_svr_svcs[] = { + { + /*** Service: Security test. */ + .type = BLE_GATT_SVC_TYPE_PRIMARY, + .uuid = &mesh_service_uuid.u, + .characteristics = (struct ble_gatt_chr_def[]){{ + /*** Characteristic: Random number generator. */ + .uuid = &toradio_uuid.u, + .access_cb = toradio_callback, + .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_ENC, + }, + { + 0, /* No more characteristics in this service. */ + }}, + }, + + { + 0, /* No more services. */ + }, +}; diff --git a/src/nimble/NimbleDefs.h b/src/nimble/NimbleDefs.h new file mode 100644 index 000000000..e74392c72 --- /dev/null +++ b/src/nimble/NimbleDefs.h @@ -0,0 +1,23 @@ +#pragma once + +#include "esp_nimble_hci.h" +#include "host/ble_hs.h" +#include "host/ble_uuid.h" +#include "nimble/nimble_port.h" +#include "nimble/nimble_port_freertos.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int toradio_callback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg); + +int fromradio_callback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg); + +int fromnum_callback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg); + +extern const struct ble_gatt_svc_def gatt_svr_svcs[]; + +#ifdef __cplusplus +}; +#endif \ No newline at end of file diff --git a/src/nrf52/NRF52Bluetooth.cpp b/src/nrf52/NRF52Bluetooth.cpp index 2ae864aae..9309e645e 100644 --- a/src/nrf52/NRF52Bluetooth.cpp +++ b/src/nrf52/NRF52Bluetooth.cpp @@ -4,16 +4,7 @@ #include "main.h" #include -// NRF52 wants these constants as byte arrays -// Generated here https://yupana-engineering.com/online-uuid-to-c-array-converter - but in REVERSE BYTE ORDER -const uint8_t MESH_SERVICE_UUID_16[16u] = {0xfd, 0xea, 0x73, 0xe2, 0xca, 0x5d, 0xa8, 0x9f, - 0x1f, 0x46, 0xa8, 0x15, 0x18, 0xb2, 0xa1, 0x6b}; -const uint8_t TORADIO_UUID_16[16u] = {0xe7, 0x01, 0x44, 0x12, 0x66, 0x78, 0xdd, 0xa1, - 0xad, 0x4d, 0x9e, 0x12, 0xd2, 0x76, 0x5c, 0xf7}; -const uint8_t FROMRADIO_UUID_16[16u] = {0xd5, 0x54, 0xe4, 0xc5, 0x25, 0xc5, 0x31, 0xa5, - 0x55, 0x4a, 0x02, 0xee, 0xc2, 0xbc, 0xa2, 0x8b}; -const uint8_t FROMNUM_UUID_16[16u] = {0x53, 0x44, 0xe3, 0x47, 0x75, 0xaa, 0x70, 0xa6, - 0x66, 0x4f, 0x00, 0xa8, 0x8c, 0xa1, 0x9d, 0xed}; + static BLEService meshBleService = BLEService(BLEUuid(MESH_SERVICE_UUID_16)); static BLECharacteristic fromNum = BLECharacteristic(BLEUuid(FROMNUM_UUID_16));