Skip to main content

rs_matter/tlv/traits/
maybe.rs

1/*
2 *
3 *    Copyright (c) 2024-2025 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//! TLV support for TLV optional values and TLV nullable types via `Maybe` and `Option`.
19//! - `Option<T>` and `Optional<T>` both represent an optional value in a TLV struct
20//! - `Nullable<T>` represents a nullable TLV type, where `T` is the non-nullable subdomain of the type.
21//!   i.e. `Nullable<u8>` represents the nullable variation of the TLV `U8` type.
22//!
23//! To elaborate, `null` and optional are two different notions in the TLV spec:
24//! - Optional values apply only to TLV structs, and have the semantics
25//!   that the value might not be provided in the TLV stream for that struct
26//! - `null` is a property of the type _domain_ and therefore applies to all TLV types,
27//!   and has the semantics that the value is provided, but is null
28//!
29//! Therefore, e.g. `Optional<Nullable<T>>` is completely valid (in the context of a struct member)
30//! and means that this struct member is optional, but additionally - when provided - can be null.
31//!
32//! In terms of memory optimizations:
33//! - Use `Option<T>` only when the optional T value is small, as `Option` cannot be in-place initialized;
34//!   otherwise, use `Optional<T>` (which is equivalent to `Maybe<T, AsOptional>` and `Maybe<T, ()>`).
35//! - Use `Nullable<T>` (which is equivalent to `Maybe<T, AsNull>`) to represent
36//!   the nullable variations of the TLV types. This type can always be initialized in-place.
37//!
38//! Using `Optional` (or `Option`) **outside** of struct members has no TLV meaning but won't fail either:
39//! - During deserialization, a stream containing a value of type `T` would be deserialized as `Some(T)` if the user has
40//!   provided an `Option<T>` or an `Optional<T>` type declaration instead of just `T`
41//! - During serialization, a value of `Some(T)` would be serialized as `T`, while a value `None` would simply not be serialized
42
43use core::fmt::Debug;
44use core::iter::empty;
45
46use crate::error::Error;
47use crate::utils::init;
48use crate::utils::maybe::Maybe;
49
50use super::{EitherIter, FromTLV, TLVElement, TLVTag, TLVValueType, TLVWrite, ToTLV, TLV};
51
52/// A tag for `Maybe` that makes it behave as an optional struct value per the TLV spec.
53pub type AsOptional = ();
54
55/// A tag for `Maybe` that makes it behave as a nullable type per the TLV spec.
56#[derive(Debug)]
57#[cfg_attr(feature = "defmt", derive(defmt::Format))]
58pub struct AsNullable;
59
60/// Represents optional and/or fabric-sensitive values as per the TLV spec
61/// and the Matter Core spec.
62///
63/// A common representation for optional and fabric-sensitive values was chosen, because
64/// optional and fabric-sensitive values are very similar in the TLV/Matter spec:
65/// - Both optional and fabric-sensitive values apply only to struct members (modulo fabric-sensitive Events);
66/// - Both optional and fabric-sensitive values might simply not be present in the stream.
67///
68/// An optional, a fabric-sensitive, and an optional _AND_ fabric-sensitive struct member are all modeled
69/// with a single representation: `Option<T>` or `Optional<T>`.
70///
71/// Note that `Option<T>` also represents an optional and/or a fabric-sensitive struct field, but `Option<T>`
72/// cannot be created in-place, which is necessary when large values are involved.
73///
74/// Therefore, using `Optional<T>` is recommended over `Option<T>` when the optional value is large.
75pub type Optional<T> = Maybe<T, AsOptional>;
76
77/// Represents nullable values as per the TLV spec.
78pub type Nullable<T> = Maybe<T, AsNullable>;
79
80impl<'a, T: FromTLV<'a>> FromTLV<'a> for Maybe<T, AsNullable> {
81    fn from_tlv(element: &TLVElement<'a>) -> Result<Self, Error> {
82        match element.control()?.value_type {
83            TLVValueType::Null => Ok(Maybe::none()),
84            _ => T::nullable_from_tlv(element).map(Maybe::some),
85        }
86    }
87
88    fn init_from_tlv(element: TLVElement<'a>) -> impl init::Init<Self, Error> {
89        unsafe {
90            init::init_from_closure(move |slot| {
91                let init = match element.control()?.value_type {
92                    TLVValueType::Null => None,
93                    _ => Some(T::init_nullable_from_tlv(element)),
94                };
95
96                init::Init::__init(Maybe::init(init), slot)
97            })
98        }
99    }
100}
101
102impl<T: ToTLV> ToTLV for Maybe<T, AsNullable> {
103    fn to_tlv<W: TLVWrite>(&self, tag: &TLVTag, mut tw: W) -> Result<(), Error> {
104        match self.as_opt_ref() {
105            None => tw.null(tag),
106            Some(s) => s.nullable_to_tlv(tag, tw),
107        }
108    }
109
110    fn tlv_iter(&self, tag: TLVTag) -> impl Iterator<Item = Result<TLV<'_>, Error>> {
111        match self.as_opt_ref() {
112            None => EitherIter::First(TLV::null(tag).into_tlv_iter()),
113            Some(s) => EitherIter::Second(s.nullable_tlv_iter(tag)),
114        }
115    }
116}
117
118impl<'a, T: FromTLV<'a> + 'a> FromTLV<'a> for Maybe<T, AsOptional> {
119    fn from_tlv(element: &TLVElement<'a>) -> Result<Self, Error> {
120        if element.is_empty() {
121            Ok(Self::none())
122        } else {
123            Ok(Self::some(T::from_tlv(element)?))
124        }
125    }
126
127    fn init_from_tlv(element: TLVElement<'a>) -> impl init::Init<Self, Error> {
128        if element.is_empty() {
129            Self::init(None)
130        } else {
131            Self::init(Some(T::init_from_tlv(element)))
132        }
133    }
134}
135
136impl<T: ToTLV> ToTLV for Maybe<T, AsOptional> {
137    fn to_tlv<W: TLVWrite>(&self, tag: &TLVTag, tw: W) -> Result<(), Error> {
138        match self.as_opt_ref() {
139            None => Ok(()),
140            Some(s) => s.to_tlv(tag, tw),
141        }
142    }
143
144    fn tlv_iter(&self, tag: TLVTag) -> impl Iterator<Item = Result<TLV<'_>, Error>> {
145        use crate::tlv::EitherIter;
146
147        match self.as_opt_ref() {
148            None => EitherIter::First(empty()),
149            Some(s) => EitherIter::Second(s.tlv_iter(tag)),
150        }
151    }
152}
153
154impl<'a, T: FromTLV<'a>> FromTLV<'a> for Option<T> {
155    fn from_tlv(element: &TLVElement<'a>) -> Result<Self, Error> {
156        if element.is_empty() {
157            return Ok(None);
158        }
159
160        Ok(Some(T::from_tlv(element)?))
161    }
162}
163
164impl<T: ToTLV> ToTLV for Option<T> {
165    fn to_tlv<W: TLVWrite>(&self, tag: &TLVTag, tw: W) -> Result<(), Error> {
166        match self.as_ref() {
167            None => Ok(()),
168            Some(s) => s.to_tlv(tag, tw),
169        }
170    }
171
172    fn tlv_iter(&self, tag: TLVTag) -> impl Iterator<Item = Result<TLV<'_>, Error>> {
173        match self.as_ref() {
174            None => EitherIter::First(empty()),
175            Some(s) => EitherIter::Second(s.tlv_iter(tag)),
176        }
177    }
178}