bumble 0.0.220__py3-none-any.whl → 0.0.221__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 +1015 -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.221.dist-info}/METADATA +3 -2
- {bumble-0.0.220.dist-info → bumble-0.0.221.dist-info}/RECORD +102 -101
- {bumble-0.0.220.dist-info → bumble-0.0.221.dist-info}/WHEEL +0 -0
- {bumble-0.0.220.dist-info → bumble-0.0.221.dist-info}/entry_points.txt +0 -0
- {bumble-0.0.220.dist-info → bumble-0.0.221.dist-info}/licenses/LICENSE +0 -0
- {bumble-0.0.220.dist-info → bumble-0.0.221.dist-info}/top_level.txt +0 -0
bumble/l2cap.py
CHANGED
|
@@ -23,18 +23,10 @@ import enum
|
|
|
23
23
|
import logging
|
|
24
24
|
import struct
|
|
25
25
|
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
|
-
)
|
|
26
|
+
from collections.abc import Callable, Iterable, Sequence
|
|
27
|
+
from typing import TYPE_CHECKING, Any, ClassVar, SupportsBytes, TypeVar
|
|
28
|
+
|
|
29
|
+
from typing_extensions import override
|
|
38
30
|
|
|
39
31
|
from bumble import hci, utils
|
|
40
32
|
from bumble.colors import color
|
|
@@ -69,7 +61,12 @@ L2CAP_MIN_LE_MTU = 23
|
|
|
69
61
|
L2CAP_MIN_BR_EDR_MTU = 48
|
|
70
62
|
L2CAP_MAX_BR_EDR_MTU = 65535
|
|
71
63
|
|
|
72
|
-
L2CAP_DEFAULT_MTU
|
|
64
|
+
L2CAP_DEFAULT_MTU = 2048 # Default value for the MTU we are willing to accept
|
|
65
|
+
L2CAP_DEFAULT_MPS = 1010 # Default value for the MPS we are willing to accept
|
|
66
|
+
DEFAULT_TX_WINDOW_SIZE = 63
|
|
67
|
+
DEFAULT_MAX_RETRANSMISSION = 1
|
|
68
|
+
DEFAULT_RETRANSMISSION_TIMEOUT = 2.0
|
|
69
|
+
DEFAULT_MONITOR_TIMEOUT = 12.0
|
|
73
70
|
|
|
74
71
|
L2CAP_DEFAULT_CONNECTIONLESS_MTU = 1024
|
|
75
72
|
|
|
@@ -133,29 +130,65 @@ L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_MTU = 2048
|
|
|
133
130
|
L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_MPS = 2048
|
|
134
131
|
L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_INITIAL_CREDITS = 256
|
|
135
132
|
|
|
136
|
-
L2CAP_MAXIMUM_TRANSMISSION_UNIT_CONFIGURATION_OPTION_TYPE = 0x01
|
|
137
|
-
|
|
138
|
-
L2CAP_MTU_CONFIGURATION_PARAMETER_TYPE = 0x01
|
|
139
|
-
|
|
140
133
|
# fmt: on
|
|
141
134
|
# pylint: enable=line-too-long
|
|
142
135
|
|
|
143
136
|
|
|
137
|
+
class TransmissionMode(utils.OpenIntEnum):
|
|
138
|
+
'''See Bluetooth spec @ Vol 3, Part A - 5.4. Retransmission and Flow Control option'''
|
|
139
|
+
|
|
140
|
+
BASIC = 0x00
|
|
141
|
+
RETRANSMISSION = 0x01
|
|
142
|
+
FLOW_CONTROL = 0x02
|
|
143
|
+
ENHANCED_RETRANSMISSION = 0x03
|
|
144
|
+
STREAMING = 0x04
|
|
145
|
+
|
|
146
|
+
|
|
144
147
|
# -----------------------------------------------------------------------------
|
|
145
148
|
# Classes
|
|
146
149
|
# -----------------------------------------------------------------------------
|
|
147
150
|
# pylint: disable=invalid-name
|
|
148
151
|
|
|
149
152
|
|
|
153
|
+
class L2capError(ProtocolError):
|
|
154
|
+
def __init__(self, error_code, error_name='', details=''):
|
|
155
|
+
super().__init__(error_code, 'L2CAP', error_name, details)
|
|
156
|
+
|
|
157
|
+
|
|
150
158
|
@dataclasses.dataclass
|
|
151
159
|
class ClassicChannelSpec:
|
|
152
|
-
|
|
160
|
+
'''Spec of L2CAP Channel over Classic Transport.
|
|
161
|
+
|
|
162
|
+
Attributes:
|
|
163
|
+
psm: PSM of channel. This is optional for server, and when it is None, a PSM
|
|
164
|
+
will be allocated.
|
|
165
|
+
mtu: Maximum Transmission Unit.
|
|
166
|
+
mps: Maximum PDU payload Size.
|
|
167
|
+
tx_window_size: The size of the transmission window for Flow Control mode,
|
|
168
|
+
Retransmission mode, and Enhanced Retransmission mode.
|
|
169
|
+
max_retransmission: The number of transmissions of a single I-frame that L2CAP
|
|
170
|
+
is allowed to try in Retransmission mode and Enhanced Retransmission mode.
|
|
171
|
+
retransmission_timeout: The timeout of retransmission in seconds.
|
|
172
|
+
monitor_timeout: The interval at which S-frames should be transmitted on the
|
|
173
|
+
return channel when no frames are received on the forward channel.
|
|
174
|
+
mode: The transmission mode to use.
|
|
175
|
+
fcs_enabled: Whether to enable FCS (Frame Check Sequence).
|
|
176
|
+
'''
|
|
177
|
+
|
|
178
|
+
psm: int | None = None
|
|
153
179
|
mtu: int = L2CAP_DEFAULT_MTU
|
|
180
|
+
mps: int = L2CAP_DEFAULT_MPS
|
|
181
|
+
tx_window_size: int = DEFAULT_TX_WINDOW_SIZE
|
|
182
|
+
max_retransmission: int = DEFAULT_MAX_RETRANSMISSION
|
|
183
|
+
retransmission_timeout: float = DEFAULT_RETRANSMISSION_TIMEOUT
|
|
184
|
+
monitor_timeout: float = DEFAULT_MONITOR_TIMEOUT
|
|
185
|
+
mode: TransmissionMode = TransmissionMode.BASIC
|
|
186
|
+
fcs_enabled: bool = False
|
|
154
187
|
|
|
155
188
|
|
|
156
189
|
@dataclasses.dataclass
|
|
157
190
|
class LeCreditBasedChannelSpec:
|
|
158
|
-
psm:
|
|
191
|
+
psm: int | None = None
|
|
159
192
|
mtu: int = L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_MTU
|
|
160
193
|
mps: int = L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_MPS
|
|
161
194
|
max_credits: int = L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_INITIAL_CREDITS
|
|
@@ -183,20 +216,29 @@ class L2CAP_PDU:
|
|
|
183
216
|
See Bluetooth spec @ Vol 3, Part A - 3 DATA PACKET FORMAT
|
|
184
217
|
'''
|
|
185
218
|
|
|
186
|
-
@
|
|
187
|
-
def from_bytes(data: bytes) -> L2CAP_PDU:
|
|
219
|
+
@classmethod
|
|
220
|
+
def from_bytes(cls, data: bytes) -> L2CAP_PDU:
|
|
188
221
|
# Check parameters
|
|
189
222
|
if len(data) < 4:
|
|
190
223
|
raise InvalidPacketError('not enough data for L2CAP header')
|
|
191
224
|
|
|
192
|
-
|
|
193
|
-
l2cap_pdu_payload = data[4:]
|
|
225
|
+
length, l2cap_pdu_cid = struct.unpack_from('<HH', data, 0)
|
|
226
|
+
l2cap_pdu_payload = data[4 : 4 + length]
|
|
194
227
|
|
|
195
|
-
return
|
|
228
|
+
return cls(l2cap_pdu_cid, l2cap_pdu_payload)
|
|
196
229
|
|
|
197
230
|
def __bytes__(self) -> bytes:
|
|
198
|
-
|
|
199
|
-
|
|
231
|
+
return self.to_bytes(with_fcs=False)
|
|
232
|
+
|
|
233
|
+
def to_bytes(self, with_fcs: bool = False) -> bytes:
|
|
234
|
+
length = len(self.payload)
|
|
235
|
+
if with_fcs:
|
|
236
|
+
length += 2
|
|
237
|
+
header = struct.pack('<HH', length, self.cid)
|
|
238
|
+
body = header + self.payload
|
|
239
|
+
if with_fcs:
|
|
240
|
+
body += struct.pack('<H', utils.crc_16(body))
|
|
241
|
+
return body
|
|
200
242
|
|
|
201
243
|
def __init__(self, cid: int, payload: bytes) -> None:
|
|
202
244
|
self.cid = cid
|
|
@@ -206,6 +248,119 @@ class L2CAP_PDU:
|
|
|
206
248
|
return f'{color("L2CAP", "green")} [CID={self.cid}]: {self.payload.hex()}'
|
|
207
249
|
|
|
208
250
|
|
|
251
|
+
class ControlField:
|
|
252
|
+
'''
|
|
253
|
+
See Bluetooth spec @ Vol 3, Part A - 3.3.2 Control field.
|
|
254
|
+
'''
|
|
255
|
+
|
|
256
|
+
class FieldType(utils.OpenIntEnum):
|
|
257
|
+
I_FRAME = 0x00
|
|
258
|
+
S_FRAME = 0x01
|
|
259
|
+
|
|
260
|
+
class SegmentationAndReassembly(utils.OpenIntEnum):
|
|
261
|
+
UNSEGMENTED = 0x00
|
|
262
|
+
START = 0x01
|
|
263
|
+
END = 0x02
|
|
264
|
+
CONTINUATION = 0x03
|
|
265
|
+
|
|
266
|
+
class SupervisoryFunction(utils.OpenIntEnum):
|
|
267
|
+
# Receiver Ready
|
|
268
|
+
RR = 0
|
|
269
|
+
# Reject
|
|
270
|
+
REJ = 1
|
|
271
|
+
# Receiver Not Ready
|
|
272
|
+
RNR = 2
|
|
273
|
+
# Select Reject
|
|
274
|
+
SREJ = 3
|
|
275
|
+
|
|
276
|
+
class RetransmissionBit(utils.OpenIntEnum):
|
|
277
|
+
NORMAL = 0x00
|
|
278
|
+
RETRANSMISSION = 0x01
|
|
279
|
+
|
|
280
|
+
req_seq: int
|
|
281
|
+
frame_type: ClassVar[FieldType]
|
|
282
|
+
|
|
283
|
+
def __bytes__(self) -> bytes:
|
|
284
|
+
raise NotImplementedError()
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
class EnhancedControlField(ControlField):
|
|
288
|
+
"""Base control field used in Enhanced Retransmission and Streaming Mode."""
|
|
289
|
+
|
|
290
|
+
final: int
|
|
291
|
+
|
|
292
|
+
@classmethod
|
|
293
|
+
def from_bytes(cls, data: bytes) -> EnhancedControlField:
|
|
294
|
+
frame_type = data[0] & 0x01
|
|
295
|
+
if frame_type == cls.FieldType.I_FRAME:
|
|
296
|
+
return InformationEnhancedControlField.from_bytes(data)
|
|
297
|
+
elif frame_type == cls.FieldType.S_FRAME:
|
|
298
|
+
return SupervisoryEnhancedControlField.from_bytes(data)
|
|
299
|
+
else:
|
|
300
|
+
raise InvalidArgumentError(f'Invalid frame type: {frame_type}')
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
@dataclasses.dataclass
|
|
304
|
+
class InformationEnhancedControlField(EnhancedControlField):
|
|
305
|
+
tx_seq: int = 0
|
|
306
|
+
req_seq: int = 0
|
|
307
|
+
segmentation_and_reassembly: int = (
|
|
308
|
+
EnhancedControlField.SegmentationAndReassembly.UNSEGMENTED
|
|
309
|
+
)
|
|
310
|
+
final: int = 1
|
|
311
|
+
|
|
312
|
+
frame_type = EnhancedControlField.FieldType.I_FRAME
|
|
313
|
+
|
|
314
|
+
@classmethod
|
|
315
|
+
def from_bytes(cls, data: bytes) -> EnhancedControlField:
|
|
316
|
+
return cls(
|
|
317
|
+
tx_seq=(data[0] >> 1) & 0b0111111,
|
|
318
|
+
final=(data[0] >> 7) & 0b1,
|
|
319
|
+
req_seq=(data[1] & 0b001111111),
|
|
320
|
+
segmentation_and_reassembly=(data[1] >> 6) & 0b11,
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
def __bytes__(self) -> bytes:
|
|
324
|
+
return bytes(
|
|
325
|
+
[
|
|
326
|
+
self.frame_type | (self.tx_seq << 1) | (self.final << 7),
|
|
327
|
+
self.req_seq | (self.segmentation_and_reassembly << 6),
|
|
328
|
+
]
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
@dataclasses.dataclass
|
|
333
|
+
class SupervisoryEnhancedControlField(EnhancedControlField):
|
|
334
|
+
supervision_function: int = ControlField.SupervisoryFunction.RR
|
|
335
|
+
poll: int = 0
|
|
336
|
+
req_seq: int = 0
|
|
337
|
+
final: int = 0
|
|
338
|
+
|
|
339
|
+
frame_type = EnhancedControlField.FieldType.S_FRAME
|
|
340
|
+
|
|
341
|
+
@classmethod
|
|
342
|
+
def from_bytes(cls, data: bytes) -> EnhancedControlField:
|
|
343
|
+
return cls(
|
|
344
|
+
supervision_function=(data[0] >> 2) & 0b11,
|
|
345
|
+
poll=(data[0] >> 4) & 0b1,
|
|
346
|
+
final=(data[0] >> 7) & 0b1,
|
|
347
|
+
req_seq=(data[1] & 0b1111111),
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
def __bytes__(self) -> bytes:
|
|
351
|
+
return bytes(
|
|
352
|
+
[
|
|
353
|
+
(
|
|
354
|
+
self.frame_type
|
|
355
|
+
| (self.supervision_function << 2)
|
|
356
|
+
| self.poll << 7
|
|
357
|
+
| (self.final << 7)
|
|
358
|
+
),
|
|
359
|
+
self.req_seq,
|
|
360
|
+
]
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
|
|
209
364
|
# -----------------------------------------------------------------------------
|
|
210
365
|
@dataclasses.dataclass
|
|
211
366
|
class L2CAP_Control_Frame:
|
|
@@ -217,7 +372,7 @@ class L2CAP_Control_Frame:
|
|
|
217
372
|
fields: ClassVar[hci.Fields] = ()
|
|
218
373
|
code: int = dataclasses.field(default=0, init=False)
|
|
219
374
|
name: str = dataclasses.field(default='', init=False)
|
|
220
|
-
_payload:
|
|
375
|
+
_payload: bytes | None = dataclasses.field(default=None, init=False)
|
|
221
376
|
|
|
222
377
|
identifier: int
|
|
223
378
|
|
|
@@ -248,14 +403,16 @@ class L2CAP_Control_Frame:
|
|
|
248
403
|
return frame
|
|
249
404
|
|
|
250
405
|
@staticmethod
|
|
251
|
-
def decode_configuration_options(
|
|
406
|
+
def decode_configuration_options(
|
|
407
|
+
data: bytes,
|
|
408
|
+
) -> list[tuple[L2CAP_Configure_Request.ParameterType, bytes]]:
|
|
252
409
|
options = []
|
|
253
410
|
while len(data) >= 2:
|
|
254
411
|
value_type = data[0]
|
|
255
412
|
length = data[1]
|
|
256
413
|
value = data[2 : 2 + length]
|
|
257
414
|
data = data[2 + length :]
|
|
258
|
-
options.append((value_type, value))
|
|
415
|
+
options.append((L2CAP_Configure_Request.ParameterType(value_type), value))
|
|
259
416
|
|
|
260
417
|
return options
|
|
261
418
|
|
|
@@ -398,6 +555,15 @@ class L2CAP_Configure_Request(L2CAP_Control_Frame):
|
|
|
398
555
|
See Bluetooth spec @ Vol 3, Part A - 4.4 CONFIGURATION REQUEST
|
|
399
556
|
'''
|
|
400
557
|
|
|
558
|
+
class ParameterType(utils.OpenIntEnum):
|
|
559
|
+
MTU = 0x01
|
|
560
|
+
FLUSH_TIMEOUT = 0x02
|
|
561
|
+
QOS = 0x03
|
|
562
|
+
RETRANSMISSION_AND_FLOW_CONTROL = 0x04
|
|
563
|
+
FCS = 0x05
|
|
564
|
+
EXTENDED_FLOW_SPEC = 0x06
|
|
565
|
+
EXTENDED_WINDOW_SIZE = 0x07
|
|
566
|
+
|
|
401
567
|
destination_cid: int = dataclasses.field(metadata=hci.metadata(2))
|
|
402
568
|
flags: int = dataclasses.field(metadata=hci.metadata(2))
|
|
403
569
|
options: bytes = dataclasses.field(metadata=hci.metadata('*'))
|
|
@@ -484,17 +650,18 @@ class L2CAP_Information_Request(L2CAP_Control_Frame):
|
|
|
484
650
|
EXTENDED_FEATURES_SUPPORTED = 0x0002
|
|
485
651
|
FIXED_CHANNELS_SUPPORTED = 0x0003
|
|
486
652
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
653
|
+
class ExtendedFeatures(hci.SpecableFlag):
|
|
654
|
+
FLOW_MODE_CONTROL = 0x0001
|
|
655
|
+
RETRANSMISSION_MODE = 0x0002
|
|
656
|
+
BIDIRECTIONAL_QOS = 0x0004
|
|
657
|
+
ENHANCED_RETRANSMISSION_MODE = 0x0008
|
|
658
|
+
STREAMING_MODE = 0x0010
|
|
659
|
+
FCS_OPTION = 0x0020
|
|
660
|
+
EXTENDED_FLOW_SPEC = 0x0040
|
|
661
|
+
FIXED_CHANNELS = 0x0080
|
|
662
|
+
EXTENDED_WINDOW_SIZE = 0x0100
|
|
663
|
+
UNICAST_CONNECTIONLESS_DATA = 0x0200
|
|
664
|
+
ENHANCED_CREDIT_BASE_FLOW_CONTROL = 0x0400
|
|
498
665
|
|
|
499
666
|
info_type: int = dataclasses.field(metadata=InfoType.type_metadata(2))
|
|
500
667
|
|
|
@@ -661,7 +828,7 @@ class L2CAP_Credit_Based_Connection_Response(L2CAP_Control_Frame):
|
|
|
661
828
|
mtu: int = dataclasses.field(metadata=hci.metadata(2))
|
|
662
829
|
mps: int = dataclasses.field(metadata=hci.metadata(2))
|
|
663
830
|
initial_credits: int = dataclasses.field(metadata=hci.metadata(2))
|
|
664
|
-
result:
|
|
831
|
+
result: Result = dataclasses.field(metadata=Result.type_metadata(2))
|
|
665
832
|
destination_cid: Sequence[int] = dataclasses.field(
|
|
666
833
|
metadata=L2CAP_Credit_Based_Connection_Request.CID_METADATA
|
|
667
834
|
)
|
|
@@ -702,6 +869,217 @@ class L2CAP_Credit_Based_Reconfigure_Response(L2CAP_Control_Frame):
|
|
|
702
869
|
result: int = dataclasses.field(metadata=Result.type_metadata(2))
|
|
703
870
|
|
|
704
871
|
|
|
872
|
+
# -----------------------------------------------------------------------------
|
|
873
|
+
class Processor:
|
|
874
|
+
def __init__(self, channel: ClassicChannel) -> None:
|
|
875
|
+
self.channel = channel
|
|
876
|
+
|
|
877
|
+
def send_sdu(self, sdu: bytes) -> None:
|
|
878
|
+
self.channel.send_pdu(sdu)
|
|
879
|
+
|
|
880
|
+
def on_pdu(self, pdu: bytes) -> None:
|
|
881
|
+
self.channel.on_sdu(pdu)
|
|
882
|
+
|
|
883
|
+
|
|
884
|
+
# TODO: Handle retransmission
|
|
885
|
+
class EnhancedRetransmissionProcessor(Processor):
|
|
886
|
+
MAX_SEQ_NUM = 64
|
|
887
|
+
|
|
888
|
+
@dataclasses.dataclass
|
|
889
|
+
class _PendingPdu:
|
|
890
|
+
payload: bytes
|
|
891
|
+
tx_seq: int
|
|
892
|
+
req_seq: int = 0
|
|
893
|
+
|
|
894
|
+
def __bytes__(self) -> bytes:
|
|
895
|
+
return (
|
|
896
|
+
bytes(
|
|
897
|
+
InformationEnhancedControlField(
|
|
898
|
+
tx_seq=self.tx_seq, req_seq=self.req_seq
|
|
899
|
+
)
|
|
900
|
+
)
|
|
901
|
+
+ self.payload
|
|
902
|
+
)
|
|
903
|
+
|
|
904
|
+
_expected_ack_seq: int = 0
|
|
905
|
+
_next_tx_seq: int = 0
|
|
906
|
+
_last_tx_seq: int = 0
|
|
907
|
+
_req_seq_num: int = 0
|
|
908
|
+
_next_seq_num: int = 0
|
|
909
|
+
_remote_is_busy: bool = False
|
|
910
|
+
|
|
911
|
+
_num_receiver_ready_polls_sent: int = 0
|
|
912
|
+
_pending_pdus: list[_PendingPdu]
|
|
913
|
+
_monitor_handle: asyncio.TimerHandle | None = None
|
|
914
|
+
_receiver_ready_poll_handle: asyncio.TimerHandle | None = None
|
|
915
|
+
|
|
916
|
+
# Timeout, in seconds.
|
|
917
|
+
monitor_timeout: float
|
|
918
|
+
retransmission_timeout: float
|
|
919
|
+
|
|
920
|
+
@classmethod
|
|
921
|
+
def _num_frames_between(cls, low: int, high: int) -> int:
|
|
922
|
+
if high < low:
|
|
923
|
+
high += cls.MAX_SEQ_NUM
|
|
924
|
+
return high - low
|
|
925
|
+
|
|
926
|
+
def __init__(
|
|
927
|
+
self,
|
|
928
|
+
channel: ClassicChannel,
|
|
929
|
+
peer_tx_window_size: int = DEFAULT_TX_WINDOW_SIZE,
|
|
930
|
+
peer_max_retransmission: int = DEFAULT_MAX_RETRANSMISSION,
|
|
931
|
+
peer_mps: int = L2CAP_DEFAULT_MPS,
|
|
932
|
+
):
|
|
933
|
+
spec = channel.spec
|
|
934
|
+
self.mps = spec.mps
|
|
935
|
+
self.peer_mps = peer_mps
|
|
936
|
+
self.peer_tx_window_size = peer_tx_window_size
|
|
937
|
+
self._pending_pdus = []
|
|
938
|
+
self.monitor_timeout = spec.monitor_timeout
|
|
939
|
+
self.channel = channel
|
|
940
|
+
self.retransmission_timeout = spec.retransmission_timeout
|
|
941
|
+
self.peer_max_retransmission = peer_max_retransmission
|
|
942
|
+
|
|
943
|
+
def _monitor(self) -> None:
|
|
944
|
+
if (
|
|
945
|
+
self.peer_max_retransmission <= 0
|
|
946
|
+
or self._num_receiver_ready_polls_sent < self.peer_max_retransmission
|
|
947
|
+
):
|
|
948
|
+
self._send_receiver_ready_poll()
|
|
949
|
+
self._start_monitor()
|
|
950
|
+
else:
|
|
951
|
+
logger.error("Max retransmission exceeded")
|
|
952
|
+
|
|
953
|
+
def _receiver_ready_poll(self) -> None:
|
|
954
|
+
self._send_receiver_ready_poll()
|
|
955
|
+
self._start_monitor()
|
|
956
|
+
|
|
957
|
+
def _start_monitor(self) -> None:
|
|
958
|
+
if self._monitor_handle:
|
|
959
|
+
self._monitor_handle.cancel()
|
|
960
|
+
self._monitor_handle = asyncio.get_running_loop().call_later(
|
|
961
|
+
self.monitor_timeout, self._monitor
|
|
962
|
+
)
|
|
963
|
+
|
|
964
|
+
def _start_receiver_ready_poll(self) -> None:
|
|
965
|
+
if self._receiver_ready_poll_handle:
|
|
966
|
+
self._receiver_ready_poll_handle.cancel()
|
|
967
|
+
self._num_receiver_ready_polls_sent = 0
|
|
968
|
+
|
|
969
|
+
self._receiver_ready_poll_handle = asyncio.get_running_loop().call_later(
|
|
970
|
+
self.retransmission_timeout, self._receiver_ready_poll
|
|
971
|
+
)
|
|
972
|
+
|
|
973
|
+
def _send_receiver_ready_poll(self) -> None:
|
|
974
|
+
self._num_receiver_ready_polls_sent += 1
|
|
975
|
+
self.channel.send_pdu(
|
|
976
|
+
SupervisoryEnhancedControlField(
|
|
977
|
+
supervision_function=SupervisoryEnhancedControlField.SupervisoryFunction.RR,
|
|
978
|
+
final=1,
|
|
979
|
+
req_seq=self._next_seq_num,
|
|
980
|
+
)
|
|
981
|
+
)
|
|
982
|
+
|
|
983
|
+
def _get_next_tx_seq(self) -> int:
|
|
984
|
+
seq_num = self._next_tx_seq
|
|
985
|
+
self._next_tx_seq = (self._next_tx_seq + 1) % self.MAX_SEQ_NUM
|
|
986
|
+
return seq_num
|
|
987
|
+
|
|
988
|
+
@override
|
|
989
|
+
def send_sdu(self, sdu: bytes) -> None:
|
|
990
|
+
if len(sdu) > self.peer_mps:
|
|
991
|
+
raise InvalidArgumentError(
|
|
992
|
+
f'SDU size({len(sdu)}) exceeds channel MPS {self.peer_mps}'
|
|
993
|
+
)
|
|
994
|
+
pdu = self._PendingPdu(payload=sdu, tx_seq=self._get_next_tx_seq())
|
|
995
|
+
self._pending_pdus.append(pdu)
|
|
996
|
+
self._process_output()
|
|
997
|
+
|
|
998
|
+
@override
|
|
999
|
+
def on_pdu(self, pdu: bytes) -> None:
|
|
1000
|
+
control_field = EnhancedControlField.from_bytes(pdu)
|
|
1001
|
+
self._update_ack_seq(control_field.req_seq, control_field.final != 0)
|
|
1002
|
+
if isinstance(control_field, InformationEnhancedControlField):
|
|
1003
|
+
if control_field.tx_seq != self._next_seq_num:
|
|
1004
|
+
return
|
|
1005
|
+
self._next_seq_num = (self._next_seq_num + 1) % self.MAX_SEQ_NUM
|
|
1006
|
+
self._req_seq_num = self._next_seq_num
|
|
1007
|
+
|
|
1008
|
+
ack_frame = SupervisoryEnhancedControlField(
|
|
1009
|
+
supervision_function=SupervisoryEnhancedControlField.SupervisoryFunction.RR,
|
|
1010
|
+
req_seq=self._next_seq_num,
|
|
1011
|
+
)
|
|
1012
|
+
self.channel.send_pdu(ack_frame)
|
|
1013
|
+
self.channel.on_sdu(pdu[2:])
|
|
1014
|
+
elif isinstance(control_field, SupervisoryEnhancedControlField):
|
|
1015
|
+
self._remote_is_busy = (
|
|
1016
|
+
control_field.supervision_function
|
|
1017
|
+
== SupervisoryEnhancedControlField.SupervisoryFunction.RNR
|
|
1018
|
+
)
|
|
1019
|
+
|
|
1020
|
+
if control_field.supervision_function in (
|
|
1021
|
+
SupervisoryEnhancedControlField.SupervisoryFunction.RR,
|
|
1022
|
+
SupervisoryEnhancedControlField.SupervisoryFunction.RNR,
|
|
1023
|
+
):
|
|
1024
|
+
if control_field.poll:
|
|
1025
|
+
self.channel.send_pdu(
|
|
1026
|
+
SupervisoryEnhancedControlField(
|
|
1027
|
+
supervision_function=SupervisoryEnhancedControlField.SupervisoryFunction.RR,
|
|
1028
|
+
final=1,
|
|
1029
|
+
req_seq=self._next_seq_num,
|
|
1030
|
+
)
|
|
1031
|
+
)
|
|
1032
|
+
else:
|
|
1033
|
+
# TODO: Handle Retransmission.
|
|
1034
|
+
pass
|
|
1035
|
+
|
|
1036
|
+
def _process_output(self) -> None:
|
|
1037
|
+
if self._remote_is_busy or self._monitor_handle:
|
|
1038
|
+
return
|
|
1039
|
+
|
|
1040
|
+
for pdu in self._pending_pdus:
|
|
1041
|
+
if self._num_unacked_frames >= self.peer_tx_window_size:
|
|
1042
|
+
return
|
|
1043
|
+
self._send_pdu(pdu)
|
|
1044
|
+
self._last_tx_seq = pdu.tx_seq
|
|
1045
|
+
|
|
1046
|
+
@property
|
|
1047
|
+
def _num_unacked_frames(self) -> int:
|
|
1048
|
+
if not self._pending_pdus:
|
|
1049
|
+
return 0
|
|
1050
|
+
return self._num_frames_between(self._expected_ack_seq, self._last_tx_seq + 1)
|
|
1051
|
+
|
|
1052
|
+
def _send_pdu(self, pdu: _PendingPdu) -> None:
|
|
1053
|
+
pdu.req_seq = self._req_seq_num
|
|
1054
|
+
|
|
1055
|
+
self._start_receiver_ready_poll()
|
|
1056
|
+
self.channel.send_pdu(bytes(pdu))
|
|
1057
|
+
|
|
1058
|
+
def _update_ack_seq(self, new_seq: int, is_poll_response: bool) -> None:
|
|
1059
|
+
num_frames_acked = self._num_frames_between(self._expected_ack_seq, new_seq)
|
|
1060
|
+
if num_frames_acked > self._num_unacked_frames:
|
|
1061
|
+
logger.error(
|
|
1062
|
+
"Received acknowledgment for %d frames but only %d frames are pending",
|
|
1063
|
+
num_frames_acked,
|
|
1064
|
+
self._num_unacked_frames,
|
|
1065
|
+
)
|
|
1066
|
+
return
|
|
1067
|
+
if is_poll_response and self._monitor_handle:
|
|
1068
|
+
self._monitor_handle.cancel()
|
|
1069
|
+
self._monitor_handle = None
|
|
1070
|
+
|
|
1071
|
+
del self._pending_pdus[:num_frames_acked]
|
|
1072
|
+
self._expected_ack_seq = new_seq
|
|
1073
|
+
if (
|
|
1074
|
+
self._expected_ack_seq == self._next_tx_seq
|
|
1075
|
+
and self._receiver_ready_poll_handle
|
|
1076
|
+
):
|
|
1077
|
+
self._receiver_ready_poll_handle.cancel()
|
|
1078
|
+
self._receiver_ready_poll_handle = None
|
|
1079
|
+
|
|
1080
|
+
self._process_output()
|
|
1081
|
+
|
|
1082
|
+
|
|
705
1083
|
# -----------------------------------------------------------------------------
|
|
706
1084
|
class ClassicChannel(utils.EventEmitter):
|
|
707
1085
|
class State(enum.IntEnum):
|
|
@@ -731,14 +1109,15 @@ class ClassicChannel(utils.EventEmitter):
|
|
|
731
1109
|
EVENT_OPEN = "open"
|
|
732
1110
|
EVENT_CLOSE = "close"
|
|
733
1111
|
|
|
734
|
-
connection_result:
|
|
735
|
-
disconnection_result:
|
|
736
|
-
response:
|
|
737
|
-
sink:
|
|
1112
|
+
connection_result: asyncio.Future[None] | None
|
|
1113
|
+
disconnection_result: asyncio.Future[None] | None
|
|
1114
|
+
response: asyncio.Future[bytes] | None
|
|
1115
|
+
sink: Callable[[bytes], Any] | None
|
|
738
1116
|
state: State
|
|
739
1117
|
connection: Connection
|
|
740
1118
|
mtu: int
|
|
741
1119
|
peer_mtu: int
|
|
1120
|
+
processor: Processor
|
|
742
1121
|
|
|
743
1122
|
def __init__(
|
|
744
1123
|
self,
|
|
@@ -747,14 +1126,14 @@ class ClassicChannel(utils.EventEmitter):
|
|
|
747
1126
|
signaling_cid: int,
|
|
748
1127
|
psm: int,
|
|
749
1128
|
source_cid: int,
|
|
750
|
-
|
|
1129
|
+
spec: ClassicChannelSpec,
|
|
751
1130
|
) -> None:
|
|
752
1131
|
super().__init__()
|
|
753
1132
|
self.manager = manager
|
|
754
1133
|
self.connection = connection
|
|
755
1134
|
self.signaling_cid = signaling_cid
|
|
756
1135
|
self.state = self.State.CLOSED
|
|
757
|
-
self.mtu = mtu
|
|
1136
|
+
self.mtu = spec.mtu
|
|
758
1137
|
self.peer_mtu = L2CAP_MIN_BR_EDR_MTU
|
|
759
1138
|
self.psm = psm
|
|
760
1139
|
self.source_cid = source_cid
|
|
@@ -762,26 +1141,47 @@ class ClassicChannel(utils.EventEmitter):
|
|
|
762
1141
|
self.connection_result = None
|
|
763
1142
|
self.disconnection_result = None
|
|
764
1143
|
self.sink = None
|
|
1144
|
+
self.fcs_enabled = spec.fcs_enabled
|
|
1145
|
+
self.spec = spec
|
|
1146
|
+
self.mode = spec.mode
|
|
1147
|
+
# Configure mode-specific processor later on configure request.
|
|
1148
|
+
self.processor = Processor(self)
|
|
1149
|
+
if self.mode not in (
|
|
1150
|
+
TransmissionMode.BASIC,
|
|
1151
|
+
TransmissionMode.ENHANCED_RETRANSMISSION,
|
|
1152
|
+
):
|
|
1153
|
+
raise InvalidArgumentError(f"Mode {spec.mode} is not supported")
|
|
765
1154
|
|
|
766
1155
|
def _change_state(self, new_state: State) -> None:
|
|
767
1156
|
logger.debug(f'{self} state change -> {color(new_state.name, "cyan")}')
|
|
768
1157
|
self.state = new_state
|
|
769
1158
|
|
|
770
|
-
def
|
|
1159
|
+
def write(self, sdu: bytes) -> None:
|
|
1160
|
+
self.processor.send_sdu(sdu)
|
|
1161
|
+
|
|
1162
|
+
def send_pdu(self, pdu: SupportsBytes | bytes) -> None:
|
|
771
1163
|
if self.state != self.State.OPEN:
|
|
772
1164
|
raise InvalidStateError('channel not open')
|
|
773
|
-
self.manager.send_pdu(
|
|
1165
|
+
self.manager.send_pdu(
|
|
1166
|
+
self.connection, self.destination_cid, pdu, self.fcs_enabled
|
|
1167
|
+
)
|
|
774
1168
|
|
|
775
1169
|
def send_control_frame(self, frame: L2CAP_Control_Frame) -> None:
|
|
776
1170
|
self.manager.send_control_frame(self.connection, self.signaling_cid, frame)
|
|
777
1171
|
|
|
778
1172
|
def on_pdu(self, pdu: bytes) -> None:
|
|
1173
|
+
if self.fcs_enabled:
|
|
1174
|
+
# Drop FCS.
|
|
1175
|
+
pdu = pdu[:-2]
|
|
1176
|
+
self.processor.on_pdu(pdu)
|
|
1177
|
+
|
|
1178
|
+
def on_sdu(self, sdu: bytes) -> None:
|
|
779
1179
|
if self.sink:
|
|
780
1180
|
# pylint: disable=not-callable
|
|
781
|
-
self.sink(
|
|
1181
|
+
self.sink(sdu)
|
|
782
1182
|
else:
|
|
783
1183
|
logger.warning(
|
|
784
|
-
color('received
|
|
1184
|
+
color('received sdu without a pending request or sink', 'red')
|
|
785
1185
|
)
|
|
786
1186
|
|
|
787
1187
|
async def connect(self) -> None:
|
|
@@ -811,10 +1211,8 @@ class ClassicChannel(utils.EventEmitter):
|
|
|
811
1211
|
finally:
|
|
812
1212
|
self.connection_result = None
|
|
813
1213
|
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
raise InvalidStateError('invalid state')
|
|
817
|
-
|
|
1214
|
+
def _disconnect_sync(self) -> None:
|
|
1215
|
+
"""For internal sync disconnection."""
|
|
818
1216
|
self._change_state(self.State.WAIT_DISCONNECT)
|
|
819
1217
|
self.send_control_frame(
|
|
820
1218
|
L2CAP_Disconnection_Request(
|
|
@@ -827,7 +1225,21 @@ class ClassicChannel(utils.EventEmitter):
|
|
|
827
1225
|
# Create a future to wait for the state machine to get to a success or error
|
|
828
1226
|
# state
|
|
829
1227
|
self.disconnection_result = asyncio.get_running_loop().create_future()
|
|
830
|
-
|
|
1228
|
+
|
|
1229
|
+
def _abort_connection_result(self, message: str = 'Connection failure') -> None:
|
|
1230
|
+
# Cancel pending connection result.
|
|
1231
|
+
if self.connection_result and not self.connection_result.done():
|
|
1232
|
+
self.connection_result.set_exception(
|
|
1233
|
+
L2capError(error_code=0, error_name=message)
|
|
1234
|
+
)
|
|
1235
|
+
|
|
1236
|
+
async def disconnect(self) -> None:
|
|
1237
|
+
if self.state != self.State.OPEN:
|
|
1238
|
+
raise InvalidStateError('invalid state')
|
|
1239
|
+
|
|
1240
|
+
self._disconnect_sync()
|
|
1241
|
+
if self.disconnection_result:
|
|
1242
|
+
return await self.disconnection_result
|
|
831
1243
|
|
|
832
1244
|
def abort(self) -> None:
|
|
833
1245
|
if self.state == self.State.OPEN:
|
|
@@ -835,20 +1247,40 @@ class ClassicChannel(utils.EventEmitter):
|
|
|
835
1247
|
self.emit(self.EVENT_CLOSE)
|
|
836
1248
|
|
|
837
1249
|
def send_configure_request(self) -> None:
|
|
838
|
-
options =
|
|
839
|
-
|
|
1250
|
+
options: list[tuple[int, bytes]] = [
|
|
1251
|
+
(
|
|
1252
|
+
L2CAP_Configure_Request.ParameterType.MTU,
|
|
1253
|
+
struct.pack('<H', self.mtu),
|
|
1254
|
+
)
|
|
1255
|
+
]
|
|
1256
|
+
if self.mode == TransmissionMode.ENHANCED_RETRANSMISSION:
|
|
1257
|
+
options.append(
|
|
840
1258
|
(
|
|
841
|
-
|
|
842
|
-
struct.pack(
|
|
1259
|
+
L2CAP_Configure_Request.ParameterType.RETRANSMISSION_AND_FLOW_CONTROL,
|
|
1260
|
+
struct.pack(
|
|
1261
|
+
'<BBBHHH',
|
|
1262
|
+
TransmissionMode.ENHANCED_RETRANSMISSION,
|
|
1263
|
+
self.spec.tx_window_size,
|
|
1264
|
+
self.spec.max_retransmission,
|
|
1265
|
+
int(self.spec.retransmission_timeout * 1000),
|
|
1266
|
+
int(self.spec.monitor_timeout * 1000),
|
|
1267
|
+
self.spec.mps,
|
|
1268
|
+
),
|
|
843
1269
|
)
|
|
844
|
-
|
|
845
|
-
|
|
1270
|
+
)
|
|
1271
|
+
if self.fcs_enabled:
|
|
1272
|
+
options.append(
|
|
1273
|
+
(
|
|
1274
|
+
L2CAP_Configure_Request.ParameterType.FCS,
|
|
1275
|
+
bytes([1 if self.fcs_enabled else 0]),
|
|
1276
|
+
)
|
|
1277
|
+
)
|
|
846
1278
|
self.send_control_frame(
|
|
847
1279
|
L2CAP_Configure_Request(
|
|
848
1280
|
identifier=self.manager.next_identifier(self.connection),
|
|
849
1281
|
destination_cid=self.destination_cid,
|
|
850
1282
|
flags=0x0000,
|
|
851
|
-
options=options,
|
|
1283
|
+
options=L2CAP_Control_Frame.encode_configuration_options(options),
|
|
852
1284
|
)
|
|
853
1285
|
)
|
|
854
1286
|
|
|
@@ -884,9 +1316,8 @@ class ClassicChannel(utils.EventEmitter):
|
|
|
884
1316
|
self._change_state(self.State.CLOSED)
|
|
885
1317
|
if self.connection_result:
|
|
886
1318
|
self.connection_result.set_exception(
|
|
887
|
-
|
|
1319
|
+
L2capError(
|
|
888
1320
|
response.result,
|
|
889
|
-
'l2cap',
|
|
890
1321
|
L2CAP_Connection_Response.Result(response.result).name,
|
|
891
1322
|
)
|
|
892
1323
|
)
|
|
@@ -903,20 +1334,111 @@ class ClassicChannel(utils.EventEmitter):
|
|
|
903
1334
|
|
|
904
1335
|
# Decode the options
|
|
905
1336
|
options = L2CAP_Control_Frame.decode_configuration_options(request.options)
|
|
1337
|
+
# Result to options
|
|
1338
|
+
replied_options = list[tuple[int, bytes]]()
|
|
1339
|
+
result = L2CAP_Configure_Response.Result.SUCCESS
|
|
1340
|
+
new_mode = TransmissionMode.BASIC
|
|
906
1341
|
for option in options:
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
1342
|
+
match option[0]:
|
|
1343
|
+
case L2CAP_Configure_Request.ParameterType.MTU:
|
|
1344
|
+
self.peer_mtu = struct.unpack('<H', option[1])[0]
|
|
1345
|
+
logger.debug('Peer MTU = %d', self.peer_mtu)
|
|
1346
|
+
replied_options.append(option)
|
|
1347
|
+
case (
|
|
1348
|
+
L2CAP_Configure_Request.ParameterType.RETRANSMISSION_AND_FLOW_CONTROL
|
|
1349
|
+
):
|
|
1350
|
+
(
|
|
1351
|
+
mode,
|
|
1352
|
+
peer_tx_window_size,
|
|
1353
|
+
peer_max_retransmission,
|
|
1354
|
+
peer_retransmission_timeout,
|
|
1355
|
+
peer_monitor_timeout,
|
|
1356
|
+
peer_mps,
|
|
1357
|
+
) = struct.unpack_from('<BBBHHH', option[1])
|
|
1358
|
+
new_mode = TransmissionMode(mode)
|
|
1359
|
+
logger.debug(
|
|
1360
|
+
'Peer requests Retransmission or Flow Control: mode=%s,'
|
|
1361
|
+
' tx_window_size=%s,'
|
|
1362
|
+
' max_retransmission=%s,'
|
|
1363
|
+
' retransmission_timeout=%s,'
|
|
1364
|
+
' monitor_timeout=%s,'
|
|
1365
|
+
' mps=%s',
|
|
1366
|
+
new_mode.name,
|
|
1367
|
+
peer_tx_window_size,
|
|
1368
|
+
peer_max_retransmission,
|
|
1369
|
+
peer_retransmission_timeout,
|
|
1370
|
+
peer_monitor_timeout,
|
|
1371
|
+
peer_mps,
|
|
1372
|
+
)
|
|
1373
|
+
if new_mode != self.mode:
|
|
1374
|
+
logger.error('Mode mismatch, abort connection')
|
|
1375
|
+
self._abort_connection_result(
|
|
1376
|
+
'Abort on configuration - mode mismatch'
|
|
1377
|
+
)
|
|
1378
|
+
self._disconnect_sync()
|
|
1379
|
+
return
|
|
1380
|
+
|
|
1381
|
+
if new_mode == TransmissionMode.BASIC:
|
|
1382
|
+
replied_options.append(option)
|
|
1383
|
+
elif new_mode == TransmissionMode.ENHANCED_RETRANSMISSION:
|
|
1384
|
+
self.processor = self.manager.make_mode_processor(
|
|
1385
|
+
self,
|
|
1386
|
+
mode=new_mode,
|
|
1387
|
+
peer_tx_window_size=peer_tx_window_size,
|
|
1388
|
+
peer_max_retransmission=peer_max_retransmission,
|
|
1389
|
+
peer_monitor_timeout=peer_monitor_timeout,
|
|
1390
|
+
peer_retransmission_timeout=peer_retransmission_timeout,
|
|
1391
|
+
peer_mps=peer_mps,
|
|
1392
|
+
)
|
|
1393
|
+
replied_options.append(option)
|
|
1394
|
+
else:
|
|
1395
|
+
logger.error("Mode %s is not supported", new_mode.name)
|
|
1396
|
+
self._abort_connection_result(
|
|
1397
|
+
'Abort on configuration - unsupported mode'
|
|
1398
|
+
)
|
|
1399
|
+
self._disconnect_sync()
|
|
1400
|
+
return
|
|
1401
|
+
|
|
1402
|
+
case L2CAP_Configure_Request.ParameterType.FCS:
|
|
1403
|
+
enabled = option[1][0] != 0
|
|
1404
|
+
logger.debug("Peer requests FCS: %s", enabled)
|
|
1405
|
+
if (
|
|
1406
|
+
L2CAP_Information_Request.ExtendedFeatures.FCS_OPTION
|
|
1407
|
+
in self.manager.extended_features
|
|
1408
|
+
):
|
|
1409
|
+
self.fcs_enabled = enabled
|
|
1410
|
+
replied_options.append(option)
|
|
1411
|
+
else:
|
|
1412
|
+
logger.error("Frame Check Sequence is not supported")
|
|
1413
|
+
result = (
|
|
1414
|
+
L2CAP_Configure_Response.Result.FAILURE_UNACCEPTABLE_PARAMETERS
|
|
1415
|
+
)
|
|
1416
|
+
replied_options = [option]
|
|
1417
|
+
break
|
|
1418
|
+
case _:
|
|
1419
|
+
logger.debug(
|
|
1420
|
+
"Reject unimplemented option %s[%s]",
|
|
1421
|
+
option[0].name,
|
|
1422
|
+
option[1].hex(),
|
|
1423
|
+
)
|
|
1424
|
+
result = L2CAP_Configure_Response.Result.FAILURE_UNKNOWN_OPTIONS
|
|
1425
|
+
replied_options = [option]
|
|
1426
|
+
break
|
|
910
1427
|
|
|
911
1428
|
self.send_control_frame(
|
|
912
1429
|
L2CAP_Configure_Response(
|
|
913
1430
|
identifier=request.identifier,
|
|
914
1431
|
source_cid=self.destination_cid,
|
|
915
1432
|
flags=0x0000,
|
|
916
|
-
result=
|
|
917
|
-
options=
|
|
1433
|
+
result=result,
|
|
1434
|
+
options=L2CAP_Control_Frame.encode_configuration_options(
|
|
1435
|
+
replied_options
|
|
1436
|
+
),
|
|
918
1437
|
)
|
|
919
1438
|
)
|
|
1439
|
+
if result != L2CAP_Configure_Response.Result.SUCCESS:
|
|
1440
|
+
return
|
|
1441
|
+
|
|
920
1442
|
if self.state == self.State.WAIT_CONFIG:
|
|
921
1443
|
self._change_state(self.State.WAIT_SEND_CONFIG)
|
|
922
1444
|
self.send_configure_request()
|
|
@@ -969,25 +1491,19 @@ class ClassicChannel(utils.EventEmitter):
|
|
|
969
1491
|
# TODO: decide how to fail gracefully
|
|
970
1492
|
|
|
971
1493
|
def on_disconnection_request(self, request: L2CAP_Disconnection_Request) -> None:
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
source_cid=request.source_cid,
|
|
978
|
-
)
|
|
1494
|
+
self.send_control_frame(
|
|
1495
|
+
L2CAP_Disconnection_Response(
|
|
1496
|
+
identifier=request.identifier,
|
|
1497
|
+
destination_cid=request.destination_cid,
|
|
1498
|
+
source_cid=request.source_cid,
|
|
979
1499
|
)
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
1500
|
+
)
|
|
1501
|
+
self._abort_connection_result()
|
|
1502
|
+
self._change_state(self.State.CLOSED)
|
|
1503
|
+
self.emit(self.EVENT_CLOSE)
|
|
1504
|
+
self.manager.on_channel_closed(self)
|
|
985
1505
|
|
|
986
1506
|
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
1507
|
if (
|
|
992
1508
|
response.destination_cid != self.destination_cid
|
|
993
1509
|
or response.source_cid != self.source_cid
|
|
@@ -1026,22 +1542,23 @@ class LeCreditBasedChannel(utils.EventEmitter):
|
|
|
1026
1542
|
CONNECTION_ERROR = 5
|
|
1027
1543
|
|
|
1028
1544
|
out_queue: deque[bytes]
|
|
1029
|
-
connection_result:
|
|
1030
|
-
disconnection_result:
|
|
1031
|
-
in_sdu:
|
|
1032
|
-
out_sdu:
|
|
1545
|
+
connection_result: asyncio.Future[LeCreditBasedChannel] | None
|
|
1546
|
+
disconnection_result: asyncio.Future[None] | None
|
|
1547
|
+
in_sdu: bytes | None
|
|
1548
|
+
out_sdu: bytes | None
|
|
1033
1549
|
state: State
|
|
1034
1550
|
connection: Connection
|
|
1035
|
-
sink:
|
|
1551
|
+
sink: Callable[[bytes], Any] | None
|
|
1036
1552
|
|
|
1037
1553
|
EVENT_OPEN = "open"
|
|
1038
1554
|
EVENT_CLOSE = "close"
|
|
1555
|
+
EVENT_ATT_MTU_UPDATE = "att_mtu_update"
|
|
1039
1556
|
|
|
1040
1557
|
def __init__(
|
|
1041
1558
|
self,
|
|
1042
1559
|
manager: ChannelManager,
|
|
1043
1560
|
connection: Connection,
|
|
1044
|
-
|
|
1561
|
+
psm: int,
|
|
1045
1562
|
source_cid: int,
|
|
1046
1563
|
destination_cid: int,
|
|
1047
1564
|
mtu: int,
|
|
@@ -1055,7 +1572,7 @@ class LeCreditBasedChannel(utils.EventEmitter):
|
|
|
1055
1572
|
super().__init__()
|
|
1056
1573
|
self.manager = manager
|
|
1057
1574
|
self.connection = connection
|
|
1058
|
-
self.
|
|
1575
|
+
self.psm = psm
|
|
1059
1576
|
self.source_cid = source_cid
|
|
1060
1577
|
self.destination_cid = destination_cid
|
|
1061
1578
|
self.mtu = mtu
|
|
@@ -1075,6 +1592,7 @@ class LeCreditBasedChannel(utils.EventEmitter):
|
|
|
1075
1592
|
self.connection_result = None
|
|
1076
1593
|
self.disconnection_result = None
|
|
1077
1594
|
self.drained = asyncio.Event()
|
|
1595
|
+
self.att_mtu = 0 # Filled by GATT client or server later.
|
|
1078
1596
|
|
|
1079
1597
|
self.drained.set()
|
|
1080
1598
|
|
|
@@ -1092,7 +1610,7 @@ class LeCreditBasedChannel(utils.EventEmitter):
|
|
|
1092
1610
|
elif new_state == self.State.DISCONNECTED:
|
|
1093
1611
|
self.emit(self.EVENT_CLOSE)
|
|
1094
1612
|
|
|
1095
|
-
def send_pdu(self, pdu:
|
|
1613
|
+
def send_pdu(self, pdu: SupportsBytes | bytes) -> None:
|
|
1096
1614
|
self.manager.send_pdu(self.connection, self.destination_cid, pdu)
|
|
1097
1615
|
|
|
1098
1616
|
def send_control_frame(self, frame: L2CAP_Control_Frame) -> None:
|
|
@@ -1111,7 +1629,7 @@ class LeCreditBasedChannel(utils.EventEmitter):
|
|
|
1111
1629
|
self._change_state(self.State.CONNECTING)
|
|
1112
1630
|
request = L2CAP_LE_Credit_Based_Connection_Request(
|
|
1113
1631
|
identifier=identifier,
|
|
1114
|
-
le_psm=self.
|
|
1632
|
+
le_psm=self.psm,
|
|
1115
1633
|
source_cid=self.source_cid,
|
|
1116
1634
|
mtu=self.mtu,
|
|
1117
1635
|
mps=self.mps,
|
|
@@ -1149,6 +1667,9 @@ class LeCreditBasedChannel(utils.EventEmitter):
|
|
|
1149
1667
|
def abort(self) -> None:
|
|
1150
1668
|
if self.state == self.State.CONNECTED:
|
|
1151
1669
|
self._change_state(self.State.DISCONNECTED)
|
|
1670
|
+
if self.state == self.State.CONNECTING:
|
|
1671
|
+
if self.connection_result is not None:
|
|
1672
|
+
self.connection_result.cancel()
|
|
1152
1673
|
|
|
1153
1674
|
def on_pdu(self, pdu: bytes) -> None:
|
|
1154
1675
|
if self.sink is None:
|
|
@@ -1239,9 +1760,8 @@ class LeCreditBasedChannel(utils.EventEmitter):
|
|
|
1239
1760
|
self._change_state(self.State.CONNECTED)
|
|
1240
1761
|
else:
|
|
1241
1762
|
self.connection_result.set_exception(
|
|
1242
|
-
|
|
1763
|
+
L2capError(
|
|
1243
1764
|
response.result,
|
|
1244
|
-
'l2cap',
|
|
1245
1765
|
L2CAP_LE_Credit_Based_Connection_Response.Result(
|
|
1246
1766
|
response.result
|
|
1247
1767
|
).name,
|
|
@@ -1252,6 +1772,22 @@ class LeCreditBasedChannel(utils.EventEmitter):
|
|
|
1252
1772
|
# Cleanup
|
|
1253
1773
|
self.connection_result = None
|
|
1254
1774
|
|
|
1775
|
+
def on_enhanced_connection_response(
|
|
1776
|
+
self, destination_cid: int, response: L2CAP_Credit_Based_Connection_Response
|
|
1777
|
+
) -> None:
|
|
1778
|
+
if (
|
|
1779
|
+
response.result
|
|
1780
|
+
== L2CAP_Credit_Based_Connection_Response.Result.ALL_CONNECTIONS_SUCCESSFUL
|
|
1781
|
+
):
|
|
1782
|
+
self.destination_cid = destination_cid
|
|
1783
|
+
self.peer_mtu = response.mtu
|
|
1784
|
+
self.peer_mps = response.mps
|
|
1785
|
+
self.credits = response.initial_credits
|
|
1786
|
+
self.connected = True
|
|
1787
|
+
self._change_state(self.State.CONNECTED)
|
|
1788
|
+
else:
|
|
1789
|
+
self._change_state(self.State.CONNECTION_ERROR)
|
|
1790
|
+
|
|
1255
1791
|
def on_credits(self, credits: int) -> None: # pylint: disable=redefined-builtin
|
|
1256
1792
|
self.credits += credits
|
|
1257
1793
|
logger.debug(f'received {credits} credits, total = {self.credits}')
|
|
@@ -1287,6 +1823,10 @@ class LeCreditBasedChannel(utils.EventEmitter):
|
|
|
1287
1823
|
self.disconnection_result.set_result(None)
|
|
1288
1824
|
self.disconnection_result = None
|
|
1289
1825
|
|
|
1826
|
+
def on_att_mtu_update(self, mtu: int) -> None:
|
|
1827
|
+
self.att_mtu = mtu
|
|
1828
|
+
self.emit(self.EVENT_ATT_MTU_UPDATE, mtu)
|
|
1829
|
+
|
|
1290
1830
|
def flush_output(self) -> None:
|
|
1291
1831
|
self.out_queue.clear()
|
|
1292
1832
|
self.out_sdu = None
|
|
@@ -1364,7 +1904,7 @@ class LeCreditBasedChannel(utils.EventEmitter):
|
|
|
1364
1904
|
return (
|
|
1365
1905
|
f'CoC({self.source_cid}->{self.destination_cid}, '
|
|
1366
1906
|
f'State={self.state.name}, '
|
|
1367
|
-
f'PSM={self.
|
|
1907
|
+
f'PSM={self.psm}, '
|
|
1368
1908
|
f'MTU={self.mtu}/{self.peer_mtu}, '
|
|
1369
1909
|
f'MPS={self.mps}/{self.peer_mps}, '
|
|
1370
1910
|
f'credits={self.credits}/{self.peer_credits})'
|
|
@@ -1379,14 +1919,14 @@ class ClassicChannelServer(utils.EventEmitter):
|
|
|
1379
1919
|
self,
|
|
1380
1920
|
manager: ChannelManager,
|
|
1381
1921
|
psm: int,
|
|
1382
|
-
handler:
|
|
1383
|
-
|
|
1922
|
+
handler: Callable[[ClassicChannel], Any] | None,
|
|
1923
|
+
spec: ClassicChannelSpec,
|
|
1384
1924
|
) -> None:
|
|
1385
1925
|
super().__init__()
|
|
1386
1926
|
self.manager = manager
|
|
1387
1927
|
self.handler = handler
|
|
1388
1928
|
self.psm = psm
|
|
1389
|
-
self.
|
|
1929
|
+
self.spec = spec
|
|
1390
1930
|
|
|
1391
1931
|
def on_connection(self, channel: ClassicChannel) -> None:
|
|
1392
1932
|
self.emit(self.EVENT_CONNECTION, channel)
|
|
@@ -1406,7 +1946,7 @@ class LeCreditBasedChannelServer(utils.EventEmitter):
|
|
|
1406
1946
|
self,
|
|
1407
1947
|
manager: ChannelManager,
|
|
1408
1948
|
psm: int,
|
|
1409
|
-
handler:
|
|
1949
|
+
handler: Callable[[LeCreditBasedChannel], Any] | None,
|
|
1410
1950
|
max_credits: int,
|
|
1411
1951
|
mtu: int,
|
|
1412
1952
|
mps: int,
|
|
@@ -1432,14 +1972,24 @@ class LeCreditBasedChannelServer(utils.EventEmitter):
|
|
|
1432
1972
|
# -----------------------------------------------------------------------------
|
|
1433
1973
|
class ChannelManager:
|
|
1434
1974
|
identifiers: dict[int, int]
|
|
1435
|
-
channels: dict[int, dict[int,
|
|
1975
|
+
channels: dict[int, dict[int, ClassicChannel | LeCreditBasedChannel]]
|
|
1436
1976
|
servers: dict[int, ClassicChannelServer]
|
|
1437
1977
|
le_coc_channels: dict[int, dict[int, LeCreditBasedChannel]]
|
|
1438
1978
|
le_coc_servers: dict[int, LeCreditBasedChannelServer]
|
|
1439
1979
|
le_coc_requests: dict[int, L2CAP_LE_Credit_Based_Connection_Request]
|
|
1440
|
-
fixed_channels: dict[int,
|
|
1441
|
-
|
|
1442
|
-
|
|
1980
|
+
fixed_channels: dict[int, Callable[[int, bytes], Any] | None]
|
|
1981
|
+
pending_credit_based_connections: dict[
|
|
1982
|
+
int,
|
|
1983
|
+
dict[
|
|
1984
|
+
int,
|
|
1985
|
+
tuple[
|
|
1986
|
+
asyncio.Future[None],
|
|
1987
|
+
list[LeCreditBasedChannel],
|
|
1988
|
+
],
|
|
1989
|
+
],
|
|
1990
|
+
]
|
|
1991
|
+
_host: Host | None
|
|
1992
|
+
connection_parameters_update_response: asyncio.Future[int] | None
|
|
1443
1993
|
|
|
1444
1994
|
def __init__(
|
|
1445
1995
|
self,
|
|
@@ -1459,7 +2009,10 @@ class ChannelManager:
|
|
|
1459
2009
|
) # LE CoC channels, mapped by connection and destination cid
|
|
1460
2010
|
self.le_coc_servers = {} # LE CoC - Servers accepting connections, by PSM
|
|
1461
2011
|
self.le_coc_requests = {} # LE CoC connection requests, by identifier
|
|
1462
|
-
self.
|
|
2012
|
+
self.pending_credit_based_connections = (
|
|
2013
|
+
{}
|
|
2014
|
+
) # Credit-based connection request contexts, by connection handle and identifier
|
|
2015
|
+
self.extended_features = set(extended_features)
|
|
1463
2016
|
self.connectionless_mtu = connectionless_mtu
|
|
1464
2017
|
self.connection_parameters_update_response = None
|
|
1465
2018
|
|
|
@@ -1501,18 +2054,26 @@ class ChannelManager:
|
|
|
1501
2054
|
|
|
1502
2055
|
raise OutOfResourcesError('no free CID available')
|
|
1503
2056
|
|
|
1504
|
-
@
|
|
1505
|
-
def find_free_le_cid(channels: Iterable[int]) -> int:
|
|
2057
|
+
@classmethod
|
|
2058
|
+
def find_free_le_cid(cls, channels: Iterable[int]) -> int | None:
|
|
2059
|
+
cids = cls.find_free_le_cids(channels, 1)
|
|
2060
|
+
return cids[0] if cids else None
|
|
2061
|
+
|
|
2062
|
+
@classmethod
|
|
2063
|
+
def find_free_le_cids(cls, channels: Iterable[int], count: int) -> list[int]:
|
|
1506
2064
|
# Pick the smallest valid CID that's not already in the list
|
|
1507
2065
|
# (not necessarily the most efficient algorithm, but the list of CID is
|
|
1508
2066
|
# very small in practice)
|
|
2067
|
+
cids: list[int] = []
|
|
1509
2068
|
for cid in range(
|
|
1510
2069
|
L2CAP_LE_U_DYNAMIC_CID_RANGE_START, L2CAP_LE_U_DYNAMIC_CID_RANGE_END + 1
|
|
1511
2070
|
):
|
|
1512
2071
|
if cid not in channels:
|
|
1513
|
-
|
|
2072
|
+
cids.append(cid)
|
|
2073
|
+
if len(cids) == count:
|
|
2074
|
+
return cids
|
|
1514
2075
|
|
|
1515
|
-
|
|
2076
|
+
return []
|
|
1516
2077
|
|
|
1517
2078
|
def next_identifier(self, connection: Connection) -> int:
|
|
1518
2079
|
identifier = (self.identifiers.setdefault(connection.handle, 0) + 1) % 256
|
|
@@ -1534,7 +2095,7 @@ class ChannelManager:
|
|
|
1534
2095
|
def create_classic_server(
|
|
1535
2096
|
self,
|
|
1536
2097
|
spec: ClassicChannelSpec,
|
|
1537
|
-
handler:
|
|
2098
|
+
handler: Callable[[ClassicChannel], Any] | None = None,
|
|
1538
2099
|
) -> ClassicChannelServer:
|
|
1539
2100
|
if not spec.psm:
|
|
1540
2101
|
# Find a free PSM
|
|
@@ -1563,14 +2124,14 @@ class ChannelManager:
|
|
|
1563
2124
|
raise InvalidArgumentError('invalid PSM')
|
|
1564
2125
|
check >>= 8
|
|
1565
2126
|
|
|
1566
|
-
self.servers[spec.psm] = ClassicChannelServer(self, spec.psm, handler, spec
|
|
2127
|
+
self.servers[spec.psm] = ClassicChannelServer(self, spec.psm, handler, spec)
|
|
1567
2128
|
|
|
1568
2129
|
return self.servers[spec.psm]
|
|
1569
2130
|
|
|
1570
2131
|
def create_le_credit_based_server(
|
|
1571
2132
|
self,
|
|
1572
2133
|
spec: LeCreditBasedChannelSpec,
|
|
1573
|
-
handler:
|
|
2134
|
+
handler: Callable[[LeCreditBasedChannel], Any] | None = None,
|
|
1574
2135
|
) -> LeCreditBasedChannelServer:
|
|
1575
2136
|
if not spec.psm:
|
|
1576
2137
|
# Find a free PSM
|
|
@@ -1599,20 +2160,30 @@ class ChannelManager:
|
|
|
1599
2160
|
|
|
1600
2161
|
return self.le_coc_servers[spec.psm]
|
|
1601
2162
|
|
|
1602
|
-
def on_disconnection(self, connection_handle: int,
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
del self.channels[connection_handle]
|
|
1608
|
-
if connection_handle in self.le_coc_channels:
|
|
1609
|
-
for _, channel in self.le_coc_channels[connection_handle].items():
|
|
2163
|
+
def on_disconnection(self, connection_handle: int, reason: int) -> None:
|
|
2164
|
+
del reason # unused.
|
|
2165
|
+
logger.debug('disconnection from %d, cleaning up channels', connection_handle)
|
|
2166
|
+
if channels := self.channels.pop(connection_handle, None):
|
|
2167
|
+
for channel in channels.values():
|
|
1610
2168
|
channel.abort()
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
2169
|
+
if le_coc_channels := self.le_coc_channels.pop(connection_handle, None):
|
|
2170
|
+
for le_coc_channel in le_coc_channels.values():
|
|
2171
|
+
le_coc_channel.abort()
|
|
2172
|
+
if pending_credit_based_connections := self.pending_credit_based_connections.pop(
|
|
2173
|
+
connection_handle, None
|
|
2174
|
+
):
|
|
2175
|
+
for future, _ in pending_credit_based_connections.values():
|
|
2176
|
+
if not future.done():
|
|
2177
|
+
future.cancel("ACL disconnected")
|
|
2178
|
+
self.identifiers.pop(connection_handle, None)
|
|
1614
2179
|
|
|
1615
|
-
def send_pdu(
|
|
2180
|
+
def send_pdu(
|
|
2181
|
+
self,
|
|
2182
|
+
connection: Connection,
|
|
2183
|
+
cid: int,
|
|
2184
|
+
pdu: SupportsBytes | bytes,
|
|
2185
|
+
with_fcs: bool = False,
|
|
2186
|
+
) -> None:
|
|
1616
2187
|
pdu_str = pdu.hex() if isinstance(pdu, bytes) else str(pdu)
|
|
1617
2188
|
pdu_bytes = bytes(pdu)
|
|
1618
2189
|
logger.debug(
|
|
@@ -1620,7 +2191,9 @@ class ChannelManager:
|
|
|
1620
2191
|
f'on connection [0x{connection.handle:04X}] (CID={cid}) '
|
|
1621
2192
|
f'{connection.peer_address}: {len(pdu_bytes)} bytes, {pdu_str}'
|
|
1622
2193
|
)
|
|
1623
|
-
self.host.
|
|
2194
|
+
self.host.send_acl_sdu(
|
|
2195
|
+
connection.handle, L2CAP_PDU(cid, bytes(pdu)).to_bytes(with_fcs=with_fcs)
|
|
2196
|
+
)
|
|
1624
2197
|
|
|
1625
2198
|
def on_pdu(self, connection: Connection, cid: int, pdu: bytes) -> None:
|
|
1626
2199
|
if cid in (L2CAP_SIGNALING_CID, L2CAP_LE_SIGNALING_CID):
|
|
@@ -1714,7 +2287,6 @@ class ChannelManager:
|
|
|
1714
2287
|
identifier=request.identifier,
|
|
1715
2288
|
destination_cid=request.source_cid,
|
|
1716
2289
|
source_cid=0,
|
|
1717
|
-
# pylint: disable=line-too-long
|
|
1718
2290
|
result=L2CAP_Connection_Response.Result.CONNECTION_REFUSED_NO_RESOURCES_AVAILABLE,
|
|
1719
2291
|
status=0x0000,
|
|
1720
2292
|
),
|
|
@@ -1726,7 +2298,7 @@ class ChannelManager:
|
|
|
1726
2298
|
f'creating server channel with cid={source_cid} for psm {request.psm}'
|
|
1727
2299
|
)
|
|
1728
2300
|
channel = ClassicChannel(
|
|
1729
|
-
self, connection, cid, request.psm, source_cid, server.
|
|
2301
|
+
self, connection, cid, request.psm, source_cid, server.spec
|
|
1730
2302
|
)
|
|
1731
2303
|
connection_channels[source_cid] = channel
|
|
1732
2304
|
|
|
@@ -1745,7 +2317,6 @@ class ChannelManager:
|
|
|
1745
2317
|
identifier=request.identifier,
|
|
1746
2318
|
destination_cid=request.source_cid,
|
|
1747
2319
|
source_cid=0,
|
|
1748
|
-
# pylint: disable=line-too-long
|
|
1749
2320
|
result=L2CAP_Connection_Response.Result.CONNECTION_REFUSED_PSM_NOT_SUPPORTED,
|
|
1750
2321
|
status=0x0000,
|
|
1751
2322
|
),
|
|
@@ -1974,106 +2545,100 @@ class ChannelManager:
|
|
|
1974
2545
|
cid: int,
|
|
1975
2546
|
request: L2CAP_LE_Credit_Based_Connection_Request,
|
|
1976
2547
|
) -> 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}'
|
|
2548
|
+
if not (server := self.le_coc_servers.get(request.le_psm)):
|
|
2549
|
+
logger.info(
|
|
2550
|
+
f'No LE server for connection 0x{connection.handle:04X} '
|
|
2551
|
+
f'on PSM {request.le_psm}'
|
|
2024
2552
|
)
|
|
2025
|
-
|
|
2026
|
-
self,
|
|
2553
|
+
self.send_control_frame(
|
|
2027
2554
|
connection,
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
True,
|
|
2555
|
+
cid,
|
|
2556
|
+
L2CAP_LE_Credit_Based_Connection_Response(
|
|
2557
|
+
identifier=request.identifier,
|
|
2558
|
+
destination_cid=0,
|
|
2559
|
+
mtu=L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_MTU,
|
|
2560
|
+
mps=L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_MPS,
|
|
2561
|
+
initial_credits=0,
|
|
2562
|
+
result=L2CAP_LE_Credit_Based_Connection_Response.Result.CONNECTION_REFUSED_LE_PSM_NOT_SUPPORTED,
|
|
2563
|
+
),
|
|
2038
2564
|
)
|
|
2039
|
-
|
|
2040
|
-
le_connection_channels[request.source_cid] = channel
|
|
2565
|
+
return
|
|
2041
2566
|
|
|
2042
|
-
|
|
2567
|
+
# Check that the CID isn't already used
|
|
2568
|
+
le_connection_channels = self.le_coc_channels.setdefault(connection.handle, {})
|
|
2569
|
+
if request.source_cid in le_connection_channels:
|
|
2570
|
+
logger.warning(f'source CID {request.source_cid} already in use')
|
|
2043
2571
|
self.send_control_frame(
|
|
2044
2572
|
connection,
|
|
2045
2573
|
cid,
|
|
2046
2574
|
L2CAP_LE_Credit_Based_Connection_Response(
|
|
2047
2575
|
identifier=request.identifier,
|
|
2048
|
-
destination_cid=
|
|
2576
|
+
destination_cid=0,
|
|
2049
2577
|
mtu=server.mtu,
|
|
2050
2578
|
mps=server.mps,
|
|
2051
|
-
initial_credits=
|
|
2052
|
-
|
|
2053
|
-
result=L2CAP_LE_Credit_Based_Connection_Response.Result.CONNECTION_SUCCESSFUL,
|
|
2579
|
+
initial_credits=0,
|
|
2580
|
+
result=L2CAP_LE_Credit_Based_Connection_Response.Result.CONNECTION_REFUSED_SOURCE_CID_ALREADY_ALLOCATED,
|
|
2054
2581
|
),
|
|
2055
2582
|
)
|
|
2583
|
+
return
|
|
2056
2584
|
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
f'No LE server for connection 0x{connection.handle:04X} '
|
|
2062
|
-
f'on PSM {request.le_psm}'
|
|
2063
|
-
)
|
|
2585
|
+
# Find a free CID for this new channel
|
|
2586
|
+
connection_channels = self.channels.setdefault(connection.handle, {})
|
|
2587
|
+
source_cid = self.find_free_le_cid(connection_channels)
|
|
2588
|
+
if source_cid is None: # Should never happen!
|
|
2064
2589
|
self.send_control_frame(
|
|
2065
2590
|
connection,
|
|
2066
2591
|
cid,
|
|
2067
2592
|
L2CAP_LE_Credit_Based_Connection_Response(
|
|
2068
2593
|
identifier=request.identifier,
|
|
2069
2594
|
destination_cid=0,
|
|
2070
|
-
mtu=
|
|
2071
|
-
mps=
|
|
2595
|
+
mtu=server.mtu,
|
|
2596
|
+
mps=server.mps,
|
|
2072
2597
|
initial_credits=0,
|
|
2073
|
-
|
|
2074
|
-
result=L2CAP_LE_Credit_Based_Connection_Response.Result.CONNECTION_REFUSED_LE_PSM_NOT_SUPPORTED,
|
|
2598
|
+
result=L2CAP_LE_Credit_Based_Connection_Response.Result.CONNECTION_REFUSED_NO_RESOURCES_AVAILABLE,
|
|
2075
2599
|
),
|
|
2076
2600
|
)
|
|
2601
|
+
return
|
|
2602
|
+
|
|
2603
|
+
# Create a new channel
|
|
2604
|
+
logger.debug(
|
|
2605
|
+
f'creating LE CoC server channel with cid={source_cid} for psm '
|
|
2606
|
+
f'{request.le_psm}'
|
|
2607
|
+
)
|
|
2608
|
+
channel = LeCreditBasedChannel(
|
|
2609
|
+
self,
|
|
2610
|
+
connection,
|
|
2611
|
+
request.le_psm,
|
|
2612
|
+
source_cid,
|
|
2613
|
+
request.source_cid,
|
|
2614
|
+
server.mtu,
|
|
2615
|
+
server.mps,
|
|
2616
|
+
request.initial_credits,
|
|
2617
|
+
request.mtu,
|
|
2618
|
+
request.mps,
|
|
2619
|
+
server.max_credits,
|
|
2620
|
+
True,
|
|
2621
|
+
)
|
|
2622
|
+
connection_channels[source_cid] = channel
|
|
2623
|
+
le_connection_channels[request.source_cid] = channel
|
|
2624
|
+
|
|
2625
|
+
# Respond
|
|
2626
|
+
self.send_control_frame(
|
|
2627
|
+
connection,
|
|
2628
|
+
cid,
|
|
2629
|
+
L2CAP_LE_Credit_Based_Connection_Response(
|
|
2630
|
+
identifier=request.identifier,
|
|
2631
|
+
destination_cid=source_cid,
|
|
2632
|
+
mtu=server.mtu,
|
|
2633
|
+
mps=server.mps,
|
|
2634
|
+
initial_credits=server.max_credits,
|
|
2635
|
+
# pylint: disable=line-too-long
|
|
2636
|
+
result=L2CAP_LE_Credit_Based_Connection_Response.Result.CONNECTION_SUCCESSFUL,
|
|
2637
|
+
),
|
|
2638
|
+
)
|
|
2639
|
+
|
|
2640
|
+
# Notify
|
|
2641
|
+
server.on_connection(channel)
|
|
2077
2642
|
|
|
2078
2643
|
def on_l2cap_le_credit_based_connection_response(
|
|
2079
2644
|
self,
|
|
@@ -2082,11 +2647,9 @@ class ChannelManager:
|
|
|
2082
2647
|
response: L2CAP_LE_Credit_Based_Connection_Response,
|
|
2083
2648
|
) -> None:
|
|
2084
2649
|
# Find the pending request by identifier
|
|
2085
|
-
request
|
|
2086
|
-
if request is None:
|
|
2650
|
+
if not (request := self.le_coc_requests.pop(response.identifier, None)):
|
|
2087
2651
|
logger.warning(color('!!! received response for unknown request', 'red'))
|
|
2088
2652
|
return
|
|
2089
|
-
del self.le_coc_requests[response.identifier]
|
|
2090
2653
|
|
|
2091
2654
|
# Find the channel for this request
|
|
2092
2655
|
channel = self.find_channel(connection.handle, request.source_cid)
|
|
@@ -2103,6 +2666,147 @@ class ChannelManager:
|
|
|
2103
2666
|
# Process the response
|
|
2104
2667
|
channel.on_connection_response(response)
|
|
2105
2668
|
|
|
2669
|
+
def on_l2cap_credit_based_connection_request(
|
|
2670
|
+
self,
|
|
2671
|
+
connection: Connection,
|
|
2672
|
+
cid: int,
|
|
2673
|
+
request: L2CAP_Credit_Based_Connection_Request,
|
|
2674
|
+
) -> None:
|
|
2675
|
+
if not (server := self.le_coc_servers.get(request.spsm)):
|
|
2676
|
+
logger.info(
|
|
2677
|
+
'No LE server for connection 0x%04X ' 'on PSM %d',
|
|
2678
|
+
connection.handle,
|
|
2679
|
+
request.spsm,
|
|
2680
|
+
)
|
|
2681
|
+
self.send_control_frame(
|
|
2682
|
+
connection,
|
|
2683
|
+
cid,
|
|
2684
|
+
L2CAP_Credit_Based_Connection_Response(
|
|
2685
|
+
identifier=request.identifier,
|
|
2686
|
+
destination_cid=[],
|
|
2687
|
+
mtu=L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_MTU,
|
|
2688
|
+
mps=L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_MPS,
|
|
2689
|
+
initial_credits=0,
|
|
2690
|
+
result=L2CAP_Credit_Based_Connection_Response.Result.ALL_CONNECTIONS_REFUSED_SPSM_NOT_SUPPORTED,
|
|
2691
|
+
),
|
|
2692
|
+
)
|
|
2693
|
+
return
|
|
2694
|
+
|
|
2695
|
+
# Check that the CID isn't already used
|
|
2696
|
+
le_connection_channels = self.le_coc_channels.setdefault(connection.handle, {})
|
|
2697
|
+
if cid_in_use := set(request.source_cid).intersection(
|
|
2698
|
+
set(le_connection_channels)
|
|
2699
|
+
):
|
|
2700
|
+
logger.warning('source CID already in use: %s', cid_in_use)
|
|
2701
|
+
self.send_control_frame(
|
|
2702
|
+
connection,
|
|
2703
|
+
cid,
|
|
2704
|
+
L2CAP_Credit_Based_Connection_Response(
|
|
2705
|
+
identifier=request.identifier,
|
|
2706
|
+
mtu=server.mtu,
|
|
2707
|
+
mps=server.mps,
|
|
2708
|
+
initial_credits=0,
|
|
2709
|
+
result=L2CAP_Credit_Based_Connection_Response.Result.SOME_CONNECTIONS_REFUSED_SOURCE_CID_ALREADY_ALLOCATED,
|
|
2710
|
+
destination_cid=[],
|
|
2711
|
+
),
|
|
2712
|
+
)
|
|
2713
|
+
return
|
|
2714
|
+
|
|
2715
|
+
# Find free CIDs for new channels
|
|
2716
|
+
connection_channels = self.channels.setdefault(connection.handle, {})
|
|
2717
|
+
source_cids = self.find_free_le_cids(
|
|
2718
|
+
connection_channels, len(request.source_cid)
|
|
2719
|
+
)
|
|
2720
|
+
if not source_cids:
|
|
2721
|
+
self.send_control_frame(
|
|
2722
|
+
connection,
|
|
2723
|
+
cid,
|
|
2724
|
+
L2CAP_Credit_Based_Connection_Response(
|
|
2725
|
+
identifier=request.identifier,
|
|
2726
|
+
destination_cid=[],
|
|
2727
|
+
mtu=server.mtu,
|
|
2728
|
+
mps=server.mps,
|
|
2729
|
+
initial_credits=server.max_credits,
|
|
2730
|
+
result=L2CAP_Credit_Based_Connection_Response.Result.SOME_CONNECTIONS_REFUSED_INSUFFICIENT_RESOURCES_AVAILABLE,
|
|
2731
|
+
),
|
|
2732
|
+
)
|
|
2733
|
+
return
|
|
2734
|
+
|
|
2735
|
+
for destination_cid in request.source_cid:
|
|
2736
|
+
# TODO: Handle Classic channels.
|
|
2737
|
+
if not (source_cid := self.find_free_le_cid(connection_channels)):
|
|
2738
|
+
logger.warning("No free CIDs available")
|
|
2739
|
+
break
|
|
2740
|
+
# Create a new channel
|
|
2741
|
+
logger.debug(
|
|
2742
|
+
'creating LE CoC server channel with cid=%s for psm %s',
|
|
2743
|
+
source_cid,
|
|
2744
|
+
request.spsm,
|
|
2745
|
+
)
|
|
2746
|
+
channel = LeCreditBasedChannel(
|
|
2747
|
+
self,
|
|
2748
|
+
connection,
|
|
2749
|
+
request.spsm,
|
|
2750
|
+
source_cid,
|
|
2751
|
+
destination_cid,
|
|
2752
|
+
server.mtu,
|
|
2753
|
+
server.mps,
|
|
2754
|
+
request.initial_credits,
|
|
2755
|
+
request.mtu,
|
|
2756
|
+
request.mps,
|
|
2757
|
+
server.max_credits,
|
|
2758
|
+
True,
|
|
2759
|
+
)
|
|
2760
|
+
connection_channels[source_cid] = channel
|
|
2761
|
+
le_connection_channels[source_cid] = channel
|
|
2762
|
+
server.on_connection(channel)
|
|
2763
|
+
|
|
2764
|
+
# Respond
|
|
2765
|
+
self.send_control_frame(
|
|
2766
|
+
connection,
|
|
2767
|
+
cid,
|
|
2768
|
+
L2CAP_Credit_Based_Connection_Response(
|
|
2769
|
+
identifier=request.identifier,
|
|
2770
|
+
destination_cid=source_cids,
|
|
2771
|
+
mtu=server.mtu,
|
|
2772
|
+
mps=server.mps,
|
|
2773
|
+
initial_credits=server.max_credits,
|
|
2774
|
+
result=L2CAP_Credit_Based_Connection_Response.Result.ALL_CONNECTIONS_SUCCESSFUL,
|
|
2775
|
+
),
|
|
2776
|
+
)
|
|
2777
|
+
|
|
2778
|
+
def on_l2cap_credit_based_connection_response(
|
|
2779
|
+
self,
|
|
2780
|
+
connection: Connection,
|
|
2781
|
+
_cid: int,
|
|
2782
|
+
response: L2CAP_Credit_Based_Connection_Response,
|
|
2783
|
+
) -> None:
|
|
2784
|
+
# Find the pending request by identifier
|
|
2785
|
+
pending_connections = self.pending_credit_based_connections.setdefault(
|
|
2786
|
+
connection.handle, {}
|
|
2787
|
+
)
|
|
2788
|
+
if not (
|
|
2789
|
+
pending_connection := pending_connections.pop(response.identifier, None)
|
|
2790
|
+
):
|
|
2791
|
+
logger.warning(color('!!! received response for unknown request', 'red'))
|
|
2792
|
+
return
|
|
2793
|
+
|
|
2794
|
+
connection_result, channels = pending_connection
|
|
2795
|
+
|
|
2796
|
+
# Process the response
|
|
2797
|
+
for channel, destination_cid in zip(channels, response.destination_cid):
|
|
2798
|
+
channel.on_enhanced_connection_response(destination_cid, response)
|
|
2799
|
+
|
|
2800
|
+
if (
|
|
2801
|
+
response.result
|
|
2802
|
+
== L2CAP_Credit_Based_Connection_Response.Result.ALL_CONNECTIONS_SUCCESSFUL
|
|
2803
|
+
):
|
|
2804
|
+
connection_result.set_result(None)
|
|
2805
|
+
else:
|
|
2806
|
+
connection_result.set_exception(
|
|
2807
|
+
L2capError(response.result, response.result.name)
|
|
2808
|
+
)
|
|
2809
|
+
|
|
2106
2810
|
def on_l2cap_le_flow_control_credit(
|
|
2107
2811
|
self, connection: Connection, _cid: int, credit: L2CAP_LE_Flow_Control_Credit
|
|
2108
2812
|
) -> None:
|
|
@@ -2138,7 +2842,7 @@ class ChannelManager:
|
|
|
2138
2842
|
channel = LeCreditBasedChannel(
|
|
2139
2843
|
manager=self,
|
|
2140
2844
|
connection=connection,
|
|
2141
|
-
|
|
2845
|
+
psm=spec.psm,
|
|
2142
2846
|
source_cid=source_cid,
|
|
2143
2847
|
destination_cid=0,
|
|
2144
2848
|
mtu=spec.mtu,
|
|
@@ -2184,12 +2888,12 @@ class ChannelManager:
|
|
|
2184
2888
|
f'creating client channel with cid={source_cid} for psm {spec.psm}'
|
|
2185
2889
|
)
|
|
2186
2890
|
channel = ClassicChannel(
|
|
2187
|
-
self,
|
|
2188
|
-
connection,
|
|
2189
|
-
L2CAP_SIGNALING_CID,
|
|
2190
|
-
spec.psm,
|
|
2191
|
-
source_cid,
|
|
2192
|
-
spec
|
|
2891
|
+
manager=self,
|
|
2892
|
+
connection=connection,
|
|
2893
|
+
signaling_cid=L2CAP_SIGNALING_CID,
|
|
2894
|
+
psm=spec.psm,
|
|
2895
|
+
source_cid=source_cid,
|
|
2896
|
+
spec=spec,
|
|
2193
2897
|
)
|
|
2194
2898
|
connection_channels[source_cid] = channel
|
|
2195
2899
|
|
|
@@ -2197,7 +2901,100 @@ class ChannelManager:
|
|
|
2197
2901
|
try:
|
|
2198
2902
|
await channel.connect()
|
|
2199
2903
|
except BaseException as e:
|
|
2200
|
-
|
|
2904
|
+
connection_channels.pop(source_cid, None)
|
|
2201
2905
|
raise e
|
|
2202
2906
|
|
|
2203
2907
|
return channel
|
|
2908
|
+
|
|
2909
|
+
async def create_enhanced_credit_based_channels(
|
|
2910
|
+
self,
|
|
2911
|
+
connection: Connection,
|
|
2912
|
+
spec: LeCreditBasedChannelSpec,
|
|
2913
|
+
count: int,
|
|
2914
|
+
) -> list[LeCreditBasedChannel]:
|
|
2915
|
+
# Find a free CID for the new channel
|
|
2916
|
+
connection_channels = self.channels.setdefault(connection.handle, {})
|
|
2917
|
+
source_cids = self.find_free_le_cids(connection_channels, count)
|
|
2918
|
+
if not source_cids: # Should never happen!
|
|
2919
|
+
raise OutOfResourcesError('all CIDs already in use')
|
|
2920
|
+
|
|
2921
|
+
if spec.psm is None:
|
|
2922
|
+
raise InvalidArgumentError('PSM cannot be None')
|
|
2923
|
+
|
|
2924
|
+
# Create the channel
|
|
2925
|
+
logger.debug(
|
|
2926
|
+
'creating coc channel with cid=%s for psm %s', source_cids, spec.psm
|
|
2927
|
+
)
|
|
2928
|
+
channels: list[LeCreditBasedChannel] = []
|
|
2929
|
+
for source_cid in source_cids:
|
|
2930
|
+
channel = LeCreditBasedChannel(
|
|
2931
|
+
manager=self,
|
|
2932
|
+
connection=connection,
|
|
2933
|
+
psm=spec.psm,
|
|
2934
|
+
source_cid=source_cid,
|
|
2935
|
+
destination_cid=0,
|
|
2936
|
+
mtu=spec.mtu,
|
|
2937
|
+
mps=spec.mps,
|
|
2938
|
+
credits=0,
|
|
2939
|
+
peer_mtu=0,
|
|
2940
|
+
peer_mps=0,
|
|
2941
|
+
peer_credits=spec.max_credits,
|
|
2942
|
+
connected=False,
|
|
2943
|
+
)
|
|
2944
|
+
connection_channels[source_cid] = channel
|
|
2945
|
+
channels.append(channel)
|
|
2946
|
+
|
|
2947
|
+
identifier = self.next_identifier(connection)
|
|
2948
|
+
request = L2CAP_Credit_Based_Connection_Request(
|
|
2949
|
+
identifier=identifier,
|
|
2950
|
+
spsm=spec.psm,
|
|
2951
|
+
mtu=spec.mtu,
|
|
2952
|
+
mps=spec.mps,
|
|
2953
|
+
initial_credits=spec.max_credits,
|
|
2954
|
+
source_cid=source_cids,
|
|
2955
|
+
)
|
|
2956
|
+
connection_result = asyncio.get_running_loop().create_future()
|
|
2957
|
+
pending_connections = self.pending_credit_based_connections.setdefault(
|
|
2958
|
+
connection.handle, {}
|
|
2959
|
+
)
|
|
2960
|
+
pending_connections[identifier] = (connection_result, channels)
|
|
2961
|
+
self.send_control_frame(
|
|
2962
|
+
connection,
|
|
2963
|
+
L2CAP_LE_SIGNALING_CID,
|
|
2964
|
+
request,
|
|
2965
|
+
)
|
|
2966
|
+
# Connect
|
|
2967
|
+
try:
|
|
2968
|
+
await connection_result
|
|
2969
|
+
except Exception:
|
|
2970
|
+
logger.exception('connection failed')
|
|
2971
|
+
for cid in source_cids:
|
|
2972
|
+
del connection_channels[cid]
|
|
2973
|
+
raise
|
|
2974
|
+
|
|
2975
|
+
# Remember the channel by source CID and destination CID
|
|
2976
|
+
le_connection_channels = self.le_coc_channels.setdefault(connection.handle, {})
|
|
2977
|
+
for channel in channels:
|
|
2978
|
+
le_connection_channels[channel.destination_cid] = channel
|
|
2979
|
+
|
|
2980
|
+
return channels
|
|
2981
|
+
|
|
2982
|
+
@classmethod
|
|
2983
|
+
def make_mode_processor(
|
|
2984
|
+
self,
|
|
2985
|
+
channel: ClassicChannel,
|
|
2986
|
+
mode: TransmissionMode,
|
|
2987
|
+
peer_tx_window_size: int,
|
|
2988
|
+
peer_max_retransmission: int,
|
|
2989
|
+
peer_retransmission_timeout: int,
|
|
2990
|
+
peer_monitor_timeout: int,
|
|
2991
|
+
peer_mps: int,
|
|
2992
|
+
) -> Processor:
|
|
2993
|
+
del peer_retransmission_timeout, peer_monitor_timeout # Unused.
|
|
2994
|
+
if mode == TransmissionMode.BASIC:
|
|
2995
|
+
return Processor(channel)
|
|
2996
|
+
elif mode == TransmissionMode.ENHANCED_RETRANSMISSION:
|
|
2997
|
+
return EnhancedRetransmissionProcessor(
|
|
2998
|
+
channel, peer_tx_window_size, peer_max_retransmission, peer_mps
|
|
2999
|
+
)
|
|
3000
|
+
raise InvalidArgumentError("Mode %s is not implemented", mode.name)
|