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/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 Dict, Type
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 = None
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') and kwargs:
262
- HCI_Object.init_from_fields(self, self.fields, kwargs)
263
- if pdu is None:
264
- data = HCI_Object.dict_to_bytes(kwargs, self.fields)
265
- pdu = (
266
- bytes([self.code, self.identifier])
267
- + struct.pack('<H', len(data))
268
- + data
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
- def __init__(self, manager, connection, signaling_cid, psm, source_cid, mtu):
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, extended_features=(), connectionless_mtu=L2CAP_DEFAULT_CONNECTIONLESS_MTU
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(self, cid, handler):
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](connection.handle, pdu)
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(self, connection, cid, 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(self, _connection, _cid, packet):
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(self, connection, cid, 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(self, connection, cid, 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(self, connection, cid, 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(self, connection, cid, 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(self, connection, cid, 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(self, connection, cid, 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(self, _connection, _cid, 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(self, connection, cid, 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(self, connection, cid, 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(self, connection, cid, 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(self, connection, cid, 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(self, connection, _cid, 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(self, connection, _cid, 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(self, connection, psm, max_credits, mtu, mps):
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