use core::marker::PhantomData;
use embassy_sync::blocking_mutex::raw::RawMutex;
use self::client_att_table::ClientAttTables;
pub use self::client_att_table::{ClientAttTable, ClientAttTableBuilder, ClientAttTableView};
use crate::att::{self, AttClient, AttCmd, AttErrorCode, AttReq};
use crate::attribute::{Attribute, AttributeData, AttributeTable, CCCD};
use crate::connection::Connection;
use crate::cursor::WriteCursor;
#[cfg(feature = "gatt")]
use crate::types::gatt_traits::AsGatt;
use crate::types::uuid::Uuid;
use crate::{codec, Error, Identity, PacketPool};
mod client_att_table;
#[derive(Default)]
struct Client {
identity: Identity,
is_connected: bool,
}
impl Client {
fn set_identity(&mut self, identity: Identity) {
self.identity = identity;
}
}
pub struct AttributeServer<'values, M: RawMutex, P: PacketPool, const ATT_MAX: usize, const CONN_MAX: usize> {
att_table: AttributeTable<'values, M, ATT_MAX>,
client_att_tables: ClientAttTables<M, CONN_MAX>,
_p: PhantomData<P>,
}
pub(crate) mod sealed {
use super::*;
pub trait DynamicAttributeServer<P: PacketPool> {
fn connect(&self, connection: &Connection<'_, P>) -> Result<(), Error>;
fn disconnect(&self, connection: &Connection<'_, P>);
fn process(
&self,
connection: &Connection<'_, P>,
packet: &AttClient,
rx: &mut [u8],
) -> Result<Option<usize>, Error>;
fn should_notify(&self, connection: &Connection<'_, P>, cccd_handle: u16) -> bool;
fn should_indicate(&self, connection: &Connection<'_, P>, cccd_handle: u16) -> bool;
fn set(&self, connection: &Connection<'_, P>, att_handle: u16, input: &[u8]) -> Result<(), Error>;
fn get(&self, connection: &Connection<'_, P>, att_handle: u16, buf: &mut [u8]) -> Result<usize, Error>;
fn update_identity(&self, identity: Identity) -> Result<(), Error>;
fn can_read(&self, connection: &Connection<'_, P>, handle: u16) -> Result<(), AttErrorCode>;
fn can_write(&self, connection: &Connection<'_, P>, handle: u16) -> Result<(), AttErrorCode>;
}
}
pub trait DynamicAttributeServer<P: PacketPool>: sealed::DynamicAttributeServer<P> {}
impl<M: RawMutex, P: PacketPool, const ATT_MAX: usize, const CONN_MAX: usize> DynamicAttributeServer<P>
for AttributeServer<'_, M, P, ATT_MAX, CONN_MAX>
{
}
impl<M: RawMutex, P: PacketPool, const ATT_MAX: usize, const CONN_MAX: usize> sealed::DynamicAttributeServer<P>
for AttributeServer<'_, M, P, ATT_MAX, CONN_MAX>
{
fn connect(&self, connection: &Connection<'_, P>) -> Result<(), Error> {
AttributeServer::connect(self, connection)
}
fn disconnect(&self, connection: &Connection<'_, P>) {
#[cfg(feature = "security")]
let bonded = connection.is_bonded_peer();
#[cfg(not(feature = "security"))]
let bonded = false;
self.client_att_tables.disconnect(&connection.peer_identity(), bonded);
}
fn process(
&self,
connection: &Connection<'_, P>,
packet: &AttClient,
rx: &mut [u8],
) -> Result<Option<usize>, Error> {
let res = AttributeServer::process(self, connection, packet, rx)?;
Ok(res)
}
fn should_notify(&self, connection: &Connection<'_, P>, cccd_handle: u16) -> bool {
AttributeServer::should_notify(self, connection, cccd_handle)
}
fn should_indicate(&self, connection: &Connection<'_, P>, cccd_handle: u16) -> bool {
AttributeServer::should_indicate(self, connection, cccd_handle)
}
fn get(&self, connection: &Connection<'_, P>, att_handle: u16, buf: &mut [u8]) -> Result<usize, Error> {
self.att_table
.with_attribute(att_handle, |att| {
if matches!(att.data, AttributeData::ClientSpecific { .. }) {
self.client_att_tables
.read(&connection.peer_identity(), att_handle, 0, buf)
} else {
att.read(0, buf)
}
.map_err(Into::into)
})
.unwrap_or(Err(Error::NotFound))
}
fn set(&self, connection: &Connection<'_, P>, att_handle: u16, input: &[u8]) -> Result<(), Error> {
self.att_table
.with_attribute_mut(att_handle, |att| {
if !att.data.is_variable_len() && att.data.capacity() != input.len() {
Err(Error::UnexpectedDataLength {
expected: att.data.capacity(),
actual: input.len(),
})
} else if matches!(att.data, AttributeData::ClientSpecific { .. }) {
self.client_att_tables
.write(&connection.peer_identity(), att_handle, 0, input)
.map_err(Into::into)
} else {
att.write(0, input).map_err(Into::into)
}
})
.unwrap_or(Err(Error::NotFound))
}
fn update_identity(&self, identity: Identity) -> Result<(), Error> {
self.client_att_tables.update_identity(identity)
}
fn can_read(&self, connection: &Connection<'_, P>, handle: u16) -> Result<(), AttErrorCode> {
self.att_table
.with_attribute(handle, |att| self.can_read(connection, att))
.unwrap_or(Err(AttErrorCode::INVALID_HANDLE))
}
fn can_write(&self, connection: &Connection<'_, P>, handle: u16) -> Result<(), AttErrorCode> {
self.att_table
.with_attribute(handle, |att| self.can_write(connection, att))
.unwrap_or(Err(AttErrorCode::INVALID_HANDLE))
}
}
impl<'values, M: RawMutex, P: PacketPool, const ATT_MAX: usize, const CONN_MAX: usize>
AttributeServer<'values, M, P, ATT_MAX, CONN_MAX>
{
pub fn new(att_table: AttributeTable<'values, M, ATT_MAX>) -> AttributeServer<'values, M, P, ATT_MAX, CONN_MAX> {
let cccd_tables = ClientAttTables::new(&att_table);
AttributeServer {
att_table,
client_att_tables: cccd_tables,
_p: PhantomData,
}
}
pub fn read(
&self,
connection: &Connection<'_, P>,
handle: u16,
offset: usize,
data: &mut [u8],
) -> Result<usize, AttErrorCode> {
self.att_table
.with_attribute(handle, |att| {
self.read_attribute_data(connection, handle, offset, att, data)
})
.unwrap_or(Err(AttErrorCode::ATTRIBUTE_NOT_FOUND))
}
pub(crate) fn connect(&self, connection: &Connection<'_, P>) -> Result<(), Error> {
self.client_att_tables.connect(&connection.peer_identity())
}
pub(crate) fn should_notify(&self, connection: &Connection<'_, P>, cccd_handle: u16) -> bool {
self.client_att_tables
.with_value(&connection.peer_identity(), cccd_handle, |value| {
if let Ok(value) = value.try_into() {
CCCD(u16::from_le_bytes(value)).should_notify()
} else {
false
}
})
.unwrap_or(false)
}
pub(crate) fn should_indicate(&self, connection: &Connection<'_, P>, cccd_handle: u16) -> bool {
self.client_att_tables
.with_value(&connection.peer_identity(), cccd_handle, |value| {
if let Ok(value) = value.try_into() {
CCCD(u16::from_le_bytes(value)).should_indicate()
} else {
false
}
})
.unwrap_or(false)
}
fn can_read(&self, connection: &Connection<'_, P>, att: &Attribute<'_>) -> Result<(), AttErrorCode> {
match connection.security_level() {
Ok(level) => att.permissions().can_read(
level,
#[cfg(feature = "legacy-pairing")]
connection.encryption_key_len().unwrap_or(0),
),
Err(_) => Err(AttErrorCode::INVALID_HANDLE),
}
}
fn can_write(&self, connection: &Connection<'_, P>, att: &Attribute<'_>) -> Result<(), AttErrorCode> {
match connection.security_level() {
Ok(level) => att.permissions().can_write(
level,
#[cfg(feature = "legacy-pairing")]
connection.encryption_key_len().unwrap_or(0),
),
Err(_) => Err(AttErrorCode::INVALID_HANDLE),
}
}
fn read_attribute_data(
&self,
connection: &Connection<'_, P>,
handle: u16,
offset: usize,
att: &Attribute<'values>,
data: &mut [u8],
) -> Result<usize, AttErrorCode> {
self.can_read(connection, att)?;
if matches!(att.data, AttributeData::ClientSpecific { .. }) {
return self
.client_att_tables
.read(&connection.peer_identity(), handle, offset, data);
}
att.read(offset, data)
}
fn write_attribute_data(
&self,
connection: &Connection<'_, P>,
handle: u16,
offset: usize,
att: &mut Attribute<'values>,
data: &[u8],
) -> Result<(), AttErrorCode> {
self.can_write(connection, att)?;
if matches!(att.data, AttributeData::ClientSpecific { .. }) {
return self
.client_att_tables
.write(&connection.peer_identity(), handle, offset, data);
}
att.write(offset, data)
}
fn handle_read_by_type_req(
&self,
connection: &Connection<'_, P>,
buf: &mut [u8],
start: u16,
end: u16,
attribute_type: &Uuid,
) -> Result<usize, codec::Error> {
let mut handle = start;
let mut data = WriteCursor::new(buf);
if start > end || start == 0 {
return Self::error_response(data, att::ATT_READ_BY_TYPE_REQ, start, AttErrorCode::INVALID_HANDLE);
}
let (mut header, mut body) = data.split(2)?;
let err = self.att_table.iterate_from(start, |it| {
let mut ret = Err(AttErrorCode::ATTRIBUTE_NOT_FOUND);
for (att_handle, att) in it {
if &att.uuid == attribute_type && att_handle <= end {
body.write(att_handle)?;
handle = att_handle;
let new_ret = self.read_attribute_data(connection, att_handle, 0, att, body.write_buf());
match (new_ret, ret) {
(Ok(first_length), Err(_)) => {
ret = new_ret;
body.commit(first_length)?;
}
(Ok(new_length), Ok(old_length)) => {
if new_length == old_length {
body.commit(new_length)?;
} else {
body.truncate(body.len() - 2);
break;
}
}
(Err(error_code), Ok(_old_length)) => {
body.truncate(body.len() - 2);
break;
}
(Err(_), Err(_)) => {
ret = new_ret;
break;
}
}
if let Ok(expected_length) = ret {
if body.available() < expected_length + 2 {
break;
}
}
}
}
ret
});
match err {
Ok(len) => {
header.write(att::ATT_READ_BY_TYPE_RSP)?;
header.write(2 + len as u8)?;
Ok(header.len() + body.len())
}
Err(e) => Ok(Self::error_response(data, att::ATT_READ_BY_TYPE_REQ, handle, e)?),
}
}
fn handle_read_by_group_type_req(
&self,
connection: &Connection<'_, P>,
buf: &mut [u8],
start: u16,
end: u16,
group_type: &Uuid,
) -> Result<usize, codec::Error> {
let mut handle = start;
let mut data = WriteCursor::new(buf);
if start > end || start == 0 {
return Self::error_response(data, att::ATT_READ_BY_TYPE_REQ, start, AttErrorCode::INVALID_HANDLE);
}
let (mut header, mut body) = data.split(2)?;
let err = self.att_table.iterate_from(start, |mut it| {
let mut ret: Result<usize, AttErrorCode> = Err(AttErrorCode::ATTRIBUTE_NOT_FOUND);
while let Some((att_handle, att)) = it.next() {
if &att.uuid == group_type && att_handle <= end {
handle = att_handle;
let last_handle_in_group = it.service_group_end();
body.write(att_handle)?;
body.write(last_handle_in_group)?;
let new_ret = self.read_attribute_data(connection, att_handle, 0, att, body.write_buf());
match (new_ret, ret) {
(Ok(first_length), Err(_)) => {
ret = new_ret;
body.commit(first_length)?;
}
(Ok(new_length), Ok(old_length)) => {
if new_length == old_length {
body.commit(new_length)?;
} else {
body.truncate(body.len() - 4);
break;
}
}
(Err(error_code), Ok(_old_length)) => {
body.truncate(body.len() - 4);
break;
}
(Err(_), Err(_)) => {
ret = new_ret;
break;
}
}
if let Ok(expected_length) = ret {
if body.available() < expected_length + 4 {
break;
}
}
}
}
ret
});
match err {
Ok(len) => {
header.write(att::ATT_READ_BY_GROUP_TYPE_RSP)?;
header.write(4 + len as u8)?;
Ok(header.len() + body.len())
}
Err(e) => Ok(Self::error_response(data, att::ATT_READ_BY_GROUP_TYPE_REQ, handle, e)?),
}
}
fn handle_read_req(
&self,
connection: &Connection<'_, P>,
buf: &mut [u8],
handle: u16,
) -> Result<usize, codec::Error> {
let mut data = WriteCursor::new(buf);
data.write(att::ATT_READ_RSP)?;
let err = self
.att_table
.with_attribute(handle, |att| {
let len = self.read_attribute_data(connection, handle, 0, att, data.write_buf())?;
data.commit(len)?;
Ok(len)
})
.unwrap_or(Err(AttErrorCode::ATTRIBUTE_NOT_FOUND));
match err {
Ok(_) => Ok(data.len()),
Err(e) => Ok(Self::error_response(data, att::ATT_READ_REQ, handle, e)?),
}
}
fn handle_write_cmd(
&self,
connection: &Connection<'_, P>,
buf: &mut [u8],
handle: u16,
data: &[u8],
) -> Result<usize, codec::Error> {
self.att_table.with_attribute_mut(handle, |att| {
let _ = self.write_attribute_data(connection, handle, 0, att, data);
});
Ok(0)
}
fn handle_write_req(
&self,
connection: &Connection<'_, P>,
buf: &mut [u8],
handle: u16,
data: &[u8],
) -> Result<usize, codec::Error> {
let err = self
.att_table
.with_attribute_mut(handle, |att| {
self.write_attribute_data(connection, handle, 0, att, data)
})
.unwrap_or(Err(AttErrorCode::ATTRIBUTE_NOT_FOUND));
let mut w = WriteCursor::new(buf);
match err {
Ok(()) => {
w.write(att::ATT_WRITE_RSP)?;
Ok(w.len())
}
Err(e) => Ok(Self::error_response(w, att::ATT_WRITE_REQ, handle, e)?),
}
}
fn handle_find_type_value(
&self,
buf: &mut [u8],
start: u16,
end: u16,
attr_type: u16,
attr_value: &[u8],
) -> Result<usize, codec::Error> {
let mut w = WriteCursor::new(buf);
let attr_type = Uuid::new_short(attr_type);
if start > end || start == 0 {
return Self::error_response(w, att::ATT_READ_BY_TYPE_REQ, start, AttErrorCode::INVALID_HANDLE);
}
w.write(att::ATT_FIND_BY_TYPE_VALUE_RSP)?;
self.att_table.iterate_from(start, |mut it| {
while let Some((handle, att)) = it.next() {
if handle <= end && att.uuid == attr_type {
let value = att.data.value();
if value == Some(attr_value) {
if w.available() < 4 {
break;
}
w.write(handle)?;
w.write(it.service_group_end())?;
}
}
}
Ok::<(), codec::Error>(())
})?;
if w.len() > 1 {
Ok(w.len())
} else {
Ok(Self::error_response(
w,
att::ATT_FIND_BY_TYPE_VALUE_REQ,
start,
AttErrorCode::ATTRIBUTE_NOT_FOUND,
)?)
}
}
fn handle_find_information(&self, buf: &mut [u8], start: u16, end: u16) -> Result<usize, codec::Error> {
let mut w = WriteCursor::new(buf);
if start > end || start == 0 {
return Self::error_response(w, att::ATT_READ_BY_TYPE_REQ, start, AttErrorCode::INVALID_HANDLE);
}
let (mut header, mut body) = w.split(2)?;
header.write(att::ATT_FIND_INFORMATION_RSP)?;
let mut t = 0;
self.att_table.iterate_from(start, |it| {
for (handle, att) in it {
if handle <= end {
if t == 0 {
t = att.uuid.get_type();
} else if t != att.uuid.get_type() {
break;
}
if body.available() < 2 + att.uuid.as_raw().len() {
break;
}
body.write(handle)?;
body.append(att.uuid.as_raw())?;
}
}
Ok::<(), codec::Error>(())
})?;
header.write(t)?;
if body.len() > 2 {
Ok(header.len() + body.len())
} else {
Ok(Self::error_response(
w,
att::ATT_FIND_INFORMATION_REQ,
start,
AttErrorCode::ATTRIBUTE_NOT_FOUND,
)?)
}
}
fn error_response(
mut w: WriteCursor<'_>,
opcode: u8,
handle: u16,
code: AttErrorCode,
) -> Result<usize, codec::Error> {
w.reset();
w.write(att::ATT_ERROR_RSP)?;
w.write(opcode)?;
w.write(handle)?;
w.write(code)?;
Ok(w.len())
}
#[cfg(feature = "att-queued-writes")]
fn handle_prepare_write(
&self,
connection: &Connection<'_, P>,
buf: &mut [u8],
handle: u16,
offset: u16,
value: &[u8],
) -> Result<usize, codec::Error> {
let mut w = WriteCursor::new(buf);
let err = connection.prepare_write(handle, offset, value);
match err {
Ok(()) => {
w.write(att::ATT_PREPARE_WRITE_RSP)?;
w.write(handle)?;
w.write(offset)?;
w.append(value)?;
Ok(w.len())
}
Err(e) => Ok(Self::error_response(w, att::ATT_PREPARE_WRITE_REQ, handle, e)?),
}
}
#[cfg(not(feature = "att-queued-writes"))]
fn handle_prepare_write(
&self,
_connection: &Connection<'_, P>,
buf: &mut [u8],
handle: u16,
_offset: u16,
_value: &[u8],
) -> Result<usize, codec::Error> {
let w = WriteCursor::new(buf);
Self::error_response(w, att::ATT_PREPARE_WRITE_REQ, 0, AttErrorCode::REQUEST_NOT_SUPPORTED)
}
#[cfg(feature = "att-queued-writes")]
fn handle_execute_write(
&self,
connection: &Connection<'_, P>,
buf: &mut [u8],
flags: u8,
) -> Result<usize, codec::Error> {
let mut w = WriteCursor::new(buf);
if flags == 0x01 {
let err = connection.with_prepare_write(|pw| {
if pw.handle == 0 {
return Ok(());
}
let data = &pw.buf[..pw.len as usize];
self.att_table
.with_attribute_mut(pw.handle, |att| {
self.write_attribute_data(connection, pw.handle, pw.offset as usize, att, data)
})
.unwrap_or(Err(AttErrorCode::ATTRIBUTE_NOT_FOUND))
});
connection.clear_prepare_write();
match err {
Ok(()) => {
w.write(att::ATT_EXECUTE_WRITE_RSP)?;
Ok(w.len())
}
Err(e) => Ok(Self::error_response(w, att::ATT_EXECUTE_WRITE_REQ, 0, e)?),
}
} else {
connection.clear_prepare_write();
w.write(att::ATT_EXECUTE_WRITE_RSP)?;
Ok(w.len())
}
}
#[cfg(not(feature = "att-queued-writes"))]
fn handle_execute_write(
&self,
_connection: &Connection<'_, P>,
buf: &mut [u8],
_flags: u8,
) -> Result<usize, codec::Error> {
let w = WriteCursor::new(buf);
Self::error_response(w, att::ATT_EXECUTE_WRITE_REQ, 0, AttErrorCode::REQUEST_NOT_SUPPORTED)
}
fn handle_read_blob(
&self,
connection: &Connection<'_, P>,
buf: &mut [u8],
handle: u16,
offset: u16,
) -> Result<usize, codec::Error> {
let mut w = WriteCursor::new(buf);
w.write(att::ATT_READ_BLOB_RSP)?;
let err = self
.att_table
.with_attribute(handle, |att| {
let n = self.read_attribute_data(connection, handle, offset as usize, att, w.write_buf())?;
w.commit(n)?;
Ok(n)
})
.unwrap_or(Err(AttErrorCode::ATTRIBUTE_NOT_FOUND));
match err {
Ok(_) => Ok(w.len()),
Err(e) => Ok(Self::error_response(w, att::ATT_READ_BLOB_REQ, handle, e)?),
}
}
fn handle_read_multiple(&self, buf: &mut [u8], handles: &[u8]) -> Result<usize, codec::Error> {
let w = WriteCursor::new(buf);
Self::error_response(
w,
att::ATT_READ_MULTIPLE_REQ,
u16::from_le_bytes([handles[0], handles[1]]),
AttErrorCode::ATTRIBUTE_NOT_FOUND,
)
}
pub fn process(
&self,
connection: &Connection<'_, P>,
packet: &AttClient,
rx: &mut [u8],
) -> Result<Option<usize>, codec::Error> {
let len = match packet {
AttClient::Request(AttReq::ReadByType {
start,
end,
attribute_type,
}) => self.handle_read_by_type_req(connection, rx, *start, *end, attribute_type)?,
AttClient::Request(AttReq::ReadByGroupType { start, end, group_type }) => {
self.handle_read_by_group_type_req(connection, rx, *start, *end, group_type)?
}
AttClient::Request(AttReq::FindInformation {
start_handle,
end_handle,
}) => self.handle_find_information(rx, *start_handle, *end_handle)?,
AttClient::Request(AttReq::Read { handle }) => self.handle_read_req(connection, rx, *handle)?,
AttClient::Command(AttCmd::Write { handle, data }) => {
self.handle_write_cmd(connection, rx, *handle, data)?;
0
}
AttClient::Request(AttReq::Write { handle, data }) => {
self.handle_write_req(connection, rx, *handle, data)?
}
AttClient::Request(AttReq::ExchangeMtu { mtu }) => 0,
AttClient::Request(AttReq::FindByTypeValue {
start_handle,
end_handle,
att_type,
att_value,
}) => self.handle_find_type_value(rx, *start_handle, *end_handle, *att_type, att_value)?,
AttClient::Request(AttReq::PrepareWrite { handle, offset, value }) => {
self.handle_prepare_write(connection, rx, *handle, *offset, value)?
}
AttClient::Request(AttReq::ExecuteWrite { flags }) => self.handle_execute_write(connection, rx, *flags)?,
AttClient::Request(AttReq::ReadBlob { handle, offset }) => {
self.handle_read_blob(connection, rx, *handle, *offset)?
}
AttClient::Request(AttReq::ReadMultiple { handles }) => self.handle_read_multiple(rx, handles)?,
AttClient::Confirmation(_) => 0,
};
if len > 0 {
Ok(Some(len))
} else {
Ok(None)
}
}
pub fn table(&self) -> &AttributeTable<'values, M, ATT_MAX> {
&self.att_table
}
#[cfg(feature = "gatt")]
pub async fn write_and_notify<C: crate::Controller, T: AsGatt + ?Sized>(
&self,
stack: &crate::Stack<'_, C, P>,
value_handle: u16,
data: &T,
) -> Result<(), Error> {
self.att_table.write(value_handle, 0, data.as_gatt())?;
let cccd_handle = value_handle + 1;
if self.att_table.uuid(cccd_handle)
!= Some(bt_hci::uuid::descriptors::CLIENT_CHARACTERISTIC_CONFIGURATION.into())
{
return Ok(());
}
self.notify(stack, value_handle, data).await
}
#[cfg(feature = "gatt")]
pub async fn notify<C: crate::Controller, T: AsGatt + ?Sized>(
&self,
stack: &crate::Stack<'_, C, P>,
value_handle: u16,
data: &T,
) -> Result<(), Error> {
let cccd_handle = value_handle + 1;
if self.att_table.uuid(cccd_handle)
!= Some(bt_hci::uuid::descriptors::CLIENT_CHARACTERISTIC_CONFIGURATION.into())
{
return Err(Error::InvalidValue);
}
let data = data.as_gatt();
for conn in stack.connections() {
if self.should_indicate(&conn, cccd_handle) {
let uns = att::AttUns::Indicate {
handle: value_handle,
data,
};
if let Ok(pdu) = crate::gatt::assemble(&conn, att::AttServer::Unsolicited(uns)) {
conn.send(pdu).await;
}
} else if self.should_notify(&conn, cccd_handle) {
let uns = att::AttUns::Notify {
handle: value_handle,
data,
};
if let Ok(pdu) = crate::gatt::assemble(&conn, att::AttServer::Unsolicited(uns)) {
conn.send(pdu).await;
}
}
}
Ok(())
}
pub fn get_client_att_table(&self, connection: &Connection<'_, P>) -> Option<ClientAttTable> {
self.client_att_tables.get_client_att_table(&connection.peer_identity())
}
pub fn set_client_att_table(&self, connection: &Connection<'_, P>, table: &ClientAttTableView<'_>) {
self.client_att_tables
.set_client_att_table(&connection.peer_identity(), table);
}
}
#[cfg(test)]
mod tests {
use core::task::Poll;
use bt_hci::param::{AddrKind, BdAddr, ConnHandle, LeConnRole};
use bt_hci::uuid::declarations::{PRIMARY_SERVICE, SECONDARY_SERVICE};
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
use super::*;
use crate::connection_manager::tests::{setup, ADDR_1};
use crate::prelude::*;
use crate::Address;
#[test]
fn test_attribute_server_last_handle_of_group() {
let primary_service_group_type = Uuid::new_short(0x2800);
let _ = env_logger::try_init();
const MAX_ATTRIBUTES: usize = 1024;
const CONNECTIONS_MAX: usize = 3;
const L2CAP_CHANNELS_MAX: usize = 5;
type FacadeDummyType = [u8; 0];
for interior_handle_count in 0..=64u8 {
debug!("Testing with interior handle count of {}", interior_handle_count);
let mut table: AttributeTable<'_, NoopRawMutex, { MAX_ATTRIBUTES }> = AttributeTable::new();
{
let svc = table.add_service(Service {
uuid: Uuid::new_long([10; 16]),
});
}
{
let mut svc = table.add_service(Service {
uuid: Uuid::new_long([0; 16]),
});
for c in 0..interior_handle_count {
let _service_instance = svc
.add_characteristic_ro::<[u8; 2], _>(Uuid::new_long([c; 16]), &[0, 0])
.build();
}
}
{
table.add_service(Service {
uuid: Uuid::new_long([8; 16]),
});
}
table.iterate(|it| {
for (handle, att) in it {
let uuid = &att.uuid;
if *uuid == PRIMARY_SERVICE.into() || *uuid == SECONDARY_SERVICE.into() {
trace!("service 0x{:0>4x?}, 0x{:0>2x?}", handle, uuid,);
} else {
trace!(" 0x{:0>4x?}, 0x{:0>2x?}", handle, uuid,);
}
}
});
let server = AttributeServer::<_, DefaultPacketPool, MAX_ATTRIBUTES, CONNECTIONS_MAX>::new(table);
let mgr = setup();
assert!(mgr.poll_accept(LeConnRole::Peripheral, &[], None).is_pending());
unwrap!(mgr.connect(
ConnHandle::new(0),
Address::new(AddrKind::RANDOM, BdAddr::new(ADDR_1)),
LeConnRole::Peripheral,
ConnParams::new(),
));
if let Poll::Ready(conn_handle) = mgr.poll_accept(LeConnRole::Peripheral, &[], None) {
let mut buffer = [0u8; 64];
let mut start = 1;
let end = u16::MAX;
for _ in 0..3 {
let length = server
.handle_read_by_group_type_req(
&conn_handle,
&mut buffer,
start,
end,
&primary_service_group_type,
)
.unwrap();
let response = &buffer[0..length];
trace!(" 0x{:0>2x?}", response);
assert_eq!(response[0], att::ATT_READ_BY_GROUP_TYPE_RSP);
let last_handle = u16::from_le_bytes([response[4], response[5]]);
start = last_handle.saturating_add(1);
}
} else {
panic!("expected connection to be accepted");
};
}
}
}