use core::num::NonZeroU8;
use crate::dm::{
ArrayAttributeRead, ArrayAttributeWrite, Cluster, ClusterId, Dataver, EndptId, ReadContext,
WriteContext,
};
use crate::error::{Error, ErrorCode};
use crate::persist::{KvBlobStore, Persist};
use crate::tlv::{FromTLV, TLVArray, TLVBuilderParent, TLVElement, ToTLV};
use crate::utils::cell::RefCell;
use crate::utils::init::{init, Init};
use crate::utils::storage::Vec;
use crate::utils::sync::blocking::Mutex;
use crate::with;
pub use crate::dm::clusters::decl::binding::*;
pub use crate::persist::BINDINGS_KEY;
pub const CLUSTER: Cluster<'static> = FULL_CLUSTER.with_attrs(with!(required));
#[derive(Debug, Clone, FromTLV, ToTLV)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Binding {
pub local_endpoint: EndptId,
pub fab_idx: NonZeroU8,
pub node: Option<u64>,
pub group: Option<u16>,
pub endpoint: Option<EndptId>,
pub cluster: Option<ClusterId>,
}
pub struct Bindings<const N: usize> {
state: Mutex<RefCell<Vec<Binding, N>>>,
}
impl<const N: usize> Bindings<N> {
pub const fn new() -> Self {
Self {
state: Mutex::new(RefCell::new(Vec::new())),
}
}
pub fn init() -> impl Init<Self> {
init!(Self {
state <- Mutex::init(RefCell::init(Vec::init())),
})
}
pub async fn load_persist<S: KvBlobStore>(
&self,
mut store: S,
buf: &mut [u8],
) -> Result<(), Error> {
let Some(data) = store.load(BINDINGS_KEY, buf)? else {
self.state.lock(|cell| cell.borrow_mut().clear());
return Ok(());
};
let loaded = Vec::<Binding, N>::from_tlv(&TLVElement::new(data))?;
self.state.lock(|cell| *cell.borrow_mut() = loaded);
info!("Loaded Binding entries for all endpoints from storage");
Ok(())
}
fn store_persist<C: WriteContext>(&self, ctx: &C) -> Result<(), Error> {
let mut persist = Persist::new(ctx.kv());
self.state.lock(|cell| {
let state = cell.borrow();
persist.store_tlv(BINDINGS_KEY, &*state)
})?;
persist.run()
}
fn parse_target(
local_endpoint: EndptId,
fab_idx: NonZeroU8,
t: &TargetStruct<'_>,
) -> Result<Binding, Error> {
let node = t.node()?;
let group = t.group()?;
let endpoint = t.endpoint()?;
let cluster = t.cluster()?;
if group.is_some() && endpoint.is_some() {
return Err(ErrorCode::ConstraintError.into());
}
if group.is_none() && endpoint.is_none() {
return Err(ErrorCode::ConstraintError.into());
}
if endpoint.is_some() && node.is_none() {
return Err(ErrorCode::ConstraintError.into());
}
Ok(Binding {
local_endpoint,
fab_idx,
node,
group,
endpoint,
cluster,
})
}
fn replace_entries<'a, C: WriteContext>(
&self,
ctx: &C,
endpoint_id: EndptId,
fab_idx: NonZeroU8,
list: &TLVArray<'a, TargetStruct<'a>>,
) -> Result<(), Error> {
let mut parsed: Vec<Binding, N> = Vec::new();
for t in list {
let t = t?;
let sb = Self::parse_target(endpoint_id, fab_idx, &t)?;
parsed.push(sb).map_err(|_| ErrorCode::ResourceExhausted)?;
}
let count = parsed.len();
self.state.lock(|cell| {
let mut state = cell.borrow_mut();
state.retain(|e| !(e.local_endpoint == endpoint_id && e.fab_idx == fab_idx));
for sb in parsed {
state.push(sb).map_err(|_| ErrorCode::ResourceExhausted)?;
}
Ok::<_, Error>(())
})?;
if count == 0 {
info!(
"Binding: cleared all targets on endpoint {}, fabric {}",
endpoint_id, fab_idx
);
} else {
info!(
"Binding: replaced list on endpoint {}, fabric {} with {} target(s)",
endpoint_id, fab_idx, count
);
}
self.store_persist(ctx)
}
fn add_entry<'a, C: WriteContext>(
&self,
ctx: &C,
endpoint_id: EndptId,
fab_idx: NonZeroU8,
entry: &TargetStruct<'a>,
) -> Result<(), Error> {
let sb = Self::parse_target(endpoint_id, fab_idx, entry)?;
info!(
"Binding: add on endpoint {}, fabric {} -> node {:?}, group {:?}, endpoint {:?}, cluster {:?}",
endpoint_id, fab_idx, sb.node, sb.group, sb.endpoint, sb.cluster
);
self.state.lock(|cell| {
cell.borrow_mut()
.push(sb)
.map_err(|_| -> Error { ErrorCode::ResourceExhausted.into() })?;
Ok::<_, Error>(())
})?;
self.store_persist(ctx)
}
pub fn len(&self) -> usize {
self.state.lock(|cell| cell.borrow().len())
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn get(&self, index: usize) -> Option<Binding> {
self.state.lock(|cell| cell.borrow().get(index).cloned())
}
fn render<P: TLVBuilderParent>(
&self,
endpoint_id: EndptId,
fab_filter: Option<NonZeroU8>,
builder: ArrayAttributeRead<TargetStructArrayBuilder<P>, TargetStructBuilder<P>>,
) -> Result<P, Error> {
self.state.lock(|cell| {
let state = cell.borrow();
let mut iter = state
.iter()
.filter(|e| e.local_endpoint == endpoint_id)
.filter(|e| fab_filter.is_none_or(|f| e.fab_idx == f));
match builder {
ArrayAttributeRead::ReadAll(mut array) => {
for e in iter {
let item = array.push()?;
let item = item.node(e.node)?;
let item = item.group(e.group)?;
let item = item.endpoint(e.endpoint)?;
let item = item.cluster(e.cluster)?;
array = item.fabric_index(Some(e.fab_idx.get()))?.end()?;
}
array.end()
}
ArrayAttributeRead::ReadOne(index, item) => {
let Some(e) = iter.nth(index as usize) else {
return Err(ErrorCode::ConstraintError.into());
};
let item = item.node(e.node)?;
let item = item.group(e.group)?;
let item = item.endpoint(e.endpoint)?;
let item = item.cluster(e.cluster)?;
item.fabric_index(Some(e.fab_idx.get()))?.end()
}
ArrayAttributeRead::ReadNone(array) => array.end(),
}
})
}
}
impl<const N: usize> Default for Bindings<N> {
fn default() -> Self {
Self::new()
}
}
pub struct BindingHandler<'a, const N: usize> {
dataver: Dataver,
endpoint_id: EndptId,
bindings: &'a Bindings<N>,
}
impl<'a, const N: usize> BindingHandler<'a, N> {
pub const fn new(dataver: Dataver, endpoint_id: EndptId, bindings: &'a Bindings<N>) -> Self {
Self {
dataver,
endpoint_id,
bindings,
}
}
pub const fn adapt(self) -> HandlerAdaptor<Self> {
HandlerAdaptor(self)
}
}
impl<const N: usize> ClusterHandler for BindingHandler<'_, N> {
const CLUSTER: Cluster<'static> = FULL_CLUSTER.with_attrs(with!(required));
fn dataver(&self) -> u32 {
self.dataver.get()
}
fn dataver_changed(&self) {
self.dataver.changed();
}
fn binding<P: TLVBuilderParent>(
&self,
ctx: impl ReadContext,
builder: ArrayAttributeRead<TargetStructArrayBuilder<P>, TargetStructBuilder<P>>,
) -> Result<P, Error> {
let attr = ctx.attr();
let fab_filter = if attr.fab_filter {
Some(NonZeroU8::new(attr.fab_idx).ok_or(ErrorCode::UnsupportedAccess)?)
} else {
None
};
self.bindings.render(self.endpoint_id, fab_filter, builder)
}
fn set_binding(
&self,
ctx: impl WriteContext,
value: ArrayAttributeWrite<TLVArray<'_, TargetStruct<'_>>, TargetStruct<'_>>,
) -> Result<(), Error> {
let fab_idx = NonZeroU8::new(ctx.attr().fab_idx).ok_or(ErrorCode::UnsupportedAccess)?;
match value {
ArrayAttributeWrite::Replace(list) => {
self.bindings
.replace_entries(&ctx, self.endpoint_id, fab_idx, &list)
}
ArrayAttributeWrite::Add(entry) => {
self.bindings
.add_entry(&ctx, self.endpoint_id, fab_idx, &entry)
}
ArrayAttributeWrite::Update(_, _) | ArrayAttributeWrite::Remove(_) => {
Err(ErrorCode::InvalidAction.into())
}
}
}
}