bumble 0.0.219__py3-none-any.whl → 0.0.221__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 (104) hide show
  1. bumble/_version.py +2 -2
  2. bumble/a2dp.py +5 -5
  3. bumble/apps/auracast.py +746 -479
  4. bumble/apps/bench.py +4 -5
  5. bumble/apps/console.py +5 -10
  6. bumble/apps/controller_info.py +12 -7
  7. bumble/apps/controller_loopback.py +1 -2
  8. bumble/apps/device_info.py +2 -3
  9. bumble/apps/gatt_dump.py +0 -1
  10. bumble/apps/lea_unicast/app.py +1 -1
  11. bumble/apps/pair.py +49 -46
  12. bumble/apps/pandora_server.py +2 -2
  13. bumble/apps/player/player.py +10 -12
  14. bumble/apps/rfcomm_bridge.py +10 -11
  15. bumble/apps/scan.py +1 -3
  16. bumble/apps/speaker/speaker.py +3 -4
  17. bumble/at.py +4 -5
  18. bumble/att.py +91 -25
  19. bumble/audio/io.py +8 -6
  20. bumble/avc.py +1 -2
  21. bumble/avctp.py +2 -3
  22. bumble/avdtp.py +53 -57
  23. bumble/avrcp.py +25 -27
  24. bumble/codecs.py +15 -15
  25. bumble/colors.py +7 -8
  26. bumble/controller.py +1201 -643
  27. bumble/core.py +41 -49
  28. bumble/crypto/__init__.py +2 -1
  29. bumble/crypto/builtin.py +2 -8
  30. bumble/data_types.py +2 -1
  31. bumble/decoder.py +2 -3
  32. bumble/device.py +278 -325
  33. bumble/drivers/__init__.py +3 -2
  34. bumble/drivers/intel.py +6 -8
  35. bumble/drivers/rtk.py +1 -1
  36. bumble/gatt.py +9 -9
  37. bumble/gatt_adapters.py +6 -6
  38. bumble/gatt_client.py +110 -60
  39. bumble/gatt_server.py +209 -139
  40. bumble/hci.py +87 -74
  41. bumble/helpers.py +5 -5
  42. bumble/hfp.py +27 -26
  43. bumble/hid.py +9 -9
  44. bumble/host.py +44 -50
  45. bumble/keys.py +17 -17
  46. bumble/l2cap.py +1015 -218
  47. bumble/link.py +54 -284
  48. bumble/ll.py +200 -0
  49. bumble/lmp.py +324 -0
  50. bumble/pairing.py +14 -15
  51. bumble/pandora/__init__.py +2 -2
  52. bumble/pandora/device.py +6 -4
  53. bumble/pandora/host.py +19 -10
  54. bumble/pandora/l2cap.py +8 -9
  55. bumble/pandora/security.py +18 -16
  56. bumble/pandora/utils.py +4 -4
  57. bumble/profiles/aics.py +6 -8
  58. bumble/profiles/ams.py +3 -5
  59. bumble/profiles/ancs.py +11 -11
  60. bumble/profiles/ascs.py +5 -5
  61. bumble/profiles/asha.py +10 -9
  62. bumble/profiles/bass.py +9 -3
  63. bumble/profiles/battery_service.py +1 -2
  64. bumble/profiles/csip.py +9 -10
  65. bumble/profiles/device_information_service.py +16 -17
  66. bumble/profiles/gap.py +3 -4
  67. bumble/profiles/gatt_service.py +0 -1
  68. bumble/profiles/gmap.py +12 -13
  69. bumble/profiles/hap.py +3 -3
  70. bumble/profiles/heart_rate_service.py +7 -8
  71. bumble/profiles/le_audio.py +1 -1
  72. bumble/profiles/mcp.py +28 -28
  73. bumble/profiles/pacs.py +13 -17
  74. bumble/profiles/pbp.py +16 -0
  75. bumble/profiles/vcs.py +2 -2
  76. bumble/profiles/vocs.py +6 -9
  77. bumble/rfcomm.py +19 -18
  78. bumble/sdp.py +12 -11
  79. bumble/smp.py +20 -30
  80. bumble/snoop.py +12 -5
  81. bumble/tools/generate_company_id_list.py +1 -1
  82. bumble/tools/intel_util.py +2 -2
  83. bumble/tools/rtk_fw_download.py +1 -1
  84. bumble/tools/rtk_util.py +1 -1
  85. bumble/transport/__init__.py +1 -2
  86. bumble/transport/android_emulator.py +2 -3
  87. bumble/transport/android_netsim.py +49 -40
  88. bumble/transport/common.py +9 -9
  89. bumble/transport/file.py +1 -2
  90. bumble/transport/hci_socket.py +2 -3
  91. bumble/transport/pty.py +3 -5
  92. bumble/transport/pyusb.py +8 -5
  93. bumble/transport/serial.py +1 -2
  94. bumble/transport/vhci.py +1 -2
  95. bumble/transport/ws_server.py +2 -3
  96. bumble/utils.py +23 -14
  97. bumble/vendor/android/hci.py +4 -2
  98. {bumble-0.0.219.dist-info → bumble-0.0.221.dist-info}/METADATA +4 -3
  99. bumble-0.0.221.dist-info/RECORD +185 -0
  100. bumble-0.0.219.dist-info/RECORD +0 -183
  101. {bumble-0.0.219.dist-info → bumble-0.0.221.dist-info}/WHEEL +0 -0
  102. {bumble-0.0.219.dist-info → bumble-0.0.221.dist-info}/entry_points.txt +0 -0
  103. {bumble-0.0.219.dist-info → bumble-0.0.221.dist-info}/licenses/LICENSE +0 -0
  104. {bumble-0.0.219.dist-info → bumble-0.0.221.dist-info}/top_level.txt +0 -0
