bumble 0.0.180__py3-none-any.whl → 0.0.182__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.
Files changed (42) hide show
  1. bumble/_version.py +2 -2
  2. bumble/apps/bench.py +397 -133
  3. bumble/apps/ble_rpa_tool.py +63 -0
  4. bumble/apps/console.py +4 -4
  5. bumble/apps/controller_info.py +64 -6
  6. bumble/apps/controller_loopback.py +200 -0
  7. bumble/apps/l2cap_bridge.py +32 -24
  8. bumble/apps/pair.py +6 -8
  9. bumble/att.py +53 -11
  10. bumble/controller.py +159 -24
  11. bumble/crypto.py +10 -0
  12. bumble/device.py +580 -113
  13. bumble/drivers/__init__.py +27 -31
  14. bumble/drivers/common.py +45 -0
  15. bumble/drivers/rtk.py +11 -4
  16. bumble/gatt.py +66 -51
  17. bumble/gatt_server.py +30 -22
  18. bumble/hci.py +258 -91
  19. bumble/helpers.py +14 -0
  20. bumble/hfp.py +37 -27
  21. bumble/hid.py +282 -61
  22. bumble/host.py +158 -93
  23. bumble/l2cap.py +11 -6
  24. bumble/link.py +55 -1
  25. bumble/profiles/asha_service.py +2 -2
  26. bumble/profiles/bap.py +1247 -0
  27. bumble/profiles/cap.py +52 -0
  28. bumble/profiles/csip.py +119 -9
  29. bumble/rfcomm.py +31 -20
  30. bumble/smp.py +1 -1
  31. bumble/transport/__init__.py +51 -22
  32. bumble/transport/android_emulator.py +1 -1
  33. bumble/transport/common.py +2 -1
  34. bumble/transport/hci_socket.py +1 -4
  35. bumble/transport/usb.py +1 -1
  36. bumble/utils.py +3 -6
  37. {bumble-0.0.180.dist-info → bumble-0.0.182.dist-info}/METADATA +1 -1
  38. {bumble-0.0.180.dist-info → bumble-0.0.182.dist-info}/RECORD +42 -37
  39. {bumble-0.0.180.dist-info → bumble-0.0.182.dist-info}/entry_points.txt +1 -0
  40. {bumble-0.0.180.dist-info → bumble-0.0.182.dist-info}/LICENSE +0 -0
  41. {bumble-0.0.180.dist-info → bumble-0.0.182.dist-info}/WHEEL +0 -0
  42. {bumble-0.0.180.dist-info → bumble-0.0.182.dist-info}/top_level.txt +0 -0
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
@@ -19,6 +19,7 @@ from __future__ import annotations
19
19
 
20
20
  import logging
21
21
  import asyncio
22
+ import dataclasses
22
23
  import itertools
23
24
  import random
24
25
  import struct
