bumble 0.0.212__py3-none-any.whl → 0.0.214__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 (92) hide show
  1. bumble/_version.py +2 -2
  2. bumble/a2dp.py +6 -0
  3. bumble/apps/README.md +0 -3
  4. bumble/apps/auracast.py +14 -11
  5. bumble/apps/bench.py +482 -37
  6. bumble/apps/console.py +3 -3
  7. bumble/apps/controller_info.py +44 -12
  8. bumble/apps/controller_loopback.py +7 -7
  9. bumble/apps/controllers.py +4 -5
  10. bumble/apps/device_info.py +4 -5
  11. bumble/apps/gatt_dump.py +5 -5
  12. bumble/apps/gg_bridge.py +5 -5
  13. bumble/apps/hci_bridge.py +5 -4
  14. bumble/apps/l2cap_bridge.py +5 -5
  15. bumble/apps/lea_unicast/app.py +8 -3
  16. bumble/apps/pair.py +19 -11
  17. bumble/apps/pandora_server.py +2 -2
  18. bumble/apps/player/player.py +2 -3
  19. bumble/apps/rfcomm_bridge.py +3 -4
  20. bumble/apps/scan.py +4 -5
  21. bumble/apps/show.py +6 -4
  22. bumble/apps/speaker/speaker.html +1 -0
  23. bumble/apps/speaker/speaker.js +113 -62
  24. bumble/apps/speaker/speaker.py +123 -19
  25. bumble/apps/unbond.py +2 -3
  26. bumble/apps/usb_probe.py +2 -3
  27. bumble/at.py +4 -4
  28. bumble/att.py +2 -6
  29. bumble/avc.py +7 -7
  30. bumble/avctp.py +3 -3
  31. bumble/avdtp.py +16 -20
  32. bumble/avrcp.py +42 -54
  33. bumble/colors.py +2 -2
  34. bumble/controller.py +174 -45
  35. bumble/device.py +398 -182
  36. bumble/drivers/__init__.py +2 -2
  37. bumble/drivers/common.py +0 -2
  38. bumble/drivers/intel.py +37 -40
  39. bumble/drivers/rtk.py +28 -35
  40. bumble/gatt.py +4 -4
  41. bumble/gatt_adapters.py +4 -5
  42. bumble/gatt_client.py +26 -31
  43. bumble/gatt_server.py +7 -11
  44. bumble/hci.py +2648 -2909
  45. bumble/helpers.py +4 -5
  46. bumble/hfp.py +32 -37
  47. bumble/host.py +104 -35
  48. bumble/keys.py +5 -5
  49. bumble/l2cap.py +312 -409
  50. bumble/link.py +16 -280
  51. bumble/logging.py +65 -0
  52. bumble/pairing.py +23 -20
  53. bumble/pandora/__init__.py +2 -2
  54. bumble/pandora/config.py +2 -2
  55. bumble/pandora/device.py +6 -6
  56. bumble/pandora/host.py +27 -28
  57. bumble/pandora/l2cap.py +2 -2
  58. bumble/pandora/security.py +6 -6
  59. bumble/pandora/utils.py +3 -3
  60. bumble/profiles/ams.py +404 -0
  61. bumble/profiles/ascs.py +142 -131
  62. bumble/profiles/asha.py +2 -2
  63. bumble/profiles/bap.py +3 -4
  64. bumble/profiles/csip.py +2 -2
  65. bumble/profiles/device_information_service.py +2 -2
  66. bumble/profiles/gap.py +2 -2
  67. bumble/profiles/hap.py +34 -33
  68. bumble/profiles/le_audio.py +4 -4
  69. bumble/profiles/mcp.py +4 -4
  70. bumble/profiles/vcs.py +3 -5
  71. bumble/rfcomm.py +10 -10
  72. bumble/rtp.py +1 -2
  73. bumble/sdp.py +2 -2
  74. bumble/smp.py +62 -63
  75. bumble/tools/intel_util.py +3 -2
  76. bumble/tools/rtk_util.py +6 -5
  77. bumble/transport/__init__.py +2 -16
  78. bumble/transport/android_netsim.py +5 -5
  79. bumble/transport/common.py +4 -4
  80. bumble/transport/pyusb.py +2 -2
  81. bumble/utils.py +2 -5
  82. bumble/vendor/android/hci.py +118 -200
  83. bumble/vendor/zephyr/hci.py +32 -27
  84. {bumble-0.0.212.dist-info → bumble-0.0.214.dist-info}/METADATA +4 -3
  85. {bumble-0.0.212.dist-info → bumble-0.0.214.dist-info}/RECORD +89 -90
  86. {bumble-0.0.212.dist-info → bumble-0.0.214.dist-info}/WHEEL +1 -1
  87. {bumble-0.0.212.dist-info → bumble-0.0.214.dist-info}/entry_points.txt +0 -1
  88. bumble/apps/link_relay/__init__.py +0 -0
  89. bumble/apps/link_relay/link_relay.py +0 -289
  90. bumble/apps/link_relay/logging.yml +0 -21
  91. {bumble-0.0.212.dist-info → bumble-0.0.214.dist-info}/licenses/LICENSE +0 -0
  92. {bumble-0.0.212.dist-info → bumble-0.0.214.dist-info}/top_level.txt +0 -0
bumble/l2cap.py CHANGED
@@ -24,21 +24,19 @@ import struct
24
24
 
25
25
  from collections import deque
26
26
  from typing import (
27
- Dict,
28
- Type,
29
- List,
30
27
  Optional,
31
- Tuple,
32
28
  Callable,
33
29
  Any,
34
30
  Union,
35
- Deque,
36
31
  Iterable,
37
32
  SupportsBytes,
33
+ TypeVar,
34
+ ClassVar,
38
35
  TYPE_CHECKING,
39
36
  )
40
37
 
41
38
  from bumble import utils
39
+ from bumble import hci
42
40
  from bumble.colors import color