bumble/gatt_server.py CHANGED
@@ -29,11 +29,11 @@ import asyncio
29
29
  import logging
30
30
  import struct
31
31
  from collections import defaultdict
32
- from typing import TYPE_CHECKING, Iterable, Optional, TypeVar
32
+ from collections.abc import Iterable
33
+ from typing import TYPE_CHECKING, TypeVar
33
34
 
34
- from bumble import att, utils
35
+ from bumble import att, core, l2cap, utils
35
36
  from bumble.colors import color
36
- from bumble.core import UUID
37
37
  from bumble.gatt import (
38
38
  GATT_CHARACTERISTIC_ATTRIBUTE_TYPE,
39
39
  GATT_CLIENT_CHARACTERISTIC_CONFIGURATION_DESCRIPTOR,
@@ -43,14 +43,13 @@ from bumble.gatt import (
43
43
  GATT_SECONDARY_SERVICE_ATTRIBUTE_TYPE,
44
44
  Characteristic,
45
45
  CharacteristicDeclaration,
46
- CharacteristicValue,
47
46
  Descriptor,
48
47
  IncludedServiceDeclaration,
49
48
  Service,
50
49
  )
51
50
 
52
51
  if TYPE_CHECKING:
53
- from bumble.device import Connection, Device
52
+ from bumble.device import Device
54
53
 
55
54
  # -----------------------------------------------------------------------------
56
55
  # Logging
@@ -64,6 +63,18 @@ logger = logging.getLogger(__name__)
64
63
  GATT_SERVER_DEFAULT_MAX_MTU = 517
65
64
 
66
65
 
66
+ # -----------------------------------------------------------------------------
67
+ # Helpers
68
+ # -----------------------------------------------------------------------------
69
+
70
+
71
+ def _bearer_id(bearer: att.Bearer) -> str:
72
+ if att.is_enhanced_bearer(bearer):
73
+ return f'[0x{bearer.connection.handle:04X}|CID=0x{bearer.source_cid:04X}]'
74
+ else:
75
+ return f'[0x{bearer.handle:04X}]'
76
+
77
+
67
78
  # -----------------------------------------------------------------------------
68
79
  # GATT Server
69
80
  # -----------------------------------------------------------------------------
@@ -71,9 +82,9 @@ class Server(utils.EventEmitter):
71
82
  attributes: list[att.Attribute]
72
83
  services: list[Service]
73
84
  attributes_by_handle: dict[int, att.Attribute]
74
- subscribers: dict[int, dict[int, bytes]]
75
- indication_semaphores: defaultdict[int, asyncio.Semaphore]
76
- pending_confirmations: defaultdict[int, Optional[asyncio.futures.Future]]
85
+ subscribers: dict[att.Bearer, dict[int, bytes]]
86
+ indication_semaphores: defaultdict[att.Bearer, asyncio.Semaphore]
87
+ pending_confirmations: defaultdict[att.Bearer, asyncio.futures.Future | None]
77
88
 
78
89
  EVENT_CHARACTERISTIC_SUBSCRIPTION = "characteristic_subscription"
79
90
 
@@ -95,8 +106,29 @@ class Server(utils.EventEmitter):
95
106
  def __str__(self) -> str:
96
107
  return "\n".join(map(str, self.attributes))
97
108
 
98
- def send_gatt_pdu(self, connection_handle: int, pdu: bytes) -> None:
99
- self.device.send_l2cap_pdu(connection_handle, att.ATT_CID, pdu)
109
+ def register_eatt(
110
+ self, spec: l2cap.LeCreditBasedChannelSpec | None = None
111
+ ) -> l2cap.LeCreditBasedChannelServer:
112
+ def on_channel(channel: l2cap.LeCreditBasedChannel):
113
+ logger.debug(
114
+ "New EATT Bearer Connection=0x%04X CID=0x%04X",
115
+ channel.connection.handle,
116
+ channel.source_cid,
117
+ )
118
+ channel.att_mtu = att.ATT_DEFAULT_MTU
119
+ channel.sink = lambda pdu: self.on_gatt_pdu(
120
+ channel, att.ATT_PDU.from_bytes(pdu)
121
+ )
122
+
123
+ return self.device.create_l2cap_server(
124
+ spec or l2cap.LeCreditBasedChannelSpec(psm=att.EATT_PSM), handler=on_channel
125
+ )
126
+
127
+ def send_gatt_pdu(self, bearer: att.Bearer, pdu: bytes) -> None:
128
+ if att.is_enhanced_bearer(bearer):
129
+ bearer.write(pdu)
130
+ else:
131
+ self.device.send_l2cap_pdu(bearer.handle, att.ATT_CID, pdu)
100
132
 
101
133
  def next_handle(self) -> int:
102
134
  return 1 + len(self.attributes)
@@ -109,7 +141,7 @@ class Server(utils.EventEmitter):
109
141
  and (data := attribute.get_advertising_data())
110
142
  }
111
143
 
112
- def get_attribute(self, handle: int) -> Optional[att.Attribute]:
144
+ def get_attribute(self, handle: int) -> att.Attribute | None:
113
145
  attribute = self.attributes_by_handle.get(handle)
114
146
  if attribute:
115
147
  return attribute
@@ -126,7 +158,7 @@ class Server(utils.EventEmitter):
126
158
 
