bumble 0.0.212__py3-none-any.whl → 0.0.213__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 (86) 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 +11 -9
  5. bumble/apps/bench.py +480 -31
  6. bumble/apps/console.py +3 -3
  7. bumble/apps/controller_info.py +47 -10
  8. bumble/apps/controller_loopback.py +7 -3
  9. bumble/apps/controllers.py +2 -2
  10. bumble/apps/device_info.py +2 -2
  11. bumble/apps/gatt_dump.py +2 -2
  12. bumble/apps/gg_bridge.py +2 -2
  13. bumble/apps/hci_bridge.py +2 -2
  14. bumble/apps/l2cap_bridge.py +2 -2
  15. bumble/apps/lea_unicast/app.py +6 -1
  16. bumble/apps/pair.py +19 -11
  17. bumble/apps/pandora_server.py +2 -2
  18. bumble/apps/rfcomm_bridge.py +1 -1
  19. bumble/apps/scan.py +2 -2
  20. bumble/apps/show.py +4 -2
  21. bumble/apps/speaker/speaker.html +1 -0
  22. bumble/apps/speaker/speaker.js +113 -62
  23. bumble/apps/speaker/speaker.py +126 -18
  24. bumble/at.py +4 -4
  25. bumble/att.py +2 -6
  26. bumble/avc.py +7 -7
  27. bumble/avctp.py +3 -3
  28. bumble/avdtp.py +16 -20
  29. bumble/avrcp.py +41 -53
  30. bumble/colors.py +2 -2
  31. bumble/controller.py +84 -23
  32. bumble/device.py +348 -182
  33. bumble/drivers/__init__.py +2 -2
  34. bumble/drivers/common.py +0 -2
  35. bumble/drivers/intel.py +37 -40
  36. bumble/drivers/rtk.py +28 -35
  37. bumble/gatt.py +4 -4
  38. bumble/gatt_adapters.py +4 -5
  39. bumble/gatt_client.py +26 -31
  40. bumble/gatt_server.py +7 -11
  41. bumble/hci.py +2601 -2909
  42. bumble/helpers.py +4 -5
  43. bumble/hfp.py +32 -37
  44. bumble/host.py +94 -35
  45. bumble/keys.py +5 -5
  46. bumble/l2cap.py +310 -394
  47. bumble/link.py +6 -270
  48. bumble/pairing.py +23 -20
  49. bumble/pandora/__init__.py +1 -1
  50. bumble/pandora/config.py +2 -2
  51. bumble/pandora/device.py +6 -6
  52. bumble/pandora/host.py +27 -28
  53. bumble/pandora/l2cap.py +2 -2
  54. bumble/pandora/security.py +6 -6
  55. bumble/pandora/utils.py +3 -3
  56. bumble/profiles/ascs.py +132 -131
  57. bumble/profiles/asha.py +2 -2
  58. bumble/profiles/bap.py +3 -4
  59. bumble/profiles/csip.py +2 -2
  60. bumble/profiles/device_information_service.py +2 -2
  61. bumble/profiles/gap.py +2 -2
  62. bumble/profiles/hap.py +34 -33
  63. bumble/profiles/le_audio.py +4 -4
  64. bumble/profiles/mcp.py +4 -4
  65. bumble/profiles/vcs.py +3 -5
  66. bumble/rfcomm.py +10 -10
  67. bumble/rtp.py +1 -2
  68. bumble/sdp.py +2 -2
  69. bumble/smp.py +57 -61
  70. bumble/tools/rtk_util.py +2 -2
  71. bumble/transport/__init__.py +2 -16
  72. bumble/transport/android_netsim.py +5 -5
  73. bumble/transport/common.py +4 -4
  74. bumble/transport/pyusb.py +2 -2
  75. bumble/utils.py +2 -5
  76. bumble/vendor/android/hci.py +118 -200
  77. bumble/vendor/zephyr/hci.py +32 -27
  78. {bumble-0.0.212.dist-info → bumble-0.0.213.dist-info}/METADATA +2 -2
  79. {bumble-0.0.212.dist-info → bumble-0.0.213.dist-info}/RECORD +83 -86
  80. {bumble-0.0.212.dist-info → bumble-0.0.213.dist-info}/WHEEL +1 -1
  81. {bumble-0.0.212.dist-info → bumble-0.0.213.dist-info}/entry_points.txt +0 -1
  82. bumble/apps/link_relay/__init__.py +0 -0
  83. bumble/apps/link_relay/link_relay.py +0 -289
  84. bumble/apps/link_relay/logging.yml +0 -21
  85. {bumble-0.0.212.dist-info → bumble-0.0.213.dist-info}/licenses/LICENSE +0 -0
  86. {bumble-0.0.212.dist-info → bumble-0.0.213.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,47 @@ 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
+ _data: 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]
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)
252
223
 
253
- cls = L2CAP_Control_Frame.classes.get(code)
254
- if cls is None:
224
+ subclass = L2CAP_Control_Frame.classes.get(code)
225
+ if subclass is None:
255
226
  instance = L2CAP_Control_Frame(pdu)
256
- instance.name = L2CAP_Control_Frame.code_name(code)
257
- instance.code = code
227
+ instance.code = CommandCode(code)
228
+ instance.name = instance.code.name
258
229
  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):
230
+ frame = subclass(
231
+ **hci.HCI_Object.dict_from_bytes(pdu, 4, subclass.fields),
232
+ identifier=identifier,
233
+ )
234
+ frame.identifier = identifier
235
+ frame.data = pdu[4:]
236
+ if length != len(pdu):
264
237
  logger.warning(
265
238
  color(
266
239
  f'!!! length mismatch: expected {len(pdu) - 4} but got {length}',
267
240
  'red',
268
241
  )
269
242
  )
270
- if hasattr(self, 'fields'):
271
- self.init_from_bytes(pdu, 4)
272
- return self
273
-
274
- @staticmethod
275
- def code_name(code: int) -> str:
276
- return name_or_number(L2CAP_CONTROL_FRAME_NAMES, code)
243
+ return frame
277
244
 
278
245
  @staticmethod
279
- def decode_configuration_options(data: bytes) -> List[Tuple[int, bytes]]:
246
+ def decode_configuration_options(data: bytes) -> list[tuple[int, bytes]]:
280
247
  options = []