43
41
  from bumble.core import (
44
42
  InvalidStateError,
@@ -47,13 +45,6 @@ from bumble.core import (
47
45
  OutOfResourcesError,
48
46
  ProtocolError,
49
47
  )
50
- from bumble.hci import (
51
- HCI_LE_Connection_Update_Command,
52
- HCI_Object,
53
- Role,
54
- key_with_value,
55
- name_or_number,
56
- )
57
48
 
58
49
  if TYPE_CHECKING:
59
50
  from bumble.device import Connection
@@ -98,54 +89,29 @@ L2CAP_PSM_DYNAMIC_RANGE_END = 0xFFFF
98
89
  L2CAP_LE_PSM_DYNAMIC_RANGE_START = 0x0080
99
90
  L2CAP_LE_PSM_DYNAMIC_RANGE_END = 0x00FF
100
91
 
101
- # Frame types
102
- L2CAP_COMMAND_REJECT = 0x01
103
- L2CAP_CONNECTION_REQUEST = 0x02
104
- L2CAP_CONNECTION_RESPONSE = 0x03
105
- L2CAP_CONFIGURE_REQUEST = 0x04
106
- L2CAP_CONFIGURE_RESPONSE = 0x05
107
- L2CAP_DISCONNECTION_REQUEST = 0x06
108
- L2CAP_DISCONNECTION_RESPONSE = 0x07
109
- L2CAP_ECHO_REQUEST = 0x08
110
- L2CAP_ECHO_RESPONSE = 0x09
111
- L2CAP_INFORMATION_REQUEST = 0x0A
112
- L2CAP_INFORMATION_RESPONSE = 0x0B
113
- L2CAP_CREATE_CHANNEL_REQUEST = 0x0C
114
- L2CAP_CREATE_CHANNEL_RESPONSE = 0x0D
115
- L2CAP_MOVE_CHANNEL_REQUEST = 0x0E
116
- L2CAP_MOVE_CHANNEL_RESPONSE = 0x0F
117
- L2CAP_MOVE_CHANNEL_CONFIRMATION = 0x10
118
- L2CAP_MOVE_CHANNEL_CONFIRMATION_RESPONSE = 0x11
119
- L2CAP_CONNECTION_PARAMETER_UPDATE_REQUEST = 0x12
120
- L2CAP_CONNECTION_PARAMETER_UPDATE_RESPONSE = 0x13
121
- L2CAP_LE_CREDIT_BASED_CONNECTION_REQUEST = 0x14
122
- L2CAP_LE_CREDIT_BASED_CONNECTION_RESPONSE = 0x15
123
- L2CAP_LE_FLOW_CONTROL_CREDIT = 0x16
124
-
125
- L2CAP_CONTROL_FRAME_NAMES = {
126
- L2CAP_COMMAND_REJECT: 'L2CAP_COMMAND_REJECT',
127
- L2CAP_CONNECTION_REQUEST: 'L2CAP_CONNECTION_REQUEST',
128
- L2CAP_CONNECTION_RESPONSE: 'L2CAP_CONNECTION_RESPONSE',
129
- L2CAP_CONFIGURE_REQUEST: 'L2CAP_CONFIGURE_REQUEST',
130
- L2CAP_CONFIGURE_RESPONSE: 'L2CAP_CONFIGURE_RESPONSE',
131
- L2CAP_DISCONNECTION_REQUEST: 'L2CAP_DISCONNECTION_REQUEST',
132
- L2CAP_DISCONNECTION_RESPONSE: 'L2CAP_DISCONNECTION_RESPONSE',
133
- L2CAP_ECHO_REQUEST: 'L2CAP_ECHO_REQUEST',
134
- L2CAP_ECHO_RESPONSE: 'L2CAP_ECHO_RESPONSE',
135
- L2CAP_INFORMATION_REQUEST: 'L2CAP_INFORMATION_REQUEST',
136
- L2CAP_INFORMATION_RESPONSE: 'L2CAP_INFORMATION_RESPONSE',
137
- L2CAP_CREATE_CHANNEL_REQUEST: 'L2CAP_CREATE_CHANNEL_REQUEST',
138
- L2CAP_CREATE_CHANNEL_RESPONSE: 'L2CAP_CREATE_CHANNEL_RESPONSE',
139
- L2CAP_MOVE_CHANNEL_REQUEST: 'L2CAP_MOVE_CHANNEL_REQUEST',
140
- L2CAP_MOVE_CHANNEL_RESPONSE: 'L2CAP_MOVE_CHANNEL_RESPONSE',
141
- L2CAP_MOVE_CHANNEL_CONFIRMATION: 'L2CAP_MOVE_CHANNEL_CONFIRMATION',
142
- L2CAP_MOVE_CHANNEL_CONFIRMATION_RESPONSE: 'L2CAP_MOVE_CHANNEL_CONFIRMATION_RESPONSE',
143
- L2CAP_CONNECTION_PARAMETER_UPDATE_REQUEST: 'L2CAP_CONNECTION_PARAMETER_UPDATE_REQUEST',
144
- L2CAP_CONNECTION_PARAMETER_UPDATE_RESPONSE: 'L2CAP_CONNECTION_PARAMETER_UPDATE_RESPONSE',
145
- L2CAP_LE_CREDIT_BASED_CONNECTION_REQUEST: 'L2CAP_LE_CREDIT_BASED_CONNECTION_REQUEST',
146
- L2CAP_LE_CREDIT_BASED_CONNECTION_RESPONSE: 'L2CAP_LE_CREDIT_BASED_CONNECTION_RESPONSE',
147
- L2CAP_LE_FLOW_CONTROL_CREDIT: 'L2CAP_LE_FLOW_CONTROL_CREDIT'
148
- }
92
+ class CommandCode(hci.SpecableEnum):
93
+ L2CAP_COMMAND_REJECT = 0x01
94
+ L2CAP_CONNECTION_REQUEST = 0x02
95
+ L2CAP_CONNECTION_RESPONSE = 0x03
96
+ L2CAP_CONFIGURE_REQUEST = 0x04
97
+ L2CAP_CONFIGURE_RESPONSE = 0x05
98
+ L2CAP_DISCONNECTION_REQUEST = 0x06
99
+ L2CAP_DISCONNECTION_RESPONSE = 0x07
100
+ L2CAP_ECHO_REQUEST = 0x08
101
+ L2CAP_ECHO_RESPONSE = 0x09
102
+ L2CAP_INFORMATION_REQUEST = 0x0A
103
+ L2CAP_INFORMATION_RESPONSE = 0x0B
104
+ L2CAP_CREATE_CHANNEL_REQUEST = 0x0C
105
+ L2CAP_CREATE_CHANNEL_RESPONSE = 0x0D
106
+ L2CAP_MOVE_CHANNEL_REQUEST = 0x0E
107
+ L2CAP_MOVE_CHANNEL_RESPONSE = 0x0F
108
+ L2CAP_MOVE_CHANNEL_CONFIRMATION = 0x10
109
+ L2CAP_MOVE_CHANNEL_CONFIRMATION_RESPONSE = 0x11
110
+ L2CAP_CONNECTION_PARAMETER_UPDATE_REQUEST = 0x12
111
+ L2CAP_CONNECTION_PARAMETER_UPDATE_RESPONSE = 0x13
112
+ L2CAP_LE_CREDIT_BASED_CONNECTION_REQUEST = 0x14
113
+ L2CAP_LE_CREDIT_BASED_CONNECTION_RESPONSE = 0x15
114
+ L2CAP_LE_FLOW_CONTROL_CREDIT = 0x16
149
115
 
150
116
  L2CAP_CONNECTION_PARAMETERS_ACCEPTED_RESULT = 0x0000
151
117
  L2CAP_CONNECTION_PARAMETERS_REJECTED_RESULT = 0x0001
@@ -237,46 +203,48 @@ class L2CAP_PDU:
237
203
 
238
204
 
239
205
  # -----------------------------------------------------------------------------
206
+ @dataclasses.dataclass
240
207
  class L2CAP_Control_Frame:
241
208
  '''
242
209
  See Bluetooth spec @ Vol 3, Part A - 4 SIGNALING PACKET FORMATS
243
210
  '''
244
211
 
245
- classes: Dict[int, Type[L2CAP_Control_Frame]] = {}
246
- code = 0
247
- name: str
212
+ classes: ClassVar[dict[int, type[L2CAP_Control_Frame]]] = {}
213
+ fields: ClassVar[hci.Fields] = ()
214
+ code: int = dataclasses.field(default=0, init=False)
215
+ name: str = dataclasses.field(default='', init=False)
216
+ _payload: Optional[bytes] = dataclasses.field(default=None, init=False)
248
217
 
249
- @staticmethod
250
- def from_bytes(pdu: bytes) -> L2CAP_Control_Frame:
251
- code = pdu[0]
252
-
253
- cls = L2CAP_Control_Frame.classes.get(code)
254
- if cls is None:
255
- instance = L2CAP_Control_Frame(pdu)
256
- instance.name = L2CAP_Control_Frame.code_name(code)
257
- instance.code = code
218
+ identifier: int
219
+
220
+ @classmethod
221
+ def from_bytes(cls, pdu: bytes) -> L2CAP_Control_Frame:
222
+ code, identifier, length = struct.unpack_from("<BBH", pdu)
223
+
224
+ subclass = L2CAP_Control_Frame.classes.get(code)
225
+ if subclass is None:
226
+ instance = L2CAP_Control_Frame(identifier=identifier)
227
+ instance.payload = pdu[4:]
228
+ instance.code = CommandCode(code)
229
+ instance.name = instance.code.name
258
230
  return instance
259
- self = cls.__new__(cls)
260
- L2CAP_Control_Frame.__init__(self, pdu)
261
- self.identifier = pdu[1]
262
- length = struct.unpack_from('<H', pdu, 2)[0]
263
- if length + 4 != len(pdu):
231
+ frame = subclass(
232
+ **hci.HCI_Object.dict_from_bytes(pdu, 4, subclass.fields),
233
+ identifier=identifier,
234
+ )
235
+ frame.identifier = identifier
236
+ frame.payload = pdu[4:]
237
+ if length != len(frame.payload):
264
238
  logger.warning(
265
239
  color(
266
- f'!!! length mismatch: expected {len(pdu) - 4} but got {length}',
240
+ f'!!! length mismatch: expected {length} but got {len(frame.payload)}',
267
241
  'red',
268
242
  )
269
243
  )
270
- if hasattr(self, 'fields'):
271
- self.init_from_bytes(pdu, 4)
272
- return self
244
+ return frame
273
245
 
274
246
  @staticmethod
275
- def code_name(code: int) -> str:
276
- return name_or_number(L2CAP_CONTROL_FRAME_NAMES, code)
277
-
278
- @staticmethod
279
- def decode_configuration_options(data: bytes) -> List[Tuple[int, bytes]]:
247
+ def decode_configuration_options(data: bytes) -> list[tuple[int, bytes]]:
280
248
  options = []
281
249
  while len(data) >= 2:
282
250
  value_type = data[0]
@@ -288,119 +256,77 @@ class L2CAP_Control_Frame:
288
256
  return options
289
257
 
290
258
  @staticmethod
291
- def encode_configuration_options(options: List[Tuple[int, bytes]]) -> bytes:
259
+ def encode_configuration_options(options: list[tuple[int, bytes]]) -> bytes:
292
260
  return b''.join(
293
261
  [bytes([option[0], len(option[1])]) + option[1] for option in options]
294
262
  )
295
263
 
296
- @staticmethod
297
- def subclass(fields):
298
- def inner(cls):
299
- cls.name = cls.__name__.upper()
300
- cls.code = key_with_value(L2CAP_CONTROL_FRAME_NAMES, cls.name)
301
- if cls.code is None:
302
- raise KeyError(
303
- f'Control Frame name {cls.name} '
304
- 'not found in L2CAP_CONTROL_FRAME_NAMES'
305
- )
306
- cls.fields = fields
307
-
308
- # Register a factory for this class
309
- L2CAP_Control_Frame.classes[cls.code] = cls
310
-
311
- return cls
312
-
313
- return inner
314
-
315
- def __init__(self, pdu=None, **kwargs) -> None:
316
- self.identifier = kwargs.get('identifier', 0)
317
- if hasattr(self, 'fields'):
318
- if kwargs:
319
- HCI_Object.init_from_fields(self, self.fields, kwargs)
320
- if pdu is None:
321
- data = HCI_Object.dict_to_bytes(kwargs, self.fields)
322
- pdu = (
323
- bytes([self.code, self.identifier])
324
- + struct.pack('<H', len(data))
325
- + data
326
- )
327
- self.pdu = pdu
264
+ _ControlFrame = TypeVar('_ControlFrame', bound='L2CAP_Control_Frame')
265
+
266
+ @classmethod
267
+ def subclass(cls, subclass: type[_ControlFrame]) -> type[_ControlFrame]:
268
+ subclass.name = subclass.__name__.upper()
269
+ subclass.code = CommandCode[subclass.name]
270
+ subclass.fields = hci.HCI_Object.fields_from_dataclass(subclass)
271
+
272
+ # Register a factory for this class
273
+ L2CAP_Control_Frame.classes[subclass.code] = subclass
328
274
 
329
- def init_from_bytes(self, pdu, offset):
330
- return HCI_Object.init_from_bytes(self, pdu, offset, self.fields)
275
+ return subclass
276
+
277
+ @property
278
+ def payload(self) -> bytes:
279
+ if self._payload is None:
280
+ self._payload = hci.HCI_Object.dict_to_bytes(self.__dict__, self.fields)
281
+ return self._payload
282
+
283
+ @payload.setter
284
+ def payload(self, payload: bytes) -> None:
285
+ self._payload = payload
331
286
 
332
287
  def __bytes__(self) -> bytes:
333
- return self.pdu
288
+ return (
289
+ struct.pack('<BBH', self.code, self.identifier, len(self.payload))
290
+ + self.payload
291
+ )
334
292
 
335
293
  def __str__(self) -> str:
336
294
  result = f'{color(self.name, "yellow")} [ID={self.identifier}]'
337
295
  if fields := getattr(self, 'fields', None):
338
- result += ':\n' + HCI_Object.format_fields(self.__dict__, fields, ' ')
296
+ result += ':\n' + hci.HCI_Object.format_fields(self.__dict__, fields, ' ')
339
297
  else:
340
- if len(self.pdu) > 1:
341
- result += f': {self.pdu.hex()}'
298
+ if len(self.payload) > 1:
299
+ result += f': {self.payload.hex()}'
342
300
  return result
343
301
 
344
302
 
345
303
  # -----------------------------------------------------------------------------
346
- @L2CAP_Control_Frame.subclass(
347
- # pylint: disable=unnecessary-lambda
348
- [
349
- (
350
- 'reason',
351
- {'size': 2, 'mapper': lambda x: L2CAP_Command_Reject.reason_name(x)},
352
- ),
353
- ('data', '*'),
354
- ]
355
- )
304
+ @L2CAP_Control_Frame.subclass
305
+ @dataclasses.dataclass
356
306
  class L2CAP_Command_Reject(L2CAP_Control_Frame):
357
307
  '''
358
308
  See Bluetooth spec @ Vol 3, Part A - 4.1 COMMAND REJECT
359
309
  '''
360
310
 
361
- COMMAND_NOT_UNDERSTOOD = 0x0000
362
- SIGNALING_MTU_EXCEEDED = 0x0001
363
- INVALID_CID_IN_REQUEST = 0x0002
311
+ class Reason(hci.SpecableEnum):
312
+ COMMAND_NOT_UNDERSTOOD = 0x0000
313
+ SIGNALING_MTU_EXCEEDED = 0x0001
314
+ INVALID_CID_IN_REQUEST = 0x0002
364
315
 
365
- REASON_NAMES = {
366
- COMMAND_NOT_UNDERSTOOD: 'COMMAND_NOT_UNDERSTOOD',
367
- SIGNALING_MTU_EXCEEDED: 'SIGNALING_MTU_EXCEEDED',
368
- INVALID_CID_IN_REQUEST: 'INVALID_CID_IN_REQUEST',
369
- }
370
-
371
- @staticmethod
372
- def reason_name(reason: int) -> str:
373
- return name_or_number(L2CAP_Command_Reject.REASON_NAMES, reason)
316
+ reason: int = dataclasses.field(metadata=Reason.type_metadata(2))
317
+ data: bytes = dataclasses.field(metadata=hci.metadata('*'))
374
318
 
375
319
 
376
320
  # -----------------------------------------------------------------------------
377
- @L2CAP_Control_Frame.subclass(
378
- # pylint: disable=unnecessary-lambda
379
- [
380
- (
381
- 'psm',
382
- {
383
- 'parser': lambda data, offset: L2CAP_Connection_Request.parse_psm(
384
- data, offset
385
- ),
386
- 'serializer': lambda value: L2CAP_Connection_Request.serialize_psm(
387
- value
388
- ),
389
- },
390
- ),
391
- ('source_cid', 2),
392
- ]
393
- )
321
+ @L2CAP_Control_Frame.subclass
322
+ @dataclasses.dataclass
394
323
  class L2CAP_Connection_Request(L2CAP_Control_Frame):
395
324
  '''
396
325
  See Bluetooth spec @ Vol 3, Part A - 4.2 CONNECTION REQUEST
397
326
  '''
398
327
 
399
- psm: int
400
- source_cid: int
401
-
402
328
  @staticmethod
403
- def parse_psm(data: bytes, offset: int = 0) -> Tuple[int, int]:
329
+ def parse_psm(data: bytes, offset: int = 0) -> tuple[int, int]:
404
330
  psm_length = 2
405
331
  psm = data[offset] | data[offset + 1] << 8
406
332
 
@@ -421,156 +347,138 @@ class L2CAP_Connection_Request(L2CAP_Control_Frame):
421
347
 
422
348
  return serialized
423
349
 
350
+ psm: int = dataclasses.field(
351
+ metadata=hci.metadata(
352
+ {
353
+ 'parser': lambda data, offset: L2CAP_Connection_Request.parse_psm(
354
+ data, offset
355
+ ),
356
+ 'serializer': lambda value: L2CAP_Connection_Request.serialize_psm(
357
+ value
358
+ ),
359
+ }
360
+ )
361
+ )
362
+ source_cid: int = dataclasses.field(metadata=hci.metadata(2))
363
+
424
364
 
425
365
  # -----------------------------------------------------------------------------
426
- @L2CAP_Control_Frame.subclass(
427
- # pylint: disable=unnecessary-lambda
428
- [
429
- ('destination_cid', 2),
430
- ('source_cid', 2),
431
- (
432
- 'result',
433
- {'size': 2, 'mapper': lambda x: L2CAP_Connection_Response.result_name(x)},
434
- ),
435
- ('status', 2),
436
- ]
437
- )
366
+ @L2CAP_Control_Frame.subclass
367
+ @dataclasses.dataclass
438
368
  class L2CAP_Connection_Response(L2CAP_Control_Frame):
439
369
  '''
440
370
  See Bluetooth spec @ Vol 3, Part A - 4.3 CONNECTION RESPONSE
441
371
  '''
442
372
 
443
- source_cid: int
444
- destination_cid: int
445
- status: int
446
- result: int
447
-
448
- CONNECTION_SUCCESSFUL = 0x0000
449
- CONNECTION_PENDING = 0x0001
450
- CONNECTION_REFUSED_PSM_NOT_SUPPORTED = 0x0002
451
- CONNECTION_REFUSED_SECURITY_BLOCK = 0x0003
452
- CONNECTION_REFUSED_NO_RESOURCES_AVAILABLE = 0x0004
453
- CONNECTION_REFUSED_INVALID_SOURCE_CID = 0x0006
454
- CONNECTION_REFUSED_SOURCE_CID_ALREADY_ALLOCATED = 0x0007
455
- CONNECTION_REFUSED_UNACCEPTABLE_PARAMETERS = 0x000B
456
-
457
- # pylint: disable=line-too-long
458
- RESULT_NAMES = {
459
- CONNECTION_SUCCESSFUL: 'CONNECTION_SUCCESSFUL',
460
- CONNECTION_PENDING: 'CONNECTION_PENDING',
461
- CONNECTION_REFUSED_PSM_NOT_SUPPORTED: 'CONNECTION_REFUSED_PSM_NOT_SUPPORTED',
462
- CONNECTION_REFUSED_SECURITY_BLOCK: 'CONNECTION_REFUSED_SECURITY_BLOCK',
463
- CONNECTION_REFUSED_NO_RESOURCES_AVAILABLE: 'CONNECTION_REFUSED_NO_RESOURCES_AVAILABLE',
464
- CONNECTION_REFUSED_INVALID_SOURCE_CID: 'CONNECTION_REFUSED_INVALID_SOURCE_CID',
465
- CONNECTION_REFUSED_SOURCE_CID_ALREADY_ALLOCATED: 'CONNECTION_REFUSED_SOURCE_CID_ALREADY_ALLOCATED',
466
- CONNECTION_REFUSED_UNACCEPTABLE_PARAMETERS: 'CONNECTION_REFUSED_UNACCEPTABLE_PARAMETERS',
467
- }
373
+ class Result(hci.SpecableEnum):
374
+ CONNECTION_SUCCESSFUL = 0x0000
375
+ CONNECTION_PENDING = 0x0001
376
+ CONNECTION_REFUSED_PSM_NOT_SUPPORTED = 0x0002
377
+ CONNECTION_REFUSED_SECURITY_BLOCK = 0x0003
378
+ CONNECTION_REFUSED_NO_RESOURCES_AVAILABLE = 0x0004
379
+ CONNECTION_REFUSED_INVALID_SOURCE_CID = 0x0006
380
+ CONNECTION_REFUSED_SOURCE_CID_ALREADY_ALLOCATED = 0x0007
381
+ CONNECTION_REFUSED_UNACCEPTABLE_PARAMETERS = 0x000B
468
382
 
469
- @staticmethod
470
- def result_name(result: int) -> str:
471
- return name_or_number(L2CAP_Connection_Response.RESULT_NAMES, result)
383
+ destination_cid: int = dataclasses.field(metadata=hci.metadata(2))
384
+ source_cid: int = dataclasses.field(metadata=hci.metadata(2))
385
+ result: int = dataclasses.field(metadata=Result.type_metadata(2))
386
+ status: int = dataclasses.field(metadata=hci.metadata(2))
472
387
 
473
388
 
474
389
  # -----------------------------------------------------------------------------
475
- @L2CAP_Control_Frame.subclass([('destination_cid', 2), ('flags', 2), ('options', '*')])
390
+ @L2CAP_Control_Frame.subclass
391
+ @dataclasses.dataclass
476
392
  class L2CAP_Configure_Request(L2CAP_Control_Frame):
477
393
  '''
478
394
  See Bluetooth spec @ Vol 3, Part A - 4.4 CONFIGURATION REQUEST
479
395
  '''
480
396
 
397
+ destination_cid: int = dataclasses.field(metadata=hci.metadata(2))
398
+ flags: int = dataclasses.field(metadata=hci.metadata(2))
399
+ options: bytes = dataclasses.field(metadata=hci.metadata('*'))
400
+
481
401
 
482
402
  # -----------------------------------------------------------------------------
483
- @L2CAP_Control_Frame.subclass(
484
- # pylint: disable=unnecessary-lambda
485
- [
486
- ('source_cid', 2),
487
- ('flags', 2),
488
- (
489
- 'result',
490
- {'size': 2, 'mapper': lambda x: L2CAP_Configure_Response.result_name(x)},
491
- ),
492
- ('options', '*'),
493
- ]
494
- )
403
+ @L2CAP_Control_Frame.subclass
404
+ @dataclasses.dataclass
495
405
  class L2CAP_Configure_Response(L2CAP_Control_Frame):
496
406
  '''
497
407
  See Bluetooth spec @ Vol 3, Part A - 4.5 CONFIGURATION RESPONSE
498
408
  '''
499
409
 
500
- SUCCESS = 0x0000
501
- FAILURE_UNACCEPTABLE_PARAMETERS = 0x0001
502
- FAILURE_REJECTED = 0x0002
503
- FAILURE_UNKNOWN_OPTIONS = 0x0003
504
- PENDING = 0x0004
505
- FAILURE_FLOW_SPEC_REJECTED = 0x0005
506
-
507
- RESULT_NAMES = {
508
- SUCCESS: 'SUCCESS',
509
- FAILURE_UNACCEPTABLE_PARAMETERS: 'FAILURE_UNACCEPTABLE_PARAMETERS',
510
- FAILURE_REJECTED: 'FAILURE_REJECTED',
511
- FAILURE_UNKNOWN_OPTIONS: 'FAILURE_UNKNOWN_OPTIONS',
512
- PENDING: 'PENDING',
513
- FAILURE_FLOW_SPEC_REJECTED: 'FAILURE_FLOW_SPEC_REJECTED',
514
- }
410
+ class Result(hci.SpecableEnum):
411
+ SUCCESS = 0x0000
412
+ FAILURE_UNACCEPTABLE_PARAMETERS = 0x0001
413
+ FAILURE_REJECTED = 0x0002
414
+ FAILURE_UNKNOWN_OPTIONS = 0x0003
415
+ PENDING = 0x0004
416
+ FAILURE_FLOW_SPEC_REJECTED = 0x0005
515
417
 
516
- @staticmethod
517
- def result_name(result: int) -> str:
518
- return name_or_number(L2CAP_Configure_Response.RESULT_NAMES, result)
418
+ source_cid: int = dataclasses.field(metadata=hci.metadata(2))
419
+ flags: int = dataclasses.field(metadata=hci.metadata(2))
420
+ result: int = dataclasses.field(metadata=Result.type_metadata(2))
421
+ options: bytes = dataclasses.field(metadata=hci.metadata('*'))
519
422
 
520
423
 
521
424
  # -----------------------------------------------------------------------------
522
- @L2CAP_Control_Frame.subclass([('destination_cid', 2), ('source_cid', 2)])
425
+ @L2CAP_Control_Frame.subclass
426
+ @dataclasses.dataclass
523
427
  class L2CAP_Disconnection_Request(L2CAP_Control_Frame):
524
428
  '''
525
429
  See Bluetooth spec @ Vol 3, Part A - 4.6 DISCONNECTION REQUEST
526
430
  '''
527
431
 
432
+ destination_cid: int = dataclasses.field(metadata=hci.metadata(2))
433
+ source_cid: int = dataclasses.field(metadata=hci.metadata(2))
434
+
528
435
 
529
436
  # -----------------------------------------------------------------------------
530
- @L2CAP_Control_Frame.subclass([('destination_cid', 2), ('source_cid', 2)])
437
+ @L2CAP_Control_Frame.subclass
438
+ @dataclasses.dataclass
531
439
  class L2CAP_Disconnection_Response(L2CAP_Control_Frame):
532
440
  '''
533
441
  See Bluetooth spec @ Vol 3, Part A - 4.7 DISCONNECTION RESPONSE
534
442
  '''
535
443
 
444
+ destination_cid: int = dataclasses.field(metadata=hci.metadata(2))
445
+ source_cid: int = dataclasses.field(metadata=hci.metadata(2))
446
+
536
447
 
537
448
  # -----------------------------------------------------------------------------
538
- @L2CAP_Control_Frame.subclass([('data', '*')])
449
+ @L2CAP_Control_Frame.subclass
450
+ @dataclasses.dataclass
539
451
  class L2CAP_Echo_Request(L2CAP_Control_Frame):
540
452
  '''
541
453
  See Bluetooth spec @ Vol 3, Part A - 4.8 ECHO REQUEST
542
454
  '''
543
455
 
456
+ data: bytes = dataclasses.field(metadata=hci.metadata('*'))
457
+
544
458
 
545
459
  # -----------------------------------------------------------------------------
546
- @L2CAP_Control_Frame.subclass([('data', '*')])
460
+ @L2CAP_Control_Frame.subclass
461
+ @dataclasses.dataclass
547
462
  class L2CAP_Echo_Response(L2CAP_Control_Frame):
548
463
  '''
549
464
  See Bluetooth spec @ Vol 3, Part A - 4.9 ECHO RESPONSE
550
465
  '''
551
466
 
467
+ data: bytes = dataclasses.field(metadata=hci.metadata('*'))
468
+
552
469
 
553
470
  # -----------------------------------------------------------------------------
554
- @L2CAP_Control_Frame.subclass(
555
- [
556
- (
557
- 'info_type',
558
- {
559
- 'size': 2,
560
- # pylint: disable-next=unnecessary-lambda
561
- 'mapper': lambda x: L2CAP_Information_Request.info_type_name(x),
562
- },
563
- )
564
- ]
565
- )
471
+ @L2CAP_Control_Frame.subclass
472
+ @dataclasses.dataclass
566
473
  class L2CAP_Information_Request(L2CAP_Control_Frame):
567
474
  '''
568
475
  See Bluetooth spec @ Vol 3, Part A - 4.10 INFORMATION REQUEST
569
476
  '''
570
477
 
571
- CONNECTIONLESS_MTU = 0x0001
572
- EXTENDED_FEATURES_SUPPORTED = 0x0002
573
- FIXED_CHANNELS_SUPPORTED = 0x0003
478
+ class InfoType(hci.SpecableEnum):
479
+ CONNECTIONLESS_MTU = 0x0001
480
+ EXTENDED_FEATURES_SUPPORTED = 0x0002
481
+ FIXED_CHANNELS_SUPPORTED = 0x0003
574
482
 
575
483
  EXTENDED_FEATURE_FLOW_MODE_CONTROL = 0x0001
576
484
  EXTENDED_FEATURE_RETRANSMISSION_MODE = 0x0002
@@ -584,139 +492,108 @@ class L2CAP_Information_Request(L2CAP_Control_Frame):
584
492
  EXTENDED_FEATURE_UNICAST_CONNECTIONLESS_DATA = 0x0200
585
493
  EXTENDED_FEATURE_ENHANCED_CREDIT_BASE_FLOW_CONTROL = 0x0400
586
494
 
587
- INFO_TYPE_NAMES = {
588
- CONNECTIONLESS_MTU: 'CONNECTIONLESS_MTU',
589
- EXTENDED_FEATURES_SUPPORTED: 'EXTENDED_FEATURES_SUPPORTED',
590
- FIXED_CHANNELS_SUPPORTED: 'FIXED_CHANNELS_SUPPORTED',
591
- }
592
-
593
- @staticmethod
594
- def info_type_name(info_type: int) -> str:
595
- return name_or_number(L2CAP_Information_Request.INFO_TYPE_NAMES, info_type)
495
+ info_type: int = dataclasses.field(metadata=InfoType.type_metadata(2))
596
496
 
597
497
 
598
498
  # -----------------------------------------------------------------------------
599
- @L2CAP_Control_Frame.subclass(
600
- [
601
- ('info_type', {'size': 2, 'mapper': L2CAP_Information_Request.info_type_name}),
602
- (
603
- 'result',
604
- # pylint: disable-next=unnecessary-lambda
605
- {'size': 2, 'mapper': lambda x: L2CAP_Information_Response.result_name(x)},
606
- ),
607
- ('data', '*'),
608
- ]
609
- )
499
+ @L2CAP_Control_Frame.subclass
500
+ @dataclasses.dataclass
610
501
  class L2CAP_Information_Response(L2CAP_Control_Frame):
611
502
  '''
612
503
  See Bluetooth spec @ Vol 3, Part A - 4.11 INFORMATION RESPONSE
613
504
  '''
614
505
 
615
- SUCCESS = 0x00
616
- NOT_SUPPORTED = 0x01
506
+ class Result(hci.SpecableEnum):
507
+ SUCCESS = 0x00
508
+ NOT_SUPPORTED = 0x01
617
509
 
618
- RESULT_NAMES = {SUCCESS: 'SUCCESS', NOT_SUPPORTED: 'NOT_SUPPORTED'}
619
-
620
- @staticmethod
621
- def result_name(result: int) -> str:
622
- return name_or_number(L2CAP_Information_Response.RESULT_NAMES, result)
510
+ info_type: int = dataclasses.field(
511
+ metadata=L2CAP_Information_Request.InfoType.type_metadata(2)
512
+ )
513
+ result: int = dataclasses.field(metadata=Result.type_metadata(2))
514
+ data: bytes = dataclasses.field(metadata=hci.metadata('*'))
623
515
 
624
516
 
625
517
  # -----------------------------------------------------------------------------
626
- @L2CAP_Control_Frame.subclass(
627
- [('interval_min', 2), ('interval_max', 2), ('latency', 2), ('timeout', 2)]
628
- )
518
+ @L2CAP_Control_Frame.subclass
519
+ @dataclasses.dataclass
629
520
  class L2CAP_Connection_Parameter_Update_Request(L2CAP_Control_Frame):
630
521
  '''
631
522
  See Bluetooth spec @ Vol 3, Part A - 4.20 CONNECTION PARAMETER UPDATE REQUEST
632
523
  '''
633
524
 
525
+ interval_min: int = dataclasses.field(metadata=hci.metadata(2))
526
+ interval_max: int = dataclasses.field(metadata=hci.metadata(2))
527
+ latency: int = dataclasses.field(metadata=hci.metadata(2))
528
+ timeout: int = dataclasses.field(metadata=hci.metadata(2))
529
+
634
530
 
635
531
  # -----------------------------------------------------------------------------
636
- @L2CAP_Control_Frame.subclass([('result', 2)])
532
+ @L2CAP_Control_Frame.subclass
533
+ @dataclasses.dataclass
637
534
  class L2CAP_Connection_Parameter_Update_Response(L2CAP_Control_Frame):
638
535
  '''
639
536
  See Bluetooth spec @ Vol 3, Part A - 4.21 CONNECTION PARAMETER UPDATE RESPONSE
640
537
  '''
641
538
 
539
+ result: int = dataclasses.field(metadata=hci.metadata(2))
540
+
642
541
 
643
542
  # -----------------------------------------------------------------------------
644
- @L2CAP_Control_Frame.subclass(
645
- [('le_psm', 2), ('source_cid', 2), ('mtu', 2), ('mps', 2), ('initial_credits', 2)]
646
- )
543
+ @L2CAP_Control_Frame.subclass
544
+ @dataclasses.dataclass
647
545
  class L2CAP_LE_Credit_Based_Connection_Request(L2CAP_Control_Frame):
648
546
  '''
649
547
  See Bluetooth spec @ Vol 3, Part A - 4.22 LE CREDIT BASED CONNECTION REQUEST
650
548
  (CODE 0x14)
651
549
  '''
652
550
 
653
- source_cid: int
551
+ le_psm: int = dataclasses.field(metadata=hci.metadata(2))
552
+ source_cid: int = dataclasses.field(metadata=hci.metadata(2))
553
+ mtu: int = dataclasses.field(metadata=hci.metadata(2))
554
+ mps: int = dataclasses.field(metadata=hci.metadata(2))
555
+ initial_credits: int = dataclasses.field(metadata=hci.metadata(2))
654
556
 
655
557
 
656
558
  # -----------------------------------------------------------------------------
657
- @L2CAP_Control_Frame.subclass(
658
- # pylint: disable=unnecessary-lambda,line-too-long
659
- [
660
- ('destination_cid', 2),
661
- ('mtu', 2),
662
- ('mps', 2),
663
- ('initial_credits', 2),
664
- (
665
- 'result',
666
- {
667
- 'size': 2,
668
- 'mapper': lambda x: L2CAP_LE_Credit_Based_Connection_Response.result_name(
669
- x
670
- ),
671
- },
672
- ),
673
- ]
674
- )
559
+ @L2CAP_Control_Frame.subclass
560
+ @dataclasses.dataclass
675
561
  class L2CAP_LE_Credit_Based_Connection_Response(L2CAP_Control_Frame):
676
562
  '''
677
563
  See Bluetooth spec @ Vol 3, Part A - 4.23 LE CREDIT BASED CONNECTION RESPONSE
678
564
  (CODE 0x15)
679
565
  '''
680
566
 
681
- CONNECTION_SUCCESSFUL = 0x0000
682
- CONNECTION_REFUSED_LE_PSM_NOT_SUPPORTED = 0x0002
683
- CONNECTION_REFUSED_NO_RESOURCES_AVAILABLE = 0x0004
684
- CONNECTION_REFUSED_INSUFFICIENT_AUTHENTICATION = 0x0005
685
- CONNECTION_REFUSED_INSUFFICIENT_AUTHORIZATION = 0x0006
686
- CONNECTION_REFUSED_INSUFFICIENT_ENCRYPTION_KEY_SIZE = 0x0007
687
- CONNECTION_REFUSED_INSUFFICIENT_ENCRYPTION = 0x0008
688
- CONNECTION_REFUSED_INVALID_SOURCE_CID = 0x0009
689
- CONNECTION_REFUSED_SOURCE_CID_ALREADY_ALLOCATED = 0x000A
690
- CONNECTION_REFUSED_UNACCEPTABLE_PARAMETERS = 0x000B
691
-
692
- # pylint: disable=line-too-long
693
- RESULT_NAMES = {
694
- CONNECTION_SUCCESSFUL: 'CONNECTION_SUCCESSFUL',
695
- CONNECTION_REFUSED_LE_PSM_NOT_SUPPORTED: 'CONNECTION_REFUSED_LE_PSM_NOT_SUPPORTED',
696
- CONNECTION_REFUSED_NO_RESOURCES_AVAILABLE: 'CONNECTION_REFUSED_NO_RESOURCES_AVAILABLE',
697
- CONNECTION_REFUSED_INSUFFICIENT_AUTHENTICATION: 'CONNECTION_REFUSED_INSUFFICIENT_AUTHENTICATION',
698
- CONNECTION_REFUSED_INSUFFICIENT_AUTHORIZATION: 'CONNECTION_REFUSED_INSUFFICIENT_AUTHORIZATION',
699
- CONNECTION_REFUSED_INSUFFICIENT_ENCRYPTION_KEY_SIZE: 'CONNECTION_REFUSED_INSUFFICIENT_ENCRYPTION_KEY_SIZE',
700
- CONNECTION_REFUSED_INSUFFICIENT_ENCRYPTION: 'CONNECTION_REFUSED_INSUFFICIENT_ENCRYPTION',
701
- CONNECTION_REFUSED_INVALID_SOURCE_CID: 'CONNECTION_REFUSED_INVALID_SOURCE_CID',
702
- CONNECTION_REFUSED_SOURCE_CID_ALREADY_ALLOCATED: 'CONNECTION_REFUSED_SOURCE_CID_ALREADY_ALLOCATED',
703
- CONNECTION_REFUSED_UNACCEPTABLE_PARAMETERS: 'CONNECTION_REFUSED_UNACCEPTABLE_PARAMETERS',
704
- }
705
-
706
- @staticmethod
707
- def result_name(result: int) -> str:
708
- return name_or_number(
709
- L2CAP_LE_Credit_Based_Connection_Response.RESULT_NAMES, result
710
- )
567
+ class Result(hci.SpecableEnum):
568
+ CONNECTION_SUCCESSFUL = 0x0000
569
+ CONNECTION_REFUSED_LE_PSM_NOT_SUPPORTED = 0x0002
570
+ CONNECTION_REFUSED_NO_RESOURCES_AVAILABLE = 0x0004
571
+ CONNECTION_REFUSED_INSUFFICIENT_AUTHENTICATION = 0x0005
572
+ CONNECTION_REFUSED_INSUFFICIENT_AUTHORIZATION = 0x0006
573
+ CONNECTION_REFUSED_INSUFFICIENT_ENCRYPTION_KEY_SIZE = 0x0007
574
+ CONNECTION_REFUSED_INSUFFICIENT_ENCRYPTION = 0x0008
575
+ CONNECTION_REFUSED_INVALID_SOURCE_CID = 0x0009
576
+ CONNECTION_REFUSED_SOURCE_CID_ALREADY_ALLOCATED = 0x000A
577
+ CONNECTION_REFUSED_UNACCEPTABLE_PARAMETERS = 0x000B
578
+
579
+ destination_cid: int = dataclasses.field(metadata=hci.metadata(2))
580
+ mtu: int = dataclasses.field(metadata=hci.metadata(2))
581
+ mps: int = dataclasses.field(metadata=hci.metadata(2))
582
+ initial_credits: int = dataclasses.field(metadata=hci.metadata(2))
583
+ result: int = dataclasses.field(metadata=Result.type_metadata(2))
711
584
 
712
585
 
713
586
  # -----------------------------------------------------------------------------
714
- @L2CAP_Control_Frame.subclass([('cid', 2), ('credits', 2)])
587
+ @L2CAP_Control_Frame.subclass
588
+ @dataclasses.dataclass
715
589
  class L2CAP_LE_Flow_Control_Credit(L2CAP_Control_Frame):
716
590
  '''
717
591
  See Bluetooth spec @ Vol 3, Part A - 4.24 LE FLOW CONTROL CREDIT (CODE 0x16)
718
592
  '''
719
593
 
594
+ cid: int = dataclasses.field(metadata=hci.metadata(2))
595
+ credits: int = dataclasses.field(metadata=hci.metadata(2))
596
+
720
597
 
721
598
  # -----------------------------------------------------------------------------
722
599
  class ClassicChannel(utils.EventEmitter):
@@ -823,9 +700,7 @@ class ClassicChannel(utils.EventEmitter):
823
700
 
824
701
  # Wait for the connection to succeed or fail
825
702
  try:
826
- return await utils.cancel_on_event(
827
- self.connection, 'disconnection', self.connection_result
828
- )
703
+ return await self.connection.cancel_on_disconnection(self.connection_result)
829
704
  finally:
830
705
  self.connection_result = None
831
706
 
@@ -870,7 +745,7 @@ class ClassicChannel(utils.EventEmitter):
870
745
  )
