bumble 0.0.211__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 (95) 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 +482 -31
  6. bumble/apps/console.py +5 -5
  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 +204 -43
  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 +15 -18
  26. bumble/avc.py +7 -7
  27. bumble/avctp.py +5 -5
  28. bumble/avdtp.py +138 -88
  29. bumble/avrcp.py +52 -58
  30. bumble/colors.py +2 -2
  31. bumble/controller.py +84 -23
  32. bumble/core.py +13 -7
  33. bumble/{crypto.py → crypto/__init__.py} +11 -95
  34. bumble/crypto/builtin.py +652 -0
  35. bumble/crypto/cryptography.py +84 -0
  36. bumble/device.py +688 -345
  37. bumble/drivers/__init__.py +2 -2
  38. bumble/drivers/common.py +0 -2
  39. bumble/drivers/intel.py +40 -40
  40. bumble/drivers/rtk.py +28 -35
  41. bumble/gatt.py +7 -9
  42. bumble/gatt_adapters.py +4 -5
  43. bumble/gatt_client.py +31 -34
  44. bumble/gatt_server.py +15 -17
  45. bumble/hci.py +2635 -2878
  46. bumble/helpers.py +4 -5
  47. bumble/hfp.py +76 -57
  48. bumble/hid.py +24 -12
  49. bumble/host.py +117 -34
  50. bumble/keys.py +68 -52
  51. bumble/l2cap.py +329 -403
  52. bumble/link.py +6 -270
  53. bumble/pairing.py +23 -20
  54. bumble/pandora/__init__.py +1 -1
  55. bumble/pandora/config.py +2 -2
  56. bumble/pandora/device.py +6 -6
  57. bumble/pandora/host.py +38 -39
  58. bumble/pandora/l2cap.py +4 -4
  59. bumble/pandora/security.py +73 -57
  60. bumble/pandora/utils.py +3 -3
  61. bumble/profiles/aics.py +3 -5
  62. bumble/profiles/ancs.py +3 -1
  63. bumble/profiles/ascs.py +143 -136
  64. bumble/profiles/asha.py +13 -8
  65. bumble/profiles/bap.py +3 -4
  66. bumble/profiles/csip.py +3 -5
  67. bumble/profiles/device_information_service.py +2 -2
  68. bumble/profiles/gap.py +2 -2
  69. bumble/profiles/gatt_service.py +1 -3
  70. bumble/profiles/hap.py +42 -58
  71. bumble/profiles/le_audio.py +4 -4
  72. bumble/profiles/mcp.py +16 -13
  73. bumble/profiles/vcs.py +8 -10
  74. bumble/profiles/vocs.py +6 -9
  75. bumble/rfcomm.py +27 -18
  76. bumble/rtp.py +1 -2
  77. bumble/sdp.py +2 -2
  78. bumble/smp.py +71 -69
  79. bumble/tools/rtk_util.py +2 -2
  80. bumble/transport/__init__.py +2 -16
  81. bumble/transport/android_netsim.py +5 -5
  82. bumble/transport/common.py +4 -4
  83. bumble/transport/pyusb.py +2 -2
  84. bumble/utils.py +2 -5
  85. bumble/vendor/android/hci.py +118 -200
  86. bumble/vendor/zephyr/hci.py +32 -27
  87. {bumble-0.0.211.dist-info → bumble-0.0.213.dist-info}/METADATA +5 -5
  88. {bumble-0.0.211.dist-info → bumble-0.0.213.dist-info}/RECORD +92 -93
  89. {bumble-0.0.211.dist-info → bumble-0.0.213.dist-info}/WHEEL +1 -1
  90. {bumble-0.0.211.dist-info → bumble-0.0.213.dist-info}/entry_points.txt +0 -1
  91. bumble/apps/link_relay/__init__.py +0 -0
  92. bumble/apps/link_relay/link_relay.py +0 -289
  93. bumble/apps/link_relay/logging.yml +0 -21
  94. {bumble-0.0.211.dist-info → bumble-0.0.213.dist-info}/licenses/LICENSE +0 -0
  95. {bumble-0.0.211.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
617
-
618
- RESULT_NAMES = {SUCCESS: 'SUCCESS', NOT_SUPPORTED: 'NOT_SUPPORTED'}
519
+ class Result(hci.SpecableEnum):
520
+ SUCCESS = 0x00
521
+ NOT_SUPPORTED = 0x01
619
522
 
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):
@@ -744,6 +634,9 @@ class ClassicChannel(utils.EventEmitter):
744
634
  WAIT_FINAL_RSP = 0x16