281
248
  while len(data) >= 2:
282
249
  value_type = data[0]
@@ -288,119 +255,91 @@ class L2CAP_Control_Frame:
288
255
  return options
289
256
 
290
257
  @staticmethod
291
- def encode_configuration_options(options: List[Tuple[int, bytes]]) -> bytes:
258
+ def encode_configuration_options(options: list[tuple[int, bytes]]) -> bytes:
292
259
  return b''.join(
293
260
  [bytes([option[0], len(option[1])]) + option[1] for option in options]
294
261
  )
295
262
 
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
263
+ _ControlFrame = TypeVar('_ControlFrame', bound='L2CAP_Control_Frame')
307
264
 
308
- # Register a factory for this class
309
- L2CAP_Control_Frame.classes[cls.code] = cls
265
+ @classmethod
266
+ def subclass(cls, subclass: type[_ControlFrame]) -> type[_ControlFrame]:
267
+ subclass.name = subclass.__name__.upper()
268
+ subclass.code = CommandCode[subclass.name]
269
+ subclass.fields = hci.HCI_Object.fields_from_dataclass(subclass)
310
270
 
311
- return cls
271
+ # Register a factory for this class
272
+ L2CAP_Control_Frame.classes[subclass.code] = subclass
312
273
 
313
- return inner
274
+ return subclass
314
275
 
315
- def __init__(self, pdu=None, **kwargs) -> None:
276
+ def __init__(self, pdu: Optional[bytes] = None, **kwargs) -> None:
316
277
  self.identifier = kwargs.get('identifier', 0)
317
- if hasattr(self, 'fields'):
278
+ if self.fields:
318
279
  if kwargs:
319
- HCI_Object.init_from_fields(self, self.fields, kwargs)
280
+ hci.HCI_Object.init_from_fields(self, self.fields, kwargs)
320
281
  if pdu is None:
321
- data = HCI_Object.dict_to_bytes(kwargs, self.fields)
282
+ data = hci.HCI_Object.dict_to_bytes(kwargs, self.fields)
322
283
  pdu = (
323
284
  bytes([self.code, self.identifier])
324
285
  + struct.pack('<H', len(data))
325
286
  + data
326
287
  )
327
- self.pdu = pdu
288
+ self.data = pdu[4:] if pdu else b''
328
289
 
329
- def init_from_bytes(self, pdu, offset):
330
- return HCI_Object.init_from_bytes(self, pdu, offset, self.fields)
290
+ @property
291
+ def data(self) -> bytes:
292
+ if self._data is None:
293
+ self._data = hci.HCI_Object.dict_to_bytes(self.__dict__, self.fields)
294
+ return self._data
295
+
296
+ @data.setter
297
+ def data(self, parameters: bytes) -> None:
298
+ self._data = parameters
331
299
 
332
300
  def __bytes__(self) -> bytes:
333
- return self.pdu
301
+ return (
302
+ struct.pack('<BBH', self.code, self.identifier, len(self.data) + 4)
303
+ + self.data
304
+ )
334
305
 
335
306
  def __str__(self) -> str:
336
307
  result = f'{color(self.name, "yellow")} [ID={self.identifier}]'
337
308
  if fields := getattr(self, 'fields', None):
338
- result += ':\n' + HCI_Object.format_fields(self.__dict__, fields, ' ')
309
+ result += ':\n' + hci.HCI_Object.format_fields(self.__dict__, fields, ' ')
339
310
  else:
340
- if len(self.pdu) > 1:
341
- result += f': {self.pdu.hex()}'
311
+ if len(self.data) > 1:
312
+ result += f': {self.data.hex()}'
342
313
  return result
343
314
 
344
315
 
345
316
  # -----------------------------------------------------------------------------
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
- )
317
+ @L2CAP_Control_Frame.subclass
318
+ @dataclasses.dataclass
356
319
  class L2CAP_Command_Reject(L2CAP_Control_Frame):
357
320
  '''
358
321
  See Bluetooth spec @ Vol 3, Part A - 4.1 COMMAND REJECT
359
322
  '''
360
323
 
361
- COMMAND_NOT_UNDERSTOOD = 0x0000
362
- SIGNALING_MTU_EXCEEDED = 0x0001
363
- INVALID_CID_IN_REQUEST = 0x0002
324
+ class Reason(hci.SpecableEnum):
325
+ COMMAND_NOT_UNDERSTOOD = 0x0000
326
+ SIGNALING_MTU_EXCEEDED = 0x0001
327
+ INVALID_CID_IN_REQUEST = 0x0002
364
328
 
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)
329
+ reason: int = dataclasses.field(metadata=Reason.type_metadata(2))
330
+ data: bytes = dataclasses.field(metadata=hci.metadata('*'))
374
331
 
375
332
 
376
333
  # -----------------------------------------------------------------------------
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
- )
334
+ @L2CAP_Control_Frame.subclass
335
+ @dataclasses.dataclass
394
336
  class L2CAP_Connection_Request(L2CAP_Control_Frame):
395
337
  '''
396
338
  See Bluetooth spec @ Vol 3, Part A - 4.2 CONNECTION REQUEST
397
339
  '''
398
340
 
399
- psm: int
400
- source_cid: int
401
-
402
341
  @staticmethod
403
- def parse_psm(data: bytes, offset: int = 0) -> Tuple[int, int]:
342
+ def parse_psm(data: bytes, offset: int = 0) -> tuple[int, int]:
404
343
  psm_length = 2
405
344
  psm = data[offset] | data[offset + 1] << 8
406
345
 
@@ -421,156 +360,138 @@ class L2CAP_Connection_Request(L2CAP_Control_Frame):
421
360
 
422
361
  return serialized
423
362
 
363
+ psm: int = dataclasses.field(
364
+ metadata=hci.metadata(
365
+ {
366
+ 'parser': lambda data, offset: L2CAP_Connection_Request.parse_psm(
367
+ data, offset
368
+ ),
369
+ 'serializer': lambda value: L2CAP_Connection_Request.serialize_psm(
370
+ value
371
+ ),
372
+ }
373
+ )
374
+ )
375
+ source_cid: int = dataclasses.field(metadata=hci.metadata(2))
376
+
424
377
 