127
159
  def get_attribute_group(
128
160
  self, handle: int, group_type: type[AttributeGroupType]
129
- ) -> Optional[AttributeGroupType]:
161
+ ) -> AttributeGroupType | None:
130
162
  return next(
131
163
  (
132
164
  attribute
@@ -137,7 +169,7 @@ class Server(utils.EventEmitter):
137
169
  None,
138
170
  )
139
171
 
140
- def get_service_attribute(self, service_uuid: UUID) -> Optional[Service]:
172
+ def get_service_attribute(self, service_uuid: core.UUID) -> Service | None:
141
173
  return next(
142
174
  (
143
175
  attribute
@@ -150,8 +182,8 @@ class Server(utils.EventEmitter):
150
182
  )
151
183
 
152
184
  def get_characteristic_attributes(
153
- self, service_uuid: UUID, characteristic_uuid: UUID
154
- ) -> Optional[tuple[CharacteristicDeclaration, Characteristic]]:
185
+ self, service_uuid: core.UUID, characteristic_uuid: core.UUID
186
+ ) -> tuple[CharacteristicDeclaration, Characteristic] | None:
155
187
  service_handle = self.get_service_attribute(service_uuid)
156
188
  if not service_handle:
157
189
  return None
@@ -175,8 +207,11 @@ class Server(utils.EventEmitter):
175
207
  )
176
208
 
177
209
  def get_descriptor_attribute(
178
- self, service_uuid: UUID, characteristic_uuid: UUID, descriptor_uuid: UUID
179
- ) -> Optional[Descriptor]:
210
+ self,
211
+ service_uuid: core.UUID,
212
+ characteristic_uuid: core.UUID,
213
+ descriptor_uuid: core.UUID,
214
+ ) -> Descriptor | None:
180
215
  characteristics = self.get_characteristic_attributes(
181
216
  service_uuid, characteristic_uuid
182
217
  )
@@ -256,14 +291,7 @@ class Server(utils.EventEmitter):
256
291
  Descriptor(
257
292
  GATT_CLIENT_CHARACTERISTIC_CONFIGURATION_DESCRIPTOR,
258
293
  att.Attribute.READABLE | att.Attribute.WRITEABLE,
259
- CharacteristicValue(
260
- read=lambda connection, characteristic=characteristic: self.read_cccd(
261
- connection, characteristic
262
- ),
263
- write=lambda connection, value, characteristic=characteristic: self.write_cccd(
264
- connection, characteristic, value
265
- ),
266
- ),
294
+ self.make_descriptor_value(characteristic),
267
295
  )
268
296
  )
269
297
 
@@ -279,10 +307,21 @@ class Server(utils.EventEmitter):
279
307
  for service in services:
280
308
  self.add_service(service)
281
309
 
282
- def read_cccd(
283
- self, connection: Connection, characteristic: Characteristic
284
- ) -> bytes:
285
- subscribers = self.subscribers.get(connection.handle)
310
+ def make_descriptor_value(
311
+ self, characteristic: Characteristic
312
+ ) -> att.AttributeValueV2:
313
+ # It is necessary to use Attribute Value V2 here to identify the bearer of CCCD.
314
+ return att.AttributeValueV2(
315
+ lambda bearer, characteristic=characteristic: self.read_cccd(
316
+ bearer, characteristic
317
+ ),
318
+ write=lambda bearer, value, characteristic=characteristic: self.write_cccd(
319
+ bearer, characteristic, value
320
+ ),
321
+ )
322
+
323
+ def read_cccd(self, bearer: att.Bearer, characteristic: Characteristic) -> bytes:
324
+ subscribers = self.subscribers.get(bearer)
286
325
  cccd = None
287
326
  if subscribers:
288
327
  cccd = subscribers.get(characteristic.handle)
@@ -291,12 +330,12 @@ class Server(utils.EventEmitter):
291
330
 
292
331
  def write_cccd(
293
332
  self,
294
- connection: Connection,
333
+ bearer: att.Bearer,
295
334
  characteristic: Characteristic,
296
335
  value: bytes,
297
336
  ) -> None:
298
337
  logger.debug(
299
- f'Subscription update for connection=0x{connection.handle:04X}, '
338
+ f'Subscription update for connection={_bearer_id(bearer)}, '
300
339
  f'handle=0x{characteristic.handle:04X}: {value.hex()}'
301
340
  )
302
341
 
@@ -305,41 +344,60 @@ class Server(utils.EventEmitter):
305
344
  logger.warning('CCCD value not 2 bytes long')
306
345
  return
307
346
 
308
- cccds = self.subscribers.setdefault(connection.handle, {})
347
+ cccds = self.subscribers.setdefault(bearer, {})
309
348
  cccds[characteristic.handle] = value
310
349
  logger.debug(f'CCCDs: {cccds}')
311
350
  notify_enabled = value[0] & 0x01 != 0
312
351
  indicate_enabled = value[0] & 0x02 != 0
313
352
  characteristic.emit(
314
353
  characteristic.EVENT_SUBSCRIPTION,
315
- connection,
354
+ bearer,
316
355
  notify_enabled,
317
356
  indicate_enabled,
318
357
  )
319
358
  self.emit(
320
359
  self.EVENT_CHARACTERISTIC_SUBSCRIPTION,
321
- connection,
360
+ bearer,
322
361
  characteristic,
323
362
  notify_enabled,
324
363
  indicate_enabled,
325
364
  )
326
365
 
327
- def send_response(self, connection: Connection, response: att.ATT_PDU) -> None:
328
- logger.debug(
329
- f'GATT Response from server: [0x{connection.handle:04X}] {response}'
330
- )
331
- self.send_gatt_pdu(connection.handle, bytes(response))
366
+ def send_response(self, bearer: att.Bearer, response: att.ATT_PDU) -> None:
367
+ logger.debug(f'GATT Response from server: {_bearer_id(bearer)} {response}')
368
+ self.send_gatt_pdu(bearer, bytes(response))
332
369
 
