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.
- bumble/_version.py +2 -2
- bumble/a2dp.py +6 -0
- bumble/apps/README.md +0 -3
- bumble/apps/auracast.py +11 -9
- bumble/apps/bench.py +480 -31
- bumble/apps/console.py +3 -3
- bumble/apps/controller_info.py +47 -10
- bumble/apps/controller_loopback.py +7 -3
- bumble/apps/controllers.py +2 -2
- bumble/apps/device_info.py +2 -2
- bumble/apps/gatt_dump.py +2 -2
- bumble/apps/gg_bridge.py +2 -2
- bumble/apps/hci_bridge.py +2 -2
- bumble/apps/l2cap_bridge.py +2 -2
- bumble/apps/lea_unicast/app.py +6 -1
- bumble/apps/pair.py +19 -11
- bumble/apps/pandora_server.py +2 -2
- bumble/apps/rfcomm_bridge.py +1 -1
- bumble/apps/scan.py +2 -2
- bumble/apps/show.py +4 -2
- bumble/apps/speaker/speaker.html +1 -0
- bumble/apps/speaker/speaker.js +113 -62
- bumble/apps/speaker/speaker.py +126 -18
- bumble/at.py +4 -4
- bumble/att.py +2 -6
- bumble/avc.py +7 -7
- bumble/avctp.py +3 -3
- bumble/avdtp.py +16 -20
- bumble/avrcp.py +41 -53
- bumble/colors.py +2 -2
- bumble/controller.py +84 -23
- bumble/device.py +348 -182
- bumble/drivers/__init__.py +2 -2
- bumble/drivers/common.py +0 -2
- bumble/drivers/intel.py +37 -40
- bumble/drivers/rtk.py +28 -35
- bumble/gatt.py +4 -4
- bumble/gatt_adapters.py +4 -5
- bumble/gatt_client.py +26 -31
- bumble/gatt_server.py +7 -11
- bumble/hci.py +2601 -2909
- bumble/helpers.py +4 -5
- bumble/hfp.py +32 -37
- bumble/host.py +94 -35
- bumble/keys.py +5 -5
- bumble/l2cap.py +310 -394
- bumble/link.py +6 -270
- bumble/pairing.py +23 -20
- bumble/pandora/__init__.py +1 -1
- bumble/pandora/config.py +2 -2
- bumble/pandora/device.py +6 -6
- bumble/pandora/host.py +27 -28
- bumble/pandora/l2cap.py +2 -2
- bumble/pandora/security.py +6 -6
- bumble/pandora/utils.py +3 -3
- bumble/profiles/ascs.py +132 -131
- bumble/profiles/asha.py +2 -2
- bumble/profiles/bap.py +3 -4
- bumble/profiles/csip.py +2 -2
- bumble/profiles/device_information_service.py +2 -2
- bumble/profiles/gap.py +2 -2
- bumble/profiles/hap.py +34 -33
- bumble/profiles/le_audio.py +4 -4
- bumble/profiles/mcp.py +4 -4
- bumble/profiles/vcs.py +3 -5
- bumble/rfcomm.py +10 -10
- bumble/rtp.py +1 -2
- bumble/sdp.py +2 -2
- bumble/smp.py +57 -61
- bumble/tools/rtk_util.py +2 -2
- bumble/transport/__init__.py +2 -16
- bumble/transport/android_netsim.py +5 -5
- bumble/transport/common.py +4 -4
- bumble/transport/pyusb.py +2 -2
- bumble/utils.py +2 -5
- bumble/vendor/android/hci.py +118 -200
- bumble/vendor/zephyr/hci.py +32 -27
- {bumble-0.0.212.dist-info → bumble-0.0.213.dist-info}/METADATA +2 -2
- {bumble-0.0.212.dist-info → bumble-0.0.213.dist-info}/RECORD +83 -86
- {bumble-0.0.212.dist-info → bumble-0.0.213.dist-info}/WHEEL +1 -1
- {bumble-0.0.212.dist-info → bumble-0.0.213.dist-info}/entry_points.txt +0 -1
- bumble/apps/link_relay/__init__.py +0 -0
- bumble/apps/link_relay/link_relay.py +0 -289
- bumble/apps/link_relay/logging.yml +0 -21
- {bumble-0.0.212.dist-info → bumble-0.0.213.dist-info}/licenses/LICENSE +0 -0
- {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
|
-
|
|
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:
|
|
246
|
-
|
|
247
|
-
|
|
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
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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
|
-
|
|
254
|
-
if
|
|
224
|
+
subclass = L2CAP_Control_Frame.classes.get(code)
|
|
225
|
+
if subclass is None:
|
|
255
226
|
instance = L2CAP_Control_Frame(pdu)
|
|
256
|
-
instance.
|
|
257
|
-
instance.
|
|
227
|
+
instance.code = CommandCode(code)
|
|
228
|
+
instance.name = instance.code.name
|
|
258
229
|
return instance
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
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
|
-
|
|
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) ->
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
309
|
-
|
|
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
|
-
|
|
271
|
+
# Register a factory for this class
|
|
272
|
+
L2CAP_Control_Frame.classes[subclass.code] = subclass
|
|
312
273
|
|
|
313
|
-
return
|
|
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
|
|
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.
|
|
288
|
+
self.data = pdu[4:] if pdu else b''
|
|
328
289
|
|
|
329
|
-
|
|
330
|
-
|
|
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
|
|
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.
|
|
341
|
-
result += f': {self.
|
|
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
|
-
|
|
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
|
-
|
|
362
|
-
|
|
363
|
-
|
|
324
|
+
class Reason(hci.SpecableEnum):
|
|
325
|
+
COMMAND_NOT_UNDERSTOOD = 0x0000
|
|
326
|
+
SIGNALING_MTU_EXCEEDED = 0x0001
|
|
327
|
+
INVALID_CID_IN_REQUEST = 0x0002
|
|
364
328
|
|
|
365
|
-
|
|
366
|
-
|
|
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
|
-
|
|
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) ->
|
|
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
|
-
|
|
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
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
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
|
-
|
|
470
|
-
|
|
471
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
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
|
-
|
|
517
|
-
|
|
518
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
572
|
-
|
|
573
|
-
|
|
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
|
-
|
|
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
|
-
|
|
616
|
-
|
|
519
|
+
class Result(hci.SpecableEnum):
|
|
520
|
+
SUCCESS = 0x00
|
|
521
|
+
NOT_SUPPORTED = 0x01
|
|
617
522
|
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
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
|
|
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
|
|
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
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
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
|
|
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.
|
|
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:
|
|
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(
|
|
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.
|
|
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:
|
|
1449
|
-
channels:
|
|
1450
|
-
servers:
|
|
1451
|
-
le_coc_channels:
|
|
1452
|
-
le_coc_servers:
|
|
1453
|
-
le_coc_requests:
|
|
1454
|
-
fixed_channels:
|
|
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,
|
|
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(
|
|
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
|
|
1900
|
-
|
|
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
|
|
1903
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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:
|