8000 aioble/service_changed: Add support for BLE service_changed. · micropython/micropython-lib@58e4149 · GitHub
[go: up one dir, main page]

Skip to content

Commit 58e4149

Browse files
committed
aioble/service_changed: Add support for BLE service_changed.
Also present database_hash for newer devices.
1 parent 32b08a0 commit 58e4149

File tree

4 files changed

+111
-10
lines changed

4 files changed

+111
-10
lines changed

micropython/bluetooth/aioble/aioble/security.py

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from micropython import const, schedule
55
import uasyncio as asyncio
66
import binascii
7+
import ustruct
78
import json
89

910
from .core import log_info, log_warn, ble, register_irq_handler
@@ -31,6 +32,9 @@
3132
_modified = False
3233
_path = None
3334

35+
connected_sec = None
36+
gatt_svc = None
37+
3438

3539
# Must call this before stack startup.
3640
def load_secrets(path=None):
@@ -45,9 +49,10 @@ def load_secrets(path=None):
4549
try:
4650
with open(_path, "r") as f:
4751
entries = json.load(f)
48-
for sec_type, key, value in entries:
52+
for sec_type, key, value, *digest in entries:
53+
digest = digest[0] or None
4954
# Decode bytes from hex.
50-
_secrets.append(((sec_type, binascii.a2b_base64(key)), binascii.a2b_base64(value)))
55+
_secrets.append(((sec_type, binascii.a2b_base64(key)), binascii.a2b_base64(value), digest))
5156
except:
5257
log_warn("No secrets available")
5358

@@ -66,15 +71,15 @@ def _save_secrets(arg=None):
6671
# Convert bytes to hex strings (otherwise JSON will treat them like
6772
# strings).
6873
json_secrets = [
69-
(sec_type, binascii.b2a_base64(key), binascii.b2a_base64(value))
70-
for (sec_type, key), value in _secrets
74+
(sec_type, binascii.b2a_base64(key), binascii.b2a_base64(value), digest)
75+
for (sec_type, key), value, digest in _secrets
7176
]
7277
json.dump(json_secrets, f)
7378
_modified = False
7479

7580

7681
def _security_irq(event, data):
77-
global _modified
82+
global _modified, connected_sec, gatt_svc
7883

7984
if event == _IRQ_ENCRYPTION_UPDATE:
8085
# Connection has updated (usually due to pairing).
@@ -89,6 +94,19 @@ def _security_irq(event, data):
8994
if encrypted and connection._pair_event:
9095
connection._pair_event.set()
9196

97+
if bonded and \
98+
None not in (gatt_svc, connected_sec) and \
99+
connected_sec[2] != gatt_svc.hexdigest:
100+
gatt_svc.send_changed(connection)
101+
102+
# Update the hash in the database
103+
_secrets.remove(connected_sec)
104+
updated_sec = connected_sec[:-1] + (gatt_svc.hexdigest,)
105+
_secrets.insert(0, updated_sec)
106+
# Queue up a save (don't synchronously write to flash).
107+
_modified = True
108+
schedule(_save_secrets, None)
109+
92110
elif event == _IRQ_SET_SECRET:
93111
sec_type, key, value = data
94112
key = sec_type, bytes(key)
@@ -99,15 +117,16 @@ def _security_irq(event, data):
99117
if value is None:
100118
# Delete secret.
101119
i = None
102-
for i, k, v in enumerate(_secrets):
120+
for i, k, v, d in enumerate(_secrets):
103121
if k == key:
104122
break
105123
if i is not None:
106124
_secrets.pop(i)
107125

108126
else:
109127
# Save secret.
110-
_secrets.insert(0, (key, value))
128+
current_digest = gatt_svc.hexdigest if gatt_svc else None
129+
_secrets.insert(0, (key, value, current_digest))
111130

112131
# Queue up a save (don't synchronously write to flash).
113132
_modified = True
@@ -123,7 +142,7 @@ def _security_irq(event, data):
123142
if key is None:
124143
# Return the index'th secret of this type.
125144
i = 0
126-
for (t, _key), value in _secrets.items():
145+
for (t, _key), value, digest in _secrets:
127146
if t == sec_type:
128147
if i == index:
129148
return value
@@ -133,8 +152,11 @@ def _security_irq(event, data):
133152
# Return the secret for this key (or None).
134153
key = sec_type, bytes(key)
135154