333
370
  async def notify_subscriber(
334
371
  self,
335
- connection: Connection,
372
+ bearer: att.Bearer,
336
373
  attribute: att.Attribute,
337
- value: Optional[bytes] = None,
374
+ value: bytes | None = None,
338
375
  force: bool = False,
376
+ ) -> None:
377
+ if att.is_enhanced_bearer(bearer) or force:
378
+ return await self._notify_single_subscriber(bearer, attribute, value, force)
379
+ else:
380
+ # If API is called to a Connection and not forced, try to notify all subscribed bearers on it.
381
+ bearers = [
382
+ channel
383
+ for channel in self.device.l2cap_channel_manager.le_coc_channels.get(
384
+ bearer.handle, {}
385
+ ).values()
386
+ if channel.psm == att.EATT_PSM
387
+ ] + [bearer]
388
+ for bearer in bearers:
389
+ await self._notify_single_subscriber(bearer, attribute, value, force)
390
+
391
+ async def _notify_single_subscriber(
392
+ self,
393
+ bearer: att.Bearer,
394
+ attribute: att.Attribute,
395
+ value: bytes | None,
396
+ force: bool,
339
397
  ) -> None:
340
398
  # Check if there's a subscriber
341
399
  if not force:
342
- subscribers = self.subscribers.get(connection.handle)
400
+ subscribers = self.subscribers.get(bearer)
343
401
  if not subscribers:
344
402
  logger.debug('not notifying, no subscribers')
345
403
  return
@@ -355,34 +413,53 @@ class Server(utils.EventEmitter):
355
413
 
356
414
  # Get or encode the value
357
415
  value = (
358
- await attribute.read_value(connection)
416
+ await attribute.read_value(bearer)
359
417
  if value is None
360
418
  else attribute.encode_value(value)
361
419
  )
362
420
 
363
421
  # Truncate if needed
364
- if len(value) > connection.att_mtu - 3:
365
- value = value[: connection.att_mtu - 3]
422
+ if len(value) > bearer.att_mtu - 3:
423
+ value = value[: bearer.att_mtu - 3]
366
424
 
367
425
  # Notify
368
426
  notification = att.ATT_Handle_Value_Notification(
369
427
  attribute_handle=attribute.handle, attribute_value=value
370
428
  )
371
- logger.debug(
372
- f'GATT Notify from server: [0x{connection.handle:04X}] {notification}'
373
- )
374
- self.send_gatt_pdu(connection.handle, bytes(notification))
429
+ logger.debug(f'GATT Notify from server: {_bearer_id(bearer)} {notification}')
430
+ self.send_gatt_pdu(bearer, bytes(notification))
375
431
 
376
432
  async def indicate_subscriber(
377
433
  self,
378
- connection: Connection,
434
+ bearer: att.Bearer,
379
435
  attribute: att.Attribute,
380
- value: Optional[bytes] = None,
436
+ value: bytes | None = None,
381
437
  force: bool = False,
438
+ ) -> None:
439
+ if att.is_enhanced_bearer(bearer) or force:
440
+ return await self._notify_single_subscriber(bearer, attribute, value, force)
441
+ else:
442
+ # If API is called to a Connection and not forced, try to indicate all subscribed bearers on it.
443
+ bearers = [
444
+ channel
445
+ for channel in self.device.l2cap_channel_manager.le_coc_channels.get(
446
+ bearer.handle, {}
447
+ ).values()
448
+ if channel.psm == att.EATT_PSM
449
+ ] + [bearer]
450
+ for bearer in bearers:
451
+ await self._indicate_single_bearer(bearer, attribute, value, force)
452
+
453
+ async def _indicate_single_bearer(
454
+ self,
455
+ bearer: att.Bearer,
456
+ attribute: att.Attribute,
457
+ value: bytes | None,
458
+ force: bool,
382
459
  ) -> None:
383
460
  # Check if there's a subscriber
384
461
  if not force:
385
- subscribers = self.subscribers.get(connection.handle)
462
+ subscribers = self.subscribers.get(bearer)
386
463
  if not subscribers:
387
464
  logger.debug('not indicating, no subscribers')
388
465
  return
@@ -398,73 +475,71 @@ class Server(utils.EventEmitter):
398
475
 
399
476
  # Get or encode the value
400
477
  value = (
401
- await attribute.read_value(connection)
478
+ await attribute.read_value(bearer)
402
479
  if value is None
403
480
  else attribute.encode_value(value)
404
481
  )
405
482
 
406
483
  # Truncate if needed
407
- if len(value) > connection.att_mtu - 3:
408
- value = value[: connection.att_mtu - 3]
484
+ if len(value) > bearer.att_mtu - 3:
485
+ value = value[: bearer.att_mtu - 3]
409
486
 
410
487
  # Indicate
411
488
  indication = att.ATT_Handle_Value_Indication(
412
489
  attribute_handle=attribute.handle, attribute_value=value
413
490
  )
414
- logger.debug(
415
- f'GATT Indicate from server: [0x{connection.handle:04X}] {indication}'
416
- )
491
+ logger.debug(f'GATT Indicate from server: {_bearer_id(bearer)} {indication}')
417
492
 
418
493
  # Wait until we can send (only one pending indication at a time per connection)
419
- async with self.indication_semaphores[connection.handle]:
420
- assert self.pending_confirmations[connection.handle] is None
494
+ async with self.indication_semaphores[bearer]:
495
+ assert self.pending_confirmations[bearer] is None
421
496
 
