use std::marker::PhantomData;
use std::ops::Range;
use std::{iter, mem, slice};
use structbuf::Unpack;
use tracing::{debug, warn};
pub use builder::*;
use crate::gap::{Uuid, Uuid16, UuidType, UuidVec};
use super::*;
mod builder;
type Idx = u16;
#[derive(Clone, Debug, Default)]
pub struct Db {
attr: Box<[Attr]>,
data: Box<[u8]>,
}
impl Db {
#[inline(always)]
#[must_use]
pub fn build() -> Builder<Self> {
Builder::new()
}
#[inline(always)]
#[must_use]
pub const fn hash(&self) -> u128 {
let (p, i) = (self.data.as_ptr(), self.data.len() - mem::size_of::<u128>());
u128::from_le_bytes(unsafe { *p.add(i).cast() })
}
#[inline]
pub fn iter(&self) -> impl Iterator<Item = (Handle, Uuid, &[u8])> {
(self.attr.iter()).map(|at| (at.hdl, self.typ(at), self.value(at)))
}
#[inline]
#[must_use]
pub fn get(&self, hdl: Handle) -> Option<(Uuid, &[u8])> {
(self.try_get(hdl).ok()).map(|at| (self.typ(at), self.value(at)))
}
#[inline]
#[must_use]
pub(super) fn get_characteristic(&self, hdl: Handle) -> Option<CharInfo> {
(self.try_get(hdl).ok()).and_then(|at| self.characteristic_for_attr(at))
}
#[inline]
pub fn primary_services(
&self,
start: Handle,
uuid: Option<Uuid>,
) -> impl Iterator<Item = DbEntry<ServiceDef>> {
let i = self.try_get(start).map_or_else(|i| i, |at| self.index(at));
let uuid = uuid.map_or_else(UuidVec::default, UuidVec::new);
GroupIter::new(self, unsafe { self.attr.get_unchecked(i..) }, move |at| {
at.is_primary_service() && (uuid.is_empty() || self.value(at) == uuid.as_ref())
})
}
pub fn includes(&self, hdls: HandleRange) -> impl Iterator<Item = DbEntry<IncludeDef>> {
(self.service_attrs(hdls).iter())
.map_while(|at| at.is_include().then(|| DbEntry::new(self, at, at.hdl)))
}
pub fn characteristics(
&self,
hdls: HandleRange,
) -> impl Iterator<Item = DbEntry<CharacteristicDef>> {
GroupIter::new(self, self.service_attrs(hdls), Attr::is_char)
}
pub fn descriptors(&self, hdls: HandleRange) -> impl Iterator<Item = DbEntry<DescriptorDef>> {
let attr = self.subset(hdls).and_then(|s| {
use private::Group;
let decl = unsafe { self.attr.get_unchecked(..s.off).iter() }
.rfind(|&at| Attr::is_char(at))?;
(value_handle(self.value(decl)) < s.first().hdl
&& !(s.attr.iter()).any(|at| CharacteristicDef::is_next_group(at.typ)))
.then_some(s.attr)
});
(attr.unwrap_or_default().iter()).map(|at| DbEntry::new(self, at, at.hdl))
}
#[inline]
pub fn try_access(&self, req: Request, hdl: Handle) -> RspResult<Handle> {
self.try_multi_access(req, [hdl]).map(|_| hdl)
}
#[inline]
pub fn try_multi_access<T: AsRef<[Handle]>>(&self, req: Request, hdls: T) -> RspResult<T> {
let v = hdls.as_ref();
if v.is_empty() {
return req.op.err(ErrorCode::InvalidPdu); }
for &hdl in v {
let Ok(at) = self.try_get(hdl) else {
warn!("Denied {} for invalid {hdl}", req.op);
return req.op.hdl_err(ErrorCode::InvalidHandle, hdl);
};
self.access_check(req, at)?;
}
Ok(hdls)
}
#[inline]
pub fn try_range_access(
&self,
req: Request,
hdls: HandleRange,
uuid: Uuid,
) -> RspResult<Vec<Handle>> {
let attr = self.subset(hdls).map_or_else(Default::default, |s| s.attr);
let mut it = (attr.iter())
.filter_map(|at| (self.typ(at) == uuid).then(|| self.access_check(req, at)))
.peekable();
match it.peek() {
None => return req.op.hdl_err(ErrorCode::AttributeNotFound, hdls.start()),
Some(&r) => {
r?;
}
}
Ok(it.map_while(RspResult::ok).collect())
}
pub fn dump(&self) {
use Declaration::*;
macro_rules! log {
($at:ident, $fmt:expr$(, $($args:tt)*)?) => {
::tracing::debug!("[{:#06X}] {}", u16::from($at.hdl), format_args!($fmt$(, $($args)*)?))
};
}
let mut vhdl = Handle::MIN;
let mut last_char_hdl = Handle::MIN;
let mut cont = ' ';
debug!("GATT database:");
for at in self.attr.iter() {
let v = self.value(at);
let mut v = v.unpack();
if let Some(uuid16) = at.typ {
match uuid16.typ() {
UuidType::Declaration(d) => match d {
PrimaryService | SecondaryService => {
last_char_hdl = (self.service_group(at.hdl).unwrap().attr.iter())
.rfind(|at| at.is_char())
.map_or(Handle::MIN, |at| at.hdl);
let sec = ((!at.is_primary_service()).then_some("Secondary"))
.unwrap_or_default();
let uuid = Uuid::try_from(v.as_ref()).unwrap();
if let UuidType::Service(_) = uuid.typ() {
log!(at, "{sec}{uuid} <{uuid:?}>");
} else {
log!(at, "{sec}Service <{uuid:?}>");
}
}
Include => log!(at, "|__ [Include {:#06X}..={:#06X}]", v.u16(), v.u16()),
Characteristic => {
cont = if at.hdl < last_char_hdl { '|' } else { ' ' };
let _prop = Prop::from_bits(v.u8()).unwrap(); vhdl = Handle::new(v.u16()).unwrap();
let uuid = Uuid::try_from(v.as_ref()).unwrap();
if let UuidType::Characteristic(_) = uuid.typ() {
log!(at, "|__ {uuid} <{uuid:?}>");
} else {
log!(at, "|__ Characteristic <{uuid:?}>");
}
}
_ => unreachable!(),
},
UuidType::Characteristic(_) => log!(at, "{cont} |__ [Value <{uuid16:?}>]"),
UuidType::Descriptor(_) => log!(at, "{cont} |__ {uuid16} <{uuid16:?}>"),
typ => log!(at, "Unexpected {typ}"),
}
} else {
let uuid = self.typ(at);
if at.hdl <= vhdl {
log!(at, "{cont} |__ [Value <{uuid:?}>]");
} else {
log!(at, "{cont} |__ Descriptor <{uuid:?}>");
}
}
}
}
fn service_attrs(&self, hdls: HandleRange) -> &[Attr] {
let attr = self.subset(hdls).and_then(|s| {
let attr = if s.first().is_service() {
unsafe { s.attr.get_unchecked(1..) }
} else {
s.attr
};
(!attr.iter().any(Attr::is_service)).then_some(attr)
});
attr.unwrap_or_default()
}
fn access_check(&self, req: Request, at: &Attr) -> RspResult<Handle> {
use Opcode::*;
let (op, hdl) = (req.op, at.hdl);
if let Err(e) = at.perms.test(req.ac) {
warn!("Denied {op} to {hdl} due to {e}");
return op.hdl_err(e, hdl);
}
let Some(ch) = self.characteristic_for_attr(at) else {
return Ok(hdl); };
if hdl != ch.vhdl {
if req.ac.typ() == Access::WRITE
&& matches!(at.typ, Some(Descriptor::CHARACTERISTIC_USER_DESCRIPTION))
&& !(ch.ext_props).map_or(false, |p| p.contains(ExtProp::WRITABLE_AUX))
{
warn!("Denied {op} to {hdl} because WRITABLE_AUX bit is not set");
return op.hdl_err(ErrorCode::WriteNotPermitted, hdl);
}
return Ok(hdl); }
let bit = match op {
ReadReq | ReadByTypeReq | ReadBlobReq | ReadMultipleReq | ReadMultipleVariableReq => Prop::READ, WriteCmd => Prop::WRITE_CMD, WriteReq | PrepareWriteReq => Prop::WRITE, SignedWriteCmd => Prop::SIGNED_WRITE_CMD, _ => {
warn!("Denied non-read/write {op} for {hdl}");
return op.hdl_err(ErrorCode::RequestNotSupported, hdl);
}
};
if !ch.props.contains(bit) {
let e = if req.ac.typ() == Access::READ {
ErrorCode::ReadNotPermitted
} else {
ErrorCode::WriteNotPermitted
};
warn!("Denied {op} for {hdl} due to {e} by properties");
return op.hdl_err(e, hdl);
}
if matches!(op, PrepareWriteReq)
&& !(ch.ext_props).map_or(true, |p| p.contains(ExtProp::RELIABLE_WRITE))
{
warn!("Denied {op} for {hdl} because RELIABLE_WRITE bit is not set");
return op.hdl_err(ErrorCode::WriteNotPermitted, hdl);
}
Ok(hdl) }
fn characteristic_for_attr(&self, at: &Attr) -> Option<CharInfo> {
use private::Group;
let i = self.index(at);
let decl = unsafe { self.attr.get_unchecked(..=i).iter() }.rposition(Attr::is_char)?;
let end = unsafe { self.attr.get_unchecked(decl + 1..).iter() }
.position(|at| CharacteristicDef::is_next_group(at.typ))
.map_or(self.attr.len(), |j| decl + 1 + j);
if end <= i {
return None; }
let dval = self.value(unsafe { self.attr.get_unchecked(decl) });
let vhdl = value_handle(dval);
let val = unsafe { self.attr.get_unchecked(decl + 1..end).iter() }
.position(|at| at.hdl == vhdl)
.map(|j| decl + 1 + j)
.expect("invalid characteristic");
let desc = unsafe { self.attr.get_unchecked(val + 1..end) };
let props = Prop::from_bits_retain(unsafe { *dval.get_unchecked(0) });
let ext_props = props.contains(Prop::EXT_PROPS).then(|| {
(desc.iter().find(|&at| Attr::is_ext_props(at))).map_or(ExtProp::empty(), |at| {
ExtProp::from_bits_truncate(self.value(at).unpack().u16())
})
});
let at = unsafe { self.attr.get_unchecked(val) };
Some(CharInfo {
props,
ext_props,
vhdl: at.hdl,
uuid: self.typ(at),
_desc: desc,
})
}
fn subset(&self, hdls: HandleRange) -> Option<Subset> {
let i = self.try_get(hdls.start()).map_or_else(
|i| (i < self.attr.len()).then_some(i),
|at| Some(self.index(at)),
)?;
let j = (self.try_get(hdls.end()))
.map_or_else(|j| (j > 0).then_some(j), |j| Some(self.index(j) + 1))?;
Some(Subset::new(&self.attr, i..j))
}
#[inline]
fn typ(&self, at: &Attr) -> Uuid {
at.typ.map_or_else(
|| unsafe {
let i = usize::from(at.val.0) - 16;
Uuid::new_unchecked(u128::from_le_bytes(*self.data.as_ptr().add(i).cast()))
},
Uuid16::as_uuid,
)
}
}
trait CommonOps {
fn attr(&self) -> &[Attr];
#[must_use]
fn data(&self) -> &[u8];
#[inline]
fn try_get(&self, hdl: Handle) -> std::result::Result<&Attr, usize> {
fn search(attr: &[Attr], hdl: Handle) -> std::result::Result<&Attr, usize> {
attr.binary_search_by(|at| at.hdl.cmp(&hdl))
.map(|i| unsafe { attr.get_unchecked(i) })
}
let i = usize::from(hdl) - 1;
let prior = match self.attr().get(i) {
Some(at) if at.hdl == hdl => return Ok(unsafe { self.attr().get_unchecked(i) }),
Some(_) => unsafe { self.attr().get_unchecked(..i) },
None => self.attr(),
};
search(prior, hdl)
}
#[inline(always)]
fn index(&self, at: &Attr) -> usize {
unsafe {
usize::try_from((at as *const Attr).offset_from(self.attr().as_ptr()))
.unwrap_unchecked()
}
}
#[inline(always)]
#[must_use]
fn value(&self, at: &Attr) -> &[u8] {
unsafe { (self.data()).get_unchecked(usize::from(at.val.0)..usize::from(at.val.1)) }
}
fn service_group(&self, hdl: Handle) -> Option<Subset> {
let Ok(at) = self.try_get(hdl) else { return None };
at.is_service().then(|| {
let i = self.index(at);
let j = unsafe { self.attr().get_unchecked(i + 1..).iter() }
.position(Attr::is_service)
.map_or(self.attr().len(), |j| i + 1 + j);
Subset::new(self.attr(), i..j)
})
}
}
impl CommonOps for Db {
#[inline(always)]
fn attr(&self) -> &[Attr] {
&self.attr
}
#[inline(always)]
fn data(&self) -> &[u8] {
&self.data
}
}
pub trait Group: private::Group {}
impl Group for ServiceDef {}
impl Group for CharacteristicDef {}
#[derive(Clone, Copy, Debug)]
pub struct DbEntry<'a, T> {
hdls: HandleRange,
typ: Uuid,
val: &'a [u8],
_marker: PhantomData<T>,
}
impl<'a, T> DbEntry<'a, T> {
#[inline(always)]
#[must_use]
fn new(db: &'a Db, at: &Attr, end_hdl: Handle) -> Self {
Self {
hdls: HandleRange::new(at.hdl, end_hdl),
typ: db.typ(at),
val: db.value(at),
_marker: PhantomData,
}
}
#[inline(always)]
#[must_use]
pub const fn handle(&self) -> Handle {
self.hdls.start()
}
#[inline(always)]
#[must_use]
pub const fn value(&self) -> &'a [u8] {
self.val
}
}
impl<T: Group> DbEntry<'_, T> {
#[inline(always)]
pub const fn handle_range(&self) -> HandleRange {
self.hdls
}
#[inline]
#[must_use]
pub fn uuid(&self) -> Uuid {
Uuid::try_from(unsafe { self.val.get_unchecked(T::UUID_OFF..) })
.map_or_else(|_| unreachable!("corrupt database"), |u| u)
}
}
impl DbEntry<'_, CharacteristicDef> {
#[inline]
#[must_use]
pub fn properties(&self) -> Prop {
Prop::from_bits_retain(self.val.unpack().u8())
}
#[inline]
#[must_use]
pub fn value_handle(&self) -> Handle {
value_handle(self.val)
}
}
impl DbEntry<'_, DescriptorDef> {
#[inline(always)]
#[must_use]
pub const fn uuid(&self) -> Uuid {
self.typ
}
}
impl<'a, T> AsRef<[u8]> for DbEntry<'a, T> {
#[inline(always)]
fn as_ref(&self) -> &'a [u8] {
self.val
}
}
#[derive(Clone, Copy, Debug)]
pub(super) struct CharInfo<'a> {
pub props: Prop,
pub ext_props: Option<ExtProp>,
pub vhdl: Handle,
pub uuid: Uuid,
_desc: &'a [Attr], }
#[derive(Clone, Copy, Debug)]
#[must_use]
struct Attr {
hdl: Handle,
typ: Option<Uuid16>,
val: (Idx, Idx),
perms: Perms,
}
impl Attr {
#[inline(always)]
const fn is_service(&self) -> bool {
matches!(
self.typ,
Some(Declaration::PRIMARY_SERVICE | Declaration::SECONDARY_SERVICE)
)
}
#[inline(always)]
const fn is_primary_service(&self) -> bool {
matches!(self.typ, Some(Declaration::PRIMARY_SERVICE))
}
#[inline(always)]
const fn is_include(&self) -> bool {
matches!(self.typ, Some(Declaration::INCLUDE))
}
#[inline(always)]
const fn is_char(&self) -> bool {
matches!(self.typ, Some(Declaration::CHARACTERISTIC))
}
#[inline(always)]
const fn is_ext_props(&self) -> bool {
matches!(
self.typ,
Some(Descriptor::CHARACTERISTIC_EXTENDED_PROPERTIES)
)
}
#[inline(always)]
const fn len(&self) -> usize {
self.val.1 as usize - self.val.0 as usize
}
}
#[derive(Clone, Copy, Debug)]
struct Subset<'a> {
off: usize,
attr: &'a [Attr],
}
impl<'a> Subset<'a> {
#[inline(always)]
fn new(attr: &[Attr], r: Range<usize>) -> Subset {
debug_assert!(!r.is_empty() && r.end <= attr.len());
Subset {
off: r.start,
attr: unsafe { attr.get_unchecked(r) },
}
}
#[inline(always)]
fn first(&self) -> &'a Attr {
unsafe { self.attr.get_unchecked(0) }
}
#[inline(always)]
fn last(&self) -> &'a Attr {
unsafe { self.attr.get_unchecked(self.attr.len() - 1) }
}
}
struct GroupIter<'a, T, F> {
db: &'a Db,
it: iter::Peekable<slice::Iter<'a, Attr>>,
is_start: F,
_marker: PhantomData<T>,
}
impl<'a, T: Group, F: Fn(&Attr) -> bool> GroupIter<'a, T, F> {
#[inline(always)]
#[must_use]
fn new(db: &'a Db, it: &'a [Attr], is_start: F) -> Self {
Self {
db,
it: it.iter().peekable(),
is_start,
_marker: PhantomData,
}
}
}
impl<'a, T: Group, F: Fn(&Attr) -> bool> Iterator for GroupIter<'a, T, F> {
type Item = DbEntry<'a, T>;
fn next(&mut self) -> Option<Self::Item> {
let decl = self.it.find(|at| (self.is_start)(at))?;
let mut end = decl.hdl;
while !self.it.peek().map_or(true, |at| T::is_next_group(at.typ)) {
end = unsafe { self.it.next().unwrap_unchecked().hdl };
}
Some(DbEntry::new(self.db, decl, end))
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.it.size_hint()
}
}
impl<T: Group, F: Fn(&Attr) -> bool> iter::FusedIterator for GroupIter<'_, T, F> {}
#[inline]
fn value_handle(decl: &[u8]) -> Handle {
Handle::new(decl.unpack().split_at(1).1.u16()).unwrap_or(Handle::MAX)
}
mod private {
use super::*;
pub trait Group {
const UUID_OFF: usize = 0;
#[inline(always)]
#[must_use]
fn is_next_group(typ: Option<Uuid16>) -> bool {
matches!(
typ,
Some(Declaration::PRIMARY_SERVICE | Declaration::SECONDARY_SERVICE)
)
}
}
impl Group for ServiceDef {}
impl Group for CharacteristicDef {
const UUID_OFF: usize = 3;
#[inline(always)]
fn is_next_group(typ: Option<Uuid16>) -> bool {
matches!(
typ,
Some(
Declaration::PRIMARY_SERVICE
| Declaration::SECONDARY_SERVICE
| Declaration::INCLUDE
| Declaration::CHARACTERISTIC
)
)
}
}
}