bumble 0.0.220__py3-none-any.whl → 0.0.222__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 +5 -5
- bumble/apps/auracast.py +746 -473
- bumble/apps/bench.py +4 -5
- bumble/apps/console.py +5 -10
- bumble/apps/controller_info.py +12 -7
- bumble/apps/controller_loopback.py +1 -2
- bumble/apps/device_info.py +2 -3
- bumble/apps/gatt_dump.py +0 -1
- bumble/apps/lea_unicast/app.py +1 -1
- bumble/apps/pair.py +49 -46
- bumble/apps/pandora_server.py +2 -2
- bumble/apps/player/player.py +10 -12
- bumble/apps/rfcomm_bridge.py +10 -11
- bumble/apps/scan.py +1 -3
- bumble/apps/speaker/speaker.py +3 -4
- bumble/at.py +4 -5
- bumble/att.py +91 -25
- bumble/audio/io.py +5 -3
- bumble/avc.py +1 -2
- bumble/avctp.py +2 -3
- bumble/avdtp.py +53 -57
- bumble/avrcp.py +25 -27
- bumble/codecs.py +15 -15
- bumble/colors.py +7 -8
- bumble/controller.py +663 -391
- bumble/core.py +41 -49
- bumble/crypto/__init__.py +2 -1
- bumble/crypto/builtin.py +2 -8
- bumble/data_types.py +2 -1
- bumble/decoder.py +2 -3
- bumble/device.py +171 -142
- bumble/drivers/__init__.py +3 -2
- bumble/drivers/intel.py +6 -8
- bumble/drivers/rtk.py +1 -1
- bumble/gatt.py +9 -9
- bumble/gatt_adapters.py +6 -6
- bumble/gatt_client.py +110 -60
- bumble/gatt_server.py +209 -139
- bumble/hci.py +87 -74
- bumble/helpers.py +5 -5
- bumble/hfp.py +27 -26
- bumble/hid.py +9 -9
- bumble/host.py +44 -50
- bumble/keys.py +17 -17
- bumble/l2cap.py +1070 -218
- bumble/link.py +26 -159
- bumble/ll.py +200 -0
- bumble/pairing.py +14 -15
- bumble/pandora/__init__.py +2 -2
- bumble/pandora/device.py +6 -4
- bumble/pandora/host.py +19 -10
- bumble/pandora/l2cap.py +8 -9
- bumble/pandora/security.py +18 -16
- bumble/pandora/utils.py +4 -4
- bumble/profiles/aics.py +6 -8
- bumble/profiles/ams.py +3 -5
- bumble/profiles/ancs.py +11 -11
- bumble/profiles/ascs.py +5 -5
- bumble/profiles/asha.py +10 -9
- bumble/profiles/bass.py +9 -3
- bumble/profiles/battery_service.py +1 -2
- bumble/profiles/csip.py +9 -10
- bumble/profiles/device_information_service.py +16 -17
- bumble/profiles/gap.py +3 -4
- bumble/profiles/gatt_service.py +0 -1
- bumble/profiles/gmap.py +12 -13
- bumble/profiles/hap.py +3 -3
- bumble/profiles/heart_rate_service.py +7 -8
- bumble/profiles/le_audio.py +1 -1
- bumble/profiles/mcp.py +28 -28
- bumble/profiles/pacs.py +13 -17
- bumble/profiles/pbp.py +16 -0
- bumble/profiles/vcs.py +2 -2
- bumble/profiles/vocs.py +6 -9
- bumble/rfcomm.py +19 -18
- bumble/sdp.py +12 -11
- bumble/smp.py +20 -30
- bumble/snoop.py +2 -1
- bumble/tools/generate_company_id_list.py +1 -1
- bumble/tools/intel_util.py +2 -2
- bumble/tools/rtk_fw_download.py +1 -1
- bumble/tools/rtk_util.py +1 -1
- bumble/transport/__init__.py +1 -2
- bumble/transport/android_emulator.py +2 -3
- bumble/transport/android_netsim.py +49 -40
- bumble/transport/common.py +9 -9
- bumble/transport/file.py +1 -2
- bumble/transport/hci_socket.py +2 -3
- bumble/transport/pty.py +3 -5
- bumble/transport/pyusb.py +8 -5
- bumble/transport/serial.py +1 -2
- bumble/transport/vhci.py +1 -2
- bumble/transport/ws_server.py +2 -3
- bumble/utils.py +22 -9
- bumble/vendor/android/hci.py +4 -2
- {bumble-0.0.220.dist-info → bumble-0.0.222.dist-info}/METADATA +3 -2
- {bumble-0.0.220.dist-info → bumble-0.0.222.dist-info}/RECORD +102 -101
- {bumble-0.0.220.dist-info → bumble-0.0.222.dist-info}/WHEEL +0 -0
- {bumble-0.0.220.dist-info → bumble-0.0.222.dist-info}/entry_points.txt +0 -0
- {bumble-0.0.220.dist-info → bumble-0.0.222.dist-info}/licenses/LICENSE +0 -0
- {bumble-0.0.220.dist-info → bumble-0.0.222.dist-info}/top_level.txt +0 -0
bumble/l2cap.py
CHANGED
|
@@ -20,21 +20,14 @@ from __future__ import annotations
|
|
|
20
20
|
import asyncio
|
|
21
21
|
import dataclasses
|
|
22
22
|
import enum
|
|
23
|
+
import itertools
|
|
23
24
|
import logging
|
|
24
25
|
import struct
|
|
25
26
|
from collections import deque
|
|
26
|
-
from collections.abc import Sequence
|
|
27
|
-
from typing import
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
Callable,
|
|
31
|
-
ClassVar,
|
|
32
|
-
Iterable,
|
|
33
|
-
Optional,
|
|
34
|
-
SupportsBytes,
|
|
35
|
-
TypeVar,
|
|
36
|
-
Union,
|
|
37
|
-
)
|
|
27
|
+
from collections.abc import Callable, Iterable, Sequence
|
|
28
|
+
from typing import TYPE_CHECKING, Any, ClassVar, SupportsBytes, TypeVar
|
|
29
|
+
|
|
30
|
+
from typing_extensions import override
|
|
38
31
|
|
|
39
32
|
from bumble import hci, utils
|
|
40
33
|
from bumble.colors import color
|
|
@@ -69,7 +62,12 @@ L2CAP_MIN_LE_MTU = 23
|
|
|
69
62
|
L2CAP_MIN_BR_EDR_MTU = 48
|
|
70
63
|
L2CAP_MAX_BR_EDR_MTU = 65535
|
|
71
64
|
|
|
72
|
-
L2CAP_DEFAULT_MTU
|
|
65
|
+
L2CAP_DEFAULT_MTU = 2048 # Default value for the MTU we are willing to accept
|
|
66
|
+
L2CAP_DEFAULT_MPS = 1010 # Default value for the MPS we are willing to accept
|
|
67
|
+
DEFAULT_TX_WINDOW_SIZE = 63
|
|
68
|
+
DEFAULT_MAX_RETRANSMISSION = 1
|
|
69
|
+
DEFAULT_RETRANSMISSION_TIMEOUT = 2.0
|
|
70
|
+
DEFAULT_MONITOR_TIMEOUT = 12.0
|
|
73
71
|
|
|
74
72
|
L2CAP_DEFAULT_CONNECTIONLESS_MTU = 1024
|
|
75
73
|
|
|
@@ -133,29 +131,65 @@ L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_MTU = 2048
|
|
|
133
131
|
L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_MPS = 2048
|
|
134
132
|
L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_INITIAL_CREDITS = 256
|
|
135
133
|
|
|
136
|
-
L2CAP_MAXIMUM_TRANSMISSION_UNIT_CONFIGURATION_OPTION_TYPE = 0x01
|
|
137
|
-
|
|
138
|
-
L2CAP_MTU_CONFIGURATION_PARAMETER_TYPE = 0x01
|
|
139
|
-
|
|
140
134
|
# fmt: on
|
|
141
135
|
# pylint: enable=line-too-long
|
|
142
136
|
|
|
143
137
|
|
|
138
|
+
class TransmissionMode(utils.OpenIntEnum):
|
|
139
|
+
'''See Bluetooth spec @ Vol 3, Part A - 5.4. Retransmission and Flow Control option'''
|
|
140
|
+
|
|
141
|
+
BASIC = 0x00
|
|
142
|
+
RETRANSMISSION = 0x01
|
|
143
|
+
FLOW_CONTROL = 0x02
|
|
144
|
+
ENHANCED_RETRANSMISSION = 0x03
|
|
145
|
+
STREAMING = 0x04
|
|
146
|
+
|
|
147
|
+
|
|
144
148
|
# -----------------------------------------------------------------------------
|
|
145
149
|
# Classes
|
|
146
150
|
# -----------------------------------------------------------------------------
|
|
147
151
|
# pylint: disable=invalid-name
|
|
148
152
|
|
|
149
153
|
|
|
154
|
+
class L2capError(ProtocolError):
|
|
155
|
+
def __init__(self, error_code, error_name='', details=''):
|
|
156
|
+
super().__init__(error_code, 'L2CAP', error_name, details)
|
|
157
|
+
|
|
158
|
+
|
|
150
159
|
@dataclasses.dataclass
|
|
151
160
|
class ClassicChannelSpec:
|
|
152
|
-
|
|
161
|
+
'''Spec of L2CAP Channel over Classic Transport.
|
|
162
|
+
|
|
163
|
+
Attributes:
|
|
164
|
+
psm: PSM of channel. This is optional for server, and when it is None, a PSM
|
|
165
|
+
will be allocated.
|
|
166
|
+
mtu: Maximum Transmission Unit.
|
|
167
|
+
mps: Maximum PDU payload Size.
|
|
168
|
+
tx_window_size: The size of the transmission window for Flow Control mode,
|
|
169
|
+
Retransmission mode, and Enhanced Retransmission mode.
|
|
170
|
+
max_retransmission: The number of transmissions of a single I-frame that L2CAP
|
|
171
|
+
is allowed to try in Retransmission mode and Enhanced Retransmission mode.
|
|
172
|
+
retransmission_timeout: The timeout of retransmission in seconds.
|
|
173
|
+
monitor_timeout: The interval at which S-frames should be transmitted on the
|
|
174
|
+
return channel when no frames are received on the forward channel.
|
|
175
|
+
mode: The transmission mode to use.
|
|
176
|
+
fcs_enabled: Whether to enable FCS (Frame Check Sequence).
|
|
177
|
+
'''
|
|
178
|
+
|
|
179
|
+
psm: int | None = None
|
|
153
180
|
mtu: int = L2CAP_DEFAULT_MTU
|
|
181
|
+
mps: int = L2CAP_DEFAULT_MPS
|
|
182
|
+
tx_window_size: int = DEFAULT_TX_WINDOW_SIZE
|
|
183
|
+
max_retransmission: int = DEFAULT_MAX_RETRANSMISSION
|
|
184
|
+
retransmission_timeout: float = DEFAULT_RETRANSMISSION_TIMEOUT
|
|
185
|
+
monitor_timeout: float = DEFAULT_MONITOR_TIMEOUT
|
|
186
|
+
mode: TransmissionMode = TransmissionMode.BASIC
|
|
187
|
+
fcs_enabled: bool = False
|
|
154
188
|
|
|
155
189
|
|
|
156
190
|
@dataclasses.dataclass
|
|
157
191
|
class LeCreditBasedChannelSpec:
|
|
158
|
-
psm:
|
|
192
|
+
psm: int | None = None
|
|
159
193
|
mtu: int = L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_MTU
|
|
160
194
|
mps: int = L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_MPS
|
|
161
195
|
max_credits: int = L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_INITIAL_CREDITS
|
|
@@ -183,20 +217,29 @@ class L2CAP_PDU:
|
|
|
183
217
|
See Bluetooth spec @ Vol 3, Part A - 3 DATA PACKET FORMAT
|
|
184
218
|
'''
|
|
185
219
|
|
|
186
|
-
@
|
|
187
|
-
def from_bytes(data: bytes) -> L2CAP_PDU:
|
|
220
|
+
@classmethod
|
|
221
|
+
def from_bytes(cls, data: bytes) -> L2CAP_PDU:
|
|
188
222
|
# Check parameters
|
|
189
223
|
if len(data) < 4:
|
|
190
224
|
raise InvalidPacketError('not enough data for L2CAP header')
|
|
191
225
|
|
|
192
|
-
|
|
193
|
-
l2cap_pdu_payload = data[4:]
|
|
226
|
+
length, l2cap_pdu_cid = struct.unpack_from('<HH', data, 0)
|
|
227
|
+
l2cap_pdu_payload = data[4 : 4 + length]
|
|
194
228
|
|
|
195
|
-
return
|
|
229
|
+
return cls(l2cap_pdu_cid, l2cap_pdu_payload)
|
|
196
230
|
|
|
197
231
|
def __bytes__(self) -> bytes:
|
|
198
|
-
|
|
199
|
-
|
|
232
|
+
return self.to_bytes(with_fcs=False)
|
|
233
|
+
|
|
234
|
+
def to_bytes(self, with_fcs: bool = False) -> bytes:
|
|
235
|
+
length = len(self.payload)
|
|
236
|
+
if with_fcs:
|
|
237
|
+
length += 2
|
|
238
|
+
header = struct.pack('<HH', length, self.cid)
|
|
239
|
+
body = header + self.payload
|
|
240
|
+
if with_fcs:
|
|
241
|
+
body += struct.pack('<H', utils.crc_16(body))
|
|
242
|
+
return body
|
|
200
243
|
|
|
201
244
|
def __init__(self, cid: int, payload: bytes) -> None:
|
|
202
245
|
self.cid = cid
|
|
@@ -206,6 +249,117 @@ class L2CAP_PDU:
|
|
|
206
249
|
return f'{color("L2CAP", "green")} [CID={self.cid}]: {self.payload.hex()}'
|
|
207
250
|
|
|
208
251
|
|
|
252
|
+
class ControlField:
|
|
253
|
+
'''
|
|
254
|
+
See Bluetooth spec @ Vol 3, Part A - 3.3.2 Control field.
|
|
255
|
+
'''
|
|
256
|
+
|
|
257
|
+
class FieldType(utils.OpenIntEnum):
|
|
258
|
+
I_FRAME = 0x00
|
|
259
|
+
S_FRAME = 0x01
|
|
260
|
+
|
|
261
|
+
class SegmentationAndReassembly(utils.OpenIntEnum):
|
|
262
|
+
UNSEGMENTED = 0x00
|
|
263
|
+
START = 0x01
|
|
264
|
+
END = 0x02
|
|
265
|
+
CONTINUATION = 0x03
|
|
266
|
+
|
|
267
|
+
class SupervisoryFunction(utils.OpenIntEnum):
|
|
268
|
+
# Receiver Ready
|
|
269
|
+
RR = 0
|
|
270
|
+
# Reject
|
|
271
|
+
REJ = 1
|
|
272
|
+
# Receiver Not Ready
|
|
273
|
+
RNR = 2
|
|
274
|
+
# Select Reject
|
|
275
|
+
SREJ = 3
|
|
276
|
+
|
|
277
|
+
class RetransmissionBit(utils.OpenIntEnum):
|
|
278
|
+
NORMAL = 0x00
|
|
279
|
+
RETRANSMISSION = 0x01
|
|
280
|
+
|
|
281
|
+
req_seq: int
|
|
282
|
+
frame_type: ClassVar[FieldType]
|
|
283
|
+
|
|
284
|
+
def __bytes__(self) -> bytes:
|
|
285
|
+
raise NotImplementedError()
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
class EnhancedControlField(ControlField):
|
|
289
|
+
"""Base control field used in Enhanced Retransmission and Streaming Mode."""
|
|
290
|
+
|
|
291
|
+
final: int
|
|
292
|
+
|
|
293
|
+
@classmethod
|
|
294
|
+
def from_bytes(cls, data: bytes) -> EnhancedControlField:
|
|
295
|
+
frame_type = data[0] & 0x01
|
|
296
|
+
if frame_type == cls.FieldType.I_FRAME:
|
|
297
|
+
return InformationEnhancedControlField.from_bytes(data)
|
|
298
|
+
elif frame_type == cls.FieldType.S_FRAME:
|
|
299
|
+
return SupervisoryEnhancedControlField.from_bytes(data)
|
|
300
|
+
else:
|
|
301
|
+
raise InvalidArgumentError(f'Invalid frame type: {frame_type}')
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
@dataclasses.dataclass
|
|
305
|
+
class InformationEnhancedControlField(EnhancedControlField):
|
|
306
|
+
tx_seq: int
|
|
307
|
+
sar: int
|
|
308
|
+
req_seq: int = 0
|
|
309
|
+
final: int = 1
|
|
310
|
+
|
|
311
|
+
frame_type = EnhancedControlField.FieldType.I_FRAME
|
|
312
|
+
|
|
313
|
+
@classmethod
|
|
314
|
+
def from_bytes(cls, data: bytes) -> EnhancedControlField:
|
|
315
|
+
return cls(
|
|
316
|
+
tx_seq=(data[0] >> 1) & 0b0111111,
|
|
317
|
+
final=(data[0] >> 7) & 0b1,
|
|
318
|
+
req_seq=(data[1] & 0b00111111),
|
|
319
|
+
sar=(data[1] >> 6) & 0b11,
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
def __bytes__(self) -> bytes:
|
|
323
|
+
return bytes(
|
|
324
|
+
[
|
|
325
|
+
self.frame_type | (self.tx_seq << 1) | (self.final << 7),
|
|
326
|
+
self.req_seq | (self.sar << 6),
|
|
327
|
+
]
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
@dataclasses.dataclass
|
|
332
|
+
class SupervisoryEnhancedControlField(EnhancedControlField):
|
|
333
|
+
supervision_function: int = ControlField.SupervisoryFunction.RR
|
|
334
|
+
poll: int = 0
|
|
335
|
+
req_seq: int = 0
|
|
336
|
+
final: int = 0
|
|
337
|
+
|
|
338
|
+
frame_type = EnhancedControlField.FieldType.S_FRAME
|
|
339
|
+
|
|
340
|
+
@classmethod
|
|
341
|
+
def from_bytes(cls, data: bytes) -> EnhancedControlField:
|
|
342
|
+
return cls(
|
|
343
|
+
supervision_function=(data[0] >> 2) & 0b11,
|
|
344
|
+
poll=(data[0] >> 4) & 0b1,
|
|
345
|
+
final=(data[0] >> 7) & 0b1,
|
|
346
|
+
req_seq=(data[1] & 0b1111111),
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
def __bytes__(self) -> bytes:
|
|
350
|
+
return bytes(
|
|
351
|
+
[
|
|
352
|
+
(
|
|
353
|
+
self.frame_type
|
|
354
|
+
| (self.supervision_function << 2)
|
|
355
|
+
| self.poll << 7
|
|
356
|
+
| (self.final << 7)
|
|
357
|
+
),
|
|
358
|
+
self.req_seq,
|
|
359
|
+
]
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
|
|
209
363
|
# -----------------------------------------------------------------------------
|
|
210
364
|
@dataclasses.dataclass
|
|
211
365
|
class L2CAP_Control_Frame:
|
|
@@ -217,7 +371,7 @@ class L2CAP_Control_Frame:
|
|
|
217
371
|
fields: ClassVar[hci.Fields] = ()
|
|
218
372
|
code: int = dataclasses.field(default=0, init=False)
|
|
219
373
|
name: str = dataclasses.field(default='', init=False)
|
|
220
|
-
_payload:
|
|
374
|
+
_payload: bytes | None = dataclasses.field(default=None, init=False)
|
|
221
375
|
|
|
222
376
|
identifier: int
|
|
223
377
|
|
|
@@ -248,14 +402,16 @@ class L2CAP_Control_Frame:
|
|
|
248
402
|
return frame
|
|
249
403
|
|
|
250
404
|
@staticmethod
|
|
251
|
-
def decode_configuration_options(
|
|
405
|
+
def decode_configuration_options(
|
|
406
|
+
data: bytes,
|
|
407
|
+
) -> list[tuple[L2CAP_Configure_Request.ParameterType, bytes]]:
|
|
252
408
|
options = []
|
|
253
409
|
while len(data) >= 2:
|
|
254
410
|
value_type = data[0]
|
|
255
411
|
length = data[1]
|
|
256
412
|
value = data[2 : 2 + length]
|
|
257
413
|
data = data[2 + length :]
|
|
258
|
-
options.append((value_type, value))
|
|
414
|
+
options.append((L2CAP_Configure_Request.ParameterType(value_type), value))
|
|
259
415
|
|
|
260
416
|
return options
|
|
261
417
|
|
|
@@ -398,6 +554,15 @@ class L2CAP_Configure_Request(L2CAP_Control_Frame):
|
|
|
398
554
|
See Bluetooth spec @ Vol 3, Part A - 4.4 CONFIGURATION REQUEST
|
|
399
555
|
'''
|
|
400
556
|
|
|
557
|
+
class ParameterType(utils.OpenIntEnum):
|
|
558
|
+
MTU = 0x01
|
|
559
|
+
FLUSH_TIMEOUT = 0x02
|
|
560
|
+
QOS = 0x03
|
|
561
|
+
RETRANSMISSION_AND_FLOW_CONTROL = 0x04
|
|
562
|
+
FCS = 0x05
|
|
563
|
+
EXTENDED_FLOW_SPEC = 0x06
|
|
564
|
+
EXTENDED_WINDOW_SIZE = 0x07
|
|
565
|
+
|
|
401
566
|
destination_cid: int = dataclasses.field(metadata=hci.metadata(2))
|
|
402
567
|
flags: int = dataclasses.field(metadata=hci.metadata(2))
|
|
403
568
|
options: bytes = dataclasses.field(metadata=hci.metadata('*'))
|
|
@@ -484,17 +649,18 @@ class L2CAP_Information_Request(L2CAP_Control_Frame):
|
|
|
484
649
|
EXTENDED_FEATURES_SUPPORTED = 0x0002
|
|
485
650
|
FIXED_CHANNELS_SUPPORTED = 0x0003
|
|
486
651
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
652
|
+
class ExtendedFeatures(hci.SpecableFlag):
|
|
653
|
+
FLOW_MODE_CONTROL = 0x0001
|
|
654
|
+
RETRANSMISSION_MODE = 0x0002
|
|
655
|
+
BIDIRECTIONAL_QOS = 0x0004
|
|
656
|
+
ENHANCED_RETRANSMISSION_MODE = 0x0008
|
|
657
|
+
STREAMING_MODE = 0x0010
|
|
658
|
+
FCS_OPTION = 0x0020
|
|
659
|
+
EXTENDED_FLOW_SPEC = 0x0040
|
|
660
|
+
FIXED_CHANNELS = 0x0080
|
|
661
|
+
EXTENDED_WINDOW_SIZE = 0x0100
|
|
662
|
+
UNICAST_CONNECTIONLESS_DATA = 0x0200
|
|
663
|
+
ENHANCED_CREDIT_BASE_FLOW_CONTROL = 0x0400
|
|
498
664
|
|
|
499
665
|
info_type: int = dataclasses.field(metadata=InfoType.type_metadata(2))
|
|
500
666
|
|
|
@@ -661,7 +827,7 @@ class L2CAP_Credit_Based_Connection_Response(L2CAP_Control_Frame):
|
|
|
661
827
|
mtu: int = dataclasses.field(metadata=hci.metadata(2))
|
|
662
828
|
mps: int = dataclasses.field(metadata=hci.metadata(2))
|
|
663
829
|
initial_credits: int = dataclasses.field(metadata=hci.metadata(2))
|
|
664
|
-
result:
|
|
830
|
+
result: Result = dataclasses.field(metadata=Result.type_metadata(2))
|
|
665
831
|
destination_cid: Sequence[int] = dataclasses.field(
|
|
666
832
|
metadata=L2CAP_Credit_Based_Connection_Request.CID_METADATA
|
|
667
833
|
)
|
|
@@ -702,6 +868,273 @@ class L2CAP_Credit_Based_Reconfigure_Response(L2CAP_Control_Frame):
|
|
|
702
868
|
result: int = dataclasses.field(metadata=Result.type_metadata(2))
|
|
703
869
|
|
|
704
870
|
|
|
871
|
+
# -----------------------------------------------------------------------------
|
|
872
|
+
class Processor:
|
|
873
|
+
def __init__(self, channel: ClassicChannel) -> None:
|
|
874
|
+
self.channel = channel
|
|
875
|
+
|
|
876
|
+
def send_sdu(self, sdu: bytes) -> None:
|
|
877
|
+
self.channel.send_pdu(sdu)
|
|
878
|
+
|
|
879
|
+
def on_pdu(self, pdu: bytes) -> None:
|
|
880
|
+
self.channel.on_sdu(pdu)
|
|
881
|
+
|
|
882
|
+
|
|
883
|
+
# TODO: Handle retransmission
|
|
884
|
+
class EnhancedRetransmissionProcessor(Processor):
|
|
885
|
+
MAX_SEQ_NUM = 64
|
|
886
|
+
|
|
887
|
+
@dataclasses.dataclass
|
|
888
|
+
class _PendingPdu:
|
|
889
|
+
payload: bytes
|
|
890
|
+
tx_seq: int
|
|
891
|
+
sar: InformationEnhancedControlField.SegmentationAndReassembly
|
|
892
|
+
sdu_length: int = 0
|
|
893
|
+
req_seq: int = 0
|
|
894
|
+
|
|
895
|
+
def __bytes__(self) -> bytes:
|
|
896
|
+
return (
|
|
897
|
+
bytes(
|
|
898
|
+
InformationEnhancedControlField(
|
|
899
|
+
tx_seq=self.tx_seq,
|
|
900
|
+
req_seq=self.req_seq,
|
|
901
|
+
sar=self.sar,
|
|
902
|
+
)
|
|
903
|
+
)
|
|
904
|
+
+ (
|
|
905
|
+
struct.pack('<H', self.sdu_length)
|
|
906
|
+
if self.sar
|
|
907
|
+
== InformationEnhancedControlField.SegmentationAndReassembly.START
|
|
908
|
+
else b''
|
|
909
|
+
)
|
|
910
|
+
+ self.payload
|
|
911
|
+
)
|
|
912
|
+
|
|
913
|
+
_last_acked_tx_seq: int = 0
|
|
914
|
+
_last_acked_rx_seq: int = 0
|
|
915
|
+
_next_tx_seq: int = 0
|
|
916
|
+
_req_seq_num: int = 0
|
|
917
|
+
_remote_is_busy: bool = False
|
|
918
|
+
_in_sdu: bytes = b''
|
|
919
|
+
|
|
920
|
+
_num_receiver_ready_polls_sent: int = 0
|
|
921
|
+
_pending_pdus: list[_PendingPdu]
|
|
922
|
+
_tx_window: list[_PendingPdu]
|
|
923
|
+
_monitor_handle: asyncio.TimerHandle | None = None
|
|
924
|
+
_receiver_ready_poll_handle: asyncio.TimerHandle | None = None
|
|
925
|
+
|
|
926
|
+
# Timeout, in seconds.
|
|
927
|
+
monitor_timeout: float
|
|
928
|
+
retransmission_timeout: float
|
|
929
|
+
|
|
930
|
+
def __init__(
|
|
931
|
+
self,
|
|
932
|
+
channel: ClassicChannel,
|
|
933
|
+
peer_tx_window_size: int = DEFAULT_TX_WINDOW_SIZE,
|
|
934
|
+
peer_max_retransmission: int = DEFAULT_MAX_RETRANSMISSION,
|
|
935
|
+
peer_mps: int = L2CAP_DEFAULT_MPS,
|
|
936
|
+
):
|
|
937
|
+
spec = channel.spec
|
|
938
|
+
self.mps = spec.mps
|
|
939
|
+
self.peer_mps = peer_mps
|
|
940
|
+
self.peer_tx_window_size = peer_tx_window_size
|
|
941
|
+
self._pending_pdus = []
|
|
942
|
+
self._tx_window = []
|
|
943
|
+
self.monitor_timeout = spec.monitor_timeout
|
|
944
|
+
self.channel = channel
|
|
945
|
+
self.retransmission_timeout = spec.retransmission_timeout
|
|
946
|
+
self.peer_max_retransmission = peer_max_retransmission
|
|
947
|
+
|
|
948
|
+
def _monitor(self) -> None:
|
|
949
|
+
if (
|
|
950
|
+
self.peer_max_retransmission <= 0
|
|
951
|
+
or self._num_receiver_ready_polls_sent < self.peer_max_retransmission
|
|
952
|
+
):
|
|
953
|
+
self._send_receiver_ready_poll()
|
|
954
|
+
self._start_monitor()
|
|
955
|
+
else:
|
|
956
|
+
logger.error("Max retransmission exceeded")
|
|
957
|
+
|
|
958
|
+
def _receiver_ready_poll(self) -> None:
|
|
959
|
+
self._send_receiver_ready_poll()
|
|
960
|
+
self._start_monitor()
|
|
961
|
+
|
|
962
|
+
def _start_monitor(self) -> None:
|
|
963
|
+
if self._monitor_handle:
|
|
964
|
+
self._monitor_handle.cancel()
|
|
965
|
+
self._monitor_handle = asyncio.get_running_loop().call_later(
|
|
966
|
+
self.monitor_timeout, self._monitor
|
|
967
|
+
)
|
|
968
|
+
|
|
969
|
+
def _start_receiver_ready_poll(self) -> None:
|
|
970
|
+
if self._receiver_ready_poll_handle:
|
|
971
|
+
self._receiver_ready_poll_handle.cancel()
|
|
972
|
+
self._num_receiver_ready_polls_sent = 0
|
|
973
|
+
|
|
974
|
+
self._receiver_ready_poll_handle = asyncio.get_running_loop().call_later(
|
|
975
|
+
self.retransmission_timeout, self._receiver_ready_poll
|
|
976
|
+
)
|
|
977
|
+
|
|
978
|
+
def _send_receiver_ready_poll(self) -> None:
|
|
979
|
+
self._num_receiver_ready_polls_sent += 1
|
|
980
|
+
self._send_s_frame(
|
|
981
|
+
supervision_function=SupervisoryEnhancedControlField.SupervisoryFunction.RR,
|
|
982
|
+
final=1,
|
|
983
|
+
)
|
|
984
|
+
|
|
985
|
+
def _get_next_tx_seq(self) -> int:
|
|
986
|
+
seq_num = self._next_tx_seq
|
|
987
|
+
self._next_tx_seq = (self._next_tx_seq + 1) % self.MAX_SEQ_NUM
|
|
988
|
+
return seq_num
|
|
989
|
+
|
|
990
|
+
@override
|
|
991
|
+
def send_sdu(self, sdu: bytes) -> None:
|
|
992
|
+
if len(sdu) <= self.peer_mps:
|
|
993
|
+
pdu = self._PendingPdu(
|
|
994
|
+
payload=sdu,
|
|
995
|
+
tx_seq=self._get_next_tx_seq(),
|
|
996
|
+
req_seq=self._req_seq_num,
|
|
997
|
+
sar=InformationEnhancedControlField.SegmentationAndReassembly.UNSEGMENTED,
|
|
998
|
+
)
|
|
999
|
+
self._pending_pdus.append(pdu)
|
|
1000
|
+
else:
|
|
1001
|
+
for offset in range(0, len(sdu), self.peer_mps):
|
|
1002
|
+
payload = sdu[offset : offset + self.peer_mps]
|
|
1003
|
+
if offset == 0:
|
|
1004
|
+
sar = (
|
|
1005
|
+
InformationEnhancedControlField.SegmentationAndReassembly.START
|
|
1006
|
+
)
|
|
1007
|
+
elif offset + len(payload) >= len(sdu):
|
|
1008
|
+
sar = InformationEnhancedControlField.SegmentationAndReassembly.END
|
|
1009
|
+
else:
|
|
1010
|
+
sar = (
|
|
1011
|
+
InformationEnhancedControlField.SegmentationAndReassembly.CONTINUATION
|
|
1012
|
+
)
|
|
1013
|
+
pdu = self._PendingPdu(
|
|
1014
|
+
payload=payload,
|
|
1015
|
+
tx_seq=self._get_next_tx_seq(),
|
|
1016
|
+
req_seq=self._req_seq_num,
|
|
1017
|
+
sar=sar,
|
|
1018
|
+
sdu_length=len(sdu),
|
|
1019
|
+
)
|
|
1020
|
+
self._pending_pdus.append(pdu)
|
|
1021
|
+
self._process_output()
|
|
1022
|
+
|
|
1023
|
+
@override
|
|
1024
|
+
def on_pdu(self, pdu: bytes) -> None:
|
|
1025
|
+
control_field = EnhancedControlField.from_bytes(pdu)
|
|
1026
|
+
self._update_ack_seq(control_field.req_seq, control_field.final != 0)
|
|
1027
|
+
if isinstance(control_field, InformationEnhancedControlField):
|
|
1028
|
+
if control_field.tx_seq != self._req_seq_num:
|
|
1029
|
+
logger.error(
|
|
1030
|
+
"tx_seq != self._req_seq_num, tx_seq: %d, self._req_seq_num: %d",
|
|
1031
|
+
control_field.tx_seq,
|
|
1032
|
+
self._req_seq_num,
|
|
1033
|
+
)
|
|
1034
|
+
return
|
|
1035
|
+
self._req_seq_num = (control_field.tx_seq + 1) % self.MAX_SEQ_NUM
|
|
1036
|
+
|
|
1037
|
+
if (
|
|
1038
|
+
control_field.sar
|
|
1039
|
+
== InformationEnhancedControlField.SegmentationAndReassembly.START
|
|
1040
|
+
):
|
|
1041
|
+
# Drop Control Field(2) + SDU Length(2)
|
|
1042
|
+
self._in_sdu += pdu[4:]
|
|
1043
|
+
else:
|
|
1044
|
+
# Drop Control Field(2)
|
|
1045
|
+
self._in_sdu += pdu[2:]
|
|
1046
|
+
if control_field.sar in (
|
|
1047
|
+
InformationEnhancedControlField.SegmentationAndReassembly.END,
|
|
1048
|
+
InformationEnhancedControlField.SegmentationAndReassembly.UNSEGMENTED,
|
|
1049
|
+
):
|
|
1050
|
+
self.channel.on_sdu(self._in_sdu)
|
|
1051
|
+
self._in_sdu = b''
|
|
1052
|
+
|
|
1053
|
+
# If sink doesn't trigger any I-frame, ack this frame.
|
|
1054
|
+
if self._req_seq_num != self._last_acked_rx_seq:
|
|
1055
|
+
self._send_s_frame(
|
|
1056
|
+
supervision_function=SupervisoryEnhancedControlField.SupervisoryFunction.RR,
|
|
1057
|
+
final=0,
|
|
1058
|
+
)
|
|
1059
|
+
elif isinstance(control_field, SupervisoryEnhancedControlField):
|
|
1060
|
+
self._remote_is_busy = (
|
|
1061
|
+
control_field.supervision_function
|
|
1062
|
+
== SupervisoryEnhancedControlField.SupervisoryFunction.RNR
|
|
1063
|
+
)
|
|
1064
|
+
|
|
1065
|
+
if control_field.supervision_function in (
|
|
1066
|
+
SupervisoryEnhancedControlField.SupervisoryFunction.RR,
|
|
1067
|
+
SupervisoryEnhancedControlField.SupervisoryFunction.RNR,
|
|
1068
|
+
):
|
|
1069
|
+
if control_field.poll:
|
|
1070
|
+
self._send_s_frame(
|
|
1071
|
+
supervision_function=SupervisoryEnhancedControlField.SupervisoryFunction.RR,
|
|
1072
|
+
final=1,
|
|
1073
|
+
)
|
|
1074
|
+
else:
|
|
1075
|
+
# TODO: Handle Retransmission.
|
|
1076
|
+
pass
|
|
1077
|
+
|
|
1078
|
+
def _process_output(self) -> None:
|
|
1079
|
+
if self._remote_is_busy:
|
|
1080
|
+
logger.debug("Remote is busy")
|
|
1081
|
+
return
|
|
1082
|
+
if self._monitor_handle:
|
|
1083
|
+
logger.debug("Monitor handle is not None")
|
|
1084
|
+
return
|
|
1085
|
+
|
|
1086
|
+
pdu_to_send = self.peer_tx_window_size - len(self._tx_window)
|
|
1087
|
+
for pdu in itertools.islice(self._pending_pdus, pdu_to_send):
|
|
1088
|
+
self._send_i_frame(pdu)
|
|
1089
|
+
self._pending_pdus = self._pending_pdus[pdu_to_send:]
|
|
1090
|
+
|
|
1091
|
+
def _send_i_frame(self, pdu: _PendingPdu) -> None:
|
|
1092
|
+
pdu.req_seq = self._req_seq_num
|
|
1093
|
+
|
|
1094
|
+
self._start_receiver_ready_poll()
|
|
1095
|
+
self._tx_window.append(pdu)
|
|
1096
|
+
self.channel.send_pdu(bytes(pdu))
|
|
1097
|
+
self._last_acked_rx_seq = self._req_seq_num
|
|
1098
|
+
|
|
1099
|
+
def _send_s_frame(
|
|
1100
|
+
self,
|
|
1101
|
+
supervision_function: SupervisoryEnhancedControlField.SupervisoryFunction,
|
|
1102
|
+
final: int,
|
|
1103
|
+
) -> None:
|
|
1104
|
+
self.channel.send_pdu(
|
|
1105
|
+
SupervisoryEnhancedControlField(
|
|
1106
|
+
supervision_function=supervision_function,
|
|
1107
|
+
final=final,
|
|
1108
|
+
req_seq=self._req_seq_num,
|
|
1109
|
+
)
|
|
1110
|
+
)
|
|
1111
|
+
self._last_acked_rx_seq = self._req_seq_num
|
|
1112
|
+
|
|
1113
|
+
def _update_ack_seq(self, new_seq: int, is_poll_response: bool) -> None:
|
|
1114
|
+
num_frames_acked = (new_seq - self._last_acked_tx_seq) % self.MAX_SEQ_NUM
|
|
1115
|
+
if num_frames_acked > len(self._tx_window):
|
|
1116
|
+
logger.error(
|
|
1117
|
+
"Received acknowledgment for %d frames but only %d frames are pending",
|
|
1118
|
+
num_frames_acked,
|
|
1119
|
+
len(self._tx_window),
|
|
1120
|
+
)
|
|
1121
|
+
return
|
|
1122
|
+
if is_poll_response and self._monitor_handle:
|
|
1123
|
+
self._monitor_handle.cancel()
|
|
1124
|
+
self._monitor_handle = None
|
|
1125
|
+
|
|
1126
|
+
del self._tx_window[:num_frames_acked]
|
|
1127
|
+
self._last_acked_tx_seq = new_seq
|
|
1128
|
+
if (
|
|
1129
|
+
self._last_acked_tx_seq == self._next_tx_seq
|
|
1130
|
+
and self._receiver_ready_poll_handle
|
|
1131
|
+
):
|
|
1132
|
+
self._receiver_ready_poll_handle.cancel()
|
|
1133
|
+
self._receiver_ready_poll_handle = None
|
|
1134
|
+
|
|
1135
|
+
self._process_output()
|
|
1136
|
+
|
|
1137
|
+
|
|
705
1138
|
# -----------------------------------------------------------------------------
|
|
706
1139
|
class ClassicChannel(utils.EventEmitter):
|
|
707
1140
|
class State(enum.IntEnum):
|
|
@@ -731,14 +1164,15 @@ class ClassicChannel(utils.EventEmitter):
|
|
|
731
1164
|
EVENT_OPEN = "open"
|
|
732
1165
|
EVENT_CLOSE = "close"
|
|
733
1166
|
|
|
734
|
-
connection_result:
|
|
735
|
-
disconnection_result:
|
|
736
|
-
response:
|
|
737
|
-
sink:
|
|
1167
|
+
connection_result: asyncio.Future[None] | None
|
|
1168
|
+
disconnection_result: asyncio.Future[None] | None
|
|
1169
|
+
response: asyncio.Future[bytes] | None
|
|
1170
|
+
sink: Callable[[bytes], Any] | None
|
|
738
1171
|
state: State
|
|
739
1172
|
connection: Connection
|
|
740
1173
|
mtu: int
|
|
741
1174
|
peer_mtu: int
|
|
1175
|
+
processor: Processor
|
|
742
1176
|
|
|
743
1177
|
def __init__(
|
|
744
1178
|
self,
|
|
@@ -747,14 +1181,14 @@ class ClassicChannel(utils.EventEmitter):
|
|
|
747
1181
|
signaling_cid: int,
|
|
748
1182
|
psm: int,
|
|
749
1183
|
source_cid: int,
|
|
750
|
-
|
|
1184
|
+
spec: ClassicChannelSpec,
|
|
751
1185
|
) -> None:
|
|
752
1186
|
super().__init__()
|
|
753
1187
|
self.manager = manager
|
|
754
1188
|
self.connection = connection
|
|
755
1189
|
self.signaling_cid = signaling_cid
|
|
756
1190
|
self.state = self.State.CLOSED
|
|
757
|
-
self.mtu = mtu
|
|
1191
|
+
self.mtu = spec.mtu
|
|
758
1192
|
self.peer_mtu = L2CAP_MIN_BR_EDR_MTU
|
|
759
1193
|
self.psm = psm
|
|
760
1194
|
self.source_cid = source_cid
|
|
@@ -762,26 +1196,47 @@ class ClassicChannel(utils.EventEmitter):
|
|
|
762
1196
|
self.connection_result = None
|
|
763
1197
|
self.disconnection_result = None
|
|
764
1198
|
self.sink = None
|
|
1199
|
+
self.fcs_enabled = spec.fcs_enabled
|
|
1200
|
+
self.spec = spec
|
|
1201
|
+
self.mode = spec.mode
|
|
1202
|
+
# Configure mode-specific processor later on configure request.
|
|
1203
|
+
self.processor = Processor(self)
|
|
1204
|
+
if self.mode not in (
|
|
1205
|
+
TransmissionMode.BASIC,
|
|
1206
|
+
TransmissionMode.ENHANCED_RETRANSMISSION,
|
|
1207
|
+
):
|
|
1208
|
+
raise InvalidArgumentError(f"Mode {spec.mode} is not supported")
|
|
765
1209
|
|
|
766
1210
|
def _change_state(self, new_state: State) -> None:
|
|
767
1211
|
logger.debug(f'{self} state change -> {color(new_state.name, "cyan")}')
|
|
768
1212
|
self.state = new_state
|
|
769
1213
|
|
|
770
|
-
def
|
|
1214
|
+
def write(self, sdu: bytes) -> None:
|
|
1215
|
+
self.processor.send_sdu(sdu)
|
|
1216
|
+
|
|
1217
|
+
def send_pdu(self, pdu: SupportsBytes | bytes) -> None:
|
|
771
1218
|
if self.state != self.State.OPEN:
|
|
772
1219
|
raise InvalidStateError('channel not open')
|
|
773
|
-
self.manager.send_pdu(
|
|
1220
|
+
self.manager.send_pdu(
|
|
1221
|
+
self.connection, self.destination_cid, pdu, self.fcs_enabled
|
|
1222
|
+
)
|
|
774
1223
|
|
|
775
1224
|
def send_control_frame(self, frame: L2CAP_Control_Frame) -> None:
|
|
776
1225
|
self.manager.send_control_frame(self.connection, self.signaling_cid, frame)
|
|
777
1226
|
|
|
778
1227
|
def on_pdu(self, pdu: bytes) -> None:
|
|
1228
|
+
if self.fcs_enabled:
|
|
1229
|
+
# Drop FCS.
|
|
1230
|
+
pdu = pdu[:-2]
|
|
1231
|
+
self.processor.on_pdu(pdu)
|
|
1232
|
+
|
|
1233
|
+
def on_sdu(self, sdu: bytes) -> None:
|
|
779
1234
|
if self.sink:
|
|
780
1235
|
# pylint: disable=not-callable
|
|
781
|
-
self.sink(
|
|
1236
|
+
self.sink(sdu)
|
|
782
1237
|
else:
|
|
783
1238
|
logger.warning(
|
|
784
|
-
color('received
|
|
1239
|
+
color('received sdu without a pending request or sink', 'red')
|
|
785
1240
|
)
|
|
786
1241
|
|
|
787
1242
|
async def connect(self) -> None:
|
|
@@ -811,10 +1266,8 @@ class ClassicChannel(utils.EventEmitter):
|
|
|
811
1266
|
finally:
|
|
812
1267
|
self.connection_result = None
|
|
813
1268
|
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
raise InvalidStateError('invalid state')
|
|
817
|
-
|
|
1269
|
+
def _disconnect_sync(self) -> None:
|
|
1270
|
+
"""For internal sync disconnection."""
|
|
818
1271
|
self._change_state(self.State.WAIT_DISCONNECT)
|
|
819
1272
|
self.send_control_frame(
|
|
820
1273
|
L2CAP_Disconnection_Request(
|
|
@@ -827,7 +1280,21 @@ class ClassicChannel(utils.EventEmitter):
|
|
|
827
1280
|
# Create a future to wait for the state machine to get to a success or error
|
|
828
1281
|
# state
|
|
829
1282
|
self.disconnection_result = asyncio.get_running_loop().create_future()
|
|
830
|
-
|
|
1283
|
+
|
|
1284
|
+
def _abort_connection_result(self, message: str = 'Connection failure') -> None:
|
|
1285
|
+
# Cancel pending connection result.
|
|
1286
|
+
if self.connection_result and not self.connection_result.done():
|
|
1287
|
+
self.connection_result.set_exception(
|
|
1288
|
+
L2capError(error_code=0, error_name=message)
|
|
1289
|
+
)
|
|
1290
|
+
|
|
1291
|
+
async def disconnect(self) -> None:
|
|
1292
|
+
if self.state != self.State.OPEN:
|
|
1293
|
+
raise InvalidStateError('invalid state')
|
|
1294
|
+
|
|
1295
|
+
self._disconnect_sync()
|
|
1296
|
+
if self.disconnection_result:
|
|
1297
|
+
return await self.disconnection_result
|
|
831
1298
|
|
|
832
1299
|
def abort(self) -> None:
|
|
833
1300
|
if self.state == self.State.OPEN:
|
|
@@ -835,20 +1302,40 @@ class ClassicChannel(utils.EventEmitter):
|
|
|
835
1302
|
self.emit(self.EVENT_CLOSE)
|
|
836
1303
|
|
|
837
1304
|
def send_configure_request(self) -> None:
|
|
838
|
-
options =
|
|
839
|
-
|
|
1305
|
+
options: list[tuple[int, bytes]] = [
|
|
1306
|
+
(
|
|
1307
|
+
L2CAP_Configure_Request.ParameterType.MTU,
|
|
1308
|
+
struct.pack('<H', self.mtu),
|
|
1309
|
+
)
|
|
1310
|
+
]
|
|
1311
|
+
if self.mode == TransmissionMode.ENHANCED_RETRANSMISSION:
|
|
1312
|
+
options.append(
|
|
840
1313
|
(
|
|
841
|
-
|
|
842
|
-
struct.pack(
|
|
1314
|
+
L2CAP_Configure_Request.ParameterType.RETRANSMISSION_AND_FLOW_CONTROL,
|
|
1315
|
+
struct.pack(
|
|
1316
|
+
'<BBBHHH',
|
|
1317
|
+
TransmissionMode.ENHANCED_RETRANSMISSION,
|
|
1318
|
+
self.spec.tx_window_size,
|
|
1319
|
+
self.spec.max_retransmission,
|
|
1320
|
+
int(self.spec.retransmission_timeout * 1000),
|
|
1321
|
+
int(self.spec.monitor_timeout * 1000),
|
|
1322
|
+
self.spec.mps,
|
|
1323
|
+
),
|
|
843
1324
|
)
|
|
844
|
-
|
|
845
|
-
|
|
1325
|
+
)
|
|
1326
|
+
if self.fcs_enabled:
|
|
1327
|
+
options.append(
|
|
1328
|
+
(
|
|
1329
|
+
L2CAP_Configure_Request.ParameterType.FCS,
|
|
1330
|
+
bytes([1 if self.fcs_enabled else 0]),
|
|
1331
|
+
)
|
|
1332
|
+
)
|
|
846
1333
|
self.send_control_frame(
|
|
847
1334
|
L2CAP_Configure_Request(
|
|
848
1335
|
identifier=self.manager.next_identifier(self.connection),
|
|
849
1336
|
destination_cid=self.destination_cid,
|
|
850
1337
|
flags=0x0000,
|
|
851
|
-
options=options,
|
|
1338
|
+
options=L2CAP_Control_Frame.encode_configuration_options(options),
|
|
852
1339
|
)
|
|
853
1340
|
)
|
|
854
1341
|
|
|
@@ -884,9 +1371,8 @@ class ClassicChannel(utils.EventEmitter):
|
|
|
884
1371
|
self._change_state(self.State.CLOSED)
|
|
885
1372
|
if self.connection_result:
|
|
886
1373
|
self.connection_result.set_exception(
|
|
887
|
-
|
|
1374
|
+
L2capError(
|
|
888
1375
|
response.result,
|
|
889
|
-
'l2cap',
|
|
890
1376
|
L2CAP_Connection_Response.Result(response.result).name,
|
|
891
1377
|
)
|
|
892
1378
|
)
|
|
@@ -903,20 +1389,111 @@ class ClassicChannel(utils.EventEmitter):
|
|
|
903
1389
|
|
|
904
1390
|
# Decode the options
|
|
905
1391
|
options = L2CAP_Control_Frame.decode_configuration_options(request.options)
|
|
1392
|
+
# Result to options
|
|
1393
|
+
replied_options = list[tuple[int, bytes]]()
|
|
1394
|
+
result = L2CAP_Configure_Response.Result.SUCCESS
|
|
1395
|
+
new_mode = TransmissionMode.BASIC
|
|
906
1396
|
for option in options:
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
1397
|
+
match option[0]:
|
|
1398
|
+
case L2CAP_Configure_Request.ParameterType.MTU:
|
|
1399
|
+
self.peer_mtu = struct.unpack('<H', option[1])[0]
|
|
1400
|
+
logger.debug('Peer MTU = %d', self.peer_mtu)
|
|
1401
|
+
replied_options.append(option)
|
|
1402
|
+
case (
|
|
1403
|
+
L2CAP_Configure_Request.ParameterType.RETRANSMISSION_AND_FLOW_CONTROL
|
|
1404
|
+
):
|
|
1405
|
+
(
|
|
1406
|
+
mode,
|
|
1407
|
+
peer_tx_window_size,
|
|
1408
|
+
peer_max_retransmission,
|
|
1409
|
+
peer_retransmission_timeout,
|
|
1410
|
+
peer_monitor_timeout,
|
|
1411
|
+
peer_mps,
|
|
1412
|
+
) = struct.unpack_from('<BBBHHH', option[1])
|
|
1413
|
+
new_mode = TransmissionMode(mode)
|
|
1414
|
+
logger.debug(
|
|
1415
|
+
'Peer requests Retransmission or Flow Control: mode=%s,'
|
|
1416
|
+
' tx_window_size=%s,'
|
|
1417
|
+
' max_retransmission=%s,'
|
|
1418
|
+
' retransmission_timeout=%s,'
|
|
1419
|
+
' monitor_timeout=%s,'
|
|
1420
|
+
' mps=%s',
|
|
1421
|
+
new_mode.name,
|
|
1422
|
+
peer_tx_window_size,
|
|
1423
|
+
peer_max_retransmission,
|
|
1424
|
+
peer_retransmission_timeout,
|
|
1425
|
+
peer_monitor_timeout,
|
|
1426
|
+
peer_mps,
|
|
1427
|
+
)
|
|
1428
|
+
if new_mode != self.mode:
|
|
1429
|
+
logger.error('Mode mismatch, abort connection')
|
|
1430
|
+
self._abort_connection_result(
|
|
1431
|
+
'Abort on configuration - mode mismatch'
|
|
1432
|
+
)
|
|
1433
|
+
self._disconnect_sync()
|
|
1434
|
+
return
|
|
1435
|
+
|
|
1436
|
+
if new_mode == TransmissionMode.BASIC:
|
|
1437
|
+
replied_options.append(option)
|
|
1438
|
+
elif new_mode == TransmissionMode.ENHANCED_RETRANSMISSION:
|
|
1439
|
+
self.processor = self.manager.make_mode_processor(
|
|
1440
|
+
self,
|
|
1441
|
+
mode=new_mode,
|
|
1442
|
+
peer_tx_window_size=peer_tx_window_size,
|
|
1443
|
+
peer_max_retransmission=peer_max_retransmission,
|
|
1444
|
+
peer_monitor_timeout=peer_monitor_timeout,
|
|
1445
|
+
peer_retransmission_timeout=peer_retransmission_timeout,
|
|
1446
|
+
peer_mps=peer_mps,
|
|
1447
|
+
)
|
|
1448
|
+
replied_options.append(option)
|
|
1449
|
+
else:
|
|
1450
|
+
logger.error("Mode %s is not supported", new_mode.name)
|
|
1451
|
+
self._abort_connection_result(
|
|
1452
|
+
'Abort on configuration - unsupported mode'
|
|
1453
|
+
)
|
|
1454
|
+
self._disconnect_sync()
|
|
1455
|
+
return
|
|
1456
|
+
|
|
1457
|
+
case L2CAP_Configure_Request.ParameterType.FCS:
|
|
1458
|
+
enabled = option[1][0] != 0
|
|
1459
|
+
logger.debug("Peer requests FCS: %s", enabled)
|
|
1460
|
+
if (
|
|
1461
|
+
L2CAP_Information_Request.ExtendedFeatures.FCS_OPTION
|
|
1462
|
+
in self.manager.extended_features
|
|
1463
|
+
):
|
|
1464
|
+
self.fcs_enabled = enabled
|
|
1465
|
+
replied_options.append(option)
|
|
1466
|
+
else:
|
|
1467
|
+
logger.error("Frame Check Sequence is not supported")
|
|
1468
|
+
result = (
|
|
1469
|
+
L2CAP_Configure_Response.Result.FAILURE_UNACCEPTABLE_PARAMETERS
|
|
1470
|
+
)
|
|
1471
|
+
replied_options = [option]
|
|
1472
|
+
break
|
|
1473
|
+
case _:
|
|
1474
|
+
logger.debug(
|
|
1475
|
+
"Reject unimplemented option %s[%s]",
|
|
1476
|
+
option[0].name,
|
|
1477
|
+
option[1].hex(),
|
|
1478
|
+
)
|
|
1479
|
+
result = L2CAP_Configure_Response.Result.FAILURE_UNKNOWN_OPTIONS
|
|
1480
|
+
replied_options = [option]
|
|
1481
|
+
break
|
|
910
1482
|
|
|
911
1483
|
self.send_control_frame(
|
|
912
1484
|
L2CAP_Configure_Response(
|
|
913
1485
|
identifier=request.identifier,
|
|
914
1486
|
source_cid=self.destination_cid,
|
|
915
1487
|
flags=0x0000,
|
|
916
|
-
result=
|
|
917
|
-
options=
|
|
1488
|
+
result=result,
|
|
1489
|
+
options=L2CAP_Control_Frame.encode_configuration_options(
|
|
1490
|
+
replied_options
|
|
1491
|
+
),
|
|
918
1492
|
)
|
|
919
1493
|
)
|
|
1494
|
+
if result != L2CAP_Configure_Response.Result.SUCCESS:
|
|
1495
|
+
return
|
|
1496
|
+
|
|
920
1497
|
if self.state == self.State.WAIT_CONFIG:
|
|
921
1498
|
self._change_state(self.State.WAIT_SEND_CONFIG)
|
|
922
1499
|
self.send_configure_request()
|
|
@@ -969,25 +1546,19 @@ class ClassicChannel(utils.EventEmitter):
|
|
|
969
1546
|
# TODO: decide how to fail gracefully
|
|
970
1547
|
|
|
971
1548
|
def on_disconnection_request(self, request: L2CAP_Disconnection_Request) -> None:
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
source_cid=request.source_cid,
|
|
978
|
-
)
|
|
1549
|
+
self.send_control_frame(
|
|
1550
|
+
L2CAP_Disconnection_Response(
|
|
1551
|
+
identifier=request.identifier,
|
|
1552
|
+
destination_cid=request.destination_cid,
|
|
1553
|
+
source_cid=request.source_cid,
|
|
979
1554
|
)
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
1555
|
+
)
|
|
1556
|
+
self._abort_connection_result()
|
|
1557
|
+
self._change_state(self.State.CLOSED)
|
|
1558
|
+
self.emit(self.EVENT_CLOSE)
|
|
1559
|
+
self.manager.on_channel_closed(self)
|
|
985
1560
|
|
|
986
1561
|
def on_disconnection_response(self, response: L2CAP_Disconnection_Response) -> None:
|
|
987
|
-
if self.state != self.State.WAIT_DISCONNECT:
|
|
988
|
-
logger.warning(color('invalid state', 'red'))
|
|
989
|
-
return
|
|
990
|
-
|
|
991
1562
|
if (
|
|
992
1563
|
response.destination_cid != self.destination_cid
|
|
993
1564
|
or response.source_cid != self.source_cid
|
|
@@ -1026,22 +1597,23 @@ class LeCreditBasedChannel(utils.EventEmitter):
|
|
|
1026
1597
|
CONNECTION_ERROR = 5
|
|
1027
1598
|
|
|
1028
1599
|
out_queue: deque[bytes]
|
|
1029
|
-
connection_result:
|
|
1030
|
-
disconnection_result:
|
|
1031
|
-
in_sdu:
|
|
1032
|
-
out_sdu:
|
|
1600
|
+
connection_result: asyncio.Future[LeCreditBasedChannel] | None
|
|
1601
|
+
disconnection_result: asyncio.Future[None] | None
|
|
1602
|
+
in_sdu: bytes | None
|
|
1603
|
+
out_sdu: bytes | None
|
|
1033
1604
|
state: State
|
|
1034
1605
|
connection: Connection
|
|
1035
|
-
sink:
|
|
1606
|
+
sink: Callable[[bytes], Any] | None
|
|
1036
1607
|
|
|
1037
1608
|
EVENT_OPEN = "open"
|
|
1038
1609
|
EVENT_CLOSE = "close"
|
|
1610
|
+
EVENT_ATT_MTU_UPDATE = "att_mtu_update"
|
|
1039
1611
|
|
|
1040
1612
|
def __init__(
|
|
1041
1613
|
self,
|
|
1042
1614
|
manager: ChannelManager,
|
|
1043
1615
|
connection: Connection,
|
|
1044
|
-
|
|
1616
|
+
psm: int,
|
|
1045
1617
|
source_cid: int,
|
|
1046
1618
|
destination_cid: int,
|
|
1047
1619
|
mtu: int,
|
|
@@ -1055,7 +1627,7 @@ class LeCreditBasedChannel(utils.EventEmitter):
|
|
|
1055
1627
|
super().__init__()
|
|
1056
1628
|
self.manager = manager
|
|
1057
1629
|
self.connection = connection
|
|
1058
|
-
self.
|
|
1630
|
+
self.psm = psm
|
|
1059
1631
|
self.source_cid = source_cid
|
|
1060
1632
|
self.destination_cid = destination_cid
|
|
1061
1633
|
self.mtu = mtu
|
|
@@ -1075,6 +1647,7 @@ class LeCreditBasedChannel(utils.EventEmitter):
|
|
|
1075
1647
|
self.connection_result = None
|
|
1076
1648
|
self.disconnection_result = None
|
|
1077
1649
|
self.drained = asyncio.Event()
|
|
1650
|
+
self.att_mtu = 0 # Filled by GATT client or server later.
|
|
1078
1651
|
|
|
1079
1652
|
self.drained.set()
|
|
1080
1653
|
|
|
@@ -1092,7 +1665,7 @@ class LeCreditBasedChannel(utils.EventEmitter):
|
|
|
1092
1665
|
elif new_state == self.State.DISCONNECTED:
|
|
1093
1666
|
self.emit(self.EVENT_CLOSE)
|
|
1094
1667
|
|
|
1095
|
-
def send_pdu(self, pdu:
|
|
1668
|
+
def send_pdu(self, pdu: SupportsBytes | bytes) -> None:
|
|
1096
1669
|
self.manager.send_pdu(self.connection, self.destination_cid, pdu)
|
|
1097
1670
|
|
|
1098
1671
|
def send_control_frame(self, frame: L2CAP_Control_Frame) -> None:
|
|
@@ -1111,7 +1684,7 @@ class LeCreditBasedChannel(utils.EventEmitter):
|
|
|
1111
1684
|
self._change_state(self.State.CONNECTING)
|
|
1112
1685
|
request = L2CAP_LE_Credit_Based_Connection_Request(
|
|
1113
1686
|
identifier=identifier,
|
|
1114
|
-
le_psm=self.
|
|
1687
|
+
le_psm=self.psm,
|
|
1115
1688
|
source_cid=self.source_cid,
|
|
1116
1689
|
mtu=self.mtu,
|
|
1117
1690
|
mps=self.mps,
|
|
@@ -1149,6 +1722,9 @@ class LeCreditBasedChannel(utils.EventEmitter):
|
|
|
1149
1722
|
def abort(self) -> None:
|
|
1150
1723
|
if self.state == self.State.CONNECTED:
|
|
1151
1724
|
self._change_state(self.State.DISCONNECTED)
|
|
1725
|
+
if self.state == self.State.CONNECTING:
|
|
1726
|
+
if self.connection_result is not None:
|
|
1727
|
+
self.connection_result.cancel()
|
|
1152
1728
|
|
|
1153
1729
|
def on_pdu(self, pdu: bytes) -> None:
|
|
1154
1730
|
if self.sink is None:
|
|
@@ -1239,9 +1815,8 @@ class LeCreditBasedChannel(utils.EventEmitter):
|
|
|
1239
1815
|
self._change_state(self.State.CONNECTED)
|
|
1240
1816
|
else:
|
|
1241
1817
|
self.connection_result.set_exception(
|
|
1242
|
-
|
|
1818
|
+
L2capError(
|
|
1243
1819
|
response.result,
|
|
1244
|
-
'l2cap',
|
|
1245
1820
|
L2CAP_LE_Credit_Based_Connection_Response.Result(
|
|
1246
1821
|
response.result
|
|
1247
1822
|
).name,
|
|
@@ -1252,6 +1827,22 @@ class LeCreditBasedChannel(utils.EventEmitter):
|
|
|
1252
1827
|
# Cleanup
|
|
1253
1828
|
self.connection_result = None
|
|
1254
1829
|
|
|
1830
|
+
def on_enhanced_connection_response(
|
|
1831
|
+
self, destination_cid: int, response: L2CAP_Credit_Based_Connection_Response
|
|
1832
|
+
) -> None:
|
|
1833
|
+
if (
|
|
1834
|
+
response.result
|
|
1835
|
+
== L2CAP_Credit_Based_Connection_Response.Result.ALL_CONNECTIONS_SUCCESSFUL
|
|
1836
|
+
):
|
|
1837
|
+
self.destination_cid = destination_cid
|
|
1838
|
+
self.peer_mtu = response.mtu
|
|
1839
|
+
self.peer_mps = response.mps
|
|
1840
|
+
self.credits = response.initial_credits
|
|
1841
|
+
self.connected = True
|
|
1842
|
+
self._change_state(self.State.CONNECTED)
|
|
1843
|
+
else:
|
|
1844
|
+
self._change_state(self.State.CONNECTION_ERROR)
|
|
1845
|
+
|
|
1255
1846
|
def on_credits(self, credits: int) -> None: # pylint: disable=redefined-builtin
|
|
1256
1847
|
self.credits += credits
|
|
1257
1848
|
logger.debug(f'received {credits} credits, total = {self.credits}')
|
|
@@ -1287,6 +1878,10 @@ class LeCreditBasedChannel(utils.EventEmitter):
|
|
|
1287
1878
|
self.disconnection_result.set_result(None)
|
|
1288
1879
|
self.disconnection_result = None
|
|
1289
1880
|
|
|
1881
|
+
def on_att_mtu_update(self, mtu: int) -> None:
|
|
1882
|
+
self.att_mtu = mtu
|
|
1883
|
+
self.emit(self.EVENT_ATT_MTU_UPDATE, mtu)
|
|
1884
|
+
|
|
1290
1885
|
def flush_output(self) -> None:
|
|
1291
1886
|
self.out_queue.clear()
|
|
1292
1887
|
self.out_sdu = None
|
|
@@ -1364,7 +1959,7 @@ class LeCreditBasedChannel(utils.EventEmitter):
|
|
|
1364
1959
|
return (
|
|
1365
1960
|
f'CoC({self.source_cid}->{self.destination_cid}, '
|
|
1366
1961
|
f'State={self.state.name}, '
|
|
1367
|
-
f'PSM={self.
|
|
1962
|
+
f'PSM={self.psm}, '
|
|
1368
1963
|
f'MTU={self.mtu}/{self.peer_mtu}, '
|
|
1369
1964
|
f'MPS={self.mps}/{self.peer_mps}, '
|
|
1370
1965
|
f'credits={self.credits}/{self.peer_credits})'
|
|
@@ -1379,14 +1974,14 @@ class ClassicChannelServer(utils.EventEmitter):
|
|
|
1379
1974
|
self,
|
|
1380
1975
|
manager: ChannelManager,
|
|
1381
1976
|
psm: int,
|
|
1382
|
-
handler:
|
|
1383
|
-
|
|
1977
|
+
handler: Callable[[ClassicChannel], Any] | None,
|
|
1978
|
+
spec: ClassicChannelSpec,
|
|
1384
1979
|
) -> None:
|
|
1385
1980
|
super().__init__()
|
|
1386
1981
|
self.manager = manager
|
|
1387
1982
|
self.handler = handler
|
|
1388
1983
|
self.psm = psm
|
|
1389
|
-
self.
|
|
1984
|
+
self.spec = spec
|
|
1390
1985
|
|
|
1391
1986
|
def on_connection(self, channel: ClassicChannel) -> None:
|
|
1392
1987
|
self.emit(self.EVENT_CONNECTION, channel)
|
|
@@ -1406,7 +2001,7 @@ class LeCreditBasedChannelServer(utils.EventEmitter):
|
|
|
1406
2001
|
self,
|
|
1407
2002
|
manager: ChannelManager,
|
|
1408
2003
|
psm: int,
|
|
1409
|
-
handler:
|
|
2004
|
+
handler: Callable[[LeCreditBasedChannel], Any] | None,
|
|
1410
2005
|
max_credits: int,
|
|
1411
2006
|
mtu: int,
|
|
1412
2007
|
mps: int,
|
|
@@ -1432,14 +2027,24 @@ class LeCreditBasedChannelServer(utils.EventEmitter):
|
|
|
1432
2027
|
# -----------------------------------------------------------------------------
|
|
1433
2028
|
class ChannelManager:
|
|
1434
2029
|
identifiers: dict[int, int]
|
|
1435
|
-
channels: dict[int, dict[int,
|
|
2030
|
+
channels: dict[int, dict[int, ClassicChannel | LeCreditBasedChannel]]
|
|
1436
2031
|
servers: dict[int, ClassicChannelServer]
|
|
1437
2032
|
le_coc_channels: dict[int, dict[int, LeCreditBasedChannel]]
|
|
1438
2033
|
le_coc_servers: dict[int, LeCreditBasedChannelServer]
|
|
1439
2034
|
le_coc_requests: dict[int, L2CAP_LE_Credit_Based_Connection_Request]
|
|
1440
|
-
fixed_channels: dict[int,
|
|
1441
|
-
|
|
1442
|
-
|
|
2035
|
+
fixed_channels: dict[int, Callable[[int, bytes], Any] | None]
|
|
2036
|
+
pending_credit_based_connections: dict[
|
|
2037
|
+
int,
|
|
2038
|
+
dict[
|
|
2039
|
+
int,
|
|
2040
|
+
tuple[
|
|
2041
|
+
asyncio.Future[None],
|
|
2042
|
+
list[LeCreditBasedChannel],
|
|
2043
|
+
],
|
|
2044
|
+
],
|
|
2045
|
+
]
|
|
2046
|
+
_host: Host | None
|
|
2047
|
+
connection_parameters_update_response: asyncio.Future[int] | None
|
|
1443
2048
|
|
|
1444
2049
|
def __init__(
|
|
1445
2050
|
self,
|
|
@@ -1459,7 +2064,10 @@ class ChannelManager:
|
|
|
1459
2064
|
) # LE CoC channels, mapped by connection and destination cid
|
|
1460
2065
|
self.le_coc_servers = {} # LE CoC - Servers accepting connections, by PSM
|
|
1461
2066
|
self.le_coc_requests = {} # LE CoC connection requests, by identifier
|
|
1462
|
-
self.
|
|
2067
|
+
self.pending_credit_based_connections = (
|
|
2068
|
+
{}
|
|
2069
|
+
) # Credit-based connection request contexts, by connection handle and identifier
|
|
2070
|
+
self.extended_features = set(extended_features)
|
|
1463
2071
|
self.connectionless_mtu = connectionless_mtu
|
|
1464
2072
|
self.connection_parameters_update_response = None
|
|
1465
2073
|
|
|
@@ -1501,18 +2109,26 @@ class ChannelManager:
|
|
|
1501
2109
|
|
|
1502
2110
|
raise OutOfResourcesError('no free CID available')
|
|
1503
2111
|
|
|
1504
|
-
@
|
|
1505
|
-
def find_free_le_cid(channels: Iterable[int]) -> int:
|
|
2112
|
+
@classmethod
|
|
2113
|
+
def find_free_le_cid(cls, channels: Iterable[int]) -> int | None:
|
|
2114
|
+
cids = cls.find_free_le_cids(channels, 1)
|
|
2115
|
+
return cids[0] if cids else None
|
|
2116
|
+
|
|
2117
|
+
@classmethod
|
|
2118
|
+
def find_free_le_cids(cls, channels: Iterable[int], count: int) -> list[int]:
|
|
1506
2119
|
# Pick the smallest valid CID that's not already in the list
|
|
1507
2120
|
# (not necessarily the most efficient algorithm, but the list of CID is
|
|
1508
2121
|
# very small in practice)
|
|
2122
|
+
cids: list[int] = []
|
|
1509
2123
|
for cid in range(
|
|
1510
2124
|
L2CAP_LE_U_DYNAMIC_CID_RANGE_START, L2CAP_LE_U_DYNAMIC_CID_RANGE_END + 1
|
|
1511
2125
|
):
|
|
1512
2126
|
if cid not in channels:
|
|
1513
|
-
|
|
2127
|
+
cids.append(cid)
|
|
2128
|
+
if len(cids) == count:
|
|
2129
|
+
return cids
|
|
1514
2130
|
|
|
1515
|
-
|
|
2131
|
+
return []
|
|
1516
2132
|
|
|
1517
2133
|
def next_identifier(self, connection: Connection) -> int:
|
|
1518
2134
|
identifier = (self.identifiers.setdefault(connection.handle, 0) + 1) % 256
|
|
@@ -1534,7 +2150,7 @@ class ChannelManager:
|
|
|
1534
2150
|
def create_classic_server(
|
|
1535
2151
|
self,
|
|
1536
2152
|
spec: ClassicChannelSpec,
|
|
1537
|
-
handler:
|
|
2153
|
+
handler: Callable[[ClassicChannel], Any] | None = None,
|
|
1538
2154
|
) -> ClassicChannelServer:
|
|
1539
2155
|
if not spec.psm:
|
|
1540
2156
|
# Find a free PSM
|
|
@@ -1563,14 +2179,14 @@ class ChannelManager:
|
|
|
1563
2179
|
raise InvalidArgumentError('invalid PSM')
|
|
1564
2180
|
check >>= 8
|
|
1565
2181
|
|
|
1566
|
-
self.servers[spec.psm] = ClassicChannelServer(self, spec.psm, handler, spec
|
|
2182
|
+
self.servers[spec.psm] = ClassicChannelServer(self, spec.psm, handler, spec)
|
|
1567
2183
|
|
|
1568
2184
|
return self.servers[spec.psm]
|
|
1569
2185
|
|
|
1570
2186
|
def create_le_credit_based_server(
|
|
1571
2187
|
self,
|
|
1572
2188
|
spec: LeCreditBasedChannelSpec,
|
|
1573
|
-
handler:
|
|
2189
|
+
handler: Callable[[LeCreditBasedChannel], Any] | None = None,
|
|
1574
2190
|
) -> LeCreditBasedChannelServer:
|
|
1575
2191
|
if not spec.psm:
|
|
1576
2192
|
# Find a free PSM
|
|
@@ -1599,20 +2215,30 @@ class ChannelManager:
|
|
|
1599
2215
|
|
|
1600
2216
|
return self.le_coc_servers[spec.psm]
|
|
1601
2217
|
|
|
1602
|
-
def on_disconnection(self, connection_handle: int,
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
2218
|
+
def on_disconnection(self, connection_handle: int, reason: int) -> None:
|
|
2219
|
+
del reason # unused.
|
|
2220
|
+
logger.debug('disconnection from %d, cleaning up channels', connection_handle)
|
|
2221
|
+
if channels := self.channels.pop(connection_handle, None):
|
|
2222
|
+
for channel in channels.values():
|
|
1606
2223
|
channel.abort()
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
2224
|
+
if le_coc_channels := self.le_coc_channels.pop(connection_handle, None):
|
|
2225
|
+
for le_coc_channel in le_coc_channels.values():
|
|
2226
|
+
le_coc_channel.abort()
|
|
2227
|
+
if pending_credit_based_connections := self.pending_credit_based_connections.pop(
|
|
2228
|
+
connection_handle, None
|
|
2229
|
+
):
|
|
2230
|
+
for future, _ in pending_credit_based_connections.values():
|
|
2231
|
+
if not future.done():
|
|
2232
|
+
future.cancel("ACL disconnected")
|
|
2233
|
+
self.identifiers.pop(connection_handle, None)
|
|
1614
2234
|
|
|
1615
|
-
def send_pdu(
|
|
2235
|
+
def send_pdu(
|
|
2236
|
+
self,
|
|
2237
|
+
connection: Connection,
|
|
2238
|
+
cid: int,
|
|
2239
|
+
pdu: SupportsBytes | bytes,
|
|
2240
|
+
with_fcs: bool = False,
|
|
2241
|
+
) -> None:
|
|
1616
2242
|
pdu_str = pdu.hex() if isinstance(pdu, bytes) else str(pdu)
|
|
1617
2243
|
pdu_bytes = bytes(pdu)
|
|
1618
2244
|
logger.debug(
|
|
@@ -1620,7 +2246,9 @@ class ChannelManager:
|
|
|
1620
2246
|
f'on connection [0x{connection.handle:04X}] (CID={cid}) '
|
|
1621
2247
|
f'{connection.peer_address}: {len(pdu_bytes)} bytes, {pdu_str}'
|
|
1622
2248
|
)
|
|
1623
|
-
self.host.
|
|
2249
|
+
self.host.send_acl_sdu(
|
|
2250
|
+
connection.handle, L2CAP_PDU(cid, bytes(pdu)).to_bytes(with_fcs=with_fcs)
|
|
2251
|
+
)
|
|
1624
2252
|
|
|
1625
2253
|
def on_pdu(self, connection: Connection, cid: int, pdu: bytes) -> None:
|
|
1626
2254
|
if cid in (L2CAP_SIGNALING_CID, L2CAP_LE_SIGNALING_CID):
|
|
@@ -1714,7 +2342,6 @@ class ChannelManager:
|
|
|
1714
2342
|
identifier=request.identifier,
|
|
1715
2343
|
destination_cid=request.source_cid,
|
|
1716
2344
|
source_cid=0,
|
|
1717
|
-
# pylint: disable=line-too-long
|
|
1718
2345
|
result=L2CAP_Connection_Response.Result.CONNECTION_REFUSED_NO_RESOURCES_AVAILABLE,
|
|
1719
2346
|
status=0x0000,
|
|
1720
2347
|
),
|
|
@@ -1726,7 +2353,7 @@ class ChannelManager:
|
|
|
1726
2353
|
f'creating server channel with cid={source_cid} for psm {request.psm}'
|
|
1727
2354
|
)
|
|
1728
2355
|
channel = ClassicChannel(
|
|
1729
|
-
self, connection, cid, request.psm, source_cid, server.
|
|
2356
|
+
self, connection, cid, request.psm, source_cid, server.spec
|
|
1730
2357
|
)
|
|
1731
2358
|
connection_channels[source_cid] = channel
|
|
1732
2359
|
|
|
@@ -1745,7 +2372,6 @@ class ChannelManager:
|
|
|
1745
2372
|
identifier=request.identifier,
|
|
1746
2373
|
destination_cid=request.source_cid,
|
|
1747
2374
|
source_cid=0,
|
|
1748
|
-
# pylint: disable=line-too-long
|
|
1749
2375
|
result=L2CAP_Connection_Response.Result.CONNECTION_REFUSED_PSM_NOT_SUPPORTED,
|
|
1750
2376
|
status=0x0000,
|
|
1751
2377
|
),
|
|
@@ -1974,106 +2600,100 @@ class ChannelManager:
|
|
|
1974
2600
|
cid: int,
|
|
1975
2601
|
request: L2CAP_LE_Credit_Based_Connection_Request,
|
|
1976
2602
|
) -> None:
|
|
1977
|
-
if
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
le_connection_channels = self.le_coc_channels.setdefault(
|
|
1982
|
-
connection.handle, {}
|
|
1983
|
-
)
|
|
1984
|
-
if request.source_cid in le_connection_channels:
|
|
1985
|
-
logger.warning(f'source CID {request.source_cid} already in use')
|
|
1986
|
-
self.send_control_frame(
|
|
1987
|
-
connection,
|
|
1988
|
-
cid,
|
|
1989
|
-
L2CAP_LE_Credit_Based_Connection_Response(
|
|
1990
|
-
identifier=request.identifier,
|
|
1991
|
-
destination_cid=0,
|
|
1992
|
-
mtu=server.mtu,
|
|
1993
|
-
mps=server.mps,
|
|
1994
|
-
initial_credits=0,
|
|
1995
|
-
# pylint: disable=line-too-long
|
|
1996
|
-
result=L2CAP_LE_Credit_Based_Connection_Response.Result.CONNECTION_REFUSED_SOURCE_CID_ALREADY_ALLOCATED,
|
|
1997
|
-
),
|
|
1998
|
-
)
|
|
1999
|
-
return
|
|
2000
|
-
|
|
2001
|
-
# Find a free CID for this new channel
|
|
2002
|
-
connection_channels = self.channels.setdefault(connection.handle, {})
|
|
2003
|
-
source_cid = self.find_free_le_cid(connection_channels)
|
|
2004
|
-
if source_cid is None: # Should never happen!
|
|
2005
|
-
self.send_control_frame(
|
|
2006
|
-
connection,
|
|
2007
|
-
cid,
|
|
2008
|
-
L2CAP_LE_Credit_Based_Connection_Response(
|
|
2009
|
-
identifier=request.identifier,
|
|
2010
|
-
destination_cid=0,
|
|
2011
|
-
mtu=server.mtu,
|
|
2012
|
-
mps=server.mps,
|
|
2013
|
-
initial_credits=0,
|
|
2014
|
-
# pylint: disable=line-too-long
|
|
2015
|
-
result=L2CAP_LE_Credit_Based_Connection_Response.Result.CONNECTION_REFUSED_NO_RESOURCES_AVAILABLE,
|
|
2016
|
-
),
|
|
2017
|
-
)
|
|
2018
|
-
return
|
|
2019
|
-
|
|
2020
|
-
# Create a new channel
|
|
2021
|
-
logger.debug(
|
|
2022
|
-
f'creating LE CoC server channel with cid={source_cid} for psm '
|
|
2023
|
-
f'{request.le_psm}'
|
|
2603
|
+
if not (server := self.le_coc_servers.get(request.le_psm)):
|
|
2604
|
+
logger.info(
|
|
2605
|
+
f'No LE server for connection 0x{connection.handle:04X} '
|
|
2606
|
+
f'on PSM {request.le_psm}'
|
|
2024
2607
|
)
|
|
2025
|
-
|
|
2026
|
-
self,
|
|
2608
|
+
self.send_control_frame(
|
|
2027
2609
|
connection,
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
True,
|
|
2610
|
+
cid,
|
|
2611
|
+
L2CAP_LE_Credit_Based_Connection_Response(
|
|
2612
|
+
identifier=request.identifier,
|
|
2613
|
+
destination_cid=0,
|
|
2614
|
+
mtu=L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_MTU,
|
|
2615
|
+
mps=L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_MPS,
|
|
2616
|
+
initial_credits=0,
|
|
2617
|
+
result=L2CAP_LE_Credit_Based_Connection_Response.Result.CONNECTION_REFUSED_LE_PSM_NOT_SUPPORTED,
|
|
2618
|
+
),
|
|
2038
2619
|
)
|
|
2039
|
-
|
|
2040
|
-
le_connection_channels[request.source_cid] = channel
|
|
2620
|
+
return
|
|
2041
2621
|
|
|
2042
|
-
|
|
2622
|
+
# Check that the CID isn't already used
|
|
2623
|
+
le_connection_channels = self.le_coc_channels.setdefault(connection.handle, {})
|
|
2624
|
+
if request.source_cid in le_connection_channels:
|
|
2625
|
+
logger.warning(f'source CID {request.source_cid} already in use')
|
|
2043
2626
|
self.send_control_frame(
|
|
2044
2627
|
connection,
|
|
2045
2628
|
cid,
|
|
2046
2629
|
L2CAP_LE_Credit_Based_Connection_Response(
|
|
2047
2630
|
identifier=request.identifier,
|
|
2048
|
-
destination_cid=
|
|
2631
|
+
destination_cid=0,
|
|
2049
2632
|
mtu=server.mtu,
|
|
2050
2633
|
mps=server.mps,
|
|
2051
|
-
initial_credits=
|
|
2052
|
-
|
|
2053
|
-
result=L2CAP_LE_Credit_Based_Connection_Response.Result.CONNECTION_SUCCESSFUL,
|
|
2634
|
+
initial_credits=0,
|
|
2635
|
+
result=L2CAP_LE_Credit_Based_Connection_Response.Result.CONNECTION_REFUSED_SOURCE_CID_ALREADY_ALLOCATED,
|
|
2054
2636
|
),
|
|
2055
2637
|
)
|
|
2638
|
+
return
|
|
2056
2639
|
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
f'No LE server for connection 0x{connection.handle:04X} '
|
|
2062
|
-
f'on PSM {request.le_psm}'
|
|
2063
|
-
)
|
|
2640
|
+
# Find a free CID for this new channel
|
|
2641
|
+
connection_channels = self.channels.setdefault(connection.handle, {})
|
|
2642
|
+
source_cid = self.find_free_le_cid(connection_channels)
|
|
2643
|
+
if source_cid is None: # Should never happen!
|
|
2064
2644
|
self.send_control_frame(
|
|
2065
2645
|
connection,
|
|
2066
2646
|
cid,
|
|
2067
2647
|
L2CAP_LE_Credit_Based_Connection_Response(
|
|
2068
2648
|
identifier=request.identifier,
|
|
2069
2649
|
destination_cid=0,
|
|
2070
|
-
mtu=
|
|
2071
|
-
mps=
|
|
2650
|
+
mtu=server.mtu,
|
|
2651
|
+
mps=server.mps,
|
|
2072
2652
|
initial_credits=0,
|
|
2073
|
-
|
|
2074
|
-
result=L2CAP_LE_Credit_Based_Connection_Response.Result.CONNECTION_REFUSED_LE_PSM_NOT_SUPPORTED,
|
|
2653
|
+
result=L2CAP_LE_Credit_Based_Connection_Response.Result.CONNECTION_REFUSED_NO_RESOURCES_AVAILABLE,
|
|
2075
2654
|
),
|
|
2076
2655
|
)
|
|
2656
|
+
return
|
|
2657
|
+
|
|
2658
|
+
# Create a new channel
|
|
2659
|
+
logger.debug(
|
|
2660
|
+
f'creating LE CoC server channel with cid={source_cid} for psm '
|
|
2661
|
+
f'{request.le_psm}'
|
|
2662
|
+
)
|
|
2663
|
+
channel = LeCreditBasedChannel(
|
|
2664
|
+
self,
|
|
2665
|
+
connection,
|
|
2666
|
+
request.le_psm,
|
|
2667
|
+
source_cid,
|
|
2668
|
+
request.source_cid,
|
|
2669
|
+
server.mtu,
|
|
2670
|
+
server.mps,
|
|
2671
|
+
request.initial_credits,
|
|
2672
|
+
request.mtu,
|
|
2673
|
+
request.mps,
|
|
2674
|
+
server.max_credits,
|
|
2675
|
+
True,
|
|
2676
|
+
)
|
|
2677
|
+
connection_channels[source_cid] = channel
|
|
2678
|
+
le_connection_channels[request.source_cid] = channel
|
|
2679
|
+
|
|
2680
|
+
# Respond
|
|
2681
|
+
self.send_control_frame(
|
|
2682
|
+
connection,
|
|
2683
|
+
cid,
|
|
2684
|
+
L2CAP_LE_Credit_Based_Connection_Response(
|
|
2685
|
+
identifier=request.identifier,
|
|
2686
|
+
destination_cid=source_cid,
|
|
2687
|
+
mtu=server.mtu,
|
|
2688
|
+
mps=server.mps,
|
|
2689
|
+
initial_credits=server.max_credits,
|
|
2690
|
+
# pylint: disable=line-too-long
|
|
2691
|
+
result=L2CAP_LE_Credit_Based_Connection_Response.Result.CONNECTION_SUCCESSFUL,
|
|
2692
|
+
),
|
|
2693
|
+
)
|
|
2694
|
+
|
|
2695
|
+
# Notify
|
|
2696
|
+
server.on_connection(channel)
|
|
2077
2697
|
|
|
2078
2698
|
def on_l2cap_le_credit_based_connection_response(
|
|
2079
2699
|
self,
|
|
@@ -2082,11 +2702,9 @@ class ChannelManager:
|
|
|
2082
2702
|
response: L2CAP_LE_Credit_Based_Connection_Response,
|
|
2083
2703
|
) -> None:
|
|
2084
2704
|
# Find the pending request by identifier
|
|
2085
|
-
request
|
|
2086
|
-
if request is None:
|
|
2705
|
+
if not (request := self.le_coc_requests.pop(response.identifier, None)):
|
|
2087
2706
|
logger.warning(color('!!! received response for unknown request', 'red'))
|
|
2088
2707
|
return
|
|
2089
|
-
del self.le_coc_requests[response.identifier]
|
|
2090
2708
|
|
|
2091
2709
|
# Find the channel for this request
|
|
2092
2710
|
channel = self.find_channel(connection.handle, request.source_cid)
|
|
@@ -2103,6 +2721,147 @@ class ChannelManager:
|
|
|
2103
2721
|
# Process the response
|
|
2104
2722
|
channel.on_connection_response(response)
|
|
2105
2723
|
|
|
2724
|
+
def on_l2cap_credit_based_connection_request(
|
|
2725
|
+
self,
|
|
2726
|
+
connection: Connection,
|
|
2727
|
+
cid: int,
|
|
2728
|
+
request: L2CAP_Credit_Based_Connection_Request,
|
|
2729
|
+
) -> None:
|
|
2730
|
+
if not (server := self.le_coc_servers.get(request.spsm)):
|
|
2731
|
+
logger.info(
|
|
2732
|
+
'No LE server for connection 0x%04X ' 'on PSM %d',
|
|
2733
|
+
connection.handle,
|
|
2734
|
+
request.spsm,
|
|
2735
|
+
)
|
|
2736
|
+
self.send_control_frame(
|
|
2737
|
+
connection,
|
|
2738
|
+
cid,
|
|
2739
|
+
L2CAP_Credit_Based_Connection_Response(
|
|
2740
|
+
identifier=request.identifier,
|
|
2741
|
+
destination_cid=[],
|
|
2742
|
+
mtu=L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_MTU,
|
|
2743
|
+
mps=L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_MPS,
|
|
2744
|
+
initial_credits=0,
|
|
2745
|
+
result=L2CAP_Credit_Based_Connection_Response.Result.ALL_CONNECTIONS_REFUSED_SPSM_NOT_SUPPORTED,
|
|
2746
|
+
),
|
|
2747
|
+
)
|
|
2748
|
+
return
|
|
2749
|
+
|
|
2750
|
+
# Check that the CID isn't already used
|
|
2751
|
+
le_connection_channels = self.le_coc_channels.setdefault(connection.handle, {})
|
|
2752
|
+
if cid_in_use := set(request.source_cid).intersection(
|
|
2753
|
+
set(le_connection_channels)
|
|
2754
|
+
):
|
|
2755
|
+
logger.warning('source CID already in use: %s', cid_in_use)
|
|
2756
|
+
self.send_control_frame(
|
|
2757
|
+
connection,
|
|
2758
|
+
cid,
|
|
2759
|
+
L2CAP_Credit_Based_Connection_Response(
|
|
2760
|
+
identifier=request.identifier,
|
|
2761
|
+
mtu=server.mtu,
|
|
2762
|
+
mps=server.mps,
|
|
2763
|
+
initial_credits=0,
|
|
2764
|
+
result=L2CAP_Credit_Based_Connection_Response.Result.SOME_CONNECTIONS_REFUSED_SOURCE_CID_ALREADY_ALLOCATED,
|
|
2765
|
+
destination_cid=[],
|
|
2766
|
+
),
|
|
2767
|
+
)
|
|
2768
|
+
return
|
|
2769
|
+
|
|
2770
|
+
# Find free CIDs for new channels
|
|
2771
|
+
connection_channels = self.channels.setdefault(connection.handle, {})
|
|
2772
|
+
source_cids = self.find_free_le_cids(
|
|
2773
|
+
connection_channels, len(request.source_cid)
|
|
2774
|
+
)
|
|
2775
|
+
if not source_cids:
|
|
2776
|
+
self.send_control_frame(
|
|
2777
|
+
connection,
|
|
2778
|
+
cid,
|
|
2779
|
+
L2CAP_Credit_Based_Connection_Response(
|
|
2780
|
+
identifier=request.identifier,
|
|
2781
|
+
destination_cid=[],
|
|
2782
|
+
mtu=server.mtu,
|
|
2783
|
+
mps=server.mps,
|
|
2784
|
+
initial_credits=server.max_credits,
|
|
2785
|
+
result=L2CAP_Credit_Based_Connection_Response.Result.SOME_CONNECTIONS_REFUSED_INSUFFICIENT_RESOURCES_AVAILABLE,
|
|
2786
|
+
),
|
|
2787
|
+
)
|
|
2788
|
+
return
|
|
2789
|
+
|
|
2790
|
+
for destination_cid in request.source_cid:
|
|
2791
|
+
# TODO: Handle Classic channels.
|
|
2792
|
+
if not (source_cid := self.find_free_le_cid(connection_channels)):
|
|
2793
|
+
logger.warning("No free CIDs available")
|
|
2794
|
+
break
|
|
2795
|
+
# Create a new channel
|
|
2796
|
+
logger.debug(
|
|
2797
|
+
'creating LE CoC server channel with cid=%s for psm %s',
|
|
2798
|
+
source_cid,
|
|
2799
|
+
request.spsm,
|
|
2800
|
+
)
|
|
2801
|
+
channel = LeCreditBasedChannel(
|
|
2802
|
+
self,
|
|
2803
|
+
connection,
|
|
2804
|
+
request.spsm,
|
|
2805
|
+
source_cid,
|
|
2806
|
+
destination_cid,
|
|
2807
|
+
server.mtu,
|
|
2808
|
+
server.mps,
|
|
2809
|
+
request.initial_credits,
|
|
2810
|
+
request.mtu,
|
|
2811
|
+
request.mps,
|
|
2812
|
+
server.max_credits,
|
|
2813
|
+
True,
|
|
2814
|
+
)
|
|
2815
|
+
connection_channels[source_cid] = channel
|
|
2816
|
+
le_connection_channels[source_cid] = channel
|
|
2817
|
+
server.on_connection(channel)
|
|
2818
|
+
|
|
2819
|
+
# Respond
|
|
2820
|
+
self.send_control_frame(
|
|
2821
|
+
connection,
|
|
2822
|
+
cid,
|
|
2823
|
+
L2CAP_Credit_Based_Connection_Response(
|
|
2824
|
+
identifier=request.identifier,
|
|
2825
|
+
destination_cid=source_cids,
|
|
2826
|
+
mtu=server.mtu,
|
|
2827
|
+
mps=server.mps,
|
|
2828
|
+
initial_credits=server.max_credits,
|
|
2829
|
+
result=L2CAP_Credit_Based_Connection_Response.Result.ALL_CONNECTIONS_SUCCESSFUL,
|
|
2830
|
+
),
|
|
2831
|
+
)
|
|
2832
|
+
|
|
2833
|
+
def on_l2cap_credit_based_connection_response(
|
|
2834
|
+
self,
|
|
2835
|
+
connection: Connection,
|
|
2836
|
+
_cid: int,
|
|
2837
|
+
response: L2CAP_Credit_Based_Connection_Response,
|
|
2838
|
+
) -> None:
|
|
2839
|
+
# Find the pending request by identifier
|
|
2840
|
+
pending_connections = self.pending_credit_based_connections.setdefault(
|
|
2841
|
+
connection.handle, {}
|
|
2842
|
+
)
|
|
2843
|
+
if not (
|
|
2844
|
+
pending_connection := pending_connections.pop(response.identifier, None)
|
|
2845
|
+
):
|
|
2846
|
+
logger.warning(color('!!! received response for unknown request', 'red'))
|
|
2847
|
+
return
|
|
2848
|
+
|
|
2849
|
+
connection_result, channels = pending_connection
|
|
2850
|
+
|
|
2851
|
+
# Process the response
|
|
2852
|
+
for channel, destination_cid in zip(channels, response.destination_cid):
|
|
2853
|
+
channel.on_enhanced_connection_response(destination_cid, response)
|
|
2854
|
+
|
|
2855
|
+
if (
|
|
2856
|
+
response.result
|
|
2857
|
+
== L2CAP_Credit_Based_Connection_Response.Result.ALL_CONNECTIONS_SUCCESSFUL
|
|
2858
|
+
):
|
|
2859
|
+
connection_result.set_result(None)
|
|
2860
|
+
else:
|
|
2861
|
+
connection_result.set_exception(
|
|
2862
|
+
L2capError(response.result, response.result.name)
|
|
2863
|
+
)
|
|
2864
|
+
|
|
2106
2865
|
def on_l2cap_le_flow_control_credit(
|
|
2107
2866
|
self, connection: Connection, _cid: int, credit: L2CAP_LE_Flow_Control_Credit
|
|
2108
2867
|
) -> None:
|
|
@@ -2138,7 +2897,7 @@ class ChannelManager:
|
|
|
2138
2897
|
channel = LeCreditBasedChannel(
|
|
2139
2898
|
manager=self,
|
|
2140
2899
|
connection=connection,
|
|
2141
|
-
|
|
2900
|
+
psm=spec.psm,
|
|
2142
2901
|
source_cid=source_cid,
|
|
2143
2902
|
destination_cid=0,
|
|
2144
2903
|
mtu=spec.mtu,
|
|
@@ -2184,12 +2943,12 @@ class ChannelManager:
|
|
|
2184
2943
|
f'creating client channel with cid={source_cid} for psm {spec.psm}'
|
|
2185
2944
|
)
|
|
2186
2945
|
channel = ClassicChannel(
|
|
2187
|
-
self,
|
|
2188
|
-
connection,
|
|
2189
|
-
L2CAP_SIGNALING_CID,
|
|
2190
|
-
spec.psm,
|
|
2191
|
-
source_cid,
|
|
2192
|
-
spec
|
|
2946
|
+
manager=self,
|
|
2947
|
+
connection=connection,
|
|
2948
|
+
signaling_cid=L2CAP_SIGNALING_CID,
|
|
2949
|
+
psm=spec.psm,
|
|
2950
|
+
source_cid=source_cid,
|
|
2951
|
+
spec=spec,
|
|
2193
2952
|
)
|
|
2194
2953
|
connection_channels[source_cid] = channel
|
|
2195
2954
|
|
|
@@ -2197,7 +2956,100 @@ class ChannelManager:
|
|
|
2197
2956
|
try:
|
|
2198
2957
|
await channel.connect()
|
|
2199
2958
|
except BaseException as e:
|
|
2200
|
-
|
|
2959
|
+
connection_channels.pop(source_cid, None)
|
|
2201
2960
|
raise e
|
|
2202
2961
|
|
|
2203
2962
|
return channel
|
|
2963
|
+
|
|
2964
|
+
async def create_enhanced_credit_based_channels(
|
|
2965
|
+
self,
|
|
2966
|
+
connection: Connection,
|
|
2967
|
+
spec: LeCreditBasedChannelSpec,
|
|
2968
|
+
count: int,
|
|
2969
|
+
) -> list[LeCreditBasedChannel]:
|
|
2970
|
+
# Find a free CID for the new channel
|
|
2971
|
+
connection_channels = self.channels.setdefault(connection.handle, {})
|
|
2972
|
+
source_cids = self.find_free_le_cids(connection_channels, count)
|
|
2973
|
+
if not source_cids: # Should never happen!
|
|
2974
|
+
raise OutOfResourcesError('all CIDs already in use')
|
|
2975
|
+
|
|
2976
|
+
if spec.psm is None:
|
|
2977
|
+
raise InvalidArgumentError('PSM cannot be None')
|
|
2978
|
+
|
|
2979
|
+
# Create the channel
|
|
2980
|
+
logger.debug(
|
|
2981
|
+
'creating coc channel with cid=%s for psm %s', source_cids, spec.psm
|
|
2982
|
+
)
|
|
2983
|
+
channels: list[LeCreditBasedChannel] = []
|
|
2984
|
+
for source_cid in source_cids:
|
|
2985
|
+
channel = LeCreditBasedChannel(
|
|
2986
|
+
manager=self,
|
|
2987
|
+
connection=connection,
|
|
2988
|
+
psm=spec.psm,
|
|
2989
|
+
source_cid=source_cid,
|
|
2990
|
+
destination_cid=0,
|
|
2991
|
+
mtu=spec.mtu,
|
|
2992
|
+
mps=spec.mps,
|
|
2993
|
+
credits=0,
|
|
2994
|
+
peer_mtu=0,
|
|
2995
|
+
peer_mps=0,
|
|
2996
|
+
peer_credits=spec.max_credits,
|
|
2997
|
+
connected=False,
|
|
2998
|
+
)
|
|
2999
|
+
connection_channels[source_cid] = channel
|
|
3000
|
+
channels.append(channel)
|
|
3001
|
+
|
|
3002
|
+
identifier = self.next_identifier(connection)
|
|
3003
|
+
request = L2CAP_Credit_Based_Connection_Request(
|
|
3004
|
+
identifier=identifier,
|
|
3005
|
+
spsm=spec.psm,
|
|
3006
|
+
mtu=spec.mtu,
|
|
3007
|
+
mps=spec.mps,
|
|
3008
|
+
initial_credits=spec.max_credits,
|
|
3009
|
+
source_cid=source_cids,
|
|
3010
|
+
)
|
|
3011
|
+
connection_result = asyncio.get_running_loop().create_future()
|
|
3012
|
+
pending_connections = self.pending_credit_based_connections.setdefault(
|
|
3013
|
+
connection.handle, {}
|
|
3014
|
+
)
|
|
3015
|
+
pending_connections[identifier] = (connection_result, channels)
|
|
3016
|
+
self.send_control_frame(
|
|
3017
|
+
connection,
|
|
3018
|
+
L2CAP_LE_SIGNALING_CID,
|
|
3019
|
+
request,
|
|
3020
|
+
)
|
|
3021
|
+
# Connect
|
|
3022
|
+
try:
|
|
3023
|
+
await connection_result
|
|
3024
|
+
except Exception:
|
|
3025
|
+
logger.exception('connection failed')
|
|
3026
|
+
for cid in source_cids:
|
|
3027
|
+
del connection_channels[cid]
|
|
3028
|
+
raise
|
|
3029
|
+
|
|
3030
|
+
# Remember the channel by source CID and destination CID
|
|
3031
|
+
le_connection_channels = self.le_coc_channels.setdefault(connection.handle, {})
|
|
3032
|
+
for channel in channels:
|
|
3033
|
+
le_connection_channels[channel.destination_cid] = channel
|
|
3034
|
+
|
|
3035
|
+
return channels
|
|
3036
|
+
|
|
3037
|
+
@classmethod
|
|
3038
|
+
def make_mode_processor(
|
|
3039
|
+
self,
|
|
3040
|
+
channel: ClassicChannel,
|
|
3041
|
+
mode: TransmissionMode,
|
|
3042
|
+
peer_tx_window_size: int,
|
|
3043
|
+
peer_max_retransmission: int,
|
|
3044
|
+
peer_retransmission_timeout: int,
|
|
3045
|
+
peer_monitor_timeout: int,
|
|
3046
|
+
peer_mps: int,
|
|
3047
|
+
) -> Processor:
|
|
3048
|
+
del peer_retransmission_timeout, peer_monitor_timeout # Unused.
|
|
3049
|
+
if mode == TransmissionMode.BASIC:
|
|
3050
|
+
return Processor(channel)
|
|
3051
|
+
elif mode == TransmissionMode.ENHANCED_RETRANSMISSION:
|
|
3052
|
+
return EnhancedRetransmissionProcessor(
|
|
3053
|
+
channel, peer_tx_window_size, peer_max_retransmission, peer_mps
|
|
3054
|
+
)
|
|
3055
|
+
raise InvalidArgumentError("Mode %s is not implemented", mode.name)
|