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 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 pub const INTERNAL_AM: Self = MessageId(b"t");
18 pub const INTERNAL_AM_LIST: Self = MessageId(b"u");
20 pub const INTERNAL_AM_END: Self = MessageId(b"v");
22 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 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
73impl<'a> PartialEq<[u8]> for MessageId<'a> {
75 fn eq(&self, other: &[u8]) -> bool {
76 self.0 == other
77 }
78}
79
80impl<'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
87impl<'a> PartialEq<MessageId<'a>> for [u8] {
89 fn eq(&self, other: &MessageId) -> bool {
90 self == other.0
91 }
92}
93
94impl<'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 pub fn wire_size_hint(self) -> usize {
133 use MessageType::*;
134 match self {
135 Callback | Custom | Unknown(_) => 0, OffsetMetadata => 0, 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 pub fn array_wire_size_hint(self, num_elements: usize) -> usize {
148 num_elements * self.wire_size_hint()
149 }
150
151 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}