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/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
- None,
620
+ role,
612
621
  None,
613
622
  None,
614
623
  )
615
624
 
616
625
  # [Classic only]
617
- def complete(self, handle, peer_resolvable_address, role, parameters):
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
- return 'CENTRAL' if self.role == BT_CENTRAL_ROLE else 'PERIPHERAL'
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 == None
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 = True
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
- # incomplete list of fields.
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 = KeyStore.create_for_device(config)
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=characteristic["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(self)
1041
- self.l2cap_channel_manager.register_fixed_channel(smp.SMP_CID, self.on_smp_pdu)
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 (irk, address) in resolving_keys:
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 all PHYs with default parameters
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(value=link_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} as {HCI_Constant.role_name(role)}'
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(self, bd_addr)
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 known if we are truly authenticated
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=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 can_compare:
2747
-
2748
- async def compare_numbers():
2749
- numbers_match = await connection.abort_on(
2750
- 'disconnection',
2751
- pairing_config.delegate.compare_numbers(code, digits=6),
2752
- )
2753
- if numbers_match:
2754
- await self.host.send_command(
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
- asyncio.create_task(compare_numbers())
2767
- else:
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
- confirm = await connection.abort_on(
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
- asyncio.create_task(confirm())
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 can_input:
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
- # classic legacy pairing
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
- can_input = pairing_config.delegate.io_capability in (
2837
- smp.SMP_KEYBOARD_ONLY_IO_CAPABILITY,
2838
- smp.SMP_KEYBOARD_DISPLAY_IO_CAPABILITY,
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
  )