electricui_embedded/
message.rs

1use core::convert::TryFrom;
2use core::{fmt, mem, str};
3
4#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
5#[repr(transparent)]
6pub struct MessageId<'a>(&'a [u8]);
7
8impl<'a> MessageId<'a> {
9    /// Maximun size in bytes
10    pub const MAX_SIZE: usize = crate::wire::packet::Packet::<&[u8]>::MAX_MSG_ID_SIZE;
11
12    pub const INTERNAL_LIB_VER: Self = MessageId(b"o");
13    pub const INTERNAL_BOARD_ID: Self = MessageId(b"i");
14    pub const INTERNAL_HEARTBEAT: Self = MessageId(b"h");
15
16    /// Announce writable ID's
17    pub const INTERNAL_AM: Self = MessageId(b"t");
18    /// Delimit writable ID
19    pub const INTERNAL_AM_LIST: Self = MessageId(b"u");
20    /// End of writable ID's
21    pub const INTERNAL_AM_END: Self = MessageId(b"v");
22    /// Send writable variables
23    pub const INTERNAL_AV: Self = MessageId(b"w");
24
25    pub const BOARD_NAME: Self = MessageId(b"name");
26
27    pub const fn new(id: &'a [u8]) -> Option<Self> {
28        if id.is_empty() || id.len() > Self::MAX_SIZE || (id.len() == 1 && id[0] == 0) {
29            None
30        } else {
31            Some(Self(id))
32        }
33    }
34
35    /// # Safety
36    /// Must follow the rules
37    pub const unsafe fn new_unchecked(id: &'a [u8]) -> Self {
38        Self(id)
39    }
40
41    pub const fn as_bytes(&self) -> &[u8] {
42        self.0
43    }
44
45    pub fn as_str(&self) -> Result<&str, str::Utf8Error> {
46        str::from_utf8(self.0)
47    }
48
49    pub fn from_utf8(s: &'a str) -> Self {
50        Self(s.as_bytes())
51    }
52
53    #[allow(clippy::len_without_is_empty)]
54    pub fn len(&self) -> usize {
55        self.0.len()
56    }
57}
58
59impl<'a> From<MessageId<'a>> for &'a [u8] {
60    fn from(id: MessageId<'a>) -> Self {
61        id.0
62    }
63}
64
65impl<'a> TryFrom<&'a MessageId<'a>> for &'a str {
66    type Error = str::Utf8Error;
67
68    fn try_from(id: &'a MessageId<'a>) -> Result<Self, Self::Error> {
69        id.as_str()
70    }
71}
72
73// MessageId == [u8]
74impl<'a> PartialEq<[u8]> for MessageId<'a> {
75    fn eq(&self, other: &[u8]) -> bool {
76        self.0 == other
77    }
78}
79
80// MessageId == &[u8; N]
81impl<'a, const N: usize> PartialEq<&[u8; N]> for MessageId<'a> {
82    fn eq(&self, other: &&[u8; N]) -> bool {
83        self.0 == *other
84    }
85}
86
87// [u8] == MessageId
88impl<'a> PartialEq<MessageId<'a>> for [u8] {
89    fn eq(&self, other: &MessageId) -> bool {
90        self == other.0
91    }
92}
93
94// &[u8; N] == MessageId
95impl<'a, const N: usize> PartialEq<MessageId<'a>> for &[u8; N] {
96    fn eq(&self, other: &MessageId) -> bool {
97        *self == other.0
98    }
99}
100
101impl<'a> fmt::Display for MessageId<'a> {
102    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103        if let Ok(s) = self.as_str() {
104            f.write_str(s)
105        } else {
106            write!(f, "{:X?}", self.0)
107        }
108    }
109}
110
111#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
112pub enum MessageType {
113    Callback,
114    Custom,
115    OffsetMetadata,
116    Byte,
117    Char,
118    I8,
119    U8,
120    I16,
121    U16,
122    I32,
123    U32,
124    F32,
125    F64,
126    Unknown(u8),
127}
128
129impl MessageType {
130    /// Returns the wire size for this MessageType variant.
131    /// Only applicable to data carrying types.
132    pub fn wire_size_hint(self) -> usize {
133        use MessageType::*;
134        match self {
135            Callback | Custom | Unknown(_) => 0, // Up to the user
136            OffsetMetadata => 0,                 // TODO - add offset support
137            Byte | Char | I8 | U8 => mem::size_of::<u8>(),
138            I16 | U16 => mem::size_of::<u16>(),
139            I32 | U32 => mem::size_of::<u32>(),
140            F32 => mem::size_of::<f32>(),
141            F64 => mem::size_of::<f64>(),
142        }
143    }
144
145    /// Returns the wire size for an array of this MessageType variant.
146    /// Only applicable to data carrying types.
147    pub fn array_wire_size_hint(self, num_elements: usize) -> usize {
148        num_elements * self.wire_size_hint()
149    }
150
151    /// Returns the number of elements for this MessageType
152    /// and data payload size.
153    /// Only applicable to data carrying types.
154    pub fn array_wire_length_hint(self, data_size: usize) -> usize {
155        let wire_size = self.wire_size_hint();
156        if wire_size == 0 {
157            0
158        } else {
159            data_size / wire_size
160        }
161    }
162}
163
164impl From<u8> for MessageType {
165    fn from(value: u8) -> Self {
166        use MessageType::*;
167        match value {
168            0 => Callback,
169            1 => Custom,
170            2 => OffsetMetadata,
171            3 => Byte,
172            4 => Char,
173            5 => I8,
174            6 => U8,
175            7 => I16,
176            8 => U16,
177            9 => I32,
178            10 => U32,
179            11 => F32,
180            12 => F64,
181            _ => Unknown(value),
182        }
183    }
184}
185
186impl From<MessageType> for u8 {
187    fn from(value: MessageType) -> Self {
188        use MessageType::*;
189        match value {
190            Callback => 0,
191            Custom => 1,
192            OffsetMetadata => 2,
193            Byte => 3,
194            Char => 4,
195            I8 => 5,
196            U8 => 6,
197            I16 => 7,
198            U16 => 8,
199            I32 => 9,
200            U32 => 10,
201            F32 => 11,
202            F64 => 12,
203            Unknown(typ) => typ,
204        }
205    }
206}
207
208impl fmt::Display for MessageType {
209    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
210        write!(f, "{:?}", self)
211    }
212}
213
214#[cfg(test)]
215pub(crate) mod propt {
216    use super::*;
217    use proptest::{
218        collection, num,
219        prelude::*,
220        prop_oneof,
221        std_facade::{vec, Vec},
222    };
223
224    pub fn gen_message_type() -> impl Strategy<Value = MessageType> {
225        prop_oneof![
226            Just(MessageType::Callback),
227            Just(MessageType::Custom),
228            Just(MessageType::OffsetMetadata),
229            Just(MessageType::Byte),
230            Just(MessageType::Char),
231            Just(MessageType::I8),
232            Just(MessageType::U8),
233            Just(MessageType::I16),
234            Just(MessageType::U16),
235            Just(MessageType::I32),
236            Just(MessageType::U32),
237            Just(MessageType::F32),
238            Just(MessageType::F64),
239            gen_unknown_msg_typ(),
240        ]
241    }
242
243    prop_compose! {
244        fn gen_unknown_msg_typ()(value in 13_u8..=0x0F_u8) -> MessageType {
245            MessageType::Unknown(value)
246        }
247    }
248
249    prop_compose! {
250        pub fn gen_msg_id_bytes()(bytes in collection::vec(num::u8::ANY, 1..=MessageId::MAX_SIZE)) -> Vec<u8> {
251            bytes
252        }
253    }
254}
255
256#[cfg(test)]
257mod tests {
258    use super::*;
259    use pretty_assertions::assert_eq;
260    use propt::*;
261    use proptest::prelude::*;
262
263    #[test]
264    fn internal_ids() {
265        assert_eq!(MessageId::INTERNAL_LIB_VER, b"o");
266        assert_eq!(MessageId::INTERNAL_BOARD_ID, b"i");
267        assert_eq!(MessageId::INTERNAL_HEARTBEAT, b"h");
268        assert_eq!(MessageId::INTERNAL_AM, b"t");
269        assert_eq!(MessageId::INTERNAL_AM_LIST, b"u");
270        assert_eq!(MessageId::INTERNAL_AM_END, b"v");
271        assert_eq!(MessageId::INTERNAL_AV, b"w");
272
273        assert_eq!(MessageId::new(b"name"), Some(MessageId::BOARD_NAME));
274    }
275
276    #[test]
277    fn invalid_ids() {
278        assert_eq!(MessageId::new(&[]), None);
279        assert_eq!(MessageId::new(&[0]), None);
280        let id_bytes: [u8; 16] = [1; 16];
281        assert_eq!(id_bytes.len(), MessageId::MAX_SIZE + 1);
282        assert_eq!(MessageId::new(&id_bytes), None);
283    }
284
285    proptest! {
286        #[test]
287        fn round_trip_message_type(v_in in gen_message_type()) {
288            let wire = u8::from(v_in);
289            let v_out = MessageType::from(wire);
290            assert_eq!(v_in, v_out);
291        }
292
293        #[test]
294        fn round_trip_message_id(id_bytes in gen_msg_id_bytes()) {
295            if id_bytes.len() == 1 && id_bytes[0] == 0 {
296                assert_eq!(MessageId::new(id_bytes.as_ref()), None);
297            } else {
298                let len = id_bytes.len();
299                let s = str::from_utf8(id_bytes.as_ref());
300                let id = MessageId::new(id_bytes.as_ref()).unwrap();
301                assert_eq!(len, id.len());
302                assert_eq!(s, id.as_str());
303            }
304        }
305
306        #[test]
307        fn round_trip_size_helpers(typ in gen_message_type(), num_elements in 1_usize..64_usize) {
308            use MessageType::*;
309            let wire_size = typ.array_wire_size_hint(num_elements);
310            let cnt = typ.array_wire_length_hint(wire_size);
311            match typ {
312                Callback | Custom | Unknown(_) | OffsetMetadata => {
313                    assert_eq!(wire_size, 0);
314                    assert_eq!(cnt, 0);
315                }
316                _ => assert_eq!(cnt, num_elements, "{typ} {wire_size}"),
317            }
318        }
319    }
320}