425
378
  # -----------------------------------------------------------------------------
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
- )
379
+ @L2CAP_Control_Frame.subclass
380
+ @dataclasses.dataclass
438
381
  class L2CAP_Connection_Response(L2CAP_Control_Frame):
439
382
  '''
440
383
  See Bluetooth spec @ Vol 3, Part A - 4.3 CONNECTION RESPONSE
441
384
  '''
442
385
 
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
- }
386
+ class Result(hci.SpecableEnum):
387
+ CONNECTION_SUCCESSFUL = 0x0000
388
+ CONNECTION_PENDING = 0x0001
389
+ CONNECTION_REFUSED_PSM_NOT_SUPPORTED = 0x0002
390
+ CONNECTION_REFUSED_SECURITY_BLOCK = 0x0003
391
+ CONNECTION_REFUSED_NO_RESOURCES_AVAILABLE = 0x0004
392
+ CONNECTION_REFUSED_INVALID_SOURCE_CID = 0x0006
393
+ CONNECTION_REFUSED_SOURCE_CID_ALREADY_ALLOCATED = 0x0007
394
+ CONNECTION_REFUSED_UNACCEPTABLE_PARAMETERS = 0x000B
468
395
 
469
- @staticmethod
470
- def result_name(result: int) -> str:
471
- return name_or_number(L2CAP_Connection_Response.RESULT_NAMES, result)
396
+ destination_cid: int = dataclasses.field(metadata=hci.metadata(2))
397
+ source_cid: int = dataclasses.field(metadata=hci.metadata(2))
398
+ result: int = dataclasses.field(metadata=Result.type_metadata(2))
399
+ status: int = dataclasses.field(metadata=hci.metadata(2))
472
400
 
473
401
 
474
402
  # -----------------------------------------------------------------------------
475
- @L2CAP_Control_Frame.subclass([('destination_cid', 2), ('flags', 2), ('options', '*')])
403
+ @L2CAP_Control_Frame.subclass
404
+ @dataclasses.dataclass
476
405
  class L2CAP_Configure_Request(L2CAP_Control_Frame):
477
406
  '''
478
407
  See Bluetooth spec @ Vol 3, Part A - 4.4 CONFIGURATION REQUEST
479
408
  '''
480
409
 
410
+ destination_cid: int = dataclasses.field(metadata=hci.metadata(2))
411
+ flags: int = dataclasses.field(metadata=hci.metadata(2))
412
+ options: bytes = dataclasses.field(metadata=hci.metadata('*'))
413
+
481
414
 
482
415
  # -----------------------------------------------------------------------------
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
- )
416
+ @L2CAP_Control_Frame.subclass
417
+ @dataclasses.dataclass
495
418
  class L2CAP_Configure_Response(L2CAP_Control_Frame):
496
419
  '''
497
420
  See Bluetooth spec @ Vol 3, Part A - 4.5 CONFIGURATION RESPONSE
498
421
  '''
499
422
 
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
- }
423
+ class Result(hci.SpecableEnum):
424
+ SUCCESS = 0x0000
425
+ FAILURE_UNACCEPTABLE_PARAMETERS = 0x0001
426
+ FAILURE_REJECTED = 0x0002
427
+ FAILURE_UNKNOWN_OPTIONS = 0x0003
428
+ PENDING = 0x0004
429
+ FAILURE_FLOW_SPEC_REJECTED = 0x0005
515
430
 
516
- @staticmethod
517
- def result_name(result: int) -> str:
518
- return name_or_number(L2CAP_Configure_Response.RESULT_NAMES, result)
431
+ source_cid: int = dataclasses.field(metadata=hci.metadata(2))
432
+ flags: int = dataclasses.field(metadata=hci.metadata(2))
433
+ result: int = dataclasses.field(metadata=Result.type_metadata(2))
434
+ options: bytes = dataclasses.field(metadata=hci.metadata('*'))
519
435
 
520
436
 
521
437
  # -----------------------------------------------------------------------------
522
- @L2CAP_Control_Frame.subclass([('destination_cid', 2), ('source_cid', 2)])
438
+ @L2CAP_Control_Frame.subclass
439
+ @dataclasses.dataclass
523
440
  class L2CAP_Disconnection_Request(L2CAP_Control_Frame):
524
441
  '''
525
442
  See Bluetooth spec @ Vol 3, Part A - 4.6 DISCONNECTION REQUEST
526
443
  '''
527
444
 
445
+ destination_cid: int = dataclasses.field(metadata=hci.metadata(2))
446
+ source_cid: int = dataclasses.field(metadata=hci.metadata(2))
447
+
528
448
 
529
449
  # -----------------------------------------------------------------------------
530
- @L2CAP_Control_Frame.subclass([('destination_cid', 2), ('source_cid', 2)])
450
+ @L2CAP_Control_Frame.subclass
451
+ @dataclasses.dataclass
531
452
  class L2CAP_Disconnection_Response(L2CAP_Control_Frame):
532
453
  '''
533
454
  See Bluetooth spec @ Vol 3, Part A - 4.7 DISCONNECTION RESPONSE
534
455
  '''
535
456
 
457
+ destination_cid: int = dataclasses.field(metadata=hci.metadata(2))
458
+ source_cid: int = dataclasses.field(metadata=hci.metadata(2))
459
+
536
460
 
537
461
  # -----------------------------------------------------------------------------
538
- @L2CAP_Control_Frame.subclass([('data', '*')])
462
+ @L2CAP_Control_Frame.subclass
463
+ @dataclasses.dataclass
539
464
  class L2CAP_Echo_Request(L2CAP_Control_Frame):
540
465
  '''
541
466
  See Bluetooth spec @ Vol 3, Part A - 4.8 ECHO REQUEST
542
467
  '''
543
468
 
469
+ data: bytes = dataclasses.field(metadata=hci.metadata('*'))
470
+
544
471
 
545
472
  # -----------------------------------------------------------------------------
546
- @L2CAP_Control_Frame.subclass([('data', '*')])
473
+ @L2CAP_Control_Frame.subclass
474
+ @dataclasses.dataclass
547
475
  class L2CAP_Echo_Response(L2CAP_Control_Frame):
548
476
  '''
549
477
  See Bluetooth spec @ Vol 3, Part A - 4.9 ECHO RESPONSE
550
478
  '''
