use super::gatt::{Uuid, ATT_DEFAULT_MTU, ATT_MAX_MTU, UUID_CHARACTERISTIC, UUID_PRIMARY_SERVICE};
use super::l2cap::{self, Reassembler, CID_ATT};
use super::{AclData, Bluetooth, Error, Event};
use crate::timer::Timer;
const OP_ERROR_RESPONSE: u8 = 0x01;
const OP_EXCHANGE_MTU_REQ: u8 = 0x02;
const OP_EXCHANGE_MTU_RSP: u8 = 0x03;
const OP_FIND_INFORMATION_REQ: u8 = 0x04;
const OP_FIND_INFORMATION_RSP: u8 = 0x05;
const OP_READ_BY_TYPE_REQ: u8 = 0x08;
const OP_READ_BY_TYPE_RSP: u8 = 0x09;
const OP_READ_BY_GROUP_TYPE_REQ: u8 = 0x10;
const OP_READ_BY_GROUP_TYPE_RSP: u8 = 0x11;
const OP_WRITE_REQ: u8 = 0x12;
const OP_WRITE_RSP: u8 = 0x13;
const OP_HANDLE_VALUE_NOTIFICATION: u8 = 0x1b;
const ERR_ATTRIBUTE_NOT_FOUND: u8 = 0x0a;
pub const CCC_NOTIFY: u16 = 0x0001;
pub const CCC_INDICATE: u16 = 0x0002;
const HANDLE_MIN: u16 = 0x0001;
const HANDLE_MAX: u16 = 0xffff;
const ATT_REQUEST_TIMEOUT_MS: u32 = 5_000;
const POLL_SLICE_MS: u32 = 500;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Service {
pub start_handle: u16,
pub end_handle: u16,
pub uuid: Uuid,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Characteristic {
pub decl_handle: u16,
pub properties: u8,
pub value_handle: u16,
pub uuid: Uuid,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Descriptor {
pub handle: u16,
pub uuid: Uuid,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Notification {
pub value_handle: u16,
pub len: usize,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ClientError {
Hci(Error),
Timeout,
Disconnected(u8),
Att {
request: u8,
handle: u16,
code: u8,
},
Protocol,
Overflow,
}
impl From<Error> for ClientError {
fn from(e: Error) -> Self {
ClientError::Hci(e)
}
}
pub struct Client {
handle: u16,
reasm: Reassembler,
mtu: u16,
}
impl Client {
pub fn new(connection_handle: u16) -> Self {
Self {
handle: connection_handle,
reasm: Reassembler::new(),
mtu: ATT_DEFAULT_MTU,
}
}
pub fn handle(&self) -> u16 {
self.handle
}
pub fn mtu(&self) -> u16 {
self.mtu
}
pub fn exchange_mtu(&mut self, bt: &mut Bluetooth, timer: &Timer) -> Result<u16, ClientError> {
let req = [
OP_EXCHANGE_MTU_REQ,
ATT_MAX_MTU.to_le_bytes()[0],
ATT_MAX_MTU.to_le_bytes()[1],
];
let mut resp = [0u8; ATT_MAX_MTU as usize];
let n = self.request(bt, &req, OP_EXCHANGE_MTU_RSP, &mut resp, timer)?;
if n < 3 {
return Err(ClientError::Protocol);
}
let server_mtu = u16::from_le_bytes([resp[1], resp[2]]);
self.mtu = server_mtu.clamp(ATT_DEFAULT_MTU, ATT_MAX_MTU);
Ok(self.mtu)
}
pub fn discover_primary_services(
&mut self,
bt: &mut Bluetooth,
out: &mut [Service],
timer: &Timer,
) -> Result<usize, ClientError> {
let mut resp = [0u8; ATT_MAX_MTU as usize];
let mut count = 0;
let mut start = HANDLE_MIN;
loop {
let ps = UUID_PRIMARY_SERVICE.to_le_bytes();
let req = [
OP_READ_BY_GROUP_TYPE_REQ,
start.to_le_bytes()[0],
start.to_le_bytes()[1],
HANDLE_MAX.to_le_bytes()[0],
HANDLE_MAX.to_le_bytes()[1],
ps[0],
ps[1],
];
let n = match self.request(bt, &req, OP_READ_BY_GROUP_TYPE_RSP, &mut resp, timer) {
Ok(n) => n,
Err(ClientError::Att { code, .. }) if code == ERR_ATTRIBUTE_NOT_FOUND => {
return Ok(count)
}
Err(e) => return Err(e),
};
if n < 2 {
return Err(ClientError::Protocol);
}
let entry_len = resp[1] as usize;
if entry_len < 6 || (n - 2) % entry_len != 0 {
return Err(ClientError::Protocol);
}
let mut last_end = 0u16;
for chunk in resp[2..n].chunks_exact(entry_len) {
let start_handle = u16::from_le_bytes([chunk[0], chunk[1]]);
let end_handle = u16::from_le_bytes([chunk[2], chunk[3]]);
let Some(uuid) = uuid_from_raw(&chunk[4..entry_len]) else {
return Err(ClientError::Protocol);
};
if count >= out.len() {
return Err(ClientError::Overflow);
}
out[count] = Service {
start_handle,
end_handle,
uuid,
};
count += 1;
last_end = end_handle;
}
if last_end == HANDLE_MAX {
return Ok(count);
}
start = last_end + 1;
}
}
pub fn discover_characteristics(
&mut self,
bt: &mut Bluetooth,
start_handle: u16,
end_handle: u16,
out: &mut [Characteristic],
timer: &Timer,
) -> Result<usize, ClientError> {
let mut resp = [0u8; ATT_MAX_MTU as usize];
let mut count = 0;
let mut start = start_handle;
loop {
if start > end_handle {
return Ok(count);
}
let ct = UUID_CHARACTERISTIC.to_le_bytes();
let req = [
OP_READ_BY_TYPE_REQ,
start.to_le_bytes()[0],
start.to_le_bytes()[1],
end_handle.to_le_bytes()[0],
end_handle.to_le_bytes()[1],
ct[0],
ct[1],
];
let n = match self.request(bt, &req, OP_READ_BY_TYPE_RSP, &mut resp, timer) {
Ok(n) => n,
Err(ClientError::Att { code, .. }) if code == ERR_ATTRIBUTE_NOT_FOUND => {
return Ok(count)
}
Err(e) => return Err(e),
};
if n < 2 {
return Err(ClientError::Protocol);
}
let entry_len = resp[1] as usize;
if entry_len < 5 || (n - 2) % entry_len != 0 {
return Err(ClientError::Protocol);
}
let mut last_handle = 0u16;
for chunk in resp[2..n].chunks_exact(entry_len) {
let decl_handle = u16::from_le_bytes([chunk[0], chunk[1]]);
let properties = chunk[2];
let value_handle = u16::from_le_bytes([chunk[3], chunk[4]]);
let Some(uuid) = uuid_from_raw(&chunk[5..entry_len]) else {
return Err(ClientError::Protocol);
};
if count >= out.len() {
return Err(ClientError::Overflow);
}
out[count] = Characteristic {
decl_handle,
properties,
value_handle,
uuid,
};
count += 1;
last_handle = decl_handle;
}
if last_handle == HANDLE_MAX {
return Ok(count);
}
start = last_handle + 1;
}
}
pub fn discover_descriptors(
&mut self,
bt: &mut Bluetooth,
start_handle: u16,
end_handle: u16,
out: &mut [Descriptor],
timer: &Timer,
) -> Result<usize, ClientError> {
let mut resp = [0u8; ATT_MAX_MTU as usize];
let mut count = 0;
let mut start = start_handle;
loop {
if start > end_handle {
return Ok(count);
}
let req = [
OP_FIND_INFORMATION_REQ,
start.to_le_bytes()[0],
start.to_le_bytes()[1],
end_handle.to_le_bytes()[0],
end_handle.to_le_bytes()[1],
];
let n = match self.request(bt, &req, OP_FIND_INFORMATION_RSP, &mut resp, timer) {
Ok(n) => n,
Err(ClientError::Att { code, .. }) if code == ERR_ATTRIBUTE_NOT_FOUND => {
return Ok(count)
}
Err(e) => return Err(e),
};
if n < 2 {
return Err(ClientError::Protocol);
}
let uuid_len = match resp[1] {
0x01 => 2,
0x02 => 16,
_ => return Err(ClientError::Protocol),
};
let entry_len = 2 + uuid_len;
if (n - 2) % entry_len != 0 {
return Err(ClientError::Protocol);
}
let mut last_handle = 0u16;
for chunk in resp[2..n].chunks_exact(entry_len) {
let handle = u16::from_le_bytes([chunk[0], chunk[1]]);
let Some(uuid) = uuid_from_raw(&chunk[2..entry_len]) else {
return Err(ClientError::Protocol);
};
if count >= out.len() {
return Err(ClientError::Overflow);
}
out[count] = Descriptor { handle, uuid };
count += 1;
last_handle = handle;
}
if last_handle == HANDLE_MAX {
return Ok(count);
}
start = last_handle + 1;
}
}
pub fn write(
&mut self,
bt: &mut Bluetooth,
handle: u16,
value: &[u8],
timer: &Timer,
) -> Result<(), ClientError> {
let mut req = [0u8; ATT_MAX_MTU as usize];
req[0] = OP_WRITE_REQ;
req[1..3].copy_from_slice(&handle.to_le_bytes());
let vlen = value.len().min(self.mtu as usize - 3);
req[3..3 + vlen].copy_from_slice(&value[..vlen]);
let mut resp = [0u8; ATT_MAX_MTU as usize];
self.request(bt, &req[..3 + vlen], OP_WRITE_RSP, &mut resp, timer)?;
Ok(())
}
pub fn subscribe(
&mut self,
bt: &mut Bluetooth,
ccc_handle: u16,
bits: u16,
timer: &Timer,
) -> Result<(), ClientError> {
self.write(bt, ccc_handle, &bits.to_le_bytes(), timer)
}
pub fn feed(&mut self, acl: &AclData, out: &mut [u8]) -> Option<Notification> {
if acl.handle != self.handle {
return None;
}
let pdu = self.reasm.feed(acl)?;
if pdu.cid != CID_ATT {
return None;
}
let payload = pdu.payload;
if payload.first() != Some(&OP_HANDLE_VALUE_NOTIFICATION) || payload.len() < 3 {
return None;
}
let value_handle = u16::from_le_bytes([payload[1], payload[2]]);
let value = &payload[3..];
let len = value.len().min(out.len());
out[..len].copy_from_slice(&value[..len]);
Some(Notification { value_handle, len })
}
fn request(
&mut self,
bt: &mut Bluetooth,
req: &[u8],
expect: u8,
resp: &mut [u8],
timer: &Timer,
) -> Result<usize, ClientError> {
l2cap::send(bt, self.handle, CID_ATT, req)?;
let deadline = timer.now_micros() + (ATT_REQUEST_TIMEOUT_MS as u64) * 1000;
loop {
let now = timer.now_micros();
if now >= deadline {
return Err(ClientError::Timeout);
}
let remaining_ms = (((deadline - now) / 1000) as u32 + 1).min(POLL_SLICE_MS);
match bt.poll(timer, remaining_ms)? {
Some(Event::Acl(acl)) => {
if acl.handle != self.handle {
continue;
}
let Some(pdu) = self.reasm.feed(&acl) else {
continue;
};
if pdu.cid != CID_ATT {
continue;
}
let payload = pdu.payload;
let Some(&op) = payload.first() else {
continue;
};
match op {
OP_ERROR_RESPONSE => {
if payload.len() < 5 {
return Err(ClientError::Protocol);
}
if payload[1] != req.first().copied().unwrap_or(0) {
continue;
}
return Err(ClientError::Att {
request: payload[1],
handle: u16::from_le_bytes([payload[2], payload[3]]),
code: payload[4],
});
}
_ if op == expect => {
let n = payload.len().min(resp.len());
resp[..n].copy_from_slice(&payload[..n]);
return Ok(n);
}
_ => continue,
}
}
Some(Event::Disconnected { reason, .. }) => {
return Err(ClientError::Disconnected(reason))
}
Some(_) | None => continue,
}
}
}
}
fn uuid_from_raw(raw: &[u8]) -> Option<Uuid> {
match raw.len() {
2 => Some(Uuid::Bit16(u16::from_le_bytes([raw[0], raw[1]]))),
16 => {
let mut bytes = [0u8; 16];
bytes.copy_from_slice(raw);
Some(Uuid::Bit128(bytes))
}
_ => None,
}
}