1use crate::pui;
8use crate::status::Status;
9
10pub const HEADER_FLG_MASK: u8 = 0xC0;
12pub const HEADER_FLG_PATTERN: u8 = 0x80;
14pub const HEADER_RESERVED_MASK: u8 = 0x38;
16pub const HEADER_TID_MASK: u8 = 0x07;
18
19pub const TID_UNSOLICITED: u8 = 0;
21pub const TID_MAX: u8 = 7;
23
24#[derive(Clone, Copy, Debug, PartialEq, Eq)]
26pub struct Header(u8);
27
28impl Header {
29 pub const fn new(tid: u8) -> Option<Self> {
33 if tid <= TID_MAX {
34 Some(Self(HEADER_FLG_PATTERN | tid))
35 } else {
36 None
37 }
38 }
39
40 pub const fn from_byte(byte: u8) -> Option<Self> {
46 if byte & HEADER_FLG_MASK == HEADER_FLG_PATTERN && byte & HEADER_RESERVED_MASK == 0 {
47 Some(Self(byte))
48 } else {
49 None
50 }
51 }
52
53 pub const fn to_byte(self) -> u8 {
54 self.0
55 }
56
57 pub const fn tid(self) -> u8 {
58 self.0 & HEADER_TID_MASK
59 }
60}
61
62#[derive(Clone, Copy, Debug, PartialEq, Eq)]
64#[repr(u8)]
65pub enum Cmd {
66 Nop = 0,
68 Reset = 1,
70 PropGet = 2,
72 PropSet = 3,
74 PropInsert = 4,
76 PropRemove = 5,
78 PropIs = 6,
80 PropInserted = 7,
82 PropRemoved = 8,
84 StrSend = 9,
86 StrRecv = 10,
88 QueueDrain = 11,
90 Save = 12,
92 Clear = 13,
94 Restore = 14,
96 FactoryReset = 15,
103}
104
105impl Cmd {
106 pub const fn from_u8(value: u8) -> Option<Self> {
107 match value {
108 0 => Some(Self::Nop),
109 1 => Some(Self::Reset),
110 2 => Some(Self::PropGet),
111 3 => Some(Self::PropSet),
112 4 => Some(Self::PropInsert),
113 5 => Some(Self::PropRemove),
114 6 => Some(Self::PropIs),
115 7 => Some(Self::PropInserted),
116 8 => Some(Self::PropRemoved),
117 9 => Some(Self::StrSend),
118 10 => Some(Self::StrRecv),
119 11 => Some(Self::QueueDrain),
120 12 => Some(Self::Save),
121 13 => Some(Self::Clear),
122 14 => Some(Self::Restore),
123 15 => Some(Self::FactoryReset),
124 _ => None,
125 }
126 }
127}
128
129#[derive(Clone, Copy, Debug, PartialEq, Eq)]
130pub enum ParseError {
131 Truncated,
133 BadFlag,
135 ReservedBits,
137 BadCommand,
140 BadPui,
142}
143
144impl From<pui::Error> for ParseError {
145 fn from(error: pui::Error) -> Self {
146 match error {
147 pui::Error::Truncated => Self::Truncated,
148 _ => Self::BadPui,
149 }
150 }
151}
152
153#[derive(Clone, Copy, Debug, PartialEq, Eq)]
159pub struct Frame<'a> {
160 pub header: Header,
161 pub cmd: u8,
162 pub payload: &'a [u8],
163}
164
165impl<'a> Frame<'a> {
166 pub fn parse(bytes: &'a [u8]) -> Result<Self, ParseError> {
167 let [header_byte, cmd, payload @ ..] = bytes else {
168 return Err(ParseError::Truncated);
169 };
170 if header_byte & HEADER_FLG_MASK != HEADER_FLG_PATTERN {
171 return Err(ParseError::BadFlag);
172 }
173 let header = Header::from_byte(*header_byte).ok_or(ParseError::ReservedBits)?;
174 if cmd & 0x80 != 0 {
175 return Err(ParseError::BadCommand);
176 }
177 Ok(Self {
178 header,
179 cmd: *cmd,
180 payload,
181 })
182 }
183
184 pub const fn command(&self) -> Option<Cmd> {
186 Cmd::from_u8(self.cmd)
187 }
188}
189
190#[derive(Clone, Copy, Debug, PartialEq, Eq)]
191pub enum WriteError {
192 BufferTooSmall,
194 InvalidTid,
196 ValueTooLarge,
198}
199
200impl From<pui::Error> for WriteError {
201 fn from(error: pui::Error) -> Self {
202 match error {
203 pui::Error::BufferTooSmall => Self::BufferTooSmall,
204 _ => Self::ValueTooLarge,
205 }
206 }
207}
208
209pub struct FrameWriter<'a> {
211 buf: &'a mut [u8],
212 len: usize,
213}
214
215impl<'a> FrameWriter<'a> {
216 pub fn new(buf: &'a mut [u8], tid: u8, cmd: Cmd) -> Result<Self, WriteError> {
218 let header = Header::new(tid).ok_or(WriteError::InvalidTid)?;
219 let mut writer = Self { buf, len: 0 };
220 writer.write_u8(header.to_byte())?;
221 writer.write_u8(cmd as u8)?;
222 Ok(writer)
223 }
224
225 pub fn write_u8(&mut self, byte: u8) -> Result<(), WriteError> {
226 if self.len >= self.buf.len() {
227 return Err(WriteError::BufferTooSmall);
228 }
229 self.buf[self.len] = byte;
230 self.len += 1;
231 Ok(())
232 }
233
234 pub fn write_bytes(&mut self, bytes: &[u8]) -> Result<(), WriteError> {
235 let end = self
236 .len
237 .checked_add(bytes.len())
238 .ok_or(WriteError::BufferTooSmall)?;
239 if end > self.buf.len() {
240 return Err(WriteError::BufferTooSmall);
241 }
242 self.buf[self.len..end].copy_from_slice(bytes);
243 self.len = end;
244 Ok(())
245 }
246
247 pub fn write_pui(&mut self, value: u32) -> Result<(), WriteError> {
248 let written = pui::encode(value, &mut self.buf[self.len..])?;
249 self.len += written;
250 Ok(())
251 }
252
253 pub fn write_u16_le(&mut self, value: u16) -> Result<(), WriteError> {
254 self.write_bytes(&value.to_le_bytes())
255 }
256
257 pub fn write_u32_le(&mut self, value: u32) -> Result<(), WriteError> {
258 self.write_bytes(&value.to_le_bytes())
259 }
260
261 pub fn finish(self) -> usize {
263 self.len
264 }
265}
266
267pub fn nop(buf: &mut [u8], tid: u8) -> Result<usize, WriteError> {
269 Ok(FrameWriter::new(buf, tid, Cmd::Nop)?.finish())
270}
271
272pub fn reset(buf: &mut [u8], tid: u8) -> Result<usize, WriteError> {
274 Ok(FrameWriter::new(buf, tid, Cmd::Reset)?.finish())
275}
276
277pub fn prop_get(buf: &mut [u8], tid: u8, key: u32) -> Result<usize, WriteError> {
279 let mut writer = FrameWriter::new(buf, tid, Cmd::PropGet)?;
280 writer.write_pui(key)?;
281 Ok(writer.finish())
282}
283
284pub fn prop_set(buf: &mut [u8], tid: u8, key: u32, value: &[u8]) -> Result<usize, WriteError> {
286 let mut writer = FrameWriter::new(buf, tid, Cmd::PropSet)?;
287 writer.write_pui(key)?;
288 writer.write_bytes(value)?;
289 Ok(writer.finish())
290}
291
292pub fn prop_is(buf: &mut [u8], tid: u8, key: u32, value: &[u8]) -> Result<usize, WriteError> {
294 let mut writer = FrameWriter::new(buf, tid, Cmd::PropIs)?;
295 writer.write_pui(key)?;
296 writer.write_bytes(value)?;
297 Ok(writer.finish())
298}
299
300pub fn prop_insert(buf: &mut [u8], tid: u8, key: u32, item: &[u8]) -> Result<usize, WriteError> {
303 let mut writer = FrameWriter::new(buf, tid, Cmd::PropInsert)?;
304 writer.write_pui(key)?;
305 writer.write_bytes(item)?;
306 Ok(writer.finish())
307}
308
309pub fn prop_remove(
312 buf: &mut [u8],
313 tid: u8,
314 key: u32,
315 selector: &[u8],
316) -> Result<usize, WriteError> {
317 let mut writer = FrameWriter::new(buf, tid, Cmd::PropRemove)?;
318 writer.write_pui(key)?;
319 writer.write_bytes(selector)?;
320 Ok(writer.finish())
321}
322
323pub fn prop_inserted(
326 buf: &mut [u8],
327 tid: u8,
328 key: u32,
329 digest: &[u8],
330) -> Result<usize, WriteError> {
331 let mut writer = FrameWriter::new(buf, tid, Cmd::PropInserted)?;
332 writer.write_pui(key)?;
333 writer.write_bytes(digest)?;
334 Ok(writer.finish())
335}
336
337pub fn prop_removed(buf: &mut [u8], tid: u8, key: u32, digest: &[u8]) -> Result<usize, WriteError> {
340 let mut writer = FrameWriter::new(buf, tid, Cmd::PropRemoved)?;
341 writer.write_pui(key)?;
342 writer.write_bytes(digest)?;
343 Ok(writer.finish())
344}
345
346pub fn queue_drain(buf: &mut [u8], tid: u8) -> Result<usize, WriteError> {
348 Ok(FrameWriter::new(buf, tid, Cmd::QueueDrain)?.finish())
349}
350
351pub fn save(buf: &mut [u8], tid: u8) -> Result<usize, WriteError> {
353 Ok(FrameWriter::new(buf, tid, Cmd::Save)?.finish())
354}
355
356pub fn clear(buf: &mut [u8], tid: u8) -> Result<usize, WriteError> {
358 Ok(FrameWriter::new(buf, tid, Cmd::Clear)?.finish())
359}
360
361pub fn factory_reset(buf: &mut [u8], tid: u8) -> Result<usize, WriteError> {
364 Ok(FrameWriter::new(buf, tid, Cmd::FactoryReset)?.finish())
365}
366
367pub fn restore(buf: &mut [u8], tid: u8) -> Result<usize, WriteError> {
369 Ok(FrameWriter::new(buf, tid, Cmd::Restore)?.finish())
370}
371
372pub fn last_status(buf: &mut [u8], tid: u8, status: Status) -> Result<usize, WriteError> {
374 let mut writer = FrameWriter::new(buf, tid, Cmd::PropIs)?;
375 writer.write_pui(crate::ids::prop::LAST_STATUS)?;
376 writer.write_pui(status.0)?;
377 Ok(writer.finish())
378}
379
380fn stream_payload(
381 writer: &mut FrameWriter<'_>,
382 stream: u32,
383 data: &[u8],
384 metadata: &[u8],
385) -> Result<(), WriteError> {
386 let data_len = u16::try_from(data.len()).map_err(|_| WriteError::ValueTooLarge)?;
387 writer.write_pui(stream)?;
388 writer.write_u16_le(data_len)?;
389 writer.write_bytes(data)?;
390 writer.write_bytes(metadata)
391}
392
393pub fn str_send(
395 buf: &mut [u8],
396 tid: u8,
397 stream: u32,
398 data: &[u8],
399 metadata: &[u8],
400) -> Result<usize, WriteError> {
401 let mut writer = FrameWriter::new(buf, tid, Cmd::StrSend)?;
402 stream_payload(&mut writer, stream, data, metadata)?;
403 Ok(writer.finish())
404}
405
406pub fn str_recv(
408 buf: &mut [u8],
409 stream: u32,
410 data: &[u8],
411 metadata: &[u8],
412) -> Result<usize, WriteError> {
413 let mut writer = FrameWriter::new(buf, TID_UNSOLICITED, Cmd::StrRecv)?;
414 stream_payload(&mut writer, stream, data, metadata)?;
415 Ok(writer.finish())
416}
417
418#[derive(Clone, Copy, Debug, PartialEq, Eq)]
422pub struct PropPayload<'a> {
423 pub key: u32,
424 pub value: &'a [u8],
425}
426
427impl<'a> PropPayload<'a> {
428 pub fn parse(payload: &'a [u8]) -> Result<Self, ParseError> {
429 let (key, consumed) = pui::decode(payload)?;
430 Ok(Self {
431 key,
432 value: &payload[consumed..],
433 })
434 }
435}
436
437#[derive(Clone, Copy, Debug, PartialEq, Eq)]
439pub struct StreamPayload<'a> {
440 pub stream: u32,
441 pub data: &'a [u8],
442 pub metadata: &'a [u8],
444}
445
446impl<'a> StreamPayload<'a> {
447 pub fn parse(payload: &'a [u8]) -> Result<Self, ParseError> {
448 let (stream, consumed) = pui::decode(payload)?;
449 let rest = &payload[consumed..];
450 let [len_lo, len_hi, rest @ ..] = rest else {
451 return Err(ParseError::Truncated);
452 };
453 let data_len = usize::from(u16::from_le_bytes([*len_lo, *len_hi]));
454 if rest.len() < data_len {
455 return Err(ParseError::Truncated);
456 }
457 let (data, metadata) = rest.split_at(data_len);
458 Ok(Self {
459 stream,
460 data,
461 metadata,
462 })
463 }
464}
465
466#[cfg(test)]
467mod tests {
468 use super::*;
469 use crate::ids::{prop, stream};
470
471 #[test]
472 fn header_round_trip() {
473 for tid in 0..=TID_MAX {
474 let header = Header::new(tid).unwrap();
475 assert_eq!(header.tid(), tid);
476 assert_eq!(Header::from_byte(header.to_byte()), Some(header));
477 }
478 assert_eq!(Header::new(TID_MAX + 1), None);
479 }
480
481 #[test]
482 fn header_rejects_bad_bytes() {
483 assert_eq!(Header::from_byte(0x00), None);
485 assert_eq!(Header::from_byte(0x40), None);
486 assert_eq!(Header::from_byte(0xC0), None);
487 for bit in [0x08u8, 0x10, 0x20] {
489 assert_eq!(Header::from_byte(HEADER_FLG_PATTERN | bit), None);
490 }
491 }
492
493 #[test]
494 fn nop_frame() {
495 let mut buf = [0u8; 8];
496 let len = nop(&mut buf, 3).unwrap();
497 assert_eq!(&buf[..len], &[0x83, 0x00]);
498
499 let frame = Frame::parse(&buf[..len]).unwrap();
500 assert_eq!(frame.header.tid(), 3);
501 assert_eq!(frame.command(), Some(Cmd::Nop));
502 assert!(frame.payload.is_empty());
503 }
504
505 #[test]
506 fn prop_get_round_trip() {
507 let mut buf = [0u8; 8];
508 let len = prop_get(&mut buf, 1, prop::PHY_DUTY_LIMIT).unwrap();
509 assert_eq!(&buf[..len], &[0x81, 0x02, 0xD6, 0x25]);
510
511 let frame = Frame::parse(&buf[..len]).unwrap();
512 assert_eq!(frame.command(), Some(Cmd::PropGet));
513 let payload = PropPayload::parse(frame.payload).unwrap();
514 assert_eq!(payload.key, prop::PHY_DUTY_LIMIT);
515 assert!(payload.value.is_empty());
516 }
517
518 #[test]
519 fn prop_set_round_trip() {
520 let mut buf = [0u8; 16];
521 let len = prop_set(&mut buf, 2, prop::PHY_FREQ, &906_875u32.to_le_bytes()).unwrap();
522
523 let frame = Frame::parse(&buf[..len]).unwrap();
524 assert_eq!(frame.header.tid(), 2);
525 assert_eq!(frame.command(), Some(Cmd::PropSet));
526 let payload = PropPayload::parse(frame.payload).unwrap();
527 assert_eq!(payload.key, prop::PHY_FREQ);
528 assert_eq!(payload.value, &906_875u32.to_le_bytes());
529 }
530
531 #[test]
532 fn last_status_frame() {
533 let mut buf = [0u8; 8];
534 let len = last_status(&mut buf, 5, Status::DUTY_LIMIT).unwrap();
535 assert_eq!(&buf[..len], &[0x85, 0x06, 0x00, 0x20]);
536
537 let frame = Frame::parse(&buf[..len]).unwrap();
538 let payload = PropPayload::parse(frame.payload).unwrap();
539 assert_eq!(payload.key, prop::LAST_STATUS);
540 let (code, consumed) = crate::pui::decode(payload.value).unwrap();
541 assert_eq!(Status(code), Status::DUTY_LIMIT);
542 assert_eq!(consumed, payload.value.len());
543 }
544
545 #[test]
546 fn stream_round_trip() {
547 let mut buf = [0u8; 32];
548 let data = [0xDEu8, 0xAD, 0xBE, 0xEF];
549 let meta = [0x7Fu8, 0x00];
550 let len = str_send(&mut buf, 4, stream::PHY_RAW, &data, &meta).unwrap();
551
552 let frame = Frame::parse(&buf[..len]).unwrap();
553 assert_eq!(frame.command(), Some(Cmd::StrSend));
554 let payload = StreamPayload::parse(frame.payload).unwrap();
555 assert_eq!(payload.stream, stream::PHY_RAW);
556 assert_eq!(payload.data, &data);
557 assert_eq!(payload.metadata, &meta);
558 }
559
560 #[test]
561 fn stream_without_metadata() {
562 let mut buf = [0u8; 16];
563 let len = str_recv(&mut buf, stream::PHY_RAW, &[0xAA], &[]).unwrap();
564
565 let frame = Frame::parse(&buf[..len]).unwrap();
566 assert_eq!(frame.header.tid(), TID_UNSOLICITED);
567 let payload = StreamPayload::parse(frame.payload).unwrap();
568 assert_eq!(payload.data, &[0xAA]);
569 assert!(payload.metadata.is_empty());
570 }
571
572 #[test]
573 fn stream_truncated_data() {
574 let payload = [0x71, 0x04, 0x00, 0xAA, 0xBB];
576 assert_eq!(StreamPayload::parse(&payload), Err(ParseError::Truncated));
577 }
578
579 #[test]
580 fn parse_rejects_malformed() {
581 assert_eq!(Frame::parse(&[]), Err(ParseError::Truncated));
582 assert_eq!(Frame::parse(&[0x80]), Err(ParseError::Truncated));
583 assert_eq!(Frame::parse(&[0x00, 0x00]), Err(ParseError::BadFlag));
584 assert_eq!(Frame::parse(&[0x88, 0x00]), Err(ParseError::ReservedBits));
585 assert_eq!(Frame::parse(&[0x80, 0x80]), Err(ParseError::BadCommand));
586 }
587
588 #[test]
589 fn unknown_command_is_well_formed() {
590 let frame = Frame::parse(&[0x81, 0x10]).unwrap();
591 assert_eq!(frame.cmd, 16);
592 assert_eq!(frame.command(), None);
593 }
594
595 #[test]
596 fn every_assigned_command_round_trips() {
597 for id in 0..=15u8 {
598 let cmd = Cmd::from_u8(id).unwrap_or_else(|| panic!("command {id} unassigned"));
599 assert_eq!(cmd as u8, id);
600 }
601 assert_eq!(Cmd::from_u8(16), None);
602 }
603
604 #[test]
605 fn insert_remove_round_trip() {
606 let mut buf = [0u8; 80];
607 let item = [0xA5u8; 33];
608 let len = prop_insert(&mut buf, 3, prop::HOST_RX_FILTERS, &item).unwrap();
609 let frame = Frame::parse(&buf[..len]).unwrap();
610 assert_eq!(frame.command(), Some(Cmd::PropInsert));
611 let payload = PropPayload::parse(frame.payload).unwrap();
612 assert_eq!(payload.key, prop::HOST_RX_FILTERS);
613 assert_eq!(payload.value, &item);
614
615 let len = prop_remove(&mut buf, 4, prop::HOST_PEER_KEYS, &item[..32]).unwrap();
616 let frame = Frame::parse(&buf[..len]).unwrap();
617 assert_eq!(frame.command(), Some(Cmd::PropRemove));
618 let payload = PropPayload::parse(frame.payload).unwrap();
619 assert_eq!(payload.key, prop::HOST_PEER_KEYS);
620 assert_eq!(payload.value, &item[..32]);
621 }
622
623 #[test]
624 fn inserted_removed_round_trip() {
625 let mut buf = [0u8; 48];
626 let digest = [0x42u8; 32];
627 let len = prop_inserted(&mut buf, 5, prop::HOST_PEER_KEYS, &digest).unwrap();
628 let frame = Frame::parse(&buf[..len]).unwrap();
629 assert_eq!(frame.command(), Some(Cmd::PropInserted));
630 let payload = PropPayload::parse(frame.payload).unwrap();
631 assert_eq!(payload.value, &digest);
632
633 let len = prop_removed(
634 &mut buf,
635 TID_UNSOLICITED,
636 prop::HOST_CHANNEL_KEYS,
637 &[0x12, 0x34],
638 )
639 .unwrap();
640 let frame = Frame::parse(&buf[..len]).unwrap();
641 assert_eq!(frame.command(), Some(Cmd::PropRemoved));
642 assert_eq!(frame.header.tid(), TID_UNSOLICITED);
643 let payload = PropPayload::parse(frame.payload).unwrap();
644 assert_eq!(payload.key, prop::HOST_CHANNEL_KEYS);
645 assert_eq!(payload.value, &[0x12, 0x34]);
646 }
647
648 #[test]
649 fn payloadless_full_commands() {
650 let mut buf = [0u8; 4];
651 for (encode, cmd) in [
652 (
653 queue_drain as fn(&mut [u8], u8) -> Result<usize, WriteError>,
654 Cmd::QueueDrain,
655 ),
656 (save, Cmd::Save),
657 (clear, Cmd::Clear),
658 (restore, Cmd::Restore),
659 (factory_reset, Cmd::FactoryReset),
660 ] {
661 let len = encode(&mut buf, 2).unwrap();
662 assert_eq!(len, 2);
663 let frame = Frame::parse(&buf[..len]).unwrap();
664 assert_eq!(frame.command(), Some(cmd));
665 assert!(frame.payload.is_empty());
666 }
667 }
668
669 #[test]
670 fn writer_reports_overflow() {
671 let mut buf = [0u8; 3];
672 assert_eq!(
673 prop_set(&mut buf, 1, prop::PHY_FREQ, &[0; 8]),
674 Err(WriteError::BufferTooSmall)
675 );
676 }
677}