551
479
 
480
+ data: bytes = dataclasses.field(metadata=hci.metadata('*'))
481
+
552
482
 
553
483
  # -----------------------------------------------------------------------------
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
- )
484
+ @L2CAP_Control_Frame.subclass
485
+ @dataclasses.dataclass
566
486
  class L2CAP_Information_Request(L2CAP_Control_Frame):
567
487
  '''
568
488
  See Bluetooth spec @ Vol 3, Part A - 4.10 INFORMATION REQUEST
569
489
  '''
570
490
 
571
- CONNECTIONLESS_MTU = 0x0001
572
- EXTENDED_FEATURES_SUPPORTED = 0x0002
573
- FIXED_CHANNELS_SUPPORTED = 0x0003
491
+ class InfoType(hci.SpecableEnum):
492
+ CONNECTIONLESS_MTU = 0x0001
493
+ EXTENDED_FEATURES_SUPPORTED = 0x0002
494
+ FIXED_CHANNELS_SUPPORTED = 0x0003
574
495
 
575
496
  EXTENDED_FEATURE_FLOW_MODE_CONTROL = 0x0001
576
497
  EXTENDED_FEATURE_RETRANSMISSION_MODE = 0x0002
@@ -584,139 +505,108 @@ class L2CAP_Information_Request(L2CAP_Control_Frame):
584
505
  EXTENDED_FEATURE_UNICAST_CONNECTIONLESS_DATA = 0x0200
585
506
  EXTENDED_FEATURE_ENHANCED_CREDIT_BASE_FLOW_CONTROL = 0x0400
586
507
 
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)
508
+ info_type: int = dataclasses.field(metadata=InfoType.type_metadata(2))
596
509
 
597
510
 
598
511
  # -----------------------------------------------------------------------------
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
- )
512
+ @L2CAP_Control_Frame.subclass
513
+ @dataclasses.dataclass
610
514
  class L2CAP_Information_Response(L2CAP_Control_Frame):
611
515
  '''
612
516
  See Bluetooth spec @ Vol 3, Part A - 4.11 INFORMATION RESPONSE
613
517
  '''
614
518
 
615
- SUCCESS = 0x00
616
- NOT_SUPPORTED = 0x01
519
+ class Result(hci.SpecableEnum):
520
+ SUCCESS = 0x00
521
+ NOT_SUPPORTED = 0x01
617
522
 
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)
523
+ info_type: int = dataclasses.field(
524
+ metadata=L2CAP_Information_Request.InfoType.type_metadata(2)
525
+ )
526
+ result: int = dataclasses.field(metadata=Result.type_metadata(2))
527
+ data: bytes = dataclasses.field(metadata=hci.metadata('*'))
623
528
 
624
529
 
625
530
  # -----------------------------------------------------------------------------
626
- @L2CAP_Control_Frame.subclass(
627
- [('interval_min', 2), ('interval_max', 2), ('latency', 2), ('timeout', 2)]
628
- )
531
+ @L2CAP_Control_Frame.subclass
532
+ @dataclasses.dataclass
629
533
  class L2CAP_Connection_Parameter_Update_Request(L2CAP_Control_Frame):
630
534
  '''
631
535
  See Bluetooth spec @ Vol 3, Part A - 4.20 CONNECTION PARAMETER UPDATE REQUEST
632
536
  '''
633
537
 
538
+ interval_min: int = dataclasses.field(metadata=hci.metadata(2))
539
+ interval_max: int = dataclasses.field(metadata=hci.metadata(2))
540
+ latency: int = dataclasses.field(metadata=hci.metadata(2))
541
+ timeout: int = dataclasses.field(metadata=hci.metadata(2))
542
+
634
543
 
635
544
  # -----------------------------------------------------------------------------
636
- @L2CAP_Control_Frame.subclass([('result', 2)])
545
+ @L2CAP_Control_Frame.subclass
546
+ @dataclasses.dataclass
637
547
  class L2CAP_Connection_Parameter_Update_Response(L2CAP_Control_Frame):
638
548
  '''
639
549
  See Bluetooth spec @ Vol 3, Part A - 4.21 CONNECTION PARAMETER UPDATE RESPONSE
640
550
  '''
641
551
 
552
+ result: int = dataclasses.field(metadata=hci.metadata(2))
553
+
642
554
 
643
555
  # -----------------------------------------------------------------------------
644
- @L2CAP_Control_Frame.subclass(
645
- [('le_psm', 2), ('source_cid', 2), ('mtu', 2), ('mps', 2), ('initial_credits', 2)]
646
- )
556
+ @L2CAP_Control_Frame.subclass
557
+ @dataclasses.dataclass
647
558
  class L2CAP_LE_Credit_Based_Connection_Request(L2CAP_Control_Frame):
648
559
  '''
649
560
  See Bluetooth spec @ Vol 3, Part A - 4.22 LE CREDIT BASED CONNECTION REQUEST
650
561
  (CODE 0x14)
651
562
  '''
652
563
 
653
- source_cid: int
564
+ le_psm: int = dataclasses.field(metadata=hci.metadata(2))
565
+ source_cid: int = dataclasses.field(metadata=hci.metadata(2))
566
+ mtu: int = dataclasses.field(metadata=hci.metadata(2))
567
+ mps: int = dataclasses.field(metadata=hci.metadata(2))
568
+ initial_credits: int = dataclasses.field(metadata=hci.metadata(2))
654
569
 
655
570
 
656
571
  # -----------------------------------------------------------------------------
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
- )
572
+ @L2CAP_Control_Frame.subclass
573
+ @dataclasses.dataclass
675
574
  class L2CAP_LE_Credit_Based_Connection_Response(L2CAP_Control_Frame):
676
575
  '''
677
576
  See Bluetooth spec @ Vol 3, Part A - 4.23 LE CREDIT BASED CONNECTION RESPONSE
678
577
  (CODE 0x15)
679
578
  '''
