Skip to content

Commit 45bba91

Browse files
pi-anlclaude
andcommitted
extmod/btstack: Add encryption state caching for event deduplication.
Implements an encryption state cache to track connection encryption status and prevent duplicate encryption update events. This ensures BTstack only sends encryption updates for PAIRING_COMPLETE and REENCRYPTION_COMPLETE events, matching NimBLE behavior. Also adds automatic acceptance of Just Works pairing requests and proper cleanup of encryption state on disconnection. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Andrew Leech <andrew.leech@planetinnovation.com.au>
1 parent 774cb88 commit 45bba91

File tree

2 files changed

+110
-5
lines changed

2 files changed

+110
-5
lines changed

extmod/btstack/modbluetooth_btstack.c

Lines changed: 107 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,63 @@ static mp_obj_bluetooth_uuid_t create_mp_uuid(uint16_t uuid16, const uint8_t *uu
9595
}
9696
#endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE
9797

98+
// Encryption state cache for all connections to prevent duplicate events
99+
typedef struct _mp_btstack_encryption_state_t {
100+
btstack_linked_item_t *next; // Must be first field to match btstack_linked_item.
101+
uint16_t conn_handle;
102+
bool encrypted;
103+
bool authenticated;
104+
bool bonded;
105+
uint8_t key_size;
106+
} mp_btstack_encryption_state_t;
107+
108+
static mp_btstack_encryption_state_t *create_encryption_state(uint16_t conn_handle) {
109+
DEBUG_printf("create_encryption_state: conn_handle=%d\n", conn_handle);
110+
mp_btstack_encryption_state_t *state = m_new(mp_btstack_encryption_state_t, 1);
111+
state->conn_handle = conn_handle;
112+
state->encrypted = false;
113+
state->authenticated = false;
114+
state->bonded = false;
115+
state->key_size = 0;
116+
bool added = btstack_linked_list_add(&MP_STATE_PORT(bluetooth_btstack_root_pointers)->encryption_states, (btstack_linked_item_t *)state);
117+
(void)added;
118+
assert(added);
119+
return state;
120+
}
121+
122+
static mp_btstack_encryption_state_t *find_encryption_state(uint16_t conn_handle) {
123+
DEBUG_printf("find_encryption_state: conn_handle=%d\n", conn_handle);
124+
btstack_linked_list_iterator_t it;
125+
btstack_linked_list_iterator_init(&it, &MP_STATE_PORT(bluetooth_btstack_root_pointers)->encryption_states);
126+
mp_btstack_encryption_state_t *state = NULL;
127+
while (btstack_linked_list_iterator_has_next(&it)) {
128+
state = (mp_btstack_encryption_state_t *)btstack_linked_list_iterator_next(&it);
129+
DEBUG_printf(" --> iter state %d\n", state->conn_handle);
130+
if (state->conn_handle == conn_handle) {
131+
break;
132+
}
133+
}
134+
return state;
135+
}
136+
137+
static void remove_encryption_state(uint16_t conn_handle) {
138+
DEBUG_printf("remove_encryption_state: conn_handle=%d\n", conn_handle);
139+
mp_btstack_encryption_state_t *state = find_encryption_state(conn_handle);
140+
if (state) {
141+
bool removed = btstack_linked_list_remove(&MP_STATE_PORT(bluetooth_btstack_root_pointers)->encryption_states, (btstack_linked_item_t *)state);
142+
(void)removed;
143+
assert(removed);
144+
m_del(mp_btstack_encryption_state_t, state, 1);
145+
}
146+
}
147+
148+
static void update_encryption_state(mp_btstack_encryption_state_t *state, bool encrypted, bool authenticated, bool bonded, uint8_t key_size) {
149+
state->encrypted = encrypted;
150+
state->authenticated = authenticated;
151+
state->bonded = bonded;
152+
state->key_size = key_size;
153+
}
154+
98155
#if MICROPY_PY_BLUETOOTH_ENABLE_GATT_CLIENT
99156
typedef struct _mp_btstack_active_connection_t {
100157
btstack_linked_item_t *next; // Must be first field to match btstack_linked_item.
@@ -232,6 +289,7 @@ static void btstack_packet_handler_generic(uint8_t packet_type, uint16_t channel
232289
// Slave role.
233290
irq_event = MP_BLUETOOTH_IRQ_CENTRAL_CONNECT;
234291
}
292+
// Encryption state will be created on first encryption event
235293
#if MICROPY_PY_BLUETOOTH_ENABLE_GATT_CLIENT
236294
create_active_connection(conn_handle);
237295
#endif
@@ -318,8 +376,7 @@ static void btstack_packet_handler_generic(uint8_t packet_type, uint16_t channel
318376
hci_connection_t *hci_con = hci_connection_for_handle(conn_handle);
319377
if (!hci_con) {
320378
DEBUG_printf(" --> warning: no HCI connection found for handle %d\n", conn_handle);
321-
// Try to create a minimal encryption update with default values
322-
mp_bluetooth_gatts_on_encryption_update(conn_handle, false, false, false, 0);
379+
// Don't send fallback events - only send real pairing completion events
323380
return;
324381
}
325382

@@ -339,15 +396,51 @@ static void btstack_packet_handler_generic(uint8_t packet_type, uint16_t channel
339396
}
340397
#endif
341398

342-
mp_bluetooth_gatts_on_encryption_update(conn_handle,
343-
encrypted, authenticated, bonded, desc->sm_actual_encryption_key_size);
399+
// Only forward encryption updates for PAIRING_COMPLETE and REENCRYPTION_COMPLETE
400+
// This matches NimBLE behavior which only reports final pairing result
401+
bool should_forward = false;
402+
403+
if (event_type == SM_EVENT_PAIRING_COMPLETE || event_type == SM_EVENT_REENCRYPTION_COMPLETE) {
404+
// Check our cache to see if this is a duplicate
405+
mp_btstack_encryption_state_t *enc_state = find_encryption_state(conn_handle);
406+
if (!enc_state) {
407+
enc_state = create_encryption_state(conn_handle);
408+
should_forward = true; // First time
409+
} else {
410+
// Check if state actually changed
411+
should_forward = (enc_state->encrypted != encrypted) ||
412+
(enc_state->authenticated != authenticated) ||
413+
(enc_state->bonded != bonded) ||
414+
(enc_state->key_size != desc->sm_actual_encryption_key_size);
415+
}
416+
417+
// Update cache
418+
if (enc_state) {
419+
update_encryption_state(enc_state, encrypted, authenticated, bonded, desc->sm_actual_encryption_key_size);
420+
}
421+
}
422+
423+
if (should_forward) {
424+
DEBUG_printf(" --> pairing complete, forwarding final encryption state to Python\n");
425+
mp_bluetooth_gatts_on_encryption_update(conn_handle,
426+
encrypted, authenticated, bonded, desc->sm_actual_encryption_key_size);
427+
} else {
428+
DEBUG_printf(" --> skipping intermediate encryption event (event_type=%d)\n", event_type);
429+
}
430+
#endif // MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING
431+
} else if (event_type == SM_EVENT_JUST_WORKS_REQUEST) {
432+
#if MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING
433+
DEBUG_printf(" --> just works request, auto-accepting\n");
434+
// Just Works pairing requires automatic acceptance
435+
uint16_t conn_handle = sm_event_just_works_request_get_handle(packet);
436+
sm_just_works_confirm(conn_handle);
437+
#endif // MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING
344438
} else if (event_type == SM_EVENT_PASSKEY_DISPLAY_NUMBER) {
345439
mp_bluetooth_gap_on_passkey_action(sm_event_passkey_display_number_get_handle(packet), MP_BLUETOOTH_PASSKEY_ACTION_DISPLAY, sm_event_passkey_display_number_get_passkey(packet));
346440
} else if (event_type == SM_EVENT_PASSKEY_INPUT_NUMBER) {
347441
mp_bluetooth_gap_on_passkey_action(sm_event_passkey_input_number_get_handle(packet), MP_BLUETOOTH_PASSKEY_ACTION_INPUT, 0);
348442
} else if (event_type == SM_EVENT_NUMERIC_COMPARISON_REQUEST) {
349443
mp_bluetooth_gap_on_passkey_action(sm_event_numeric_comparison_request_get_handle(packet), MP_BLUETOOTH_PASSKEY_ACTION_NUMERIC_COMPARISON, sm_event_numeric_comparison_request_get_passkey(packet));
350-
#endif // MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING
351444
} else if (event_type == HCI_EVENT_DISCONNECTION_COMPLETE) {
352445
DEBUG_printf(" --> hci disconnect complete\n");
353446
uint16_t conn_handle = hci_event_disconnection_complete_get_connection_handle(packet);
@@ -362,6 +455,12 @@ static void btstack_packet_handler_generic(uint8_t packet_type, uint16_t channel
362455
}
363456
uint8_t addr[6] = {0};
364457
mp_bluetooth_gap_on_connected_disconnected(irq_event, conn_handle, 0xff, addr);
458+
459+
#if MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING
460+
// Remove encryption state cache for this connection
461+
remove_encryption_state(conn_handle);
462+
#endif
463+
365464
#if MICROPY_PY_BLUETOOTH_ENABLE_GATT_CLIENT
366465
remove_active_connection(conn_handle);
367466
#endif
@@ -697,6 +796,9 @@ int mp_bluetooth_init(void) {
697796
MP_STATE_PORT(bluetooth_btstack_root_pointers) = m_new0(mp_bluetooth_btstack_root_pointers_t, 1);
698797
mp_bluetooth_gatts_db_create(&MP_STATE_PORT(bluetooth_btstack_root_pointers)->gatts_db);
699798

799+
// Initialize encryption state cache
800+
MP_STATE_PORT(bluetooth_btstack_root_pointers)->encryption_states = NULL;
801+
700802
// Set the default GAP device name.
701803
const char *gap_name = MICROPY_PY_BLUETOOTH_DEFAULT_GAP_NAME;
702804
size_t gap_len = strlen(gap_name);

extmod/btstack/modbluetooth_btstack.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ typedef struct _mp_bluetooth_btstack_root_pointers_t {
4242
// Characteristic (and descriptor) value storage.
4343
mp_gatts_db_t gatts_db;
4444

45+
// Encryption state cache for all connections (prevents duplicate events).
46+
btstack_linked_list_t encryption_states;
47+
4548
#if MICROPY_PY_BLUETOOTH_ENABLE_GATT_CLIENT
4649
// Registration for notify/indicate events.
4750
gatt_client_notification_t notification;

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy