1# Copyright 2021-2022 Google LLC 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# https://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15# ----------------------------------------------------------------------------- 16# Imports 17# ----------------------------------------------------------------------------- 18import logging 19import asyncio 20 21from pyee import EventEmitter 22 23from . import core 24from .colors import color 25from .core import BT_BR_EDR_TRANSPORT, InvalidStateError, ProtocolError 26 27# ----------------------------------------------------------------------------- 28# Logging 29# ----------------------------------------------------------------------------- 30logger = logging.getLogger(__name__) 31 32 33# ----------------------------------------------------------------------------- 34# Constants 35# ----------------------------------------------------------------------------- 36# fmt: off 37 38RFCOMM_PSM = 0x0003 39 40 41# Frame types 42RFCOMM_SABM_FRAME = 0x2F # Control field [1,1,1,1,_,1,0,0] LSB-first 43RFCOMM_UA_FRAME = 0x63 # Control field [0,1,1,0,_,0,1,1] LSB-first 44RFCOMM_DM_FRAME = 0x0F # Control field [1,1,1,1,_,0,0,0] LSB-first 45RFCOMM_DISC_FRAME = 0x43 # Control field [0,1,0,_,0,0,1,1] LSB-first 46RFCOMM_UIH_FRAME = 0xEF # Control field [1,1,1,_,1,1,1,1] LSB-first 47RFCOMM_UI_FRAME = 0x03 # Control field [0,0,0,_,0,0,1,1] LSB-first 48 49RFCOMM_FRAME_TYPE_NAMES = { 50 RFCOMM_SABM_FRAME: 'SABM', 51 RFCOMM_UA_FRAME: 'UA', 52 RFCOMM_DM_FRAME: 'DM', 53 RFCOMM_DISC_FRAME: 'DISC', 54 RFCOMM_UIH_FRAME: 'UIH', 55 RFCOMM_UI_FRAME: 'UI' 56} 57 58# MCC Types 59RFCOMM_MCC_PN_TYPE = 0x20 60RFCOMM_MCC_MSC_TYPE = 0x38 61 62# FCS CRC 63CRC_TABLE = bytes([ 64 0X00, 0X91, 0XE3, 0X72, 0X07, 0X96, 0XE4, 0X75, 65 0X0E, 0X9F, 0XED, 0X7C, 0X09, 0X98, 0XEA, 0X7B, 66 0X1C, 0X8D, 0XFF, 0X6E, 0X1B, 0X8A, 0XF8, 0X69, 67 0X12, 0X83, 0XF1, 0X60, 0X15, 0X84, 0XF6, 0X67, 68 0X38, 0XA9, 0XDB, 0X4A, 0X3F, 0XAE, 0XDC, 0X4D, 69 0X36, 0XA7, 0XD5, 0X44, 0X31, 0XA0, 0XD2, 0X43, 70 0X24, 0XB5, 0XC7, 0X56, 0X23, 0XB2, 0XC0, 0X51, 71 0X2A, 0XBB, 0XC9, 0X58, 0X2D, 0XBC, 0XCE, 0X5F, 72 0X70, 0XE1, 0X93, 0X02, 0X77, 0XE6, 0X94, 0X05, 73 0X7E, 0XEF, 0X9D, 0X0C, 0X79, 0XE8, 0X9A, 0X0B, 74 0X6C, 0XFD, 0X8F, 0X1E, 0X6B, 0XFA, 0X88, 0X19, 75 0X62, 0XF3, 0X81, 0X10, 0X65, 0XF4, 0X86, 0X17, 76 0X48, 0XD9, 0XAB, 0X3A, 0X4F, 0XDE, 0XAC, 0X3D, 77 0X46, 0XD7, 0XA5, 0X34, 0X41, 0XD0, 0XA2, 0X33, 78 0X54, 0XC5, 0XB7, 0X26, 0X53, 0XC2, 0XB0, 0X21, 79 0X5A, 0XCB, 0XB9, 0X28, 0X5D, 0XCC, 0XBE, 0X2F, 80 0XE0, 0X71, 0X03, 0X92, 0XE7, 0X76, 0X04, 0X95, 81 0XEE, 0X7F, 0X0D, 0X9C, 0XE9, 0X78, 0X0A, 0X9B, 82 0XFC, 0X6D, 0X1F, 0X8E, 0XFB, 0X6A, 0X18, 0X89, 83 0XF2, 0X63, 0X11, 0X80, 0XF5, 0X64, 0X16, 0X87, 84 0XD8, 0X49, 0X3B, 0XAA, 0XDF, 0X4E, 0X3C, 0XAD, 85 0XD6, 0X47, 0X35, 0XA4, 0XD1, 0X40, 0X32, 0XA3, 86 0XC4, 0X55, 0X27, 0XB6, 0XC3, 0X52, 0X20, 0XB1, 87 0XCA, 0X5B, 0X29, 0XB8, 0XCD, 0X5C, 0X2E, 0XBF, 88 0X90, 0X01, 0X73, 0XE2, 0X97, 0X06, 0X74, 0XE5, 89 0X9E, 0X0F, 0X7D, 0XEC, 0X99, 0X08, 0X7A, 0XEB, 90 0X8C, 0X1D, 0X6F, 0XFE, 0X8B, 0X1A, 0X68, 0XF9, 91 0X82, 0X13, 0X61, 0XF0, 0X85, 0X14, 0X66, 0XF7, 92 0XA8, 0X39, 0X4B, 0XDA, 0XAF, 0X3E, 0X4C, 0XDD, 93 0XA6, 0X37, 0X45, 0XD4, 0XA1, 0X30, 0X42, 0XD3, 94 0XB4, 0X25, 0X57, 0XC6, 0XB3, 0X22, 0X50, 0XC1, 95 0XBA, 0X2B, 0X59, 0XC8, 0XBD, 0X2C, 0X5E, 0XCF 96]) 97 98RFCOMM_DEFAULT_INITIAL_RX_CREDITS = 7 99RFCOMM_DEFAULT_PREFERRED_MTU = 1280 100 101RFCOMM_DYNAMIC_CHANNEL_NUMBER_START = 1 102RFCOMM_DYNAMIC_CHANNEL_NUMBER_END = 30 103 104# fmt: on 105 106 107# ----------------------------------------------------------------------------- 108def compute_fcs(buffer): 109 result = 0xFF 110 for byte in buffer: 111 result = CRC_TABLE[result ^ byte] 112 return 0xFF - result 113 114 115# ----------------------------------------------------------------------------- 116class RFCOMM_Frame: 117 def __init__(self, frame_type, c_r, dlci, p_f, information=b'', with_credits=False): 118 self.type = frame_type 119 self.c_r = c_r 120 self.dlci = dlci 121 self.p_f = p_f 122 self.information = information 123 length = len(information) 124 if with_credits: 125 length -= 1 126 if length > 0x7F: 127 # 2-byte length indicator 128 self.length = bytes([(length & 0x7F) << 1, (length >> 7) & 0xFF]) 129 else: 130 # 1-byte length indicator 131 self.length = bytes([(length << 1) | 1]) 132 self.address = (dlci << 2) | (c_r << 1) | 1 133 self.control = frame_type | (p_f << 4) 134 if frame_type == RFCOMM_UIH_FRAME: 135 self.fcs = compute_fcs(bytes([self.address, self.control])) 136 else: 137 self.fcs = compute_fcs(bytes([self.address, self.control]) + self.length) 138 139 def type_name(self): 140 return RFCOMM_FRAME_TYPE_NAMES[self.type] 141 142 @staticmethod 143 def parse_mcc(data): 144 mcc_type = data[0] >> 2 145 c_r = (data[0] >> 1) & 1 146 length = data[1] 147 if data[1] & 1: 148 length >>= 1 149 value = data[2:] 150 else: 151 length = (data[3] << 7) & (length >> 1) 152 value = data[3 : 3 + length] 153 154 return (mcc_type, c_r, value) 155 156 @staticmethod 157 def make_mcc(mcc_type, c_r, data): 158 return ( 159 bytes([(mcc_type << 2 | c_r << 1 | 1) & 0xFF, (len(data) & 0x7F) << 1 | 1]) 160 + data 161 ) 162 163 @staticmethod 164 def sabm(c_r, dlci): 165 return RFCOMM_Frame(RFCOMM_SABM_FRAME, c_r, dlci, 1) 166 167 @staticmethod 168 def ua(c_r, dlci): 169 return RFCOMM_Frame(RFCOMM_UA_FRAME, c_r, dlci, 1) 170 171 @staticmethod 172 def dm(c_r, dlci): 173 return RFCOMM_Frame(RFCOMM_DM_FRAME, c_r, dlci, 1) 174 175 @staticmethod 176 def disc(c_r, dlci): 177 return RFCOMM_Frame(RFCOMM_DISC_FRAME, c_r, dlci, 1) 178 179 @staticmethod 180 def uih(c_r, dlci, information, p_f=0): 181 return RFCOMM_Frame( 182 RFCOMM_UIH_FRAME, c_r, dlci, p_f, information, with_credits=(p_f == 1) 183 ) 184 185 @staticmethod 186 def from_bytes(data): 187 # Extract fields 188 dlci = (data[0] >> 2) & 0x3F 189 c_r = (data[0] >> 1) & 0x01 190 frame_type = data[1] & 0xEF 191 p_f = (data[1] >> 4) & 0x01 192 length = data[2] 193 if length & 0x01: 194 length >>= 1 195 information = data[3:-1] 196 else: 197 length = (data[3] << 7) & (length >> 1) 198 information = data[4:-1] 199 fcs = data[-1] 200 201 # Construct the frame and check the CRC 202 frame = RFCOMM_Frame(frame_type, c_r, dlci, p_f, information) 203 if frame.fcs != fcs: 204 logger.warning(f'FCS mismatch: got {fcs:02X}, expected {frame.fcs:02X}') 205 raise ValueError('fcs mismatch') 206 207 return frame 208 209 def __bytes__(self): 210 return ( 211 bytes([self.address, self.control]) 212 + self.length 213 + self.information 214 + bytes([self.fcs]) 215 ) 216 217 def __str__(self): 218 return ( 219 f'{color(self.type_name(), "yellow")}' 220 f'(c/r={self.c_r},' 221 f'dlci={self.dlci},' 222 f'p/f={self.p_f},' 223 f'length={len(self.information)},' 224 f'fcs=0x{self.fcs:02X})' 225 ) 226 227 228# ----------------------------------------------------------------------------- 229class RFCOMM_MCC_PN: 230 def __init__( 231 self, 232 dlci, 233 cl, 234 priority, 235 ack_timer, 236 max_frame_size, 237 max_retransmissions, 238 window_size, 239 ): 240 self.dlci = dlci 241 self.cl = cl 242 self.priority = priority 243 self.ack_timer = ack_timer 244 self.max_frame_size = max_frame_size 245 self.max_retransmissions = max_retransmissions 246 self.window_size = window_size 247 248 @staticmethod 249 def from_bytes(data): 250 return RFCOMM_MCC_PN( 251 dlci=data[0], 252 cl=data[1], 253 priority=data[2], 254 ack_timer=data[3], 255 max_frame_size=data[4] | data[5] << 8, 256 max_retransmissions=data[6], 257 window_size=data[7], 258 ) 259 260 def __bytes__(self): 261 return bytes( 262 [ 263 self.dlci & 0xFF, 264 self.cl & 0xFF, 265 self.priority & 0xFF, 266 self.ack_timer & 0xFF, 267 self.max_frame_size & 0xFF, 268 (self.max_frame_size >> 8) & 0xFF, 269 self.max_retransmissions & 0xFF, 270 self.window_size & 0xFF, 271 ] 272 ) 273 274 def __str__(self): 275 return ( 276 f'PN(dlci={self.dlci},' 277 f'cl={self.cl},' 278 f'priority={self.priority},' 279 f'ack_timer={self.ack_timer},' 280 f'max_frame_size={self.max_frame_size},' 281 f'max_retransmissions={self.max_retransmissions},' 282 f'window_size={self.window_size})' 283 ) 284 285 286# ----------------------------------------------------------------------------- 287class RFCOMM_MCC_MSC: 288 def __init__(self, dlci, fc, rtc, rtr, ic, dv): 289 self.dlci = dlci 290 self.fc = fc 291 self.rtc = rtc 292 self.rtr = rtr 293 self.ic = ic 294 self.dv = dv 295 296 @staticmethod 297 def from_bytes(data): 298 return RFCOMM_MCC_MSC( 299 dlci=data[0] >> 2, 300 fc=data[1] >> 1 & 1, 301 rtc=data[1] >> 2 & 1, 302 rtr=data[1] >> 3 & 1, 303 ic=data[1] >> 6 & 1, 304 dv=data[1] >> 7 & 1, 305 ) 306 307 def __bytes__(self): 308 return bytes( 309 [ 310 (self.dlci << 2) | 3, 311 1 312 | self.fc << 1 313 | self.rtc << 2 314 | self.rtr << 3 315 | self.ic << 6 316 | self.dv << 7, 317 ] 318 ) 319 320 def __str__(self): 321 return ( 322 f'MSC(dlci={self.dlci},' 323 f'fc={self.fc},' 324 f'rtc={self.rtc},' 325 f'rtr={self.rtr},' 326 f'ic={self.ic},' 327 f'dv={self.dv})' 328 ) 329 330 331# ----------------------------------------------------------------------------- 332class DLC(EventEmitter): 333 # States 334 INIT = 0x00 335 CONNECTING = 0x01 336 CONNECTED = 0x02 337 DISCONNECTING = 0x03 338 DISCONNECTED = 0x04 339 RESET = 0x05 340 341 STATE_NAMES = { 342 INIT: 'INIT', 343 CONNECTING: 'CONNECTING', 344 CONNECTED: 'CONNECTED', 345 DISCONNECTING: 'DISCONNECTING', 346 DISCONNECTED: 'DISCONNECTED', 347 RESET: 'RESET', 348 } 349 350 def __init__(self, multiplexer, dlci, max_frame_size, initial_tx_credits): 351 super().__init__() 352 self.multiplexer = multiplexer 353 self.dlci = dlci 354 self.rx_credits = RFCOMM_DEFAULT_INITIAL_RX_CREDITS 355 self.rx_threshold = self.rx_credits // 2 356 self.tx_credits = initial_tx_credits 357 self.tx_buffer = b'' 358 self.state = DLC.INIT 359 self.role = multiplexer.role 360 self.c_r = 1 if self.role == Multiplexer.INITIATOR else 0 361 self.sink = None 362 self.connection_result = None 363 364 # Compute the MTU 365 max_overhead = 4 + 1 # header with 2-byte length + fcs 366 self.mtu = min( 367 max_frame_size, self.multiplexer.l2cap_channel.mtu - max_overhead 368 ) 369 370 @staticmethod 371 def state_name(state): 372 return DLC.STATE_NAMES[state] 373 374 def change_state(self, new_state): 375 logger.debug( 376 f'{self} state change -> {color(self.state_name(new_state), "magenta")}' 377 ) 378 self.state = new_state 379 380 def send_frame(self, frame): 381 self.multiplexer.send_frame(frame) 382 383 def on_frame(self, frame): 384 handler = getattr(self, f'on_{frame.type_name()}_frame'.lower()) 385 handler(frame) 386 387 def on_sabm_frame(self, _frame): 388 if self.state != DLC.CONNECTING: 389 logger.warning( 390 color('!!! received SABM when not in CONNECTING state', 'red') 391 ) 392 return 393 394 self.send_frame(RFCOMM_Frame.ua(c_r=1 - self.c_r, dlci=self.dlci)) 395 396 # Exchange the modem status with the peer 397 msc = RFCOMM_MCC_MSC(dlci=self.dlci, fc=0, rtc=1, rtr=1, ic=0, dv=1) 398 mcc = RFCOMM_Frame.make_mcc( 399 mcc_type=RFCOMM_MCC_MSC_TYPE, c_r=1, data=bytes(msc) 400 ) 401 logger.debug(f'>>> MCC MSC Command: {msc}') 402 self.send_frame(RFCOMM_Frame.uih(c_r=self.c_r, dlci=0, information=mcc)) 403 404 self.change_state(DLC.CONNECTED) 405 self.emit('open') 406 407 def on_ua_frame(self, _frame): 408 if self.state != DLC.CONNECTING: 409 logger.warning( 410 color('!!! received SABM when not in CONNECTING state', 'red') 411 ) 412 return 413 414 # Exchange the modem status with the peer 415 msc = RFCOMM_MCC_MSC(dlci=self.dlci, fc=0, rtc=1, rtr=1, ic=0, dv=1) 416 mcc = RFCOMM_Frame.make_mcc( 417 mcc_type=RFCOMM_MCC_MSC_TYPE, c_r=1, data=bytes(msc) 418 ) 419 logger.debug(f'>>> MCC MSC Command: {msc}') 420 self.send_frame(RFCOMM_Frame.uih(c_r=self.c_r, dlci=0, information=mcc)) 421 422 self.change_state(DLC.CONNECTED) 423 self.multiplexer.on_dlc_open_complete(self) 424 425 def on_dm_frame(self, frame): 426 # TODO: handle all states 427 pass 428 429 def on_disc_frame(self, _frame): 430 # TODO: handle all states 431 self.send_frame(RFCOMM_Frame.ua(c_r=1 - self.c_r, dlci=self.dlci)) 432 433 def on_uih_frame(self, frame): 434 data = frame.information 435 if frame.p_f == 1: 436 # With credits 437 received_credits = frame.information[0] 438 self.tx_credits += received_credits 439 440 logger.debug( 441 f'<<< Credits [{self.dlci}]: ' 442 f'received {credits}, total={self.tx_credits}' 443 ) 444 data = data[1:] 445 446 logger.debug( 447 f'{color("<<< Data", "yellow")} ' 448 f'[{self.dlci}] {len(data)} bytes, ' 449 f'rx_credits={self.rx_credits}: {data.hex()}' 450 ) 451 if len(data) and self.sink: 452 self.sink(data) # pylint: disable=not-callable 453 454 # Update the credits 455 if self.rx_credits > 0: 456 self.rx_credits -= 1 457 else: 458 logger.warning(color('!!! received frame with no rx credits', 'red')) 459 460 # Check if there's anything to send (including credits) 461 self.process_tx() 462 463 def on_ui_frame(self, frame): 464 pass 465 466 def on_mcc_msc(self, c_r, msc): 467 if c_r: 468 # Command 469 logger.debug(f'<<< MCC MSC Command: {msc}') 470 msc = RFCOMM_MCC_MSC(dlci=self.dlci, fc=0, rtc=1, rtr=1, ic=0, dv=1) 471 mcc = RFCOMM_Frame.make_mcc( 472 mcc_type=RFCOMM_MCC_MSC_TYPE, c_r=0, data=bytes(msc) 473 ) 474 logger.debug(f'>>> MCC MSC Response: {msc}') 475 self.send_frame(RFCOMM_Frame.uih(c_r=self.c_r, dlci=0, information=mcc)) 476 else: 477 # Response 478 logger.debug(f'<<< MCC MSC Response: {msc}') 479 480 def connect(self): 481 if self.state != DLC.INIT: 482 raise InvalidStateError('invalid state') 483 484 self.change_state(DLC.CONNECTING) 485 self.connection_result = asyncio.get_running_loop().create_future() 486 self.send_frame(RFCOMM_Frame.sabm(c_r=self.c_r, dlci=self.dlci)) 487 488 def accept(self): 489 if self.state != DLC.INIT: 490 raise InvalidStateError('invalid state') 491 492 pn = RFCOMM_MCC_PN( 493 dlci=self.dlci, 494 cl=0xE0, 495 priority=7, 496 ack_timer=0, 497 max_frame_size=RFCOMM_DEFAULT_PREFERRED_MTU, 498 max_retransmissions=0, 499 window_size=RFCOMM_DEFAULT_INITIAL_RX_CREDITS, 500 ) 501 mcc = RFCOMM_Frame.make_mcc(mcc_type=RFCOMM_MCC_PN_TYPE, c_r=0, data=bytes(pn)) 502 logger.debug(f'>>> PN Response: {pn}') 503 self.send_frame(RFCOMM_Frame.uih(c_r=self.c_r, dlci=0, information=mcc)) 504 self.change_state(DLC.CONNECTING) 505 506 def rx_credits_needed(self): 507 if self.rx_credits <= self.rx_threshold: 508 return RFCOMM_DEFAULT_INITIAL_RX_CREDITS - self.rx_credits 509 510 return 0 511 512 def process_tx(self): 513 # Send anything we can (or an empty frame if we need to send rx credits) 514 rx_credits_needed = self.rx_credits_needed() 515 while (self.tx_buffer and self.tx_credits > 0) or rx_credits_needed > 0: 516 # Get the next chunk, up to MTU size 517 if rx_credits_needed > 0: 518 chunk = bytes([rx_credits_needed]) + self.tx_buffer[: self.mtu - 1] 519 self.tx_buffer = self.tx_buffer[len(chunk) - 1 :] 520 self.rx_credits += rx_credits_needed 521 tx_credit_spent = len(chunk) > 1 522 else: 523 chunk = self.tx_buffer[: self.mtu] 524 self.tx_buffer = self.tx_buffer[len(chunk) :] 525 tx_credit_spent = True 526 527 # Update the tx credits 528 # (no tx credit spent for empty frames that only contain rx credits) 529 if tx_credit_spent: 530 self.tx_credits -= 1 531 532 # Send the frame 533 logger.debug( 534 f'>>> sending {len(chunk)} bytes with {rx_credits_needed} credits, ' 535 f'rx_credits={self.rx_credits}, ' 536 f'tx_credits={self.tx_credits}' 537 ) 538 self.send_frame( 539 RFCOMM_Frame.uih( 540 c_r=self.c_r, 541 dlci=self.dlci, 542 information=chunk, 543 p_f=1 if rx_credits_needed > 0 else 0, 544 ) 545 ) 546 547 rx_credits_needed = 0 548 549 # Stream protocol 550 def write(self, data): 551 # We can only send bytes 552 if not isinstance(data, bytes): 553 if isinstance(data, str): 554 # Automatically convert strings to bytes using UTF-8 555 data = data.encode('utf-8') 556 else: 557 raise ValueError('write only accept bytes or strings') 558 559 self.tx_buffer += data 560 self.process_tx() 561 562 def drain(self): 563 # TODO 564 pass 565 566 def __str__(self): 567 return f'DLC(dlci={self.dlci},state={self.state_name(self.state)})' 568 569 570# ----------------------------------------------------------------------------- 571class Multiplexer(EventEmitter): 572 # Roles 573 INITIATOR = 0x00 574 RESPONDER = 0x01 575 576 # States 577 INIT = 0x00 578 CONNECTING = 0x01 579 CONNECTED = 0x02 580 OPENING = 0x03 581 DISCONNECTING = 0x04 582 DISCONNECTED = 0x05 583 RESET = 0x06 584 585 STATE_NAMES = { 586 INIT: 'INIT', 587 CONNECTING: 'CONNECTING', 588 CONNECTED: 'CONNECTED', 589 OPENING: 'OPENING', 590 DISCONNECTING: 'DISCONNECTING', 591 DISCONNECTED: 'DISCONNECTED', 592 RESET: 'RESET', 593 } 594 595 def __init__(self, l2cap_channel, role): 596 super().__init__() 597 self.role = role 598 self.l2cap_channel = l2cap_channel 599 self.state = Multiplexer.INIT 600 self.dlcs = {} # DLCs, by DLCI 601 self.connection_result = None 602 self.disconnection_result = None 603 self.open_result = None 604 self.acceptor = None 605 606 # Become a sink for the L2CAP channel 607 l2cap_channel.sink = self.on_pdu 608 609 @staticmethod 610 def state_name(state): 611 return Multiplexer.STATE_NAMES[state] 612 613 def change_state(self, new_state): 614 logger.debug( 615 f'{self} state change -> {color(self.state_name(new_state), "cyan")}' 616 ) 617 self.state = new_state 618 619 def send_frame(self, frame): 620 logger.debug(f'>>> Multiplexer sending {frame}') 621 self.l2cap_channel.send_pdu(frame) 622 623 def on_pdu(self, pdu): 624 frame = RFCOMM_Frame.from_bytes(pdu) 625 logger.debug(f'<<< Multiplexer received {frame}') 626 627 # Dispatch to this multiplexer or to a dlc, depending on the address 628 if frame.dlci == 0: 629 self.on_frame(frame) 630 else: 631 if frame.type == RFCOMM_DM_FRAME: 632 # DM responses are for a DLCI, but since we only create the dlc when we 633 # receive a PN response (because we need the parameters), we handle DM 634 # frames at the Multiplexer level 635 self.on_dm_frame(frame) 636 else: 637 dlc = self.dlcs.get(frame.dlci) 638 if dlc is None: 639 logger.warning(f'no dlc for DLCI {frame.dlci}') 640 return 641 dlc.on_frame(frame) 642 643 def on_frame(self, frame): 644 handler = getattr(self, f'on_{frame.type_name()}_frame'.lower()) 645 handler(frame) 646 647 def on_sabm_frame(self, _frame): 648 if self.state != Multiplexer.INIT: 649 logger.debug('not in INIT state, ignoring SABM') 650 return 651 self.change_state(Multiplexer.CONNECTED) 652 self.send_frame(RFCOMM_Frame.ua(c_r=1, dlci=0)) 653 654 def on_ua_frame(self, _frame): 655 if self.state == Multiplexer.CONNECTING: 656 self.change_state(Multiplexer.CONNECTED) 657 if self.connection_result: 658 self.connection_result.set_result(0) 659 self.connection_result = None 660 elif self.state == Multiplexer.DISCONNECTING: 661 self.change_state(Multiplexer.DISCONNECTED) 662 if self.disconnection_result: 663 self.disconnection_result.set_result(None) 664 self.disconnection_result = None 665 666 def on_dm_frame(self, _frame): 667 if self.state == Multiplexer.OPENING: 668 self.change_state(Multiplexer.CONNECTED) 669 if self.open_result: 670 self.open_result.set_exception( 671 core.ConnectionError( 672 core.ConnectionError.CONNECTION_REFUSED, 673 BT_BR_EDR_TRANSPORT, 674 self.l2cap_channel.connection.peer_address, 675 'rfcomm', 676 ) 677 ) 678 else: 679 logger.warning(f'unexpected state for DM: {self}') 680 681 def on_disc_frame(self, _frame): 682 self.change_state(Multiplexer.DISCONNECTED) 683 self.send_frame( 684 RFCOMM_Frame.ua(c_r=0 if self.role == Multiplexer.INITIATOR else 1, dlci=0) 685 ) 686 687 def on_uih_frame(self, frame): 688 (mcc_type, c_r, value) = RFCOMM_Frame.parse_mcc(frame.information) 689 690 if mcc_type == RFCOMM_MCC_PN_TYPE: 691 pn = RFCOMM_MCC_PN.from_bytes(value) 692 self.on_mcc_pn(c_r, pn) 693 elif mcc_type == RFCOMM_MCC_MSC_TYPE: 694 mcs = RFCOMM_MCC_MSC.from_bytes(value) 695 self.on_mcc_msc(c_r, mcs) 696 697 def on_ui_frame(self, frame): 698 pass 699 700 def on_mcc_pn(self, c_r, pn): 701 if c_r == 1: 702 # Command 703 logger.debug(f'<<< PN Command: {pn}') 704 705 # Check with the multiplexer if there's an acceptor for this channel 706 if pn.dlci & 1: 707 # Not expected, this is an initiator-side number 708 # TODO: error out 709 logger.warning(f'invalid DLCI: {pn.dlci}') 710 else: 711 if self.acceptor: 712 channel_number = pn.dlci >> 1 713 if self.acceptor(channel_number): 714 # Create a new DLC 715 dlc = DLC(self, pn.dlci, pn.max_frame_size, pn.window_size) 716 self.dlcs[pn.dlci] = dlc 717 718 # Re-emit the handshake completion event 719 dlc.on('open', lambda: self.emit('dlc', dlc)) 720 721 # Respond to complete the handshake 722 dlc.accept() 723 else: 724 # No acceptor, we're in Disconnected Mode 725 self.send_frame(RFCOMM_Frame.dm(c_r=1, dlci=pn.dlci)) 726 else: 727 # No acceptor?? shouldn't happen 728 logger.warning(color('!!! no acceptor registered', 'red')) 729 else: 730 # Response 731 logger.debug(f'>>> PN Response: {pn}') 732 if self.state == Multiplexer.OPENING: 733 dlc = DLC(self, pn.dlci, pn.max_frame_size, pn.window_size) 734 self.dlcs[pn.dlci] = dlc 735 dlc.connect() 736 else: 737 logger.warning('ignoring PN response') 738 739 def on_mcc_msc(self, c_r, msc): 740 dlc = self.dlcs.get(msc.dlci) 741 if dlc is None: 742 logger.warning(f'no dlc for DLCI {msc.dlci}') 743 return 744 dlc.on_mcc_msc(c_r, msc) 745 746 async def connect(self): 747 if self.state != Multiplexer.INIT: 748 raise InvalidStateError('invalid state') 749 750 self.change_state(Multiplexer.CONNECTING) 751 self.connection_result = asyncio.get_running_loop().create_future() 752 self.send_frame(RFCOMM_Frame.sabm(c_r=1, dlci=0)) 753 return await self.connection_result 754 755 async def disconnect(self): 756 if self.state != Multiplexer.CONNECTED: 757 return 758 759 self.disconnection_result = asyncio.get_running_loop().create_future() 760 self.change_state(Multiplexer.DISCONNECTING) 761 self.send_frame( 762 RFCOMM_Frame.disc( 763 c_r=1 if self.role == Multiplexer.INITIATOR else 0, dlci=0 764 ) 765 ) 766 await self.disconnection_result 767 768 async def open_dlc(self, channel): 769 if self.state != Multiplexer.CONNECTED: 770 if self.state == Multiplexer.OPENING: 771 raise InvalidStateError('open already in progress') 772 773 raise InvalidStateError('not connected') 774 775 pn = RFCOMM_MCC_PN( 776 dlci=channel << 1, 777 cl=0xF0, 778 priority=7, 779 ack_timer=0, 780 max_frame_size=RFCOMM_DEFAULT_PREFERRED_MTU, 781 max_retransmissions=0, 782 window_size=RFCOMM_DEFAULT_INITIAL_RX_CREDITS, 783 ) 784 mcc = RFCOMM_Frame.make_mcc(mcc_type=RFCOMM_MCC_PN_TYPE, c_r=1, data=bytes(pn)) 785 logger.debug(f'>>> Sending MCC: {pn}') 786 self.open_result = asyncio.get_running_loop().create_future() 787 self.change_state(Multiplexer.OPENING) 788 self.send_frame( 789 RFCOMM_Frame.uih( 790 c_r=1 if self.role == Multiplexer.INITIATOR else 0, 791 dlci=0, 792 information=mcc, 793 ) 794 ) 795 result = await self.open_result 796 self.open_result = None 797 return result 798 799 def on_dlc_open_complete(self, dlc): 800 logger.debug(f'DLC [{dlc.dlci}] open complete') 801 self.change_state(Multiplexer.CONNECTED) 802 if self.open_result: 803 self.open_result.set_result(dlc) 804 805 def __str__(self): 806 return f'Multiplexer(state={self.state_name(self.state)})' 807 808 809# ----------------------------------------------------------------------------- 810class Client: 811 def __init__(self, device, connection): 812 self.device = device 813 self.connection = connection 814 self.l2cap_channel = None 815 self.multiplexer = None 816 817 async def start(self): 818 # Create a new L2CAP connection 819 try: 820 self.l2cap_channel = await self.device.l2cap_channel_manager.connect( 821 self.connection, RFCOMM_PSM 822 ) 823 except ProtocolError as error: 824 logger.warning(f'L2CAP connection failed: {error}') 825 raise 826 827 # Create a mutliplexer to manage DLCs with the server 828 self.multiplexer = Multiplexer(self.l2cap_channel, Multiplexer.INITIATOR) 829 830 # Connect the multiplexer 831 await self.multiplexer.connect() 832 833 return self.multiplexer 834 835 async def shutdown(self): 836 # Disconnect the multiplexer 837 await self.multiplexer.disconnect() 838 self.multiplexer = None 839 840 # Close the L2CAP channel 841 # TODO 842 843 844# ----------------------------------------------------------------------------- 845class Server(EventEmitter): 846 def __init__(self, device): 847 super().__init__() 848 self.device = device 849 self.multiplexer = None 850 self.acceptors = {} 851 852 # Register ourselves with the L2CAP channel manager 853 device.register_l2cap_server(RFCOMM_PSM, self.on_connection) 854 855 def listen(self, acceptor, channel=0): 856 if channel: 857 if channel in self.acceptors: 858 # Busy 859 return 0 860 else: 861 # Find a free channel number 862 for candidate in range( 863 RFCOMM_DYNAMIC_CHANNEL_NUMBER_START, 864 RFCOMM_DYNAMIC_CHANNEL_NUMBER_END + 1, 865 ): 866 if candidate not in self.acceptors: 867 channel = candidate 868 break 869 870 if channel == 0: 871 # All channels used... 872 return 0 873 874 self.acceptors[channel] = acceptor 875 return channel 876 877 def on_connection(self, l2cap_channel): 878 logger.debug(f'+++ new L2CAP connection: {l2cap_channel}') 879 l2cap_channel.on('open', lambda: self.on_l2cap_channel_open(l2cap_channel)) 880 881 def on_l2cap_channel_open(self, l2cap_channel): 882 logger.debug(f'$$$ L2CAP channel open: {l2cap_channel}') 883 884 # Create a new multiplexer for the channel 885 multiplexer = Multiplexer(l2cap_channel, Multiplexer.RESPONDER) 886 multiplexer.acceptor = self.accept_dlc 887 multiplexer.on('dlc', self.on_dlc) 888 889 # Notify 890 self.emit('start', multiplexer) 891 892 def accept_dlc(self, channel_number): 893 return channel_number in self.acceptors 894 895 def on_dlc(self, dlc): 896 logger.debug(f'@@@ new DLC connected: {dlc}') 897 898 # Let the acceptor know 899 acceptor = self.acceptors.get(dlc.dlci >> 1) 900 if acceptor: 901 acceptor(dlc) 902