745
635
  WAIT_CONTROL_IND = 0x17
746
636
 
637
+ EVENT_OPEN = "open"
638
+ EVENT_CLOSE = "close"
639
+
747
640
  connection_result: Optional[asyncio.Future[None]]
748
641
  disconnection_result: Optional[asyncio.Future[None]]
749
642
  response: Optional[asyncio.Future[bytes]]
@@ -820,9 +713,7 @@ class ClassicChannel(utils.EventEmitter):
820
713
 
821
714
  # Wait for the connection to succeed or fail
822
715
  try:
823
- return await utils.cancel_on_event(
824
- self.connection, 'disconnection', self.connection_result
825
- )
716
+ return await self.connection.cancel_on_disconnection(self.connection_result)
826
717
  finally:
827
718
  self.connection_result = None
828
719
 
@@ -847,7 +738,7 @@ class ClassicChannel(utils.EventEmitter):
847
738
  def abort(self) -> None:
848
739
  if self.state == self.State.OPEN:
849
740
  self._change_state(self.State.CLOSED)
850
- self.emit('close')
741
+ self.emit(self.EVENT_CLOSE)
851
742
 
852
743
  def send_configure_request(self) -> None:
853
744
  options = L2CAP_Control_Frame.encode_configuration_options(
@@ -867,7 +758,7 @@ class ClassicChannel(utils.EventEmitter):
867
758
  )
868
759
  )
869
760
 
870
- def on_connection_request(self, request) -> None:
761
+ def on_connection_request(self, request: L2CAP_Connection_Request) -> None:
871
762
  self.destination_cid = request.source_cid
872
763
  self._change_state(self.State.WAIT_CONNECT)
873
764
  self.send_control_frame(
@@ -875,7 +766,7 @@ class ClassicChannel(utils.EventEmitter):
875
766
  identifier=request.identifier,
876
767
  destination_cid=self.source_cid,
877
768
  source_cid=self.destination_cid,
878
- result=L2CAP_Connection_Response.CONNECTION_SUCCESSFUL,
769
+ result=L2CAP_Connection_Response.Result.CONNECTION_SUCCESSFUL,
879
770
  status=0x0000,
880
771
  )
881
772
  )
@@ -883,30 +774,31 @@ class ClassicChannel(utils.EventEmitter):
883
774
  self.send_configure_request()
884
775
  self._change_state(self.State.WAIT_CONFIG_REQ_RSP)
885
776
 
886
- def on_connection_response(self, response):
777
+ def on_connection_response(self, response: L2CAP_Connection_Response):
887
778
  if self.state != self.State.WAIT_CONNECT_RSP:
888
779
  logger.warning(color('invalid state', 'red'))
889
780
  return
890
781
 
891
- if response.result == L2CAP_Connection_Response.CONNECTION_SUCCESSFUL:
782
+ if response.result == L2CAP_Connection_Response.Result.CONNECTION_SUCCESSFUL:
892
783
  self.destination_cid = response.destination_cid
893
784
  self._change_state(self.State.WAIT_CONFIG)
894
785
  self.send_configure_request()
895
786
  self._change_state(self.State.WAIT_CONFIG_REQ_RSP)
896
- elif response.result == L2CAP_Connection_Response.CONNECTION_PENDING:
787
+ elif response.result == L2CAP_Connection_Response.Result.CONNECTION_PENDING:
897
788
  pass
898
789
  else:
899
790
  self._change_state(self.State.CLOSED)
900
- self.connection_result.set_exception(
901
- ProtocolError(
902
- response.result,
903
- 'l2cap',
904
- 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
+ )
905
798
  )
906
- )
907
- self.connection_result = None
799
+ self.connection_result = None
908
800
 
909
- def on_configure_request(self, request) -> None:
801
+ def on_configure_request(self, request: L2CAP_Configure_Request) -> None:
910
802
  if self.state not in (
911
803
  self.State.WAIT_CONFIG,
912
804
  self.State.WAIT_CONFIG_REQ,
@@ -927,7 +819,7 @@ class ClassicChannel(utils.EventEmitter):
927
819
  identifier=request.identifier,
928
820
  source_cid=self.destination_cid,
929
821
  flags=0x0000,
930
- result=L2CAP_Configure_Response.SUCCESS,
822
+ result=L2CAP_Configure_Response.Result.SUCCESS,
931
823
  options=request.options, # TODO: don't accept everything blindly
932
824
  )
