Skip to main content

matter_clusters/
datatypes.rs

1//! Hand-written Matter *global* datatypes referenced by generated cluster
2//! code but defined outside any single cluster.
3
4use crate::error::ClusterError;
5use crate::types::Nullable;
6use matter_codec::{ContainerKind, Element, Tag, TlvReader, Value};
7
8/// A semantic tag (`semtag` global, Matter spec §7.19.2) — e.g. the entries
9/// of `Descriptor.TagList`.
10///
11/// Fields per the spec: `MfgCode` (0, nullable vendor-id), `NamespaceID`
12/// (1, enum8), `Tag` (2, enum8), `Label` (3, optional nullable string).
13#[derive(Clone, Debug, PartialEq)]
14#[non_exhaustive]
15pub struct SemanticTagStruct {
16    /// Manufacturer code (`null` for standard namespaces).
17    pub mfg_code: Nullable<u16>,
18    /// Namespace identifier.
19    pub namespace_id: u8,
20    /// Tag within the namespace.
21    pub tag: u8,
22    /// Optional human-readable label.
23    pub label: Option<Nullable<String>>,
24}
25
26impl SemanticTagStruct {
27    /// Decode the fields of an already-opened anonymous structure (reader
28    /// positioned after the struct start; consumes to its matching end).
29    ///
30    /// # Errors
31    ///
32    /// Returns [`ClusterError`] on a malformed structure or missing required
33    /// field.
34    pub fn decode_from(r: &mut TlvReader<'_>) -> Result<Self, ClusterError> {
35        let mut mfg_code: Option<Nullable<u16>> = None;
36        let mut namespace_id: Option<u8> = None;
37        let mut tag: Option<u8> = None;
38        let mut label: Option<Nullable<String>> = None;
39        loop {
40            match r.next()? {
41                Some(Element::ContainerEnd) => break,
42                Some(Element::Scalar {
43                    tag: Tag::Context(0),
44                    value: Value::Null,
45                }) => {
46                    mfg_code = Some(Nullable::Null);
47                }
48                Some(Element::Scalar {
49                    tag: Tag::Context(0),
50                    value: Value::Uint(v),
51                }) => {
52                    mfg_code =
53                        Some(Nullable::Value(u16::try_from(v).map_err(|_| {
54                            ClusterError::InvalidLength("SemanticTag.MfgCode")
55                        })?));
56                }
57                Some(Element::Scalar {
58                    tag: Tag::Context(1),
59                    value: Value::Uint(v),
60                }) => {
61                    namespace_id = Some(
62                        u8::try_from(v)
63                            .map_err(|_| ClusterError::InvalidLength("SemanticTag.NamespaceID"))?,
64                    );
65                }
66                Some(Element::Scalar {
67                    tag: Tag::Context(2),
68                    value: Value::Uint(v),
69                }) => {
70                    tag = Some(
71                        u8::try_from(v)
72                            .map_err(|_| ClusterError::InvalidLength("SemanticTag.Tag"))?,
73                    );
74                }
75                Some(Element::Scalar {
76                    tag: Tag::Context(3),
77                    value: Value::Null,
78                }) => {
79                    label = Some(Nullable::Null);
80                }
81                Some(Element::Scalar {
82                    tag: Tag::Context(3),
83                    value: Value::Utf8(s),
84                }) => {
85                    label = Some(Nullable::Value(s));
86                }
87                None => return Err(ClusterError::Tlv(matter_codec::Error::UnclosedContainer)),
88                Some(Element::ContainerStart { .. }) => r.skip_container()?,
89                Some(_) => {}
90            }
91        }
92        Ok(Self {
93            mfg_code: mfg_code.ok_or(ClusterError::MissingField("SemanticTag.MfgCode"))?,
94            namespace_id: namespace_id
95                .ok_or(ClusterError::MissingField("SemanticTag.NamespaceID"))?,
96            tag: tag.ok_or(ClusterError::MissingField("SemanticTag.Tag"))?,
97            label,
98        })
99    }
100
101    /// Decode from a standalone anonymous TLV structure.
102    ///
103    /// # Errors
104    ///
105    /// Returns [`ClusterError`] if the bytes are not an anonymous structure or
106    /// a field is malformed.
107    pub fn decode(tlv: &[u8]) -> Result<Self, ClusterError> {
108        let mut r = TlvReader::new(tlv);
109        match r.next()? {
110            Some(Element::ContainerStart {
111                kind: ContainerKind::Structure,
112                ..
113            }) => {}
114            _ => {
115                return Err(ClusterError::UnexpectedType {
116                    context: "SemanticTagStruct",
117                })
118            }
119        }
120        Self::decode_from(&mut r)
121    }
122}
123
124#[cfg(test)]
125mod tests {
126    #![allow(clippy::unwrap_used, clippy::expect_used)]
127    use super::*;
128    use matter_codec::TlvWriter;
129
130    #[test]
131    fn decodes_a_minimal_tag() {
132        // { MfgCode(0)=null, NamespaceID(1)=7, Tag(2)=3 }
133        let mut buf = Vec::new();
134        let mut w = TlvWriter::new(&mut buf);
135        w.start_structure(Tag::Anonymous).unwrap();
136        w.put_null(Tag::Context(0)).unwrap();
137        w.put_uint(Tag::Context(1), 7).unwrap();
138        w.put_uint(Tag::Context(2), 3).unwrap();
139        w.end_container().unwrap();
140        let t = SemanticTagStruct::decode(&buf).unwrap();
141        assert_eq!(
142            t,
143            SemanticTagStruct {
144                mfg_code: Nullable::Null,
145                namespace_id: 7,
146                tag: 3,
147                label: None
148            }
149        );
150    }
151}