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