bumble 0.0.180__py3-none-any.whl → 0.0.181__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bumble/_version.py +2 -2
- bumble/apps/bench.py +110 -81
- bumble/apps/ble_rpa_tool.py +63 -0
- bumble/apps/console.py +4 -4
- bumble/apps/controller_info.py +34 -2
- bumble/apps/pair.py +6 -8
- bumble/att.py +53 -11
- bumble/controller.py +28 -1
- bumble/crypto.py +10 -0
- bumble/device.py +566 -111
- bumble/drivers/__init__.py +27 -31
- bumble/drivers/common.py +45 -0
- bumble/drivers/rtk.py +11 -4
- bumble/gatt.py +66 -51
- bumble/gatt_server.py +30 -22
- bumble/hci.py +223 -92
- bumble/helpers.py +14 -0
- bumble/hfp.py +37 -27
- bumble/hid.py +282 -61
- bumble/host.py +158 -93
- bumble/l2cap.py +3 -3
- bumble/profiles/asha_service.py +2 -2
- bumble/profiles/bap.py +1247 -0
- bumble/profiles/cap.py +52 -0
- bumble/profiles/csip.py +62 -4
- bumble/rfcomm.py +24 -17
- bumble/smp.py +1 -1
- bumble/transport/__init__.py +49 -21
- bumble/transport/android_emulator.py +1 -1
- bumble/transport/common.py +2 -1
- bumble/transport/hci_socket.py +1 -4
- bumble/transport/usb.py +1 -1
- bumble/utils.py +3 -6
- {bumble-0.0.180.dist-info → bumble-0.0.181.dist-info}/METADATA +1 -1
- {bumble-0.0.180.dist-info → bumble-0.0.181.dist-info}/RECORD +39 -35
- {bumble-0.0.180.dist-info → bumble-0.0.181.dist-info}/entry_points.txt +1 -0
- {bumble-0.0.180.dist-info → bumble-0.0.181.dist-info}/LICENSE +0 -0
- {bumble-0.0.180.dist-info → bumble-0.0.181.dist-info}/WHEEL +0 -0
- {bumble-0.0.180.dist-info → bumble-0.0.181.dist-info}/top_level.txt +0 -0
bumble/apps/console.py
CHANGED
|
@@ -777,7 +777,7 @@ class ConsoleApp:
|
|
|
777
777
|
if not service:
|
|
778
778
|
continue
|
|
779
779
|
values = [
|
|
780
|
-
attribute.read_value(connection)
|
|
780
|
+
await attribute.read_value(connection)
|
|
781
781
|
for connection in self.device.connections.values()
|
|
782
782
|
]
|
|
783
783
|
if not values:
|
|
@@ -796,11 +796,11 @@ class ConsoleApp:
|
|
|
796
796
|
if not characteristic:
|
|
797
797
|
continue
|
|
798
798
|
values = [
|
|
799
|
-
attribute.read_value(connection)
|
|
799
|
+
await attribute.read_value(connection)
|
|
800
800
|
for connection in self.device.connections.values()
|
|
801
801
|
]
|
|
802
802
|
if not values:
|
|
803
|
-
values = [attribute.read_value(None)]
|
|
803
|
+
values = [await attribute.read_value(None)]
|
|
804
804
|
|
|
805
805
|
# TODO: future optimization: convert CCCD value to human readable string
|
|
806
806
|
|
|
@@ -944,7 +944,7 @@ class ConsoleApp:
|
|
|
944
944
|
|
|
945
945
|
# send data to any subscribers
|
|
946
946
|
if isinstance(attribute, Characteristic):
|
|
947
|
-
attribute.write_value(None, value)
|
|
947
|
+
await attribute.write_value(None, value)
|
|
948
948
|
if attribute.has_properties(Characteristic.NOTIFY):
|
|
949
949
|
await self.device.gatt_server.notify_subscribers(attribute)
|
|
950
950
|
if attribute.has_properties(Characteristic.INDICATE):
|
bumble/apps/controller_info.py
CHANGED
|
@@ -32,10 +32,14 @@ from bumble.hci import (
|
|
|
32
32
|
HCI_Command,
|
|
33
33
|
HCI_Command_Complete_Event,
|
|
34
34
|
HCI_Command_Status_Event,
|
|
35
|
+
HCI_READ_BUFFER_SIZE_COMMAND,
|
|
36
|
+
HCI_Read_Buffer_Size_Command,
|
|
35
37
|
HCI_READ_BD_ADDR_COMMAND,
|
|
36
38
|
HCI_Read_BD_ADDR_Command,
|
|
37
39
|
HCI_READ_LOCAL_NAME_COMMAND,
|
|
38
40
|
HCI_Read_Local_Name_Command,
|
|
41
|
+
HCI_LE_READ_BUFFER_SIZE_COMMAND,
|
|
42
|
+
HCI_LE_Read_Buffer_Size_Command,
|
|
39
43
|
HCI_LE_READ_MAXIMUM_DATA_LENGTH_COMMAND,
|
|
40
44
|
HCI_LE_Read_Maximum_Data_Length_Command,
|
|
41
45
|
HCI_LE_READ_NUMBER_OF_SUPPORTED_ADVERTISING_SETS_COMMAND,
|
|
@@ -59,7 +63,7 @@ def command_succeeded(response):
|
|
|
59
63
|
|
|
60
64
|
|
|
61
65
|
# -----------------------------------------------------------------------------
|
|
62
|
-
async def get_classic_info(host):
|
|
66
|
+
async def get_classic_info(host: Host) -> None:
|
|
63
67
|
if host.supports_command(HCI_READ_BD_ADDR_COMMAND):
|
|
64
68
|
response = await host.send_command(HCI_Read_BD_ADDR_Command())
|
|
65
69
|
if command_succeeded(response):
|
|
@@ -80,7 +84,7 @@ async def get_classic_info(host):
|
|
|
80
84
|
|
|
81
85
|
|
|
82
86
|
# -----------------------------------------------------------------------------
|
|
83
|
-
async def get_le_info(host):
|
|
87
|
+
async def get_le_info(host: Host) -> None:
|
|
84
88
|
print()
|
|
85
89
|
|
|
86
90
|
if host.supports_command(HCI_LE_READ_NUMBER_OF_SUPPORTED_ADVERTISING_SETS_COMMAND):
|
|
@@ -136,6 +140,31 @@ async def get_le_info(host):
|
|
|
136
140
|
print(' ', name_or_number(HCI_LE_SUPPORTED_FEATURES_NAMES, feature))
|
|
137
141
|
|
|
138
142
|
|
|
143
|
+
# -----------------------------------------------------------------------------
|
|
144
|
+
async def get_acl_flow_control_info(host: Host) -> None:
|
|
145
|
+
print()
|
|
146
|
+
|
|
147
|
+
if host.supports_command(HCI_READ_BUFFER_SIZE_COMMAND):
|
|
148
|
+
response = await host.send_command(
|
|
149
|
+
HCI_Read_Buffer_Size_Command(), check_result=True
|
|
150
|
+
)
|
|
151
|
+
print(
|
|
152
|
+
color('ACL Flow Control:', 'yellow'),
|
|
153
|
+
f'{response.return_parameters.hc_total_num_acl_data_packets} '
|
|
154
|
+
f'packets of size {response.return_parameters.hc_acl_data_packet_length}',
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
if host.supports_command(HCI_LE_READ_BUFFER_SIZE_COMMAND):
|
|
158
|
+
response = await host.send_command(
|
|
159
|
+
HCI_LE_Read_Buffer_Size_Command(), check_result=True
|
|
160
|
+
)
|
|
161
|
+
print(
|
|
162
|
+
color('LE ACL Flow Control:', 'yellow'),
|
|
163
|
+
f'{response.return_parameters.hc_total_num_le_acl_data_packets} '
|
|
164
|
+
f'packets of size {response.return_parameters.hc_le_acl_data_packet_length}',
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
|
|
139
168
|
# -----------------------------------------------------------------------------
|
|
140
169
|
async def async_main(transport):
|
|
141
170
|
print('<<< connecting to HCI...')
|
|
@@ -168,6 +197,9 @@ async def async_main(transport):
|
|
|
168
197
|
# Get the LE info
|
|
169
198
|
await get_le_info(host)
|
|
170
199
|
|
|
200
|
+
# Print the ACL flow control info
|
|
201
|
+
await get_acl_flow_control_info(host)
|
|
202
|
+
|
|
171
203
|
# Print the list of commands supported by the controller
|
|
172
204
|
print()
|
|
173
205
|
print(color('Supported Commands:', 'yellow'))
|
bumble/apps/pair.py
CHANGED
|
@@ -52,11 +52,13 @@ from bumble.att import (
|
|
|
52
52
|
class Waiter:
|
|
53
53
|
instance = None
|
|
54
54
|
|
|
55
|
-
def __init__(self):
|
|
55
|
+
def __init__(self, linger=False):
|
|
56
56
|
self.done = asyncio.get_running_loop().create_future()
|
|
57
|
+
self.linger = linger
|
|
57
58
|
|
|
58
59
|
def terminate(self):
|
|
59
|
-
self.
|
|
60
|
+
if not self.linger:
|
|
61
|
+
self.done.set_result(None)
|
|
60
62
|
|
|
61
63
|
async def wait_until_terminated(self):
|
|
62
64
|
return await self.done
|
|
@@ -302,7 +304,7 @@ async def pair(
|
|
|
302
304
|
hci_transport,
|
|
303
305
|
address_or_name,
|
|
304
306
|
):
|
|
305
|
-
Waiter.instance = Waiter()
|
|
307
|
+
Waiter.instance = Waiter(linger=linger)
|
|
306
308
|
|
|
307
309
|
print('<<< connecting to HCI...')
|
|
308
310
|
async with await open_transport_or_link(hci_transport) as (hci_source, hci_sink):
|
|
@@ -396,7 +398,6 @@ async def pair(
|
|
|
396
398
|
address_or_name,
|
|
397
399
|
transport=BT_LE_TRANSPORT if mode == 'le' else BT_BR_EDR_TRANSPORT,
|
|
398
400
|
)
|
|
399
|
-
pairing_failure = False
|
|
400
401
|
|
|
401
402
|
if not request:
|
|
402
403
|
try:
|
|
@@ -405,11 +406,8 @@ async def pair(
|
|
|
405
406
|
else:
|
|
406
407
|
await connection.authenticate()
|
|
407
408
|
except ProtocolError as error:
|
|
408
|
-
pairing_failure = True
|
|
409
409
|
print(color(f'Pairing failed: {error}', 'red'))
|
|
410
410
|
|
|
411
|
-
if not linger or pairing_failure:
|
|
412
|
-
return
|
|
413
411
|
else:
|
|
414
412
|
if mode == 'le':
|
|
415
413
|
# Advertise so that peers can find us and connect
|
|
@@ -459,7 +457,7 @@ class LogHandler(logging.Handler):
|
|
|
459
457
|
help='Enable CTKD',
|
|
460
458
|
show_default=True,
|
|
461
459
|
)
|
|
462
|
-
@click.option('--linger', default=
|
|
460
|
+
@click.option('--linger', default=False, is_flag=True, help='Linger after pairing')
|
|
463
461
|
@click.option(
|
|
464
462
|
'--io',
|
|
465
463
|
type=click.Choice(
|
bumble/att.py
CHANGED
|
@@ -25,9 +25,21 @@
|
|
|
25
25
|
from __future__ import annotations
|
|
26
26
|
import enum
|
|
27
27
|
import functools
|
|
28
|
+
import inspect
|
|
28
29
|
import struct
|
|
30
|
+
from typing import (
|
|
31
|
+
Any,
|
|
32
|
+
Awaitable,
|
|
33
|
+
Callable,
|
|
34
|
+
Dict,
|
|
35
|
+
List,
|
|
36
|
+
Optional,
|
|
37
|
+
Type,
|
|
38
|
+
Union,
|
|
39
|
+
TYPE_CHECKING,
|
|
40
|
+
)
|
|
41
|
+
|
|
29
42
|
from pyee import EventEmitter
|
|
30
|
-
from typing import Dict, Type, List, Protocol, Union, Optional, Any, TYPE_CHECKING
|
|
31
43
|
|
|
32
44
|
from bumble.core import UUID, name_or_number, ProtocolError
|
|
33
45
|
from bumble.hci import HCI_Object, key_with_value
|
|
@@ -722,12 +734,38 @@ class ATT_Handle_Value_Confirmation(ATT_PDU):
|
|
|
722
734
|
|
|
723
735
|
|
|
724
736
|
# -----------------------------------------------------------------------------
|
|
725
|
-
class
|
|
726
|
-
|
|
727
|
-
|
|
737
|
+
class AttributeValue:
|
|
738
|
+
'''
|
|
739
|
+
Attribute value where reading and/or writing is delegated to functions
|
|
740
|
+
passed as arguments to the constructor.
|
|
741
|
+
'''
|
|
742
|
+
|
|
743
|
+
def __init__(
|
|
744
|
+
self,
|
|
745
|
+
read: Union[
|
|
746
|
+
Callable[[Optional[Connection]], bytes],
|
|
747
|
+
Callable[[Optional[Connection]], Awaitable[bytes]],
|
|
748
|
+
None,
|
|
749
|
+
] = None,
|
|
750
|
+
write: Union[
|
|
751
|
+
Callable[[Optional[Connection], bytes], None],
|
|
752
|
+
Callable[[Optional[Connection], bytes], Awaitable[None]],
|
|
753
|
+
None,
|
|
754
|
+
] = None,
|
|
755
|
+
):
|
|
756
|
+
self._read = read
|
|
757
|
+
self._write = write
|
|
758
|
+
|
|
759
|
+
def read(self, connection: Optional[Connection]) -> Union[bytes, Awaitable[bytes]]:
|
|
760
|
+
return self._read(connection) if self._read else b''
|
|
761
|
+
|
|
762
|
+
def write(
|
|
763
|
+
self, connection: Optional[Connection], value: bytes
|
|
764
|
+
) -> Union[Awaitable[None], None]:
|
|
765
|
+
if self._write:
|
|
766
|
+
return self._write(connection, value)
|
|
728
767
|
|
|
729
|
-
|
|
730
|
-
...
|
|
768
|
+
return None
|
|
731
769
|
|
|
732
770
|
|
|
733
771
|
# -----------------------------------------------------------------------------
|
|
@@ -770,13 +808,13 @@ class Attribute(EventEmitter):
|
|
|
770
808
|
READ_REQUIRES_AUTHORIZATION = Permissions.READ_REQUIRES_AUTHORIZATION
|
|
771
809
|
WRITE_REQUIRES_AUTHORIZATION = Permissions.WRITE_REQUIRES_AUTHORIZATION
|
|
772
810
|
|
|
773
|
-
value: Union[
|
|
811
|
+
value: Union[bytes, AttributeValue]
|
|
774
812
|
|
|
775
813
|
def __init__(
|
|
776
814
|
self,
|
|
777
815
|
attribute_type: Union[str, bytes, UUID],
|
|
778
816
|
permissions: Union[str, Attribute.Permissions],
|
|
779
|
-
value: Union[str, bytes,
|
|
817
|
+
value: Union[str, bytes, AttributeValue] = b'',
|
|
780
818
|
) -> None:
|
|
781
819
|
EventEmitter.__init__(self)
|
|
782
820
|
self.handle = 0
|
|
@@ -806,7 +844,7 @@ class Attribute(EventEmitter):
|
|
|
806
844
|
def decode_value(self, value_bytes: bytes) -> Any:
|
|
807
845
|
return value_bytes
|
|
808
846
|
|
|
809
|
-
def read_value(self, connection: Optional[Connection]) -> bytes:
|
|
847
|
+
async def read_value(self, connection: Optional[Connection]) -> bytes:
|
|
810
848
|
if (
|
|
811
849
|
(self.permissions & self.READ_REQUIRES_ENCRYPTION)
|
|
812
850
|
and connection is not None
|
|
@@ -832,6 +870,8 @@ class Attribute(EventEmitter):
|
|
|
832
870
|
if hasattr(self.value, 'read'):
|
|
833
871
|
try:
|
|
834
872
|
value = self.value.read(connection)
|
|
873
|
+
if inspect.isawaitable(value):
|
|
874
|
+
value = await value
|
|
835
875
|
except ATT_Error as error:
|
|
836
876
|
raise ATT_Error(
|
|
837
877
|
error_code=error.error_code, att_handle=self.handle
|
|
@@ -841,7 +881,7 @@ class Attribute(EventEmitter):
|
|
|
841
881
|
|
|
842
882
|
return self.encode_value(value)
|
|
843
883
|
|
|
844
|
-
def write_value(self, connection: Connection, value_bytes: bytes) -> None:
|
|
884
|
+
async def write_value(self, connection: Connection, value_bytes: bytes) -> None:
|
|
845
885
|
if (
|
|
846
886
|
self.permissions & self.WRITE_REQUIRES_ENCRYPTION
|
|
847
887
|
) and not connection.encryption:
|
|
@@ -864,7 +904,9 @@ class Attribute(EventEmitter):
|
|
|
864
904
|
|
|
865
905
|
if hasattr(self.value, 'write'):
|
|
866
906
|
try:
|
|
867
|
-
self.value.write(connection, value)
|
|
907
|
+
result = self.value.write(connection, value)
|
|
908
|
+
if inspect.isawaitable(result):
|
|
909
|
+
await result
|
|
868
910
|
except ATT_Error as error:
|
|
869
911
|
raise ATT_Error(
|
|
870
912
|
error_code=error.error_code, att_handle=self.handle
|
bumble/controller.py
CHANGED
|
@@ -134,12 +134,14 @@ class Controller:
|
|
|
134
134
|
'0000000060000000'
|
|
135
135
|
) # BR/EDR Not Supported, LE Supported (Controller)
|
|
136
136
|
self.manufacturer_name = 0xFFFF
|
|
137
|
+
self.hc_data_packet_length = 27
|
|
138
|
+
self.hc_total_num_data_packets = 64
|
|
137
139
|
self.hc_le_data_packet_length = 27
|
|
138
140
|
self.hc_total_num_le_data_packets = 64
|
|
139
141
|
self.event_mask = 0
|
|
140
142
|
self.event_mask_page_2 = 0
|
|
141
143
|
self.supported_commands = bytes.fromhex(
|
|
142
|
-
'
|
|
144
|
+
'2000800000c000000000e4000000a822000000000000040000f7ffff7f000000'
|
|
143
145
|
'30f0f9ff01008004000000000000000000000000000000000000000000000000'
|
|
144
146
|
)
|
|
145
147
|
self.le_event_mask = 0
|
|
@@ -914,6 +916,19 @@ class Controller:
|
|
|
914
916
|
'''
|
|
915
917
|
return bytes([HCI_SUCCESS]) + self.lmp_features
|
|
916
918
|
|
|
919
|
+
def on_hci_read_buffer_size_command(self, _command):
|
|
920
|
+
'''
|
|
921
|
+
See Bluetooth spec Vol 4, Part E - 7.4.5 Read Buffer Size Command
|
|
922
|
+
'''
|
|
923
|
+
return struct.pack(
|
|
924
|
+
'<BHBHH',
|
|
925
|
+
HCI_SUCCESS,
|
|
926
|
+
self.hc_data_packet_length,
|
|
927
|
+
0,
|
|
928
|
+
self.hc_total_num_data_packets,
|
|
929
|
+
0,
|
|
930
|
+
)
|
|
931
|
+
|
|
917
932
|
def on_hci_read_bd_addr_command(self, _command):
|
|
918
933
|
'''
|
|
919
934
|
See Bluetooth spec Vol 4, Part E - 7.4.6 Read BD_ADDR Command
|
|
@@ -1263,3 +1278,15 @@ class Controller:
|
|
|
1263
1278
|
See Bluetooth spec Vol 4, Part E - 7.8.74 LE Read Transmit Power Command
|
|
1264
1279
|
'''
|
|
1265
1280
|
return struct.pack('<BBB', HCI_SUCCESS, 0, 0)
|
|
1281
|
+
|
|
1282
|
+
def on_hci_le_setup_iso_data_path_command(self, command):
|
|
1283
|
+
'''
|
|
1284
|
+
See Bluetooth spec Vol 4, Part E - 7.8.109 LE Setup ISO Data Path Command
|
|
1285
|
+
'''
|
|
1286
|
+
return struct.pack('<BH', HCI_SUCCESS, command.connection_handle)
|
|
1287
|
+
|
|
1288
|
+
def on_hci_le_remove_iso_data_path_command(self, command):
|
|
1289
|
+
'''
|
|
1290
|
+
See Bluetooth spec Vol 4, Part E - 7.8.110 LE Remove ISO Data Path Command
|
|
1291
|
+
'''
|
|
1292
|
+
return struct.pack('<BH', HCI_SUCCESS, command.connection_handle)
|
bumble/crypto.py
CHANGED
|
@@ -100,6 +100,16 @@ class EccKey:
|
|
|
100
100
|
# -----------------------------------------------------------------------------
|
|
101
101
|
|
|
102
102
|
|
|
103
|
+
# -----------------------------------------------------------------------------
|
|
104
|
+
def generate_prand() -> bytes:
|
|
105
|
+
'''Generates random 3 bytes, with the 2 most significant bits of 0b01.
|
|
106
|
+
|
|
107
|
+
See Bluetooth spec, Vol 6, Part E - Table 1.2.
|
|
108
|
+
'''
|
|
109
|
+
prand_bytes = secrets.token_bytes(6)
|
|
110
|
+
return prand_bytes[:2] + bytes([(prand_bytes[2] & 0b01111111) | 0b01000000])
|
|
111
|
+
|
|
112
|
+
|
|
103
113
|
# -----------------------------------------------------------------------------
|
|
104
114
|
def xor(x: bytes, y: bytes) -> bytes:
|
|
105
115
|
assert len(x) == len(y)
|