@@ -42,6 +43,7 @@ from bumble.hci import (
42
43
  HCI_LE_1M_PHY,
43
44
  HCI_SUCCESS,
44
45
  HCI_UNKNOWN_HCI_COMMAND_ERROR,
46
+ HCI_UNKNOWN_CONNECTION_IDENTIFIER_ERROR,
45
47
  HCI_REMOTE_USER_TERMINATED_CONNECTION_ERROR,
46
48
  HCI_VERSION_BLUETOOTH_CORE_5_0,
47
49
  Address,
@@ -53,6 +55,7 @@ from bumble.hci import (
53
55
  HCI_Connection_Request_Event,
54
56
  HCI_Disconnection_Complete_Event,
55
57
  HCI_Encryption_Change_Event,
58
+ HCI_Synchronous_Connection_Complete_Event,
56
59
  HCI_LE_Advertising_Report_Event,
57
60
  HCI_LE_Connection_Complete_Event,
58
61
  HCI_LE_Read_Remote_Features_Complete_Event,
@@ -60,10 +63,11 @@ from bumble.hci import (
60
63
  HCI_Packet,
61
64
  HCI_Role_Change_Event,
62
65
  )
63
- from typing import Optional, Union, Dict, TYPE_CHECKING
66
+ from typing import Optional, Union, Dict, Any, TYPE_CHECKING
64
67
 
65
68
  if TYPE_CHECKING:
66
- from bumble.transport.common import TransportSink, TransportSource
69
+ from bumble.link import LocalLink
70
+ from bumble.transport.common import TransportSink
67
71
 
68
72
  # -----------------------------------------------------------------------------
69
73
  # Logging
@@ -79,15 +83,18 @@ class DataObject:
79
83
 
80
84
 
81
85
  # -----------------------------------------------------------------------------
86
+ @dataclasses.dataclass
82
87
  class Connection:
83
- def __init__(self, controller, handle, role, peer_address, link, transport):
84
- self.controller = controller
85
- self.handle = handle
86
- self.role = role
87
- self.peer_address = peer_address
88
- self.link = link
88
+ controller: Controller
89
+ handle: int
90
+ role: int
91
+ peer_address: Address
92
+ link: Any
93
+ transport: int
94
+ link_type: int
95
+
96
+ def __post_init__(self):
89
97
  self.assembler = HCI_AclDataPacketAssembler(self.on_acl_pdu)
90
- self.transport = transport
91
98
 
92
99
  def on_hci_acl_data_packet(self, packet):
93
100
  self.assembler.feed_packet(packet)
@@ -106,10 +113,10 @@ class Connection:
106
113
  class Controller:
107
114
  def __init__(
108
115
  self,
109
- name,
116
+ name: str,
110
117
  host_source=None,
111
118
  host_sink: Optional[TransportSink] = None,
112
- link=None,
119
+ link: Optional[LocalLink] = None,
113
120
  public_address: Optional[Union[bytes, str, Address]] = None,
114
121
  ):
115
122
  self.name = name
@@ -134,12 +141,14 @@ class Controller:
134
141
  '0000000060000000'
135
142
  ) # BR/EDR Not Supported, LE Supported (Controller)
136
143
  self.manufacturer_name = 0xFFFF
144
+ self.hc_data_packet_length = 27
145
+ self.hc_total_num_data_packets = 64
137
146
  self.hc_le_data_packet_length = 27
138
147
  self.hc_total_num_le_data_packets = 64
139
148
  self.event_mask = 0
140
149
  self.event_mask_page_2 = 0
141
150
  self.supported_commands = bytes.fromhex(
142
- '2000800000c000000000e40000002822000000000000040000f7ffff7f000000'
151
+ '2000800000c000000000e4000000a822000000000000040000f7ffff7f000000'
143
152
  '30f0f9ff01008004000000000000000000000000000000000000000000000000'
144
153
  )
145
154
  self.le_event_mask = 0
@@ -357,12 +366,13 @@ class Controller:
357
366
  if connection is None:
358
367
  connection_handle = self.allocate_connection_handle()
359
368
  connection = Connection(
360
- self,
361
- connection_handle,
362
- BT_PERIPHERAL_ROLE,
363
- peer_address,
364
- self.link,
365
- BT_LE_TRANSPORT,
369
+ controller=self,
370
+ handle=connection_handle,
371
+ role=BT_PERIPHERAL_ROLE,
372
+ peer_address=peer_address,
373
+ link=self.link,
374
+ transport=BT_LE_TRANSPORT,
375
+ link_type=HCI_Connection_Complete_Event.ACL_LINK_TYPE,
366
376
  )
367
377
  self.peripheral_connections[peer_address] = connection
368
378
  logger.debug(f'New PERIPHERAL connection handle: 0x{connection_handle:04X}')
@@ -416,12 +426,13 @@ class Controller:
416
426
  if connection is None:
417
427
  connection_handle = self.allocate_connection_handle()
418
428
  connection = Connection(
419
- self,
420
- connection_handle,
421
- BT_CENTRAL_ROLE,
422
- peer_address,
423
- self.link,
424
- BT_LE_TRANSPORT,
429
+ controller=self,
430
+ handle=connection_handle,
431
+ role=BT_CENTRAL_ROLE,
432
+ peer_address=peer_address,
433
+ link=self.link,
434
+ transport=BT_LE_TRANSPORT,
435
+ link_type=HCI_Connection_Complete_Event.ACL_LINK_TYPE,
425
436
  )
426
437
  self.central_connections[peer_address] = connection
427
438
  logger.debug(
@@ -566,6 +577,7 @@ class Controller:
566
577
  peer_address=peer_address,
567
578
  link=self.link,
568
579
  transport=BT_BR_EDR_TRANSPORT,
580
+ link_type=HCI_Connection_Complete_Event.ACL_LINK_TYPE,
569
581
  )
570
582
  self.classic_connections[peer_address] = connection
571
583
  logger.debug(
@@ -619,6 +631,42 @@ class Controller:
619
631
  )
620
632
  )
621
633
 
634
+ def on_classic_sco_connection_complete(
635
+ self, peer_address: Address, status: int, link_type: int
636
+ ):
637
+ if status == HCI_SUCCESS:
638
+ # Allocate (or reuse) a connection handle
639
+ connection_handle = self.allocate_connection_handle()
640
+ connection = Connection(
641
+ controller=self,
642
+ handle=connection_handle,
643
+ # Role doesn't matter in SCO.
644
+ role=BT_CENTRAL_ROLE,
645
+ peer_address=peer_address,
646
+ link=self.link,
647
+ transport=BT_BR_EDR_TRANSPORT,
648
+ link_type=link_type,
649
+ )
650
+ self.classic_connections[peer_address] = connection
651
+ logger.debug(f'New SCO connection handle: 0x{connection_handle:04X}')
652
+ else:
653
+ connection_handle = 0
654
+
655
+ self.send_hci_packet(
656
+ HCI_Synchronous_Connection_Complete_Event(
657
+ status=status,
658
+ connection_handle=connection_handle,
659
+ bd_addr=peer_address,
660
+ link_type=link_type,
661
+ # TODO: Provide SCO connection parameters.
662
+ transmission_interval=0,
663
+ retransmission_window=0,
664
+ rx_packet_length=0,
665
+ tx_packet_length=0,
666
+ air_mode=0,
667
+ )
668
+ )
669
+
622
670
  ############################################################