871
746
  )
872
747
 
873
- def on_connection_request(self, request) -> None:
748
+ def on_connection_request(self, request: L2CAP_Connection_Request) -> None:
874
749
  self.destination_cid = request.source_cid
875
750
  self._change_state(self.State.WAIT_CONNECT)
876
751
  self.send_control_frame(
@@ -878,7 +753,7 @@ class ClassicChannel(utils.EventEmitter):
878
753
  identifier=request.identifier,
879
754
  destination_cid=self.source_cid,
880
755
  source_cid=self.destination_cid,
881
- result=L2CAP_Connection_Response.CONNECTION_SUCCESSFUL,
756
+ result=L2CAP_Connection_Response.Result.CONNECTION_SUCCESSFUL,
882
757
  status=0x0000,
883
758
  )
884
759
  )
@@ -886,30 +761,31 @@ class ClassicChannel(utils.EventEmitter):
886
761
  self.send_configure_request()
887
762
  self._change_state(self.State.WAIT_CONFIG_REQ_RSP)
888
763
 
889
- def on_connection_response(self, response):
764
+ def on_connection_response(self, response: L2CAP_Connection_Response):
890
765
  if self.state != self.State.WAIT_CONNECT_RSP:
891
766
  logger.warning(color('invalid state', 'red'))
