diff --git a/supervisor/shared/bluetooth/bluetooth.c b/supervisor/shared/bluetooth/bluetooth.c index 74edcbb46bb..15cddd7d28c 100644 --- a/supervisor/shared/bluetooth/bluetooth.c +++ b/supervisor/shared/bluetooth/bluetooth.c @@ -32,11 +32,20 @@ #include "supervisor/shared/bluetooth/serial.h" #endif +// BLE workflow exists if either or both of these are turned on. +#define BLE_WORKFLOW (CIRCUITPY_BLE_FILE_SERVICE || CIRCUITPY_BLE_SERIAL_SERVICE) + +#if BLE_WORKFLOW +#include "shared-bindings/_bleio/Characteristic.h" +#include "shared-bindings/_bleio/Service.h" +#include "shared-bindings/_bleio/UUID.h" +#endif + #if CIRCUITPY_STATUS_BAR #include "supervisor/shared/status_bar.h" #endif -#if (CIRCUITPY_BLE_FILE_SERVICE || CIRCUITPY_BLE_SERIAL_SERVICE || (CIRCUITPY_WEB_WORKFLOW && CIRCUITPY_WIFI)) && CIRCUITPY_SETTINGS_TOML +#if (BLE_WORKFLOW || (CIRCUITPY_WEB_WORKFLOW && CIRCUITPY_WIFI)) && CIRCUITPY_SETTINGS_TOML #include "supervisor/shared/settings.h" #endif @@ -67,7 +76,7 @@ const uint8_t private_advertising_data[] = { // This scan response advertises the full device name (if it fits.) uint8_t circuitpython_scan_response_data[31]; -#if CIRCUITPY_BLE_FILE_SERVICE || CIRCUITPY_BLE_SERIAL_SERVICE +#if BLE_WORKFLOW static bool boot_in_discovery_mode = false; static bool advertising = false; static bool _private_advertising = false; @@ -180,13 +189,13 @@ static void supervisor_bluetooth_start_advertising(void) { advertising = status == 0; } -#endif // CIRCUITPY_BLE_FILE_SERVICE || CIRCUITPY_BLE_SERIAL_SERVICE +#endif // BLE_WORKFLOW #define BLE_DISCOVERY_DATA_GUARD 0xbb0000bb #define BLE_DISCOVERY_DATA_GUARD_MASK 0xff0000ff void supervisor_bluetooth_init(void) { - #if (CIRCUITPY_BLE_FILE_SERVICE || CIRCUITPY_BLE_SERIAL_SERVICE) + #if BLE_WORKFLOW #if CIRCUITPY_SETTINGS_TOML // Check if the user disabled BLE workflow in settings.toml. The default is that it's enabled. @@ -297,7 +306,7 @@ void supervisor_bluetooth_init(void) { } void supervisor_bluetooth_background(void) { - #if CIRCUITPY_BLE_FILE_SERVICE || CIRCUITPY_BLE_SERIAL_SERVICE + #if BLE_WORKFLOW if (!ble_started) { return; } @@ -328,8 +337,21 @@ void supervisor_bluetooth_background(void) { #endif } +#if BLE_WORKFLOW +// adafXXXX-4369-7263-7569-74507974686e, stored little-endian. The XXXX bytes are +// filled in by common_hal_bleio_uuid_construct(). +const uint8_t circuitpython_base_uuid[16] = {0x6e, 0x68, 0x74, 0x79, 0x50, 0x74, 0x69, 0x75, 0x63, 0x72, 0x69, 0x43, 0x00, 0x00, 0xaf, 0xad }; + +static bleio_service_obj_t guard_service; +static bleio_uuid_obj_t guard_service_uuid; +static bleio_characteristic_obj_t guard_characteristic; +static bleio_uuid_obj_t guard_characteristic_uuid; +static mp_obj_list_t guard_characteristic_list; +static mp_obj_t guard_characteristic_list_items[1]; +#endif + void supervisor_start_bluetooth(void) { - #if CIRCUITPY_BLE_FILE_SERVICE || CIRCUITPY_BLE_SERIAL_SERVICE + #if BLE_WORKFLOW if (workflow_state != WORKFLOW_ENABLED || ble_started) { return; @@ -345,6 +367,45 @@ void supervisor_start_bluetooth(void) { supervisor_start_bluetooth_serial(); #endif + // Create an unused service that will be in the GATT table immediately after + // the BLE workflow services. This protects the workflow services from being + // invalidated if user code creates services. + // + // A peripheral reports its last primary service with End Group Handle 0xffff, + // and hosts cache that literally. So a service that user code adds later lands + // inside the cached range of whichever service was last, and hosts treat the + // addition as a change to that service: Chrome invalidates the page's object + // for it (and has no event to report that to JavaScript), and BlueZ drops it + // from its cache entirely. This guard is created last so that it, + // not a workflow service, is the one affected, and file transfer and serial + // keep working. Costs three attribute handles. + guard_service_uuid.base.type = &bleio_uuid_type; + common_hal_bleio_uuid_construct(&guard_service_uuid, 0x0004, circuitpython_base_uuid); + guard_characteristic_list.base.type = &mp_type_list; + guard_characteristic_list.alloc = 1; + guard_characteristic_list.len = 0; + guard_characteristic_list.items = guard_characteristic_list_items; + guard_characteristic_list_items[0] = MP_OBJ_NULL; + guard_service.base.type = &bleio_service_type; + _common_hal_bleio_service_construct(&guard_service, &guard_service_uuid, false /* is secondary */, &guard_characteristic_list); + + // One read-only characteristic: we need at least one because on espressif + // a service is not registered with NimBLE until its first characteristic is added. + guard_characteristic_uuid.base.type = &bleio_uuid_type; + common_hal_bleio_uuid_construct(&guard_characteristic_uuid, 0x0005, circuitpython_base_uuid); + guard_characteristic.base.type = &bleio_characteristic_type; + common_hal_bleio_characteristic_construct(&guard_characteristic, + &guard_service, + 0, // handle (for remote only) + &guard_characteristic_uuid, + CHAR_PROP_READ, + SECURITY_MODE_ENC_NO_MITM, + SECURITY_MODE_NO_ACCESS, + 1, // max length + true, // fixed length + NULL, // no initial value + NULL); + // Mark as started so that the background call does something. ble_started = true; @@ -359,7 +420,7 @@ void supervisor_start_bluetooth(void) { } void supervisor_stop_bluetooth(void) { - #if CIRCUITPY_BLE_FILE_SERVICE || CIRCUITPY_BLE_SERIAL_SERVICE + #if BLE_WORKFLOW if (!ble_started && workflow_state != WORKFLOW_ENABLED) { return; @@ -375,11 +436,15 @@ void supervisor_stop_bluetooth(void) { supervisor_stop_bluetooth_serial(); #endif + // Tear down the guard service too. + common_hal_bleio_characteristic_deinit(&guard_characteristic); + common_hal_bleio_service_deinit(&guard_service); + #endif } void supervisor_bluetooth_enable_workflow(void) { - #if CIRCUITPY_BLE_FILE_SERVICE || CIRCUITPY_BLE_SERIAL_SERVICE + #if BLE_WORKFLOW if (!ble_workflow_setting || workflow_state == WORKFLOW_DISABLED) { return; } @@ -388,13 +453,13 @@ void supervisor_bluetooth_enable_workflow(void) { } void supervisor_bluetooth_disable_workflow(void) { - #if CIRCUITPY_BLE_FILE_SERVICE || CIRCUITPY_BLE_SERIAL_SERVICE + #if BLE_WORKFLOW workflow_state = WORKFLOW_DISABLED; #endif } bool supervisor_bluetooth_workflow_is_enabled(void) { - #if CIRCUITPY_BLE_FILE_SERVICE || CIRCUITPY_BLE_SERIAL_SERVICE + #if BLE_WORKFLOW if (workflow_state == WORKFLOW_ENABLED) { return true; } diff --git a/supervisor/shared/bluetooth/bluetooth.h b/supervisor/shared/bluetooth/bluetooth.h index 14e74a07c73..0050d702e02 100644 --- a/supervisor/shared/bluetooth/bluetooth.h +++ b/supervisor/shared/bluetooth/bluetooth.h @@ -7,6 +7,9 @@ #pragma once #include +#include + +extern const uint8_t circuitpython_base_uuid[16]; void supervisor_bluetooth_background(void); void supervisor_bluetooth_init(void); diff --git a/supervisor/shared/bluetooth/serial.c b/supervisor/shared/bluetooth/serial.c index c57dcdecf3b..037e5e3c7b7 100644 --- a/supervisor/shared/bluetooth/serial.c +++ b/supervisor/shared/bluetooth/serial.c @@ -15,6 +15,7 @@ #include "shared-bindings/_bleio/Service.h" #include "shared-bindings/_bleio/UUID.h" #include "shared-module/storage/__init__.h" +#include "supervisor/shared/bluetooth/bluetooth.h" #include "supervisor/shared/bluetooth/serial.h" #include "common-hal/_bleio/__init__.h" @@ -30,9 +31,6 @@ static bleio_uuid_obj_t supervisor_ble_circuitpython_tx_uuid; static bleio_characteristic_obj_t supervisor_ble_circuitpython_version_characteristic; static bleio_uuid_obj_t supervisor_ble_circuitpython_version_uuid; -// This is the base UUID for the CircuitPython service. -const uint8_t circuitpython_base_uuid[16] = {0x6e, 0x68, 0x74, 0x79, 0x50, 0x74, 0x69, 0x75, 0x63, 0x72, 0x69, 0x43, 0x00, 0x00, 0xaf, 0xad }; - static mp_obj_list_t characteristic_list; static mp_obj_t characteristic_list_items[3];