933
825
  )
@@ -940,12 +832,12 @@ class ClassicChannel(utils.EventEmitter):
940
832
  if self.connection_result:
941
833
  self.connection_result.set_result(None)
942
834
  self.connection_result = None
943
- self.emit('open')
835
+ self.emit(self.EVENT_OPEN)
944
836
  elif self.state == self.State.WAIT_CONFIG_REQ_RSP:
945
837
  self._change_state(self.State.WAIT_CONFIG_RSP)
946
838
 
947
- def on_configure_response(self, response) -> None:
948
- 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:
949
841
  if self.state == self.State.WAIT_CONFIG_REQ_RSP:
950
842
  self._change_state(self.State.WAIT_CONFIG_REQ)
951
843
  elif self.state in (
@@ -956,11 +848,12 @@ class ClassicChannel(utils.EventEmitter):
956
848
  if self.connection_result:
957
849
  self.connection_result.set_result(None)
958
850
  self.connection_result = None
959
- self.emit('open')
851
+ self.emit(self.EVENT_OPEN)
960
852
  else:
961
853
  logger.warning(color('invalid state', 'red'))
962
854
  elif (
963
- response.result == L2CAP_Configure_Response.FAILURE_UNACCEPTABLE_PARAMETERS
855
+ response.result
856
+ == L2CAP_Configure_Response.Result.FAILURE_UNACCEPTABLE_PARAMETERS
964
857
  ):
965
858
  # Re-configure with what's suggested in the response
966
859
  self.send_control_frame(
@@ -975,13 +868,13 @@ class ClassicChannel(utils.EventEmitter):
975
868
  logger.warning(
976
869
  color(
977
870
  '!!! configuration rejected: '
978
- f'{L2CAP_Configure_Response.result_name(response.result)}',
871
+ f'{L2CAP_Configure_Response.Result(response.result).name}',
979
872
  'red',
980
873
  )
981
874
  )
982
875
  # TODO: decide how to fail gracefully
983
876
 
984
- def on_disconnection_request(self, request) -> None:
877
+ def on_disconnection_request(self, request: L2CAP_Disconnection_Request) -> None:
985
878
  if self.state in (self.State.OPEN, self.State.WAIT_DISCONNECT):
986
879
  self.send_control_frame(
987
880
  L2CAP_Disconnection_Response(
@@ -991,12 +884,12 @@ class ClassicChannel(utils.EventEmitter):
991
884
  )
992
885
  )
993
886
  self._change_state(self.State.CLOSED)
994
- self.emit('close')
887
+ self.emit(self.EVENT_CLOSE)
995
888
  self.manager.on_channel_closed(self)
996
889
  else:
997
890
  logger.warning(color('invalid state', 'red'))
998
891
 
999
- def on_disconnection_response(self, response) -> None:
892
+ def on_disconnection_response(self, response: L2CAP_Disconnection_Response) -> None:
1000
893
  if self.state != self.State.WAIT_DISCONNECT:
1001
894
  logger.warning(color('invalid state', 'red'))
1002
895
  return
@@ -1012,7 +905,7 @@ class ClassicChannel(utils.EventEmitter):
1012
905
  if self.disconnection_result:
1013
906
  self.disconnection_result.set_result(None)
1014
907
  self.disconnection_result = None
1015
- self.emit('close')
908
+ self.emit(self.EVENT_CLOSE)
1016
909
  self.manager.on_channel_closed(self)
1017
910
 
1018
911
  def __str__(self) -> str:
@@ -1038,7 +931,7 @@ class LeCreditBasedChannel(utils.EventEmitter):
1038
931
  DISCONNECTED = 4
1039
932
  CONNECTION_ERROR = 5
1040
933
 
1041
- out_queue: Deque[bytes]
934
+ out_queue: deque[bytes]
1042
935
  connection_result: Optional[asyncio.Future[LeCreditBasedChannel]]
1043
936
  disconnection_result: Optional[asyncio.Future[None]]
1044
937
  in_sdu: Optional[bytes]
@@ -1047,6 +940,9 @@ class LeCreditBasedChannel(utils.EventEmitter):
1047
940
  connection: Connection
1048
941
  sink: Optional[Callable[[bytes], Any]]
1049
942
 
943
+ EVENT_OPEN = "open"
944
+ EVENT_CLOSE = "close"
945
+
1050
946
  def __init__(
1051
947
  self,
1052
948
  manager: ChannelManager,
@@ -1098,9 +994,9 @@ class LeCreditBasedChannel(utils.EventEmitter):
1098
994
  self.state = new_state
1099
995
 
1100
996
  if new_state == self.State.CONNECTED:
1101
- self.emit('open')
997
+ self.emit(self.EVENT_OPEN)
1102
998
  elif new_state == self.State.DISCONNECTED:
1103
- self.emit('close')
999
+ self.emit(self.EVENT_CLOSE)
1104
1000
 
1105
1001
  def send_pdu(self, pdu: Union[SupportsBytes, bytes]) -> None:
1106
1002
  self.manager.send_pdu(self.connection, self.destination_cid, pdu)
@@ -1226,7 +1122,9 @@ class LeCreditBasedChannel(utils.EventEmitter):
1226
1122
  self.in_sdu = None
1227
1123
  self.in_sdu_length = 0
1228
1124
 
1229
- def on_connection_response(self, response) -> None:
1125
+ def on_connection_response(
1126
+ self, response: L2CAP_LE_Credit_Based_Connection_Response
1127
+ ) -> None:
1230
1128
  # Look for a matching pending response result
1231
1129
  if self.connection_result is None:
1232
1130
  logger.warning(
@@ -1236,7 +1134,7 @@ class LeCreditBasedChannel(utils.EventEmitter):
1236
1134
 
1237
1135
  if (
1238
1136
  response.result
1239
- == L2CAP_LE_Credit_Based_Connection_Response.CONNECTION_SUCCESSFUL
1137
+ == L2CAP_LE_Credit_Based_Connection_Response.Result.CONNECTION_SUCCESSFUL
1240
1138
  ):
1241
1139
  self.destination_cid = response.destination_cid
1242
1140
  self.peer_mtu = response.mtu
@@ -1250,9 +1148,9 @@ class LeCreditBasedChannel(utils.EventEmitter):
1250
1148
  ProtocolError(
1251
1149
  response.result,
1252
1150
  'l2cap',
1253
- L2CAP_LE_Credit_Based_Connection_Response.result_name(
1151
+ L2CAP_LE_Credit_Based_Connection_Response.Result(
1254
1152
  response.result
1255
- ),
1153
+ ).name,
1256
1154
  )
1257
1155
  )
1258
1156
  self._change_state(self.State.CONNECTION_ERROR)
@@ -1267,7 +1165,7 @@ class LeCreditBasedChannel(utils.EventEmitter):
1267
1165
  # Try to send more data if we have any queued up
1268
1166
  self.process_output()
1269
1167
 
1270
- def on_disconnection_request(self, request) -> None:
1168
+ def on_disconnection_request(self, request: L2CAP_Disconnection_Request) -> None:
1271
1169
  self.send_control_frame(
1272
1170
  L2CAP_Disconnection_Response(
1273
1171
  identifier=request.identifier,
@@ -1278,7 +1176,7 @@ class LeCreditBasedChannel(utils.EventEmitter):
1278
1176
  self._change_state(self.State.DISCONNECTED)
1279
1177
  self.flush_output()
1280
1178
 
1281
- def on_disconnection_response(self, response) -> None:
1179
+ def on_disconnection_response(self, response: L2CAP_Disconnection_Response) -> None:
1282
1180
  if self.state != self.State.DISCONNECTING:
1283
1181
  logger.warning(color('invalid state', 'red'))
1284
1182
  return
@@ -1381,6 +1279,8 @@ class LeCreditBasedChannel(utils.EventEmitter):
1381
1279
 
1382
1280
  # -----------------------------------------------------------------------------
1383
1281
  class ClassicChannelServer(utils.EventEmitter):
1282
+ EVENT_CONNECTION = "connection"
1283
+
1384
1284
  def __init__(
1385
1285
  self,
1386
1286
  manager: ChannelManager,
@@ -1395,7 +1295,7 @@ class ClassicChannelServer(utils.EventEmitter):
1395
1295
  self.mtu = mtu
1396
1296
 
1397
1297
  def on_connection(self, channel: ClassicChannel) -> None:
1398
- self.emit('connection', channel)
1298
+ self.emit(self.EVENT_CONNECTION, channel)
1399
1299
  if self.handler:
1400
1300
  self.handler(channel)
1401
1301
 
@@ -1406,6 +1306,8 @@ class ClassicChannelServer(utils.EventEmitter):
1406
1306
 
1407
1307
  # -----------------------------------------------------------------------------
1408
1308
  class LeCreditBasedChannelServer(utils.EventEmitter):
1309
+ EVENT_CONNECTION = "connection"
1310
+
1409
1311
  def __init__(
1410
1312
  self,
1411
1313
  manager: ChannelManager,
@@ -1424,7 +1326,7 @@ class LeCreditBasedChannelServer(utils.EventEmitter):
1424
1326
  self.mps = mps
1425
1327
 
1426
1328
  def on_connection(self, channel: LeCreditBasedChannel) -> None:
1427
- self.emit('connection', channel)
1329
+ self.emit(self.EVENT_CONNECTION, channel)
1428
1330
  if self.handler:
1429
1331
  self.handler(channel)
1430
1332
 
@@ -1435,13 +1337,13 @@ class LeCreditBasedChannelServer(utils.EventEmitter):
1435
1337
 
1436
1338
  # -----------------------------------------------------------------------------
1437
1339
  class ChannelManager:
1438
- identifiers: Dict[int, int]
1439
- channels: Dict[int, Dict[int, Union[ClassicChannel, LeCreditBasedChannel]]]
1440
- servers: Dict[int, ClassicChannelServer]
1441
- le_coc_channels: Dict[int, Dict[int, LeCreditBasedChannel]]
1442
- le_coc_servers: Dict[int, LeCreditBasedChannelServer]
1443
- le_coc_requests: Dict[int, L2CAP_LE_Credit_Based_Connection_Request]
1444
- 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]]]
1445
1347
  _host: Optional[Host]
1446
1348
  connection_parameters_update_response: Optional[asyncio.Future[int]]
1447
1349
 
@@ -1723,12 +1625,12 @@ class ChannelManager:
1723
1625
  )
1724
1626
 
1725
1627
  def on_l2cap_command_reject(
1726
- self, _connection: Connection, _cid: int, packet
1628
+ self, _connection: Connection, _cid: int, packet: L2CAP_Command_Reject
1727
1629
  ) -> None:
1728
1630
  logger.warning(f'{color("!!! Command rejected:", "red")} {packet.reason}')
1729
1631
 
1730
1632
  def on_l2cap_connection_request(
1731
- self, connection: Connection, cid: int, request
1633
+ self, connection: Connection, cid: int, request: L2CAP_Connection_Request
1732
1634
  ) -> None:
1733
1635
  # Check if there's a server for this PSM
1734
1636
  server = self.servers.get(request.psm)
@@ -1745,7 +1647,7 @@ class ChannelManager:
1745
1647
  destination_cid=request.source_cid,
1746
1648
  source_cid=0,
1747
1649
  # pylint: disable=line-too-long
1748
- result=L2CAP_Connection_Response.CONNECTION_REFUSED_NO_RESOURCES_AVAILABLE,
1650
+ result=L2CAP_Connection_Response.Result.CONNECTION_REFUSED_NO_RESOURCES_AVAILABLE,
1749
1651
  status=0x0000,
1750
1652
  ),
1751
1653
  )
@@ -1776,13 +1678,16 @@ class ChannelManager:
1776
1678
  destination_cid=request.source_cid,
1777
1679
  source_cid=0,
1778
1680
  # pylint: disable=line-too-long
1779
- result=L2CAP_Connection_Response.CONNECTION_REFUSED_PSM_NOT_SUPPORTED,
1681
+ result=L2CAP_Connection_Response.Result.CONNECTION_REFUSED_PSM_NOT_SUPPORTED,
1780
1682
  status=0x0000,
1781
1683
  ),
