• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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