forked from auracaster/bumble_mirror
improve DLC parameters
This commit is contained in:
@@ -899,14 +899,26 @@ class L2capServer(StreamedPacketIO):
|
||||
# RfcommClient
|
||||
# -----------------------------------------------------------------------------
|
||||
class RfcommClient(StreamedPacketIO):
|
||||
def __init__(self, device, channel, uuid, l2cap_mtu, max_frame_size, window_size):
|
||||
def __init__(
|
||||
self,
|
||||
device,
|
||||
channel,
|
||||
uuid,
|
||||
l2cap_mtu,
|
||||
max_frame_size,
|
||||
initial_credits,
|
||||
max_credits,
|
||||
credits_threshold,
|
||||
):
|
||||
super().__init__()
|
||||
self.device = device
|
||||
self.channel = channel
|
||||
self.uuid = uuid
|
||||
self.l2cap_mtu = l2cap_mtu
|
||||
self.max_frame_size = max_frame_size
|
||||
self.window_size = window_size
|
||||
self.initial_credits = initial_credits
|
||||
self.max_credits = max_credits
|
||||
self.credits_threshold = credits_threshold
|
||||
self.rfcomm_session = None
|
||||
self.ready = asyncio.Event()
|
||||
|
||||
@@ -940,12 +952,17 @@ class RfcommClient(StreamedPacketIO):
|
||||
logging.info(color(f'### Opening session for channel {channel}...', 'yellow'))
|
||||
try:
|
||||
dlc_options = {}
|
||||
if self.max_frame_size:
|
||||
if self.max_frame_size is not None:
|
||||
dlc_options['max_frame_size'] = self.max_frame_size
|
||||
if self.window_size:
|
||||
dlc_options['window_size'] = self.window_size
|
||||
if self.initial_credits is not None:
|
||||
dlc_options['initial_credits'] = self.initial_credits
|
||||
rfcomm_session = await rfcomm_mux.open_dlc(channel, **dlc_options)
|
||||
logging.info(color(f'### Session open: {rfcomm_session}', 'yellow'))
|
||||
if self.max_credits is not None:
|
||||
rfcomm_session.rx_max_credits = self.max_credits
|
||||
if self.credits_threshold is not None:
|
||||
rfcomm_session.rx_credits_threshold = self.credits_threshold
|
||||
|
||||
except bumble.core.ConnectionError as error:
|
||||
logging.info(color(f'!!! Session open failed: {error}', 'red'))
|
||||
await rfcomm_mux.disconnect()
|
||||
@@ -969,8 +986,19 @@ class RfcommClient(StreamedPacketIO):
|
||||
# RfcommServer
|
||||
# -----------------------------------------------------------------------------
|
||||
class RfcommServer(StreamedPacketIO):
|
||||
def __init__(self, device, channel, l2cap_mtu):
|
||||
def __init__(
|
||||
self,
|
||||
device,
|
||||
channel,
|
||||
l2cap_mtu,
|
||||
max_frame_size,
|
||||
initial_credits,
|
||||
max_credits,
|
||||
credits_threshold,
|
||||
):
|
||||
super().__init__()
|
||||
self.max_credits = max_credits
|
||||
self.credits_threshold = credits_threshold
|
||||
self.dlc = None
|
||||
self.ready = asyncio.Event()
|
||||
|
||||
@@ -981,7 +1009,12 @@ class RfcommServer(StreamedPacketIO):
|
||||
rfcomm_server = bumble.rfcomm.Server(device, **server_options)
|
||||
|
||||
# Listen for incoming DLC connections
|
||||
channel_number = rfcomm_server.listen(self.on_dlc, channel)
|
||||
dlc_options = {}
|
||||
if max_frame_size is not None:
|
||||
dlc_options['max_frame_size'] = max_frame_size
|
||||
if initial_credits is not None:
|
||||
dlc_options['initial_credits'] = initial_credits
|
||||
channel_number = rfcomm_server.listen(self.on_dlc, channel, **dlc_options)
|
||||
|
||||
# Setup the SDP to advertise this channel
|
||||
device.sdp_service_records = make_sdp_records(channel_number)
|
||||
@@ -1004,6 +1037,10 @@ class RfcommServer(StreamedPacketIO):
|
||||
dlc.sink = self.on_packet
|
||||
self.io_sink = dlc.write
|
||||
self.dlc = dlc
|
||||
if self.max_credits is not None:
|
||||
dlc.rx_max_credits = self.max_credits
|
||||
if self.credits_threshold is not None:
|
||||
dlc.rx_credits_threshold = self.credits_threshold
|
||||
|
||||
async def drain(self):
|
||||
assert self.dlc
|
||||
@@ -1321,7 +1358,9 @@ def create_mode_factory(ctx, default_mode):
|
||||
uuid=ctx.obj['rfcomm_uuid'],
|
||||
l2cap_mtu=ctx.obj['rfcomm_l2cap_mtu'],
|
||||
max_frame_size=ctx.obj['rfcomm_max_frame_size'],
|
||||
window_size=ctx.obj['rfcomm_window_size'],
|
||||
initial_credits=ctx.obj['rfcomm_initial_credits'],
|
||||
max_credits=ctx.obj['rfcomm_max_credits'],
|
||||
credits_threshold=ctx.obj['rfcomm_credits_threshold'],
|
||||
)
|
||||
|
||||
if mode == 'rfcomm-server':
|
||||
@@ -1329,6 +1368,10 @@ def create_mode_factory(ctx, default_mode):
|
||||
device,
|
||||
channel=ctx.obj['rfcomm_channel'],
|
||||
l2cap_mtu=ctx.obj['rfcomm_l2cap_mtu'],
|
||||
max_frame_size=ctx.obj['rfcomm_max_frame_size'],
|
||||
initial_credits=ctx.obj['rfcomm_initial_credits'],
|
||||
max_credits=ctx.obj['rfcomm_max_credits'],
|
||||
credits_threshold=ctx.obj['rfcomm_credits_threshold'],
|
||||
)
|
||||
|
||||
raise ValueError('invalid mode')
|
||||
@@ -1427,9 +1470,19 @@ def create_role_factory(ctx, default_role):
|
||||
help='RFComm maximum frame size',
|
||||
)
|
||||
@click.option(
|
||||
'--rfcomm-window-size',
|
||||
'--rfcomm-initial-credits',
|
||||
type=int,
|
||||
help='RFComm window size',
|
||||
help='RFComm initial credits',
|
||||
)
|
||||
@click.option(
|
||||
'--rfcomm-max-credits',
|
||||
type=int,
|
||||
help='RFComm max credits',
|
||||
)
|
||||
@click.option(
|
||||
'--rfcomm-credits-threshold',
|
||||
type=int,
|
||||
help='RFComm credits threshold',
|
||||
)
|
||||
@click.option(
|
||||
'--l2cap-psm',
|
||||
@@ -1530,7 +1583,9 @@ def bench(
|
||||
rfcomm_uuid,
|
||||
rfcomm_l2cap_mtu,
|
||||
rfcomm_max_frame_size,
|
||||
rfcomm_window_size,
|
||||
rfcomm_initial_credits,
|
||||
rfcomm_max_credits,
|
||||
rfcomm_credits_threshold,
|
||||
l2cap_psm,
|
||||
l2cap_mtu,
|
||||
l2cap_mps,
|
||||
@@ -1545,7 +1600,9 @@ def bench(
|
||||
ctx.obj['rfcomm_uuid'] = rfcomm_uuid
|
||||
ctx.obj['rfcomm_l2cap_mtu'] = rfcomm_l2cap_mtu
|
||||
ctx.obj['rfcomm_max_frame_size'] = rfcomm_max_frame_size
|
||||
ctx.obj['rfcomm_window_size'] = rfcomm_window_size
|
||||
ctx.obj['rfcomm_initial_credits'] = rfcomm_initial_credits
|
||||
ctx.obj['rfcomm_max_credits'] = rfcomm_max_credits
|
||||
ctx.obj['rfcomm_credits_threshold'] = rfcomm_credits_threshold
|
||||
ctx.obj['l2cap_psm'] = l2cap_psm
|
||||
ctx.obj['l2cap_mtu'] = l2cap_mtu
|
||||
ctx.obj['l2cap_mps'] = l2cap_mps
|
||||
|
||||
@@ -24,7 +24,7 @@ from typing import Optional
|
||||
import click
|
||||
|
||||
from bumble.colors import color
|
||||
from bumble.device import Device, Connection
|
||||
from bumble.device import Device, DeviceConfiguration, Connection
|
||||
from bumble import core
|
||||
from bumble import hci
|
||||
from bumble import rfcomm
|
||||
@@ -37,7 +37,8 @@ from bumble import utils
|
||||
# -----------------------------------------------------------------------------
|
||||
DEFAULT_RFCOMM_UUID = "E6D55659-C8B4-4B85-96BB-B1143AF6D3AE"
|
||||
DEFAULT_MTU = 4096
|
||||
DEFAULT_TCP_PORT = 9544
|
||||
DEFAULT_CLIENT_TCP_PORT = 9544
|
||||
DEFAULT_SERVER_TCP_PORT = 9545
|
||||
|
||||
TRACE_MAX_SIZE = 48
|
||||
|
||||
@@ -149,7 +150,7 @@ class ServerBridge:
|
||||
|
||||
def on_disconnection(self, reason: int):
|
||||
print(
|
||||
color("@@@ Bluetooth disconnection:", "blue"),
|
||||
color("@@@ Bluetooth disconnection:", "red"),
|
||||
hci.HCI_Constant.error_name(reason),
|
||||
)
|
||||
|
||||
@@ -271,6 +272,7 @@ class ClientBridge:
|
||||
self.address, transport=core.BT_BR_EDR_TRANSPORT
|
||||
)
|
||||
print(color(f"@@@ Bluetooth connection: {self.connection}", "blue"))
|
||||
self.connection.on("disconnection", self.on_disconnection)
|
||||
|
||||
if self.encrypt:
|
||||
print(color("@@@ Encrypting Bluetooth connection", "blue"))
|
||||
@@ -278,19 +280,16 @@ class ClientBridge:
|
||||
print(color("@@@ Bluetooth connection encrypted", "blue"))
|
||||
|
||||
self.rfcomm_client = rfcomm.Client(self.connection)
|
||||
try:
|
||||
self.rfcomm_mux = await self.rfcomm_client.start()
|
||||
|
||||
def on_disconnection(reason):
|
||||
print(
|
||||
color("@@@ Bluetooth disconnection:", "red"),
|
||||
hci.HCI_Constant.error_name(reason),
|
||||
)
|
||||
self.connection = None
|
||||
|
||||
self.connection.on("disconnection", on_disconnection)
|
||||
except BaseException as e:
|
||||
print(color("!!! Failed to setup RFCOMM connection", "red"), e)
|
||||
raise
|
||||
|
||||
async def start(self, device: Device) -> None:
|
||||
self.device = device
|
||||
await device.set_connectable(False)
|
||||
await device.set_discoverable(False)
|
||||
|
||||
# Called when a TCP connection is established
|
||||
async def on_tcp_connection(reader, writer):
|
||||
@@ -303,9 +302,15 @@ class ClientBridge:
|
||||
return
|
||||
self.tcp_connected = True
|
||||
|
||||
try:
|
||||
await self.pipe(reader, writer)
|
||||
except BaseException as error:
|
||||
print(color("!!! Exception while piping data:", "red"), error)
|
||||
return
|
||||
finally:
|
||||
writer.close()
|
||||
await writer.wait_closed()
|
||||
self.tcp_connected = False
|
||||
|
||||
await asyncio.start_server(
|
||||
on_tcp_connection,
|
||||
@@ -339,12 +344,12 @@ class ClientBridge:
|
||||
# Connect a new RFCOMM channel
|
||||
await self.connect()
|
||||
assert self.rfcomm_mux
|
||||
print(color(f'*** Opening RFCOMM channel {channel}', 'green'))
|
||||
print(color(f"*** Opening RFCOMM channel {channel}", "green"))
|
||||
try:
|
||||
rfcomm_channel = await self.rfcomm_mux.open_dlc(channel)
|
||||
print(color(f'*** RFCOMM channel open: {rfcomm_channel}', "green"))
|
||||
print(color(f"*** RFCOMM channel open: {rfcomm_channel}", "green"))
|
||||
except Exception as error:
|
||||
print(color(f'!!! RFCOMM open failed: {error}', 'red'))
|
||||
print(color(f"!!! RFCOMM open failed: {error}", "red"))
|
||||
return
|
||||
|
||||
# Pipe data from RFCOMM to TCP
|
||||
@@ -383,6 +388,13 @@ class ClientBridge:
|
||||
|
||||
print(color("~~~ Bye bye", "magenta"))
|
||||
|
||||
def on_disconnection(self, reason: int) -> None:
|
||||
print(
|
||||
color("@@@ Bluetooth disconnection:", "red"),
|
||||
hci.HCI_Constant.error_name(reason),
|
||||
)
|
||||
self.connection = None
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
async def run(device_config, hci_transport, bridge):
|
||||
@@ -393,7 +405,14 @@ async def run(device_config, hci_transport, bridge):
|
||||
):
|
||||
print("<<< connected")
|
||||
|
||||
device = Device.from_config_file_with_hci(device_config, hci_source, hci_sink)
|
||||
if device_config:
|
||||
device = Device.from_config_file_with_hci(
|
||||
device_config, hci_source, hci_sink
|
||||
)
|
||||
else:
|
||||
device = Device.from_config_with_hci(
|
||||
DeviceConfiguration(), hci_source, hci_sink
|
||||
)
|
||||
device.classic_enabled = True
|
||||
|
||||
# Let's go
|
||||
@@ -412,11 +431,28 @@ async def run(device_config, hci_transport, bridge):
|
||||
# -----------------------------------------------------------------------------
|
||||
@click.group()
|
||||
@click.pass_context
|
||||
@click.option("--device-config", help="Device configuration file", required=True)
|
||||
@click.option("--hci-transport", help="HCI transport", required=True)
|
||||
@click.option(
|
||||
"--device-config",
|
||||
metavar="CONFIG_FILE",
|
||||
help="Device configuration file",
|
||||
)
|
||||
@click.option(
|
||||
"--hci-transport", metavar="TRANSPORT_NAME", help="HCI transport", required=True
|
||||
)
|
||||
@click.option("--trace", is_flag=True, help="Trace bridged data to stdout")
|
||||
@click.option("--channel", help="RFCOMM channel number", type=int, default=0)
|
||||
@click.option("--uuid", help="UUID for the RFCOMM channel", default=DEFAULT_RFCOMM_UUID)
|
||||
@click.option(
|
||||
"--channel",
|
||||
metavar="CHANNEL_NUMER",
|
||||
help="RFCOMM channel number",
|
||||
type=int,
|
||||
default=0,
|
||||
)
|
||||
@click.option(
|
||||
"--uuid",
|
||||
metavar="UUID",
|
||||
help="UUID for the RFCOMM channel",
|
||||
default=DEFAULT_RFCOMM_UUID,
|
||||
)
|
||||
def cli(
|
||||
context,
|
||||
device_config,
|
||||
@@ -437,7 +473,7 @@ def cli(
|
||||
@cli.command()
|
||||
@click.pass_context
|
||||
@click.option("--tcp-host", help="TCP host", default="localhost")
|
||||
@click.option("--tcp-port", help="TCP port", default=DEFAULT_TCP_PORT)
|
||||
@click.option("--tcp-port", help="TCP port", default=DEFAULT_SERVER_TCP_PORT)
|
||||
def server(context, tcp_host, tcp_port):
|
||||
bridge = ServerBridge(
|
||||
context.obj["channel"],
|
||||
@@ -454,7 +490,7 @@ def server(context, tcp_host, tcp_port):
|
||||
@click.pass_context
|
||||
@click.argument("bluetooth-address")
|
||||
@click.option("--tcp-host", help="TCP host", default="_")
|
||||
@click.option("--tcp-port", help="TCP port", default=DEFAULT_TCP_PORT)
|
||||
@click.option("--tcp-port", help="TCP port", default=DEFAULT_CLIENT_TCP_PORT)
|
||||
@click.option("--encrypt", is_flag=True, help="Encrypt the connection")
|
||||
def client(context, bluetooth_address, tcp_host, tcp_port, encrypt):
|
||||
bridge = ClientBridge(
|
||||
|
||||
@@ -833,7 +833,9 @@ class ClassicChannel(EventEmitter):
|
||||
|
||||
# Wait for the connection to succeed or fail
|
||||
try:
|
||||
return await self.connection_result
|
||||
return await self.connection.abort_on(
|
||||
'disconnection', self.connection_result
|
||||
)
|
||||
finally:
|
||||
self.connection_result = None
|
||||
|
||||
@@ -2226,7 +2228,7 @@ class ChannelManager:
|
||||
# Connect
|
||||
try:
|
||||
await channel.connect()
|
||||
except Exception as e:
|
||||
except BaseException as e:
|
||||
del connection_channels[source_cid]
|
||||
raise e
|
||||
|
||||
|
||||
103
bumble/rfcomm.py
103
bumble/rfcomm.py
@@ -107,7 +107,9 @@ CRC_TABLE = bytes([
|
||||
])
|
||||
|
||||
RFCOMM_DEFAULT_L2CAP_MTU = 2048
|
||||
RFCOMM_DEFAULT_WINDOW_SIZE = 7
|
||||
RFCOMM_DEFAULT_INITIAL_CREDITS = 7
|
||||
RFCOMM_DEFAULT_MAX_CREDITS = 32
|
||||
RFCOMM_DEFAULT_CREDIT_THRESHOLD = RFCOMM_DEFAULT_MAX_CREDITS // 2
|
||||
RFCOMM_DEFAULT_MAX_FRAME_SIZE = 2000
|
||||
|
||||
RFCOMM_DYNAMIC_CHANNEL_NUMBER_START = 1
|
||||
@@ -365,12 +367,12 @@ class RFCOMM_MCC_PN:
|
||||
ack_timer: int
|
||||
max_frame_size: int
|
||||
max_retransmissions: int
|
||||
window_size: int
|
||||
initial_credits: int
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if self.window_size < 1 or self.window_size > 7:
|
||||
if self.initial_credits < 1 or self.initial_credits > 7:
|
||||
logger.warning(
|
||||
f'Error Recovery Window size {self.window_size} is out of range [1, 7].'
|
||||
f'Initial credits {self.initial_credits} is out of range [1, 7].'
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@@ -382,7 +384,7 @@ class RFCOMM_MCC_PN:
|
||||
ack_timer=data[3],
|
||||
max_frame_size=data[4] | data[5] << 8,
|
||||
max_retransmissions=data[6],
|
||||
window_size=data[7] & 0x07,
|
||||
initial_credits=data[7] & 0x07,
|
||||
)
|
||||
|
||||
def __bytes__(self) -> bytes:
|
||||
@@ -396,7 +398,7 @@ class RFCOMM_MCC_PN:
|
||||
(self.max_frame_size >> 8) & 0xFF,
|
||||
self.max_retransmissions & 0xFF,
|
||||
# Only 3 bits are meaningful.
|
||||
self.window_size & 0x07,
|
||||
self.initial_credits & 0x07,
|
||||
]
|
||||
)
|
||||
|
||||
@@ -450,17 +452,21 @@ class DLC(EventEmitter):
|
||||
self,
|
||||
multiplexer: Multiplexer,
|
||||
dlci: int,
|
||||
max_frame_size: int,
|
||||
window_size: int,
|
||||
tx_max_frame_size: int,
|
||||
tx_initial_credits: int,
|
||||
rx_max_frame_size: int,
|
||||
rx_initial_credits: int,
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self.multiplexer = multiplexer
|
||||
self.dlci = dlci
|
||||
self.max_frame_size = max_frame_size
|
||||
self.window_size = window_size
|
||||
self.rx_credits = window_size
|
||||
self.rx_threshold = window_size // 2
|
||||
self.tx_credits = window_size
|
||||
self.rx_max_frame_size = rx_max_frame_size
|
||||
self.rx_initial_credits = rx_initial_credits
|
||||
self.rx_max_credits = RFCOMM_DEFAULT_MAX_CREDITS
|
||||
self.rx_credits = rx_initial_credits
|
||||
self.rx_credits_threshold = RFCOMM_DEFAULT_CREDIT_THRESHOLD
|
||||
self.tx_max_frame_size = tx_max_frame_size
|
||||
self.tx_credits = tx_initial_credits
|
||||
self.tx_buffer = b''
|
||||
self.state = DLC.State.INIT
|
||||
self.role = multiplexer.role
|
||||
@@ -478,7 +484,7 @@ class DLC(EventEmitter):
|
||||
# Compute the MTU
|
||||
max_overhead = 4 + 1 # header with 2-byte length + fcs
|
||||
self.mtu = min(
|
||||
max_frame_size, self.multiplexer.l2cap_channel.peer_mtu - max_overhead
|
||||
tx_max_frame_size, self.multiplexer.l2cap_channel.peer_mtu - max_overhead
|
||||
)
|
||||
|
||||
@property
|
||||
@@ -645,9 +651,9 @@ class DLC(EventEmitter):
|
||||
cl=0xE0,
|
||||
priority=7,
|
||||
ack_timer=0,
|
||||
max_frame_size=self.max_frame_size,
|
||||
max_frame_size=self.rx_max_frame_size,
|
||||
max_retransmissions=0,
|
||||
window_size=self.window_size,
|
||||
initial_credits=self.rx_initial_credits,
|
||||
)
|
||||
mcc = RFCOMM_Frame.make_mcc(mcc_type=MccType.PN, c_r=0, data=bytes(pn))
|
||||
logger.debug(f'>>> PN Response: {pn}')
|
||||
@@ -655,8 +661,8 @@ class DLC(EventEmitter):
|
||||
self.change_state(DLC.State.CONNECTING)
|
||||
|
||||
def rx_credits_needed(self) -> int:
|
||||
if self.rx_credits <= self.rx_threshold:
|
||||
return self.window_size - self.rx_credits
|
||||
if self.rx_credits <= self.rx_credits_threshold:
|
||||
return self.rx_max_credits - self.rx_credits
|
||||
|
||||
return 0
|
||||
|
||||
@@ -749,7 +755,7 @@ class Multiplexer(EventEmitter):
|
||||
connection_result: Optional[asyncio.Future]
|
||||
disconnection_result: Optional[asyncio.Future]
|
||||
open_result: Optional[asyncio.Future]
|
||||
acceptor: Optional[Callable[[int], bool]]
|
||||
acceptor: Optional[Callable[[int], Optional[Tuple[int, int]]]]
|
||||
dlcs: Dict[int, DLC]
|
||||
|
||||
def __init__(self, l2cap_channel: l2cap.ClassicChannel, role: Role) -> None:
|
||||
@@ -761,6 +767,8 @@ class Multiplexer(EventEmitter):
|
||||
self.connection_result = None
|
||||
self.disconnection_result = None
|
||||
self.open_result = None
|
||||
self.open_pn: Optional[RFCOMM_MCC_PN] = None
|
||||
self.open_rx_max_credits = 0
|
||||
self.acceptor = None
|
||||
|
||||
# Become a sink for the L2CAP channel
|
||||
@@ -869,9 +877,16 @@ class Multiplexer(EventEmitter):
|
||||
else:
|
||||
if self.acceptor:
|
||||
channel_number = pn.dlci >> 1
|
||||
if self.acceptor(channel_number):
|
||||
if dlc_params := self.acceptor(channel_number):
|
||||
# Create a new DLC
|
||||
dlc = DLC(self, pn.dlci, pn.max_frame_size, pn.window_size)
|
||||
dlc = DLC(
|
||||
self,
|
||||
dlci=pn.dlci,
|
||||
tx_max_frame_size=pn.max_frame_size,
|
||||
tx_initial_credits=pn.initial_credits,
|
||||
rx_max_frame_size=dlc_params[0],
|
||||
rx_initial_credits=dlc_params[1],
|
||||
)
|
||||
self.dlcs[pn.dlci] = dlc
|
||||
|
||||
# Re-emit the handshake completion event
|
||||
@@ -889,8 +904,17 @@ class Multiplexer(EventEmitter):
|
||||
# Response
|
||||
logger.debug(f'>>> PN Response: {pn}')
|
||||
if self.state == Multiplexer.State.OPENING:
|
||||
dlc = DLC(self, pn.dlci, pn.max_frame_size, pn.window_size)
|
||||
assert self.open_pn
|
||||
dlc = DLC(
|
||||
self,
|
||||
dlci=pn.dlci,
|
||||
tx_max_frame_size=pn.max_frame_size,
|
||||
tx_initial_credits=pn.initial_credits,
|
||||
rx_max_frame_size=self.open_pn.max_frame_size,
|
||||
rx_initial_credits=self.open_pn.initial_credits,
|
||||
)
|
||||
self.dlcs[pn.dlci] = dlc
|
||||
self.open_pn = None
|
||||
dlc.connect()
|
||||
else:
|
||||
logger.warning('ignoring PN response')
|
||||
@@ -928,7 +952,7 @@ class Multiplexer(EventEmitter):
|
||||
self,
|
||||
channel: int,
|
||||
max_frame_size: int = RFCOMM_DEFAULT_MAX_FRAME_SIZE,
|
||||
window_size: int = RFCOMM_DEFAULT_WINDOW_SIZE,
|
||||
initial_credits: int = RFCOMM_DEFAULT_INITIAL_CREDITS,
|
||||
) -> DLC:
|
||||
if self.state != Multiplexer.State.CONNECTED:
|
||||
if self.state == Multiplexer.State.OPENING:
|
||||
@@ -936,17 +960,19 @@ class Multiplexer(EventEmitter):
|
||||
|
||||
raise InvalidStateError('not connected')
|
||||
|
||||
pn = RFCOMM_MCC_PN(
|
||||
self.open_pn = RFCOMM_MCC_PN(
|
||||
dlci=channel << 1,
|
||||
cl=0xF0,
|
||||
priority=7,
|
||||
ack_timer=0,
|
||||
max_frame_size=max_frame_size,
|
||||
max_retransmissions=0,
|
||||
window_size=window_size,
|
||||
initial_credits=initial_credits,
|
||||
)
|
||||
mcc = RFCOMM_Frame.make_mcc(mcc_type=MccType.PN, c_r=1, data=bytes(pn))
|
||||
logger.debug(f'>>> Sending MCC: {pn}')
|
||||
mcc = RFCOMM_Frame.make_mcc(
|
||||
mcc_type=MccType.PN, c_r=1, data=bytes(self.open_pn)
|
||||
)
|
||||
logger.debug(f'>>> Sending MCC: {self.open_pn}')
|
||||
self.open_result = asyncio.get_running_loop().create_future()
|
||||
self.change_state(Multiplexer.State.OPENING)
|
||||
self.send_frame(
|
||||
@@ -1039,15 +1065,13 @@ class Client:
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
class Server(EventEmitter):
|
||||
acceptors: Dict[int, Callable[[DLC], None]]
|
||||
|
||||
def __init__(
|
||||
self, device: Device, l2cap_mtu: int = RFCOMM_DEFAULT_L2CAP_MTU
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self.device = device
|
||||
self.multiplexer = None
|
||||
self.acceptors = {}
|
||||
self.acceptors: Dict[int, Callable[[DLC], None]] = {}
|
||||
self.dlc_configs: Dict[int, Tuple[int, int]] = {}
|
||||
|
||||
# Register ourselves with the L2CAP channel manager
|
||||
self.l2cap_server = device.create_l2cap_server(
|
||||
@@ -1055,7 +1079,13 @@ class Server(EventEmitter):
|
||||
handler=self.on_connection,
|
||||
)
|
||||
|
||||
def listen(self, acceptor: Callable[[DLC], None], channel: int = 0) -> int:
|
||||
def listen(
|
||||
self,
|
||||
acceptor: Callable[[DLC], None],
|
||||
channel: int = 0,
|
||||
max_frame_size: int = RFCOMM_DEFAULT_MAX_FRAME_SIZE,
|
||||
initial_credits: int = RFCOMM_DEFAULT_INITIAL_CREDITS,
|
||||
) -> int:
|
||||
if channel:
|
||||
if channel in self.acceptors:
|
||||
# Busy
|
||||
@@ -1075,6 +1105,8 @@ class Server(EventEmitter):
|
||||
return 0
|
||||
|
||||
self.acceptors[channel] = acceptor
|
||||
self.dlc_configs[channel] = (max_frame_size, initial_credits)
|
||||
|
||||
return channel
|
||||
|
||||
def on_connection(self, l2cap_channel: l2cap.ClassicChannel) -> None:
|
||||
@@ -1092,15 +1124,14 @@ class Server(EventEmitter):
|
||||
# Notify
|
||||
self.emit('start', multiplexer)
|
||||
|
||||
def accept_dlc(self, channel_number: int) -> bool:
|
||||
return channel_number in self.acceptors
|
||||
def accept_dlc(self, channel_number: int) -> Optional[Tuple[int, int]]:
|
||||
return self.dlc_configs.get(channel_number)
|
||||
|
||||
def on_dlc(self, dlc: DLC) -> None:
|
||||
logger.debug(f'@@@ new DLC connected: {dlc}')
|
||||
|
||||
# Let the acceptor know
|
||||
acceptor = self.acceptors.get(dlc.dlci >> 1)
|
||||
if acceptor:
|
||||
if acceptor := self.acceptors.get(dlc.dlci >> 1):
|
||||
acceptor(dlc)
|
||||
|
||||
def __enter__(self) -> Self:
|
||||
|
||||
Reference in New Issue
Block a user