use crate::dm::{
ArrayAttributeRead, ArrayAttributeWrite, Cluster, Dataver, EndptId, ReadContext, WriteContext,
};
use crate::error::{Error, ErrorCode};
use crate::persist::{KvBlobStore, Persist};
use crate::tlv::{FromTLV, TLVArray, TLVBuilderParent, TLVElement, TLVTag, TLVWrite, ToTLV, TLV};
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::globals::{
LabelStruct, LabelStructArrayBuilder, LabelStructBuilder,
};
pub use crate::dm::clusters::decl::user_label::*;
pub use crate::persist::USER_LABELS_KEY;
pub const CLUSTER: Cluster<'static> = FULL_CLUSTER.with_attrs(with!(required));
pub const MAX_LABEL_LEN: usize = 16;
pub const MAX_VALUE_LEN: usize = 16;
#[derive(Debug, Clone, PartialEq, Eq, FromTLV, ToTLV)]
pub struct LabelEntry {
pub label: heapless::String<MAX_LABEL_LEN>,
pub value: heapless::String<MAX_VALUE_LEN>,
}
#[derive(Debug, Clone)]
struct EndpointLabels<const N: usize> {
endpoint_id: EndptId,
entries: Vec<LabelEntry, N>,
}
impl<const N: usize> ToTLV for EndpointLabels<N> {
fn to_tlv<W: TLVWrite>(&self, tag: &TLVTag, mut tw: W) -> Result<(), Error> {
tw.start_struct(tag)?;
self.endpoint_id.to_tlv(&TLVTag::Context(0), &mut tw)?;
self.entries.to_tlv(&TLVTag::Context(1), &mut tw)?;
tw.end_container()
}
fn tlv_iter(&self, _tag: TLVTag) -> impl Iterator<Item = Result<TLV<'_>, Error>> {
core::iter::empty()
}
}
impl<'a, const N: usize> FromTLV<'a> for EndpointLabels<N> {
fn from_tlv(element: &TLVElement<'a>) -> Result<Self, Error> {
let s = element.structure()?;
Ok(Self {
endpoint_id: EndptId::from_tlv(&s.ctx(0)?)?,
entries: Vec::<LabelEntry, N>::from_tlv(&s.ctx(1)?)?,
})
}
}
pub struct UserLabels<const E: usize, const N: usize = 4> {
state: Mutex<RefCell<Vec<EndpointLabels<N>, E>>>,
}
impl<const E: usize, const N: usize> UserLabels<E, 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(USER_LABELS_KEY, buf)? else {
self.state.lock(|cell| cell.borrow_mut().clear());
return Ok(());
};
let loaded = Vec::<EndpointLabels<N>, E>::from_tlv(&TLVElement::new(data))?;
self.state.lock(|cell| *cell.borrow_mut() = loaded);
info!("Loaded UserLabel 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(USER_LABELS_KEY, &*state)
})?;
persist.run()
}
fn with_entries<R>(
&self,
endpoint_id: EndptId,
f: impl FnOnce(&[LabelEntry]) -> Result<R, Error>,
) -> Result<R, Error> {
self.state.lock(|cell| {
let state = cell.borrow();
match state.iter().find(|slot| slot.endpoint_id == endpoint_id) {
Some(slot) => f(slot.entries.as_slice()),
None => f(&[]),
}
})
}
fn replace_entries<'a, C: WriteContext>(
&self,
ctx: &C,
endpoint_id: EndptId,
list: &TLVArray<'a, LabelStruct<'a>>,
) -> Result<(), Error> {
let mut count = 0usize;
for entry in list {
let entry = entry?;
Self::validate_entry(&entry)?;
count += 1;
if count > N {
return Err(ErrorCode::ResourceExhausted.into());
}
}
self.state.lock(|cell| {
let mut state = cell.borrow_mut();
let slot = Self::slot_mut(&mut state, endpoint_id)?;
slot.entries.clear();
for entry in list {
let entry = entry?;
let (label, value) = Self::validate_entry(&entry)?;
Self::push_into(&mut slot.entries, label, value)?;
}
Ok::<_, Error>(())
})?;
self.store_persist(ctx)
}
fn add_entry<'a, C: WriteContext>(
&self,
ctx: &C,
endpoint_id: EndptId,
entry: &LabelStruct<'a>,
) -> Result<(), Error> {
let (label, value) = Self::validate_entry(entry)?;
self.state.lock(|cell| {
let mut state = cell.borrow_mut();
let slot = Self::slot_mut(&mut state, endpoint_id)?;
Self::push_into(&mut slot.entries, label, value)
})?;
self.store_persist(ctx)
}
fn slot_mut(
state: &mut Vec<EndpointLabels<N>, E>,
endpoint_id: EndptId,
) -> Result<&mut EndpointLabels<N>, Error> {
if let Some(idx) = state.iter().position(|s| s.endpoint_id == endpoint_id) {
return Ok(&mut state[idx]);
}
state
.push(EndpointLabels {
endpoint_id,
entries: Vec::new(),
})
.map_err(|_| ErrorCode::ResourceExhausted)?;
Ok(state.last_mut().expect("just pushed"))
}
fn validate_entry<'a>(entry: &'a LabelStruct<'a>) -> Result<(&'a str, &'a str), Error> {
let label = entry.label()?;
let value = entry.value()?;
if label.len() > MAX_LABEL_LEN || value.len() > MAX_VALUE_LEN {
return Err(ErrorCode::ConstraintError.into());
}
Ok((label, value))
}
fn push_into(list: &mut Vec<LabelEntry, N>, label: &str, value: &str) -> Result<(), Error> {
let label = heapless::String::try_from(label).map_err(|_| ErrorCode::ConstraintError)?;
let value = heapless::String::try_from(value).map_err(|_| ErrorCode::ConstraintError)?;
list.push(LabelEntry { label, value })
.map_err(|_| ErrorCode::ResourceExhausted)?;
Ok(())
}
}
impl<const E: usize, const N: usize> Default for UserLabels<E, N> {
fn default() -> Self {
Self::new()
}
}
pub struct UserLabelHandler<'a, const E: usize, const N: usize = 4> {
dataver: Dataver,
endpoint_id: EndptId,
labels: &'a UserLabels<E, N>,
}
impl<'a, const E: usize, const N: usize> UserLabelHandler<'a, E, N> {
pub const fn new(dataver: Dataver, endpoint_id: EndptId, labels: &'a UserLabels<E, N>) -> Self {
Self {
dataver,
endpoint_id,
labels,
}
}
pub const fn adapt(self) -> HandlerAdaptor<Self> {
HandlerAdaptor(self)
}
}
impl<const E: usize, const N: usize> ClusterHandler for UserLabelHandler<'_, E, 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 label_list<P: TLVBuilderParent>(
&self,
_ctx: impl ReadContext,
builder: ArrayAttributeRead<LabelStructArrayBuilder<P>, LabelStructBuilder<P>>,
) -> Result<P, Error> {
self.labels
.with_entries(self.endpoint_id, |entries| match builder {
ArrayAttributeRead::ReadAll(mut array) => {
for entry in entries {
array = array
.push()?
.label(entry.label.as_str())?
.value(entry.value.as_str())?
.end()?;
}
array.end()
}
ArrayAttributeRead::ReadOne(index, item) => {
let Some(entry) = entries.get(index as usize) else {
return Err(ErrorCode::ConstraintError.into());
};
item.label(entry.label.as_str())?
.value(entry.value.as_str())?
.end()
}
ArrayAttributeRead::ReadNone(array) => array.end(),
})
}
fn set_label_list(
&self,
ctx: impl WriteContext,
value: ArrayAttributeWrite<TLVArray<'_, LabelStruct<'_>>, LabelStruct<'_>>,
) -> Result<(), Error> {
match value {
ArrayAttributeWrite::Replace(list) => {
self.labels.replace_entries(&ctx, self.endpoint_id, &list)
}
ArrayAttributeWrite::Add(entry) => {
self.labels.add_entry(&ctx, self.endpoint_id, &entry)
}
ArrayAttributeWrite::Update(_, _) | ArrayAttributeWrite::Remove(_) => {
Err(ErrorCode::InvalidAction.into())
}
}
}
}