bumble 0.0.147__py3-none-any.whl → 0.0.149__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 +4 -2
- bumble/apps/console.py +213 -17
- bumble/apps/gg_bridge.py +3 -4
- bumble/apps/pair.py +21 -4
- bumble/att.py +4 -4
- bumble/device.py +128 -100
- bumble/gap.py +2 -2
- bumble/gatt.py +61 -51
- bumble/gatt_client.py +63 -10
- bumble/gatt_server.py +20 -2
- bumble/host.py +12 -17
- bumble/keys.py +27 -11
- bumble/pairing.py +184 -0
- bumble/profiles/asha_service.py +6 -5
- bumble/profiles/battery_service.py +1 -1
- bumble/profiles/device_information_service.py +5 -3
- bumble/profiles/heart_rate_service.py +3 -3
- bumble/rfcomm.py +1 -1
- bumble/smp.py +21 -88
- {bumble-0.0.147.dist-info → bumble-0.0.149.dist-info}/LICENSE +19 -0
- {bumble-0.0.147.dist-info → bumble-0.0.149.dist-info}/METADATA +3 -1
- {bumble-0.0.147.dist-info → bumble-0.0.149.dist-info}/RECORD +26 -25
- {bumble-0.0.147.dist-info → bumble-0.0.149.dist-info}/WHEEL +0 -0
- {bumble-0.0.147.dist-info → bumble-0.0.149.dist-info}/entry_points.txt +0 -0
- {bumble-0.0.147.dist-info → bumble-0.0.149.dist-info}/top_level.txt +0 -0
bumble/device.py
CHANGED
|
@@ -29,11 +29,13 @@ from .colors import color
|
|
|
29
29
|
from .att import ATT_CID, ATT_DEFAULT_MTU, ATT_PDU
|
|
30
30
|
from .gatt import Characteristic, Descriptor, Service
|
|
31
31
|
from .hci import (
|
|
32
|
+
HCI_AUTHENTICATED_COMBINATION_KEY_GENERATED_FROM_P_192_TYPE,
|
|
33
|
+
HCI_AUTHENTICATED_COMBINATION_KEY_GENERATED_FROM_P_256_TYPE,
|
|
32
34
|
HCI_CENTRAL_ROLE,
|
|
33
35
|
HCI_COMMAND_STATUS_PENDING,
|
|
34
36
|
HCI_CONNECTION_REJECTED_DUE_TO_LIMITED_RESOURCES_ERROR,
|
|
35
|
-
HCI_DISPLAY_ONLY_IO_CAPABILITY,
|
|
36
37
|
HCI_DISPLAY_YES_NO_IO_CAPABILITY,
|
|
38
|
+
HCI_DISPLAY_ONLY_IO_CAPABILITY,
|
|
37
39
|
HCI_EXTENDED_INQUIRY_MODE,
|
|
38
40
|
HCI_GENERAL_INQUIRY_LAP,
|
|
39
41
|
HCI_INVALID_HCI_COMMAND_PARAMETERS_ERROR,
|
|
@@ -141,6 +143,7 @@ from .keys import (
|
|
|
141
143
|
KeyStore,
|
|
142
144
|
PairingKeys,
|
|
143
145
|
)
|
|
146
|
+
from .pairing import PairingConfig
|
|
144
147
|
from . import gatt_client
|
|
145
148
|
from . import gatt_server
|
|
146
149
|
from . import smp
|
|
@@ -198,6 +201,7 @@ DEVICE_DEFAULT_L2CAP_COC_MAX_CREDITS = l2cap.L2CAP_LE_CREDIT_BASED_CONN
|
|
|
198
201
|
# Classes
|
|
199
202
|
# -----------------------------------------------------------------------------
|
|
200
203
|
|
|
204
|
+
|
|
201
205
|
# -----------------------------------------------------------------------------
|
|
202
206
|
class Advertisement:
|
|
203
207
|
address: Address
|
|
@@ -529,6 +533,9 @@ class Connection(CompositeEventEmitter):
|
|
|
529
533
|
authenticated: bool
|
|
530
534
|
sc: bool
|
|
531
535
|
link_key_type: int
|
|
536
|
+
gatt_client: gatt_client.Client
|
|
537
|
+
pairing_peer_io_capability: Optional[int]
|
|
538
|
+
pairing_peer_authentication_requirements: Optional[int]
|
|
532
539
|
|
|
533
540
|
@composite_listener
|
|
534
541
|
class Listener:
|
|
@@ -592,10 +599,12 @@ class Connection(CompositeEventEmitter):
|
|
|
592
599
|
self.gatt_server = (
|
|
593
600
|
device.gatt_server
|
|
594
601
|
) # By default, use the device's shared server
|
|
602
|
+
self.pairing_peer_io_capability = None
|
|
603
|
+
self.pairing_peer_authentication_requirements = None
|
|
595
604
|
|
|
596
605
|
# [Classic only]
|
|
597
606
|
@classmethod
|
|
598
|
-
def incomplete(cls, device, peer_address):
|
|
607
|
+
def incomplete(cls, device, peer_address, role):
|
|
599
608
|
"""
|
|
600
609
|
Instantiate an incomplete connection (ie. one waiting for a HCI Connection
|
|
601
610
|
Complete event).
|
|
@@ -608,28 +617,30 @@ class Connection(CompositeEventEmitter):
|
|
|
608
617
|
device.public_address,
|
|
609
618
|
peer_address,
|
|
610
619
|
None,
|
|
611
|
-
|
|
620
|
+
role,
|
|
612
621
|
None,
|
|
613
622
|
None,
|
|
614
623
|
)
|
|
615
624
|
|
|
616
625
|
# [Classic only]
|
|
617
|
-
def complete(self, handle,
|
|
626
|
+
def complete(self, handle, parameters):
|
|
618
627
|
"""
|
|
619
628
|
Finish an incomplete connection upon completion.
|
|
620
629
|
"""
|
|
621
630
|
assert self.handle is None
|
|
622
631
|
assert self.transport == BT_BR_EDR_TRANSPORT
|
|
623
632
|
self.handle = handle
|
|
624
|
-
self.peer_resolvable_address = peer_resolvable_address
|
|
625
|
-
# Quirk: role might be known before complete
|
|
626
|
-
if self.role is None:
|
|
627
|
-
self.role = role
|
|
628
633
|
self.parameters = parameters
|
|
629
634
|
|
|
630
635
|
@property
|
|
631
636
|
def role_name(self):
|
|
632
|
-
|
|
637
|
+
if self.role is None:
|
|
638
|
+
return 'NOT-SET'
|
|
639
|
+
if self.role == BT_CENTRAL_ROLE:
|
|
640
|
+
return 'CENTRAL'
|
|
641
|
+
if self.role == BT_PERIPHERAL_ROLE:
|
|
642
|
+
return 'PERIPHERAL'
|
|
643
|
+
return f'UNKNOWN[{self.role}]'
|
|
633
644
|
|
|
634
645
|
@property
|
|
635
646
|
def is_encrypted(self):
|
|
@@ -637,7 +648,7 @@ class Connection(CompositeEventEmitter):
|
|
|
637
648
|
|
|
638
649
|
@property
|
|
639
650
|
def is_incomplete(self) -> bool:
|
|
640
|
-
return self.handle
|
|
651
|
+
return self.handle is None
|
|
641
652
|
|
|
642
653
|
def send_l2cap_pdu(self, cid, pdu):
|
|
643
654
|
self.device.send_l2cap_pdu(self.handle, cid, pdu)
|
|
@@ -750,10 +761,11 @@ class DeviceConfiguration:
|
|
|
750
761
|
self.advertising_interval_max = DEVICE_DEFAULT_ADVERTISING_INTERVAL
|
|
751
762
|
self.le_enabled = True
|
|
752
763
|
# LE host enable 2nd parameter
|
|
753
|
-
self.le_simultaneous_enabled =
|
|
764
|
+
self.le_simultaneous_enabled = False
|
|
754
765
|
self.classic_enabled = False
|
|
755
766
|
self.classic_sc_enabled = True
|
|
756
767
|
self.classic_ssp_enabled = True
|
|
768
|
+
self.classic_smp_enabled = True
|
|
757
769
|
self.classic_accept_any = True
|
|
758
770
|
self.connectable = True
|
|
759
771
|
self.discoverable = True
|
|
@@ -788,6 +800,9 @@ class DeviceConfiguration:
|
|
|
788
800
|
self.classic_ssp_enabled = config.get(
|
|
789
801
|
'classic_ssp_enabled', self.classic_ssp_enabled
|
|
790
802
|
)
|
|
803
|
+
self.classic_smp_enabled = config.get(
|
|
804
|
+
'classic_smp_enabled', self.classic_smp_enabled
|
|
805
|
+
)
|
|
791
806
|
self.classic_accept_any = config.get(
|
|
792
807
|
'classic_accept_any', self.classic_accept_any
|
|
793
808
|
)
|
|
@@ -878,7 +893,7 @@ device_host_event_handlers: list[str] = []
|
|
|
878
893
|
|
|
879
894
|
# -----------------------------------------------------------------------------
|
|
880
895
|
class Device(CompositeEventEmitter):
|
|
881
|
-
#
|
|
896
|
+
# Incomplete list of fields.
|
|
882
897
|
random_address: Address
|
|
883
898
|
public_address: Address
|
|
884
899
|
classic_enabled: bool
|
|
@@ -893,6 +908,7 @@ class Device(CompositeEventEmitter):
|
|
|
893
908
|
Address, List[asyncio.Future[Union[Connection, Tuple[Address, int, int]]]]
|
|
894
909
|
]
|
|
895
910
|
advertisement_accumulators: Dict[Address, AdvertisementDataAccumulator]
|
|
911
|
+
config: DeviceConfiguration
|
|
896
912
|
|
|
897
913
|
@composite_listener
|
|
898
914
|
class Listener:
|
|
@@ -980,9 +996,10 @@ class Device(CompositeEventEmitter):
|
|
|
980
996
|
self.connect_own_address_type = None
|
|
981
997
|
|
|
982
998
|
# Use the initial config or a default
|
|
999
|
+
config = config or DeviceConfiguration()
|
|
1000
|
+
self.config = config
|
|
1001
|
+
|
|
983
1002
|
self.public_address = Address('00:00:00:00:00:00')
|
|
984
|
-
if config is None:
|
|
985
|
-
config = DeviceConfiguration()
|
|
986
1003
|
self.name = config.name
|
|
987
1004
|
self.random_address = config.address
|
|
988
1005
|
self.class_of_device = config.class_of_device
|
|
@@ -990,13 +1007,14 @@ class Device(CompositeEventEmitter):
|
|
|
990
1007
|
self.advertising_data = config.advertising_data
|
|
991
1008
|
self.advertising_interval_min = config.advertising_interval_min
|
|
992
1009
|
self.advertising_interval_max = config.advertising_interval_max
|
|
993
|
-
self.keystore =
|
|
1010
|
+
self.keystore = None
|
|
994
1011
|
self.irk = config.irk
|
|
995
1012
|
self.le_enabled = config.le_enabled
|
|
996
1013
|
self.classic_enabled = config.classic_enabled
|
|
997
1014
|
self.le_simultaneous_enabled = config.le_simultaneous_enabled
|
|
998
|
-
self.classic_ssp_enabled = config.classic_ssp_enabled
|
|
999
1015
|
self.classic_sc_enabled = config.classic_sc_enabled
|
|
1016
|
+
self.classic_ssp_enabled = config.classic_ssp_enabled
|
|
1017
|
+
self.classic_smp_enabled = config.classic_smp_enabled
|
|
1000
1018
|
self.discoverable = config.discoverable
|
|
1001
1019
|
self.connectable = config.connectable
|
|
1002
1020
|
self.classic_accept_any = config.classic_accept_any
|
|
@@ -1018,7 +1036,9 @@ class Device(CompositeEventEmitter):
|
|
|
1018
1036
|
descriptors.append(new_descriptor)
|
|
1019
1037
|
new_characteristic = Characteristic(
|
|
1020
1038
|
uuid=characteristic["uuid"],
|
|
1021
|
-
properties=
|
|
1039
|
+
properties=Characteristic.Properties.from_string(
|
|
1040
|
+
characteristic["properties"]
|
|
1041
|
+
),
|
|
1022
1042
|
permissions=characteristic["permissions"],
|
|
1023
1043
|
descriptors=descriptors,
|
|
1024
1044
|
)
|
|
@@ -1037,12 +1057,12 @@ class Device(CompositeEventEmitter):
|
|
|
1037
1057
|
self.random_address = address
|
|
1038
1058
|
|
|
1039
1059
|
# Setup SMP
|
|
1040
|
-
self.smp_manager = smp.Manager(
|
|
1041
|
-
|
|
1042
|
-
self.l2cap_channel_manager.register_fixed_channel(
|
|
1043
|
-
smp.SMP_BR_CID, self.on_smp_pdu
|
|
1060
|
+
self.smp_manager = smp.Manager(
|
|
1061
|
+
self, pairing_config_factory=lambda connection: PairingConfig()
|
|
1044
1062
|
)
|
|
1045
1063
|
|
|
1064
|
+
self.l2cap_channel_manager.register_fixed_channel(smp.SMP_CID, self.on_smp_pdu)
|
|
1065
|
+
|
|
1046
1066
|
# Register the SDP server with the L2CAP Channel Manager
|
|
1047
1067
|
self.sdp_server.register(self.l2cap_channel_manager)
|
|
1048
1068
|
|
|
@@ -1165,6 +1185,7 @@ class Device(CompositeEventEmitter):
|
|
|
1165
1185
|
# Reset the controller
|
|
1166
1186
|
await self.host.reset()
|
|
1167
1187
|
|
|
1188
|
+
# Try to get the public address from the controller
|
|
1168
1189
|
response = await self.send_command(HCI_Read_BD_ADDR_Command()) # type: ignore[call-arg]
|
|
1169
1190
|
if response.return_parameters.status == HCI_SUCCESS:
|
|
1170
1191
|
logger.debug(
|
|
@@ -1172,6 +1193,17 @@ class Device(CompositeEventEmitter):
|
|
|
1172
1193
|
)
|
|
1173
1194
|
self.public_address = response.return_parameters.bd_addr
|
|
1174
1195
|
|
|
1196
|
+
# Instantiate the Key Store (we do this here rather than at __init__ time
|
|
1197
|
+
# because some Key Store implementations use the public address as a namespace)
|
|
1198
|
+
if self.keystore is None:
|
|
1199
|
+
self.keystore = KeyStore.create_for_device(self)
|
|
1200
|
+
|
|
1201
|
+
# Finish setting up SMP based on post-init configurable options
|
|
1202
|
+
if self.classic_smp_enabled:
|
|
1203
|
+
self.l2cap_channel_manager.register_fixed_channel(
|
|
1204
|
+
smp.SMP_BR_CID, self.on_smp_pdu
|
|
1205
|
+
)
|
|
1206
|
+
|
|
1175
1207
|
if self.host.supports_command(HCI_WRITE_LE_HOST_SUPPORT_COMMAND):
|
|
1176
1208
|
await self.send_command(
|
|
1177
1209
|
HCI_Write_LE_Host_Support_Command(
|
|
@@ -1219,7 +1251,7 @@ class Device(CompositeEventEmitter):
|
|
|
1219
1251
|
await self.send_command(HCI_LE_Clear_Resolving_List_Command()) # type: ignore[call-arg]
|
|
1220
1252
|
|
|
1221
1253
|
resolving_keys = await self.keystore.get_resolving_keys()
|
|
1222
|
-
for
|
|
1254
|
+
for irk, address in resolving_keys:
|
|
1223
1255
|
await self.send_command(
|
|
1224
1256
|
HCI_LE_Add_Device_To_Resolving_List_Command(
|
|
1225
1257
|
peer_identity_address_type=address.address_type,
|
|
@@ -1600,7 +1632,7 @@ class Device(CompositeEventEmitter):
|
|
|
1600
1632
|
pending connection.
|
|
1601
1633
|
|
|
1602
1634
|
connection_parameters_preferences: (BLE only, ignored for BR/EDR)
|
|
1603
|
-
* None: use
|
|
1635
|
+
* None: use the 1M PHY with default parameters
|
|
1604
1636
|
* map: each entry has a PHY as key and a ConnectionParametersPreferences
|
|
1605
1637
|
object as value
|
|
1606
1638
|
|
|
@@ -1669,9 +1701,7 @@ class Device(CompositeEventEmitter):
|
|
|
1669
1701
|
if connection_parameters_preferences is None:
|
|
1670
1702
|
if connection_parameters_preferences is None:
|
|
1671
1703
|
connection_parameters_preferences = {
|
|
1672
|
-
HCI_LE_1M_PHY: ConnectionParametersPreferences.default
|
|
1673
|
-
HCI_LE_2M_PHY: ConnectionParametersPreferences.default,
|
|
1674
|
-
HCI_LE_CODED_PHY: ConnectionParametersPreferences.default,
|
|
1704
|
+
HCI_LE_1M_PHY: ConnectionParametersPreferences.default
|
|
1675
1705
|
}
|
|
1676
1706
|
|
|
1677
1707
|
self.connect_own_address_type = own_address_type
|
|
@@ -1793,7 +1823,7 @@ class Device(CompositeEventEmitter):
|
|
|
1793
1823
|
else:
|
|
1794
1824
|
# Save pending connection
|
|
1795
1825
|
self.pending_connections[peer_address] = Connection.incomplete(
|
|
1796
|
-
self, peer_address
|
|
1826
|
+
self, peer_address, BT_CENTRAL_ROLE
|
|
1797
1827
|
)
|
|
1798
1828
|
|
|
1799
1829
|
# TODO: allow passing other settings
|
|
@@ -1930,9 +1960,12 @@ class Device(CompositeEventEmitter):
|
|
|
1930
1960
|
self.on('connection', on_connection)
|
|
1931
1961
|
self.on('connection_failure', on_connection_failure)
|
|
1932
1962
|
|
|
1933
|
-
# Save pending connection
|
|
1963
|
+
# Save pending connection, with the Peripheral role.
|
|
1964
|
+
# Even if we requested a role switch in the HCI_Accept_Connection_Request
|
|
1965
|
+
# command, this connection is still considered Peripheral until an eventual
|
|
1966
|
+
# role change event.
|
|
1934
1967
|
self.pending_connections[peer_address] = Connection.incomplete(
|
|
1935
|
-
self, peer_address
|
|
1968
|
+
self, peer_address, BT_PERIPHERAL_ROLE
|
|
1936
1969
|
)
|
|
1937
1970
|
|
|
1938
1971
|
try:
|
|
@@ -2205,6 +2238,10 @@ class Device(CompositeEventEmitter):
|
|
|
2205
2238
|
keys = await self.keystore.get(str(address))
|
|
2206
2239
|
if keys is not None:
|
|
2207
2240
|
logger.debug('found keys in the key store')
|
|
2241
|
+
if keys.link_key is None:
|
|
2242
|
+
logger.warning('no link key')
|
|
2243
|
+
return None
|
|
2244
|
+
|
|
2208
2245
|
return keys.link_key.value
|
|
2209
2246
|
|
|
2210
2247
|
# [Classic only]
|
|
@@ -2409,8 +2446,14 @@ class Device(CompositeEventEmitter):
|
|
|
2409
2446
|
def on_link_key(self, bd_addr, link_key, key_type):
|
|
2410
2447
|
# Store the keys in the key store
|
|
2411
2448
|
if self.keystore:
|
|
2449
|
+
authenticated = key_type in (
|
|
2450
|
+
HCI_AUTHENTICATED_COMBINATION_KEY_GENERATED_FROM_P_192_TYPE,
|
|
2451
|
+
HCI_AUTHENTICATED_COMBINATION_KEY_GENERATED_FROM_P_256_TYPE,
|
|
2452
|
+
)
|
|
2412
2453
|
pairing_keys = PairingKeys()
|
|
2413
|
-
pairing_keys.link_key = PairingKeys.Key(
|
|
2454
|
+
pairing_keys.link_key = PairingKeys.Key(
|
|
2455
|
+
value=link_key, authenticated=authenticated
|
|
2456
|
+
)
|
|
2414
2457
|
|
|
2415
2458
|
async def store_keys():
|
|
2416
2459
|
try:
|
|
@@ -2454,25 +2497,24 @@ class Device(CompositeEventEmitter):
|
|
|
2454
2497
|
connection_handle,
|
|
2455
2498
|
transport,
|
|
2456
2499
|
peer_address,
|
|
2457
|
-
peer_resolvable_address,
|
|
2458
2500
|
role,
|
|
2459
2501
|
connection_parameters,
|
|
2460
2502
|
):
|
|
2461
2503
|
logger.debug(
|
|
2462
2504
|
f'*** Connection: [0x{connection_handle:04X}] '
|
|
2463
|
-
f'{peer_address}
|
|
2505
|
+
f'{peer_address} {"" if role is None else HCI_Constant.role_name(role)}'
|
|
2464
2506
|
)
|
|
2465
2507
|
if connection_handle in self.connections:
|
|
2466
2508
|
logger.warning(
|
|
2467
2509
|
'new connection reuses the same handle as a previous connection'
|
|
2468
2510
|
)
|
|
2469
2511
|
|
|
2512
|
+
peer_resolvable_address = None
|
|
2513
|
+
|
|
2470
2514
|
if transport == BT_BR_EDR_TRANSPORT:
|
|
2471
2515
|
# Create a new connection
|
|
2472
2516
|
connection = self.pending_connections.pop(peer_address)
|
|
2473
|
-
connection.complete(
|
|
2474
|
-
connection_handle, peer_resolvable_address, role, connection_parameters
|
|
2475
|
-
)
|
|
2517
|
+
connection.complete(connection_handle, connection_parameters)
|
|
2476
2518
|
self.connections[connection_handle] = connection
|
|
2477
2519
|
|
|
2478
2520
|
# Emit an event to notify listeners of the new connection
|
|
@@ -2584,7 +2626,9 @@ class Device(CompositeEventEmitter):
|
|
|
2584
2626
|
# device configuration is set to accept any incoming connection
|
|
2585
2627
|
elif self.classic_accept_any:
|
|
2586
2628
|
# Save pending connection
|
|
2587
|
-
self.pending_connections[bd_addr] = Connection.incomplete(
|
|
2629
|
+
self.pending_connections[bd_addr] = Connection.incomplete(
|
|
2630
|
+
self, bd_addr, BT_PERIPHERAL_ROLE
|
|
2631
|
+
)
|
|
2588
2632
|
|
|
2589
2633
|
self.host.send_command_sync(
|
|
2590
2634
|
HCI_Accept_Connection_Request_Command(
|
|
@@ -2675,7 +2719,7 @@ class Device(CompositeEventEmitter):
|
|
|
2675
2719
|
# On Secure Simple Pairing complete, in case:
|
|
2676
2720
|
# - Connection isn't already authenticated
|
|
2677
2721
|
# - AND we are not the initiator of the authentication
|
|
2678
|
-
# We must trigger authentication to
|
|
2722
|
+
# We must trigger authentication to know if we are truly authenticated
|
|
2679
2723
|
if not connection.authenticating and not connection.authenticated:
|
|
2680
2724
|
logger.debug(
|
|
2681
2725
|
f'*** Trigger Connection Authentication: [0x{connection.handle:04X}] '
|
|
@@ -2690,22 +2734,6 @@ class Device(CompositeEventEmitter):
|
|
|
2690
2734
|
# Ask what the pairing config should be for this connection
|
|
2691
2735
|
pairing_config = self.pairing_config_factory(connection)
|
|
2692
2736
|
|
|
2693
|
-
# Map the SMP IO capability to a Classic IO capability
|
|
2694
|
-
# pylint: disable=line-too-long
|
|
2695
|
-
io_capability = {
|
|
2696
|
-
smp.SMP_DISPLAY_ONLY_IO_CAPABILITY: HCI_DISPLAY_ONLY_IO_CAPABILITY,
|
|
2697
|
-
smp.SMP_DISPLAY_YES_NO_IO_CAPABILITY: HCI_DISPLAY_YES_NO_IO_CAPABILITY,
|
|
2698
|
-
smp.SMP_KEYBOARD_ONLY_IO_CAPABILITY: HCI_KEYBOARD_ONLY_IO_CAPABILITY,
|
|
2699
|
-
smp.SMP_NO_INPUT_NO_OUTPUT_IO_CAPABILITY: HCI_NO_INPUT_NO_OUTPUT_IO_CAPABILITY,
|
|
2700
|
-
smp.SMP_KEYBOARD_DISPLAY_IO_CAPABILITY: HCI_DISPLAY_YES_NO_IO_CAPABILITY,
|
|
2701
|
-
}.get(pairing_config.delegate.io_capability)
|
|
2702
|
-
|
|
2703
|
-
if io_capability is None:
|
|
2704
|
-
logger.warning(
|
|
2705
|
-
f'cannot map IO capability ({pairing_config.delegate.io_capability}'
|
|
2706
|
-
)
|
|
2707
|
-
io_capability = HCI_NO_INPUT_NO_OUTPUT_IO_CAPABILITY
|
|
2708
|
-
|
|
2709
2737
|
# Compute the authentication requirements
|
|
2710
2738
|
authentication_requirements = (
|
|
2711
2739
|
# No Bonding
|
|
@@ -2724,53 +2752,50 @@ class Device(CompositeEventEmitter):
|
|
|
2724
2752
|
self.host.send_command_sync(
|
|
2725
2753
|
HCI_IO_Capability_Request_Reply_Command(
|
|
2726
2754
|
bd_addr=connection.peer_address,
|
|
2727
|
-
io_capability=
|
|
2755
|
+
io_capability=pairing_config.delegate.classic_io_capability,
|
|
2728
2756
|
oob_data_present=0x00, # Not present
|
|
2729
2757
|
authentication_requirements=authentication_requirements,
|
|
2730
2758
|
)
|
|
2731
2759
|
)
|
|
2732
2760
|
|
|
2761
|
+
# [Classic only]
|
|
2762
|
+
@host_event_handler
|
|
2763
|
+
@with_connection_from_address
|
|
2764
|
+
def on_authentication_io_capability_response(
|
|
2765
|
+
self, connection, io_capability, authentication_requirements
|
|
2766
|
+
):
|
|
2767
|
+
connection.peer_pairing_io_capability = io_capability
|
|
2768
|
+
connection.peer_pairing_authentication_requirements = (
|
|
2769
|
+
authentication_requirements
|
|
2770
|
+
)
|
|
2771
|
+
|
|
2733
2772
|
# [Classic only]
|
|
2734
2773
|
@host_event_handler
|
|
2735
2774
|
@with_connection_from_address
|
|
2736
2775
|
def on_authentication_user_confirmation_request(self, connection, code):
|
|
2737
2776
|
# Ask what the pairing config should be for this connection
|
|
2738
2777
|
pairing_config = self.pairing_config_factory(connection)
|
|
2739
|
-
|
|
2740
|
-
can_compare = pairing_config.delegate.io_capability not in (
|
|
2741
|
-
smp.SMP_NO_INPUT_NO_OUTPUT_IO_CAPABILITY,
|
|
2742
|
-
smp.SMP_DISPLAY_ONLY_IO_CAPABILITY,
|
|
2743
|
-
)
|
|
2778
|
+
io_capability = pairing_config.delegate.classic_io_capability
|
|
2744
2779
|
|
|
2745
2780
|
# Respond
|
|
2746
|
-
if
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
)
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
HCI_User_Confirmation_Request_Reply_Command(
|
|
2756
|
-
bd_addr=connection.peer_address
|
|
2757
|
-
)
|
|
2758
|
-
)
|
|
2759
|
-
else:
|
|
2760
|
-
await self.host.send_command(
|
|
2761
|
-
HCI_User_Confirmation_Request_Negative_Reply_Command(
|
|
2762
|
-
bd_addr=connection.peer_address
|
|
2763
|
-
)
|
|
2781
|
+
if io_capability == HCI_DISPLAY_YES_NO_IO_CAPABILITY:
|
|
2782
|
+
if connection.peer_pairing_io_capability in (
|
|
2783
|
+
HCI_DISPLAY_YES_NO_IO_CAPABILITY,
|
|
2784
|
+
HCI_DISPLAY_ONLY_IO_CAPABILITY,
|
|
2785
|
+
):
|
|
2786
|
+
# Display the code and ask the user to compare
|
|
2787
|
+
async def prompt():
|
|
2788
|
+
return (
|
|
2789
|
+
await pairing_config.delegate.compare_numbers(code, digits=6),
|
|
2764
2790
|
)
|
|
2765
2791
|
|
|
2766
|
-
|
|
2767
|
-
|
|
2792
|
+
else:
|
|
2793
|
+
# Ask the user to confirm the pairing, without showing a code
|
|
2794
|
+
async def prompt():
|
|
2795
|
+
return await pairing_config.delegate.confirm()
|
|
2768
2796
|
|
|
2769
2797
|
async def confirm():
|
|
2770
|
-
|
|
2771
|
-
'disconnection', pairing_config.delegate.confirm()
|
|
2772
|
-
)
|
|
2773
|
-
if confirm:
|
|
2798
|
+
if await prompt():
|
|
2774
2799
|
await self.host.send_command(
|
|
2775
2800
|
HCI_User_Confirmation_Request_Reply_Command(
|
|
2776
2801
|
bd_addr=connection.peer_address
|
|
@@ -2783,7 +2808,17 @@ class Device(CompositeEventEmitter):
|
|
|
2783
2808
|
)
|
|
2784
2809
|
)
|
|
2785
2810
|
|
|
2786
|
-
|
|
2811
|
+
AsyncRunner.spawn(connection.abort_on('disconnection', confirm()))
|
|
2812
|
+
return
|
|
2813
|
+
|
|
2814
|
+
if io_capability == HCI_DISPLAY_ONLY_IO_CAPABILITY:
|
|
2815
|
+
# Display the code to the user
|
|
2816
|
+
AsyncRunner.spawn(pairing_config.delegate.display_number(code, 6))
|
|
2817
|
+
|
|
2818
|
+
# Automatic confirmation
|
|
2819
|
+
self.host.send_command_sync(
|
|
2820
|
+
HCI_User_Confirmation_Request_Reply_Command(bd_addr=connection.peer_address)
|
|
2821
|
+
)
|
|
2787
2822
|
|
|
2788
2823
|
# [Classic only]
|
|
2789
2824
|
@host_event_handler
|
|
@@ -2791,15 +2826,11 @@ class Device(CompositeEventEmitter):
|
|
|
2791
2826
|
def on_authentication_user_passkey_request(self, connection):
|
|
2792
2827
|
# Ask what the pairing config should be for this connection
|
|
2793
2828
|
pairing_config = self.pairing_config_factory(connection)
|
|
2794
|
-
|
|
2795
|
-
can_input = pairing_config.delegate.io_capability in (
|
|
2796
|
-
smp.SMP_KEYBOARD_ONLY_IO_CAPABILITY,
|
|
2797
|
-
smp.SMP_KEYBOARD_DISPLAY_IO_CAPABILITY,
|
|
2798
|
-
)
|
|
2829
|
+
io_capability = pairing_config.delegate.classic_io_capability
|
|
2799
2830
|
|
|
2800
2831
|
# Respond
|
|
2801
|
-
if
|
|
2802
|
-
|
|
2832
|
+
if io_capability == HCI_KEYBOARD_ONLY_IO_CAPABILITY:
|
|
2833
|
+
# Ask the user to input a number
|
|
2803
2834
|
async def get_number():
|
|
2804
2835
|
number = await connection.abort_on(
|
|
2805
2836
|
'disconnection', pairing_config.delegate.get_number()
|
|
@@ -2829,18 +2860,14 @@ class Device(CompositeEventEmitter):
|
|
|
2829
2860
|
@host_event_handler
|
|
2830
2861
|
@with_connection_from_address
|
|
2831
2862
|
def on_pin_code_request(self, connection):
|
|
2832
|
-
#
|
|
2863
|
+
# Classic legacy pairing
|
|
2833
2864
|
# Ask what the pairing config should be for this connection
|
|
2834
2865
|
pairing_config = self.pairing_config_factory(connection)
|
|
2866
|
+
io_capability = pairing_config.delegate.classic_io_capability
|
|
2835
2867
|
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
)
|
|
2840
|
-
|
|
2841
|
-
# respond the pin code
|
|
2842
|
-
if can_input:
|
|
2843
|
-
|
|
2868
|
+
# Respond
|
|
2869
|
+
if io_capability == HCI_KEYBOARD_ONLY_IO_CAPABILITY:
|
|
2870
|
+
# Ask the user to enter a string
|
|
2844
2871
|
async def get_pin_code():
|
|
2845
2872
|
pin_code = await connection.abort_on(
|
|
2846
2873
|
'disconnection', pairing_config.delegate.get_string(16)
|
|
@@ -2880,6 +2907,7 @@ class Device(CompositeEventEmitter):
|
|
|
2880
2907
|
# Ask what the pairing config should be for this connection
|
|
2881
2908
|
pairing_config = self.pairing_config_factory(connection)
|
|
2882
2909
|
|
|
2910
|
+
# Show the passkey to the user
|
|
2883
2911
|
connection.abort_on(
|
|
2884
2912
|
'disconnection', pairing_config.delegate.display_number(passkey)
|
|
2885
2913
|
)
|
bumble/gap.py
CHANGED
|
@@ -41,14 +41,14 @@ class GenericAccessService(Service):
|
|
|
41
41
|
def __init__(self, device_name, appearance=(0, 0)):
|
|
42
42
|
device_name_characteristic = Characteristic(
|
|
43
43
|
GATT_DEVICE_NAME_CHARACTERISTIC,
|
|
44
|
-
Characteristic.READ,
|
|
44
|
+
Characteristic.Properties.READ,
|
|
45
45
|
Characteristic.READABLE,
|
|
46
46
|
device_name.encode('utf-8')[:248],
|
|
47
47
|
)
|
|
48
48
|
|
|
49
49
|
appearance_characteristic = Characteristic(
|
|
50
50
|
GATT_APPEARANCE_CHARACTERISTIC,
|
|
51
|
-
Characteristic.READ,
|
|
51
|
+
Characteristic.Properties.READ,
|
|
52
52
|
Characteristic.READABLE,
|
|
53
53
|
struct.pack('<H', (appearance[0] << 6) | appearance[1]),
|
|
54
54
|
)
|