680
579
 
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
- )
580
+ class Result(hci.SpecableEnum):
581
+ CONNECTION_SUCCESSFUL = 0x0000
582
+ CONNECTION_REFUSED_LE_PSM_NOT_SUPPORTED = 0x0002
583
+ CONNECTION_REFUSED_NO_RESOURCES_AVAILABLE = 0x0004
584
+ CONNECTION_REFUSED_INSUFFICIENT_AUTHENTICATION = 0x0005
585
+ CONNECTION_REFUSED_INSUFFICIENT_AUTHORIZATION = 0x0006
586
+ CONNECTION_REFUSED_INSUFFICIENT_ENCRYPTION_KEY_SIZE = 0x0007
587
+ CONNECTION_REFUSED_INSUFFICIENT_ENCRYPTION = 0x0008
588
+ CONNECTION_REFUSED_INVALID_SOURCE_CID = 0x0009
589
+ CONNECTION_REFUSED_SOURCE_CID_ALREADY_ALLOCATED = 0x000A
590
+ CONNECTION_REFUSED_UNACCEPTABLE_PARAMETERS = 0x000B
591
+
592
+ destination_cid: int = dataclasses.field(metadata=hci.metadata(2))
593
+ mtu: int = dataclasses.field(metadata=hci.metadata(2))
594
+ mps: int = dataclasses.field(metadata=hci.metadata(2))
595
+ initial_credits: int = dataclasses.field(metadata=hci.metadata(2))
596
+ result: int = dataclasses.field(metadata=Result.type_metadata(2))
711
597
 
712
598
 
713
599
  # -----------------------------------------------------------------------------
714
- @L2CAP_Control_Frame.subclass([('cid', 2), ('credits', 2)])
600
+ @L2CAP_Control_Frame.subclass
601
+ @dataclasses.dataclass
715
602
  class L2CAP_LE_Flow_Control_Credit(L2CAP_Control_Frame):
716
603
  '''
717
604
  See Bluetooth spec @ Vol 3, Part A - 4.24 LE FLOW CONTROL CREDIT (CODE 0x16)
718
605
  '''
719
606
 
607
+ cid: int = dataclasses.field(metadata=hci.metadata(2))
608
+ credits: int = dataclasses.field(metadata=hci.metadata(2))
609
+
720
610
 
721
611
  # -----------------------------------------------------------------------------
722
612
  class ClassicChannel(utils.EventEmitter):
@@ -823,9 +713,7 @@ class ClassicChannel(utils.EventEmitter):
823
713
 
824
714
  # Wait for the connection to succeed or fail
825
715
  try:
826
- return await utils.cancel_on_event(
827
- self.connection, 'disconnection', self.connection_result
828
- )
716
+ return await self.connection.cancel_on_disconnection(self.connection_result)
829
717
  finally:
830
718
  self.connection_result = None
831
719
 
@@ -870,7 +758,7 @@ class ClassicChannel(utils.EventEmitter):
870
758
  )
871
759
  )
872
760
 
873
- def on_connection_request(self, request) -> None:
761
+ def on_connection_request(self, request: L2CAP_Connection_Request) -> None:
874
762
  self.destination_cid = request.source_cid
875
763
  self._change_state(self.State.WAIT_CONNECT)
876
764
  self.send_control_frame(
@@ -878,7 +766,7 @@ class ClassicChannel(utils.EventEmitter):
878
766
  identifier=request.identifier,
879
767
  destination_cid=self.source_cid,
880
768
  source_cid=self.destination_cid,
881
- result=L2CAP_Connection_Response.CONNECTION_SUCCESSFUL,
769
+ result=L2CAP_Connection_Response.Result.CONNECTION_SUCCESSFUL,
882
770
  status=0x0000,
883
771
  )
884
772
  )
@@ -886,30 +774,31 @@ class ClassicChannel(utils.EventEmitter):
886
774
  self.send_configure_request()
887
775
  self._change_state(self.State.WAIT_CONFIG_REQ_RSP)
888
776
 
889
- def on_connection_response(self, response):
777
+ def on_connection_response(self, response: L2CAP_Connection_Response):
890
778
  if self.state != self.State.WAIT_CONNECT_RSP:
891
779
  logger.warning(color('invalid state', 'red'))
892
780
  return
893
781
 
894
- if response.result == L2CAP_Connection_Response.CONNECTION_SUCCESSFUL:
782
+ if response.result == L2CAP_Connection_Response.Result.CONNECTION_SUCCESSFUL:
895
783
  self.destination_cid = response.destination_cid
896
784
  self._change_state(self.State.WAIT_CONFIG)
897
785
  self.send_configure_request()
898
786
  self._change_state(self.State.WAIT_CONFIG_REQ_RSP)
899
- elif response.result == L2CAP_Connection_Response.CONNECTION_PENDING:
787
+ elif response.result == L2CAP_Connection_Response.Result.CONNECTION_PENDING:
900
788
  pass
901
789
  else:
902
790
  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),
791
+ if self.connection_result:
792
+ self.connection_result.set_exception(
793
+ ProtocolError(
794
+ response.result,
795
+ 'l2cap',
796
+ L2CAP_Connection_Response.Result(response.result).name,
797
+ )
908
798
  )
909
- )
910
- self.connection_result = None
799
+ self.connection_result = None
911
800
 
912
- def on_configure_request(self, request) -> None:
801
+ def on_configure_request(self, request: L2CAP_Configure_Request) -> None:
913
802
  if self.state not in (
914
803
  self.State.WAIT_CONFIG,
915
804
  self.State.WAIT_CONFIG_REQ,
@@ -930,7 +819,7 @@ class ClassicChannel(utils.EventEmitter):
930
819
  identifier=request.identifier,
931
820
  source_cid=self.destination_cid,
932
821
  flags=0x0000,
933
- result=L2CAP_Configure_Response.SUCCESS,
822
+ result=L2CAP_Configure_Response.Result.SUCCESS,
934
823
  options=request.options, # TODO: don't accept everything blindly
935
824
  )
936
825
  )
@@ -947,8 +836,8 @@ class ClassicChannel(utils.EventEmitter):
947
836
  elif self.state == self.State.WAIT_CONFIG_REQ_RSP:
948
837
  self._change_state(self.State.WAIT_CONFIG_RSP)
949
838
 
950
- def on_configure_response(self, response) -> None:
951
- if response.result == L2CAP_Configure_Response.SUCCESS:
839
+ def on_configure_response(self, response: L2CAP_Configure_Response) -> None:
840
+ if response.result == L2CAP_Configure_Response.Result.SUCCESS:
952
841
  if self.state == self.State.WAIT_CONFIG_REQ_RSP:
