Skip to main content

rs_matter/dm/networks/wireless/
thread.rs

1/*
2 *
3 *    Copyright (c) 2025-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//! This module contains Thread-specific types.
19
20use core::fmt::{Debug, Display};
21
22use crate::dm::clusters::net_comm::WirelessCreds;
23use crate::error::{Error, ErrorCode};
24use crate::tlv::{FromTLV, OctetsOwned, ToTLV};
25use crate::utils::init::{init, Init, IntoFallibleInit};
26use crate::utils::storage::Vec;
27
28use super::{WirelessNetwork, WirelessNetworks};
29
30pub type ThreadNetworks<const N: usize> = WirelessNetworks<N, Thread>;
31
32/// A struct implementing the `WirelessNetwork` trait for Thread networks.
33#[derive(Debug, Clone, Eq, PartialEq, Hash, ToTLV, FromTLV)]
34#[cfg_attr(feature = "defmt", derive(defmt::Format))]
35pub struct Thread {
36    /// Thread dataset in TLV format
37    pub dataset: OctetsOwned<256>,
38}
39
40impl Default for Thread {
41    fn default() -> Self {
42        Self::new()
43    }
44}
45
46impl Thread {
47    /// Create a new, empty instance of `Thread`.
48    pub const fn new() -> Self {
49        Self {
50            dataset: OctetsOwned { vec: Vec::new() },
51        }
52    }
53
54    /// Return an in-place initializer for an empty `Thread` insrtance.
55    pub fn init() -> impl Init<Self> {
56        init!(Self {
57            dataset <- OctetsOwned::init(),
58        })
59    }
60
61    /// Get the Extended PAN ID from the operational dataset
62    pub fn dataset_ext_pan_id(dataset_tlv: &[u8]) -> Result<&[u8], Error> {
63        ThreadTLV::new(dataset_tlv).ext_pan_id()
64    }
65
66    /// Get the Extended PAN ID from the operational dataset
67    pub fn ext_pan_id(&self) -> &[u8] {
68        // This unwrap! should never fail because the Thread TLV dataset
69        // is checked - upon creation of the `Thread` instance - to be valid,
70        // i.e. to contain an Extended PAN ID TLV.
71        unwrap!(Self::dataset_ext_pan_id(&self.dataset.vec))
72    }
73}
74
75impl WirelessNetwork for Thread {
76    fn id(&self) -> &[u8] {
77        self.ext_pan_id()
78    }
79
80    #[cfg(not(feature = "defmt"))]
81    fn display_id(id: &[u8]) -> impl Display {
82        use super::DisplayId;
83
84        DisplayId::Thread(id)
85    }
86
87    #[cfg(feature = "defmt")]
88    fn display_id(id: &[u8]) -> impl Display + defmt::Format {
89        use super::DisplayId;
90
91        DisplayId::Thread(id)
92    }
93
94    fn init_from<'a>(creds: &'a WirelessCreds<'a>) -> impl Init<Self, Error> + 'a {
95        Self::init().into_fallible().chain(move |network| {
96            let WirelessCreds::Thread { dataset_tlv } = creds else {
97                return Err(ErrorCode::InvalidData.into());
98            };
99
100            network
101                .dataset
102                .vec
103                .extend_from_slice(dataset_tlv)
104                .map_err(|_| ErrorCode::InvalidData)?;
105
106            Ok(())
107        })
108    }
109
110    fn update(&mut self, creds: &WirelessCreds<'_>) -> Result<(), Error> {
111        let WirelessCreds::Thread { dataset_tlv } = creds else {
112            return Err(ErrorCode::InvalidData.into());
113        };
114
115        if dataset_tlv.len() > self.dataset.vec.capacity() {
116            return Err(ErrorCode::InvalidData.into());
117        }
118
119        self.dataset.vec.clear();
120
121        unwrap!(self.dataset.vec.extend_from_slice(dataset_tlv));
122
123        Ok(())
124    }
125
126    fn creds(&self) -> WirelessCreds<'_> {
127        WirelessCreds::Thread {
128            dataset_tlv: &self.dataset.vec,
129        }
130    }
131}
132
133/// A simple Thread TLV reader
134#[derive(Debug, Copy, Clone)]
135#[cfg_attr(feature = "defmt", derive(defmt::Format))]
136pub struct ThreadTLV<'a>(&'a [u8]);
137
138impl<'a> ThreadTLV<'a> {
139    /// Create a new `ThreadTLV` instance with the given TLV data
140    pub const fn new(tlv: &'a [u8]) -> Self {
141        Self(tlv)
142    }
143
144    /// Get the Extended PAN ID from the operational dataset
145    pub fn ext_pan_id(&mut self) -> Result<&'a [u8], Error> {
146        const EXT_PAN_ID: u8 = 2;
147
148        let ext_pan_id =
149            self.find_map(|(tlv_type, tlv_value)| (tlv_type == EXT_PAN_ID).then_some(tlv_value));
150
151        let Some(ext_pan_id) = ext_pan_id else {
152            return Err(ErrorCode::InvalidData.into());
153        };
154
155        Ok(ext_pan_id)
156    }
157
158    /// Get the next TLV from the data
159    ///
160    /// Returns `Some` with the TLV type and value if there is a TLV available,
161    /// otherwise returns `None`.
162    pub fn next_tlv(&mut self) -> Option<(u8, &'a [u8])> {
163        const LONG_VALUE_ID: u8 = 255;
164
165        // Adopted from here:
166        // https://github.com/openthread/openthread/blob/main/tools/tcat_ble_client/tlv/tlv.py
167
168        let mut slice = self.0;
169
170        (slice.len() >= 2).then_some(())?;
171
172        let tlv_type = slice[0];
173        slice = &slice[1..];
174
175        let tlv_len_size = if slice[0] == LONG_VALUE_ID {
176            slice = &slice[1..];
177            3
178        } else {
179            1
180        };
181
182        (slice.len() >= tlv_len_size).then_some(())?;
183
184        let tlv_len = if tlv_len_size == 1 {
185            slice[0] as usize
186        } else {
187            u32::from_be_bytes([0, slice[0], slice[1], slice[2]]) as usize
188        };
189
190        slice = &slice[tlv_len_size..];
191        (slice.len() >= tlv_len).then_some(())?;
192
193        let tlv_value = &slice[..tlv_len];
194
195        slice = &slice[tlv_len..];
196
197        self.0 = slice;
198
199        Some((tlv_type, tlv_value))
200    }
201}
202
203impl<'a> Iterator for ThreadTLV<'a> {
204    type Item = (u8, &'a [u8]);
205
206    fn next(&mut self) -> Option<Self::Item> {
207        self.next_tlv()
208    }
209}