Skip to main content

rs_matter/dm/clusters/
fixed_label.rs

1/*
2 *
3 *    Copyright (c) 2026 Project CHIP Authors
4 *
5 *    Licensed under the Apache License, Version 2.0 (the "License");
6 *    you may not use this file except in compliance with the License.
7 *    You may obtain a copy of the License at
8 *
9 *        http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *    Unless required by applicable law or agreed to in writing, software
12 *    distributed under the License is distributed on an "AS IS" BASIS,
13 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *    See the License for the specific language governing permissions and
15 *    limitations under the License.
16 */
17
18//! FixedLabel cluster handler (Matter Application Cluster spec).
19//!
20//! Per-endpoint **read-only** list of `(label, value)` string pairs
21//! that the manufacturer bakes into the device firmware — typical use
22//! is exposing immutable tags such as `"serial"` → `"abc123"` or
23//! `"hwrev"` → `"B"`. Compare with [`super::user_label`], the
24//! writable counterpart used by commissioners.
25//!
26//! The list is **fixed** in the F-quality sense (Matter Core spec):
27//! it never changes for the lifetime of the device firmware,
28//! so we don't need a persistence layer, a mutex, or any per-entry
29//! storage. [`FixedLabelHandler`] just borrows a static slice of
30//! [`FixedLabelEntry`] from the application and iterates it on read.
31//! Writes are rejected by the framework before they reach the handler
32//! because the cluster metadata declares `LabelList` as read-only
33//! (`Access::READ`), which the IM dispatch maps to `UnsupportedWrite`
34//! — exactly the behaviour `TC_FLABEL_2_1` step 3 expects.
35//!
36//! Application wiring:
37//!
38//! ```ignore
39//! const LABELS: &[FixedLabelEntry] = &[
40//!     FixedLabelEntry { label: "room", value: "kitchen" },
41//!     FixedLabelEntry { label: "hwrev", value: "B" },
42//! ];
43//!
44//! let handler = FixedLabelHandler::new(Dataver::new_rand(rand), LABELS);
45//! ```
46
47use crate::dm::{ArrayAttributeRead, Cluster, Dataver, ReadContext};
48use crate::error::{Error, ErrorCode};
49use crate::tlv::TLVBuilderParent;
50use crate::with;
51
52pub use crate::dm::clusters::decl::fixed_label::*;
53pub use crate::dm::clusters::decl::globals::{
54    LabelStruct, LabelStructArrayBuilder, LabelStructBuilder,
55};
56
57/// Cluster metadata exposed by [`FixedLabelHandler`].
58///
59/// Exposed as a free constant so callers can spell out
60/// `EpClMatcher::new(Some(ep), Some(fixed_label::CLUSTER.id))` without
61/// reaching for the lifetime-parameterised handler type.
62pub const CLUSTER: Cluster<'static> = FULL_CLUSTER.with_attrs(with!(required));
63
64/// One entry in a `LabelList`.
65///
66/// Per Matter Application Cluster spec (`LabelStruct`), each
67/// field is at most 16 characters. We don't enforce this here — the
68/// application is responsible for supplying spec-compliant data, and
69/// `TC_FLABEL_2_1` step 2 sanity-checks the lengths on the read path.
70#[derive(Debug, Clone, Copy, PartialEq, Eq)]
71pub struct FixedLabelEntry<'a> {
72    pub label: &'a str,
73    pub value: &'a str,
74}
75
76/// The handler for the FixedLabel Matter cluster.
77///
78/// Per-endpoint instance: each endpoint that advertises FixedLabel
79/// must own its own handler so the per-cluster-instance `Dataver`
80/// stays granular (Matter Core spec). The entries slice is
81/// borrowed — typically a `&'static [FixedLabelEntry<'static>]` —
82/// because the list is part of the device's firmware identity and
83/// doesn't change at runtime.
84pub struct FixedLabelHandler<'a> {
85    dataver: Dataver,
86    entries: &'a [FixedLabelEntry<'a>],
87}
88
89impl<'a> FixedLabelHandler<'a> {
90    /// Construct a handler exposing `entries` as the cluster's
91    /// `LabelList` attribute.
92    pub const fn new(dataver: Dataver, entries: &'a [FixedLabelEntry<'a>]) -> Self {
93        Self { dataver, entries }
94    }
95
96    /// Adapt the handler instance to the generic `rs-matter` `Handler` trait.
97    pub const fn adapt(self) -> HandlerAdaptor<Self> {
98        HandlerAdaptor(self)
99    }
100}
101
102impl ClusterHandler for FixedLabelHandler<'_> {
103    const CLUSTER: Cluster<'static> = FULL_CLUSTER.with_attrs(with!(required));
104
105    fn dataver(&self) -> u32 {
106        self.dataver.get()
107    }
108
109    fn dataver_changed(&self) {
110        self.dataver.changed();
111    }
112
113    fn label_list<P: TLVBuilderParent>(
114        &self,
115        _ctx: impl ReadContext,
116        builder: ArrayAttributeRead<LabelStructArrayBuilder<P>, LabelStructBuilder<P>>,
117    ) -> Result<P, Error> {
118        match builder {
119            ArrayAttributeRead::ReadAll(mut array) => {
120                for entry in self.entries {
121                    array = array
122                        .push()?
123                        .label(entry.label)?
124                        .value(entry.value)?
125                        .end()?;
126                }
127                array.end()
128            }
129            ArrayAttributeRead::ReadOne(index, item) => {
130                let Some(entry) = self.entries.get(index as usize) else {
131                    // List-element index out of bounds — IM convention
132                    // is `ConstraintError`; mirrors `UserLabelHandler`'s
133                    // out-of-range behaviour.
134                    return Err(ErrorCode::ConstraintError.into());
135                };
136                item.label(entry.label)?.value(entry.value)?.end()
137            }
138            ArrayAttributeRead::ReadNone(array) => array.end(),
139        }
140    }
141}