136-
for k, v in _secrets:
155+
for k, v, d in _secrets:
156+
log_info(f"get secret: {k=}")
137157
if k == key:
158+
connected_sec = k, v, d
159+
log_info("get secret: found key")
138160
return v
139161
return None
140162

micropython/bluetooth/aioble/aioble/server.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,9 +228,13 @@ def __init__(self, characteristic, uuid, read=False, write=False, initial=None):
228228

229229
# Turn the Service/Characteristic/Descriptor classes into a registration tuple
230230
# and then extract their value handles.
231-
def register_services(*services):
231+
def register_services(*services, include_gatt_svc=True):
232232
ensure_active()
233233
_registered_characteristics.clear()
234+
if include_gatt_svc:
235+
from .services.generic_attribute_service import GenericAttributeService
236+
gatt_svc = GenericAttributeService(services)
237+
services = (gatt_svc,) + services
234238
handles = ble.gatts_register_services(tuple(s._tuple() for s in services))
235239
for i in range(len(services)):
236240
service_handles = handles[i]

micropython/bluetooth/aioble/aioble/services/__init__.py

Whitespace-only changes.
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# @file
4+
# @brief: Bluetooth Generic Attribute Service
5+
#
6+
# Copyright (c) 2021, Planet Innovation
7+
# 436 Elgar Road, Box Hill, 3128, VIC, Australia
8+
# Phone: +61 3 9945 7510
9+
#
10+
# The copyright to the computer program(s) herein is the property of
11+
# Planet Innovation, Australia.
12+
# The program(s) may be used and/or copied only with the written permission
13+
# of Planet Innovation or in accordance with the terms and conditions
14+
# stipulated in the agreement/contract under which the program(s) have been
15+
# supplied.
16+
#
17+
18+
import ustruct
19+
import bluetooth
20+
from aioble import Service, Characteristic, security
21+
from aioble.core import ble, log_info
22+
from hashlib import md5
23+
from ubinascii import hexlify
24+
try:
25+
from utyping import *
26+
except:
27+
pass
28+
29+
30+
class GenericAttributeService(Service):
31+
# Generic Attribute service UUID
32+
SERVICE_UUID = bluetooth.UUID(0x1801)
33+
34+
# Service Changed Characteristic
35+
UUID_SERVICE_CHANGED = bluetooth.UUID(0x2A05)
36+
# Database Hash Characteristic (New in BLE 5.1)
37+
UUID_DATABASE_HASH = bluetooth.UUID(0x2B2A)
38+
39+
def __init__(self, services: Tuple[Service]):
40+
41+
super().__init__(self.SERVICE_UUID)
42+
43+
# Database hash is typically a 128bit AES-CMAC value, however
44+
# is generally only monitored for change as an opaque value.
45+
# MD5 is also 128 bit, faster and builtin
46+
hasher = md5()
47+
for service in services:
48+
for char in service.characteristics:
49+
hasher.update(char.uuid)
50+
hasher.update(str(char.flags))
51+
self.digest = hasher.digest()
52+
self.hexdigest = hexlify(self.digest).decode()
53+
log_info("BLE: DB Hash=", self.hexdigest)
54+
security.current_digest = self.hexdigest
55+
security.gatt_svc = self
56+
57+
self.SERVICE_CHANGED = Characteristic(
58+
service=self,
59+
uuid=self.UUID_SERVICE_CHANGED,
60+
read=True,
61+
indicate=True,
62+
initial=''
63+
)
64+
65+
self.DATABASE_HASH = Characteristic(
66+
service=self,
67+
uuid=self.UUID_DATABASE_HASH,
68+
read=True,
69+
initial=self.digest
70+
)
71+
72+
def send_changed(self, connection, start=0, end=0xFFFF):
73+
self.SERVICE_CHANGED.write(ustruct.pack('!HH', start, end))
74+
log_info("Indicate Service Changed")
75+
ble.gatts_indicate(connection._conn_handle, self.SERVICE_CHANGED._value_handle)

0 commit comments

Comments
 (0)
0