953
842
  self._change_state(self.State.WAIT_CONFIG_REQ)
954
843
  elif self.state in (
@@ -963,7 +852,8 @@ class ClassicChannel(utils.EventEmitter):
963
852
  else:
964
853
  logger.warning(color('invalid state', 'red'))
965
854
  elif (
966
- response.result == L2CAP_Configure_Response.FAILURE_UNACCEPTABLE_PARAMETERS
855
+ response.result
856
+ == L2CAP_Configure_Response.Result.FAILURE_UNACCEPTABLE_PARAMETERS
967
857
  ):
968
858
  # Re-configure with what's suggested in the response
969
859
  self.send_control_frame(
@@ -978,13 +868,13 @@ class ClassicChannel(utils.EventEmitter):
978
868
  logger.warning(
979
869
  color(
980
870
  '!!! configuration rejected: '
981
- f'{L2CAP_Configure_Response.result_name(response.result)}',
871
+ f'{L2CAP_Configure_Response.Result(response.result).name}',
982
872
  'red',
983
873
  )
984
874
  )
985
875
  # TODO: decide how to fail gracefully
986
876
 
987
- def on_disconnection_request(self, request) -> None:
877
+ def on_disconnection_request(self, request: L2CAP_Disconnection_Request) -> None:
988
878
  if self.state in (self.State.OPEN, self.State.WAIT_DISCONNECT):
989
879
  self.send_control_frame(
990
880
  L2CAP_Disconnection_Response(
@@ -999,7 +889,7 @@ class ClassicChannel(utils.EventEmitter):
999
889
  else:
1000
890
  logger.warning(color('invalid state', 'red'))
1001
891
 
1002
- def on_disconnection_response(self, response) -> None:
892
+ def on_disconnection_response(self, response: L2CAP_Disconnection_Response) -> None:
1003
893
  if self.state != self.State.WAIT_DISCONNECT:
1004
894
  logger.warning(color('invalid state', 'red'))
1005
895
  return
@@ -1041,7 +931,7 @@ class LeCreditBasedChannel(utils.EventEmitter):
1041
931
  DISCONNECTED = 4
1042
932
  CONNECTION_ERROR = 5
1043
933
 
1044
- out_queue: Deque[bytes]
934
+ out_queue: deque[bytes]
1045
935
  connection_result: Optional[asyncio.Future[LeCreditBasedChannel]]
1046
936
  disconnection_result: Optional[asyncio.Future[None]]
1047
937
  in_sdu: Optional[bytes]
@@ -1232,7 +1122,9 @@ class LeCreditBasedChannel(utils.EventEmitter):
1232
1122
  self.in_sdu = None
1233
1123
  self.in_sdu_length = 0
1234
1124
 
1235
- def on_connection_response(self, response) -> None:
1125
+ def on_connection_response(
1126
+ self, response: L2CAP_LE_Credit_Based_Connection_Response
1127
+ ) -> None:
1236
1128
  # Look for a matching pending response result
1237
1129
  if self.connection_result is None:
1238
1130
  logger.warning(
@@ -1242,7 +1134,7 @@ class LeCreditBasedChannel(utils.EventEmitter):
1242
1134
 
1243
1135
  if (
1244
1136
  response.result
1245
- == L2CAP_LE_Credit_Based_Connection_Response.CONNECTION_SUCCESSFUL
1137
+ == L2CAP_LE_Credit_Based_Connection_Response.Result.CONNECTION_SUCCESSFUL
1246
1138
  ):
1247
1139
  self.destination_cid = response.destination_cid
1248
1140
  self.peer_mtu = response.mtu
@@ -1256,9 +1148,9 @@ class LeCreditBasedChannel(utils.EventEmitter):
1256
1148
  ProtocolError(
1257
1149
  response.result,
1258
1150
  'l2cap',
1259
- L2CAP_LE_Credit_Based_Connection_Response.result_name(
1151
+ L2CAP_LE_Credit_Based_Connection_Response.Result(
1260
1152
  response.result
1261
- ),
1153
+ ).name,
1262
1154
  )
1263
1155
  )
1264
1156
  self._change_state(self.State.CONNECTION_ERROR)
@@ -1273,7 +1165,7 @@ class LeCreditBasedChannel(utils.EventEmitter):
1273
1165
  # Try to send more data if we have any queued up
1274
1166
  self.process_output()
1275
1167
 
1276
- def on_disconnection_request(self, request) -> None:
1168
+ def on_disconnection_request(self, request: L2CAP_Disconnection_Request) -> None:
1277
1169
  self.send_control_frame(
1278
1170
  L2CAP_Disconnection_Response(
1279
1171
  identifier=request.identifier,
@@ -1284,7 +1176,7 @@ class LeCreditBasedChannel(utils.EventEmitter):
1284
1176
  self._change_state(self.State.DISCONNECTED)
1285
1177
  self.flush_output()
1286
1178
 
1287
- def on_disconnection_response(self, response) -> None:
1179
+ def on_disconnection_response(self, response: L2CAP_Disconnection_Response) -> None:
1288
1180
  if self.state != self.State.DISCONNECTING:
1289
1181
  logger.warning(color('invalid state', 'red'))
1290
1182
  return
@@ -1445,13 +1337,13 @@ class LeCreditBasedChannelServer(utils.EventEmitter):
1445
1337
 
1446
1338
  # -----------------------------------------------------------------------------
1447
1339
  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]]]
1340
+ identifiers: dict[int, int]
1341
+ channels: dict[int, dict[int, Union[ClassicChannel, LeCreditBasedChannel]]]
1342
+ servers: dict[int, ClassicChannelServer]
1343
+ le_coc_channels: dict[int, dict[int, LeCreditBasedChannel]]
1344
+ le_coc_servers: dict[int, LeCreditBasedChannelServer]
1345
+ le_coc_requests: dict[int, L2CAP_LE_Credit_Based_Connection_Request]
1346
+ fixed_channels: dict[int, Optional[Callable[[int, bytes], Any]]]
1455
1347
  _host: Optional[Host]
1456
1348
  connection_parameters_update_response: Optional[asyncio.Future[int]]
1457
1349
 
@@ -1733,12 +1625,12 @@ class ChannelManager:
1733
1625
  )
1734
1626
 
1735
1627
  def on_l2cap_command_reject(
1736
- self, _connection: Connection, _cid: int, packet
1628
+ self, _connection: Connection, _cid: int, packet: L2CAP_Command_Reject
1737
1629
  ) -> None:
1738
1630
  logger.warning(f'{color("!!! Command rejected:", "red")} {packet.reason}')
1739
1631
 
1740
1632
  def on_l2cap_connection_request(
1741
- self, connection: Connection, cid: int, request
1633
+ self, connection: Connection, cid: int, request: L2CAP_Connection_Request
1742
1634
  ) -> None:
1743
1635
  # Check if there's a server for this PSM
1744
1636
  server = self.servers.get(request.psm)
@@ -1755,7 +1647,7 @@ class ChannelManager:
1755
1647
  destination_cid=request.source_cid,
1756
1648
  source_cid=0,
1757
1649
  # pylint: disable=line-too-long
1758
- result=L2CAP_Connection_Response.CONNECTION_REFUSED_NO_RESOURCES_AVAILABLE,
1650
+ result=L2CAP_Connection_Response.Result.CONNECTION_REFUSED_NO_RESOURCES_AVAILABLE,
1759
1651
  status=0x0000,
1760
1652
  ),
1761
1653
  )
