bumble 0.0.179__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/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):
@@ -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.done.set_result(None)
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=True, is_flag=True, help='Linger after pairing')
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 ConnectionValue(Protocol):
726
- def read(self, connection) -> bytes:
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
- def write(self, connection, value: bytes) -> None:
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[str, bytes, ConnectionValue]
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, ConnectionValue] = b'',
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) # pylint: disable=not-callable
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
- '2000800000c000000000e40000002822000000000000040000f7ffff7f000000'
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)