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.
- bumble/_version.py +2 -2
- bumble/apps/bench.py +397 -133
- bumble/apps/ble_rpa_tool.py +63 -0
- bumble/apps/console.py +4 -4
- bumble/apps/controller_info.py +64 -6
- bumble/apps/controller_loopback.py +200 -0
- bumble/apps/l2cap_bridge.py +32 -24
- bumble/apps/pair.py +6 -8
- bumble/att.py +53 -11
- bumble/controller.py +159 -24
- bumble/crypto.py +10 -0
- bumble/device.py +580 -113
- 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 +258 -91
- bumble/helpers.py +14 -0
- bumble/hfp.py +37 -27
- bumble/hid.py +282 -61
- bumble/host.py +158 -93
- bumble/l2cap.py +11 -6
- bumble/link.py +55 -1
- bumble/profiles/asha_service.py +2 -2
- bumble/profiles/bap.py +1247 -0
- bumble/profiles/cap.py +52 -0
- bumble/profiles/csip.py +119 -9
- bumble/rfcomm.py +31 -20
- bumble/smp.py +1 -1
- bumble/transport/__init__.py +51 -22
- 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.182.dist-info}/METADATA +1 -1
- {bumble-0.0.180.dist-info → bumble-0.0.182.dist-info}/RECORD +42 -37
- {bumble-0.0.180.dist-info → bumble-0.0.182.dist-info}/entry_points.txt +1 -0
- {bumble-0.0.180.dist-info → bumble-0.0.182.dist-info}/LICENSE +0 -0
- {bumble-0.0.180.dist-info → bumble-0.0.182.dist-info}/WHEEL +0 -0
- {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
|
|
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
|
@@ -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.
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
'
|
|
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)
|