use super::l2cap::{self, Reassembler, MAX_PAYLOAD};
use super::{AclData, Bluetooth, Error, Event};
use crate::timer::Timer;
const CID_SIGNALING: u16 = 0x0001;
const SIG_COMMAND_REJECT: u8 = 0x01;
const SIG_CONNECTION_REQ: u8 = 0x02;
const SIG_CONNECTION_RSP: u8 = 0x03;
const SIG_CONFIG_REQ: u8 = 0x04;
const SIG_CONFIG_RSP: u8 = 0x05;
const SIG_DISCONNECTION_REQ: u8 = 0x06;
const SIG_DISCONNECTION_RSP: u8 = 0x07;
const SIG_INFO_REQ: u8 = 0x0a;
const SIG_INFO_RSP: u8 = 0x0b;
const L2CAP_SUCCESS: u16 = 0x0000;
const L2CAP_PENDING: u16 = 0x0001;
const CONFIG_OPT_MTU: u8 = 0x01;
const LOCAL_MTU: u16 = MAX_PAYLOAD as u16;
const INFO_TYPE_EXTENDED_FEATURES: u16 = 0x0002;
const INFO_RESULT_SUCCESS: u16 = 0x0000;
const INFO_RESULT_NOT_SUPPORTED: u16 = 0x0001;
const FIRST_DYNAMIC_CID: u16 = 0x0040;
pub const MAX_CHANNELS: usize = 4;
const SIG_BUF: usize = 64;
const POLL_SLICE_MS: u32 = 500;
const DISCONNECT_TIMEOUT_MS: u32 = 1_000;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum L2capError {
Hci(Error),
Timeout,
Disconnected(u8),
Rejected,
NoChannel,
}
impl From<Error> for L2capError {
fn from(e: Error) -> Self {
L2capError::Hci(e)
}
}
#[derive(Clone, Copy)]
struct Channel {
in_use: bool,
local_cid: u16,
remote_cid: u16,
connected: bool,
config_rsp_received: bool,
config_req_answered: bool,
}
impl Channel {
const fn empty() -> Self {
Self {
in_use: false,
local_cid: 0,
remote_cid: 0,
connected: false,
config_rsp_received: false,
config_req_answered: false,
}
}
fn is_open(&self) -> bool {
self.connected && self.config_rsp_received && self.config_req_answered
}
}
pub struct L2cap {
handle: u16,
reasm: Reassembler,
channels: [Channel; MAX_CHANNELS],
ident: u8,
}
impl L2cap {
pub fn new(handle: u16) -> Self {
Self {
handle,
reasm: Reassembler::new(),
channels: [Channel::empty(); MAX_CHANNELS],
ident: 1,
}
}
pub fn open(
&mut self,
bt: &mut Bluetooth,
psm: u16,
timer: &Timer,
timeout_ms: u32,
) -> Result<u16, L2capError> {
let slot = self.alloc_channel().ok_or(L2capError::NoChannel)?;
let local_cid = self.channels[slot].local_cid;
let ident = self.next_ident();
let mut req = [0u8; 4];
req[0..2].copy_from_slice(&psm.to_le_bytes());
req[2..4].copy_from_slice(&local_cid.to_le_bytes());
self.send_sig(bt, SIG_CONNECTION_REQ, ident, &req)?;
let deadline = timer.now_micros() + (timeout_ms as u64) * 1000;
loop {
if self.channels[slot].is_open() {
return Ok(local_cid);
}
let now = timer.now_micros();
if now >= deadline {
return Err(L2capError::Timeout);
}
let remaining = (((deadline - now) / 1000) as u32 + 1).min(POLL_SLICE_MS);
match bt.poll(timer, remaining)? {
Some(Event::Acl(acl)) => {
self.on_acl_signaling(bt, &acl)?;
if !self.channels[slot].in_use {
return Err(L2capError::Rejected);
}
}
Some(Event::Disconnected { reason, .. }) => {
return Err(L2capError::Disconnected(reason))
}
Some(_) | None => {}
}
}
}
pub fn send(
&mut self,
bt: &mut Bluetooth,
local_cid: u16,
payload: &[u8],
) -> Result<(), L2capError> {
let ch = self
.channel_by_local_cid(local_cid)
.ok_or(L2capError::NoChannel)?;
let remote_cid = self.channels[ch].remote_cid;
l2cap::send(bt, self.handle, remote_cid, payload)?;
Ok(())
}
pub fn recv(
&mut self,
bt: &mut Bluetooth,
timer: &Timer,
timeout_ms: u32,
out: &mut [u8],
) -> Result<Option<(u16, usize)>, L2capError> {
let deadline = timer.now_micros() + (timeout_ms as u64) * 1000;
loop {
let now = timer.now_micros();
if now >= deadline {
return Ok(None);
}
let remaining = (((deadline - now) / 1000) as u32 + 1).min(POLL_SLICE_MS);
match bt.poll(timer, remaining)? {
Some(Event::Acl(acl)) => {
if let Some(hit) = self.on_acl_data(bt, &acl, out)? {
return Ok(Some(hit));
}
}
Some(Event::Disconnected { reason, .. }) => {
return Err(L2capError::Disconnected(reason))
}
Some(_) | None => {}
}
}
}
pub fn close(
&mut self,
bt: &mut Bluetooth,
local_cid: u16,
timer: &Timer,
) -> Result<(), L2capError> {
let Some(ch) = self.channel_by_local_cid(local_cid) else {
return Ok(());
};
let remote_cid = self.channels[ch].remote_cid;
let ident = self.next_ident();
let mut data = [0u8; 4];
data[0..2].copy_from_slice(&remote_cid.to_le_bytes());
data[2..4].copy_from_slice(&local_cid.to_le_bytes());
self.send_sig(bt, SIG_DISCONNECTION_REQ, ident, &data)?;
let deadline = timer.now_micros() + (DISCONNECT_TIMEOUT_MS as u64) * 1000;
while timer.now_micros() < deadline && self.channel_by_local_cid(local_cid).is_some() {
let now = timer.now_micros();
let remaining = (((deadline - now) / 1000) as u32 + 1).min(POLL_SLICE_MS);
match bt.poll(timer, remaining)? {
Some(Event::Acl(acl)) => self.on_acl_signaling(bt, &acl)?,
Some(Event::Disconnected { reason, .. }) => {
return Err(L2capError::Disconnected(reason))
}
Some(_) | None => {}
}
}
if let Some(ch) = self.channel_by_local_cid(local_cid) {
self.channels[ch] = Channel::empty();
}
Ok(())
}
fn on_acl_signaling(&mut self, bt: &mut Bluetooth, acl: &AclData) -> Result<(), L2capError> {
let _ = self.on_acl_data(bt, acl, &mut [])?;
Ok(())
}
fn on_acl_data(
&mut self,
bt: &mut Bluetooth,
acl: &AclData,
out: &mut [u8],
) -> Result<Option<(u16, usize)>, L2capError> {
if acl.handle != self.handle {
return Ok(None);
}
let mut frame = [0u8; MAX_PAYLOAD];
let (cid, len) = match self.reasm.feed(acl) {
Some(pdu) => {
let n = pdu.payload.len().min(frame.len());
frame[..n].copy_from_slice(&pdu.payload[..n]);
(pdu.cid, n)
}
None => return Ok(None),
};
if cid == CID_SIGNALING {
self.handle_signaling(bt, &frame[..len])?;
return Ok(None);
}
if let Some(_ch) = self.channel_by_local_cid(cid) {
let n = len.min(out.len());
out[..n].copy_from_slice(&frame[..n]);
return Ok(Some((cid, n)));
}
Ok(None)
}
fn handle_signaling(&mut self, bt: &mut Bluetooth, frame: &[u8]) -> Result<(), L2capError> {
let mut i = 0;
while i + 4 <= frame.len() {
let code = frame[i];
let ident = frame[i + 1];
let len = u16::from_le_bytes([frame[i + 2], frame[i + 3]]) as usize;
let start = i + 4;
let end = start + len;
if end > frame.len() {
break;
}
let data = &frame[start..end];
match code {
SIG_CONNECTION_RSP => self.on_connection_rsp(bt, data)?,
SIG_CONFIG_REQ => self.on_config_req(bt, ident, data)?,
SIG_CONFIG_RSP => self.on_config_rsp(data),
SIG_DISCONNECTION_REQ => self.on_disconnection_req(bt, ident, data)?,
SIG_DISCONNECTION_RSP => self.on_disconnection_rsp(data),
SIG_INFO_REQ => self.on_info_req(bt, ident, data)?,
SIG_COMMAND_REJECT => self.on_command_reject(),
_ => {}
}
i = end;
}
Ok(())
}
fn on_connection_rsp(&mut self, bt: &mut Bluetooth, data: &[u8]) -> Result<(), L2capError> {
if data.len() < 8 {
return Ok(());
}
let dcid = u16::from_le_bytes([data[0], data[1]]);
let scid = u16::from_le_bytes([data[2], data[3]]);
let result = u16::from_le_bytes([data[4], data[5]]);
let Some(ch) = self.channel_by_local_cid(scid) else {
return Ok(());
};
match result {
L2CAP_SUCCESS => {
self.channels[ch].remote_cid = dcid;
self.channels[ch].connected = true;
self.send_config_req(bt, ch)
}
L2CAP_PENDING => Ok(()),
_ => {
self.channels[ch] = Channel::empty();
Ok(())
}
}
}
fn on_config_req(
&mut self,
bt: &mut Bluetooth,
ident: u8,
data: &[u8],
) -> Result<(), L2capError> {
if data.len() < 4 {
return Ok(());
}
let dcid = u16::from_le_bytes([data[0], data[1]]);
let options = &data[4..];
let Some(ch) = self.channel_by_local_cid(dcid) else {
return Ok(());
};
let remote_cid = self.channels[ch].remote_cid;
let mut rsp = [0u8; SIG_BUF];
rsp[0..2].copy_from_slice(&remote_cid.to_le_bytes());
rsp[4..6].copy_from_slice(&L2CAP_SUCCESS.to_le_bytes());
let opt_len = options.len().min(rsp.len() - 6);
rsp[6..6 + opt_len].copy_from_slice(&options[..opt_len]);
self.channels[ch].config_req_answered = true;
self.send_sig(bt, SIG_CONFIG_RSP, ident, &rsp[..6 + opt_len])
}
fn on_config_rsp(&mut self, data: &[u8]) {
if data.len() < 6 {
return;
}
let scid = u16::from_le_bytes([data[0], data[1]]);
let result = u16::from_le_bytes([data[4], data[5]]);
if result == L2CAP_SUCCESS {
if let Some(ch) = self.channel_by_local_cid(scid) {
self.channels[ch].config_rsp_received = true;
}
}
}
fn on_disconnection_req(
&mut self,
bt: &mut Bluetooth,
ident: u8,
data: &[u8],
) -> Result<(), L2capError> {
if data.len() < 4 {
return Ok(());
}
let dcid = u16::from_le_bytes([data[0], data[1]]);
self.send_sig(bt, SIG_DISCONNECTION_RSP, ident, &data[..4])?;
if let Some(ch) = self.channel_by_local_cid(dcid) {
self.channels[ch] = Channel::empty();
}
Ok(())
}
fn on_disconnection_rsp(&mut self, data: &[u8]) {
if data.len() < 4 {
return;
}
let scid = u16::from_le_bytes([data[2], data[3]]);
if let Some(ch) = self.channel_by_local_cid(scid) {
self.channels[ch] = Channel::empty();
}
}
fn on_command_reject(&mut self) {
if let Some(ch) = self.channels.iter().rposition(|c| c.in_use && !c.is_open()) {
self.channels[ch] = Channel::empty();
}
}
fn on_info_req(
&mut self,
bt: &mut Bluetooth,
ident: u8,
data: &[u8],
) -> Result<(), L2capError> {
if data.len() < 2 {
return Ok(());
}
let info_type = u16::from_le_bytes([data[0], data[1]]);
let mut rsp = [0u8; 12];
rsp[0..2].copy_from_slice(&info_type.to_le_bytes());
let n = if info_type == INFO_TYPE_EXTENDED_FEATURES {
rsp[2..4].copy_from_slice(&INFO_RESULT_SUCCESS.to_le_bytes());
8
} else {
rsp[2..4].copy_from_slice(&INFO_RESULT_NOT_SUPPORTED.to_le_bytes());
4
};
self.send_sig(bt, SIG_INFO_RSP, ident, &rsp[..n])
}
fn send_config_req(&mut self, bt: &mut Bluetooth, ch: usize) -> Result<(), L2capError> {
let ident = self.next_ident();
let dcid = self.channels[ch].remote_cid;
let mut data = [0u8; 8];
data[0..2].copy_from_slice(&dcid.to_le_bytes());
data[4] = CONFIG_OPT_MTU;
data[5] = 0x02;
data[6..8].copy_from_slice(&LOCAL_MTU.to_le_bytes());
self.send_sig(bt, SIG_CONFIG_REQ, ident, &data)
}
fn send_sig(
&mut self,
bt: &mut Bluetooth,
code: u8,
ident: u8,
data: &[u8],
) -> Result<(), L2capError> {
let mut buf = [0u8; SIG_BUF];
buf[0] = code;
buf[1] = ident;
buf[2..4].copy_from_slice(&(data.len() as u16).to_le_bytes());
let end = 4 + data.len();
buf[4..end].copy_from_slice(data);
l2cap::send(bt, self.handle, CID_SIGNALING, &buf[..end])?;
Ok(())
}
fn alloc_channel(&mut self) -> Option<usize> {
let slot = self.channels.iter().position(|c| !c.in_use)?;
let local_cid = FIRST_DYNAMIC_CID + slot as u16;
self.channels[slot] = Channel::empty();
self.channels[slot].in_use = true;
self.channels[slot].local_cid = local_cid;
Some(slot)
}
fn next_ident(&mut self) -> u8 {
let id = self.ident;
self.ident = self.ident.wrapping_add(1);
if self.ident == 0 {
self.ident = 1;
}
id
}
fn channel_by_local_cid(&self, cid: u16) -> Option<usize> {
self.channels
.iter()
.position(|c| c.in_use && c.local_cid == cid)
}
}