From 4f07465c03d22549611788f71a21026685b98fb0 Mon Sep 17 00:00:00 2001 From: Mike Mabey Date: Wed, 9 Sep 2026 14:35:13 -0600 Subject: [PATCH 1/4] _bleio: LE Secure Connections numeric-comparison pairing Stock CircuitPython BLE peripherals can only do legacy "Just Works" pairing: nothing surfaces a passkey or numeric-comparison value to Python, and the espressif port additionally rejects *_WITH_MITM characteristic permissions outright. A peripheral with a display and a button (a lock, a medical device) therefore can't require an authenticated bond. Add numeric-comparison pairing to _bleio. NimBLE already ships LE Secure Connections crypto, so the espressif port is a config change rather than a crypto port; nordic, silabs and zephyr-cp get stubs. shared-bindings/_bleio/Connection: - authenticated: True when the link is MITM-protected (numeric comparison or passkey entry completed), not merely encrypted. Ports without an implementation always report False. - pairing_numeric_comparison: the pending 6-digit value, or None. Non-blocking - the peer drives the SM procedure. - confirm_pairing(accept): answer it. Raises ConnectionError / BluetoothError rather than silently doing nothing on misuse. espressif: - The adapter keeps the legacy Just Works defaults (NO_IO, sm_mitm = 0) until a characteristic or descriptor is constructed with a *_WITH_MITM permission, at which point bleio_adapter_enable_mitm_pairing() raises sm_io_cap to DISPLAY_YESNO and sm_mitm to 1. Existing headless peripherals are unaffected. sm_sc is now always 1 (negotiated per pairing, strictly better, and numeric comparison requires it). - Characteristic.c / Descriptor.c stop raising NotImplementedError for *_WITH_MITM and map the MITM / LESC-MITM / SIGNED modes to NimBLE's _AUTHEN flags. - Connection.c: a PAIR_WAITING_NUMCMP state holds the value from BLE_GAP_EVENT_PASSKEY_ACTION; confirm_pairing() injects the answer with ble_sm_inject_io(). A passkey action this device can't service (INPUT / OOB) terminates the link instead of stalling SM. nordic / silabs / zephyr-cp: get_authenticated() returns False; the other two entry points raise NotImplementedError. --- locale/circuitpython.pot | 33 ++++---- ports/espressif/common-hal/_bleio/Adapter.c | 18 ++++- ports/espressif/common-hal/_bleio/Adapter.h | 7 ++ .../common-hal/_bleio/Characteristic.c | 20 +++-- .../espressif/common-hal/_bleio/Connection.c | 64 +++++++++++++++ .../espressif/common-hal/_bleio/Connection.h | 12 +++ .../espressif/common-hal/_bleio/Descriptor.c | 18 +++-- ports/nordic/common-hal/_bleio/Connection.c | 15 ++++ ports/silabs/common-hal/_bleio/Connection.c | 15 ++++ .../zephyr-cp/common-hal/_bleio/Connection.c | 15 ++++ shared-bindings/_bleio/Attribute.h | 4 + shared-bindings/_bleio/Connection.c | 77 +++++++++++++++++++ shared-bindings/_bleio/Connection.h | 3 + shared-module/_bleio/Attribute.c | 11 +++ 14 files changed, 285 insertions(+), 27 deletions(-) diff --git a/locale/circuitpython.pot b/locale/circuitpython.pot index c55efbcf8c1..df114f0adbf 100644 --- a/locale/circuitpython.pot +++ b/locale/circuitpython.pot @@ -1180,11 +1180,6 @@ msgstr "" msgid "Timeout is too long: Maximum timeout length is %d seconds" msgstr "" -#: ports/espressif/common-hal/_bleio/Characteristic.c -#: ports/espressif/common-hal/_bleio/Descriptor.c -msgid "MITM security not supported" -msgstr "" - #: ports/espressif/common-hal/_bleio/Characteristic.c #: ports/nordic/common-hal/_bleio/Characteristic.c #: ports/nordic/common-hal/_bleio/Descriptor.c @@ -1213,6 +1208,20 @@ msgstr "" msgid "Can't set CCCD on local Characteristic" msgstr "" +#: ports/espressif/common-hal/_bleio/Connection.c +#: ports/espressif/common-hal/_bleio/__init__.c +#: ports/nordic/common-hal/_bleio/__init__.c +#: ports/zephyr-cp/common-hal/_bleio/Characteristic.c +#: ports/zephyr-cp/common-hal/_bleio/Connection.c +#: ports/zephyr-cp/common-hal/_bleio/Descriptor.c +#: shared-bindings/_bleio/CharacteristicBuffer.c +msgid "Not connected" +msgstr "" + +#: ports/espressif/common-hal/_bleio/Connection.c +msgid "No pairing in progress" +msgstr "" + #: ports/espressif/common-hal/_bleio/Connection.c #: ports/nordic/common-hal/_bleio/Connection.c msgid "non-UUID found in service_uuids_whitelist" @@ -1246,15 +1255,6 @@ msgstr "" msgid "Invalid BLE parameter" msgstr "" -#: ports/espressif/common-hal/_bleio/__init__.c -#: ports/nordic/common-hal/_bleio/__init__.c -#: ports/zephyr-cp/common-hal/_bleio/Characteristic.c -#: ports/zephyr-cp/common-hal/_bleio/Connection.c -#: ports/zephyr-cp/common-hal/_bleio/Descriptor.c -#: shared-bindings/_bleio/CharacteristicBuffer.c -msgid "Not connected" -msgstr "" - #: ports/espressif/common-hal/_bleio/__init__.c #: shared-module/audiofilewriter/AudioFileWriter.c msgid "Already in progress" @@ -1728,6 +1728,11 @@ msgstr "" msgid "Failed to connect: timeout" msgstr "" +#: ports/nordic/common-hal/_bleio/Connection.c +#: ports/zephyr-cp/common-hal/_bleio/Connection.c +msgid "Numeric comparison pairing" +msgstr "" + #: ports/nordic/common-hal/_bleio/UUID.c msgid "Unexpected nrfx uuid type" msgstr "" diff --git a/ports/espressif/common-hal/_bleio/Adapter.c b/ports/espressif/common-hal/_bleio/Adapter.c index e97c84b9b95..34122a1048c 100644 --- a/ports/espressif/common-hal/_bleio/Adapter.c +++ b/ports/espressif/common-hal/_bleio/Adapter.c @@ -32,6 +32,7 @@ #include "nimble/nimble_port_freertos.h" #include "host/ble_gap.h" #include "host/ble_gatt.h" +#include "host/ble_sm.h" #include "host/util/util.h" #include "services/gap/ble_svc_gap.h" #include "services/gatt/ble_svc_gatt.h" @@ -145,7 +146,13 @@ void common_hal_bleio_adapter_set_enabled(bleio_adapter_obj_t *self, bool enable ble_hs_cfg.sm_their_key_dist |= BLE_SM_PAIR_KEY_DIST_ENC; ble_hs_cfg.sm_mitm = 0; - ble_hs_cfg.sm_sc = 0; + // LE Secure Connections is always offered. It's negotiated per pairing (a peer + // that only does legacy pairing still works) and is strictly better crypto, so + // there's no downside for existing "Just Works" users. Numeric-comparison pairing + // needs it. IO capability and the MITM requirement stay at the legacy defaults + // above until an attribute is constructed with a *_WITH_MITM permission, which + // raises them - see bleio_adapter_enable_mitm_pairing(). + ble_hs_cfg.sm_sc = 1; /* Stores the IRK */ ble_hs_cfg.sm_our_key_dist |= BLE_SM_PAIR_KEY_DIST_ID; ble_hs_cfg.sm_their_key_dist |= BLE_SM_PAIR_KEY_DIST_ID; @@ -197,6 +204,15 @@ bool common_hal_bleio_adapter_get_enabled(bleio_adapter_obj_t *self) { return xTaskGetHandle("nimble_host") != NULL; } +void bleio_adapter_enable_mitm_pairing(void) { + // ble_hs_cfg is a plain global read by the SM code at pairing time, so bumping it + // here (from Characteristic / Descriptor construction, after the adapter is up) takes + // effect for every subsequent pairing. Idempotent and one-way for the lifetime of + // the adapter - a characteristic that needs authentication doesn't stop needing it. + ble_hs_cfg.sm_io_cap = BLE_SM_IO_CAP_DISP_YES_NO; + ble_hs_cfg.sm_mitm = 1; +} + bleio_address_obj_t *common_hal_bleio_adapter_get_address(bleio_adapter_obj_t *self) { uint8_t address_bytes[6]; uint8_t address_type = BLE_ADDR_RANDOM; diff --git a/ports/espressif/common-hal/_bleio/Adapter.h b/ports/espressif/common-hal/_bleio/Adapter.h index 40f24cfcdfe..31bd853f2b4 100644 --- a/ports/espressif/common-hal/_bleio/Adapter.h +++ b/ports/espressif/common-hal/_bleio/Adapter.h @@ -44,3 +44,10 @@ typedef struct { void bleio_adapter_gc_collect(bleio_adapter_obj_t *adapter); void bleio_adapter_reset(bleio_adapter_obj_t *adapter); + +// Raise the SM pairing configuration so an authenticated (MITM-protected) bond can be +// formed: advertise DISPLAY_YESNO IO capability and require MITM. Called from +// Characteristic / Descriptor construction when an attribute is given a *_WITH_MITM +// permission. A no-op-safe idempotent bump; devices with no such attribute keep the +// legacy "Just Works" defaults set in common_hal_bleio_adapter_set_enabled(). +void bleio_adapter_enable_mitm_pairing(void); diff --git a/ports/espressif/common-hal/_bleio/Characteristic.c b/ports/espressif/common-hal/_bleio/Characteristic.c index 9436376a811..6ea78629569 100644 --- a/ports/espressif/common-hal/_bleio/Characteristic.c +++ b/ports/espressif/common-hal/_bleio/Characteristic.c @@ -153,10 +153,6 @@ void common_hal_bleio_characteristic_construct(bleio_characteristic_obj_t *self, if ((props & CHAR_PROP_WRITE_NO_RESPONSE) != 0) { self->flags |= BLE_GATT_CHR_F_WRITE_NO_RSP; } - if (read_perm == SECURITY_MODE_ENC_WITH_MITM || write_perm == SECURITY_MODE_ENC_WITH_MITM || - read_perm == SECURITY_MODE_SIGNED_WITH_MITM || write_perm == SECURITY_MODE_SIGNED_WITH_MITM) { - mp_raise_NotImplementedError(MP_ERROR_TEXT("MITM security not supported")); - } // The BLE_GATT_CHR_F_NOTIFY_INDICATE_* flags are set below to require encryption or // authentication when writing the auto-generated CCCD, if reading the // characteristic requires it. This matches the nordic port behavior. @@ -164,18 +160,30 @@ void common_hal_bleio_characteristic_construct(bleio_characteristic_obj_t *self, // so an unpaired central can subscribe and nothing ever requires it to pair. // // TODO: This behavior was fixed in NimBLE 1.10.0. ESP-IDF 6.0.1 uses a fork of NimBLE. + // + // NimBLE's GATT layer has no dedicated "LESC" or "signed" characteristic flag: + // ENC_WITH_MITM, LESC_ENC_WITH_MITM and the SIGNED modes all map to the _AUTHEN + // flags, i.e. "require a man-in-the-middle-protected link". A *_WITH_MITM permission + // additionally makes the adapter offer numeric-comparison pairing (below) so such a + // link can actually be established. if (read_perm == SECURITY_MODE_ENC_NO_MITM) { self->flags |= BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_NOTIFY_INDICATE_ENC; } - if (read_perm == SECURITY_MODE_SIGNED_NO_MITM) { + if (read_perm == SECURITY_MODE_ENC_WITH_MITM || read_perm == SECURITY_MODE_LESC_ENC_WITH_MITM || + read_perm == SECURITY_MODE_SIGNED_NO_MITM || read_perm == SECURITY_MODE_SIGNED_WITH_MITM) { self->flags |= BLE_GATT_CHR_F_READ_AUTHEN | BLE_GATT_CHR_F_NOTIFY_INDICATE_AUTHEN; } if (write_perm == SECURITY_MODE_ENC_NO_MITM) { self->flags |= BLE_GATT_CHR_F_WRITE_ENC; } - if (write_perm == SECURITY_MODE_SIGNED_NO_MITM) { + if (write_perm == SECURITY_MODE_ENC_WITH_MITM || write_perm == SECURITY_MODE_LESC_ENC_WITH_MITM || + write_perm == SECURITY_MODE_SIGNED_NO_MITM || write_perm == SECURITY_MODE_SIGNED_WITH_MITM) { self->flags |= BLE_GATT_CHR_F_WRITE_AUTHEN; } + if (bleio_attribute_security_mode_requires_mitm(read_perm) || + bleio_attribute_security_mode_requires_mitm(write_perm)) { + bleio_adapter_enable_mitm_pairing(); + } // If max_length is 0, then no storage is allocated. if (max_length > 0) { diff --git a/ports/espressif/common-hal/_bleio/Connection.c b/ports/espressif/common-hal/_bleio/Connection.c index a063f3ee1a8..e5db2e1b4a5 100644 --- a/ports/espressif/common-hal/_bleio/Connection.c +++ b/ports/espressif/common-hal/_bleio/Connection.c @@ -34,6 +34,7 @@ #include "freertos/queue.h" #include "host/ble_att.h" +#include "host/ble_sm.h" #include "host/ble_store.h" // Uncomment to turn on debug logging just in this file. @@ -47,6 +48,7 @@ int bleio_connection_event_cb(struct ble_gap_event *event, void *connection_in) case BLE_GAP_EVENT_DISCONNECT: { connection->conn_handle = BLEIO_HANDLE_INVALID; connection->pair_status = PAIR_NOT_PAIRED; + connection->mitm_protected = false; #if CIRCUITPY_VERBOSE_BLE mp_printf(&mp_plat_print, "event->disconnect.reason: 0x%x\n", event->disconnect.reason); @@ -79,6 +81,34 @@ int bleio_connection_event_cb(struct ble_gap_event *event, void *connection_in) ble_gap_conn_find(event->enc_change.conn_handle, &desc); if (desc.sec_state.encrypted) { connection->pair_status = PAIR_PAIRED; + } else if (connection->pair_status == PAIR_WAITING_NUMCMP) { + // Numeric comparison was rejected, or SM otherwise failed. + connection->pair_status = PAIR_NOT_PAIRED; + } + // .authenticated is set once the MITM leg (numeric comparison / passkey entry) + // completes; a plain "Just Works" bond leaves it clear. Surfaced as + // Connection.authenticated. + connection->mitm_protected = desc.sec_state.authenticated; + break; + } + case BLE_GAP_EVENT_PASSKEY_ACTION: { + // The peer started MITM pairing. An attribute was constructed with a + // *_WITH_MITM permission, which made the adapter advertise DISPLAY_YESNO IO + // capability (bleio_adapter_enable_mitm_pairing()), so NimBLE asks us to + // confirm a 6-digit numeric-comparison value. Stash it and enter + // PAIR_WAITING_NUMCMP; Python reads it via Connection.pairing_numeric_comparison + // and answers with confirm_pairing(). + if (event->passkey.params.action == BLE_SM_IOACT_NUMCMP) { + connection->pairing_numcmp = event->passkey.params.numcmp; + connection->pair_status = PAIR_WAITING_NUMCMP; + } else { + // Passkey entry / OOB: a display + yes/no button can't service these. + // Fail fast instead of letting SM hang until its ~30 s timeout. + #if CIRCUITPY_VERBOSE_BLE + mp_printf(&mp_plat_print, "unsupported passkey action %d; terminating\n", + event->passkey.params.action); + #endif + ble_gap_terminate(event->passkey.conn_handle, BLE_ERR_AUTH_FAIL); } break; } @@ -167,6 +197,40 @@ void common_hal_bleio_connection_pair(bleio_connection_internal_t *self, bool bo } } +// LE Secure Connections numeric comparison. Unlike pair(), these don't block: the peer +// (a central) drives the SM procedure; we just observe the pending value and inject the +// yes/no. BLE_GAP_EVENT_PASSKEY_ACTION sets pair_status = PAIR_WAITING_NUMCMP. +mp_obj_t common_hal_bleio_connection_get_pairing_numeric_comparison(bleio_connection_obj_t *self) { + if (self->connection == NULL || self->connection->pair_status != PAIR_WAITING_NUMCMP) { + return mp_const_none; + } + return mp_obj_new_int_from_uint(self->connection->pairing_numcmp); +} + +void common_hal_bleio_connection_confirm_pairing(bleio_connection_obj_t *self, bool accept) { + if (self->connection == NULL) { + mp_raise_ConnectionError(MP_ERROR_TEXT("Not connected")); + } + if (self->connection->pair_status != PAIR_WAITING_NUMCMP) { + mp_raise_bleio_BluetoothError(MP_ERROR_TEXT("No pairing in progress")); + } + struct ble_sm_io io = { + .action = BLE_SM_IOACT_NUMCMP, + .numcmp_accept = accept, + }; + CHECK_NIMBLE_ERROR(ble_sm_inject_io(self->connection->conn_handle, &io)); + // SM proceeds from here. On accept, ENC_CHANGE moves us to PAIR_PAIRED; on reject it + // won't, so drop straight back to PAIR_NOT_PAIRED rather than waiting for the peer. + self->connection->pair_status = accept ? PAIR_WAITING : PAIR_NOT_PAIRED; +} + +bool common_hal_bleio_connection_get_authenticated(bleio_connection_obj_t *self) { + if (self->connection == NULL) { + return false; + } + return self->connection->mitm_protected; +} + mp_float_t common_hal_bleio_connection_get_connection_interval(bleio_connection_internal_t *self) { while (self->conn_params_updating && !mp_hal_is_interrupted()) { RUN_BACKGROUND_TASKS; diff --git a/ports/espressif/common-hal/_bleio/Connection.h b/ports/espressif/common-hal/_bleio/Connection.h index 8d8e727e3da..af6a46866ee 100644 --- a/ports/espressif/common-hal/_bleio/Connection.h +++ b/ports/espressif/common-hal/_bleio/Connection.h @@ -22,6 +22,11 @@ typedef enum { PAIR_NOT_PAIRED, PAIR_WAITING, + // The peer is running LE Secure Connections numeric-comparison pairing and NimBLE is + // waiting for us to confirm the 6-digit value. Python reads it from + // Connection.pairing_numeric_comparison and answers with confirm_pairing(). This is a + // peer-driven, non-blocking path; the blocking pair() does not use it. + PAIR_WAITING_NUMCMP, PAIR_PAIRED, } pair_status_t; @@ -50,6 +55,13 @@ typedef struct { uint16_t ediv; volatile pair_status_t pair_status; uint8_t sec_status; // Internal security status. + // The 6-digit LE Secure Connections numeric-comparison value stashed by + // BLE_GAP_EVENT_PASSKEY_ACTION. Only valid while pair_status == PAIR_WAITING_NUMCMP. + volatile uint32_t pairing_numcmp; + // True once the link is encrypted AND authenticated against MITM (numeric comparison + // or passkey entry completed). A plain encrypted "Just Works" link leaves this false. + // Surfaced to Python as Connection.authenticated. + volatile bool mitm_protected; mp_obj_t connection_obj; volatile bool conn_params_updating; uint16_t mtu; diff --git a/ports/espressif/common-hal/_bleio/Descriptor.c b/ports/espressif/common-hal/_bleio/Descriptor.c index 3f2870740f1..617e862206f 100644 --- a/ports/espressif/common-hal/_bleio/Descriptor.c +++ b/ports/espressif/common-hal/_bleio/Descriptor.c @@ -15,6 +15,8 @@ #include "shared-bindings/_bleio/Service.h" #include "shared-bindings/_bleio/UUID.h" +#include "common-hal/_bleio/Adapter.h" + #include "host/ble_att.h" void common_hal_bleio_descriptor_construct(bleio_descriptor_obj_t *self, bleio_characteristic_obj_t *characteristic, bleio_uuid_obj_t *uuid, bleio_attribute_security_mode_t read_perm, bleio_attribute_security_mode_t write_perm, mp_int_t max_length, bool fixed_length, mp_buffer_info_t *initial_value_bufinfo) { @@ -33,22 +35,26 @@ void common_hal_bleio_descriptor_construct(bleio_descriptor_obj_t *self, bleio_c if (write_perm != SECURITY_MODE_NO_ACCESS) { self->flags |= BLE_ATT_F_WRITE; } - if (read_perm == SECURITY_MODE_ENC_WITH_MITM || write_perm == SECURITY_MODE_ENC_WITH_MITM || - read_perm == SECURITY_MODE_SIGNED_WITH_MITM || write_perm == SECURITY_MODE_SIGNED_WITH_MITM) { - mp_raise_NotImplementedError(MP_ERROR_TEXT("MITM security not supported")); - } + // MITM / LESC-MITM / SIGNED all map to the _AUTHEN flags (require an authenticated + // link); see the matching block in Characteristic.c. if (read_perm == SECURITY_MODE_ENC_NO_MITM) { self->flags |= BLE_ATT_F_READ_ENC; } - if (read_perm == SECURITY_MODE_SIGNED_NO_MITM) { + if (read_perm == SECURITY_MODE_ENC_WITH_MITM || read_perm == SECURITY_MODE_LESC_ENC_WITH_MITM || + read_perm == SECURITY_MODE_SIGNED_NO_MITM || read_perm == SECURITY_MODE_SIGNED_WITH_MITM) { self->flags |= BLE_ATT_F_READ_AUTHEN; } if (write_perm == SECURITY_MODE_ENC_NO_MITM) { self->flags |= BLE_ATT_F_WRITE_ENC; } - if (write_perm == SECURITY_MODE_SIGNED_NO_MITM) { + if (write_perm == SECURITY_MODE_ENC_WITH_MITM || write_perm == SECURITY_MODE_LESC_ENC_WITH_MITM || + write_perm == SECURITY_MODE_SIGNED_NO_MITM || write_perm == SECURITY_MODE_SIGNED_WITH_MITM) { self->flags |= BLE_ATT_F_WRITE_AUTHEN; } + if (bleio_attribute_security_mode_requires_mitm(read_perm) || + bleio_attribute_security_mode_requires_mitm(write_perm)) { + bleio_adapter_enable_mitm_pairing(); + } const mp_int_t max_length_max = BLE_ATT_ATTR_MAX_LEN; if (max_length < 0 || max_length > max_length_max) { diff --git a/ports/nordic/common-hal/_bleio/Connection.c b/ports/nordic/common-hal/_bleio/Connection.c index fbd3263b725..2fef2e1962b 100644 --- a/ports/nordic/common-hal/_bleio/Connection.c +++ b/ports/nordic/common-hal/_bleio/Connection.c @@ -332,6 +332,21 @@ bool common_hal_bleio_connection_get_paired(bleio_connection_obj_t *self) { return self->connection->pair_status == PAIR_PAIRED; } +// Authenticated (MITM-protected) pairing - numeric comparison - is implemented on the +// espressif port only. This port can't tell a "Just Works" bond from an authenticated +// one, so report the conservative answer rather than a guarantee it can't make. +bool common_hal_bleio_connection_get_authenticated(bleio_connection_obj_t *self) { + return false; +} + +mp_obj_t common_hal_bleio_connection_get_pairing_numeric_comparison(bleio_connection_obj_t *self) { + return mp_const_none; +} + +void common_hal_bleio_connection_confirm_pairing(bleio_connection_obj_t *self, bool accept) { + mp_raise_NotImplementedError(MP_ERROR_TEXT("Numeric comparison pairing")); +} + bool common_hal_bleio_connection_get_connected(bleio_connection_obj_t *self) { if (self->connection == NULL) { return false; diff --git a/ports/silabs/common-hal/_bleio/Connection.c b/ports/silabs/common-hal/_bleio/Connection.c index 94e556dd0bf..15a42a36b95 100644 --- a/ports/silabs/common-hal/_bleio/Connection.c +++ b/ports/silabs/common-hal/_bleio/Connection.c @@ -57,6 +57,21 @@ bool common_hal_bleio_connection_get_paired(bleio_connection_obj_t *self) { return self->connection->pair_status == PAIR_PAIRED; } +// Authenticated (MITM-protected) pairing - numeric comparison - is implemented on the +// espressif port only. This port can't tell a "Just Works" bond from an authenticated +// one, so report the conservative answer rather than a guarantee it can't make. +bool common_hal_bleio_connection_get_authenticated(bleio_connection_obj_t *self) { + return false; +} + +mp_obj_t common_hal_bleio_connection_get_pairing_numeric_comparison(bleio_connection_obj_t *self) { + return mp_const_none; +} + +void common_hal_bleio_connection_confirm_pairing(bleio_connection_obj_t *self, bool accept) { + mp_raise_NotImplementedError(MP_ERROR_TEXT("Numeric comparison pairing")); +} + // Get connected status bool common_hal_bleio_connection_get_connected(bleio_connection_obj_t *self) { if (self->connection == NULL) { diff --git a/ports/zephyr-cp/common-hal/_bleio/Connection.c b/ports/zephyr-cp/common-hal/_bleio/Connection.c index 77942728a5f..75266fb9787 100644 --- a/ports/zephyr-cp/common-hal/_bleio/Connection.c +++ b/ports/zephyr-cp/common-hal/_bleio/Connection.c @@ -577,6 +577,21 @@ bool common_hal_bleio_connection_get_paired(bleio_connection_obj_t *self) { return self->connection->pair_status == PAIR_PAIRED; } +// Authenticated (MITM-protected) pairing - numeric comparison - is implemented on the +// espressif port only. This port can't tell a "Just Works" bond from an authenticated +// one, so report the conservative answer rather than a guarantee it can't make. +bool common_hal_bleio_connection_get_authenticated(bleio_connection_obj_t *self) { + return false; +} + +mp_obj_t common_hal_bleio_connection_get_pairing_numeric_comparison(bleio_connection_obj_t *self) { + return mp_const_none; +} + +void common_hal_bleio_connection_confirm_pairing(bleio_connection_obj_t *self, bool accept) { + mp_raise_NotImplementedError(MP_ERROR_TEXT("Numeric comparison pairing")); +} + mp_obj_tuple_t *common_hal_bleio_connection_discover_remote_services(bleio_connection_obj_t *self, mp_obj_t service_uuids_whitelist) { bleio_connection_internal_t *connection = self->connection; if (connection == NULL || connection->conn == NULL) { diff --git a/shared-bindings/_bleio/Attribute.h b/shared-bindings/_bleio/Attribute.h index 51278dc0157..60a941a5772 100644 --- a/shared-bindings/_bleio/Attribute.h +++ b/shared-bindings/_bleio/Attribute.h @@ -14,3 +14,7 @@ extern const mp_obj_type_t bleio_attribute_type; extern void common_hal_bleio_attribute_security_mode_check_valid(bleio_attribute_security_mode_t security_mode); + +// True for the security modes that demand a man-in-the-middle-protected (authenticated) +// link: pairing must use numeric comparison or passkey entry, not "Just Works". +extern bool bleio_attribute_security_mode_requires_mitm(bleio_attribute_security_mode_t security_mode); diff --git a/shared-bindings/_bleio/Connection.c b/shared-bindings/_bleio/Connection.c index 4a81d753433..fe2b844488c 100644 --- a/shared-bindings/_bleio/Connection.c +++ b/shared-bindings/_bleio/Connection.c @@ -165,6 +165,80 @@ MP_PROPERTY_GETTER(bleio_connection_paired_obj, (mp_obj_t)&bleio_connection_get_paired_obj); +//| authenticated: bool +//| """True if the link is encrypted *and* authenticated against +//| man-in-the-middle attacks - i.e. LE Secure Connections numeric comparison (or +//| passkey entry) completed. A plain encrypted-but-unauthenticated ("Just Works") +//| link reads ``False``. +//| +//| Ports that don't implement authenticated pairing always report ``False``, +//| even for a bonded link.""" +static mp_obj_t bleio_connection_get_authenticated(mp_obj_t self_in) { + bleio_connection_obj_t *self = MP_OBJ_TO_PTR(self_in); + + return mp_obj_new_bool(common_hal_bleio_connection_get_authenticated(self)); +} +static MP_DEFINE_CONST_FUN_OBJ_1(bleio_connection_get_authenticated_obj, bleio_connection_get_authenticated); + +MP_PROPERTY_GETTER(bleio_connection_authenticated_obj, + (mp_obj_t)&bleio_connection_get_authenticated_obj); + + +//| pairing_numeric_comparison: Optional[int] +//| """When the peer has started LE Secure Connections pairing and is waiting for the +//| user to confirm a 6-digit numeric-comparison value, this is that value (an ``int``, +//| 0-999999); otherwise ``None``. Poll it, show the value to the user, then answer +//| with `confirm_pairing`. This is the peripheral side of pairing: the peer drives +//| the procedure, so unlike `pair` this never blocks. +//| +//| Numeric comparison happens automatically when a central pairs to reach a +//| characteristic whose ``read_perm`` / ``write_perm`` is +//| `Attribute.LESC_ENCRYPT_WITH_MITM`. A peripheral with a display and a button +//| services it roughly like this:: +//| +//| while connection.connected: +//| code = connection.pairing_numeric_comparison +//| if code is not None: +//| display.show(f"Pair? {code:06d}") +//| connection.confirm_pairing(accept=button_a.value) +//| if connection.authenticated: +//| break +//| time.sleep(0.1) +//| +//| Only implemented on the espressif port.""" +static mp_obj_t bleio_connection_get_pairing_numeric_comparison(mp_obj_t self_in) { + bleio_connection_obj_t *self = MP_OBJ_TO_PTR(self_in); + + return common_hal_bleio_connection_get_pairing_numeric_comparison(self); +} +static MP_DEFINE_CONST_FUN_OBJ_1(bleio_connection_get_pairing_numeric_comparison_obj, bleio_connection_get_pairing_numeric_comparison); + +MP_PROPERTY_GETTER(bleio_connection_pairing_numeric_comparison_obj, + (mp_obj_t)&bleio_connection_get_pairing_numeric_comparison_obj); + + +//| def confirm_pairing(self, accept: bool) -> None: +//| """Answer a pending numeric-comparison pairing request (see +//| `pairing_numeric_comparison`). Pass ``accept=True`` if the value shown on the +//| peer matches the one shown here, ``accept=False`` to reject and abort pairing. +//| +//| Only implemented on the espressif port. +//| +//| :raises ConnectionError: if the connection has dropped. +//| :raises _bleio.BluetoothError: if no numeric-comparison request is pending - +//| nothing was offered on `pairing_numeric_comparison`, or it already expired +//| because the peer disconnected or the pairing procedure timed out.""" +//| ... +//| +static mp_obj_t bleio_connection_confirm_pairing(mp_obj_t self_in, mp_obj_t accept_in) { + bleio_connection_obj_t *self = MP_OBJ_TO_PTR(self_in); + + common_hal_bleio_connection_confirm_pairing(self, mp_obj_is_true(accept_in)); + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_2(bleio_connection_confirm_pairing_obj, bleio_connection_confirm_pairing); + + //| connection_interval: float //| """Time between transmissions in milliseconds. Will be multiple of 1.25ms. Lower numbers //| increase speed and decrease latency but increase power consumption. @@ -223,12 +297,15 @@ MP_PROPERTY_GETTER(bleio_connection_max_packet_length_obj, static const mp_rom_map_elem_t bleio_connection_locals_dict_table[] = { // Methods { MP_ROM_QSTR(MP_QSTR_pair), MP_ROM_PTR(&bleio_connection_pair_obj) }, + { MP_ROM_QSTR(MP_QSTR_confirm_pairing), MP_ROM_PTR(&bleio_connection_confirm_pairing_obj) }, { MP_ROM_QSTR(MP_QSTR_disconnect), MP_ROM_PTR(&bleio_connection_disconnect_obj) }, { MP_ROM_QSTR(MP_QSTR_discover_remote_services), MP_ROM_PTR(&bleio_connection_discover_remote_services_obj) }, // Properties { MP_ROM_QSTR(MP_QSTR_connected), MP_ROM_PTR(&bleio_connection_connected_obj) }, { MP_ROM_QSTR(MP_QSTR_paired), MP_ROM_PTR(&bleio_connection_paired_obj) }, + { MP_ROM_QSTR(MP_QSTR_authenticated), MP_ROM_PTR(&bleio_connection_authenticated_obj) }, + { MP_ROM_QSTR(MP_QSTR_pairing_numeric_comparison), MP_ROM_PTR(&bleio_connection_pairing_numeric_comparison_obj) }, { MP_ROM_QSTR(MP_QSTR_connection_interval), MP_ROM_PTR(&bleio_connection_connection_interval_obj) }, { MP_ROM_QSTR(MP_QSTR_max_packet_length), MP_ROM_PTR(&bleio_connection_max_packet_length_obj) }, }; diff --git a/shared-bindings/_bleio/Connection.h b/shared-bindings/_bleio/Connection.h index 4be4992fb2f..4124eb4a7a9 100644 --- a/shared-bindings/_bleio/Connection.h +++ b/shared-bindings/_bleio/Connection.h @@ -18,6 +18,9 @@ void common_hal_bleio_connection_disconnect(bleio_connection_internal_t *self); bool common_hal_bleio_connection_get_connected(bleio_connection_obj_t *self); mp_int_t common_hal_bleio_connection_get_max_packet_length(bleio_connection_internal_t *self); bool common_hal_bleio_connection_get_paired(bleio_connection_obj_t *self); +bool common_hal_bleio_connection_get_authenticated(bleio_connection_obj_t *self); +mp_obj_t common_hal_bleio_connection_get_pairing_numeric_comparison(bleio_connection_obj_t *self); +void common_hal_bleio_connection_confirm_pairing(bleio_connection_obj_t *self, bool accept); mp_obj_tuple_t *common_hal_bleio_connection_discover_remote_services(bleio_connection_obj_t *self, mp_obj_t service_uuids_whitelist); mp_float_t common_hal_bleio_connection_get_connection_interval(bleio_connection_internal_t *self); diff --git a/shared-module/_bleio/Attribute.c b/shared-module/_bleio/Attribute.c index b12f2b121de..cfc5999df1e 100644 --- a/shared-module/_bleio/Attribute.c +++ b/shared-module/_bleio/Attribute.c @@ -24,3 +24,14 @@ void common_hal_bleio_attribute_security_mode_check_valid(bleio_attribute_securi break; } } + +bool bleio_attribute_security_mode_requires_mitm(bleio_attribute_security_mode_t security_mode) { + switch (security_mode) { + case SECURITY_MODE_ENC_WITH_MITM: + case SECURITY_MODE_LESC_ENC_WITH_MITM: + case SECURITY_MODE_SIGNED_WITH_MITM: + return true; + default: + return false; + } +} From c32803c836bc9da3c7426b8ced1c5182976fa25b Mon Sep 17 00:00:00 2001 From: Mike Mabey Date: Fri, 11 Sep 2026 10:23:52 -0600 Subject: [PATCH 2/4] _bleio: add ble_hci stubs for numeric-comparison pairing API Ports using devices/ble_hci/common-hal/_bleio (atmel-samd samd51, mimxrt10xx, raspberrypi, stm, broadcom, cxd56) were missing common_hal_bleio_connection_get_authenticated, common_hal_bleio_connection_get_pairing_numeric_comparison, and common_hal_bleio_connection_confirm_pairing, causing link failures. Add the same not-implemented stubs already used by nordic, silabs, and zephyr-cp. --- devices/ble_hci/common-hal/_bleio/Connection.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/devices/ble_hci/common-hal/_bleio/Connection.c b/devices/ble_hci/common-hal/_bleio/Connection.c index 30ed0af575c..605e71147db 100644 --- a/devices/ble_hci/common-hal/_bleio/Connection.c +++ b/devices/ble_hci/common-hal/_bleio/Connection.c @@ -323,6 +323,18 @@ bool common_hal_bleio_connection_get_connected(bleio_connection_obj_t *self) { return self->connection->conn_handle != BLE_CONN_HANDLE_INVALID; } +bool common_hal_bleio_connection_get_authenticated(bleio_connection_obj_t *self) { + return false; +} + +mp_obj_t common_hal_bleio_connection_get_pairing_numeric_comparison(bleio_connection_obj_t *self) { + return mp_const_none; +} + +void common_hal_bleio_connection_confirm_pairing(bleio_connection_obj_t *self, bool accept) { + mp_raise_NotImplementedError(MP_ERROR_TEXT("Numeric comparison pairing")); +} + void common_hal_bleio_connection_disconnect(bleio_connection_internal_t *self) { hci_disconnect(self->conn_handle); } From 7f59c22b3d546ebaaaa5541efa4077ee3b04225f Mon Sep 17 00:00:00 2001 From: Mike Mabey Date: Fri, 11 Sep 2026 15:27:04 -0600 Subject: [PATCH 3/4] _bleio: address review feedback on numeric-comparison pairing tannewt reviewed PR #11340 and asked for a few changes: - Rename pairing_numeric_comparison to numeric_comparison, since the pairing_ prefix was redundant on a Connection property already about pairing. Borrows the term directly from the Bluetooth spec's "Numeric Comparison" association model. - Drop the "Only implemented on the espressif port." line from confirm_pairing()'s docstring: numeric_comparison already reads None on ports without this feature, so a correct polling loop never calls confirm_pairing() there, and calling it anyway already raises NotImplementedError. - Stop passing a custom message to that NotImplementedError - the raise already makes it obvious via the function name. Matches the existing mp_raise_NotImplementedError(NULL) convention used elsewhere in the codebase, including twice already in zephyr-cp's own Connection.c. The NotImplementedError message removal also fixes a CI failure: dropping that translatable string frees up enough flash to get bluemicro840's ja locale build back under its 524 KB limit (it was overflowing by 16 bytes). --- .../ble_hci/common-hal/_bleio/Connection.c | 4 ++-- .../espressif/common-hal/_bleio/Connection.c | 4 ++-- .../espressif/common-hal/_bleio/Connection.h | 2 +- ports/nordic/common-hal/_bleio/Connection.c | 4 ++-- ports/silabs/common-hal/_bleio/Connection.c | 4 ++-- .../zephyr-cp/common-hal/_bleio/Connection.c | 4 ++-- shared-bindings/_bleio/Connection.c | 22 +++++++++---------- shared-bindings/_bleio/Connection.h | 2 +- 8 files changed, 22 insertions(+), 24 deletions(-) diff --git a/devices/ble_hci/common-hal/_bleio/Connection.c b/devices/ble_hci/common-hal/_bleio/Connection.c index 605e71147db..028b68796b5 100644 --- a/devices/ble_hci/common-hal/_bleio/Connection.c +++ b/devices/ble_hci/common-hal/_bleio/Connection.c @@ -327,12 +327,12 @@ bool common_hal_bleio_connection_get_authenticated(bleio_connection_obj_t *self) return false; } -mp_obj_t common_hal_bleio_connection_get_pairing_numeric_comparison(bleio_connection_obj_t *self) { +mp_obj_t common_hal_bleio_connection_get_numeric_comparison(bleio_connection_obj_t *self) { return mp_const_none; } void common_hal_bleio_connection_confirm_pairing(bleio_connection_obj_t *self, bool accept) { - mp_raise_NotImplementedError(MP_ERROR_TEXT("Numeric comparison pairing")); + mp_raise_NotImplementedError(NULL); } void common_hal_bleio_connection_disconnect(bleio_connection_internal_t *self) { diff --git a/ports/espressif/common-hal/_bleio/Connection.c b/ports/espressif/common-hal/_bleio/Connection.c index e5db2e1b4a5..ff8a4402001 100644 --- a/ports/espressif/common-hal/_bleio/Connection.c +++ b/ports/espressif/common-hal/_bleio/Connection.c @@ -96,7 +96,7 @@ int bleio_connection_event_cb(struct ble_gap_event *event, void *connection_in) // *_WITH_MITM permission, which made the adapter advertise DISPLAY_YESNO IO // capability (bleio_adapter_enable_mitm_pairing()), so NimBLE asks us to // confirm a 6-digit numeric-comparison value. Stash it and enter - // PAIR_WAITING_NUMCMP; Python reads it via Connection.pairing_numeric_comparison + // PAIR_WAITING_NUMCMP; Python reads it via Connection.numeric_comparison // and answers with confirm_pairing(). if (event->passkey.params.action == BLE_SM_IOACT_NUMCMP) { connection->pairing_numcmp = event->passkey.params.numcmp; @@ -200,7 +200,7 @@ void common_hal_bleio_connection_pair(bleio_connection_internal_t *self, bool bo // LE Secure Connections numeric comparison. Unlike pair(), these don't block: the peer // (a central) drives the SM procedure; we just observe the pending value and inject the // yes/no. BLE_GAP_EVENT_PASSKEY_ACTION sets pair_status = PAIR_WAITING_NUMCMP. -mp_obj_t common_hal_bleio_connection_get_pairing_numeric_comparison(bleio_connection_obj_t *self) { +mp_obj_t common_hal_bleio_connection_get_numeric_comparison(bleio_connection_obj_t *self) { if (self->connection == NULL || self->connection->pair_status != PAIR_WAITING_NUMCMP) { return mp_const_none; } diff --git a/ports/espressif/common-hal/_bleio/Connection.h b/ports/espressif/common-hal/_bleio/Connection.h index af6a46866ee..143cc64d28a 100644 --- a/ports/espressif/common-hal/_bleio/Connection.h +++ b/ports/espressif/common-hal/_bleio/Connection.h @@ -24,7 +24,7 @@ typedef enum { PAIR_WAITING, // The peer is running LE Secure Connections numeric-comparison pairing and NimBLE is // waiting for us to confirm the 6-digit value. Python reads it from - // Connection.pairing_numeric_comparison and answers with confirm_pairing(). This is a + // Connection.numeric_comparison and answers with confirm_pairing(). This is a // peer-driven, non-blocking path; the blocking pair() does not use it. PAIR_WAITING_NUMCMP, PAIR_PAIRED, diff --git a/ports/nordic/common-hal/_bleio/Connection.c b/ports/nordic/common-hal/_bleio/Connection.c index 2fef2e1962b..e34aa56c4e2 100644 --- a/ports/nordic/common-hal/_bleio/Connection.c +++ b/ports/nordic/common-hal/_bleio/Connection.c @@ -339,12 +339,12 @@ bool common_hal_bleio_connection_get_authenticated(bleio_connection_obj_t *self) return false; } -mp_obj_t common_hal_bleio_connection_get_pairing_numeric_comparison(bleio_connection_obj_t *self) { +mp_obj_t common_hal_bleio_connection_get_numeric_comparison(bleio_connection_obj_t *self) { return mp_const_none; } void common_hal_bleio_connection_confirm_pairing(bleio_connection_obj_t *self, bool accept) { - mp_raise_NotImplementedError(MP_ERROR_TEXT("Numeric comparison pairing")); + mp_raise_NotImplementedError(NULL); } bool common_hal_bleio_connection_get_connected(bleio_connection_obj_t *self) { diff --git a/ports/silabs/common-hal/_bleio/Connection.c b/ports/silabs/common-hal/_bleio/Connection.c index 15a42a36b95..4f5da679931 100644 --- a/ports/silabs/common-hal/_bleio/Connection.c +++ b/ports/silabs/common-hal/_bleio/Connection.c @@ -64,12 +64,12 @@ bool common_hal_bleio_connection_get_authenticated(bleio_connection_obj_t *self) return false; } -mp_obj_t common_hal_bleio_connection_get_pairing_numeric_comparison(bleio_connection_obj_t *self) { +mp_obj_t common_hal_bleio_connection_get_numeric_comparison(bleio_connection_obj_t *self) { return mp_const_none; } void common_hal_bleio_connection_confirm_pairing(bleio_connection_obj_t *self, bool accept) { - mp_raise_NotImplementedError(MP_ERROR_TEXT("Numeric comparison pairing")); + mp_raise_NotImplementedError(NULL); } // Get connected status diff --git a/ports/zephyr-cp/common-hal/_bleio/Connection.c b/ports/zephyr-cp/common-hal/_bleio/Connection.c index 75266fb9787..12b2a8efe5f 100644 --- a/ports/zephyr-cp/common-hal/_bleio/Connection.c +++ b/ports/zephyr-cp/common-hal/_bleio/Connection.c @@ -584,12 +584,12 @@ bool common_hal_bleio_connection_get_authenticated(bleio_connection_obj_t *self) return false; } -mp_obj_t common_hal_bleio_connection_get_pairing_numeric_comparison(bleio_connection_obj_t *self) { +mp_obj_t common_hal_bleio_connection_get_numeric_comparison(bleio_connection_obj_t *self) { return mp_const_none; } void common_hal_bleio_connection_confirm_pairing(bleio_connection_obj_t *self, bool accept) { - mp_raise_NotImplementedError(MP_ERROR_TEXT("Numeric comparison pairing")); + mp_raise_NotImplementedError(NULL); } mp_obj_tuple_t *common_hal_bleio_connection_discover_remote_services(bleio_connection_obj_t *self, mp_obj_t service_uuids_whitelist) { diff --git a/shared-bindings/_bleio/Connection.c b/shared-bindings/_bleio/Connection.c index fe2b844488c..275fa0d435d 100644 --- a/shared-bindings/_bleio/Connection.c +++ b/shared-bindings/_bleio/Connection.c @@ -184,7 +184,7 @@ MP_PROPERTY_GETTER(bleio_connection_authenticated_obj, (mp_obj_t)&bleio_connection_get_authenticated_obj); -//| pairing_numeric_comparison: Optional[int] +//| numeric_comparison: Optional[int] //| """When the peer has started LE Secure Connections pairing and is waiting for the //| user to confirm a 6-digit numeric-comparison value, this is that value (an ``int``, //| 0-999999); otherwise ``None``. Poll it, show the value to the user, then answer @@ -197,7 +197,7 @@ MP_PROPERTY_GETTER(bleio_connection_authenticated_obj, //| services it roughly like this:: //| //| while connection.connected: -//| code = connection.pairing_numeric_comparison +//| code = connection.numeric_comparison //| if code is not None: //| display.show(f"Pair? {code:06d}") //| connection.confirm_pairing(accept=button_a.value) @@ -206,27 +206,25 @@ MP_PROPERTY_GETTER(bleio_connection_authenticated_obj, //| time.sleep(0.1) //| //| Only implemented on the espressif port.""" -static mp_obj_t bleio_connection_get_pairing_numeric_comparison(mp_obj_t self_in) { +static mp_obj_t bleio_connection_get_numeric_comparison(mp_obj_t self_in) { bleio_connection_obj_t *self = MP_OBJ_TO_PTR(self_in); - return common_hal_bleio_connection_get_pairing_numeric_comparison(self); + return common_hal_bleio_connection_get_numeric_comparison(self); } -static MP_DEFINE_CONST_FUN_OBJ_1(bleio_connection_get_pairing_numeric_comparison_obj, bleio_connection_get_pairing_numeric_comparison); +static MP_DEFINE_CONST_FUN_OBJ_1(bleio_connection_get_numeric_comparison_obj, bleio_connection_get_numeric_comparison); -MP_PROPERTY_GETTER(bleio_connection_pairing_numeric_comparison_obj, - (mp_obj_t)&bleio_connection_get_pairing_numeric_comparison_obj); +MP_PROPERTY_GETTER(bleio_connection_numeric_comparison_obj, + (mp_obj_t)&bleio_connection_get_numeric_comparison_obj); //| def confirm_pairing(self, accept: bool) -> None: //| """Answer a pending numeric-comparison pairing request (see -//| `pairing_numeric_comparison`). Pass ``accept=True`` if the value shown on the +//| `numeric_comparison`). Pass ``accept=True`` if the value shown on the //| peer matches the one shown here, ``accept=False`` to reject and abort pairing. //| -//| Only implemented on the espressif port. -//| //| :raises ConnectionError: if the connection has dropped. //| :raises _bleio.BluetoothError: if no numeric-comparison request is pending - -//| nothing was offered on `pairing_numeric_comparison`, or it already expired +//| nothing was offered on `numeric_comparison`, or it already expired //| because the peer disconnected or the pairing procedure timed out.""" //| ... //| @@ -305,7 +303,7 @@ static const mp_rom_map_elem_t bleio_connection_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_connected), MP_ROM_PTR(&bleio_connection_connected_obj) }, { MP_ROM_QSTR(MP_QSTR_paired), MP_ROM_PTR(&bleio_connection_paired_obj) }, { MP_ROM_QSTR(MP_QSTR_authenticated), MP_ROM_PTR(&bleio_connection_authenticated_obj) }, - { MP_ROM_QSTR(MP_QSTR_pairing_numeric_comparison), MP_ROM_PTR(&bleio_connection_pairing_numeric_comparison_obj) }, + { MP_ROM_QSTR(MP_QSTR_numeric_comparison), MP_ROM_PTR(&bleio_connection_numeric_comparison_obj) }, { MP_ROM_QSTR(MP_QSTR_connection_interval), MP_ROM_PTR(&bleio_connection_connection_interval_obj) }, { MP_ROM_QSTR(MP_QSTR_max_packet_length), MP_ROM_PTR(&bleio_connection_max_packet_length_obj) }, }; diff --git a/shared-bindings/_bleio/Connection.h b/shared-bindings/_bleio/Connection.h index 4124eb4a7a9..fbc856c7d45 100644 --- a/shared-bindings/_bleio/Connection.h +++ b/shared-bindings/_bleio/Connection.h @@ -19,7 +19,7 @@ bool common_hal_bleio_connection_get_connected(bleio_connection_obj_t *self); mp_int_t common_hal_bleio_connection_get_max_packet_length(bleio_connection_internal_t *self); bool common_hal_bleio_connection_get_paired(bleio_connection_obj_t *self); bool common_hal_bleio_connection_get_authenticated(bleio_connection_obj_t *self); -mp_obj_t common_hal_bleio_connection_get_pairing_numeric_comparison(bleio_connection_obj_t *self); +mp_obj_t common_hal_bleio_connection_get_numeric_comparison(bleio_connection_obj_t *self); void common_hal_bleio_connection_confirm_pairing(bleio_connection_obj_t *self, bool accept); mp_obj_tuple_t *common_hal_bleio_connection_discover_remote_services(bleio_connection_obj_t *self, mp_obj_t service_uuids_whitelist); From 41b619ac39aff54d297828b0b31d7d212cc7eab8 Mon Sep 17 00:00:00 2001 From: Mike Mabey Date: Sat, 12 Sep 2026 10:28:44 -0600 Subject: [PATCH 4/4] boards: disable aesio on 4 boards overflowing after LESC numcmp bleio API The numeric-comparison pairing additions in shared-bindings/_bleio/Connection.c (authenticated, numeric_comparison, confirm_pairing) are compiled into every board with BLE enabled, adding a small fixed cost that pushed these already near-full boards over their flash budget in the ja locale build: circuitbrains_deluxe_m4 (+104B), datalore_ip_m4 (+124B), bluemicro840 (+144B), electronut_labs_blip (+80B). None of them need AES, so disable it to reclaim space, following the same approach used previously for electronut_labs_blip (74e380bed0) and matrixportal_m4 (c098474854). --- ports/atmel-samd/boards/circuitbrains_deluxe_m4/mpconfigboard.mk | 1 + ports/atmel-samd/boards/datalore_ip_m4/mpconfigboard.mk | 1 + ports/nordic/boards/bluemicro840/mpconfigboard.mk | 1 + ports/nordic/boards/electronut_labs_blip/mpconfigboard.mk | 1 + 4 files changed, 4 insertions(+) diff --git a/ports/atmel-samd/boards/circuitbrains_deluxe_m4/mpconfigboard.mk b/ports/atmel-samd/boards/circuitbrains_deluxe_m4/mpconfigboard.mk index 5c1cab422cb..1895f74f31b 100755 --- a/ports/atmel-samd/boards/circuitbrains_deluxe_m4/mpconfigboard.mk +++ b/ports/atmel-samd/boards/circuitbrains_deluxe_m4/mpconfigboard.mk @@ -10,6 +10,7 @@ QSPI_FLASH_FILESYSTEM = 1 EXTERNAL_FLASH_DEVICES = "W25Q64JVxQ, S25FL064L" LONGINT_IMPL = MPZ +CIRCUITPY_AESIO = 0 CIRCUITPY_I2CTARGET = 0 CIRCUITPY_PS2IO = 1 CIRCUITPY_JPEGIO = 0 diff --git a/ports/atmel-samd/boards/datalore_ip_m4/mpconfigboard.mk b/ports/atmel-samd/boards/datalore_ip_m4/mpconfigboard.mk index 2ad140094b3..4289a8799d9 100644 --- a/ports/atmel-samd/boards/datalore_ip_m4/mpconfigboard.mk +++ b/ports/atmel-samd/boards/datalore_ip_m4/mpconfigboard.mk @@ -10,6 +10,7 @@ QSPI_FLASH_FILESYSTEM = 1 EXTERNAL_FLASH_DEVICES = "GD25Q16C, W25Q16JVxQ, W25Q16JVxM" LONGINT_IMPL = MPZ +CIRCUITPY_AESIO = 0 CIRCUITPY_I2CTARGET = 0 CIRCUITPY_JPEGIO = 0 CIRCUITPY_SPITARGET = 0 diff --git a/ports/nordic/boards/bluemicro840/mpconfigboard.mk b/ports/nordic/boards/bluemicro840/mpconfigboard.mk index 16d36704a3e..93b8ac56fa6 100644 --- a/ports/nordic/boards/bluemicro840/mpconfigboard.mk +++ b/ports/nordic/boards/bluemicro840/mpconfigboard.mk @@ -6,3 +6,4 @@ USB_MANUFACTURER = "nrf52.jpconstantineau.com" MCU_CHIP = nrf52840 INTERNAL_FLASH_FILESYSTEM = 1 +CIRCUITPY_AESIO = 0 diff --git a/ports/nordic/boards/electronut_labs_blip/mpconfigboard.mk b/ports/nordic/boards/electronut_labs_blip/mpconfigboard.mk index fc59a101e45..4ead814dcab 100644 --- a/ports/nordic/boards/electronut_labs_blip/mpconfigboard.mk +++ b/ports/nordic/boards/electronut_labs_blip/mpconfigboard.mk @@ -12,3 +12,4 @@ CIRCUITPY_AUDIOIO = 0 CIRCUITPY_DISPLAYIO = 1 CIRCUITPY_STAGE = 1 CIRCUITPY_DIGITALINOUT_PROTOCOL = 0 +CIRCUITPY_AESIO = 0