892
767
  return
893
768
 
894
- if response.result == L2CAP_Connection_Response.CONNECTION_SUCCESSFUL:
769
+ if response.result == L2CAP_Connection_Response.Result.CONNECTION_SUCCESSFUL:
895
770
  self.destination_cid = response.destination_cid
896
771
  self._change_state(self.State.WAIT_CONFIG)
897
772
  self.send_configure_request()
898
773
  self._change_state(self.State.WAIT_CONFIG_REQ_RSP)
899
- elif response.result == L2CAP_Connection_Response.CONNECTION_PENDING:
774
+ elif response.result == L2CAP_Connection_Response.Result.CONNECTION_PENDING:
900
775
  pass
901
776
  else:
902
777
  self._change_state(self.State.CLOSED)
903
- self.connection_result.set_exception(
904
- ProtocolError(
905
- response.result,
906
- 'l2cap',
907
- L2CAP_Connection_Response.result_name(response.result),
778
+ if self.connection_result:
779
+ self.connection_result.set_exception(
780
+ ProtocolError(
781
+ response.result,
782
+ 'l2cap',
783
+ L2CAP_Connection_Response.Result(response.result).name,
784
+ )
908
785
  )
909
- )
910
- self.connection_result = None
786
+ self.connection_result = None
911
787
 
912
- def on_configure_request(self, request) -> None:
788
+ def on_configure_request(self, request: L2CAP_Configure_Request) -> None:
913
789
  if self.state not in (
914
790
  self.State.WAIT_CONFIG,
915
791
  self.State.WAIT_CONFIG_REQ,
@@ -930,7 +806,7 @@ class ClassicChannel(utils.EventEmitter):
930
806
  identifier=request.identifier,
931
807
  source_cid=self.destination_cid,
932
808
  flags=0x0000,
933
- result=L2CAP_Configure_Response.SUCCESS,
809
+ result=L2CAP_Configure_Response.Result.SUCCESS,
934
810
  options=request.options, # TODO: don't accept everything blindly
935
811
  )
936
812
  )