@@ -1786,13 +1678,16 @@ class ChannelManager:
1786
1678
  destination_cid=request.source_cid,
1787
1679
  source_cid=0,
1788
1680
  # pylint: disable=line-too-long
1789
- result=L2CAP_Connection_Response.CONNECTION_REFUSED_PSM_NOT_SUPPORTED,
1681
+ result=L2CAP_Connection_Response.Result.CONNECTION_REFUSED_PSM_NOT_SUPPORTED,
1790
1682
  status=0x0000,
1791
1683
  ),
1792
1684
  )
1793
1685
 
1794
1686
  def on_l2cap_connection_response(
1795
- self, connection: Connection, cid: int, response
1687
+ self,
1688
+ connection: Connection,
1689
+ cid: int,
1690
+ response: L2CAP_Connection_Response,
1796
1691
  ) -> None:
1797
1692
  if (
1798
1693
  channel := self.find_channel(connection.handle, response.source_cid)
@@ -1809,7 +1704,7 @@ class ChannelManager:
1809
1704
  channel.on_connection_response(response)
1810
1705
 
1811
1706
  def on_l2cap_configure_request(
1812
- self, connection: Connection, cid: int, request
1707
+ self, connection: Connection, cid: int, request: L2CAP_Configure_Request
1813
1708
  ) -> None:
1814
1709
  if (
1815
1710
  channel := self.find_channel(connection.handle, request.destination_cid)
@@ -1826,7 +1721,7 @@ class ChannelManager:
1826
1721
  channel.on_configure_request(request)
1827
1722
 
1828
1723
  def on_l2cap_configure_response(
1829
- self, connection: Connection, cid: int, response
1724
+ self, connection: Connection, cid: int, response: L2CAP_Configure_Response
1830
1725
  ) -> None:
1831
1726
  if (
1832
1727
  channel := self.find_channel(connection.handle, response.source_cid)
@@ -1843,7 +1738,7 @@ class ChannelManager:
1843
1738
  channel.on_configure_response(response)
1844
1739
 
1845
1740
  def on_l2cap_disconnection_request(
1846
- self, connection: Connection, cid: int, request
1741
+ self, connection: Connection, cid: int, request: L2CAP_Disconnection_Request
1847
1742
  ) -> None:
1848
1743
  if (
1849
1744
  channel := self.find_channel(connection.handle, request.destination_cid)
@@ -1860,7 +1755,7 @@ class ChannelManager:
1860
1755
  channel.on_disconnection_request(request)
1861
1756
 
1862
1757
  def on_l2cap_disconnection_response(
1863
- self, connection: Connection, cid: int, response
1758
+ self, connection: Connection, cid: int, response: L2CAP_Disconnection_Response
1864
1759
  ) -> None:
1865
1760
  if (
1866
1761
  channel := self.find_channel(connection.handle, response.source_cid)
@@ -1876,7 +1771,9 @@ class ChannelManager:
1876
1771
 
1877
1772
  channel.on_disconnection_response(response)
1878
1773
 
1879
- def on_l2cap_echo_request(self, connection: Connection, cid: int, request) -> None:
1774
+ def on_l2cap_echo_request(
1775
+ self, connection: Connection, cid: int, request: L2CAP_Echo_Request
1776
+ ) -> None:
1880
1777
  logger.debug(f'<<< Echo request: data={request.data.hex()}')
1881
1778
  self.send_control_frame(
1882
1779
  connection,
@@ -1885,25 +1782,31 @@ class ChannelManager:
1885
1782
  )
1886
1783
 
1887
1784
  def on_l2cap_echo_response(
1888
- self, _connection: Connection, _cid: int, response
1785
+ self, _connection: Connection, _cid: int, response: L2CAP_Echo_Response
1889
1786
  ) -> None:
1890
1787
  logger.debug(f'<<< Echo response: data={response.data.hex()}')
1891
1788
  # TODO notify listeners
1892
1789
 
1893
1790
  def on_l2cap_information_request(
1894
- self, connection: Connection, cid: int, request
1791
+ self, connection: Connection, cid: int, request: L2CAP_Information_Request
1895
1792
  ) -> None:
1896
- if request.info_type == L2CAP_Information_Request.CONNECTIONLESS_MTU:
1897
- result = L2CAP_Information_Response.SUCCESS
1793
+ if request.info_type == L2CAP_Information_Request.InfoType.CONNECTIONLESS_MTU:
1794
+ result = L2CAP_Information_Response.Result.SUCCESS
1898
1795
  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
1796
+ elif (
1797
+ request.info_type
1798
+ == L2CAP_Information_Request.InfoType.EXTENDED_FEATURES_SUPPORTED
1799
+ ):
1800
+ result = L2CAP_Information_Response.Result.SUCCESS
1901
1801
  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
1802
+ elif (
1803
+ request.info_type
1804
+ == L2CAP_Information_Request.InfoType.FIXED_CHANNELS_SUPPORTED
1805
+ ):
1806
+ result = L2CAP_Information_Response.Result.SUCCESS
1904
1807
  data = sum(1 << cid for cid in self.fixed_channels).to_bytes(8, 'little')
1905
1808
  else:
1906
- result = L2CAP_Information_Response.NOT_SUPPORTED
1809
+ result = L2CAP_Information_Response.Result.NOT_SUPPORTED
1907
1810
  data = b''
1908
1811
 
1909
1812
  self.send_control_frame(
@@ -1918,9 +1821,12 @@ class ChannelManager:
1918
1821
  )
1919
1822
 
1920
1823
  def on_l2cap_connection_parameter_update_request(
1921
- self, connection: Connection, cid: int, request
1824
+ self,
1825
+ connection: Connection,
1826
+ cid: int,
1827
+ request: L2CAP_Connection_Parameter_Update_Request,
1922
1828
  ):
1923
- if connection.role == Role.CENTRAL:
1829
+ if connection.role == hci.Role.CENTRAL:
1924
1830
  self.send_control_frame(
1925
1831
  connection,
1926
1832
  cid,
@@ -1930,7 +1836,7 @@ class ChannelManager:
1930
1836
  ),
1931
1837
  )
1932
1838
  self.host.send_command_sync(
1933
- HCI_LE_Connection_Update_Command(
1839
+ hci.HCI_LE_Connection_Update_Command(
1934
1840
  connection_handle=connection.handle,
1935
1841
  connection_interval_min=request.interval_min,
1936
1842
  connection_interval_max=request.interval_max,
@@ -1968,6 +1874,7 @@ class ChannelManager:
1968
1874
  connection,
1969
1875
  L2CAP_LE_SIGNALING_CID,
1970
1876
  L2CAP_Connection_Parameter_Update_Request(
1877
+ identifier=self.next_identifier(connection),
1971
1878
  interval_min=interval_min,
1972
1879
  interval_max=interval_max,
1973
1880
  latency=latency,
@@ -1977,7 +1884,10 @@ class ChannelManager:
1977
1884
  return await self.connection_parameters_update_response
1978
1885
 
1979
1886
  def on_l2cap_connection_parameter_update_response(
1980
- self, connection: Connection, cid: int, response
1887
+ self,
1888
+ connection: Connection,
1889
+ cid: int,
1890
+ response: L2CAP_Connection_Parameter_Update_Response,
1981
1891
  ) -> None:
1982
1892
  if self.connection_parameters_update_response:
1983
1893
  self.connection_parameters_update_response.set_result(response.result)
@@ -1991,7 +1901,10 @@ class ChannelManager:
1991
1901
  )
1992
1902
 
1993
1903
  def on_l2cap_le_credit_based_connection_request(
1994
- self, connection: Connection, cid: int, request
1904
+ self,
1905
+ connection: Connection,
1906
+ cid: int,
1907
+ request: L2CAP_LE_Credit_Based_Connection_Request,
1995
1908
  ) -> None:
1996
1909
  if request.le_psm in self.le_coc_servers:
1997
1910
  server = self.le_coc_servers[request.le_psm]
@@ -2012,7 +1925,7 @@ class ChannelManager:
2012
1925
  mps=server.mps,
2013
1926
  initial_credits=0,
2014
1927
  # pylint: disable=line-too-long
2015
- result=L2CAP_LE_Credit_Based_Connection_Response.CONNECTION_REFUSED_SOURCE_CID_ALREADY_ALLOCATED,
1928
+ result=L2CAP_LE_Credit_Based_Connection_Response.Result.CONNECTION_REFUSED_SOURCE_CID_ALREADY_ALLOCATED,
2016
1929
  ),
2017
1930
  )
2018
1931
  return
@@ -2031,7 +1944,7 @@ class ChannelManager:
2031
1944
  mps=server.mps,
2032
1945
  initial_credits=0,
2033
1946
  # pylint: disable=line-too-long
2034
- result=L2CAP_LE_Credit_Based_Connection_Response.CONNECTION_REFUSED_NO_RESOURCES_AVAILABLE,
1947
+ result=L2CAP_LE_Credit_Based_Connection_Response.Result.CONNECTION_REFUSED_NO_RESOURCES_AVAILABLE,
2035
1948
  ),
2036
1949
  )
2037
1950
  return
@@ -2069,7 +1982,7 @@ class ChannelManager:
2069
1982
  mps=server.mps,
2070
1983
  initial_credits=server.max_credits,
2071
1984
  # pylint: disable=line-too-long
2072
- result=L2CAP_LE_Credit_Based_Connection_Response.CONNECTION_SUCCESSFUL,
1985
+ result=L2CAP_LE_Credit_Based_Connection_Response.Result.CONNECTION_SUCCESSFUL,
2073
1986
  ),
2074
1987
  )
2075
1988
 
@@ -2090,12 +2003,15 @@ class ChannelManager:
2090
2003
  mps=L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_MPS,
2091
2004
  initial_credits=0,
2092
2005
  # pylint: disable=line-too-long
2093
- result=L2CAP_LE_Credit_Based_Connection_Response.CONNECTION_REFUSED_LE_PSM_NOT_SUPPORTED,
2006
+ result=L2CAP_LE_Credit_Based_Connection_Response.Result.CONNECTION_REFUSED_LE_PSM_NOT_SUPPORTED,
2094
2007
  ),
2095
2008
  )
2096
2009
 
2097
2010
  def on_l2cap_le_credit_based_connection_response(
2098
- self, connection: Connection, _cid: int, response
2011
+ self,
2012
+ connection: Connection,
2013
+ _cid: int,
2014
+ response: L2CAP_LE_Credit_Based_Connection_Response,
2099
2015
  ) -> None:
2100
2016
  # Find the pending request by identifier
2101
2017
  request = self.le_coc_requests.get(response.identifier)
@@ -2120,7 +2036,7 @@ class ChannelManager:
2120
2036
  channel.on_connection_response(response)
2121
2037
 
2122
2038
  def on_l2cap_le_flow_control_credit(
2123
- self, connection: Connection, _cid: int, credit
2039
+ self, connection: Connection, _cid: int, credit: L2CAP_LE_Flow_Control_Credit
2124
2040
  ) -> None:
2125
2041
  channel = self.find_le_coc_channel(connection.handle, credit.cid)
2126
2042
  if channel is None: