diff --git a/examples/nrf-sdc/src/bin/ble_l2cap_central.rs b/examples/nrf-sdc/src/bin/ble_l2cap_central.rs index e50397c..6d7b0f1 100644 --- a/examples/nrf-sdc/src/bin/ble_l2cap_central.rs +++ b/examples/nrf-sdc/src/bin/ble_l2cap_central.rs @@ -14,7 +14,7 @@ use sdc::rng_pool::RngPool; use static_cell::StaticCell; use trouble_host::adapter::{Adapter, HostResources}; use trouble_host::connection::ConnectConfig; -use trouble_host::l2cap::L2capChannel; +use trouble_host::l2cap::{L2capChannel, L2capChannelConfig}; use trouble_host::scan::ScanConfig; use trouble_host::{Address, PacketQos}; use {defmt_rtt as _, panic_probe as _}; @@ -139,8 +139,18 @@ async fn main(spawner: Spawner) { let conn = unwrap!(adapter.connect(&config).await); info!("Connected, creating l2cap channel"); const PAYLOAD_LEN: usize = 27; - let mut ch1 = - unwrap!(L2capChannel::create(&adapter, &conn, 0x2349, PAYLOAD_LEN as u16, Default::default()).await); + let mut ch1 = unwrap!( + L2capChannel::create( + &adapter, + &conn, + 0x2349, + &L2capChannelConfig { + mtu: PAYLOAD_LEN as u16, + ..Default::default() + } + ) + .await + ); info!("New l2cap channel created, sending some data!"); for i in 0..10 { let tx = [i; PAYLOAD_LEN]; diff --git a/examples/nrf-sdc/src/bin/ble_l2cap_peripheral.rs b/examples/nrf-sdc/src/bin/ble_l2cap_peripheral.rs index 2a8d6ed..85a4de8 100644 --- a/examples/nrf-sdc/src/bin/ble_l2cap_peripheral.rs +++ b/examples/nrf-sdc/src/bin/ble_l2cap_peripheral.rs @@ -14,7 +14,7 @@ use sdc::rng_pool::RngPool; use static_cell::StaticCell; use trouble_host::adapter::{Adapter, HostResources}; use trouble_host::advertise::{AdStructure, Advertisement, BR_EDR_NOT_SUPPORTED, LE_GENERAL_DISCOVERABLE}; -use trouble_host::l2cap::L2capChannel; +use trouble_host::l2cap::{L2capChannel, L2capChannelConfig}; use trouble_host::{Address, PacketQos}; use {defmt_rtt as _, panic_probe as _}; @@ -149,8 +149,18 @@ async fn main(spawner: Spawner) { info!("Connection established"); - let mut ch1 = - unwrap!(L2capChannel::accept(&adapter, &conn, &[0x2349], PAYLOAD_LEN as u16, Default::default()).await); + let mut ch1 = unwrap!( + L2capChannel::accept( + &adapter, + &conn, + &[0x2349], + &L2capChannelConfig { + mtu: PAYLOAD_LEN as u16, + ..Default::default() + } + ) + .await + ); info!("L2CAP channel accepted"); diff --git a/host/src/adapter.rs b/host/src/adapter.rs index 1788073..97991a1 100644 --- a/host/src/adapter.rs +++ b/host/src/adapter.rs @@ -18,7 +18,7 @@ use bt_hci::param::{ AddrKind, AdvChannelMap, AdvHandle, AdvKind, BdAddr, ConnHandle, DisconnectReason, EventMask, FilterDuplicates, InitiatingPhy, LeEventMask, Operation, PhyParams, ScanningPhy, }; -use bt_hci::ControllerToHostPacket; +use bt_hci::{ControllerToHostPacket, FromHciBytes, WriteHci}; use embassy_futures::select::{select, Either}; use embassy_sync::blocking_mutex::raw::RawMutex; use embassy_sync::channel::Channel; @@ -29,12 +29,14 @@ use crate::advertise::{Advertisement, AdvertisementConfig, RawAdvertisement}; use crate::channel_manager::ChannelManager; use crate::connection::{ConnectConfig, Connection}; use crate::connection_manager::{ConnectionInfo, ConnectionManager}; -use crate::cursor::{ReadCursor, WriteCursor}; +use crate::cursor::WriteCursor; use crate::l2cap::sar::PacketReassembly; use crate::packet_pool::{AllocId, DynamicPacketPool, PacketPool, Qos}; use crate::pdu::Pdu; use crate::scan::{PhySet, ScanConfig, ScanReport}; -use crate::types::l2cap::{L2capHeader, L2capLeSignal, L2CAP_CID_ATT, L2CAP_CID_DYN_START, L2CAP_CID_LE_U_SIGNAL}; +use crate::types::l2cap::{ + L2capHeader, L2capSignal, L2capSignalHeader, L2CAP_CID_ATT, L2CAP_CID_DYN_START, L2CAP_CID_LE_U_SIGNAL, +}; #[cfg(feature = "gatt")] use crate::{attribute::AttributeTable, gatt::GattServer}; use crate::{AdapterError, Address, Error}; @@ -530,14 +532,12 @@ where async fn handle_acl(&self, acl: AclPacket<'_>) -> Result<(), Error> { let (header, packet) = match acl.boundary_flag() { AclPacketBoundary::FirstFlushable => { - let (header, data) = L2capHeader::decode(&acl)?; + let (header, data) = L2capHeader::from_hci_bytes(acl.data())?; // Avoids using the packet buffer for signalling packets if header.channel == L2CAP_CID_LE_U_SIGNAL { assert!(data.len() == header.length as usize); - let mut r = ReadCursor::new(data); - let signal: L2capLeSignal = r.read()?; - self.channels.control(acl.handle(), signal).await?; + self.channels.control(acl.handle(), &data).await?; return Ok(()); } @@ -851,28 +851,29 @@ impl<'d, T: Controller> HciController<'d, T> { Ok(()) } - pub(crate) async fn signal( + pub(crate) async fn signal( &self, handle: ConnHandle, - response: &L2capLeSignal, + identifier: u8, + signal: &D, + p_buf: &mut [u8], ) -> Result<(), AdapterError> { - // TODO: Refactor signal to avoid encode/decode - // info!("[{}] sending signal: {:?}", handle, response); - let mut tx = [0; 32]; - let mut w = WriteCursor::new(&mut tx); - let (mut header, mut body) = w.split(4)?; - - body.write_ref(response)?; - - // TODO: Move into l2cap packet type - header.write(body.len() as u16)?; - header.write(L2CAP_CID_LE_U_SIGNAL)?; - let len = header.len() + body.len(); - - header.finish(); - body.finish(); - w.finish(); - self.send(handle, &tx[..len]).await?; + let header = L2capSignalHeader { + identifier, + code: D::code(), + length: signal.size() as u16, + }; + let l2cap = L2capHeader { + channel: D::channel(), + length: header.size() as u16 + header.length, + }; + + let mut w = WriteCursor::new(p_buf); + w.write_hci(&l2cap)?; + w.write_hci(&header)?; + w.write_hci(signal)?; + + self.send(handle, w.finish()).await?; Ok(()) } diff --git a/host/src/channel_manager.rs b/host/src/channel_manager.rs index 86b6e83..829f9f8 100644 --- a/host/src/channel_manager.rs +++ b/host/src/channel_manager.rs @@ -4,6 +4,7 @@ use core::task::{Context, Poll}; use bt_hci::controller::Controller; use bt_hci::param::ConnHandle; +use bt_hci::FromHciBytes; use embassy_sync::blocking_mutex::raw::RawMutex; use embassy_sync::blocking_mutex::Mutex; use embassy_sync::channel::Channel; @@ -14,8 +15,8 @@ use crate::cursor::{ReadCursor, WriteCursor}; use crate::packet_pool::{AllocId, DynamicPacketPool, Packet}; use crate::pdu::Pdu; use crate::types::l2cap::{ - L2capHeader, L2capLeSignal, L2capLeSignalData, LeCreditConnReq, LeCreditConnRes, LeCreditConnResultCode, - LeCreditFlowInd, + CommandRejectRes, DisconnectionReq, DisconnectionRes, L2capHeader, L2capSignalCode, L2capSignalHeader, + LeCreditConnReq, LeCreditConnRes, LeCreditConnResultCode, LeCreditFlowInd, }; use crate::{AdapterError, Error}; @@ -274,6 +275,7 @@ impl< psm: &[u16], mut mtu: u16, credit_flow: CreditFlowPolicy, + initial_credits: Option, controller: &HciController<'_, T>, ) -> Result> { let mut req_id = 0; @@ -282,7 +284,7 @@ impl< req_id = req.request_id; let mps = req.mps.min(self.pool.mtu() as u16 - 4); mtu = req.mtu.min(mtu); - let credits = self.pool.min_available(AllocId::dynamic(idx)) as u16; + let credits = initial_credits.unwrap_or(self.pool.min_available(AllocId::dynamic(idx)) as u16); // info!("Accept L2CAP, initial credits: {}", credits); ConnectedState { conn: req.conn, @@ -299,31 +301,34 @@ impl< }) .await; - let response = L2capLeSignal::new( - req_id, - L2capLeSignalData::LeCreditConnRes(LeCreditConnRes { - mps: state.mps, - dcid: state.cid, - mtu, - credits: 0, - result: LeCreditConnResultCode::Success, - }), - ); - - controller.signal(conn, &response).await?; + let mut tx = [0; 18]; + controller + .signal( + conn, + req_id, + &LeCreditConnRes { + mps: state.mps, + dcid: state.cid, + mtu, + credits: 0, + result: LeCreditConnResultCode::Success, + }, + &mut tx[..], + ) + .await?; // Send initial credits let next_req_id = self.next_request_id(); + controller .signal( conn, - &L2capLeSignal::new( - next_req_id, - L2capLeSignalData::LeCreditFlowInd(LeCreditFlowInd { - cid: state.cid, - credits: state.flow_control.available(), - }), - ), + next_req_id, + &LeCreditFlowInd { + cid: state.cid, + credits: state.flow_control.available(), + }, + &mut tx[..], ) .await?; @@ -336,6 +341,7 @@ impl< psm: u16, mtu: u16, credit_flow: CreditFlowPolicy, + initial_credits: Option, controller: &HciController<'_, T>, ) -> Result> { let req_id = self.next_request_id(); @@ -343,7 +349,7 @@ impl< let mut cid: u16 = 0; self.connect(|i, c| { cid = c; - credits = self.pool.min_available(AllocId::dynamic(i)) as u16; + credits = initial_credits.unwrap_or(self.pool.min_available(AllocId::dynamic(i)) as u16); ConnectingState { conn, cid, @@ -356,19 +362,19 @@ impl< } })?; //info!("Created connect state with idx cid {}", cid); + // + let mut tx = [0; 18]; + + let command = LeCreditConnReq { + psm, + mps: self.pool.mtu() as u16 - 4, + scid: cid, + mtu, + credits: 0, + }; - let command = L2capLeSignal::new( - req_id, - L2capLeSignalData::LeCreditConnReq(LeCreditConnReq { - psm, - mps: self.pool.mtu() as u16 - 4, - scid: cid, - mtu, - credits: 0, - }), - ); //info!("Signal packet to remote: {:?}", command); - controller.signal(conn, &command).await?; + controller.signal(conn, req_id, &command, &mut tx[..]).await?; // info!("Sent signal packet to remote, awaiting response"); let (idx, cid) = poll_fn(|cx| { @@ -395,14 +401,8 @@ impl< // Send initial credits let next_req_id = self.next_request_id(); - controller - .signal( - conn, - &L2capLeSignal::new( - next_req_id, - L2capLeSignalData::LeCreditFlowInd(LeCreditFlowInd { cid, credits }), - ), - ) + let req = controller + .signal(conn, next_req_id, &LeCreditFlowInd { cid, credits }, &mut tx[..]) .await?; // info!("Done!"); @@ -442,15 +442,17 @@ impl< Ok(()) } - pub async fn control(&self, conn: ConnHandle, signal: L2capLeSignal) -> Result<(), Error> { + pub async fn control(&self, conn: ConnHandle, data: &[u8]) -> Result<(), Error> { // info!("Inbound signal: {:?}", signal); - match signal.data { - L2capLeSignalData::LeCreditConnReq(req) => { + let (header, data) = L2capSignalHeader::from_hci_bytes(data)?; + match header.code { + L2capSignalCode::LeCreditConnReq => { + let req = LeCreditConnReq::from_hci_bytes_complete(data)?; self.peer_connect(|i, c| PeerConnectingState { conn, cid: c, psm: req.psm, - request_id: signal.id, + request_id: header.identifier, peer_cid: req.scid, offered_credits: req.credits, mps: req.mps, @@ -458,12 +460,13 @@ impl< })?; Ok(()) } - L2capLeSignalData::LeCreditConnRes(res) => { + L2capSignalCode::LeCreditConnRes => { + let res = LeCreditConnRes::from_hci_bytes_complete(data)?; // info!("Got response to create request: {:?}", res); match res.result { LeCreditConnResultCode::Success => { // Must be a response of a previous request which should already by allocated a channel for - self.connected(signal.id, |idx, req| ConnectedState { + self.connected(header.identifier, |idx, req| ConnectedState { conn: req.conn, cid: req.cid, psm: req.psm, @@ -482,24 +485,29 @@ impl< } } } - L2capLeSignalData::LeCreditFlowInd(req) => { + L2capSignalCode::LeCreditFlowInd => { + let req = LeCreditFlowInd::from_hci_bytes_complete(data)?; self.remote_credits(req.cid, req.credits)?; Ok(()) } - L2capLeSignalData::CommandRejectRes(reject) => { + L2capSignalCode::CommandRejectRes => { + let (reject, _) = CommandRejectRes::from_hci_bytes(data)?; warn!("Rejected: {:?}", reject); Ok(()) } - L2capLeSignalData::DisconnectionReq(req) => { + L2capSignalCode::DisconnectionReq => { + let req = DisconnectionReq::from_hci_bytes_complete(data)?; info!("Disconnect request: {:?}!", req); self.disconnect(req.dcid)?; Ok(()) } - L2capLeSignalData::DisconnectionRes(res) => { + L2capSignalCode::DisconnectionRes => { + let res = DisconnectionRes::from_hci_bytes_complete(data)?; warn!("Disconnection result!"); self.disconnected(res.dcid)?; Ok(()) } + _ => Err(Error::NotSupported), } } @@ -556,8 +564,7 @@ impl< let mut remaining = remaining as usize - data.len(); - drop(packet); - self.flow_control(cid, hci).await?; + self.flow_control(cid, hci, packet.packet).await?; //info!( // "Total size of PDU is {}, read buffer size is {} remaining; {}", // len, @@ -574,8 +581,7 @@ impl< pos += to_copy; } remaining -= packet.len; - drop(packet); - self.flow_control(cid, hci).await?; + self.flow_control(cid, hci, packet.packet).await?; } // info!("Total reserved {} bytes", pos); @@ -656,6 +662,7 @@ impl< &self, cid: u16, hci: &HciController<'_, T>, + mut packet: Packet<'_>, ) -> Result<(), AdapterError> { let (conn, credits) = self.state.lock(|state| { let mut state = state.borrow_mut(); @@ -671,15 +678,11 @@ impl< })?; if let Some(credits) = credits { - let next_req_id = self.next_request_id(); - hci.signal( - conn, - &L2capLeSignal::new( - next_req_id, - L2capLeSignalData::LeCreditFlowInd(LeCreditFlowInd { cid, credits }), - ), - ) - .await?; + let identifier = self.next_request_id(); + let signal = LeCreditFlowInd { cid, credits }; + + // Reuse packet buffer for signalling data to save the extra TX buffer + hci.signal(conn, identifier, &signal, packet.as_mut()).await?; } Ok(()) } diff --git a/host/src/cursor.rs b/host/src/cursor.rs index 669e78f..1550da4 100644 --- a/host/src/cursor.rs +++ b/host/src/cursor.rs @@ -1,6 +1,8 @@ //! Module for cursors over a byte slice. //! +use bt_hci::WriteHci; + use crate::codec::{Decode, Encode, Error}; // Not a byte writer. It is just a cursor to track where a byte slice is being written. @@ -57,6 +59,18 @@ impl<'d> WriteCursor<'d> { } } + /// Write fixed sized type + pub fn write_hci(&mut self, data: &E) -> Result<(), Error> { + if self.available() < data.size() { + Err(Error::InsufficientSpace) + } else { + data.write_hci(&mut self.data[self.pos..self.pos + data.size()]) + .map_err(|_| Error::InsufficientSpace)?; + self.pos += data.size(); + Ok(()) + } + } + pub fn write_ref(&mut self, data: &E) -> Result<(), Error> { if self.available() < data.size() { Err(Error::InsufficientSpace) diff --git a/host/src/l2cap.rs b/host/src/l2cap.rs index 1de815a..43cde87 100644 --- a/host/src/l2cap.rs +++ b/host/src/l2cap.rs @@ -10,12 +10,34 @@ use crate::AdapterError; pub(crate) mod sar; +/// Handle representing an L2CAP channel. #[derive(Clone)] pub struct L2capChannel { handle: ConnHandle, cid: u16, } +/// Configuration for an L2CAP channel. +pub struct L2capChannelConfig { + /// Desired mtu of the Service Delivery Unit (SDU). May be fragmented according to the host + /// adapter L2CAP MTU. + pub mtu: u16, + /// Flow control policy for connection oriented channels. + pub flow_policy: CreditFlowPolicy, + /// Initial credits for connection oriented channels. + pub initial_credits: Option, +} + +impl Default for L2capChannelConfig { + fn default() -> Self { + Self { + mtu: 23, + flow_policy: Default::default(), + initial_credits: None, + } + } +} + impl L2capChannel { pub async fn send< M: RawMutex, @@ -77,13 +99,19 @@ impl L2capChannel { adapter: &Adapter<'_, M, T, CONNS, CHANNELS, L2CAP_MTU, L2CAP_TXQ, L2CAP_RXQ>, connection: &Connection, psm: &[u16], - mtu: u16, - flow_policy: CreditFlowPolicy, + config: &L2capChannelConfig, ) -> Result> { let handle = connection.handle(); let cid = adapter .channels - .accept(handle, psm, mtu, flow_policy, &adapter.hci()) + .accept( + handle, + psm, + config.mtu, + config.flow_policy, + config.initial_credits, + &adapter.hci(), + ) .await?; Ok(Self { cid, handle }) @@ -121,14 +149,20 @@ impl L2capChannel { adapter: &Adapter<'_, M, T, CONNS, CHANNELS, L2CAP_MTU, L2CAP_TXQ, L2CAP_RXQ>, connection: &Connection, psm: u16, - mtu: u16, - flow_policy: CreditFlowPolicy, + config: &L2capChannelConfig, ) -> Result> where { let handle = connection.handle(); let cid = adapter .channels - .create(connection.handle(), psm, mtu, flow_policy, &adapter.hci()) + .create( + connection.handle(), + psm, + config.mtu, + config.flow_policy, + config.initial_credits, + &adapter.hci(), + ) .await?; Ok(Self { handle, cid }) diff --git a/host/src/types/l2cap.rs b/host/src/types/l2cap.rs index c912583..7ba9452 100644 --- a/host/src/types/l2cap.rs +++ b/host/src/types/l2cap.rs @@ -1,34 +1,50 @@ -use bt_hci::data::AclPacket; -use trouble_host_macros::*; +use bt_hci::{FixedSizeValue, WriteHci}; -use crate::codec::{self, Decode, Encode, Error, FixedSize, Type}; -use crate::cursor::{ReadCursor, WriteCursor}; +use crate::codec::Error; pub(crate) const L2CAP_CID_ATT: u16 = 0x0004; pub(crate) const L2CAP_CID_LE_U_SIGNAL: u16 = 0x0005; pub(crate) const L2CAP_CID_DYN_START: u16 = 0x0040; #[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] +#[repr(C)] pub struct L2capHeader { pub length: u16, pub channel: u16, } -impl L2capHeader { - pub fn decode<'m>(packet: &AclPacket<'m>) -> Result<(L2capHeader, &'m [u8]), codec::Error> { - let data = packet.data(); - let mut r = ReadCursor::new(data); - let length: u16 = r.read()?; - let channel: u16 = r.read()?; - Ok((Self { length, channel }, &packet.data()[4..])) +unsafe impl FixedSizeValue for L2capHeader { + fn is_valid(data: &[u8]) -> bool { + return true; } } #[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C)] #[derive(Debug, Clone, Copy)] -#[repr(u8)] -pub enum SignalCode { +pub struct L2capSignalHeader { + pub code: L2capSignalCode, + pub identifier: u8, + pub length: u16, +} + +unsafe impl FixedSizeValue for L2capSignalHeader { + fn is_valid(data: &[u8]) -> bool { + return true; + } +} + +pub trait L2capSignal: WriteHci + FixedSizeValue { + fn channel() -> u16 { + L2CAP_CID_LE_U_SIGNAL + } + fn code() -> L2capSignalCode; +} + +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum L2capSignalCode { CommandRejectRes = 0x01, ConnectionReq = 0x02, ConnectionRes = 0x03, @@ -51,7 +67,7 @@ pub enum SignalCode { CreditConnReconfigRes = 0x1A, } -impl TryFrom for SignalCode { +impl TryFrom for L2capSignalCode { type Error = Error; fn try_from(val: u8) -> Result { Ok(match val { @@ -80,167 +96,9 @@ impl TryFrom for SignalCode { } } -impl FixedSize for SignalCode { - const SIZE: usize = 1; -} - -impl Encode for SignalCode { - fn encode(&self, dest: &mut [u8]) -> Result<(), Error> { - dest[0] = *self as u8; - Ok(()) - } -} - -impl Decode for SignalCode { - fn decode(src: &[u8]) -> Result { - src[0].try_into() - } -} - -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[derive(Debug)] -pub struct L2capLeSignal { - pub id: u8, - pub data: L2capLeSignalData, -} - -impl L2capLeSignal { - pub fn new(id: u8, data: L2capLeSignalData) -> Self { - Self { id, data } - } -} - -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[derive(Debug)] -pub enum L2capLeSignalData { - CommandRejectRes(CommandRejectRes), - LeCreditConnReq(LeCreditConnReq), - LeCreditConnRes(LeCreditConnRes), - LeCreditFlowInd(LeCreditFlowInd), - DisconnectionReq(DisconnectionReq), - DisconnectionRes(DisconnectionRes), -} - -impl L2capLeSignalData { - fn code(&self) -> SignalCode { - match self { - Self::CommandRejectRes(_) => SignalCode::CommandRejectRes, - Self::LeCreditConnReq(_) => SignalCode::LeCreditConnReq, - Self::LeCreditConnRes(_) => SignalCode::LeCreditConnRes, - Self::LeCreditFlowInd(_) => SignalCode::LeCreditFlowInd, - Self::DisconnectionReq(_) => SignalCode::DisconnectionReq, - Self::DisconnectionRes(_) => SignalCode::DisconnectionRes, - } - } - - fn decode(code: SignalCode, mut r: ReadCursor<'_>) -> Result { - Ok(match code { - SignalCode::LeCreditConnReq => { - let req = r.read()?; - Self::LeCreditConnReq(req) - } - SignalCode::LeCreditConnRes => { - let res = r.read()?; - Self::LeCreditConnRes(res) - } - SignalCode::CommandRejectRes => { - let res = r.read()?; - Self::CommandRejectRes(res) - } - SignalCode::LeCreditFlowInd => { - let res = r.read()?; - Self::LeCreditFlowInd(res) - } - SignalCode::DisconnectionReq => { - let res = r.read()?; - Self::DisconnectionReq(res) - } - SignalCode::DisconnectionRes => { - let res = r.read()?; - Self::DisconnectionRes(res) - } - code => { - warn!("Unimplemented signal code: {:?}", code); - panic!(); - } - }) - } -} - -impl Type for L2capLeSignalData { - fn size(&self) -> usize { - match self { - Self::CommandRejectRes(r) => r.size(), - Self::LeCreditConnReq(r) => r.size(), - Self::LeCreditConnRes(r) => r.size(), - Self::LeCreditFlowInd(r) => r.size(), - Self::DisconnectionReq(r) => r.size(), - Self::DisconnectionRes(r) => r.size(), - } - } -} - -impl Encode for L2capLeSignalData { - fn encode(&self, dest: &mut [u8]) -> Result<(), Error> { - let mut w = WriteCursor::new(dest); - match &self { - Self::LeCreditConnReq(r) => { - w.write_ref(r)?; - } - Self::LeCreditConnRes(r) => { - w.write_ref(r)?; - } - Self::CommandRejectRes(r) => { - w.write_ref(r)?; - } - Self::LeCreditFlowInd(r) => { - w.write_ref(r)?; - } - Self::DisconnectionReq(r) => { - w.write_ref(r)?; - } - Self::DisconnectionRes(r) => { - w.write_ref(r)?; - } - } - Ok(()) - } -} - -impl Type for L2capLeSignal { - fn size(&self) -> usize { - 4 + self.data.size() - } -} - -impl Encode for L2capLeSignal { - fn encode(&self, dest: &mut [u8]) -> Result<(), Error> { - let mut w = WriteCursor::new(dest); - let (mut header, mut data) = w.split(4)?; - data.write_ref(&self.data)?; - let code = self.data.code(); - let len = self.data.size(); - header.write(code)?; - header.write(self.id)?; - header.write(len as u16)?; - Ok(()) - } -} - -impl Decode for L2capLeSignal { - fn decode(src: &[u8]) -> Result { - let mut r = ReadCursor::new(src); - let code: SignalCode = r.read()?; - let id: u8 = r.read()?; - let len: u16 = r.read()?; - assert!(len <= r.available() as u16); - let data = L2capLeSignalData::decode(code, r)?; - Ok(Self { id, data }) - } -} - #[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[derive(Debug, Codec)] +#[repr(C)] +#[derive(Debug, Clone, Copy)] pub struct LeCreditConnReq { pub psm: u16, pub scid: u16, @@ -249,6 +107,18 @@ pub struct LeCreditConnReq { pub credits: u16, } +unsafe impl FixedSizeValue for LeCreditConnReq { + fn is_valid(data: &[u8]) -> bool { + return true; + } +} + +impl L2capSignal for LeCreditConnReq { + fn code() -> L2capSignalCode { + L2capSignalCode::LeCreditConnReq + } +} + #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[derive(Debug, Clone, Copy)] #[repr(u16)] @@ -265,44 +135,9 @@ pub enum LeCreditConnResultCode { UnacceptableParameters = 0x000B, } -impl TryFrom for LeCreditConnResultCode { - type Error = Error; - fn try_from(val: u16) -> Result { - Ok(match val { - 0x0000 => Self::Success, - 0x0002 => Self::SpsmNotSupported, - 0x0004 => Self::NoResources, - 0x0005 => Self::InsufficientAuthentication, - 0x0006 => Self::InsufficientAuthorization, - 0x0007 => Self::EncryptionKeyTooShort, - 0x0008 => Self::InsufficientEncryption, - 0x0009 => Self::InvalidSourceId, - 0x000A => Self::ScidAlreadyAllocated, - 0x000B => Self::UnacceptableParameters, - _ => return Err(Error::InvalidValue), - }) - } -} - -impl FixedSize for LeCreditConnResultCode { - const SIZE: usize = 2; -} - -impl Encode for LeCreditConnResultCode { - fn encode(&self, dest: &mut [u8]) -> Result<(), Error> { - dest.copy_from_slice(&(*self as u16).to_le_bytes()[..]); - Ok(()) - } -} - -impl Decode for LeCreditConnResultCode { - fn decode(src: &[u8]) -> Result { - u16::from_le_bytes([src[0], src[1]]).try_into() - } -} - #[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[derive(Debug, Codec)] +#[repr(C)] +#[derive(Debug, Clone, Copy)] pub struct LeCreditConnRes { pub dcid: u16, pub mtu: u16, @@ -311,30 +146,76 @@ pub struct LeCreditConnRes { pub result: LeCreditConnResultCode, } -#[derive(Debug, Codec)] +impl L2capSignal for LeCreditConnRes { + fn code() -> L2capSignalCode { + L2capSignalCode::LeCreditConnRes + } +} + +unsafe impl FixedSizeValue for LeCreditConnRes { + fn is_valid(data: &[u8]) -> bool { + return true; + } +} + +#[repr(C)] +#[derive(Debug, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct LeCreditFlowInd { pub cid: u16, pub credits: u16, } +unsafe impl FixedSizeValue for LeCreditFlowInd { + fn is_valid(data: &[u8]) -> bool { + return true; + } +} + +impl L2capSignal for LeCreditFlowInd { + fn code() -> L2capSignalCode { + L2capSignalCode::LeCreditFlowInd + } +} + #[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[derive(Debug, Codec)] +#[repr(C)] +#[derive(Debug, Clone, Copy)] pub struct CommandRejectRes { pub reason: u16, // TODO: Optional fields pub data: u16, } +unsafe impl FixedSizeValue for CommandRejectRes { + fn is_valid(data: &[u8]) -> bool { + return true; + } +} + #[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[derive(Debug, Codec)] +#[repr(C)] +#[derive(Debug, Clone, Copy)] pub struct DisconnectionReq { pub dcid: u16, pub scid: u16, } +unsafe impl FixedSizeValue for DisconnectionReq { + fn is_valid(data: &[u8]) -> bool { + return true; + } +} + #[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[derive(Debug, Codec)] +#[repr(C)] +#[derive(Debug, Clone, Copy)] pub struct DisconnectionRes { pub dcid: u16, pub scid: u16, } + +unsafe impl FixedSizeValue for DisconnectionRes { + fn is_valid(data: &[u8]) -> bool { + return true; + } +} diff --git a/host/tests/l2cap.rs b/host/tests/l2cap.rs index c4ac356..6d434b8 100644 --- a/host/tests/l2cap.rs +++ b/host/tests/l2cap.rs @@ -10,7 +10,7 @@ use tokio_serial::{DataBits, Parity, SerialStream, StopBits}; use trouble_host::adapter::{Adapter, HostResources}; use trouble_host::advertise::{AdStructure, Advertisement, BR_EDR_NOT_SUPPORTED, LE_GENERAL_DISCOVERABLE}; use trouble_host::connection::ConnectConfig; -use trouble_host::l2cap::L2capChannel; +use trouble_host::l2cap::{L2capChannel, L2capChannelConfig}; use trouble_host::scan::ScanConfig; use trouble_host::PacketQos; @@ -95,7 +95,9 @@ async fn l2cap_connection_oriented_channels() { }).await?; println!("[peripheral] connected"); - let mut ch1 = L2capChannel::accept(&adapter, &conn, &[0x2349], PAYLOAD_LEN as u16, Default::default()).await?; + let mut ch1 = L2capChannel::accept(&adapter, &conn, &[0x2349], &L2capChannelConfig { + mtu: PAYLOAD_LEN as u16, ..Default::default() + }).await?; println!("[peripheral] channel created"); @@ -164,7 +166,10 @@ async fn l2cap_connection_oriented_channels() { let conn = adapter.connect(&config).await.unwrap(); println!("[central] connected"); const PAYLOAD_LEN: usize = 27; - let mut ch1 = L2capChannel::create(&adapter, &conn, 0x2349, PAYLOAD_LEN as u16, Default::default()).await?; + let mut ch1 = L2capChannel::create(&adapter, &conn, 0x2349, &L2capChannelConfig { + mtu: PAYLOAD_LEN as u16, + ..Default::default() + }).await?; println!("[central] channel created"); for i in 0..10 { let tx = [i; PAYLOAD_LEN];