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 Reboot = 16,
110 BleClearBonds = 17,
117 PropMultiGet = 21,
124 PropMultiSet = 22,
127 PropAre = 23,
130}
131
132impl Cmd {
133 pub const fn from_u8(value: u8) -> Option<Self> {
134 match value {
135 0 => Some(Self::Nop),
136 1 => Some(Self::Reset),
137 2 => Some(Self::PropGet),
138 3 => Some(Self::PropSet),
139 4 => Some(Self::PropInsert),
140 5 => Some(Self::PropRemove),
141 6 => Some(Self::PropIs),
142 7 => Some(Self::PropInserted),
143 8 => Some(Self::PropRemoved),
144 9 => Some(Self::StrSend),
145 10 => Some(Self::StrRecv),
146 11 => Some(Self::QueueDrain),
147 12 => Some(Self::Save),
148 13 => Some(Self::Clear),
149 14 => Some(Self::Restore),
150 15 => Some(Self::FactoryReset),
151 16 => Some(Self::Reboot),
152 17 => Some(Self::BleClearBonds),
153 21 => Some(Self::PropMultiGet),
154 22 => Some(Self::PropMultiSet),
155 23 => Some(Self::PropAre),
156 _ => None,
157 }
158 }
159}
160
161#[derive(Clone, Copy, Debug, PartialEq, Eq)]
162pub enum ParseError {
163 Truncated,
165 BadFlag,
167 ReservedBits,
169 BadCommand,
172 BadPui,
174}
175
176impl From<pui::Error> for ParseError {
177 fn from(error: pui::Error) -> Self {
178 match error {
179 pui::Error::Truncated => Self::Truncated,
180 _ => Self::BadPui,
181 }
182 }
183}
184
185#[derive(Clone, Copy, Debug, PartialEq, Eq)]
191pub struct Frame<'a> {
192 pub header: Header,
193 pub cmd: u8,
194 pub payload: &'a [u8],
195}
196
197impl<'a> Frame<'a> {
198 pub fn parse(bytes: &'a [u8]) -> Result<Self, ParseError> {
199 let [header_byte, cmd, payload @ ..] = bytes else {
200 return Err(ParseError::Truncated);
201 };
202 if header_byte & HEADER_FLG_MASK != HEADER_FLG_PATTERN {
203 return Err(ParseError::BadFlag);
204 }
205 let header = Header::from_byte(*header_byte).ok_or(ParseError::ReservedBits)?;
206 if cmd & 0x80 != 0 {
207 return Err(ParseError::BadCommand);
208 }
209 Ok(Self {
210 header,
211 cmd: *cmd,
212 payload,
213 })
214 }
215
216 pub const fn command(&self) -> Option<Cmd> {
218 Cmd::from_u8(self.cmd)
219 }
220}
221
222#[derive(Clone, Copy, Debug, PartialEq, Eq)]
223pub enum WriteError {
224 BufferTooSmall,
226 InvalidTid,
228 ValueTooLarge,
230}
231
232impl From<pui::Error> for WriteError {
233 fn from(error: pui::Error) -> Self {
234 match error {
235 pui::Error::BufferTooSmall => Self::BufferTooSmall,
236 _ => Self::ValueTooLarge,
237 }
238 }
239}
240
241pub struct FrameWriter<'a> {
243 buf: &'a mut [u8],
244 len: usize,
245}
246
247impl<'a> FrameWriter<'a> {
248 pub fn new(buf: &'a mut [u8], tid: u8, cmd: Cmd) -> Result<Self, WriteError> {
250 let header = Header::new(tid).ok_or(WriteError::InvalidTid)?;
251 let mut writer = Self { buf, len: 0 };
252 writer.write_u8(header.to_byte())?;
253 writer.write_u8(cmd as u8)?;
254 Ok(writer)
255 }
256
257 pub fn write_u8(&mut self, byte: u8) -> Result<(), WriteError> {
258 if self.len >= self.buf.len() {
259 return Err(WriteError::BufferTooSmall);
260 }
261 self.buf[self.len] = byte;
262 self.len += 1;
263 Ok(())
264 }
265
266 pub fn write_bytes(&mut self, bytes: &[u8]) -> Result<(), WriteError> {
267 let end = self
268 .len
269 .checked_add(bytes.len())
270 .ok_or(WriteError::BufferTooSmall)?;
271 if end > self.buf.len() {
272 return Err(WriteError::BufferTooSmall);
273 }
274 self.buf[self.len..end].copy_from_slice(bytes);
275 self.len = end;
276 Ok(())
277 }
278
279 pub fn write_pui(&mut self, value: u32) -> Result<(), WriteError> {
280 let written = pui::encode(value, &mut self.buf[self.len..])?;
281 self.len += written;
282 Ok(())
283 }
284
285 pub fn write_u16_le(&mut self, value: u16) -> Result<(), WriteError> {
286 self.write_bytes(&value.to_le_bytes())
287 }
288
289 pub fn write_u32_le(&mut self, value: u32) -> Result<(), WriteError> {
290 self.write_bytes(&value.to_le_bytes())
291 }
292
293 pub const fn remaining(&self) -> usize {
295 self.buf.len() - self.len
296 }
297
298 pub const fn len(&self) -> usize {
300 self.len
301 }
302
303 pub const fn is_empty(&self) -> bool {
304 self.len == 0
305 }
306
307 pub fn write_entry(&mut self, key: u32, value: &[u8]) -> Result<(), WriteError> {
313 let total = entry_len(key, value.len()).ok_or(WriteError::ValueTooLarge)?;
314 if self.remaining() < total {
315 return Err(WriteError::BufferTooSmall);
316 }
317 let body = pui::encoded_len(key) + value.len();
318 self.write_pui(body as u32)?;
319 self.write_pui(key)?;
320 self.write_bytes(value)
321 }
322
323 pub fn write_status_entry(&mut self, status: Status) -> Result<(), WriteError> {
326 let mut value = [0u8; pui::MAX_LEN];
327 let len = pui::encode(status.0, &mut value)?;
328 self.write_entry(crate::ids::prop::LAST_STATUS, &value[..len])
329 }
330
331 pub fn finish(self) -> usize {
333 self.len
334 }
335}
336
337pub const fn entry_len(key: u32, value_len: usize) -> Option<usize> {
342 let body = pui::encoded_len(key) + value_len;
343 if body > pui::MAX_VALUE as usize {
344 return None;
345 }
346 Some(pui::encoded_len(body as u32) + body)
347}
348
349pub fn nop(buf: &mut [u8], tid: u8) -> Result<usize, WriteError> {
351 Ok(FrameWriter::new(buf, tid, Cmd::Nop)?.finish())
352}
353
354pub fn reset(buf: &mut [u8], tid: u8) -> Result<usize, WriteError> {
356 Ok(FrameWriter::new(buf, tid, Cmd::Reset)?.finish())
357}
358
359pub fn prop_get(buf: &mut [u8], tid: u8, key: u32) -> Result<usize, WriteError> {
361 let mut writer = FrameWriter::new(buf, tid, Cmd::PropGet)?;
362 writer.write_pui(key)?;
363 Ok(writer.finish())
364}
365
366pub fn prop_set(buf: &mut [u8], tid: u8, key: u32, value: &[u8]) -> Result<usize, WriteError> {
368 let mut writer = FrameWriter::new(buf, tid, Cmd::PropSet)?;
369 writer.write_pui(key)?;
370 writer.write_bytes(value)?;
371 Ok(writer.finish())
372}
373
374pub fn prop_is(buf: &mut [u8], tid: u8, key: u32, value: &[u8]) -> Result<usize, WriteError> {
376 let mut writer = FrameWriter::new(buf, tid, Cmd::PropIs)?;
377 writer.write_pui(key)?;
378 writer.write_bytes(value)?;
379 Ok(writer.finish())
380}
381
382pub fn prop_insert(buf: &mut [u8], tid: u8, key: u32, item: &[u8]) -> Result<usize, WriteError> {
385 let mut writer = FrameWriter::new(buf, tid, Cmd::PropInsert)?;
386 writer.write_pui(key)?;
387 writer.write_bytes(item)?;
388 Ok(writer.finish())
389}
390
391pub fn prop_remove(
394 buf: &mut [u8],
395 tid: u8,
396 key: u32,
397 selector: &[u8],
398) -> Result<usize, WriteError> {
399 let mut writer = FrameWriter::new(buf, tid, Cmd::PropRemove)?;
400 writer.write_pui(key)?;
401 writer.write_bytes(selector)?;
402 Ok(writer.finish())
403}
404
405pub fn prop_inserted(
408 buf: &mut [u8],
409 tid: u8,
410 key: u32,
411 digest: &[u8],
412) -> Result<usize, WriteError> {
413 let mut writer = FrameWriter::new(buf, tid, Cmd::PropInserted)?;
414 writer.write_pui(key)?;
415 writer.write_bytes(digest)?;
416 Ok(writer.finish())
417}
418
419pub fn prop_removed(buf: &mut [u8], tid: u8, key: u32, digest: &[u8]) -> Result<usize, WriteError> {
422 let mut writer = FrameWriter::new(buf, tid, Cmd::PropRemoved)?;
423 writer.write_pui(key)?;
424 writer.write_bytes(digest)?;
425 Ok(writer.finish())
426}
427
428pub fn prop_multi_get(buf: &mut [u8], tid: u8, keys: &[u32]) -> Result<usize, WriteError> {
431 let mut writer = FrameWriter::new(buf, tid, Cmd::PropMultiGet)?;
432 for &key in keys {
433 writer.write_pui(key)?;
434 }
435 Ok(writer.finish())
436}
437
438pub fn prop_multi_set(
440 buf: &mut [u8],
441 tid: u8,
442 entries: &[(u32, &[u8])],
443) -> Result<usize, WriteError> {
444 let mut writer = FrameWriter::new(buf, tid, Cmd::PropMultiSet)?;
445 for &(key, value) in entries {
446 writer.write_entry(key, value)?;
447 }
448 Ok(writer.finish())
449}
450
451pub fn prop_are(buf: &mut [u8], tid: u8) -> Result<FrameWriter<'_>, WriteError> {
454 FrameWriter::new(buf, tid, Cmd::PropAre)
455}
456
457#[derive(Clone, Copy, Debug, PartialEq, Eq)]
459pub struct MultiEntry<'a> {
460 pub key: u32,
461 pub value: &'a [u8],
462}
463
464#[derive(Clone, Copy, Debug)]
470pub struct MultiEntries<'a> {
471 rest: &'a [u8],
472}
473
474impl<'a> MultiEntries<'a> {
475 pub const fn new(payload: &'a [u8]) -> Self {
476 Self { rest: payload }
477 }
478
479 pub const fn remainder(&self) -> &'a [u8] {
482 self.rest
483 }
484
485 fn take_entry(&mut self) -> Result<MultiEntry<'a>, ParseError> {
486 let payload = core::mem::take(&mut self.rest);
487 let (body_len, consumed) = pui::decode(payload)?;
488 let after_len = &payload[consumed..];
489 let body_len = body_len as usize;
490 if after_len.len() < body_len {
491 return Err(ParseError::Truncated);
492 }
493 let (body, tail) = after_len.split_at(body_len);
494 let (key, key_len) = pui::decode(body)?;
495 self.rest = tail;
496 Ok(MultiEntry {
497 key,
498 value: &body[key_len..],
499 })
500 }
501}
502
503impl<'a> Iterator for MultiEntries<'a> {
504 type Item = Result<MultiEntry<'a>, ParseError>;
505
506 fn next(&mut self) -> Option<Self::Item> {
507 if self.rest.is_empty() {
508 return None;
509 }
510 Some(self.take_entry())
511 }
512}
513
514#[derive(Clone, Copy, Debug)]
517pub struct MultiGetKeys<'a> {
518 rest: &'a [u8],
519}
520
521impl<'a> MultiGetKeys<'a> {
522 pub const fn new(payload: &'a [u8]) -> Self {
523 Self { rest: payload }
524 }
525
526 pub const fn remainder(&self) -> &'a [u8] {
528 self.rest
529 }
530}
531
532impl Iterator for MultiGetKeys<'_> {
533 type Item = Result<u32, ParseError>;
534
535 fn next(&mut self) -> Option<Self::Item> {
536 if self.rest.is_empty() {
537 return None;
538 }
539 let payload = core::mem::take(&mut self.rest);
540 match pui::decode(payload) {
541 Ok((key, consumed)) => {
542 self.rest = &payload[consumed..];
543 Some(Ok(key))
544 }
545 Err(error) => Some(Err(error.into())),
546 }
547 }
548}
549
550pub fn queue_drain(buf: &mut [u8], tid: u8) -> Result<usize, WriteError> {
552 Ok(FrameWriter::new(buf, tid, Cmd::QueueDrain)?.finish())
553}
554
555pub fn save(buf: &mut [u8], tid: u8) -> Result<usize, WriteError> {
557 Ok(FrameWriter::new(buf, tid, Cmd::Save)?.finish())
558}
559
560pub fn clear(buf: &mut [u8], tid: u8) -> Result<usize, WriteError> {
562 Ok(FrameWriter::new(buf, tid, Cmd::Clear)?.finish())
563}
564
565pub fn factory_reset(buf: &mut [u8], tid: u8) -> Result<usize, WriteError> {
568 Ok(FrameWriter::new(buf, tid, Cmd::FactoryReset)?.finish())
569}
570
571pub fn reboot(buf: &mut [u8], tid: u8) -> Result<usize, WriteError> {
574 Ok(FrameWriter::new(buf, tid, Cmd::Reboot)?.finish())
575}
576
577pub fn ble_clear_bonds(buf: &mut [u8], tid: u8) -> Result<usize, WriteError> {
581 Ok(FrameWriter::new(buf, tid, Cmd::BleClearBonds)?.finish())
582}
583
584pub fn restore(buf: &mut [u8], tid: u8) -> Result<usize, WriteError> {
586 Ok(FrameWriter::new(buf, tid, Cmd::Restore)?.finish())
587}
588
589pub fn last_status(buf: &mut [u8], tid: u8, status: Status) -> Result<usize, WriteError> {
591 let mut writer = FrameWriter::new(buf, tid, Cmd::PropIs)?;
592 writer.write_pui(crate::ids::prop::LAST_STATUS)?;
593 writer.write_pui(status.0)?;
594 Ok(writer.finish())
595}
596
597fn stream_payload(
598 writer: &mut FrameWriter<'_>,
599 stream: u32,
600 data: &[u8],
601 metadata: &[u8],
602) -> Result<(), WriteError> {
603 let data_len = u16::try_from(data.len()).map_err(|_| WriteError::ValueTooLarge)?;
604 writer.write_pui(stream)?;
605 writer.write_u16_le(data_len)?;
606 writer.write_bytes(data)?;
607 writer.write_bytes(metadata)
608}
609
610pub fn str_send(
612 buf: &mut [u8],
613 tid: u8,
614 stream: u32,
615 data: &[u8],
616 metadata: &[u8],
617) -> Result<usize, WriteError> {
618 let mut writer = FrameWriter::new(buf, tid, Cmd::StrSend)?;
619 stream_payload(&mut writer, stream, data, metadata)?;
620 Ok(writer.finish())
621}
622
623pub fn str_recv(
625 buf: &mut [u8],
626 stream: u32,
627 data: &[u8],
628 metadata: &[u8],
629) -> Result<usize, WriteError> {
630 let mut writer = FrameWriter::new(buf, TID_UNSOLICITED, Cmd::StrRecv)?;
631 stream_payload(&mut writer, stream, data, metadata)?;
632 Ok(writer.finish())
633}
634
635#[derive(Clone, Copy, Debug, PartialEq, Eq)]
639pub struct PropPayload<'a> {
640 pub key: u32,
641 pub value: &'a [u8],
642}
643
644impl<'a> PropPayload<'a> {
645 pub fn parse(payload: &'a [u8]) -> Result<Self, ParseError> {
646 let (key, consumed) = pui::decode(payload)?;
647 Ok(Self {
648 key,
649 value: &payload[consumed..],
650 })
651 }
652}
653
654#[derive(Clone, Copy, Debug, PartialEq, Eq)]
656pub struct StreamPayload<'a> {
657 pub stream: u32,
658 pub data: &'a [u8],
659 pub metadata: &'a [u8],
661}
662
663impl<'a> StreamPayload<'a> {
664 pub fn parse(payload: &'a [u8]) -> Result<Self, ParseError> {
665 let (stream, consumed) = pui::decode(payload)?;
666 let rest = &payload[consumed..];
667 let [len_lo, len_hi, rest @ ..] = rest else {
668 return Err(ParseError::Truncated);
669 };
670 let data_len = usize::from(u16::from_le_bytes([*len_lo, *len_hi]));
671 if rest.len() < data_len {
672 return Err(ParseError::Truncated);
673 }
674 let (data, metadata) = rest.split_at(data_len);
675 Ok(Self {
676 stream,
677 data,
678 metadata,
679 })
680 }
681}
682
683#[cfg(test)]
684mod tests {
685 use super::*;
686 use crate::ids::{prop, stream};
687
688 #[test]
689 fn header_round_trip() {
690 for tid in 0..=TID_MAX {
691 let header = Header::new(tid).unwrap();
692 assert_eq!(header.tid(), tid);
693 assert_eq!(Header::from_byte(header.to_byte()), Some(header));
694 }
695 assert_eq!(Header::new(TID_MAX + 1), None);
696 }
697
698 #[test]
699 fn header_rejects_bad_bytes() {
700 assert_eq!(Header::from_byte(0x00), None);
702 assert_eq!(Header::from_byte(0x40), None);
703 assert_eq!(Header::from_byte(0xC0), None);
704 for bit in [0x08u8, 0x10, 0x20] {
706 assert_eq!(Header::from_byte(HEADER_FLG_PATTERN | bit), None);
707 }
708 }
709
710 #[test]
711 fn nop_frame() {
712 let mut buf = [0u8; 8];
713 let len = nop(&mut buf, 3).unwrap();
714 assert_eq!(&buf[..len], &[0x83, 0x00]);
715
716 let frame = Frame::parse(&buf[..len]).unwrap();
717 assert_eq!(frame.header.tid(), 3);
718 assert_eq!(frame.command(), Some(Cmd::Nop));
719 assert!(frame.payload.is_empty());
720 }
721
722 #[test]
723 fn prop_get_round_trip() {
724 let mut buf = [0u8; 8];
725 let len = prop_get(&mut buf, 1, prop::PHY_DUTY_LIMIT).unwrap();
726 assert_eq!(&buf[..len], &[0x81, 0x02, 0xD6, 0x25]);
727
728 let frame = Frame::parse(&buf[..len]).unwrap();
729 assert_eq!(frame.command(), Some(Cmd::PropGet));
730 let payload = PropPayload::parse(frame.payload).unwrap();
731 assert_eq!(payload.key, prop::PHY_DUTY_LIMIT);
732 assert!(payload.value.is_empty());
733 }
734
735 #[test]
736 fn prop_set_round_trip() {
737 let mut buf = [0u8; 16];
738 let len = prop_set(&mut buf, 2, prop::PHY_FREQ, &906_875u32.to_le_bytes()).unwrap();
739
740 let frame = Frame::parse(&buf[..len]).unwrap();
741 assert_eq!(frame.header.tid(), 2);
742 assert_eq!(frame.command(), Some(Cmd::PropSet));
743 let payload = PropPayload::parse(frame.payload).unwrap();
744 assert_eq!(payload.key, prop::PHY_FREQ);
745 assert_eq!(payload.value, &906_875u32.to_le_bytes());
746 }
747
748 #[test]
749 fn last_status_frame() {
750 let mut buf = [0u8; 8];
751 let len = last_status(&mut buf, 5, Status::DUTY_LIMIT).unwrap();
752 assert_eq!(&buf[..len], &[0x85, 0x06, 0x00, 0x20]);
753
754 let frame = Frame::parse(&buf[..len]).unwrap();
755 let payload = PropPayload::parse(frame.payload).unwrap();
756 assert_eq!(payload.key, prop::LAST_STATUS);
757 let (code, consumed) = crate::pui::decode(payload.value).unwrap();
758 assert_eq!(Status(code), Status::DUTY_LIMIT);
759 assert_eq!(consumed, payload.value.len());
760 }
761
762 #[test]
763 fn stream_round_trip() {
764 let mut buf = [0u8; 32];
765 let data = [0xDEu8, 0xAD, 0xBE, 0xEF];
766 let meta = [0x7Fu8, 0x00];
767 let len = str_send(&mut buf, 4, stream::PHY_RAW, &data, &meta).unwrap();
768
769 let frame = Frame::parse(&buf[..len]).unwrap();
770 assert_eq!(frame.command(), Some(Cmd::StrSend));
771 let payload = StreamPayload::parse(frame.payload).unwrap();
772 assert_eq!(payload.stream, stream::PHY_RAW);
773 assert_eq!(payload.data, &data);
774 assert_eq!(payload.metadata, &meta);
775 }
776
777 #[test]
778 fn stream_without_metadata() {
779 let mut buf = [0u8; 16];
780 let len = str_recv(&mut buf, stream::PHY_RAW, &[0xAA], &[]).unwrap();
781
782 let frame = Frame::parse(&buf[..len]).unwrap();
783 assert_eq!(frame.header.tid(), TID_UNSOLICITED);
784 let payload = StreamPayload::parse(frame.payload).unwrap();
785 assert_eq!(payload.data, &[0xAA]);
786 assert!(payload.metadata.is_empty());
787 }
788
789 #[test]
790 fn stream_truncated_data() {
791 let payload = [0x71, 0x04, 0x00, 0xAA, 0xBB];
793 assert_eq!(StreamPayload::parse(&payload), Err(ParseError::Truncated));
794 }
795
796 #[test]
797 fn parse_rejects_malformed() {
798 assert_eq!(Frame::parse(&[]), Err(ParseError::Truncated));
799 assert_eq!(Frame::parse(&[0x80]), Err(ParseError::Truncated));
800 assert_eq!(Frame::parse(&[0x00, 0x00]), Err(ParseError::BadFlag));
801 assert_eq!(Frame::parse(&[0x88, 0x00]), Err(ParseError::ReservedBits));
802 assert_eq!(Frame::parse(&[0x80, 0x80]), Err(ParseError::BadCommand));
803 }
804
805 #[test]
806 fn unknown_command_is_well_formed() {
807 let frame = Frame::parse(&[0x81, 0x12]).unwrap();
808 assert_eq!(frame.cmd, 18);
809 assert_eq!(frame.command(), None);
810 }
811
812 #[test]
813 fn every_assigned_command_round_trips() {
814 for id in (0..=17u8).chain(21..=23) {
815 let cmd = Cmd::from_u8(id).unwrap_or_else(|| panic!("command {id} unassigned"));
816 assert_eq!(cmd as u8, id);
817 }
818 for id in (18..=20u8).chain(24..=127) {
819 assert_eq!(Cmd::from_u8(id), None, "command {id} should be unassigned");
820 }
821 }
822
823 #[test]
824 fn insert_remove_round_trip() {
825 let mut buf = [0u8; 80];
826 let item = [0xA5u8; 33];
827 let len = prop_insert(&mut buf, 3, prop::HOST_RX_FILTERS, &item).unwrap();
828 let frame = Frame::parse(&buf[..len]).unwrap();
829 assert_eq!(frame.command(), Some(Cmd::PropInsert));
830 let payload = PropPayload::parse(frame.payload).unwrap();
831 assert_eq!(payload.key, prop::HOST_RX_FILTERS);
832 assert_eq!(payload.value, &item);
833
834 let len = prop_remove(&mut buf, 4, prop::HOST_PEER_KEYS, &item[..32]).unwrap();
835 let frame = Frame::parse(&buf[..len]).unwrap();
836 assert_eq!(frame.command(), Some(Cmd::PropRemove));
837 let payload = PropPayload::parse(frame.payload).unwrap();
838 assert_eq!(payload.key, prop::HOST_PEER_KEYS);
839 assert_eq!(payload.value, &item[..32]);
840 }
841
842 #[test]
843 fn inserted_removed_round_trip() {
844 let mut buf = [0u8; 48];
845 let digest = [0x42u8; 32];
846 let len = prop_inserted(&mut buf, 5, prop::HOST_PEER_KEYS, &digest).unwrap();
847 let frame = Frame::parse(&buf[..len]).unwrap();
848 assert_eq!(frame.command(), Some(Cmd::PropInserted));
849 let payload = PropPayload::parse(frame.payload).unwrap();
850 assert_eq!(payload.value, &digest);
851
852 let len = prop_removed(
853 &mut buf,
854 TID_UNSOLICITED,
855 prop::HOST_CHANNEL_KEYS,
856 &[0x12, 0x34],
857 )
858 .unwrap();
859 let frame = Frame::parse(&buf[..len]).unwrap();
860 assert_eq!(frame.command(), Some(Cmd::PropRemoved));
861 assert_eq!(frame.header.tid(), TID_UNSOLICITED);
862 let payload = PropPayload::parse(frame.payload).unwrap();
863 assert_eq!(payload.key, prop::HOST_CHANNEL_KEYS);
864 assert_eq!(payload.value, &[0x12, 0x34]);
865 }
866
867 #[test]
868 fn payloadless_full_commands() {
869 let mut buf = [0u8; 4];
870 for (encode, cmd) in [
871 (
872 queue_drain as fn(&mut [u8], u8) -> Result<usize, WriteError>,
873 Cmd::QueueDrain,
874 ),
875 (save, Cmd::Save),
876 (clear, Cmd::Clear),
877 (restore, Cmd::Restore),
878 (factory_reset, Cmd::FactoryReset),
879 (reboot, Cmd::Reboot),
880 (ble_clear_bonds, Cmd::BleClearBonds),
881 ] {
882 let len = encode(&mut buf, 2).unwrap();
883 assert_eq!(len, 2);
884 let frame = Frame::parse(&buf[..len]).unwrap();
885 assert_eq!(frame.command(), Some(cmd));
886 assert!(frame.payload.is_empty());
887 }
888 }
889
890 #[test]
891 fn multi_get_round_trip() {
892 let mut buf = [0u8; 32];
893 let keys = [prop::CAPS, prop::PHY_DUTY_LIMIT, prop::DEV_ADMINS];
894 let len = prop_multi_get(&mut buf, 6, &keys).unwrap();
895
896 let frame = Frame::parse(&buf[..len]).unwrap();
897 assert_eq!(frame.header.tid(), 6);
898 assert_eq!(frame.command(), Some(Cmd::PropMultiGet));
899 let decoded: Result<std::vec::Vec<_>, _> = MultiGetKeys::new(frame.payload).collect();
900 assert_eq!(decoded.unwrap(), keys);
901 }
902
903 #[test]
904 fn multi_set_round_trip() {
905 let mut buf = [0u8; 64];
906 let long = [0xA5u8; 32];
907 let entries: [(u32, &[u8]); 3] = [
908 (prop::PHY_FREQ, &906_875u32.to_le_bytes()),
909 (prop::DEV_ADMINS, &long),
910 (prop::PHY_TX_POWER, &[]),
911 ];
912 let len = prop_multi_set(&mut buf, 2, &entries).unwrap();
913
914 let frame = Frame::parse(&buf[..len]).unwrap();
915 assert_eq!(frame.command(), Some(Cmd::PropMultiSet));
916 let decoded: std::vec::Vec<_> = MultiEntries::new(frame.payload)
917 .map(|entry| entry.unwrap())
918 .map(|entry| (entry.key, entry.value))
919 .collect();
920 assert_eq!(decoded, entries);
921 }
922
923 #[test]
924 fn are_carries_values_and_statuses() {
925 let mut buf = [0u8; 32];
926 let mut writer = prop_are(&mut buf, 4).unwrap();
927 writer.write_entry(prop::PHY_TX_POWER, &[14]).unwrap();
928 writer.write_status_entry(Status::PROP_NOT_FOUND).unwrap();
929 let len = writer.finish();
930
931 let frame = Frame::parse(&buf[..len]).unwrap();
932 assert_eq!(frame.header.tid(), 4);
933 assert_eq!(frame.command(), Some(Cmd::PropAre));
934 let entries: std::vec::Vec<_> = MultiEntries::new(frame.payload)
935 .map(|entry| entry.unwrap())
936 .collect();
937 assert_eq!(entries.len(), 2);
938 assert_eq!(entries[0].key, prop::PHY_TX_POWER);
939 assert_eq!(entries[0].value, &[14]);
940 assert_eq!(entries[1].key, prop::LAST_STATUS);
941 let (code, _) = crate::pui::decode(entries[1].value).unwrap();
942 assert_eq!(Status(code), Status::PROP_NOT_FOUND);
943 }
944
945 #[test]
946 fn entry_length_matches_what_is_written() {
947 let mut buf = [0u8; 512];
948 for value_len in [0usize, 1, 125, 126, 127, 300] {
950 let value = std::vec![0x5Au8; value_len];
951 for key in [prop::CAPS, prop::PHY_DUTY_LIMIT] {
952 let mut writer = prop_are(&mut buf, 1).unwrap();
953 let before = writer.len();
954 writer.write_entry(key, &value).unwrap();
955 assert_eq!(
956 writer.len() - before,
957 entry_len(key, value_len).unwrap(),
958 "key {key}, value length {value_len}"
959 );
960 }
961 }
962 }
963
964 #[test]
965 fn entry_that_does_not_fit_leaves_the_frame_intact() {
966 let mut buf = [0u8; 12];
967 let mut writer = prop_are(&mut buf, 1).unwrap();
968 writer.write_entry(prop::PHY_TX_POWER, &[7]).unwrap();
969 let after_first = writer.len();
970 assert_eq!(
971 writer.write_entry(prop::CAPS, &[0u8; 32]),
972 Err(WriteError::BufferTooSmall)
973 );
974 assert_eq!(writer.len(), after_first);
975
976 let len = writer.finish();
977 let frame = Frame::parse(&buf[..len]).unwrap();
978 let entries: std::vec::Vec<_> = MultiEntries::new(frame.payload)
979 .map(|entry| entry.unwrap())
980 .collect();
981 assert_eq!(entries.len(), 1);
982 assert_eq!(entries[0].value, &[7]);
983 }
984
985 #[test]
986 fn malformed_entries_end_the_iteration() {
987 let mut entries = MultiEntries::new(&[0x09, 0x71, 0xAA, 0xBB]);
989 assert_eq!(entries.next(), Some(Err(ParseError::Truncated)));
990 assert_eq!(entries.next(), None);
991
992 let mut entries = MultiEntries::new(&[0x01, 0x80]);
994 assert_eq!(entries.next(), Some(Err(ParseError::Truncated)));
995 assert_eq!(entries.next(), None);
996
997 let mut keys = MultiGetKeys::new(&[0x71, 0x80]);
998 assert_eq!(keys.next(), Some(Ok(113)));
999 assert_eq!(keys.next(), Some(Err(ParseError::Truncated)));
1000 assert_eq!(keys.next(), None);
1001 }
1002
1003 #[test]
1004 fn empty_multi_payloads_yield_nothing() {
1005 assert_eq!(MultiEntries::new(&[]).next(), None);
1006 assert_eq!(MultiGetKeys::new(&[]).next(), None);
1007 }
1008
1009 #[test]
1010 fn writer_reports_overflow() {
1011 let mut buf = [0u8; 3];
1012 assert_eq!(
1013 prop_set(&mut buf, 1, prop::PHY_FREQ, &[0; 8]),
1014 Err(WriteError::BufferTooSmall)
1015 );
1016 }
1017}