623
671
  # Advertising support
624
672
  ############################################################
@@ -738,6 +786,68 @@ class Controller:
738
786
  )
739
787
  self.link.classic_accept_connection(self, command.bd_addr, command.role)
740
788
 
789
+ def on_hci_enhanced_setup_synchronous_connection_command(self, command):
790
+ '''
791
+ See Bluetooth spec Vol 4, Part E - 7.1.45 Enhanced Setup Synchronous Connection command
792
+ '''
793
+
794
+ if self.link is None:
795
+ return
796
+
797
+ if not (
798
+ connection := self.find_classic_connection_by_handle(
799
+ command.connection_handle
800
+ )
801
+ ):
802
+ self.send_hci_packet(
803
+ HCI_Command_Status_Event(
804
+ status=HCI_UNKNOWN_CONNECTION_IDENTIFIER_ERROR,
805
+ num_hci_command_packets=1,
806
+ command_opcode=command.op_code,
807
+ )
808
+ )
809
+ return
810
+
811
+ self.send_hci_packet(
812
+ HCI_Command_Status_Event(
813
+ status=HCI_SUCCESS,
814
+ num_hci_command_packets=1,
815
+ command_opcode=command.op_code,
816
+ )
817
+ )
818
+ self.link.classic_sco_connect(
819
+ self, connection.peer_address, HCI_Connection_Complete_Event.ESCO_LINK_TYPE
820
+ )
821
+
822
+ def on_hci_enhanced_accept_synchronous_connection_request_command(self, command):
823
+ '''
824
+ See Bluetooth spec Vol 4, Part E - 7.1.46 Enhanced Accept Synchronous Connection Request command
825
+ '''
826
+
827
+ if self.link is None:
828
+ return
829
+
830
+ if not (connection := self.find_classic_connection_by_address(command.bd_addr)):
831
+ self.send_hci_packet(
832
+ HCI_Command_Status_Event(
833
+ status=HCI_UNKNOWN_CONNECTION_IDENTIFIER_ERROR,
834
+ num_hci_command_packets=1,
835
+ command_opcode=command.op_code,
836
+ )
837
+ )
838
+ return
839
+
840
+ self.send_hci_packet(
841
+ HCI_Command_Status_Event(
842
+ status=HCI_SUCCESS,
843
+ num_hci_command_packets=1,
844
+ command_opcode=command.op_code,
845
+ )
846
+ )
847
+ self.link.classic_accept_sco_connection(
848
+ self, connection.peer_address, HCI_Connection_Complete_Event.ESCO_LINK_TYPE
849
+ )
850
+
741
851
  def on_hci_switch_role_command(self, command):
742
852
  '''
743
853
  See Bluetooth spec Vol 4, Part E - 7.2.8 Switch Role command
@@ -914,6 +1024,19 @@ class Controller:
914
1024
  '''
915
1025
  return bytes([HCI_SUCCESS]) + self.lmp_features
916
1026
 
1027
+ def on_hci_read_buffer_size_command(self, _command):
1028
+ '''
1029
+ See Bluetooth spec Vol 4, Part E - 7.4.5 Read Buffer Size Command
1030
+ '''
1031
+ return struct.pack(
1032
+ '<BHBHH',
1033
+ HCI_SUCCESS,
1034
+ self.hc_data_packet_length,
1035
+ 0,
1036
+ self.hc_total_num_data_packets,
1037
+ 0,
1038
+ )
1039
+
917
1040
  def on_hci_read_bd_addr_command(self, _command):
918
1041
  '''
919
1042
  See Bluetooth spec Vol 4, Part E - 7.4.6 Read BD_ADDR Command
@@ -1263,3 +1386,15 @@ class Controller:
1263
1386
  See Bluetooth spec Vol 4, Part E - 7.8.74 LE Read Transmit Power Command
1264
1387
  '''
1265
1388
  return struct.pack('<BBB', HCI_SUCCESS, 0, 0)
1389
+
1390
+ def on_hci_le_setup_iso_data_path_command(self, command):
1391
+ '''
1392
+ See Bluetooth spec Vol 4, Part E - 7.8.109 LE Setup ISO Data Path Command
1393
+ '''
1394
+ return struct.pack('<BH', HCI_SUCCESS, command.connection_handle)
1395
+
1396
+ def on_hci_le_remove_iso_data_path_command(self, command):
1397
+ '''
1398
+ See Bluetooth spec Vol 4, Part E - 7.8.110 LE Remove ISO Data Path Command
1399
+ '''
1400
+ 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)