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}