@@ -947,8 +823,8 @@ class ClassicChannel(utils.EventEmitter):
947
823
  elif self.state == self.State.WAIT_CONFIG_REQ_RSP:
948
824
  self._change_state(self.State.WAIT_CONFIG_RSP)
949
825
 
950
- def on_configure_response(self, response) -> None:
951
- if response.result == L2CAP_Configure_Response.SUCCESS:
826
+ def on_configure_response(self, response: L2CAP_Configure_Response) -> None:
827
+ if response.result == L2CAP_Configure_Response.Result.SUCCESS:
952
828
  if self.state == self.State.WAIT_CONFIG_REQ_RSP:
953
829
  self._change_state(self.State.WAIT_CONFIG_REQ)
954
830
  elif self.state in (
@@ -963,7 +839,8 @@ class ClassicChannel(utils.EventEmitter):
963
839
  else:
964
840
  logger.warning(color('invalid state', 'red'))
965
841
  elif (
966
- response.result == L2CAP_Configure_Response.FAILURE_UNACCEPTABLE_PARAMETERS
842
+ response.result
843
+ == L2CAP_Configure_Response.Result.FAILURE_UNACCEPTABLE_PARAMETERS
967
844
  ):
968
845
  # Re-configure with what's suggested in the response
969
846
  self.send_control_frame(
@@ -978,13 +855,13 @@ class ClassicChannel(utils.EventEmitter):
978
855
  logger.warning(
979
856
  color(
980
857
  '!!! configuration rejected: '
981
- f'{L2CAP_Configure_Response.result_name(response.result)}',
858
+ f'{L2CAP_Configure_Response.Result(response.result).name}',
982
859
  'red',
983
860
  )
984
861
  )
985
862
  # TODO: decide how to fail gracefully
986
863
 
987
- def on_disconnection_request(self, request) -> None:
864
+ def on_disconnection_request(self, request: L2CAP_Disconnection_Request) -> None:
988
865
  if self.state in (self.State.OPEN, self.State.WAIT_DISCONNECT):
989
866
  self.send_control_frame(
990
867
  L2CAP_Disconnection_Response(
@@ -999,7 +876,7 @@ class ClassicChannel(utils.EventEmitter):
999
876
  else:
1000
877
  logger.warning(color('invalid state', 'red'))
1001
878
 
1002
- def on_disconnection_response(self, response) -> None:
879
+ def on_disconnection_response(self, response: L2CAP_Disconnection_Response) -> None:
1003
880
  if self.state != self.State.WAIT_DISCONNECT:
1004
881
  logger.warning(color('invalid state', 'red'))
1005
882
  return
@@ -1041,7 +918,7 @@ class LeCreditBasedChannel(utils.EventEmitter):
1041
918
  DISCONNECTED = 4
1042
919
  CONNECTION_ERROR = 5
1043
920
 
1044
- out_queue: Deque[bytes]
921
+ out_queue: deque[bytes]
1045
922
  connection_result: Optional[asyncio.Future[LeCreditBasedChannel]]
1046
923
  disconnection_result: Optional[asyncio.Future[None]]
1047
924
  in_sdu: Optional[bytes]
@@ -1232,7 +1109,9 @@ class LeCreditBasedChannel(utils.EventEmitter):
1232
1109
  self.in_sdu = None
1233
1110
  self.in_sdu_length = 0
1234
1111
 
1235
- def on_connection_response(self, response) -> None:
1112
+ def on_connection_response(
1113
+ self, response: L2CAP_LE_Credit_Based_Connection_Response
1114
+ ) -> None:
1236
1115
  # Look for a matching pending response result
1237
1116
  if self.connection_result is None:
1238
1117
  logger.warning(
@@ -1242,7 +1121,7 @@ class LeCreditBasedChannel(utils.EventEmitter):
1242
1121
 
1243
1122
  if (
1244
1123
  response.result
1245
- == L2CAP_LE_Credit_Based_Connection_Response.CONNECTION_SUCCESSFUL
1124
+ == L2CAP_LE_Credit_Based_Connection_Response.Result.CONNECTION_SUCCESSFUL
1246
1125
  ):
1247
1126
  self.destination_cid = response.destination_cid
1248
1127
  self.peer_mtu = response.mtu
@@ -1256,9 +1135,9 @@ class LeCreditBasedChannel(utils.EventEmitter):
1256
1135
  ProtocolError(
1257
1136
  response.result,
1258
1137
  'l2cap',
1259
- L2CAP_LE_Credit_Based_Connection_Response.result_name(
1138
+ L2CAP_LE_Credit_Based_Connection_Response.Result(
1260
1139
  response.result
1261
- ),
1140
+ ).name,
1262
1141
  )
1263
1142
  )
1264
1143
  self._change_state(self.State.CONNECTION_ERROR)
@@ -1273,7 +1152,7 @@ class LeCreditBasedChannel(utils.EventEmitter):
1273
1152
  # Try to send more data if we have any queued up
1274
1153
  self.process_output()
1275
1154
 
1276
- def on_disconnection_request(self, request) -> None:
1155
+ def on_disconnection_request(self, request: L2CAP_Disconnection_Request) -> None:
1277
1156
  self.send_control_frame(
1278
1157
  L2CAP_Disconnection_Response(
1279
1158
  identifier=request.identifier,
@@ -1284,7 +1163,7 @@ class LeCreditBasedChannel(utils.EventEmitter):
1284
1163
  self._change_state(self.State.DISCONNECTED)
1285
1164
  self.flush_output()
1286
1165
 
1287
- def on_disconnection_response(self, response) -> None:
1166
+ def on_disconnection_response(self, response: L2CAP_Disconnection_Response) -> None:
1288
1167
  if self.state != self.State.DISCONNECTING:
1289
1168
  logger.warning(color('invalid state', 'red'))
1290
1169
  return
@@ -1445,13 +1324,13 @@ class LeCreditBasedChannelServer(utils.EventEmitter):
1445
1324
 
1446
1325
  # -----------------------------------------------------------------------------
1447
1326
  class ChannelManager:
1448
- identifiers: Dict[int, int]
1449
- channels: Dict[int, Dict[int, Union[ClassicChannel, LeCreditBasedChannel]]]
1450
- servers: Dict[int, ClassicChannelServer]
1451
- le_coc_channels: Dict[int, Dict[int, LeCreditBasedChannel]]
1452
- le_coc_servers: Dict[int, LeCreditBasedChannelServer]
1453
- le_coc_requests: Dict[int, L2CAP_LE_Credit_Based_Connection_Request]
1454
- fixed_channels: Dict[int, Optional[Callable[[int, bytes], Any]]]
1327
+ identifiers: dict[int, int]
1328
+ channels: dict[int, dict[int, Union[ClassicChannel, LeCreditBasedChannel]]]
1329
+ servers: dict[int, ClassicChannelServer]
1330
+ le_coc_channels: dict[int, dict[int, LeCreditBasedChannel]]
1331
+ le_coc_servers: dict[int, LeCreditBasedChannelServer]
1332
+ le_coc_requests: dict[int, L2CAP_LE_Credit_Based_Connection_Request]
1333
+ fixed_channels: dict[int, Optional[Callable[[int, bytes], Any]]]
1455
1334
  _host: Optional[Host]
1456
1335
  connection_parameters_update_response: Optional[asyncio.Future[int]]
1457
1336
 
@@ -1733,12 +1612,12 @@ class ChannelManager:
1733
1612
  )
1734
1613
 
1735
1614
  def on_l2cap_command_reject(
1736
- self, _connection: Connection, _cid: int, packet
1615
+ self, _connection: Connection, _cid: int, packet: L2CAP_Command_Reject
1737
1616
  ) -> None:
1738
1617
  logger.warning(f'{color("!!! Command rejected:", "red")} {packet.reason}')
1739
1618
 
1740
1619
  def on_l2cap_connection_request(
1741
- self, connection: Connection, cid: int, request
1620
+ self, connection: Connection, cid: int, request: L2CAP_Connection_Request
1742
1621
  ) -> None:
1743
1622
  # Check if there's a server for this PSM
1744
1623
  server = self.servers.get(request.psm)
@@ -1755,7 +1634,7 @@ class ChannelManager:
1755
1634
  destination_cid=request.source_cid,
1756
1635
  source_cid=0,
1757
1636
  # pylint: disable=line-too-long
1758
- result=L2CAP_Connection_Response.CONNECTION_REFUSED_NO_RESOURCES_AVAILABLE,
1637
+ result=L2CAP_Connection_Response.Result.CONNECTION_REFUSED_NO_RESOURCES_AVAILABLE,
1759
1638
  status=0x0000,
1760
1639
  ),
1761
1640
  )
@@ -1786,13 +1665,16 @@ class ChannelManager:
1786
1665
  destination_cid=request.source_cid,
1787
1666
  source_cid=0,
1788
1667
  # pylint: disable=line-too-long
1789
- result=L2CAP_Connection_Response.CONNECTION_REFUSED_PSM_NOT_SUPPORTED,
1668
+ result=L2CAP_Connection_Response.Result.CONNECTION_REFUSED_PSM_NOT_SUPPORTED,
1790
1669
  status=0x0000,
1791
1670
  ),
1792
1671
  )
1793
1672
 
1794
1673
  def on_l2cap_connection_response(
1795
- self, connection: Connection, cid: int, response
1674
+ self,
1675
+ connection: Connection,
1676
+ cid: int,
1677
+ response: L2CAP_Connection_Response,
1796
1678
  ) -> None:
1797
1679
  if (
1798
1680
  channel := self.find_channel(connection.handle, response.source_cid)
@@ -1809,7 +1691,7 @@ class ChannelManager:
1809
1691
  channel.on_connection_response(response)
1810
1692
 
1811
1693
  def on_l2cap_configure_request(
1812
- self, connection: Connection, cid: int, request
1694
+ self, connection: Connection, cid: int, request: L2CAP_Configure_Request
1813
1695
  ) -> None:
1814
1696
  if (
1815
1697
  channel := self.find_channel(connection.handle, request.destination_cid)
@@ -1826,7 +1708,7 @@ class ChannelManager:
1826
1708
  channel.on_configure_request(request)
1827
1709
 
1828
1710
  def on_l2cap_configure_response(
1829
- self, connection: Connection, cid: int, response
1711
+ self, connection: Connection, cid: int, response: L2CAP_Configure_Response
1830
1712
  ) -> None:
1831
1713
  if (
1832
1714
  channel := self.find_channel(connection.handle, response.source_cid)
@@ -1843,7 +1725,7 @@ class ChannelManager:
1843
1725
  channel.on_configure_response(response)
1844
1726
 
1845
1727
  def on_l2cap_disconnection_request(
1846
- self, connection: Connection, cid: int, request
1728
+ self, connection: Connection, cid: int, request: L2CAP_Disconnection_Request
1847
1729
  ) -> None:
1848
1730
  if (
1849
1731
  channel := self.find_channel(connection.handle, request.destination_cid)
@@ -1860,7 +1742,7 @@ class ChannelManager:
1860
1742
  channel.on_disconnection_request(request)
1861
1743
 
1862
1744
  def on_l2cap_disconnection_response(
1863
- self, connection: Connection, cid: int, response
1745
+ self, connection: Connection, cid: int, response: L2CAP_Disconnection_Response
1864
1746
  ) -> None:
1865
1747
  if (
1866
1748
  channel := self.find_channel(connection.handle, response.source_cid)
@@ -1876,7 +1758,9 @@ class ChannelManager:
1876
1758
 
1877
1759
  channel.on_disconnection_response(response)
1878
1760
 
1879
- def on_l2cap_echo_request(self, connection: Connection, cid: int, request) -> None:
1761
+ def on_l2cap_echo_request(
1762
+ self, connection: Connection, cid: int, request: L2CAP_Echo_Request
1763
+ ) -> None:
1880
1764
  logger.debug(f'<<< Echo request: data={request.data.hex()}')
1881
1765
  self.send_control_frame(
1882
1766
  connection,
@@ -1885,25 +1769,31 @@ class ChannelManager:
1885
1769
  )
1886
1770
 
1887
1771
  def on_l2cap_echo_response(
1888
- self, _connection: Connection, _cid: int, response
1772
+ self, _connection: Connection, _cid: int, response: L2CAP_Echo_Response
1889
1773
  ) -> None:
1890
1774
  logger.debug(f'<<< Echo response: data={response.data.hex()}')
1891
1775
  # TODO notify listeners
1892
1776
 
1893
1777
  def on_l2cap_information_request(
1894
- self, connection: Connection, cid: int, request
1778
+ self, connection: Connection, cid: int, request: L2CAP_Information_Request
1895
1779
  ) -> None:
1896
- if request.info_type == L2CAP_Information_Request.CONNECTIONLESS_MTU:
1897
- result = L2CAP_Information_Response.SUCCESS
1780
+ if request.info_type == L2CAP_Information_Request.InfoType.CONNECTIONLESS_MTU:
1781
+ result = L2CAP_Information_Response.Result.SUCCESS
1898
1782
  data = self.connectionless_mtu.to_bytes(2, 'little')
1899
- elif request.info_type == L2CAP_Information_Request.EXTENDED_FEATURES_SUPPORTED:
1900
- result = L2CAP_Information_Response.SUCCESS
1783
+ elif (
1784
+ request.info_type
1785
+ == L2CAP_Information_Request.InfoType.EXTENDED_FEATURES_SUPPORTED
1786
+ ):
1787
+ result = L2CAP_Information_Response.Result.SUCCESS
1901
1788
  data = sum(self.extended_features).to_bytes(4, 'little')
1902
- elif request.info_type == L2CAP_Information_Request.FIXED_CHANNELS_SUPPORTED:
1903
- result = L2CAP_Information_Response.SUCCESS
1789
+ elif (
1790
+ request.info_type
1791
+ == L2CAP_Information_Request.InfoType.FIXED_CHANNELS_SUPPORTED
1792
+ ):
1793
+ result = L2CAP_Information_Response.Result.SUCCESS
1904
1794
  data = sum(1 << cid for cid in self.fixed_channels).to_bytes(8, 'little')
1905
1795
  else:
1906
- result = L2CAP_Information_Response.NOT_SUPPORTED
1796
+ result = L2CAP_Information_Response.Result.NOT_SUPPORTED
1907
1797
  data = b''
1908
1798
 
1909
1799
  self.send_control_frame(
@@ -1918,9 +1808,12 @@ class ChannelManager:
1918
1808
  )
1919
1809
 
1920
1810
  def on_l2cap_connection_parameter_update_request(
1921
- self, connection: Connection, cid: int, request
1811
+ self,
1812
+ connection: Connection,
1813
+ cid: int,
1814
+ request: L2CAP_Connection_Parameter_Update_Request,
1922
1815
  ):
1923
- if connection.role == Role.CENTRAL:
1816
+ if connection.role == hci.Role.CENTRAL:
1924
1817
  self.send_control_frame(
1925
1818
  connection,
1926
1819
  cid,
@@ -1930,7 +1823,7 @@ class ChannelManager:
1930
1823
  ),
1931
1824
  )
1932
1825
  self.host.send_command_sync(
1933
- HCI_LE_Connection_Update_Command(
1826
+ hci.HCI_LE_Connection_Update_Command(
1934
1827
  connection_handle=connection.handle,
1935
1828
  connection_interval_min=request.interval_min,
1936
1829
  connection_interval_max=request.interval_max,
@@ -1968,6 +1861,7 @@ class ChannelManager:
1968
1861
  connection,
1969
1862
  L2CAP_LE_SIGNALING_CID,
1970
1863
  L2CAP_Connection_Parameter_Update_Request(
1864
+ identifier=self.next_identifier(connection),
1971
1865
  interval_min=interval_min,
1972
1866
  interval_max=interval_max,
1973
1867
  latency=latency,
@@ -1977,7 +1871,10 @@ class ChannelManager:
1977
1871
  return await self.connection_parameters_update_response
1978
1872
 
1979
1873
  def on_l2cap_connection_parameter_update_response(
1980
- self, connection: Connection, cid: int, response
1874
+ self,
1875
+ connection: Connection,
1876
+ cid: int,
1877
+ response: L2CAP_Connection_Parameter_Update_Response,
1981
1878
  ) -> None:
1982
1879
  if self.connection_parameters_update_response:
1983
1880
  self.connection_parameters_update_response.set_result(response.result)
@@ -1991,7 +1888,10 @@ class ChannelManager:
1991
1888
  )
1992
1889
 
1993
1890
  def on_l2cap_le_credit_based_connection_request(
1994
- self, connection: Connection, cid: int, request
1891
+ self,
1892
+ connection: Connection,
1893
+ cid: int,
1894
+ request: L2CAP_LE_Credit_Based_Connection_Request,
1995
1895
  ) -> None:
1996
1896
  if request.le_psm in self.le_coc_servers:
1997
1897
  server = self.le_coc_servers[request.le_psm]
@@ -2012,7 +1912,7 @@ class ChannelManager:
2012
1912
  mps=server.mps,
2013
1913
  initial_credits=0,
2014
1914
  # pylint: disable=line-too-long
2015
- result=L2CAP_LE_Credit_Based_Connection_Response.CONNECTION_REFUSED_SOURCE_CID_ALREADY_ALLOCATED,
1915
+ result=L2CAP_LE_Credit_Based_Connection_Response.Result.CONNECTION_REFUSED_SOURCE_CID_ALREADY_ALLOCATED,
2016
1916
  ),
2017
1917
  )
2018
1918
  return
@@ -2031,7 +1931,7 @@ class ChannelManager:
2031
1931
  mps=server.mps,
2032
1932
  initial_credits=0,
2033
1933
  # pylint: disable=line-too-long
2034
- result=L2CAP_LE_Credit_Based_Connection_Response.CONNECTION_REFUSED_NO_RESOURCES_AVAILABLE,
1934
+ result=L2CAP_LE_Credit_Based_Connection_Response.Result.CONNECTION_REFUSED_NO_RESOURCES_AVAILABLE,
2035
1935
  ),
2036
1936
  )
2037
1937
  return
@@ -2069,7 +1969,7 @@ class ChannelManager:
2069
1969
  mps=server.mps,
2070
1970
  initial_credits=server.max_credits,
2071
1971
  # pylint: disable=line-too-long
2072
- result=L2CAP_LE_Credit_Based_Connection_Response.CONNECTION_SUCCESSFUL,
1972
+ result=L2CAP_LE_Credit_Based_Connection_Response.Result.CONNECTION_SUCCESSFUL,
2073
1973
  ),
2074
1974
  )
2075
1975
 
@@ -2090,12 +1990,15 @@ class ChannelManager:
2090
1990
  mps=L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_MPS,
2091
1991
  initial_credits=0,
2092
1992
  # pylint: disable=line-too-long
2093
- result=L2CAP_LE_Credit_Based_Connection_Response.CONNECTION_REFUSED_LE_PSM_NOT_SUPPORTED,
1993
+ result=L2CAP_LE_Credit_Based_Connection_Response.Result.CONNECTION_REFUSED_LE_PSM_NOT_SUPPORTED,
2094
1994
  ),
2095
1995
  )
2096
1996
 
2097
1997
  def on_l2cap_le_credit_based_connection_response(
2098
- self, connection: Connection, _cid: int, response
1998
+ self,
1999
+ connection: Connection,
2000
+ _cid: int,
2001
+ response: L2CAP_LE_Credit_Based_Connection_Response,
2099
2002
  ) -> None:
2100
2003
  # Find the pending request by identifier
2101
2004
  request = self.le_coc_requests.get(response.identifier)
@@ -2120,7 +2023,7 @@ class ChannelManager:
2120
2023
  channel.on_connection_response(response)
2121
2024
 
2122
2025
  def on_l2cap_le_flow_control_credit(
2123
- self, connection: Connection, _cid: int, credit
2026
+ self, connection: Connection, _cid: int, credit: L2CAP_LE_Flow_Control_Credit
2124
2027
  ) -> None:
2125
2028
  channel = self.find_le_coc_channel(connection.handle, credit.cid)
2126
2029
  if channel is None: