Cleanup legacy L2CAP API

This commit is contained in:
Josh Wu
2023-10-11 13:49:02 +08:00
parent 7255a09705
commit 5a85765360
14 changed files with 98 additions and 54 deletions

View File

@@ -24,6 +24,7 @@ import time
import click import click
from bumble import l2cap
from bumble.core import ( from bumble.core import (
BT_BR_EDR_TRANSPORT, BT_BR_EDR_TRANSPORT,
BT_LE_TRANSPORT, BT_LE_TRANSPORT,
@@ -85,6 +86,7 @@ DEFAULT_LINGER_TIME = 1.0
DEFAULT_RFCOMM_CHANNEL = 8 DEFAULT_RFCOMM_CHANNEL = 8
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# Utils # Utils
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
@@ -197,6 +199,7 @@ class PacketType(enum.IntEnum):
PACKET_FLAG_LAST = 1 PACKET_FLAG_LAST = 1
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# Sender # Sender
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
@@ -659,17 +662,19 @@ class L2capClient(StreamedPacketIO):
self.mps = mps self.mps = mps
self.ready = asyncio.Event() self.ready = asyncio.Event()
async def on_connection(self, connection): async def on_connection(self, connection: Connection) -> None:
connection.on('disconnection', self.on_disconnection) connection.on('disconnection', self.on_disconnection)
# Connect a new L2CAP channel # Connect a new L2CAP channel
print(color(f'>>> Opening L2CAP channel on PSM = {self.psm}', 'yellow')) print(color(f'>>> Opening L2CAP channel on PSM = {self.psm}', 'yellow'))
try: try:
l2cap_channel = await connection.open_l2cap_channel( l2cap_channel = await connection.create_l2cap_channel(
psm=self.psm, spec=l2cap.LeCreditBasedChannelSpec(
max_credits=self.max_credits, psm=self.psm,
mtu=self.mtu, max_credits=self.max_credits,
mps=self.mps, mtu=self.mtu,
mps=self.mps,
)
) )
print(color('*** L2CAP channel:', 'cyan'), l2cap_channel) print(color('*** L2CAP channel:', 'cyan'), l2cap_channel)
except Exception as error: except Exception as error:
@@ -695,7 +700,7 @@ class L2capClient(StreamedPacketIO):
class L2capServer(StreamedPacketIO): class L2capServer(StreamedPacketIO):
def __init__( def __init__(
self, self,
device, device: Device,
psm=DEFAULT_L2CAP_PSM, psm=DEFAULT_L2CAP_PSM,
max_credits=DEFAULT_L2CAP_MAX_CREDITS, max_credits=DEFAULT_L2CAP_MAX_CREDITS,
mtu=DEFAULT_L2CAP_MTU, mtu=DEFAULT_L2CAP_MTU,
@@ -706,12 +711,11 @@ class L2capServer(StreamedPacketIO):
self.ready = asyncio.Event() self.ready = asyncio.Event()
# Listen for incoming L2CAP CoC connections # Listen for incoming L2CAP CoC connections
device.register_l2cap_channel_server( device.create_l2cap_server(
psm=psm, spec=l2cap.LeCreditBasedChannelSpec(
server=self.on_l2cap_channel, psm=psm, mtu=mtu, mps=mps, max_credits=max_credits
max_credits=max_credits, ),
mtu=mtu, handler=self.on_l2cap_channel,
mps=mps,
) )
print(color(f'### Listening for CoC connection on PSM {psm}', 'yellow')) print(color(f'### Listening for CoC connection on PSM {psm}', 'yellow'))

View File

@@ -21,6 +21,7 @@ import struct
import logging import logging
import click import click
from bumble import l2cap
from bumble.colors import color from bumble.colors import color
from bumble.device import Device, Peer from bumble.device import Device, Peer
from bumble.core import AdvertisingData from bumble.core import AdvertisingData
@@ -204,7 +205,7 @@ class GattlinkHubBridge(GattlinkL2capEndpoint, Device.Listener):
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
class GattlinkNodeBridge(GattlinkL2capEndpoint, Device.Listener): class GattlinkNodeBridge(GattlinkL2capEndpoint, Device.Listener):
def __init__(self, device): def __init__(self, device: Device):
super().__init__() super().__init__()
self.device = device self.device = device
self.peer = None self.peer = None
@@ -218,7 +219,12 @@ class GattlinkNodeBridge(GattlinkL2capEndpoint, Device.Listener):
# Listen for incoming L2CAP CoC connections # Listen for incoming L2CAP CoC connections
psm = 0xFB psm = 0xFB
device.register_l2cap_channel_server(0xFB, self.on_coc) device.create_l2cap_server(
spec=l2cap.LeCreditBasedChannelSpec(
psm=0xFB,
),
handler=self.on_coc,
)
print(f'### Listening for CoC connection on PSM {psm}') print(f'### Listening for CoC connection on PSM {psm}')
# Setup the Gattlink service # Setup the Gattlink service

View File

@@ -20,6 +20,7 @@ import logging
import os import os
import click import click
from bumble import l2cap
from bumble.colors import color from bumble.colors import color
from bumble.transport import open_transport_or_link from bumble.transport import open_transport_or_link
from bumble.device import Device from bumble.device import Device
@@ -47,14 +48,13 @@ class ServerBridge:
self.tcp_host = tcp_host self.tcp_host = tcp_host
self.tcp_port = tcp_port self.tcp_port = tcp_port
async def start(self, device): async def start(self, device: Device) -> None:
# Listen for incoming L2CAP CoC connections # Listen for incoming L2CAP CoC connections
device.register_l2cap_channel_server( device.create_l2cap_server(
psm=self.psm, spec=l2cap.LeCreditBasedChannelSpec(
server=self.on_coc, psm=self.psm, mtu=self.mtu, mps=self.mps, max_credits=self.max_credits
max_credits=self.max_credits, ),
mtu=self.mtu, handler=self.on_coc,
mps=self.mps,
) )
print(color(f'### Listening for CoC connection on PSM {self.psm}', 'yellow')) print(color(f'### Listening for CoC connection on PSM {self.psm}', 'yellow'))
@@ -195,11 +195,13 @@ class ClientBridge:
# Connect a new L2CAP channel # Connect a new L2CAP channel
print(color(f'>>> Opening L2CAP channel on PSM = {self.psm}', 'yellow')) print(color(f'>>> Opening L2CAP channel on PSM = {self.psm}', 'yellow'))
try: try:
l2cap_channel = await connection.open_l2cap_channel( l2cap_channel = await connection.create_l2cap_channel(
psm=self.psm, spec=l2cap.LeCreditBasedChannelSpec(
max_credits=self.max_credits, psm=self.psm,
mtu=self.mtu, max_credits=self.max_credits,
mps=self.mps, mtu=self.mtu,
mps=self.mps,
)
) )
print(color('*** L2CAP channel:', 'cyan'), l2cap_channel) print(color('*** L2CAP channel:', 'cyan'), l2cap_channel)
except Exception as error: except Exception as error:

View File

@@ -641,7 +641,7 @@ class Speaker:
self.device.on('connection', self.on_bluetooth_connection) self.device.on('connection', self.on_bluetooth_connection)
# Create a listener to wait for AVDTP connections # Create a listener to wait for AVDTP connections
self.listener = Listener(Listener.create_registrar(self.device)) self.listener = Listener.for_device(self.device)
self.listener.on('connection', self.on_avdtp_connection) self.listener.on('connection', self.on_avdtp_connection)
print(f'Speaker ready to play, codec={color(self.codec, "cyan")}') print(f'Speaker ready to play, codec={color(self.codec, "cyan")}')

View File

@@ -1036,9 +1036,11 @@ class LeCreditBasedChannel(EventEmitter):
out_queue: Deque[bytes] out_queue: Deque[bytes]
connection_result: Optional[asyncio.Future[LeCreditBasedChannel]] connection_result: Optional[asyncio.Future[LeCreditBasedChannel]]
disconnection_result: Optional[asyncio.Future[None]] disconnection_result: Optional[asyncio.Future[None]]
in_sdu: Optional[bytes]
out_sdu: Optional[bytes] out_sdu: Optional[bytes]
state: State state: State
connection: Connection connection: Connection
sink: Optional[Callable[[bytes], Any]]
def __init__( def __init__(
self, self,
@@ -1525,7 +1527,7 @@ class ChannelManager:
if cid in self.fixed_channels: if cid in self.fixed_channels:
del self.fixed_channels[cid] del self.fixed_channels[cid]
@deprecated("Please use create_classic_channel_server") @deprecated("Please use create_classic_server")
def register_server( def register_server(
self, self,
psm: int, psm: int,
@@ -1540,7 +1542,7 @@ class ChannelManager:
spec: ClassicChannelSpec, spec: ClassicChannelSpec,
handler: Optional[Callable[[ClassicChannel], Any]] = None, handler: Optional[Callable[[ClassicChannel], Any]] = None,
) -> ClassicChannelServer: ) -> ClassicChannelServer:
if spec.psm is None: if not spec.psm:
# Find a free PSM # Find a free PSM
for candidate in range( for candidate in range(
L2CAP_PSM_DYNAMIC_RANGE_START, L2CAP_PSM_DYNAMIC_RANGE_END + 1, 2 L2CAP_PSM_DYNAMIC_RANGE_START, L2CAP_PSM_DYNAMIC_RANGE_END + 1, 2
@@ -1592,7 +1594,7 @@ class ChannelManager:
spec: LeCreditBasedChannelSpec, spec: LeCreditBasedChannelSpec,
handler: Optional[Callable[[LeCreditBasedChannel], Any]] = None, handler: Optional[Callable[[LeCreditBasedChannel], Any]] = None,
) -> LeCreditBasedChannelServer: ) -> LeCreditBasedChannelServer:
if spec.psm is None: if not spec.psm:
# Find a free PSM # Find a free PSM
for candidate in range( for candidate in range(
L2CAP_LE_PSM_DYNAMIC_RANGE_START, L2CAP_LE_PSM_DYNAMIC_RANGE_END + 1 L2CAP_LE_PSM_DYNAMIC_RANGE_START, L2CAP_LE_PSM_DYNAMIC_RANGE_END + 1

View File

@@ -19,6 +19,8 @@
import struct import struct
import logging import logging
from typing import List from typing import List
from bumble import l2cap
from ..core import AdvertisingData from ..core import AdvertisingData
from ..device import Device, Connection from ..device import Device, Connection
from ..gatt import ( from ..gatt import (
@@ -149,7 +151,10 @@ class AshaService(TemplateService):
channel.sink = on_data channel.sink = on_data
# let the server find a free PSM # let the server find a free PSM
self.psm = self.device.register_l2cap_channel_server(self.psm, on_coc, 8) self.psm = device.create_l2cap_server(
spec=l2cap.LeCreditBasedChannelSpec(psm=self.psm, max_credits=8),
handler=on_coc,
).psm
self.le_psm_out_characteristic = Characteristic( self.le_psm_out_characteristic = Characteristic(
GATT_ASHA_LE_PSM_OUT_CHARACTERISTIC, GATT_ASHA_LE_PSM_OUT_CHARACTERISTIC,
Characteristic.Properties.READ, Characteristic.Properties.READ,

View File

@@ -898,8 +898,8 @@ class Client:
async def start(self) -> Multiplexer: async def start(self) -> Multiplexer:
# Create a new L2CAP connection # Create a new L2CAP connection
try: try:
self.l2cap_channel = await self.device.l2cap_channel_manager.connect( self.l2cap_channel = await self.connection.create_l2cap_channel(
self.connection, RFCOMM_PSM spec=l2cap.ClassicChannelSpec(RFCOMM_PSM)
) )
except ProtocolError as error: except ProtocolError as error:
logger.warning(f'L2CAP connection failed: {error}') logger.warning(f'L2CAP connection failed: {error}')
@@ -936,7 +936,9 @@ class Server(EventEmitter):
self.acceptors = {} self.acceptors = {}
# Register ourselves with the L2CAP channel manager # Register ourselves with the L2CAP channel manager
device.register_l2cap_server(RFCOMM_PSM, self.on_connection) device.create_l2cap_server(
spec=l2cap.ClassicChannelSpec(psm=RFCOMM_PSM), 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) -> int:
if channel: if channel:

View File

@@ -766,8 +766,9 @@ class Client:
self.channel = None self.channel = None
async def connect(self, connection: Connection) -> None: async def connect(self, connection: Connection) -> None:
result = await self.device.l2cap_channel_manager.connect(connection, SDP_PSM) self.channel = await connection.create_l2cap_channel(
self.channel = result spec=l2cap.ClassicChannelSpec(SDP_PSM)
)
async def disconnect(self) -> None: async def disconnect(self) -> None:
if self.channel: if self.channel:
@@ -933,7 +934,9 @@ class Server:
self.current_response = None self.current_response = None
def register(self, l2cap_channel_manager: l2cap.ChannelManager) -> None: def register(self, l2cap_channel_manager: l2cap.ChannelManager) -> None:
l2cap_channel_manager.register_server(SDP_PSM, self.on_connection) l2cap_channel_manager.create_classic_server(
spec=l2cap.ClassicChannelSpec(psm=SDP_PSM), handler=self.on_connection
)
def send_response(self, response): def send_response(self, response):
logger.debug(f'{color(">>> Sending SDP Response", "blue")}: {response}') logger.debug(f'{color(">>> Sending SDP Response", "blue")}: {response}')

View File

@@ -131,7 +131,7 @@ async def main():
await device.power_on() await device.power_on()
# Create a listener to wait for AVDTP connections # Create a listener to wait for AVDTP connections
listener = Listener(Listener.create_registrar(device)) listener = Listener.for_device(device)
listener.on('connection', on_avdtp_connection) listener.on('connection', on_avdtp_connection)
if len(sys.argv) >= 5: if len(sys.argv) >= 5:

View File

@@ -179,7 +179,7 @@ async def main():
await stream_packets(read, protocol) await stream_packets(read, protocol)
else: else:
# Create a listener to wait for AVDTP connections # Create a listener to wait for AVDTP connections
listener = Listener(Listener.create_registrar(device), version=(1, 2)) listener = Listener.for_device(device=device, version=(1, 2))
listener.on( listener.on(
'connection', lambda protocol: on_avdtp_connection(read, protocol) 'connection', lambda protocol: on_avdtp_connection(read, protocol)
) )

View File

@@ -21,6 +21,7 @@ import sys
import os import os
import logging import logging
from bumble import l2cap
from bumble.core import AdvertisingData from bumble.core import AdvertisingData
from bumble.device import Device from bumble.device import Device
from bumble.transport import open_transport_or_link from bumble.transport import open_transport_or_link
@@ -95,8 +96,10 @@ async def main():
channel.sink = on_data channel.sink = on_data
psm = device.register_l2cap_channel_server(0, on_coc, 8) server = device.create_l2cap_server(
print(f'### LE_PSM_OUT = {psm}') spec=l2cap.LeCreditBasedChannelSpec(max_credits=8), handler=on_coc
)
print(f'### LE_PSM_OUT = {server.psm}')
# Add the ASHA service to the GATT server # Add the ASHA service to the GATT server
read_only_properties_characteristic = Characteristic( read_only_properties_characteristic = Characteristic(
@@ -147,7 +150,7 @@ async def main():
ASHA_LE_PSM_OUT_CHARACTERISTIC, ASHA_LE_PSM_OUT_CHARACTERISTIC,
Characteristic.Properties.READ, Characteristic.Properties.READ,
Characteristic.READABLE, Characteristic.READABLE,
struct.pack('<H', psm), struct.pack('<H', server.psm),
) )
device.add_service( device.add_service(
Service( Service(

View File

@@ -185,7 +185,7 @@ async def test_source_sink_1():
sink.on('rtp_packet', on_rtp_packet) sink.on('rtp_packet', on_rtp_packet)
# Create a listener to wait for AVDTP connections # Create a listener to wait for AVDTP connections
listener = Listener(Listener.create_registrar(two_devices.devices[1])) listener = Listener.for_device(two_devices.devices[1])
listener.on('connection', on_avdtp_connection) listener.on('connection', on_avdtp_connection)
async def make_connection(): async def make_connection():

View File

@@ -22,7 +22,11 @@ import random
import pytest import pytest
from bumble.core import ProtocolError from bumble.core import ProtocolError
from bumble.l2cap import L2CAP_Connection_Request from bumble.l2cap import (
L2CAP_Connection_Request,
ClassicChannelSpec,
LeCreditBasedChannelSpec,
)
from .test_utils import TwoDevices from .test_utils import TwoDevices
@@ -80,7 +84,9 @@ async def test_basic_connection():
# Check that if there's no one listening, we can't connect # Check that if there's no one listening, we can't connect
with pytest.raises(ProtocolError): with pytest.raises(ProtocolError):
l2cap_channel = await devices.connections[0].open_l2cap_channel(psm) l2cap_channel = await devices.connections[0].create_l2cap_channel(
spec=LeCreditBasedChannelSpec(psm)
)
# Now add a listener # Now add a listener
incoming_channel = None incoming_channel = None
@@ -95,8 +101,12 @@ async def test_basic_connection():
channel.sink = on_data channel.sink = on_data
devices.devices[1].register_l2cap_channel_server(psm, on_coc) devices.devices[1].create_l2cap_server(
l2cap_channel = await devices.connections[0].open_l2cap_channel(psm) spec=LeCreditBasedChannelSpec(psm=1234), handler=on_coc
)
l2cap_channel = await devices.connections[0].create_l2cap_channel(
spec=LeCreditBasedChannelSpec(psm)
)
messages = (bytes([1, 2, 3]), bytes([4, 5, 6]), bytes(10000)) messages = (bytes([1, 2, 3]), bytes([4, 5, 6]), bytes(10000))
for message in messages: for message in messages:
@@ -138,10 +148,13 @@ async def transfer_payload(max_credits, mtu, mps):
channel.sink = on_data channel.sink = on_data
psm = devices.devices[1].register_l2cap_channel_server( server = devices.devices[1].create_l2cap_server(
psm=0, server=on_coc, max_credits=max_credits, mtu=mtu, mps=mps spec=LeCreditBasedChannelSpec(max_credits=max_credits, mtu=mtu, mps=mps),
handler=on_coc,
)
l2cap_channel = await devices.connections[0].create_l2cap_channel(
spec=LeCreditBasedChannelSpec(server.psm)
) )
l2cap_channel = await devices.connections[0].open_l2cap_channel(psm)
messages = [bytes([1, 2, 3, 4, 5, 6, 7]) * x for x in (3, 10, 100, 789)] messages = [bytes([1, 2, 3, 4, 5, 6, 7]) * x for x in (3, 10, 100, 789)]
for message in messages: for message in messages:
@@ -189,8 +202,12 @@ async def test_bidirectional_transfer():
def on_client_data(data): def on_client_data(data):
client_received.append(data) client_received.append(data)
psm = devices.devices[1].register_l2cap_channel_server(psm=0, server=on_server_coc) server = devices.devices[1].create_l2cap_server(
client_channel = await devices.connections[0].open_l2cap_channel(psm) spec=LeCreditBasedChannelSpec(), handler=on_server_coc
)
client_channel = await devices.connections[0].create_l2cap_channel(
spec=LeCreditBasedChannelSpec(server.psm)
)
client_channel.sink = on_client_data client_channel.sink = on_client_data
messages = [bytes([1, 2, 3, 4, 5, 6, 7]) * x for x in (3, 10, 100)] messages = [bytes([1, 2, 3, 4, 5, 6, 7]) * x for x in (3, 10, 100)]

View File

@@ -296,7 +296,7 @@ class Speaker:
self.device.on('key_store_update', self.on_key_store_update) self.device.on('key_store_update', self.on_key_store_update)
# Create a listener to wait for AVDTP connections # Create a listener to wait for AVDTP connections
self.listener = Listener(Listener.create_registrar(self.device)) self.listener = Listener.for_device(self.device)
self.listener.on('connection', self.on_avdtp_connection) self.listener.on('connection', self.on_avdtp_connection)
print(f'Speaker ready to play, codec={self.codec}') print(f'Speaker ready to play, codec={self.codec}')