Skip to main content

twine_codec/radio/
channel_mask.rs

1// Copyright (c) 2025 Jake Swensen
2// SPDX-License-Identifier: MPL-2.0
3//
4// This Source Code Form is subject to the terms of the Mozilla Public
5// License, v. 2.0. If a copy of the MPL was not distributed with this
6// file, You can obtain one at http://mozilla.org/MPL/2.0/.
7
8use core::str::FromStr;
9
10use bitflags::bitflags;
11use bytes::{Buf, BufMut};
12use twine_rs_macros::Tlv;
13use twine_tlv::prelude::*;
14use typed_builder::TypedBuilder;
15
16use crate::TwineCodecError;
17
18bitflags! {
19    /// IEEE 802.15.4 channel page mask
20    #[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash)]
21    pub struct ChannelPageMask: u8 {
22        /// 2.4 GHz O-QPSK PHY
23        const PAGE_0 = 0;
24    }
25
26    /// IEEE 802.15.4 Channel Mask
27    ///
28    /// Each bit in the channel mask represents the selected channel.
29    #[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash)]
30    pub struct ChannelMaskBits: u32 {
31        const CHANNEL_0 = 1 << 0;
32        const CHANNEL_1 = 1 << 1;
33        const CHANNEL_2 = 1 << 2;
34        const CHANNEL_3 = 1 << 3;
35        const CHANNEL_4 = 1 << 4;
36        const CHANNEL_5 = 1 << 5;
37        const CHANNEL_6 = 1 << 6;
38        const CHANNEL_7 = 1 << 7;
39        const CHANNEL_8 = 1 << 8;
40        const CHANNEL_9 = 1 << 9;
41        const CHANNEL_10 = 1 << 10;
42        const CHANNEL_11 = 1 << 11;
43        const CHANNEL_12 = 1 << 12;
44        const CHANNEL_13 = 1 << 13;
45        const CHANNEL_14 = 1 << 14;
46        const CHANNEL_15 = 1 << 15;
47        const CHANNEL_16 = 1 << 16;
48        const CHANNEL_17 = 1 << 17;
49        const CHANNEL_18 = 1 << 18;
50        const CHANNEL_19 = 1 << 19;
51        const CHANNEL_20 = 1 << 20;
52        const CHANNEL_21 = 1 << 21;
53        const CHANNEL_22 = 1 << 22;
54        const CHANNEL_23 = 1 << 23;
55        const CHANNEL_24 = 1 << 24;
56        const CHANNEL_25 = 1 << 25;
57        const CHANNEL_26 = 1 << 26;
58        const CHANNEL_27 = 1 << 27;
59        const CHANNEL_28 = 1 << 28;
60        const CHANNEL_29 = 1 << 29;
61        const CHANNEL_30 = 1 << 30;
62        const CHANNEL_31 = 1 << 31;
63    }
64}
65
66impl From<ChannelPageMask> for u8 {
67    fn from(value: ChannelPageMask) -> Self {
68        value.bits()
69    }
70}
71
72impl From<u8> for ChannelPageMask {
73    fn from(value: u8) -> Self {
74        match value {
75            0 => ChannelPageMask::PAGE_0,
76            _ => panic!("Invalid channel page mask: {}", value),
77        }
78    }
79}
80
81/// IEEE 802.15.4 Channel Mask TLV
82///
83/// FIXME: Currently only supports a single channel mask entry.
84///
85/// The TLV is required to support multiple channel mask entries, but
86/// practically, this is rarely used.
87#[derive(Copy, Clone, Debug, Eq, PartialEq, Tlv, TypedBuilder)]
88#[tlv(tlv_type = 0x35, tlv_length = 6)]
89pub struct ChannelMask {
90    page: ChannelPageMask,
91    len: u8,
92    mask: ChannelMaskBits,
93}
94
95impl Default for ChannelMask {
96    fn default() -> Self {
97        let mask = ChannelMaskBits::CHANNEL_11
98            | ChannelMaskBits::CHANNEL_12
99            | ChannelMaskBits::CHANNEL_13
100            | ChannelMaskBits::CHANNEL_14
101            | ChannelMaskBits::CHANNEL_15
102            | ChannelMaskBits::CHANNEL_16
103            | ChannelMaskBits::CHANNEL_17
104            | ChannelMaskBits::CHANNEL_18
105            | ChannelMaskBits::CHANNEL_19
106            | ChannelMaskBits::CHANNEL_20
107            | ChannelMaskBits::CHANNEL_21
108            | ChannelMaskBits::CHANNEL_22
109            | ChannelMaskBits::CHANNEL_23
110            | ChannelMaskBits::CHANNEL_24
111            | ChannelMaskBits::CHANNEL_25
112            | ChannelMaskBits::CHANNEL_26;
113
114        ChannelMask {
115            page: ChannelPageMask::PAGE_0,
116            len: 4,
117            mask,
118        }
119    }
120}
121
122impl ChannelMask {
123    pub fn mask(&self) -> u32 {
124        self.mask.bits()
125    }
126}
127
128impl core::fmt::Display for ChannelMask {
129    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
130        write!(f, "0x{:08x}", self.mask())
131    }
132}
133
134impl FromStr for ChannelMask {
135    type Err = TwineCodecError;
136
137    fn from_str(s: &str) -> Result<Self, Self::Err> {
138        let s = s
139            .strip_prefix("0x")
140            .or_else(|| s.strip_prefix("0X"))
141            .unwrap_or(s);
142        let mask = u32::from_str_radix(s, 16).map_err(|_| TwineCodecError::StringParseError)?;
143        Ok(ChannelMask {
144            page: ChannelPageMask::PAGE_0,
145            len: 4,
146            mask: ChannelMaskBits::from_bits_truncate(mask),
147        })
148    }
149}
150
151impl DecodeTlvValueUnchecked for ChannelMask {
152    fn decode_tlv_value_unchecked(buffer: impl AsRef<[u8]>) -> Self {
153        let mut buffer = buffer.as_ref();
154        let page = buffer.get_u8();
155        let mask_len = buffer.get_u8();
156        let mask_bytes = buffer.get_u32();
157
158        // Reverse the bits of the channel mask to match the TLV format listed in the Thread 1.4.0 specification.
159        //
160        // Thread 1.4.0 8.10.1.18.1 Channel Mask Entry
161        let mask = ChannelMaskBits::from_bits_truncate(mask_bytes.reverse_bits());
162
163        Self {
164            page: page.into(),
165            len: mask_len,
166            mask,
167        }
168    }
169}
170
171impl TryEncodeTlvValue for ChannelMask {
172    fn try_encode_tlv_value(&self, buffer: &mut [u8]) -> Result<usize, twine_tlv::TwineTlvError> {
173        let mut buffer = buffer;
174        buffer.put_u8(self.page.into());
175        buffer.put_u8(self.len);
176        buffer.put_u32(self.mask.bits().reverse_bits());
177        Ok(self.tlv_len())
178    }
179}
180
181#[cfg(test)]
182mod tests {
183    use super::*;
184
185    #[test]
186    fn success_default() {
187        let default = ChannelMask::default();
188        assert_eq!(default.page, ChannelPageMask::PAGE_0);
189        assert_eq!(default.len, 4);
190        assert_eq!(default.mask(), 0x07FF_F800);
191    }
192
193    #[test]
194    fn success_decode_tlv() {
195        let tlv_bytes: [u8; 8] = [53, 6, 0, 4, 0, 31, 255, 224];
196        let channel_mask = ChannelMask::decode_tlv_unchecked(tlv_bytes);
197        assert_eq!(channel_mask.page, ChannelPageMask::PAGE_0);
198        assert_eq!(channel_mask.len, 4);
199        assert_eq!(channel_mask.mask(), 0x07FF_F800);
200    }
201
202    #[test]
203    fn success_encode_tlv() {
204        let channel_mask = ChannelMask::default();
205        let mut buffer = [0_u8; 8];
206        let bytes_written = channel_mask
207            .try_encode_tlv(&mut buffer)
208            .expect("Could not encode ChannelMask");
209        assert_eq!(bytes_written, channel_mask.tlv_total_len());
210        let expected_bytes: [u8; 8] = [53, 6, 0, 4, 0, 31, 255, 224];
211        assert_eq!(expected_bytes.as_ref(), &buffer[..bytes_written]);
212    }
213}