use std::collections::BTreeMap;
use std::fmt::Debug;
use std::time::Duration;
use tracing::{info, warn};
pub use {consts::*, db::*, io::*, server::*};
use crate::att::*;
use crate::le;
use crate::smp::BondId;
mod consts;
#[path = "db/db.rs"]
mod db;
mod io;
mod server;
pub type CacheStore = dyn crate::PeerStore<Value = Cache>;
#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)]
#[serde(deny_unknown_fields)]
pub struct Cache {
bond_id: Option<BondId>,
#[serde(with = "burble_crypto::u128ser")]
db_hash: u128,
service_changed: Option<ServiceChanged>,
client_features: ClientFeature,
cccd: BTreeMap<Handle, Cccd>,
}
impl Cache {
#[inline(always)]
#[must_use]
const fn is_robust(&self) -> bool {
self.client_features.contains(ClientFeature::ROBUST_CACHING)
}
#[inline(always)]
#[must_use]
const fn is_change_aware(&self, db_hash: u128) -> bool {
!self.is_robust() || self.db_hash == db_hash
}
}
#[derive(Clone, Copy, Debug, serde::Deserialize, serde::Serialize)]
#[serde(deny_unknown_fields)]
struct ServiceChanged {
#[serde(with = "burble_crypto::u128ser")]
hash: u128,
handle: Handle,
cccd: Cccd,
}
impl ServiceChanged {
#[must_use]
fn new(db: &Db, features: ServerFeature) -> Option<Self> {
let mut vhdl = None;
let mut m = burble_crypto::AesCmac::db_hash();
m.update([features.bits()]);
for (hdl, uuid, _) in db.iter() {
if uuid == Characteristic::ServiceChanged {
assert_eq!(vhdl, None, "found multiple Service Changed characteristics");
vhdl = Some(hdl);
}
m.update(u16::from(hdl).to_le_bytes());
m.update(u128::from(uuid).to_le_bytes());
}
let vhdl = vhdl?;
if u16::from(vhdl) != 3 {
warn!("GATT service isn't first, which may result in compatibility problems");
}
Some(Self {
hash: m.finalize(),
handle: vhdl,
cccd: Cccd::empty(),
})
}
#[inline(always)]
#[must_use]
const fn is_enabled(&self) -> bool {
self.cccd.contains(Cccd::INDICATE)
}
async fn indicate_all(&self, br: &mut Bearer, peer: le::Addr) -> bool {
const ALL_HANDLES: [u8; 4] = [0x01, 0x00, 0xFF, 0xFF];
if !self.is_enabled() {
info!("Service Changed indications not enabled for {peer}");
return false;
}
info!("Sending Service Changed indication to {peer}");
let indicate = tokio::time::timeout(
Duration::from_secs(3),
br.handle_value_ind(self.handle, ALL_HANDLES.as_ref()),
);
match indicate.await {
Ok(Ok(_)) => {
info!("Service Changed confirmed");
return true;
}
Ok(Err(e)) => warn!("Service Changed indication error: {e}"),
Err(_) => warn!("Service Changed indication timeout"),
}
false
}
}
impl Default for ServiceChanged {
#[inline(always)]
fn default() -> Self {
Self {
hash: 0,
handle: Handle::MAX,
cccd: Cccd::empty(),
}
}
}