Skip to main content

rs_matter/im/encoding/
event.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
18use num_enum::TryFromPrimitive;
19
20use crate::error::{Error, ErrorCode};
21use crate::tlv::{FromTLV, TLVElement, TLVTag, TLVWrite, TagType, ToTLV, TLV};
22
23use super::{ClusterId, EndptId, EventId, EventNumber, GenericPath, IMStatusCode, NodeId, Status};
24
25/// Event Filter
26///
27/// Corresponds to the `EventFilterIB` TLV structure in the Interaction Model.
28#[derive(Default, Debug, Clone, PartialEq, Eq, Hash, FromTLV, ToTLV)]
29#[cfg_attr(feature = "defmt", derive(defmt::Format))]
30pub struct EventFilter {
31    pub node: Option<NodeId>,
32    pub event_min: Option<EventNumber>,
33}
34
35/// Event Path
36///
37/// Corresponds to the `EventPathIB` TLV structure in the Interaction Model.
38#[derive(Default, Debug, Clone, PartialEq, Eq, Hash, FromTLV, ToTLV)]
39#[cfg_attr(feature = "defmt", derive(defmt::Format))]
40#[tlvargs(datatype = "list")]
41pub struct EventPath {
42    pub node: Option<NodeId>,
43    pub endpoint: Option<EndptId>,
44    pub cluster: Option<ClusterId>,
45    pub event: Option<EventId>,
46    pub is_urgent: Option<bool>,
47}
48
49impl EventPath {
50    /// Create a new `EventPath` from the provided `GenericPath`,
51    /// filling all fields which are not provided with their default values.
52    pub const fn from_gp(path: &GenericPath) -> Self {
53        Self {
54            node: None,
55            endpoint: path.endpoint,
56            cluster: path.cluster,
57            event: path.leaf,
58            is_urgent: None,
59        }
60    }
61
62    /// Convert this `EventPath` to a `GenericPath`.
63    pub const fn to_gp(&self) -> GenericPath {
64        GenericPath::new(self.endpoint, self.cluster, self.event)
65    }
66
67    /// Return true, if the path is wildcard
68    pub const fn is_wildcard(&self) -> bool {
69        self.endpoint.is_none() || self.cluster.is_none() || self.event.is_none()
70    }
71}
72
73/// Tags corresponding to the fields in the `EventReportIB` TLV structure.
74///
75/// Used when there is a need to perform low-level TLV serde on
76/// `EventReportIB` structures.
77#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
78#[cfg_attr(feature = "defmt", derive(defmt::Format))]
79#[repr(u8)]
80pub enum EventRespTag {
81    Status = 0,
82    Data = 1,
83}
84
85/// Tags corresponding to the fields in the `EventDataIB` TLV structure.
86///
87/// Used when there is a need to perform low-level TLV serde on
88/// EventDataIB structures.
89#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, TryFromPrimitive)]
90#[cfg_attr(feature = "defmt", derive(defmt::Format))]
91#[repr(u8)]
92pub enum EventDataTag {
93    Path = 0,
94    EventNumber = 1,
95    Priority = 2,
96    EpochTimestamp = 3,
97    SystemTimestamp = 4,
98    DeltaEpochTimestamp = 5,
99    DeltaSystemTimestamp = 6,
100    Data = 7,
101}
102
103/// A status response for an event in the Interaction Model.
104///
105/// Corresponds to the `EventStatusIB` TLV structure in the Interaction Model.
106#[derive(Debug, Clone, PartialEq, Eq, Hash, FromTLV, ToTLV)]
107#[cfg_attr(feature = "defmt", derive(defmt::Format))]
108pub struct EventStatus {
109    /// The path to the event.
110    pub path: EventPath,
111    /// The status of the event operation.
112    pub status: Status,
113}
114
115impl EventStatus {
116    /// Create a new `EventStatus` with the given path, status code, and optional cluster status.
117    pub const fn new(path: EventPath, status: IMStatusCode, cluster_status: Option<u16>) -> Self {
118        Self {
119            path,
120            status: Status::new(status, cluster_status),
121        }
122    }
123
124    /// Create a new `EventStatus` from a `GenericPath`, status code, and optional cluster status.
125    ///
126    /// ATTENTION: the actual reply `EventPath` will be filled with the `GenericPath` values,
127    /// however these are not necessarily expressing the full path of the incoming data as `EventPath` does.
128    ///
129    /// Hence, this method is primarily useful for unit tests.
130    pub const fn from_gp(
131        path: &GenericPath,
132        status: IMStatusCode,
133        cluster_status: Option<u16>,
134    ) -> Self {
135        Self::new(EventPath::from_gp(path), status, cluster_status)
136    }
137}
138
139/// Event Response
140///
141/// Corresponds to the `EventReportIB` TLV structure in the Interaction Model.
142#[derive(Clone, FromTLV, ToTLV, PartialEq, Debug)]
143#[cfg_attr(feature = "defmt", derive(defmt::Format))]
144#[tlvargs(lifetime = "'a")]
145pub enum EventResp<'a> {
146    Status(EventStatus),
147    Data(EventData<'a>),
148}
149
150/// A data response for an event in the Interaction Model.
151///
152/// Corresponds to the `EventDataIB` TLV structure in the Interaction Model.
153#[derive(Debug, Clone, PartialEq)]
154#[cfg_attr(feature = "defmt", derive(defmt::Format))]
155pub struct EventData<'a> {
156    /// The path to the event.
157    pub path: EventPath,
158    /// The event number counter for the node. While the node is running it is
159    /// monotonically increasing, but the spec allows for (large) incremental jumps
160    /// on node reboot.
161    pub event_number: EventNumber,
162    /// Event priority.
163    pub priority: EventPriority,
164    /// Event timestamp, one of multiple mutually exclusive options.
165    pub timestamp: EventDataTimestamp,
166    /// The data for the event, represented as a TLV element.
167    pub data: TLVElement<'a>,
168}
169
170impl<'a> EventData<'a> {
171    /// Create a new `EventData` with the given data version, path, and data.
172    pub const fn new(
173        path: EventPath,
174        event_number: EventNumber,
175        priority: EventPriority,
176        timestamp: EventDataTimestamp,
177        data: TLVElement<'a>,
178    ) -> Self {
179        Self {
180            path,
181            event_number,
182            priority,
183            timestamp,
184            data,
185        }
186    }
187
188    pub fn write_preamble<T: TLVWrite>(&self, tag: &TLVTag, mut tw: T) -> Result<(), Error> {
189        tw.start_struct(tag)?;
190
191        self.path
192            .to_tlv(&TagType::Context(EventDataTag::Path as _), &mut tw)?;
193
194        tw.u64(
195            &TagType::Context(EventDataTag::EventNumber as _),
196            self.event_number,
197        )?;
198
199        tw.u8(
200            &TagType::Context(EventDataTag::Priority as _),
201            self.priority as _,
202        )?;
203
204        match self.timestamp {
205            EventDataTimestamp::EpochTimestamp(ts) => {
206                tw.u64(&TagType::Context(EventDataTag::EpochTimestamp as _), ts)?
207            }
208            EventDataTimestamp::SystemTimestamp(ts) => {
209                tw.u64(&TagType::Context(EventDataTag::SystemTimestamp as _), ts)?
210            }
211            EventDataTimestamp::DeltaEpochTimestamp(ts) => tw.u64(
212                &TagType::Context(EventDataTag::DeltaEpochTimestamp as _),
213                ts,
214            )?,
215            EventDataTimestamp::DeltaSystemTimestamp(ts) => tw.u64(
216                &TagType::Context(EventDataTag::DeltaSystemTimestamp as _),
217                ts,
218            )?,
219        }
220
221        Ok(())
222    }
223}
224
225// Manually implemented because of the tagged union used for the timestamp
226impl<'a> ToTLV for EventData<'a> {
227    fn to_tlv<W: TLVWrite>(&self, tag: &TLVTag, mut tw: W) -> Result<(), Error> {
228        self.write_preamble(tag, &mut tw)?;
229
230        self.data
231            .to_tlv(&TagType::Context(EventDataTag::Data as _), &mut tw)?;
232
233        tw.end_container()
234    }
235
236    fn tlv_iter(&self, tag: crate::tlv::TLVTag) -> impl Iterator<Item = Result<TLV<'_>, Error>> {
237        let (timestamp_tag, timestamp_val) = match self.timestamp {
238            EventDataTimestamp::EpochTimestamp(ts) => (EventDataTag::EpochTimestamp, ts),
239            EventDataTimestamp::SystemTimestamp(ts) => (EventDataTag::SystemTimestamp, ts),
240            EventDataTimestamp::DeltaEpochTimestamp(ts) => (EventDataTag::DeltaEpochTimestamp, ts),
241            EventDataTimestamp::DeltaSystemTimestamp(ts) => {
242                (EventDataTag::DeltaSystemTimestamp, ts)
243            }
244        };
245
246        let header = [Ok(TLV::structure(tag))].into_iter();
247        let middle_fields = [
248            Ok(TLV::u64(
249                TLVTag::Context(EventDataTag::EventNumber as _),
250                self.event_number,
251            )),
252            Ok(TLV::u8(
253                TLVTag::Context(EventDataTag::Priority as _),
254                self.priority as _,
255            )),
256            Ok(TLV::u64(TLVTag::Context(timestamp_tag as _), timestamp_val)),
257        ]
258        .into_iter();
259        let trailer = [Ok(TLV::end_container())].into_iter();
260
261        header
262            .chain(self.path.tlv_iter(TLVTag::Context(EventDataTag::Path as _)))
263            .chain(middle_fields)
264            .chain(self.data.tlv_iter(TLVTag::Context(EventDataTag::Data as _)))
265            .chain(trailer)
266    }
267}
268
269impl<'a> FromTLV<'a> for EventData<'a> {
270    fn from_tlv(element: &TLVElement<'a>) -> Result<Self, Error> {
271        let mut path = None;
272        let mut event_number = None;
273        let mut priority = None;
274        let mut timestamp = None;
275        let mut data = None;
276
277        for field in element.structure()?.iter() {
278            let el = field?;
279
280            match el.tag()? {
281                TLVTag::Context(tag) => match EventDataTag::try_from(tag)? {
282                    EventDataTag::Path => path = Some(EventPath::from_tlv(&el)?),
283                    EventDataTag::EventNumber => event_number = Some(el.u64()?),
284                    EventDataTag::Priority => priority = Some(EventPriority::from_tlv(&el)?),
285                    EventDataTag::EpochTimestamp => {
286                        timestamp = Some(EventDataTimestamp::EpochTimestamp(el.u64()?))
287                    }
288                    EventDataTag::SystemTimestamp => {
289                        timestamp = Some(EventDataTimestamp::SystemTimestamp(el.u64()?))
290                    }
291                    EventDataTag::DeltaEpochTimestamp => {
292                        timestamp = Some(EventDataTimestamp::DeltaEpochTimestamp(el.u64()?))
293                    }
294                    EventDataTag::DeltaSystemTimestamp => {
295                        timestamp = Some(EventDataTimestamp::DeltaSystemTimestamp(el.u64()?))
296                    }
297                    EventDataTag::Data => data = Some(el),
298                },
299                _ => return Err(Error::new(ErrorCode::Invalid)),
300            }
301        }
302
303        Ok(EventData::new(
304            path.ok_or(Error::new(ErrorCode::Invalid))?,
305            event_number.ok_or(Error::new(ErrorCode::Invalid))?,
306            priority.ok_or(Error::new(ErrorCode::Invalid))?,
307            timestamp.ok_or(Error::new(ErrorCode::Invalid))?,
308            data.ok_or(Error::new(ErrorCode::Invalid))?,
309        ))
310    }
311}
312
313/// An enum type describing the priority each event might have
314#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, FromTLV, ToTLV)]
315#[cfg_attr(feature = "defmt", derive(defmt::Format))]
316#[tlvargs(datatype = "u8")]
317#[repr(u8)]
318pub enum EventPriority {
319    Debug = 0,
320    Info = 1,
321    Critical = 2,
322}
323
324impl EventPriority {
325    /// Get the next (higher) priority, if any
326    pub const fn next(&self) -> Option<Self> {
327        match self {
328            Self::Debug => Some(Self::Info),
329            Self::Info => Some(Self::Critical),
330            Self::Critical => None,
331        }
332    }
333
334    /// Get the previous (lower) priority, if any
335    pub const fn prev(&self) -> Option<Self> {
336        match self {
337            Self::Debug => None,
338            Self::Info => Some(Self::Debug),
339            Self::Critical => Some(Self::Info),
340        }
341    }
342}
343
344// Timestamp on an EventData, corresponds to the mutually exclusive timestamp
345// options on EventDataIB in the Interaction Model
346#[derive(Debug, Clone, PartialEq)]
347#[cfg_attr(feature = "defmt", derive(defmt::Format))]
348pub enum EventDataTimestamp {
349    // Posix milliseconds since the epoch, 1970-01-01 00:00:00 UTC
350    EpochTimestamp(u64),
351    // Milliseconds since booting
352    SystemTimestamp(u64),
353    // Delta-encoded version of EpochTimestamp. Same clock and unit, but value
354    // is relative to most recently emitted event.
355    DeltaEpochTimestamp(u64),
356    // Delta-encoded version of SystemTimestamp. Same clock and unit, but value
357    // is relative to most recently emitted event.
358    DeltaSystemTimestamp(u64),
359}