422
497
  # Create a future value to hold the eventual response
423
- pending_confirmation = self.pending_confirmations[connection.handle] = (
498
+ pending_confirmation = self.pending_confirmations[bearer] = (
424
499
  asyncio.get_running_loop().create_future()
425
500
  )
426
501
 
427
502
  try:
428
- self.send_gatt_pdu(connection.handle, bytes(indication))
503
+ self.send_gatt_pdu(bearer, bytes(indication))
429
504
  await asyncio.wait_for(pending_confirmation, GATT_REQUEST_TIMEOUT)
430
505
  except asyncio.TimeoutError as error:
431
506
  logger.warning(color('!!! GATT Indicate timeout', 'red'))
432
507
  raise TimeoutError(f'GATT timeout for {indication.name}') from error
433
508
  finally:
434
- self.pending_confirmations[connection.handle] = None
509
+ self.pending_confirmations[bearer] = None
435
510
 
436
511
  async def _notify_or_indicate_subscribers(
437
512
  self,
438
513
  indicate: bool,
439
514
  attribute: att.Attribute,
440
- value: Optional[bytes] = None,
515
+ value: bytes | None = None,
441
516
  force: bool = False,
442
517
  ) -> None:
443
- # Get all the connections for which there's at least one subscription
444
- connections = [
445
- connection
446
- for connection in [
447
- self.device.lookup_connection(connection_handle)
448
- for (connection_handle, subscribers) in self.subscribers.items()
449
- if force or subscribers.get(attribute.handle)
450
- ]
451
- if connection is not None
518
+ # Get all the bearers for which there's at least one subscription
519
+ bearers: list[att.Bearer] = [
520
+ bearer
521
+ for bearer, subscribers in self.subscribers.items()
522
+ if force or subscribers.get(attribute.handle)
452
523
  ]
453
524
 
454
525
  # Indicate or notify for each connection
455
- if connections:
456
- coroutine = self.indicate_subscriber if indicate else self.notify_subscriber
526
+ if bearers:
527
+ coroutine = (
528
+ self._indicate_single_bearer
529
+ if indicate
530
+ else self._notify_single_subscriber
531
+ )
457
532
  await asyncio.wait(
458
533
  [
459
- asyncio.create_task(coroutine(connection, attribute, value, force))
460
- for connection in connections
534
+ asyncio.create_task(coroutine(bearer, attribute, value, force))
535
+ for bearer in bearers
461
536
  ]
462
537
  )
463
538
 
464
539
  async def notify_subscribers(
465
540
  self,
466
541
  attribute: att.Attribute,
467
- value: Optional[bytes] = None,
542
+ value: bytes | None = None,
468
543
  force: bool = False,
469
544
  ):
470
545
  return await self._notify_or_indicate_subscribers(
@@ -474,26 +549,23 @@ class Server(utils.EventEmitter):
474
549
  async def indicate_subscribers(
475
550
  self,
476
551
  attribute: att.Attribute,
477
- value: Optional[bytes] = None,
552
+ value: bytes | None = None,
478
553
  force: bool = False,
479
554
  ):
480
555
  return await self._notify_or_indicate_subscribers(True, attribute, value, force)
481
556
 
482
- def on_disconnection(self, connection: Connection) -> None:
483
- if connection.handle in self.subscribers:
484
- del self.subscribers[connection.handle]
485
- if connection.handle in self.indication_semaphores:
486
- del self.indication_semaphores[connection.handle]
487
- if connection.handle in self.pending_confirmations:
488
- del self.pending_confirmations[connection.handle]
557
+ def on_disconnection(self, bearer: att.Bearer) -> None:
558
+ self.subscribers.pop(bearer, None)
559
+ self.indication_semaphores.pop(bearer, None)
560
+ self.pending_confirmations.pop(bearer, None)
489
561
 
490
- def on_gatt_pdu(self, connection: Connection, att_pdu: att.ATT_PDU) -> None:
491
- logger.debug(f'GATT Request to server: [0x{connection.handle:04X}] {att_pdu}')
562
+ def on_gatt_pdu(self, bearer: att.Bearer, att_pdu: att.ATT_PDU) -> None:
563
+ logger.debug(f'GATT Request to server: {_bearer_id(bearer)} {att_pdu}')
492
564
  handler_name = f'on_{att_pdu.name.lower()}'
493
565
  handler = getattr(self, handler_name, None)
494
566
  if handler is not None:
495
567
  try:
496
- handler(connection, att_pdu)
568
+ handler(bearer, att_pdu)
497
569
  except att.ATT_Error as error:
498
570
  logger.debug(f'normal exception returned by handler: {error}')
499
571
  response = att.ATT_Error_Response(
@@ -501,7 +573,7 @@ class Server(utils.EventEmitter):
501
573
  attribute_handle_in_error=error.att_handle,
502
574
  error_code=error.error_code,
503
575
  )
504
- self.send_response(connection, response)
576
+ self.send_response(bearer, response)
505
577
  except Exception:
506
578
  logger.exception(color("!!! Exception in handler:", "red"))
507
579
  response = att.ATT_Error_Response(
@@ -509,18 +581,18 @@ class Server(utils.EventEmitter):
509
581
  attribute_handle_in_error=0x0000,
510
582
  error_code=att.ATT_UNLIKELY_ERROR_ERROR,
511
583
  )
512
- self.send_response(connection, response)
584
+ self.send_response(bearer, response)
513
585
  raise
514
586
  else:
515
587
  # No specific handler registered
516
588
  if att_pdu.op_code in att.ATT_REQUESTS:
517
589
  # Invoke the generic handler
518
- self.on_att_request(connection, att_pdu)
590
+ self.on_att_request(bearer, att_pdu)
519
591
  else:
520
592
  # Just ignore
521
593
  logger.warning(
522
594
  color(
523
- f'--- Ignoring GATT Request from [0x{connection.handle:04X}]: ',
595
+ f'--- Ignoring GATT Request from {_bearer_id(bearer)}: ',
524
596
  'red',
525
597
  )
526
598
  + str(att_pdu)
@@ -529,13 +601,14 @@ class Server(utils.EventEmitter):
529
601
  #######################################################
530
602
  # ATT handlers
531
603
  #######################################################
532
- def on_att_request(self, connection: Connection, pdu: att.ATT_PDU) -> None:
604
+ def on_att_request(self, bearer: att.Bearer, pdu: att.ATT_PDU) -> None:
533
605
  '''
534
606
  Handler for requests without a more specific handler
535
607
  '''
536
608
  logger.warning(
537
609
  color(
538
- f'--- Unsupported ATT Request from [0x{connection.handle:04X}]: ', 'red'
610
+ f'--- Unsupported ATT Request from {_bearer_id(bearer)}: ',
611
+ 'red',
539
612
  )
540
613
  + str(pdu)
541
614
  )
@@ -544,29 +617,28 @@ class Server(utils.EventEmitter):
544
617
  attribute_handle_in_error=0x0000,
545
618
  error_code=att.ATT_REQUEST_NOT_SUPPORTED_ERROR,
546
619
  )
547
- self.send_response(connection, response)
620
+ self.send_response(bearer, response)
548
621
 
549
622
  def on_att_exchange_mtu_request(
550
- self, connection: Connection, request: att.ATT_Exchange_MTU_Request
623
+ self, bearer: att.Bearer, request: att.ATT_Exchange_MTU_Request
551
624
  ):
552
625
  '''
553
626
  See Bluetooth spec Vol 3, Part F - 3.4.2.1 Exchange MTU Request
554
627
  '''
555
628
  self.send_response(
556
- connection, att.ATT_Exchange_MTU_Response(server_rx_mtu=self.max_mtu)
629
+ bearer, att.ATT_Exchange_MTU_Response(server_rx_mtu=self.max_mtu)
557
630
  )
558
631
 
559
632
  # Compute the final MTU
560
633
  if request.client_rx_mtu >= att.ATT_DEFAULT_MTU:
561
634
  mtu = min(self.max_mtu, request.client_rx_mtu)
562
635
 
563
- # Notify the device
564
- self.device.on_connection_att_mtu_update(connection.handle, mtu)
636
+ bearer.on_att_mtu_update(mtu)
565
637
  else:
566
638
  logger.warning('invalid client_rx_mtu received, MTU not changed')
567
639
 
568
640
  def on_att_find_information_request(
569
- self, connection: Connection, request: att.ATT_Find_Information_Request
641
+ self, bearer: att.Bearer, request: att.ATT_Find_Information_Request
570
642
  ):
571
643
  '''
572
644
  See Bluetooth spec Vol 3, Part F - 3.4.3.1 Find Information Request
@@ -579,7 +651,7 @@ class Server(utils.EventEmitter):
579
651
  or request.starting_handle > request.ending_handle
580
652
  ):
581
653
  self.send_response(
582
- connection,
654
+ bearer,
583
655
  att.ATT_Error_Response(
584
656
  request_opcode_in_error=request.op_code,
585
657
  attribute_handle_in_error=request.starting_handle,
@@ -589,7 +661,7 @@ class Server(utils.EventEmitter):
589
661
  return
590
662
 
591
663
  # Build list of returned attributes
592
- pdu_space_available = connection.att_mtu - 2
664
+ pdu_space_available = bearer.att_mtu - 2
593
665
  attributes: list[att.Attribute] = []
594
666
  uuid_size = 0
595
667
  for attribute in (
@@ -631,18 +703,18 @@ class Server(utils.EventEmitter):
631
703
  error_code=att.ATT_ATTRIBUTE_NOT_FOUND_ERROR,
632
704
  )
633
705
 
634
- self.send_response(connection, response)
706
+ self.send_response(bearer, response)
635
707
 
636
708
  @utils.AsyncRunner.run_in_task()
637
709
  async def on_att_find_by_type_value_request(
638
- self, connection: Connection, request: att.ATT_Find_By_Type_Value_Request
710
+ self, bearer: att.Bearer, request: att.ATT_Find_By_Type_Value_Request
639
711
  ):
640
712
  '''
641
713
  See Bluetooth spec Vol 3, Part F - 3.4.3.3 Find By Type Value Request
642
714
  '''
643
715
 
644
716
  # Build list of returned attributes
645
- pdu_space_available = connection.att_mtu - 2
717
+ pdu_space_available = bearer.att_mtu - 2
646
718
  attributes = []
647
719
  response: att.ATT_PDU
648
720
  async for attribute in (
@@ -651,7 +723,7 @@ class Server(utils.EventEmitter):
651
723
  if attribute.handle >= request.starting_handle
652
724
  and attribute.handle <= request.ending_handle
653
725
  and attribute.type == request.attribute_type
654
- and (await attribute.read_value(connection)) == request.attribute_value
726
+ and (await attribute.read_value(bearer)) == request.attribute_value
655
727
  and pdu_space_available >= 4
656
728
  ):
657
729
  # TODO: check permissions
@@ -687,17 +759,17 @@ class Server(utils.EventEmitter):
687
759
  error_code=att.ATT_ATTRIBUTE_NOT_FOUND_ERROR,
688
760
  )
689
761
 
690
- self.send_response(connection, response)
762
+ self.send_response(bearer, response)
691
763
 
692
764
  @utils.AsyncRunner.run_in_task()
693
765
  async def on_att_read_by_type_request(
694
- self, connection: Connection, request: att.ATT_Read_By_Type_Request
766
+ self, bearer: att.Bearer, request: att.ATT_Read_By_Type_Request
695
767
  ):
696
768
  '''
697
769
  See Bluetooth spec Vol 3, Part F - 3.4.4.1 Read By Type Request
698
770
  '''
699
771
 
700
- pdu_space_available = connection.att_mtu - 2
772
+ pdu_space_available = bearer.att_mtu - 2
701
773
 
702
774
  response: att.ATT_PDU = att.ATT_Error_Response(
703
775
  request_opcode_in_error=request.op_code,
@@ -715,7 +787,7 @@ class Server(utils.EventEmitter):
715
787
  and pdu_space_available
716
788
  ):
717
789
  try:
718
- attribute_value = await attribute.read_value(connection)
790
+ attribute_value = await attribute.read_value(bearer)
719
791
  except att.ATT_Error as error:
720
792
  # If the first attribute is unreadable, return an error
721
793
  # Otherwise return attributes up to this point
@@ -728,7 +800,7 @@ class Server(utils.EventEmitter):
728
800
  break
729
801
 
730
802
  # Check the attribute value size
731
- max_attribute_size = min(connection.att_mtu - 4, 253)
803
+ max_attribute_size = min(bearer.att_mtu - 4, 253)
732
804
  if len(attribute_value) > max_attribute_size:
733
805
  # We need to truncate
734
806
  attribute_value = attribute_value[:max_attribute_size]
@@ -755,11 +827,11 @@ class Server(utils.EventEmitter):
755
827
  else:
756
828
  logging.debug(f"not found {request}")
757
829
 
758
- self.send_response(connection, response)
830
+ self.send_response(bearer, response)
759
831
 
760
832
  @utils.AsyncRunner.run_in_task()
761
833
  async def on_att_read_request(
762
- self, connection: Connection, request: att.ATT_Read_Request
834
+ self, bearer: att.Bearer, request: att.ATT_Read_Request
763
835
  ):
764
836
  '''
765
837
  See Bluetooth spec Vol 3, Part F - 3.4.4.3 Read Request
@@ -768,7 +840,7 @@ class Server(utils.EventEmitter):
768
840
  response: att.ATT_PDU
769
841
  if attribute := self.get_attribute(request.attribute_handle):
770
842
  try:
771
- value = await attribute.read_value(connection)
843
+ value = await attribute.read_value(bearer)
772
844
  except att.ATT_Error as error:
773
845
  response = att.ATT_Error_Response(
774
846
  request_opcode_in_error=request.op_code,
@@ -776,7 +848,7 @@ class Server(utils.EventEmitter):
776
848
  error_code=error.error_code,
777
849
  )
778
850
  else:
779
- value_size = min(connection.att_mtu - 1, len(value))
851
+ value_size = min(bearer.att_mtu - 1, len(value))
780
852
  response = att.ATT_Read_Response(attribute_value=value[:value_size])
781
853
  else:
782
854
  response = att.ATT_Error_Response(
@@ -784,11 +856,11 @@ class Server(utils.EventEmitter):
784
856
  attribute_handle_in_error=request.attribute_handle,
785
857
  error_code=att.ATT_INVALID_HANDLE_ERROR,
786
858
  )
787
- self.send_response(connection, response)
859
+ self.send_response(bearer, response)
788
860
 
789
861
  @utils.AsyncRunner.run_in_task()
790
862
  async def on_att_read_blob_request(
791
- self, connection: Connection, request: att.ATT_Read_Blob_Request
863
+ self, bearer: att.Bearer, request: att.ATT_Read_Blob_Request
792
864
  ):
793
865
  '''
794
866
  See Bluetooth spec Vol 3, Part F - 3.4.4.5 Read Blob Request
@@ -797,7 +869,7 @@ class Server(utils.EventEmitter):
797
869
  response: att.ATT_PDU
798
870
  if attribute := self.get_attribute(request.attribute_handle):
799
871
  try:
800
- value = await attribute.read_value(connection)
872
+ value = await attribute.read_value(bearer)
801
873
  except att.ATT_Error as error:
802
874
  response = att.ATT_Error_Response(
803
875
  request_opcode_in_error=request.op_code,
@@ -811,7 +883,7 @@ class Server(utils.EventEmitter):
811
883
  attribute_handle_in_error=request.attribute_handle,
812
884
  error_code=att.ATT_INVALID_OFFSET_ERROR,
813
885
  )
814
- elif len(value) <= connection.att_mtu - 1:
886
+ elif len(value) <= bearer.att_mtu - 1:
815
887
  response = att.ATT_Error_Response(
816
888
  request_opcode_in_error=request.op_code,
817
889
  attribute_handle_in_error=request.attribute_handle,
@@ -819,7 +891,7 @@ class Server(utils.EventEmitter):
819
891
  )
820
892
  else:
821
893
  part_size = min(
822
- connection.att_mtu - 1, len(value) - request.value_offset
894
+ bearer.att_mtu - 1, len(value) - request.value_offset
823
895
  )
824
896
  response = att.ATT_Read_Blob_Response(
825
897
  part_attribute_value=value[
@@ -832,11 +904,11 @@ class Server(utils.EventEmitter):
832
904
  attribute_handle_in_error=request.attribute_handle,
833
905
  error_code=att.ATT_INVALID_HANDLE_ERROR,
834
906
  )
835
- self.send_response(connection, response)
907
+ self.send_response(bearer, response)
836
908
 
837
909
  @utils.AsyncRunner.run_in_task()
838
910
  async def on_att_read_by_group_type_request(
839
- self, connection: Connection, request: att.ATT_Read_By_Group_Type_Request
911
+ self, bearer: att.Bearer, request: att.ATT_Read_By_Group_Type_Request
840
912
  ):
841
913
  '''
842
914
  See Bluetooth spec Vol 3, Part F - 3.4.4.9 Read by Group Type Request
@@ -851,10 +923,10 @@ class Server(utils.EventEmitter):
851
923
  attribute_handle_in_error=request.starting_handle,
852
924
  error_code=att.ATT_UNSUPPORTED_GROUP_TYPE_ERROR,
853
925
  )
854
- self.send_response(connection, response)
926
+ self.send_response(bearer, response)
855
927
  return
856
928
 
857
- pdu_space_available = connection.att_mtu - 2
929
+ pdu_space_available = bearer.att_mtu - 2
858
930
  attributes: list[tuple[int, int, bytes]] = []
859
931
  for attribute in (
860
932
  attribute
@@ -866,9 +938,9 @@ class Server(utils.EventEmitter):
866
938
  ):
867
939
  # No need to catch permission errors here, since these attributes
868
940
  # must all be world-readable
869
- attribute_value = await attribute.read_value(connection)
941
+ attribute_value = await attribute.read_value(bearer)
870
942
  # Check the attribute value size
871
- max_attribute_size = min(connection.att_mtu - 6, 251)
943
+ max_attribute_size = min(bearer.att_mtu - 6, 251)
872
944
  if len(attribute_value) > max_attribute_size:
873
945
  # We need to truncate
874
946
  attribute_value = attribute_value[:max_attribute_size]
@@ -903,11 +975,11 @@ class Server(utils.EventEmitter):
903
975
  error_code=att.ATT_ATTRIBUTE_NOT_FOUND_ERROR,
904
976
  )
905
977
 
906
- self.send_response(connection, response)
978
+ self.send_response(bearer, response)
907
979
 
908
980
  @utils.AsyncRunner.run_in_task()
909
981
  async def on_att_write_request(
910
- self, connection: Connection, request: att.ATT_Write_Request
982
+ self, bearer: att.Bearer, request: att.ATT_Write_Request
911
983
  ):
912
984
  '''
913
985
  See Bluetooth spec Vol 3, Part F - 3.4.5.1 Write Request
@@ -917,7 +989,7 @@ class Server(utils.EventEmitter):
917
989
  attribute = self.get_attribute(request.attribute_handle)
918
990
  if attribute is None:
919
991
  self.send_response(
920
- connection,
992
+ bearer,
921
993
  att.ATT_Error_Response(
922
994
  request_opcode_in_error=request.op_code,
923
995
  attribute_handle_in_error=request.attribute_handle,
@@ -931,7 +1003,7 @@ class Server(utils.EventEmitter):
931
1003
  # Check the request parameters
932
1004
  if len(request.attribute_value) > GATT_MAX_ATTRIBUTE_VALUE_SIZE:
933
1005
  self.send_response(
934
- connection,
1006
+ bearer,
935
1007
  att.ATT_Error_Response(
936
1008
  request_opcode_in_error=request.op_code,
937
1009
  attribute_handle_in_error=request.attribute_handle,
@@ -943,7 +1015,7 @@ class Server(utils.EventEmitter):
943
1015
  response: att.ATT_PDU
944
1016
  try:
945
1017
  # Accept the value
946
- await attribute.write_value(connection, request.attribute_value)
1018
+ await attribute.write_value(bearer, request.attribute_value)
947
1019
  except att.ATT_Error as error:
948
1020
  response = att.ATT_Error_Response(
949
1021
  request_opcode_in_error=request.op_code,
@@ -953,11 +1025,11 @@ class Server(utils.EventEmitter):
953
1025
  else:
954
1026
  # Done
955
1027
  response = att.ATT_Write_Response()
956
- self.send_response(connection, response)
1028
+ self.send_response(bearer, response)
957
1029
 
958
1030
  @utils.AsyncRunner.run_in_task()
959
1031
  async def on_att_write_command(
960
- self, connection: Connection, request: att.ATT_Write_Command
1032
+ self, bearer: att.Bearer, request: att.ATT_Write_Command
961
1033
  ):
962
1034
  '''
963
1035
  See Bluetooth spec Vol 3, Part F - 3.4.5.3 Write Command
@@ -976,22 +1048,20 @@ class Server(utils.EventEmitter):
976
1048
 
977
1049
  # Accept the value
978
1050
  try:
979
- await attribute.write_value(connection, request.attribute_value)
1051
+ await attribute.write_value(bearer, request.attribute_value)
980
1052
  except Exception:
981
1053
  logger.exception('!!! ignoring exception')
982
1054
 
983
1055
  def on_att_handle_value_confirmation(
984
1056
  self,
985
- connection: Connection,
1057
+ bearer: att.Bearer,
986
1058
  confirmation: att.ATT_Handle_Value_Confirmation,
987
1059
  ):
988
1060
  '''
989
1061
  See Bluetooth spec Vol 3, Part F - 3.4.7.3 Handle Value Confirmation
990
1062
  '''
991
1063
  del confirmation # Unused.
992
- if (
993
- pending_confirmation := self.pending_confirmations[connection.handle]
994
- ) is None:
1064
+ if (pending_confirmation := self.pending_confirmations[bearer]) is None:
995
1065
  # Not expected!
996
1066
  logger.warning(
997
1067
  '!!! unexpected confirmation, there is no pending indication'