bumble 0.0.160__py3-none-any.whl → 0.0.161__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/controller.py +73 -55
- bumble/core.py +74 -3
- bumble/gatt.py +23 -22
- bumble/l2cap.py +210 -122
- bumble/smp.py +93 -66
- {bumble-0.0.160.dist-info → bumble-0.0.161.dist-info}/METADATA +1 -1
- {bumble-0.0.160.dist-info → bumble-0.0.161.dist-info}/RECORD +12 -12
- {bumble-0.0.160.dist-info → bumble-0.0.161.dist-info}/WHEEL +1 -1
- {bumble-0.0.160.dist-info → bumble-0.0.161.dist-info}/LICENSE +0 -0
- {bumble-0.0.160.dist-info → bumble-0.0.161.dist-info}/entry_points.txt +0 -0
- {bumble-0.0.160.dist-info → bumble-0.0.161.dist-info}/top_level.txt +0 -0
bumble/l2cap.py
CHANGED
|
@@ -22,7 +22,19 @@ import struct
|
|
|
22
22
|
|
|
23
23
|
from collections import deque
|
|
24
24
|
from pyee import EventEmitter
|
|
25
|
-
from typing import
|
|
25
|
+
from typing import (
|
|
26
|
+
Dict,
|
|
27
|
+
Type,
|
|
28
|
+
List,
|
|
29
|
+
Optional,
|
|
30
|
+
Tuple,
|
|
31
|
+
Callable,
|
|
32
|
+
Any,
|
|
33
|
+
Union,
|
|
34
|
+
Deque,
|
|
35
|
+
Iterable,
|
|
36
|
+
TYPE_CHECKING,
|
|
37
|
+
)
|
|
26
38
|
|
|
27
39
|
from .colors import color
|
|
28
40
|
from .core import BT_CENTRAL_ROLE, InvalidStateError, ProtocolError
|
|
@@ -33,6 +45,9 @@ from .hci import (
|
|
|
33
45
|
name_or_number,
|
|
34
46
|
)
|
|
35
47
|
|
|
48
|
+
if TYPE_CHECKING:
|
|
49
|
+
from bumble.device import Connection
|
|
50
|
+
|
|
36
51
|
# -----------------------------------------------------------------------------
|
|
37
52
|
# Logging
|
|
38
53
|
# -----------------------------------------------------------------------------
|
|
@@ -155,7 +170,7 @@ class L2CAP_PDU:
|
|
|
155
170
|
'''
|
|
156
171
|
|
|
157
172
|
@staticmethod
|
|
158
|
-
def from_bytes(data):
|
|
173
|
+
def from_bytes(data: bytes) -> L2CAP_PDU:
|
|
159
174
|
# Sanity check
|
|
160
175
|
if len(data) < 4:
|
|
161
176
|
raise ValueError('not enough data for L2CAP header')
|
|
@@ -165,18 +180,18 @@ class L2CAP_PDU:
|
|
|
165
180
|
|
|
166
181
|
return L2CAP_PDU(l2cap_pdu_cid, l2cap_pdu_payload)
|
|
167
182
|
|
|
168
|
-
def to_bytes(self):
|
|
183
|
+
def to_bytes(self) -> bytes:
|
|
169
184
|
header = struct.pack('<HH', len(self.payload), self.cid)
|
|
170
185
|
return header + self.payload
|
|
171
186
|
|
|
172
|
-
def __init__(self, cid, payload):
|
|
187
|
+
def __init__(self, cid: int, payload: bytes) -> None:
|
|
173
188
|
self.cid = cid
|
|
174
189
|
self.payload = payload
|
|
175
190
|
|
|
176
|
-
def __bytes__(self):
|
|
191
|
+
def __bytes__(self) -> bytes:
|
|
177
192
|
return self.to_bytes()
|
|
178
193
|
|
|
179
|
-
def __str__(self):
|
|
194
|
+
def __str__(self) -> str:
|
|
180
195
|
return f'{color("L2CAP", "green")} [CID={self.cid}]: {self.payload.hex()}'
|
|
181
196
|
|
|
182
197
|
|
|
@@ -188,10 +203,10 @@ class L2CAP_Control_Frame:
|
|
|
188
203
|
|
|
189
204
|
classes: Dict[int, Type[L2CAP_Control_Frame]] = {}
|
|
190
205
|
code = 0
|
|
191
|
-
name
|
|
206
|
+
name: str
|
|
192
207
|
|
|
193
208
|
@staticmethod
|
|
194
|
-
def from_bytes(pdu):
|
|
209
|
+
def from_bytes(pdu: bytes) -> L2CAP_Control_Frame:
|
|
195
210
|
code = pdu[0]
|
|
196
211
|
|
|
197
212
|
cls = L2CAP_Control_Frame.classes.get(code)
|
|
@@ -216,11 +231,11 @@ class L2CAP_Control_Frame:
|
|
|
216
231
|
return self
|
|
217
232
|
|
|
218
233
|
@staticmethod
|
|
219
|
-
def code_name(code):
|
|
234
|
+
def code_name(code: int) -> str:
|
|
220
235
|
return name_or_number(L2CAP_CONTROL_FRAME_NAMES, code)
|
|
221
236
|
|
|
222
237
|
@staticmethod
|
|
223
|
-
def decode_configuration_options(data):
|
|
238
|
+
def decode_configuration_options(data: bytes) -> List[Tuple[int, bytes]]:
|
|
224
239
|
options = []
|
|
225
240
|
while len(data) >= 2:
|
|
226
241
|
value_type = data[0]
|
|
@@ -232,7 +247,7 @@ class L2CAP_Control_Frame:
|
|
|
232
247
|
return options
|
|
233
248
|
|
|
234
249
|
@staticmethod
|
|
235
|
-
def encode_configuration_options(options):
|
|
250
|
+
def encode_configuration_options(options: List[Tuple[int, bytes]]) -> bytes:
|
|
236
251
|
return b''.join(
|
|
237
252
|
[bytes([option[0], len(option[1])]) + option[1] for option in options]
|
|
238
253
|
)
|
|
@@ -256,29 +271,30 @@ class L2CAP_Control_Frame:
|
|
|
256
271
|
|
|
257
272
|
return inner
|
|
258
273
|
|
|
259
|
-
def __init__(self, pdu=None, **kwargs):
|
|
274
|
+
def __init__(self, pdu=None, **kwargs) -> None:
|
|
260
275
|
self.identifier = kwargs.get('identifier', 0)
|
|
261
|
-
if hasattr(self, 'fields')
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
276
|
+
if hasattr(self, 'fields'):
|
|
277
|
+
if kwargs:
|
|
278
|
+
HCI_Object.init_from_fields(self, self.fields, kwargs)
|
|
279
|
+
if pdu is None:
|
|
280
|
+
data = HCI_Object.dict_to_bytes(kwargs, self.fields)
|
|
281
|
+
pdu = (
|
|
282
|
+
bytes([self.code, self.identifier])
|
|
283
|
+
+ struct.pack('<H', len(data))
|
|
284
|
+
+ data
|
|
285
|
+
)
|
|
270
286
|
self.pdu = pdu
|
|
271
287
|
|
|
272
288
|
def init_from_bytes(self, pdu, offset):
|
|
273
289
|
return HCI_Object.init_from_bytes(self, pdu, offset, self.fields)
|
|
274
290
|
|
|
275
|
-
def to_bytes(self):
|
|
291
|
+
def to_bytes(self) -> bytes:
|
|
276
292
|
return self.pdu
|
|
277
293
|
|
|
278
|
-
def __bytes__(self):
|
|
294
|
+
def __bytes__(self) -> bytes:
|
|
279
295
|
return self.to_bytes()
|
|
280
296
|
|
|
281
|
-
def __str__(self):
|
|
297
|
+
def __str__(self) -> str:
|
|
282
298
|
result = f'{color(self.name, "yellow")} [ID={self.identifier}]'
|
|
283
299
|
if fields := getattr(self, 'fields', None):
|
|
284
300
|
result += ':\n' + HCI_Object.format_fields(self.__dict__, fields, ' ')
|
|
@@ -315,7 +331,7 @@ class L2CAP_Command_Reject(L2CAP_Control_Frame):
|
|
|
315
331
|
}
|
|
316
332
|
|
|
317
333
|
@staticmethod
|
|
318
|
-
def reason_name(reason):
|
|
334
|
+
def reason_name(reason: int) -> str:
|
|
319
335
|
return name_or_number(L2CAP_Command_Reject.REASON_NAMES, reason)
|
|
320
336
|
|
|
321
337
|
|
|
@@ -343,7 +359,7 @@ class L2CAP_Connection_Request(L2CAP_Control_Frame):
|
|
|
343
359
|
'''
|
|
344
360
|
|
|
345
361
|
@staticmethod
|
|
346
|
-
def parse_psm(data, offset=0):
|
|
362
|
+
def parse_psm(data: bytes, offset: int = 0) -> Tuple[int, int]:
|
|
347
363
|
psm_length = 2
|
|
348
364
|
psm = data[offset] | data[offset + 1] << 8
|
|
349
365
|
|
|
@@ -355,7 +371,7 @@ class L2CAP_Connection_Request(L2CAP_Control_Frame):
|
|
|
355
371
|
return offset + psm_length, psm
|
|
356
372
|
|
|
357
373
|
@staticmethod
|
|
358
|
-
def serialize_psm(psm):
|
|
374
|
+
def serialize_psm(psm: int) -> bytes:
|
|
359
375
|
serialized = struct.pack('<H', psm & 0xFFFF)
|
|
360
376
|
psm >>= 16
|
|
361
377
|
while psm:
|
|
@@ -405,7 +421,7 @@ class L2CAP_Connection_Response(L2CAP_Control_Frame):
|
|
|
405
421
|
}
|
|
406
422
|
|
|
407
423
|
@staticmethod
|
|
408
|
-
def result_name(result):
|
|
424
|
+
def result_name(result: int) -> str:
|
|
409
425
|
return name_or_number(L2CAP_Connection_Response.RESULT_NAMES, result)
|
|
410
426
|
|
|
411
427
|
|
|
@@ -452,7 +468,7 @@ class L2CAP_Configure_Response(L2CAP_Control_Frame):
|
|
|
452
468
|
}
|
|
453
469
|
|
|
454
470
|
@staticmethod
|
|
455
|
-
def result_name(result):
|
|
471
|
+
def result_name(result: int) -> str:
|
|
456
472
|
return name_or_number(L2CAP_Configure_Response.RESULT_NAMES, result)
|
|
457
473
|
|
|
458
474
|
|
|
@@ -529,7 +545,7 @@ class L2CAP_Information_Request(L2CAP_Control_Frame):
|
|
|
529
545
|
}
|
|
530
546
|
|
|
531
547
|
@staticmethod
|
|
532
|
-
def info_type_name(info_type):
|
|
548
|
+
def info_type_name(info_type: int) -> str:
|
|
533
549
|
return name_or_number(L2CAP_Information_Request.INFO_TYPE_NAMES, info_type)
|
|
534
550
|
|
|
535
551
|
|
|
@@ -556,7 +572,7 @@ class L2CAP_Information_Response(L2CAP_Control_Frame):
|
|
|
556
572
|
RESULT_NAMES = {SUCCESS: 'SUCCESS', NOT_SUPPORTED: 'NOT_SUPPORTED'}
|
|
557
573
|
|
|
558
574
|
@staticmethod
|
|
559
|
-
def result_name(result):
|
|
575
|
+
def result_name(result: int) -> str:
|
|
560
576
|
return name_or_number(L2CAP_Information_Response.RESULT_NAMES, result)
|
|
561
577
|
|
|
562
578
|
|
|
@@ -588,6 +604,8 @@ class L2CAP_LE_Credit_Based_Connection_Request(L2CAP_Control_Frame):
|
|
|
588
604
|
(CODE 0x14)
|
|
589
605
|
'''
|
|
590
606
|
|
|
607
|
+
source_cid: int
|
|
608
|
+
|
|
591
609
|
|
|
592
610
|
# -----------------------------------------------------------------------------
|
|
593
611
|
@L2CAP_Control_Frame.subclass(
|
|
@@ -640,7 +658,7 @@ class L2CAP_LE_Credit_Based_Connection_Response(L2CAP_Control_Frame):
|
|
|
640
658
|
}
|
|
641
659
|
|
|
642
660
|
@staticmethod
|
|
643
|
-
def result_name(result):
|
|
661
|
+
def result_name(result: int) -> str:
|
|
644
662
|
return name_or_number(
|
|
645
663
|
L2CAP_LE_Credit_Based_Connection_Response.RESULT_NAMES, result
|
|
646
664
|
)
|
|
@@ -701,7 +719,22 @@ class Channel(EventEmitter):
|
|
|
701
719
|
WAIT_CONTROL_IND: 'WAIT_CONTROL_IND',
|
|
702
720
|
}
|
|
703
721
|
|
|
704
|
-
|
|
722
|
+
connection_result: Optional[asyncio.Future[None]]
|
|
723
|
+
disconnection_result: Optional[asyncio.Future[None]]
|
|
724
|
+
response: Optional[asyncio.Future[bytes]]
|
|
725
|
+
sink: Optional[Callable[[bytes], Any]]
|
|
726
|
+
state: int
|
|
727
|
+
connection: Connection
|
|
728
|
+
|
|
729
|
+
def __init__(
|
|
730
|
+
self,
|
|
731
|
+
manager: 'ChannelManager',
|
|
732
|
+
connection: Connection,
|
|
733
|
+
signaling_cid: int,
|
|
734
|
+
psm: int,
|
|
735
|
+
source_cid: int,
|
|
736
|
+
mtu: int,
|
|
737
|
+
) -> None:
|
|
705
738
|
super().__init__()
|
|
706
739
|
self.manager = manager
|
|
707
740
|
self.connection = connection
|
|
@@ -716,19 +749,19 @@ class Channel(EventEmitter):
|
|
|
716
749
|
self.disconnection_result = None
|
|
717
750
|
self.sink = None
|
|
718
751
|
|
|
719
|
-
def change_state(self, new_state):
|
|
752
|
+
def change_state(self, new_state: int) -> None:
|
|
720
753
|
logger.debug(
|
|
721
754
|
f'{self} state change -> {color(Channel.STATE_NAMES[new_state], "cyan")}'
|
|
722
755
|
)
|
|
723
756
|
self.state = new_state
|
|
724
757
|
|
|
725
|
-
def send_pdu(self, pdu):
|
|
758
|
+
def send_pdu(self, pdu) -> None:
|
|
726
759
|
self.manager.send_pdu(self.connection, self.destination_cid, pdu)
|
|
727
760
|
|
|
728
|
-
def send_control_frame(self, frame):
|
|
761
|
+
def send_control_frame(self, frame) -> None:
|
|
729
762
|
self.manager.send_control_frame(self.connection, self.signaling_cid, frame)
|
|
730
763
|
|
|
731
|
-
async def send_request(self, request):
|
|
764
|
+
async def send_request(self, request) -> bytes:
|
|
732
765
|
# Check that there isn't already a request pending
|
|
733
766
|
if self.response:
|
|
734
767
|
raise InvalidStateError('request already pending')
|
|
@@ -739,7 +772,7 @@ class Channel(EventEmitter):
|
|
|
739
772
|
self.send_pdu(request)
|
|
740
773
|
return await self.response
|
|
741
774
|
|
|
742
|
-
def on_pdu(self, pdu):
|
|
775
|
+
def on_pdu(self, pdu) -> None:
|
|
743
776
|
if self.response:
|
|
744
777
|
self.response.set_result(pdu)
|
|
745
778
|
self.response = None
|
|
@@ -751,7 +784,7 @@ class Channel(EventEmitter):
|
|
|
751
784
|
color('received pdu without a pending request or sink', 'red')
|
|
752
785
|
)
|
|
753
786
|
|
|
754
|
-
async def connect(self):
|
|
787
|
+
async def connect(self) -> None:
|
|
755
788
|
if self.state != Channel.CLOSED:
|
|
756
789
|
raise InvalidStateError('invalid state')
|
|
757
790
|
|
|
@@ -778,7 +811,7 @@ class Channel(EventEmitter):
|
|
|
778
811
|
finally:
|
|
779
812
|
self.connection_result = None
|
|
780
813
|
|
|
781
|
-
async def disconnect(self):
|
|
814
|
+
async def disconnect(self) -> None:
|
|
782
815
|
if self.state != Channel.OPEN:
|
|
783
816
|
raise InvalidStateError('invalid state')
|
|
784
817
|
|
|
@@ -796,12 +829,12 @@ class Channel(EventEmitter):
|
|
|
796
829
|
self.disconnection_result = asyncio.get_running_loop().create_future()
|
|
797
830
|
return await self.disconnection_result
|
|
798
831
|
|
|
799
|
-
def abort(self):
|
|
832
|
+
def abort(self) -> None:
|
|
800
833
|
if self.state == self.OPEN:
|
|
801
834
|
self.change_state(self.CLOSED)
|
|
802
835
|
self.emit('close')
|
|
803
836
|
|
|
804
|
-
def send_configure_request(self):
|
|
837
|
+
def send_configure_request(self) -> None:
|
|
805
838
|
options = L2CAP_Control_Frame.encode_configuration_options(
|
|
806
839
|
[
|
|
807
840
|
(
|
|
@@ -819,7 +852,7 @@ class Channel(EventEmitter):
|
|
|
819
852
|
)
|
|
820
853
|
)
|
|
821
854
|
|
|
822
|
-
def on_connection_request(self, request):
|
|
855
|
+
def on_connection_request(self, request) -> None:
|
|
823
856
|
self.destination_cid = request.source_cid
|
|
824
857
|
self.change_state(Channel.WAIT_CONNECT)
|
|
825
858
|
self.send_control_frame(
|
|
@@ -858,7 +891,7 @@ class Channel(EventEmitter):
|
|
|
858
891
|
)
|
|
859
892
|
self.connection_result = None
|
|
860
893
|
|
|
861
|
-
def on_configure_request(self, request):
|
|
894
|
+
def on_configure_request(self, request) -> None:
|
|
862
895
|
if self.state not in (
|
|
863
896
|
Channel.WAIT_CONFIG,
|
|
864
897
|
Channel.WAIT_CONFIG_REQ,
|
|
@@ -896,7 +929,7 @@ class Channel(EventEmitter):
|
|
|
896
929
|
elif self.state == Channel.WAIT_CONFIG_REQ_RSP:
|
|
897
930
|
self.change_state(Channel.WAIT_CONFIG_RSP)
|
|
898
931
|
|
|
899
|
-
def on_configure_response(self, response):
|
|
932
|
+
def on_configure_response(self, response) -> None:
|
|
900
933
|
if response.result == L2CAP_Configure_Response.SUCCESS:
|
|
901
934
|
if self.state == Channel.WAIT_CONFIG_REQ_RSP:
|
|
902
935
|
self.change_state(Channel.WAIT_CONFIG_REQ)
|
|
@@ -930,7 +963,7 @@ class Channel(EventEmitter):
|
|
|
930
963
|
)
|
|
931
964
|
# TODO: decide how to fail gracefully
|
|
932
965
|
|
|
933
|
-
def on_disconnection_request(self, request):
|
|
966
|
+
def on_disconnection_request(self, request) -> None:
|
|
934
967
|
if self.state in (Channel.OPEN, Channel.WAIT_DISCONNECT):
|
|
935
968
|
self.send_control_frame(
|
|
936
969
|
L2CAP_Disconnection_Response(
|
|
@@ -945,7 +978,7 @@ class Channel(EventEmitter):
|
|
|
945
978
|
else:
|
|
946
979
|
logger.warning(color('invalid state', 'red'))
|
|
947
980
|
|
|
948
|
-
def on_disconnection_response(self, response):
|
|
981
|
+
def on_disconnection_response(self, response) -> None:
|
|
949
982
|
if self.state != Channel.WAIT_DISCONNECT:
|
|
950
983
|
logger.warning(color('invalid state', 'red'))
|
|
951
984
|
return
|
|
@@ -964,7 +997,7 @@ class Channel(EventEmitter):
|
|
|
964
997
|
self.emit('close')
|
|
965
998
|
self.manager.on_channel_closed(self)
|
|
966
999
|
|
|
967
|
-
def __str__(self):
|
|
1000
|
+
def __str__(self) -> str:
|
|
968
1001
|
return (
|
|
969
1002
|
f'Channel({self.source_cid}->{self.destination_cid}, '
|
|
970
1003
|
f'PSM={self.psm}, '
|
|
@@ -995,25 +1028,32 @@ class LeConnectionOrientedChannel(EventEmitter):
|
|
|
995
1028
|
CONNECTION_ERROR: 'CONNECTION_ERROR',
|
|
996
1029
|
}
|
|
997
1030
|
|
|
1031
|
+
out_queue: Deque[bytes]
|
|
1032
|
+
connection_result: Optional[asyncio.Future[LeConnectionOrientedChannel]]
|
|
1033
|
+
disconnection_result: Optional[asyncio.Future[None]]
|
|
1034
|
+
out_sdu: Optional[bytes]
|
|
1035
|
+
state: int
|
|
1036
|
+
connection: Connection
|
|
1037
|
+
|
|
998
1038
|
@staticmethod
|
|
999
|
-
def state_name(state):
|
|
1039
|
+
def state_name(state: int) -> str:
|
|
1000
1040
|
return name_or_number(LeConnectionOrientedChannel.STATE_NAMES, state)
|
|
1001
1041
|
|
|
1002
1042
|
def __init__(
|
|
1003
1043
|
self,
|
|
1004
|
-
manager,
|
|
1005
|
-
connection,
|
|
1006
|
-
le_psm,
|
|
1007
|
-
source_cid,
|
|
1008
|
-
destination_cid,
|
|
1009
|
-
mtu,
|
|
1010
|
-
mps,
|
|
1011
|
-
credits, # pylint: disable=redefined-builtin
|
|
1012
|
-
peer_mtu,
|
|
1013
|
-
peer_mps,
|
|
1014
|
-
peer_credits,
|
|
1015
|
-
connected,
|
|
1016
|
-
):
|
|
1044
|
+
manager: 'ChannelManager',
|
|
1045
|
+
connection: Connection,
|
|
1046
|
+
le_psm: int,
|
|
1047
|
+
source_cid: int,
|
|
1048
|
+
destination_cid: int,
|
|
1049
|
+
mtu: int,
|
|
1050
|
+
mps: int,
|
|
1051
|
+
credits: int, # pylint: disable=redefined-builtin
|
|
1052
|
+
peer_mtu: int,
|
|
1053
|
+
peer_mps: int,
|
|
1054
|
+
peer_credits: int,
|
|
1055
|
+
connected: bool,
|
|
1056
|
+
) -> None:
|
|
1017
1057
|
super().__init__()
|
|
1018
1058
|
self.manager = manager
|
|
1019
1059
|
self.connection = connection
|
|
@@ -1045,7 +1085,7 @@ class LeConnectionOrientedChannel(EventEmitter):
|
|
|
1045
1085
|
else:
|
|
1046
1086
|
self.state = LeConnectionOrientedChannel.INIT
|
|
1047
1087
|
|
|
1048
|
-
def change_state(self, new_state):
|
|
1088
|
+
def change_state(self, new_state: int) -> None:
|
|
1049
1089
|
logger.debug(
|
|
1050
1090
|
f'{self} state change -> {color(self.state_name(new_state), "cyan")}'
|
|
1051
1091
|
)
|
|
@@ -1056,13 +1096,13 @@ class LeConnectionOrientedChannel(EventEmitter):
|
|
|
1056
1096
|
elif new_state == self.DISCONNECTED:
|
|
1057
1097
|
self.emit('close')
|
|
1058
1098
|
|
|
1059
|
-
def send_pdu(self, pdu):
|
|
1099
|
+
def send_pdu(self, pdu) -> None:
|
|
1060
1100
|
self.manager.send_pdu(self.connection, self.destination_cid, pdu)
|
|
1061
1101
|
|
|
1062
|
-
def send_control_frame(self, frame):
|
|
1102
|
+
def send_control_frame(self, frame) -> None:
|
|
1063
1103
|
self.manager.send_control_frame(self.connection, L2CAP_LE_SIGNALING_CID, frame)
|
|
1064
1104
|
|
|
1065
|
-
async def connect(self):
|
|
1105
|
+
async def connect(self) -> LeConnectionOrientedChannel:
|
|
1066
1106
|
# Check that we're in the right state
|
|
1067
1107
|
if self.state != self.INIT:
|
|
1068
1108
|
raise InvalidStateError('not in a connectable state')
|
|
@@ -1090,7 +1130,7 @@ class LeConnectionOrientedChannel(EventEmitter):
|
|
|
1090
1130
|
# Wait for the connection to succeed or fail
|
|
1091
1131
|
return await self.connection_result
|
|
1092
1132
|
|
|
1093
|
-
async def disconnect(self):
|
|
1133
|
+
async def disconnect(self) -> None:
|
|
1094
1134
|
# Check that we're connected
|
|
1095
1135
|
if self.state != self.CONNECTED:
|
|
1096
1136
|
raise InvalidStateError('not connected')
|
|
@@ -1110,11 +1150,11 @@ class LeConnectionOrientedChannel(EventEmitter):
|
|
|
1110
1150
|
self.disconnection_result = asyncio.get_running_loop().create_future()
|
|
1111
1151
|
return await self.disconnection_result
|
|
1112
1152
|
|
|
1113
|
-
def abort(self):
|
|
1153
|
+
def abort(self) -> None:
|
|
1114
1154
|
if self.state == self.CONNECTED:
|
|
1115
1155
|
self.change_state(self.DISCONNECTED)
|
|
1116
1156
|
|
|
1117
|
-
def on_pdu(self, pdu):
|
|
1157
|
+
def on_pdu(self, pdu) -> None:
|
|
1118
1158
|
if self.sink is None:
|
|
1119
1159
|
logger.warning('received pdu without a sink')
|
|
1120
1160
|
return
|
|
@@ -1180,7 +1220,7 @@ class LeConnectionOrientedChannel(EventEmitter):
|
|
|
1180
1220
|
self.in_sdu = None
|
|
1181
1221
|
self.in_sdu_length = 0
|
|
1182
1222
|
|
|
1183
|
-
def on_connection_response(self, response):
|
|
1223
|
+
def on_connection_response(self, response) -> None:
|
|
1184
1224
|
# Look for a matching pending response result
|
|
1185
1225
|
if self.connection_result is None:
|
|
1186
1226
|
logger.warning(
|
|
@@ -1214,14 +1254,14 @@ class LeConnectionOrientedChannel(EventEmitter):
|
|
|
1214
1254
|
# Cleanup
|
|
1215
1255
|
self.connection_result = None
|
|
1216
1256
|
|
|
1217
|
-
def on_credits(self, credits): # pylint: disable=redefined-builtin
|
|
1257
|
+
def on_credits(self, credits: int) -> None: # pylint: disable=redefined-builtin
|
|
1218
1258
|
self.credits += credits
|
|
1219
1259
|
logger.debug(f'received {credits} credits, total = {self.credits}')
|
|
1220
1260
|
|
|
1221
1261
|
# Try to send more data if we have any queued up
|
|
1222
1262
|
self.process_output()
|
|
1223
1263
|
|
|
1224
|
-
def on_disconnection_request(self, request):
|
|
1264
|
+
def on_disconnection_request(self, request) -> None:
|
|
1225
1265
|
self.send_control_frame(
|
|
1226
1266
|
L2CAP_Disconnection_Response(
|
|
1227
1267
|
identifier=request.identifier,
|
|
@@ -1232,7 +1272,7 @@ class LeConnectionOrientedChannel(EventEmitter):
|
|
|
1232
1272
|
self.change_state(self.DISCONNECTED)
|
|
1233
1273
|
self.flush_output()
|
|
1234
1274
|
|
|
1235
|
-
def on_disconnection_response(self, response):
|
|
1275
|
+
def on_disconnection_response(self, response) -> None:
|
|
1236
1276
|
if self.state != self.DISCONNECTING:
|
|
1237
1277
|
logger.warning(color('invalid state', 'red'))
|
|
1238
1278
|
return
|
|
@@ -1249,11 +1289,11 @@ class LeConnectionOrientedChannel(EventEmitter):
|
|
|
1249
1289
|
self.disconnection_result.set_result(None)
|
|
1250
1290
|
self.disconnection_result = None
|
|
1251
1291
|
|
|
1252
|
-
def flush_output(self):
|
|
1292
|
+
def flush_output(self) -> None:
|
|
1253
1293
|
self.out_queue.clear()
|
|
1254
1294
|
self.out_sdu = None
|
|
1255
1295
|
|
|
1256
|
-
def process_output(self):
|
|
1296
|
+
def process_output(self) -> None:
|
|
1257
1297
|
while self.credits > 0:
|
|
1258
1298
|
if self.out_sdu is not None:
|
|
1259
1299
|
# Finish the current SDU
|
|
@@ -1296,7 +1336,7 @@ class LeConnectionOrientedChannel(EventEmitter):
|
|
|
1296
1336
|
self.drained.set()
|
|
1297
1337
|
return
|
|
1298
1338
|
|
|
1299
|
-
def write(self, data):
|
|
1339
|
+
def write(self, data: bytes) -> None:
|
|
1300
1340
|
if self.state != self.CONNECTED:
|
|
1301
1341
|
logger.warning('not connected, dropping data')
|
|
1302
1342
|
return
|
|
@@ -1311,18 +1351,18 @@ class LeConnectionOrientedChannel(EventEmitter):
|
|
|
1311
1351
|
# Send what we can
|
|
1312
1352
|
self.process_output()
|
|
1313
1353
|
|
|
1314
|
-
async def drain(self):
|
|
1354
|
+
async def drain(self) -> None:
|
|
1315
1355
|
await self.drained.wait()
|
|
1316
1356
|
|
|
1317
|
-
def pause_reading(self):
|
|
1357
|
+
def pause_reading(self) -> None:
|
|
1318
1358
|
# TODO: not implemented yet
|
|
1319
1359
|
pass
|
|
1320
1360
|
|
|
1321
|
-
def resume_reading(self):
|
|
1361
|
+
def resume_reading(self) -> None:
|
|
1322
1362
|
# TODO: not implemented yet
|
|
1323
1363
|
pass
|
|
1324
1364
|
|
|
1325
|
-
def __str__(self):
|
|
1365
|
+
def __str__(self) -> str:
|
|
1326
1366
|
return (
|
|
1327
1367
|
f'CoC({self.source_cid}->{self.destination_cid}, '
|
|
1328
1368
|
f'State={self.state_name(self.state)}, '
|
|
@@ -1335,9 +1375,21 @@ class LeConnectionOrientedChannel(EventEmitter):
|
|
|
1335
1375
|
|
|
1336
1376
|
# -----------------------------------------------------------------------------
|
|
1337
1377
|
class ChannelManager:
|
|
1378
|
+
identifiers: Dict[int, int]
|
|
1379
|
+
channels: Dict[int, Dict[int, Union[Channel, LeConnectionOrientedChannel]]]
|
|
1380
|
+
servers: Dict[int, Callable[[Channel], Any]]
|
|
1381
|
+
le_coc_channels: Dict[int, Dict[int, LeConnectionOrientedChannel]]
|
|
1382
|
+
le_coc_servers: Dict[
|
|
1383
|
+
int, Tuple[Callable[[LeConnectionOrientedChannel], Any], int, int, int]
|
|
1384
|
+
]
|
|
1385
|
+
le_coc_requests: Dict[int, L2CAP_LE_Credit_Based_Connection_Request]
|
|
1386
|
+
fixed_channels: Dict[int, Optional[Callable[[int, bytes], Any]]]
|
|
1387
|
+
|
|
1338
1388
|
def __init__(
|
|
1339
|
-
self,
|
|
1340
|
-
|
|
1389
|
+
self,
|
|
1390
|
+
extended_features: Iterable[int] = (),
|
|
1391
|
+
connectionless_mtu: int = L2CAP_DEFAULT_CONNECTIONLESS_MTU,
|
|
1392
|
+
) -> None:
|
|
1341
1393
|
self._host = None
|
|
1342
1394
|
self.identifiers = {} # Incrementing identifier values by connection
|
|
1343
1395
|
self.channels = {} # All channels, mapped by connection and source cid
|
|
@@ -1366,20 +1418,20 @@ class ChannelManager:
|
|
|
1366
1418
|
if host is not None:
|
|
1367
1419
|
host.on('disconnection', self.on_disconnection)
|
|
1368
1420
|
|
|
1369
|
-
def find_channel(self, connection_handle, cid):
|
|
1421
|
+
def find_channel(self, connection_handle: int, cid: int):
|
|
1370
1422
|
if connection_channels := self.channels.get(connection_handle):
|
|
1371
1423
|
return connection_channels.get(cid)
|
|
1372
1424
|
|
|
1373
1425
|
return None
|
|
1374
1426
|
|
|
1375
|
-
def find_le_coc_channel(self, connection_handle, cid):
|
|
1427
|
+
def find_le_coc_channel(self, connection_handle: int, cid: int):
|
|
1376
1428
|
if connection_channels := self.le_coc_channels.get(connection_handle):
|
|
1377
1429
|
return connection_channels.get(cid)
|
|
1378
1430
|
|
|
1379
1431
|
return None
|
|
1380
1432
|
|
|
1381
1433
|
@staticmethod
|
|
1382
|
-
def find_free_br_edr_cid(channels):
|
|
1434
|
+
def find_free_br_edr_cid(channels: Iterable[int]) -> int:
|
|
1383
1435
|
# Pick the smallest valid CID that's not already in the list
|
|
1384
1436
|
# (not necessarily the most efficient algorithm, but the list of CID is
|
|
1385
1437
|
# very small in practice)
|
|
@@ -1392,7 +1444,7 @@ class ChannelManager:
|
|
|
1392
1444
|
raise RuntimeError('no free CID available')
|
|
1393
1445
|
|
|
1394
1446
|
@staticmethod
|
|
1395
|
-
def find_free_le_cid(channels):
|
|
1447
|
+
def find_free_le_cid(channels: Iterable[int]) -> int:
|
|
1396
1448
|
# Pick the smallest valid CID that's not already in the list
|
|
1397
1449
|
# (not necessarily the most efficient algorithm, but the list of CID is
|
|
1398
1450
|
# very small in practice)
|
|
@@ -1405,7 +1457,7 @@ class ChannelManager:
|
|
|
1405
1457
|
raise RuntimeError('no free CID')
|
|
1406
1458
|
|
|
1407
1459
|
@staticmethod
|
|
1408
|
-
def check_le_coc_parameters(max_credits, mtu, mps):
|
|
1460
|
+
def check_le_coc_parameters(max_credits: int, mtu: int, mps: int) -> None:
|
|
1409
1461
|
if (
|
|
1410
1462
|
max_credits < 1
|
|
1411
1463
|
or max_credits > L2CAP_LE_CREDIT_BASED_CONNECTION_MAX_CREDITS
|
|
@@ -1419,19 +1471,21 @@ class ChannelManager:
|
|
|
1419
1471
|
):
|
|
1420
1472
|
raise ValueError('MPS out of range')
|
|
1421
1473
|
|
|
1422
|
-
def next_identifier(self, connection):
|
|
1474
|
+
def next_identifier(self, connection: Connection) -> int:
|
|
1423
1475
|
identifier = (self.identifiers.setdefault(connection.handle, 0) + 1) % 256
|
|
1424
1476
|
self.identifiers[connection.handle] = identifier
|
|
1425
1477
|
return identifier
|
|
1426
1478
|
|
|
1427
|
-
def register_fixed_channel(
|
|
1479
|
+
def register_fixed_channel(
|
|
1480
|
+
self, cid: int, handler: Callable[[int, bytes], Any]
|
|
1481
|
+
) -> None:
|
|
1428
1482
|
self.fixed_channels[cid] = handler
|
|
1429
1483
|
|
|
1430
|
-
def deregister_fixed_channel(self, cid):
|
|
1484
|
+
def deregister_fixed_channel(self, cid: int) -> None:
|
|
1431
1485
|
if cid in self.fixed_channels:
|
|
1432
1486
|
del self.fixed_channels[cid]
|
|
1433
1487
|
|
|
1434
|
-
def register_server(self, psm, server):
|
|
1488
|
+
def register_server(self, psm: int, server: Callable[[Channel], Any]) -> int:
|
|
1435
1489
|
if psm == 0:
|
|
1436
1490
|
# Find a free PSM
|
|
1437
1491
|
for candidate in range(
|
|
@@ -1465,12 +1519,12 @@ class ChannelManager:
|
|
|
1465
1519
|
|
|
1466
1520
|
def register_le_coc_server(
|
|
1467
1521
|
self,
|
|
1468
|
-
psm,
|
|
1469
|
-
server,
|
|
1470
|
-
max_credits=L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_INITIAL_CREDITS,
|
|
1471
|
-
mtu=L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_MTU,
|
|
1472
|
-
mps=L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_MPS,
|
|
1473
|
-
):
|
|
1522
|
+
psm: int,
|
|
1523
|
+
server: Callable[[LeConnectionOrientedChannel], Any],
|
|
1524
|
+
max_credits: int = L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_INITIAL_CREDITS,
|
|
1525
|
+
mtu: int = L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_MTU,
|
|
1526
|
+
mps: int = L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_MPS,
|
|
1527
|
+
) -> int:
|
|
1474
1528
|
self.check_le_coc_parameters(max_credits, mtu, mps)
|
|
1475
1529
|
|
|
1476
1530
|
if psm == 0:
|
|
@@ -1498,7 +1552,7 @@ class ChannelManager:
|
|
|
1498
1552
|
|
|
1499
1553
|
return psm
|
|
1500
1554
|
|
|
1501
|
-
def on_disconnection(self, connection_handle, _reason):
|
|
1555
|
+
def on_disconnection(self, connection_handle: int, _reason: int) -> None:
|
|
1502
1556
|
logger.debug(f'disconnection from {connection_handle}, cleaning up channels')
|
|
1503
1557
|
if connection_handle in self.channels:
|
|
1504
1558
|
for _, channel in self.channels[connection_handle].items():
|
|
@@ -1511,7 +1565,7 @@ class ChannelManager:
|
|
|
1511
1565
|
if connection_handle in self.identifiers:
|
|
1512
1566
|
del self.identifiers[connection_handle]
|
|
1513
1567
|
|
|
1514
|
-
def send_pdu(self, connection, cid, pdu):
|
|
1568
|
+
def send_pdu(self, connection, cid: int, pdu) -> None:
|
|
1515
1569
|
pdu_str = pdu.hex() if isinstance(pdu, bytes) else str(pdu)
|
|
1516
1570
|
logger.debug(
|
|
1517
1571
|
f'{color(">>> Sending L2CAP PDU", "blue")} '
|
|
@@ -1520,14 +1574,16 @@ class ChannelManager:
|
|
|
1520
1574
|
)
|
|
1521
1575
|
self.host.send_l2cap_pdu(connection.handle, cid, bytes(pdu))
|
|
1522
1576
|
|
|
1523
|
-
def on_pdu(self, connection, cid, pdu):
|
|
1577
|
+
def on_pdu(self, connection: Connection, cid: int, pdu) -> None:
|
|
1524
1578
|
if cid in (L2CAP_SIGNALING_CID, L2CAP_LE_SIGNALING_CID):
|
|
1525
1579
|
# Parse the L2CAP payload into a Control Frame object
|
|
1526
1580
|
control_frame = L2CAP_Control_Frame.from_bytes(pdu)
|
|
1527
1581
|
|
|
1528
1582
|
self.on_control_frame(connection, cid, control_frame)
|
|
1529
1583
|
elif cid in self.fixed_channels:
|
|
1530
|
-
self.fixed_channels[cid]
|
|
1584
|
+
handler = self.fixed_channels[cid]
|
|
1585
|
+
assert handler is not None
|
|
1586
|
+
handler(connection.handle, pdu)
|
|
1531
1587
|
else:
|
|
1532
1588
|
if (channel := self.find_channel(connection.handle, cid)) is None:
|
|
1533
1589
|
logger.warning(
|
|
@@ -1539,7 +1595,9 @@ class ChannelManager:
|
|
|
1539
1595
|
|
|
1540
1596
|
channel.on_pdu(pdu)
|
|
1541
1597
|
|
|
1542
|
-
def send_control_frame(
|
|
1598
|
+
def send_control_frame(
|
|
1599
|
+
self, connection: Connection, cid: int, control_frame
|
|
1600
|
+
) -> None:
|
|
1543
1601
|
logger.debug(
|
|
1544
1602
|
f'{color(">>> Sending L2CAP Signaling Control Frame", "blue")} '
|
|
1545
1603
|
f'on connection [0x{connection.handle:04X}] (CID={cid}) '
|
|
@@ -1547,7 +1605,7 @@ class ChannelManager:
|
|
|
1547
1605
|
)
|
|
1548
1606
|
self.host.send_l2cap_pdu(connection.handle, cid, bytes(control_frame))
|
|
1549
1607
|
|
|
1550
|
-
def on_control_frame(self, connection, cid, control_frame):
|
|
1608
|
+
def on_control_frame(self, connection: Connection, cid: int, control_frame) -> None:
|
|
1551
1609
|
logger.debug(
|
|
1552
1610
|
f'{color("<<< Received L2CAP Signaling Control Frame", "green")} '
|
|
1553
1611
|
f'on connection [0x{connection.handle:04X}] (CID={cid}) '
|
|
@@ -1584,10 +1642,14 @@ class ChannelManager:
|
|
|
1584
1642
|
),
|
|
1585
1643
|
)
|
|
1586
1644
|
|
|
1587
|
-
def on_l2cap_command_reject(
|
|
1645
|
+
def on_l2cap_command_reject(
|
|
1646
|
+
self, _connection: Connection, _cid: int, packet
|
|
1647
|
+
) -> None:
|
|
1588
1648
|
logger.warning(f'{color("!!! Command rejected:", "red")} {packet.reason}')
|
|
1589
1649
|
|
|
1590
|
-
def on_l2cap_connection_request(
|
|
1650
|
+
def on_l2cap_connection_request(
|
|
1651
|
+
self, connection: Connection, cid: int, request
|
|
1652
|
+
) -> None:
|
|
1591
1653
|
# Check if there's a server for this PSM
|
|
1592
1654
|
server = self.servers.get(request.psm)
|
|
1593
1655
|
if server:
|
|
@@ -1639,7 +1701,9 @@ class ChannelManager:
|
|
|
1639
1701
|
),
|
|
1640
1702
|
)
|
|
1641
1703
|
|
|
1642
|
-
def on_l2cap_connection_response(
|
|
1704
|
+
def on_l2cap_connection_response(
|
|
1705
|
+
self, connection: Connection, cid: int, response
|
|
1706
|
+
) -> None:
|
|
1643
1707
|
if (
|
|
1644
1708
|
channel := self.find_channel(connection.handle, response.source_cid)
|
|
1645
1709
|
) is None:
|
|
@@ -1654,7 +1718,9 @@ class ChannelManager:
|
|
|
1654
1718
|
|
|
1655
1719
|
channel.on_connection_response(response)
|
|
1656
1720
|
|
|
1657
|
-
def on_l2cap_configure_request(
|
|
1721
|
+
def on_l2cap_configure_request(
|
|
1722
|
+
self, connection: Connection, cid: int, request
|
|
1723
|
+
) -> None:
|
|
1658
1724
|
if (
|
|
1659
1725
|
channel := self.find_channel(connection.handle, request.destination_cid)
|
|
1660
1726
|
) is None:
|
|
@@ -1669,7 +1735,9 @@ class ChannelManager:
|
|
|
1669
1735
|
|
|
1670
1736
|
channel.on_configure_request(request)
|
|
1671
1737
|
|
|
1672
|
-
def on_l2cap_configure_response(
|
|
1738
|
+
def on_l2cap_configure_response(
|
|
1739
|
+
self, connection: Connection, cid: int, response
|
|
1740
|
+
) -> None:
|
|
1673
1741
|
if (
|
|
1674
1742
|
channel := self.find_channel(connection.handle, response.source_cid)
|
|
1675
1743
|
) is None:
|
|
@@ -1684,7 +1752,9 @@ class ChannelManager:
|
|
|
1684
1752
|
|
|
1685
1753
|
channel.on_configure_response(response)
|
|
1686
1754
|
|
|
1687
|
-
def on_l2cap_disconnection_request(
|
|
1755
|
+
def on_l2cap_disconnection_request(
|
|
1756
|
+
self, connection: Connection, cid: int, request
|
|
1757
|
+
) -> None:
|
|
1688
1758
|
if (
|
|
1689
1759
|
channel := self.find_channel(connection.handle, request.destination_cid)
|
|
1690
1760
|
) is None:
|
|
@@ -1699,7 +1769,9 @@ class ChannelManager:
|
|
|
1699
1769
|
|
|
1700
1770
|
channel.on_disconnection_request(request)
|
|
1701
1771
|
|
|
1702
|
-
def on_l2cap_disconnection_response(
|
|
1772
|
+
def on_l2cap_disconnection_response(
|
|
1773
|
+
self, connection: Connection, cid: int, response
|
|
1774
|
+
) -> None:
|
|
1703
1775
|
if (
|
|
1704
1776
|
channel := self.find_channel(connection.handle, response.source_cid)
|
|
1705
1777
|
) is None:
|
|
@@ -1714,7 +1786,7 @@ class ChannelManager:
|
|
|
1714
1786
|
|
|
1715
1787
|
channel.on_disconnection_response(response)
|
|
1716
1788
|
|
|
1717
|
-
def on_l2cap_echo_request(self, connection, cid, request):
|
|
1789
|
+
def on_l2cap_echo_request(self, connection: Connection, cid: int, request) -> None:
|
|
1718
1790
|
logger.debug(f'<<< Echo request: data={request.data.hex()}')
|
|
1719
1791
|
self.send_control_frame(
|
|
1720
1792
|
connection,
|
|
@@ -1722,11 +1794,15 @@ class ChannelManager:
|
|
|
1722
1794
|
L2CAP_Echo_Response(identifier=request.identifier, data=request.data),
|
|
1723
1795
|
)
|
|
1724
1796
|
|
|
1725
|
-
def on_l2cap_echo_response(
|
|
1797
|
+
def on_l2cap_echo_response(
|
|
1798
|
+
self, _connection: Connection, _cid: int, response
|
|
1799
|
+
) -> None:
|
|
1726
1800
|
logger.debug(f'<<< Echo response: data={response.data.hex()}')
|
|
1727
1801
|
# TODO notify listeners
|
|
1728
1802
|
|
|
1729
|
-
def on_l2cap_information_request(
|
|
1803
|
+
def on_l2cap_information_request(
|
|
1804
|
+
self, connection: Connection, cid: int, request
|
|
1805
|
+
) -> None:
|
|
1730
1806
|
if request.info_type == L2CAP_Information_Request.CONNECTIONLESS_MTU:
|
|
1731
1807
|
result = L2CAP_Information_Response.SUCCESS
|
|
1732
1808
|
data = self.connectionless_mtu.to_bytes(2, 'little')
|
|
@@ -1750,7 +1826,9 @@ class ChannelManager:
|
|
|
1750
1826
|
),
|
|
1751
1827
|
)
|
|
1752
1828
|
|
|
1753
|
-
def on_l2cap_connection_parameter_update_request(
|
|
1829
|
+
def on_l2cap_connection_parameter_update_request(
|
|
1830
|
+
self, connection: Connection, cid: int, request
|
|
1831
|
+
):
|
|
1754
1832
|
if connection.role == BT_CENTRAL_ROLE:
|
|
1755
1833
|
self.send_control_frame(
|
|
1756
1834
|
connection,
|
|
@@ -1769,7 +1847,7 @@ class ChannelManager:
|
|
|
1769
1847
|
supervision_timeout=request.timeout,
|
|
1770
1848
|
min_ce_length=0,
|
|
1771
1849
|
max_ce_length=0,
|
|
1772
|
-
)
|
|
1850
|
+
) # type: ignore[call-arg]
|
|
1773
1851
|
)
|
|
1774
1852
|
else:
|
|
1775
1853
|
self.send_control_frame(
|
|
@@ -1781,11 +1859,15 @@ class ChannelManager:
|
|
|
1781
1859
|
),
|
|
1782
1860
|
)
|
|
1783
1861
|
|
|
1784
|
-
def on_l2cap_connection_parameter_update_response(
|
|
1862
|
+
def on_l2cap_connection_parameter_update_response(
|
|
1863
|
+
self, connection: Connection, cid: int, response
|
|
1864
|
+
) -> None:
|
|
1785
1865
|
# TODO: check response
|
|
1786
1866
|
pass
|
|
1787
1867
|
|
|
1788
|
-
def on_l2cap_le_credit_based_connection_request(
|
|
1868
|
+
def on_l2cap_le_credit_based_connection_request(
|
|
1869
|
+
self, connection: Connection, cid: int, request
|
|
1870
|
+
) -> None:
|
|
1789
1871
|
if request.le_psm in self.le_coc_servers:
|
|
1790
1872
|
(server, max_credits, mtu, mps) = self.le_coc_servers[request.le_psm]
|
|
1791
1873
|
|
|
@@ -1887,7 +1969,9 @@ class ChannelManager:
|
|
|
1887
1969
|
),
|
|
1888
1970
|
)
|
|
1889
1971
|
|
|
1890
|
-
def on_l2cap_le_credit_based_connection_response(
|
|
1972
|
+
def on_l2cap_le_credit_based_connection_response(
|
|
1973
|
+
self, connection: Connection, _cid: int, response
|
|
1974
|
+
) -> None:
|
|
1891
1975
|
# Find the pending request by identifier
|
|
1892
1976
|
request = self.le_coc_requests.get(response.identifier)
|
|
1893
1977
|
if request is None:
|
|
@@ -1910,7 +1994,9 @@ class ChannelManager:
|
|
|
1910
1994
|
# Process the response
|
|
1911
1995
|
channel.on_connection_response(response)
|
|
1912
1996
|
|
|
1913
|
-
def on_l2cap_le_flow_control_credit(
|
|
1997
|
+
def on_l2cap_le_flow_control_credit(
|
|
1998
|
+
self, connection: Connection, _cid: int, credit
|
|
1999
|
+
) -> None:
|
|
1914
2000
|
channel = self.find_le_coc_channel(connection.handle, credit.cid)
|
|
1915
2001
|
if channel is None:
|
|
1916
2002
|
logger.warning(f'received credits for an unknown channel (cid={credit.cid}')
|
|
@@ -1918,13 +2004,15 @@ class ChannelManager:
|
|
|
1918
2004
|
|
|
1919
2005
|
channel.on_credits(credit.credits)
|
|
1920
2006
|
|
|
1921
|
-
def on_channel_closed(self, channel):
|
|
2007
|
+
def on_channel_closed(self, channel: Channel) -> None:
|
|
1922
2008
|
connection_channels = self.channels.get(channel.connection.handle)
|
|
1923
2009
|
if connection_channels:
|
|
1924
2010
|
if channel.source_cid in connection_channels:
|
|
1925
2011
|
del connection_channels[channel.source_cid]
|
|
1926
2012
|
|
|
1927
|
-
async def open_le_coc(
|
|
2013
|
+
async def open_le_coc(
|
|
2014
|
+
self, connection: Connection, psm: int, max_credits: int, mtu: int, mps: int
|
|
2015
|
+
) -> LeConnectionOrientedChannel:
|
|
1928
2016
|
self.check_le_coc_parameters(max_credits, mtu, mps)
|
|
1929
2017
|
|
|
1930
2018
|
# Find a free CID for the new channel
|
|
@@ -1965,7 +2053,7 @@ class ChannelManager:
|
|
|
1965
2053
|
|
|
1966
2054
|
return channel
|
|
1967
2055
|
|
|
1968
|
-
async def connect(self, connection, psm):
|
|
2056
|
+
async def connect(self, connection: Connection, psm: int) -> Channel:
|
|
1969
2057
|
# NOTE: this implementation hard-codes BR/EDR
|
|
1970
2058
|
|
|
1971
2059
|
# Find a free CID for a new channel
|