1782
1684
  )
1783
1685
 
1784
1686
  def on_l2cap_connection_response(
1785
- self, connection: Connection, cid: int, response
1687
+ self,
1688
+ connection: Connection,
1689
+ cid: int,
1690
+ response: L2CAP_Connection_Response,
1786
1691
  ) -> None:
1787
1692
  if (
1788
1693
  channel := self.find_channel(connection.handle, response.source_cid)
@@ -1799,7 +1704,7 @@ class ChannelManager:
1799
1704
  channel.on_connection_response(response)
1800
1705
 
1801
1706
  def on_l2cap_configure_request(
1802
- self, connection: Connection, cid: int, request
1707
+ self, connection: Connection, cid: int, request: L2CAP_Configure_Request
1803
1708
  ) -> None:
1804
1709
  if (
1805
1710
  channel := self.find_channel(connection.handle, request.destination_cid)
@@ -1816,7 +1721,7 @@ class ChannelManager:
1816
1721
  channel.on_configure_request(request)
1817
1722
 
1818
1723
  def on_l2cap_configure_response(
1819
- self, connection: Connection, cid: int, response
1724
+ self, connection: Connection, cid: int, response: L2CAP_Configure_Response
1820
1725
  ) -> None:
1821
1726
  if (
1822
1727
  channel := self.find_channel(connection.handle, response.source_cid)
@@ -1833,7 +1738,7 @@ class ChannelManager:
1833
1738
  channel.on_configure_response(response)
1834
1739
 
1835
1740
  def on_l2cap_disconnection_request(
1836
- self, connection: Connection, cid: int, request
1741
+ self, connection: Connection, cid: int, request: L2CAP_Disconnection_Request
1837
1742
  ) -> None:
1838
1743
  if (
1839
1744
  channel := self.find_channel(connection.handle, request.destination_cid)
@@ -1850,7 +1755,7 @@ class ChannelManager:
1850
1755
  channel.on_disconnection_request(request)
1851
1756
 
1852
1757
  def on_l2cap_disconnection_response(
1853
- self, connection: Connection, cid: int, response
1758
+ self, connection: Connection, cid: int, response: L2CAP_Disconnection_Response
1854
1759
  ) -> None:
1855
1760
  if (
1856
1761
  channel := self.find_channel(connection.handle, response.source_cid)
@@ -1866,7 +1771,9 @@ class ChannelManager:
1866
1771
 
1867
1772
  channel.on_disconnection_response(response)
1868
1773
 
1869
- 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:
1870
1777
  logger.debug(f'<<< Echo request: data={request.data.hex()}')
1871
1778
  self.send_control_frame(
1872
1779
  connection,
@@ -1875,25 +1782,31 @@ class ChannelManager:
1875
1782
  )
1876
1783
 
1877
1784
  def on_l2cap_echo_response(
1878
- self, _connection: Connection, _cid: int, response
1785
+ self, _connection: Connection, _cid: int, response: L2CAP_Echo_Response
1879
1786
  ) -> None:
1880
1787
  logger.debug(f'<<< Echo response: data={response.data.hex()}')
1881
1788
  # TODO notify listeners
1882
1789
 
1883
1790
  def on_l2cap_information_request(
1884
- self, connection: Connection, cid: int, request
1791
+ self, connection: Connection, cid: int, request: L2CAP_Information_Request
1885
1792
  ) -> None:
1886
- if request.info_type == L2CAP_Information_Request.CONNECTIONLESS_MTU:
1887
- result = L2CAP_Information_Response.SUCCESS
1793
+ if request.info_type == L2CAP_Information_Request.InfoType.CONNECTIONLESS_MTU:
1794
+ result = L2CAP_Information_Response.Result.SUCCESS
1888
1795
  data = self.connectionless_mtu.to_bytes(2, 'little')
1889
- elif request.info_type == L2CAP_Information_Request.EXTENDED_FEATURES_SUPPORTED:
1890
- 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
1891
1801
  data = sum(self.extended_features).to_bytes(4, 'little')
1892
- elif request.info_type == L2CAP_Information_Request.FIXED_CHANNELS_SUPPORTED:
1893
- 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
1894
1807
  data = sum(1 << cid for cid in self.fixed_channels).to_bytes(8, 'little')
1895
1808
  else:
1896
- result = L2CAP_Information_Response.NOT_SUPPORTED
1809
+ result = L2CAP_Information_Response.Result.NOT_SUPPORTED
1897
1810
  data = b''
1898
1811
 
1899
1812
  self.send_control_frame(
@@ -1908,9 +1821,12 @@ class ChannelManager:
1908
1821
  )
1909
1822
 
1910
1823
  def on_l2cap_connection_parameter_update_request(
1911
- self, connection: Connection, cid: int, request
1824
+ self,
1825
+ connection: Connection,
1826
+ cid: int,
1827
+ request: L2CAP_Connection_Parameter_Update_Request,
1912
1828
  ):
1913
- if connection.role == Role.CENTRAL:
1829
+ if connection.role == hci.Role.CENTRAL:
1914
1830
  self.send_control_frame(
1915
1831
  connection,
1916
1832
  cid,
@@ -1920,7 +1836,7 @@ class ChannelManager:
1920
1836
  ),
1921
1837
  )
1922
1838
  self.host.send_command_sync(
1923
- HCI_LE_Connection_Update_Command(
1839
+ hci.HCI_LE_Connection_Update_Command(
1924
1840
  connection_handle=connection.handle,
1925
1841
  connection_interval_min=request.interval_min,
1926
1842
  connection_interval_max=request.interval_max,
@@ -1958,6 +1874,7 @@ class ChannelManager:
1958
1874
  connection,
1959
1875
  L2CAP_LE_SIGNALING_CID,
1960
1876
  L2CAP_Connection_Parameter_Update_Request(
1877
+ identifier=self.next_identifier(connection),
1961
1878
  interval_min=interval_min,
1962
1879
  interval_max=interval_max,
1963
1880
  latency=latency,
@@ -1967,7 +1884,10 @@ class ChannelManager:
1967
1884
  return await self.connection_parameters_update_response
1968
1885
 
1969
1886
  def on_l2cap_connection_parameter_update_response(
1970
- self, connection: Connection, cid: int, response
1887
+ self,
1888
+ connection: Connection,
1889
+ cid: int,
1890
+ response: L2CAP_Connection_Parameter_Update_Response,
1971
1891
  ) -> None:
1972
1892
  if self.connection_parameters_update_response:
1973
1893
  self.connection_parameters_update_response.set_result(response.result)
@@ -1981,7 +1901,10 @@ class ChannelManager:
1981
1901
  )
1982
1902
 
1983
1903
  def on_l2cap_le_credit_based_connection_request(
1984
- self, connection: Connection, cid: int, request
1904
+ self,
1905
+ connection: Connection,
1906
+ cid: int,
1907
+ request: L2CAP_LE_Credit_Based_Connection_Request,
1985
1908
  ) -> None:
1986
1909
  if request.le_psm in self.le_coc_servers:
1987
1910
  server = self.le_coc_servers[request.le_psm]
@@ -2002,7 +1925,7 @@ class ChannelManager:
2002
1925
  mps=server.mps,
2003
1926
  initial_credits=0,
2004
1927
  # pylint: disable=line-too-long
2005
- 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,
2006
1929
  ),
2007
1930
  )
2008
1931
  return
@@ -2021,7 +1944,7 @@ class ChannelManager:
2021
1944
  mps=server.mps,
2022
1945
  initial_credits=0,
2023
1946
  # pylint: disable=line-too-long
2024
- 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,
2025
1948
  ),
2026
1949
  )
2027
1950
  return
@@ -2059,7 +1982,7 @@ class ChannelManager:
2059
1982
  mps=server.mps,
2060
1983
  initial_credits=server.max_credits,
2061
1984
  # pylint: disable=line-too-long
2062
- result=L2CAP_LE_Credit_Based_Connection_Response.CONNECTION_SUCCESSFUL,
1985
+ result=L2CAP_LE_Credit_Based_Connection_Response.Result.CONNECTION_SUCCESSFUL,
2063
1986
  ),
2064
1987
  )
2065
1988
 
@@ -2080,12 +2003,15 @@ class ChannelManager:
2080
2003
  mps=L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_MPS,
2081
2004
  initial_credits=0,
2082
2005
  # pylint: disable=line-too-long
2083
- 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,
2084
2007
  ),
2085
2008
  )
2086
2009
 
2087
2010
  def on_l2cap_le_credit_based_connection_response(
2088
- self, connection: Connection, _cid: int, response
2011
+ self,
2012
+ connection: Connection,
2013
+ _cid: int,
2014
+ response: L2CAP_LE_Credit_Based_Connection_Response,
2089
2015
  ) -> None:
2090
2016
  # Find the pending request by identifier
2091
2017
  request = self.le_coc_requests.get(response.identifier)
@@ -2110,7 +2036,7 @@ class ChannelManager:
2110
2036
  channel.on_connection_response(response)
2111
2037
 
2112
2038
  def on_l2cap_le_flow_control_credit(
2113
- self, connection: Connection, _cid: int, credit
2039
+ self, connection: Connection, _cid: int, credit: L2CAP_LE_Flow_Control_Credit
2114
2040
  ) -> None:
2115
2041
  channel = self.find_le_coc_channel(connection.handle, credit.cid)
2116
2042
  if channel is None: