1use core::ops::Range;
2
3use crate::{EncodeError, ParseError, options::OptionDecoder};
4
5pub const UMSH_VERSION: u8 = 0b11;
7
8#[derive(Clone, Copy, Debug, PartialEq, Eq)]
10#[repr(u8)]
11pub enum PacketType {
12 Broadcast = 0,
13 MacAck = 1,
14 Unicast = 2,
15 UnicastAckReq = 3,
16 Multicast = 4,
17 Reserved5 = 5,
18 BlindUnicast = 6,
19 BlindUnicastAckReq = 7,
20}
21
22impl PacketType {
23 pub const fn from_bits(value: u8) -> Self {
25 match value & 0x07 {
26 0 => Self::Broadcast,
27 1 => Self::MacAck,
28 2 => Self::Unicast,
29 3 => Self::UnicastAckReq,
30 4 => Self::Multicast,
31 5 => Self::Reserved5,
32 6 => Self::BlindUnicast,
33 _ => Self::BlindUnicastAckReq,
34 }
35 }
36
37 pub fn is_secure(self) -> bool {
39 matches!(
40 self,
41 Self::Unicast
42 | Self::UnicastAckReq
43 | Self::Multicast
44 | Self::BlindUnicast
45 | Self::BlindUnicastAckReq
46 )
47 }
48
49 pub fn ack_requested(self) -> bool {
51 matches!(self, Self::UnicastAckReq | Self::BlindUnicastAckReq)
52 }
53
54 pub fn is_routable(self) -> bool {
56 !matches!(self, Self::Reserved5)
57 }
58}
59
60#[derive(Clone, Copy, Debug, PartialEq, Eq)]
66#[repr(u8)]
67pub enum PayloadType {
68 Empty = 0xFF,
70 Unspecified = 0,
72 NodeIdentity = 1,
74 MacCommand = 2,
76 TextMessage = 3,
78 ChatRoomMessage = 5,
80 CoapOverUmsh = 7,
82 NodeManagement = 8,
84}
85
86impl PayloadType {
87 pub fn from_byte(byte: u8) -> Option<Self> {
89 match byte {
90 0 => Some(Self::Unspecified),
91 1 => Some(Self::NodeIdentity),
92 2 => Some(Self::MacCommand),
93 3 => Some(Self::TextMessage),
94 5 => Some(Self::ChatRoomMessage),
95 7 => Some(Self::CoapOverUmsh),
96 8 => Some(Self::NodeManagement),
97 _ => None,
98 }
99 }
100
101 pub fn allowed_for(self, packet_type: PacketType) -> bool {
103 match self {
104 Self::Empty | Self::Unspecified | Self::NodeIdentity => {
105 !matches!(packet_type, PacketType::MacAck)
106 }
107 Self::MacCommand => matches!(
112 packet_type,
113 PacketType::Unicast
114 | PacketType::UnicastAckReq
115 | PacketType::BlindUnicast
116 | PacketType::BlindUnicastAckReq
117 | PacketType::Multicast
118 | PacketType::Broadcast
119 ),
120 Self::TextMessage | Self::CoapOverUmsh => matches!(
121 packet_type,
122 PacketType::Unicast
123 | PacketType::UnicastAckReq
124 | PacketType::BlindUnicast
125 | PacketType::BlindUnicastAckReq
126 | PacketType::Multicast
127 ),
128 Self::NodeManagement | Self::ChatRoomMessage => matches!(
129 packet_type,
130 PacketType::Unicast
131 | PacketType::UnicastAckReq
132 | PacketType::BlindUnicast
133 | PacketType::BlindUnicastAckReq
134 ),
135 }
136 }
137}
138
139#[derive(Clone, Copy, Debug, PartialEq, Eq)]
141pub struct Fcf(pub u8);
142
143impl Fcf {
144 pub const fn new(packet_type: PacketType, full_source: bool, flood_hops_present: bool) -> Self {
146 Self(
147 (UMSH_VERSION << 6)
148 | ((packet_type as u8) << 3)
149 | ((full_source as u8) << 2)
150 | flood_hops_present as u8,
151 )
152 }
153
154 pub const fn version(self) -> u8 {
156 self.0 >> 6
157 }
158
159 pub const fn packet_type(self) -> PacketType {
161 PacketType::from_bits((self.0 >> 3) & 0x07)
162 }
163
164 pub const fn full_source(self) -> bool {
166 self.0 & 0x04 != 0
167 }
168
169 pub const fn reserved_valid(self) -> bool {
171 self.0 & 0x02 == 0
172 }
173
174 pub const fn flood_hops_present(self) -> bool {
176 self.0 & 0x01 != 0
177 }
178
179 pub const fn aad_byte(self) -> u8 {
190 self.0 & !0x01
191 }
192}
193
194#[derive(Clone, Copy, Debug, PartialEq, Eq)]
196pub struct Scf(pub u8);
197
198impl Scf {
199 pub const fn new(encrypted: bool, mic_size: MicSize, salt_present: bool) -> Self {
201 Self(((encrypted as u8) << 7) | ((mic_size as u8) << 5) | ((salt_present as u8) << 4))
202 }
203
204 pub const fn encrypted(self) -> bool {
206 self.0 & 0x80 != 0
207 }
208
209 pub fn mic_size(self) -> Result<MicSize, ParseError> {
211 MicSize::from_bits((self.0 >> 5) & 0x03)
212 }
213
214 pub const fn salt_present(self) -> bool {
216 self.0 & 0x10 != 0
217 }
218
219 pub const fn reserved_valid(self) -> bool {
221 self.0 & 0x0F == 0
222 }
223}
224
225#[derive(Clone, Copy, Debug, PartialEq, Eq)]
227pub enum MicSize {
228 Mic4 = 0,
229 Mic8 = 1,
230 Mic12 = 2,
231 Mic16 = 3,
232}
233
234impl MicSize {
235 pub const fn byte_len(self) -> usize {
237 match self {
238 Self::Mic4 => 4,
239 Self::Mic8 => 8,
240 Self::Mic12 => 12,
241 Self::Mic16 => 16,
242 }
243 }
244
245 pub fn from_bits(value: u8) -> Result<Self, ParseError> {
247 match value {
248 0 => Ok(Self::Mic4),
249 1 => Ok(Self::Mic8),
250 2 => Ok(Self::Mic12),
251 3 => Ok(Self::Mic16),
252 other => Err(ParseError::InvalidMicSize(other)),
253 }
254 }
255}
256
257#[derive(Clone, Copy, Debug, PartialEq, Eq)]
259pub struct FloodHops(pub u8);
260
261impl FloodHops {
262 pub fn new(remaining: u8, accumulated: u8) -> Option<Self> {
264 if remaining <= 0x0F && accumulated <= 0x0F {
265 Some(Self((remaining << 4) | accumulated))
266 } else {
267 None
268 }
269 }
270
271 pub const fn remaining(self) -> u8 {
273 self.0 >> 4
274 }
275
276 pub const fn accumulated(self) -> u8 {
278 self.0 & 0x0F
279 }
280
281 pub fn decremented(self) -> Self {
283 let remaining = self.remaining();
284 if remaining == 0 {
285 self
286 } else {
287 Self::new(remaining - 1, self.accumulated().saturating_add(1)).unwrap_or(self)
288 }
289 }
290}
291
292#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
294pub struct NodeHint(pub [u8; 3]);
295
296impl NodeHint {
297 pub fn from_public_key(key: &PublicKey) -> Self {
299 Self([key.0[0], key.0[1], key.0[2]])
300 }
301}
302
303impl core::fmt::Display for NodeHint {
304 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
307 crate::base58::fmt_hint(f, &self.0, 4)
308 }
309}
310
311#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
313pub struct RouterHint(pub [u8; 2]);
314
315impl RouterHint {
316 pub fn from_public_key(key: &PublicKey) -> Self {
318 Self([key.0[0], key.0[1]])
319 }
320}
321
322impl core::fmt::Display for RouterHint {
323 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
326 crate::base58::fmt_hint(f, &self.0, 3)
327 }
328}
329
330#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
332pub struct ChannelId(pub [u8; 2]);
333
334#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
343pub struct ChannelTag(pub [u8; 16]);
344
345impl ChannelTag {
346 pub fn channel_id(&self) -> ChannelId {
351 ChannelId([self.0[0], self.0[1]])
352 }
353}
354
355#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
357pub struct PublicKey(pub [u8; 32]);
358
359impl PublicKey {
360 pub fn hint(&self) -> NodeHint {
362 NodeHint::from_public_key(self)
363 }
364
365 pub fn router_hint(&self) -> RouterHint {
367 RouterHint::from_public_key(self)
368 }
369}
370
371impl core::fmt::Display for PublicKey {
372 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
374 let encoded = crate::base58::encode(&self.0);
375 f.write_str(core::str::from_utf8(&encoded).map_err(|_| core::fmt::Error)?)
376 }
377}
378
379impl core::fmt::LowerHex for PublicKey {
380 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
381 for byte in self.0 {
382 write!(f, "{byte:02x}")?;
383 }
384 Ok(())
385 }
386}
387
388impl core::fmt::UpperHex for PublicKey {
389 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
390 for byte in self.0 {
391 write!(f, "{byte:02X}")?;
392 }
393 Ok(())
394 }
395}
396
397impl core::str::FromStr for PublicKey {
398 type Err = crate::AddressParseError;
399
400 fn from_str(s: &str) -> Result<Self, Self::Err> {
403 fn hex_value(digit: u8) -> Result<u8, crate::AddressParseError> {
404 match digit {
405 b'0'..=b'9' => Ok(digit - b'0'),
406 b'a'..=b'f' => Ok(digit - b'a' + 10),
407 b'A'..=b'F' => Ok(digit - b'A' + 10),
408 _ => Err(crate::AddressParseError::InvalidCharacter),
409 }
410 }
411
412 let bytes = s.as_bytes();
413 match bytes.len() {
414 crate::base58::ENCODED_LEN => crate::base58::decode(bytes).map(Self),
415 64 => {
416 let mut key = [0u8; 32];
417 for (slot, pair) in key.iter_mut().zip(bytes.chunks_exact(2)) {
418 *slot = (hex_value(pair[0])? << 4) | hex_value(pair[1])?;
419 }
420 Ok(Self(key))
421 }
422 _ => Err(crate::AddressParseError::InvalidLength),
423 }
424 }
425}
426
427#[derive(Clone, Copy, zeroize::Zeroize)]
429pub struct ChannelKey(pub [u8; 32]);
430
431impl PartialEq for ChannelKey {
432 fn eq(&self, other: &Self) -> bool {
433 self.0 == other.0
434 }
435}
436
437impl Eq for ChannelKey {}
438
439impl core::fmt::Debug for ChannelKey {
440 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
441 f.write_str("ChannelKey([redacted])")
442 }
443}
444
445#[derive(Clone, Copy, Debug, PartialEq, Eq)]
447pub enum SourceAddr<'a> {
448 Hint(NodeHint),
449 Full(&'a PublicKey),
450}
451
452impl SourceAddr<'_> {
453 pub fn hint(&self) -> NodeHint {
455 match self {
456 Self::Hint(hint) => *hint,
457 Self::Full(key) => key.hint(),
458 }
459 }
460}
461
462#[derive(Clone, Copy, Debug, PartialEq, Eq)]
464pub struct SecInfo {
465 pub scf: Scf,
466 pub frame_counter: u32,
467 pub salt: Option<u16>,
468}
469
470impl SecInfo {
471 pub fn wire_len(&self) -> usize {
473 if self.salt.is_some() { 7 } else { 5 }
474 }
475
476 pub fn encode(&self, buf: &mut [u8]) -> Result<usize, EncodeError> {
478 let needed = self.wire_len();
479 if buf.len() < needed {
480 return Err(EncodeError::BufferTooSmall);
481 }
482 buf[0] = self.scf.0;
483 buf[1..5].copy_from_slice(&self.frame_counter.to_be_bytes());
484 if let Some(salt) = self.salt {
485 buf[5..7].copy_from_slice(&salt.to_be_bytes());
486 }
487 Ok(needed)
488 }
489
490 pub fn decode(buf: &[u8]) -> Result<Self, ParseError> {
492 if buf.len() < 5 {
493 return Err(ParseError::Truncated);
494 }
495 let scf = Scf(buf[0]);
496 if !scf.reserved_valid() {
497 return Err(ParseError::InvalidScfReserved);
498 }
499 let salt = if scf.salt_present() {
500 if buf.len() < 7 {
501 return Err(ParseError::Truncated);
502 }
503 Some(u16::from_be_bytes([buf[5], buf[6]]))
504 } else {
505 None
506 };
507 Ok(Self {
508 scf,
509 frame_counter: u32::from_be_bytes([buf[1], buf[2], buf[3], buf[4]]),
510 salt,
511 })
512 }
513}
514
515#[derive(Debug, Clone, Copy, PartialEq, Eq)]
517pub enum OptionNumber {
518 RegionCode,
519 TraceRoute,
520 SourceRoute,
521 OperatorCallsign,
522 MinRssi,
523 RouteRetry,
524 StationCallsign,
525 AckMic,
526 MinSnr,
527 TraceSignal,
528 Unknown(u16),
529}
530
531impl OptionNumber {
532 pub fn as_u16(self) -> u16 {
534 match self {
535 Self::RegionCode => 11,
536 Self::TraceRoute => 2,
537 Self::SourceRoute => 3,
538 Self::OperatorCallsign => 4,
539 Self::MinRssi => 5,
540 Self::RouteRetry => 6,
541 Self::StationCallsign => 7,
542 Self::AckMic => 8,
543 Self::MinSnr => 9,
544 Self::TraceSignal => 10,
545 Self::Unknown(value) => value,
546 }
547 }
548
549 pub fn is_critical(self) -> bool {
551 self.as_u16() & 1 != 0
552 }
553
554 pub fn is_dynamic(self) -> bool {
556 self.as_u16() & 2 != 0
557 }
558}
559
560impl From<u16> for OptionNumber {
561 fn from(value: u16) -> Self {
562 match value {
563 2 => Self::TraceRoute,
564 3 => Self::SourceRoute,
565 4 => Self::OperatorCallsign,
566 5 => Self::MinRssi,
567 6 => Self::RouteRetry,
568 7 => Self::StationCallsign,
569 8 => Self::AckMic,
570 9 => Self::MinSnr,
571 10 => Self::TraceSignal,
572 11 => Self::RegionCode,
573 other => Self::Unknown(other),
574 }
575 }
576}
577
578#[derive(Clone, Copy, Debug, PartialEq, Eq)]
580pub enum SourceAddrRef {
581 Hint(NodeHint),
582 FullKeyAt { offset: usize },
583 Encrypted { offset: usize, len: usize },
584 None,
585}
586
587#[derive(Clone, Debug, PartialEq, Eq)]
589pub struct PacketHeader {
590 pub fcf: Fcf,
591 pub options_range: Range<usize>,
592 pub flood_hops: Option<FloodHops>,
593 pub dst: Option<NodeHint>,
594 pub channel: Option<ChannelId>,
595 pub source: SourceAddrRef,
596 pub sec_info: Option<SecInfo>,
597 pub body_range: Range<usize>,
598 pub mic_range: Range<usize>,
599 pub total_len: usize,
600}
601
602impl PacketHeader {
603 pub fn parse(buf: &[u8]) -> Result<Self, ParseError> {
605 if buf.is_empty() {
606 return Err(ParseError::Truncated);
607 }
608
609 let fcf = Fcf(buf[0]);
610 if fcf.version() != UMSH_VERSION {
611 return Err(ParseError::InvalidVersion(fcf.version()));
612 }
613 if !fcf.reserved_valid() {
614 return Err(ParseError::InvalidFcfReserved);
615 }
616
617 let mut cursor = 1;
618 let flood_hops = if fcf.flood_hops_present() {
619 if cursor >= buf.len() {
620 return Err(ParseError::Truncated);
621 }
622 let fh = FloodHops(buf[cursor]);
623 cursor += 1;
624 Some(fh)
625 } else {
626 None
627 };
628
629 let packet_type = fcf.packet_type();
630 let mut dst = None;
631 let mut channel = None;
632 let mut source = SourceAddrRef::None;
633 let mut sec_info = None;
634
635 match packet_type {
636 PacketType::Broadcast => {
637 let src_len = source_len(fcf.full_source());
638 source = if fcf.full_source() {
639 ensure_len(buf, cursor, 32)?;
640 SourceAddrRef::FullKeyAt { offset: cursor }
641 } else {
642 ensure_len(buf, cursor, 3)?;
643 SourceAddrRef::Hint(NodeHint([buf[cursor], buf[cursor + 1], buf[cursor + 2]]))
644 };
645 cursor += src_len;
646 let options_start = cursor;
647 let options_end = buf.len();
648 let (consumed, has_marker) =
649 scan_options_bounded(&buf[options_start..options_end])?;
650 let options_range = options_start..options_start + consumed;
651 let body_start = options_start + consumed;
652 let body_end = if has_marker { buf.len() } else { body_start };
653 Ok(Self {
654 fcf,
655 options_range,
656 flood_hops,
657 dst,
658 channel,
659 source,
660 sec_info,
661 body_range: body_start..body_end,
662 mic_range: buf.len()..buf.len(),
663 total_len: buf.len(),
664 })
665 }
666 PacketType::MacAck => {
667 let options_start = cursor;
671 let options_end = buf.len().checked_sub(8).ok_or(ParseError::Truncated)?;
672 if options_end < options_start {
673 return Err(ParseError::Truncated);
674 }
675 let region = &buf[options_start..options_end];
683 let (consumed, _has_marker) = scan_options_bounded(region)?;
684 if consumed != region.len() {
685 return Err(ParseError::MalformedOption);
686 }
687 let options_range = options_start..options_start + consumed;
688 let trailer_start = options_end;
689 Ok(Self {
690 fcf,
691 options_range,
692 flood_hops,
693 dst,
694 channel,
695 source,
696 sec_info,
697 body_range: trailer_start..trailer_start + 8,
698 mic_range: trailer_start..trailer_start + 8,
699 total_len: trailer_start + 8,
700 })
701 }
702 PacketType::Unicast | PacketType::UnicastAckReq => {
703 ensure_len(buf, cursor, 3)?;
704 dst = Some(NodeHint([buf[cursor], buf[cursor + 1], buf[cursor + 2]]));
705 cursor += 3;
706 let src_len = source_len(fcf.full_source());
707 source = if fcf.full_source() {
708 ensure_len(buf, cursor, 32)?;
709 SourceAddrRef::FullKeyAt { offset: cursor }
710 } else {
711 ensure_len(buf, cursor, 3)?;
712 SourceAddrRef::Hint(NodeHint([buf[cursor], buf[cursor + 1], buf[cursor + 2]]))
713 };
714 cursor += src_len;
715 let parsed_sec = SecInfo::decode(&buf[cursor..])?;
716 let sec_len = parsed_sec.wire_len();
717 sec_info = Some(parsed_sec);
718 cursor += sec_len;
719 let mic_len = parsed_sec.scf.mic_size()?.byte_len();
720 let mic_start = buf
721 .len()
722 .checked_sub(mic_len)
723 .ok_or(ParseError::Truncated)?;
724 if mic_start < cursor {
725 return Err(ParseError::Truncated);
726 }
727 let options_start = cursor;
728 let (consumed, has_marker) = scan_options_bounded(&buf[options_start..mic_start])?;
729 let options_range = options_start..options_start + consumed;
730 let body_start = options_start + consumed;
731 let body_end = if has_marker { mic_start } else { body_start };
732 Ok(Self {
733 fcf,
734 options_range,
735 flood_hops,
736 dst,
737 channel,
738 source,
739 sec_info,
740 body_range: body_start..body_end,
741 mic_range: mic_start..buf.len(),
742 total_len: buf.len(),
743 })
744 }
745 PacketType::Multicast => {
746 ensure_len(buf, cursor, 2)?;
747 channel = Some(ChannelId([buf[cursor], buf[cursor + 1]]));
748 cursor += 2;
749 let parsed_sec = SecInfo::decode(&buf[cursor..])?;
750 let sec_len = parsed_sec.wire_len();
751 sec_info = Some(parsed_sec);
752 cursor += sec_len;
753 let mic_len = parsed_sec.scf.mic_size()?.byte_len();
754 let mic_start = buf
755 .len()
756 .checked_sub(mic_len)
757 .ok_or(ParseError::Truncated)?;
758 if mic_start < cursor {
759 return Err(ParseError::Truncated);
760 }
761 let options_start = cursor;
762 let (consumed, has_marker) = scan_options_bounded(&buf[options_start..mic_start])?;
763 let options_range = options_start..options_start + consumed;
764 cursor = options_start + consumed;
765 if parsed_sec.scf.encrypted() {
766 let src_len = source_len(fcf.full_source());
767 source = SourceAddrRef::Encrypted {
768 offset: cursor,
769 len: src_len,
770 };
771 Ok(Self {
772 fcf,
773 options_range,
774 flood_hops,
775 dst,
776 channel,
777 source,
778 sec_info,
779 body_range: cursor..mic_start,
780 mic_range: mic_start..buf.len(),
781 total_len: buf.len(),
782 })
783 } else {
784 let src_len = source_len(fcf.full_source());
785 source = if fcf.full_source() {
786 ensure_len(buf, cursor, 32)?;
787 SourceAddrRef::FullKeyAt { offset: cursor }
788 } else {
789 ensure_len(buf, cursor, 3)?;
790 SourceAddrRef::Hint(NodeHint([
791 buf[cursor],
792 buf[cursor + 1],
793 buf[cursor + 2],
794 ]))
795 };
796 cursor += src_len;
797 let body_start = cursor;
798 let body_end = if has_marker { mic_start } else { body_start };
799 Ok(Self {
800 fcf,
801 options_range,
802 flood_hops,
803 dst,
804 channel,
805 source,
806 sec_info,
807 body_range: body_start..body_end,
808 mic_range: mic_start..buf.len(),
809 total_len: buf.len(),
810 })
811 }
812 }
813 PacketType::BlindUnicast | PacketType::BlindUnicastAckReq => {
814 ensure_len(buf, cursor, 2)?;
815 channel = Some(ChannelId([buf[cursor], buf[cursor + 1]]));
816 cursor += 2;
817 let parsed_sec = SecInfo::decode(&buf[cursor..])?;
818 let sec_len = parsed_sec.wire_len();
819 sec_info = Some(parsed_sec);
820 cursor += sec_len;
821 let mic_len = parsed_sec.scf.mic_size()?.byte_len();
822 let mic_start = buf
823 .len()
824 .checked_sub(mic_len)
825 .ok_or(ParseError::Truncated)?;
826 if mic_start < cursor {
827 return Err(ParseError::Truncated);
828 }
829 let options_start = cursor;
830 let (consumed, has_marker) = scan_options_bounded(&buf[options_start..mic_start])?;
831 let options_range = options_start..options_start + consumed;
832 cursor = options_start + consumed;
833 let src_len = source_len(fcf.full_source());
834 ensure_len(buf, cursor, 3 + src_len)?;
835 if parsed_sec.scf.encrypted() {
836 source = SourceAddrRef::Encrypted {
837 offset: cursor + 3,
838 len: src_len,
839 };
840 cursor += 3 + src_len;
841 } else {
842 dst = Some(NodeHint([buf[cursor], buf[cursor + 1], buf[cursor + 2]]));
843 cursor += 3;
844 source = if fcf.full_source() {
845 ensure_len(buf, cursor, 32)?;
846 SourceAddrRef::FullKeyAt { offset: cursor }
847 } else {
848 ensure_len(buf, cursor, 3)?;
849 SourceAddrRef::Hint(NodeHint([
850 buf[cursor],
851 buf[cursor + 1],
852 buf[cursor + 2],
853 ]))
854 };
855 cursor += src_len;
856 }
857 let body_start = cursor;
858 let body_end = if has_marker { mic_start } else { body_start };
859 Ok(Self {
860 fcf,
861 options_range,
862 flood_hops,
863 dst,
864 channel,
865 source,
866 sec_info,
867 body_range: body_start..body_end,
868 mic_range: mic_start..buf.len(),
869 total_len: buf.len(),
870 })
871 }
872 PacketType::Reserved5 => Err(ParseError::MalformedOption),
873 }
874 }
875
876 pub fn packet_type(&self) -> PacketType {
878 self.fcf.packet_type()
879 }
880
881 pub fn ack_requested(&self) -> bool {
883 self.packet_type().ack_requested()
884 }
885
886 pub fn is_beacon(&self) -> bool {
888 self.packet_type() == PacketType::Broadcast && self.body_range.is_empty()
889 }
890}
891
892pub const DEFAULT_MIN_RSSI_DBM: i16 = -100;
895
896pub const DEFAULT_MIN_SNR_DB: i8 = -3;
899
900#[derive(Clone, Debug, Default, PartialEq, Eq)]
901pub struct ParsedOptions {
902 pub region_code: Option<[u8; 2]>,
903 pub source_route: Option<Range<usize>>,
904 pub trace_route: Option<Range<usize>>,
905 pub trace_signal: Option<Range<usize>>,
906 pub min_rssi: Option<i16>,
907 pub min_snr: Option<i8>,
908 pub route_retry: bool,
909 pub has_unknown_critical: bool,
910}
911
912impl ParsedOptions {
913 pub fn extract(buf: &[u8], range: Range<usize>) -> Result<Self, ParseError> {
914 let mut parsed = Self::default();
915 if range.is_empty() {
916 return Ok(parsed);
917 }
918 let options = &buf[range.clone()];
919 for entry in OptionDecoder::new(options) {
920 let (number, value) = entry?;
921 let relative_start = unsafe { value.as_ptr().offset_from(options.as_ptr()) } as usize;
922 let value_start = range.start + relative_start;
923 let value_range = value_start..value_start + value.len();
924 match OptionNumber::from(number) {
925 OptionNumber::RegionCode if value.len() == 2 => {
926 parsed.region_code = Some([value[0], value[1]]);
927 }
928 OptionNumber::TraceRoute => parsed.trace_route = Some(value_range),
929 OptionNumber::TraceSignal => parsed.trace_signal = Some(value_range),
930 OptionNumber::SourceRoute => parsed.source_route = Some(value_range),
931 OptionNumber::RouteRetry if value.is_empty() => parsed.route_retry = true,
932 OptionNumber::MinRssi if value.len() <= 1 => {
937 parsed.min_rssi = Some(match value.first() {
938 Some(&byte) => -i16::from(byte),
939 None => DEFAULT_MIN_RSSI_DBM,
940 });
941 }
942 OptionNumber::MinSnr if value.len() <= 1 => {
945 parsed.min_snr = Some(match value.first() {
946 Some(&byte) => byte as i8,
947 None => DEFAULT_MIN_SNR_DB,
948 });
949 }
950 OptionNumber::Unknown(raw) if raw & 1 != 0 => parsed.has_unknown_critical = true,
951 _ => {}
952 }
953 }
954 Ok(parsed)
955 }
956}
957
958#[cfg(test)]
959mod parsed_options_tests {
960 use super::*;
961
962 #[test]
963 fn min_rssi_one_byte_is_negated_dbm() {
964 let buf = [0x51u8, 130];
966 let parsed = ParsedOptions::extract(&buf, 0..buf.len()).unwrap();
967 assert_eq!(parsed.min_rssi, Some(-130));
968 }
969
970 #[test]
971 fn min_rssi_zero_len_selects_default() {
972 let buf = [0x50u8];
974 let parsed = ParsedOptions::extract(&buf, 0..buf.len()).unwrap();
975 assert_eq!(parsed.min_rssi, Some(DEFAULT_MIN_RSSI_DBM));
976 }
977
978 #[test]
979 fn min_snr_one_byte_is_signed_db() {
980 let buf = [0x91u8, 0xFD];
982 let parsed = ParsedOptions::extract(&buf, 0..buf.len()).unwrap();
983 assert_eq!(parsed.min_snr, Some(-3));
984 }
985
986 #[test]
987 fn min_snr_zero_len_selects_default() {
988 let buf = [0x90u8];
990 let parsed = ParsedOptions::extract(&buf, 0..buf.len()).unwrap();
991 assert_eq!(parsed.min_snr, Some(DEFAULT_MIN_SNR_DB));
992 }
993
994 #[test]
995 fn min_rssi_two_bytes_is_ignored() {
996 let buf = [0x52u8, 0x00, 0x82];
998 let parsed = ParsedOptions::extract(&buf, 0..buf.len()).unwrap();
999 assert_eq!(parsed.min_rssi, None);
1000 }
1001}
1002
1003pub fn iter_options<'a>(buf: &'a [u8], range: Range<usize>) -> OptionDecoder<'a> {
1004 OptionDecoder::new(&buf[range])
1005}
1006
1007pub fn feed_aad(header: &PacketHeader, packet_buf: &[u8], mut sink: impl FnMut(&[u8])) {
1008 sink(&[header.fcf.aad_byte()]);
1009 for option in iter_options(packet_buf, header.options_range.clone()) {
1010 let Ok((number, value)) = option else {
1011 return;
1012 };
1013 let option_number = OptionNumber::from(number);
1014 if option_number.is_dynamic() {
1015 continue;
1016 }
1017 let mut tl = [0u8; 4];
1018 tl[..2].copy_from_slice(&number.to_be_bytes());
1019 tl[2..].copy_from_slice(&(value.len() as u16).to_be_bytes());
1020 sink(&tl);
1021 sink(value);
1022 }
1023
1024 if let Some(dst) = header.dst {
1025 sink(&dst.0);
1026 }
1027 if let Some(channel) = header.channel {
1028 sink(&channel.0);
1029 }
1030 match header.source {
1031 SourceAddrRef::Hint(hint) => sink(&hint.0),
1032 SourceAddrRef::FullKeyAt { offset } => sink(&packet_buf[offset..offset + 32]),
1033 SourceAddrRef::Encrypted { .. } | SourceAddrRef::None => {}
1034 }
1035 if let Some(sec_info) = header.sec_info {
1036 let mut buf = [0u8; 7];
1037 let Ok(len) = sec_info.encode(&mut buf) else {
1038 return;
1039 };
1040 sink(&buf[..len]);
1041 }
1042}
1043
1044fn ensure_len(buf: &[u8], offset: usize, len: usize) -> Result<(), ParseError> {
1045 if buf.len() < offset + len {
1046 Err(ParseError::Truncated)
1047 } else {
1048 Ok(())
1049 }
1050}
1051
1052fn scan_options_bounded(data: &[u8]) -> Result<(usize, bool), ParseError> {
1059 let mut pos = 0;
1060 let mut last_number: u16 = 0;
1061 while pos < data.len() {
1062 let first = data[pos];
1063 if first == 0xFF {
1064 return Ok((pos + 1, true));
1065 }
1066 pos += 1;
1067 let delta_nibble = first >> 4;
1068 let len_nibble = first & 0x0F;
1069 let (delta, delta_len) = read_extended(&data[pos..], delta_nibble)?;
1070 pos += delta_len;
1071 let (len, len_len) = read_extended(&data[pos..], len_nibble)?;
1072 pos += len_len;
1073 if pos + len as usize > data.len() {
1074 return Err(ParseError::Truncated);
1075 }
1076 let number = last_number
1077 .checked_add(delta)
1078 .ok_or(ParseError::MalformedOption)?;
1079 pos += len as usize;
1080 last_number = number;
1081 }
1082 Ok((pos, false))
1083}
1084
1085fn read_extended(data: &[u8], nibble: u8) -> Result<(u16, usize), ParseError> {
1086 match nibble {
1087 0..=12 => Ok((nibble as u16, 0)),
1088 13 => {
1089 if data.is_empty() {
1090 return Err(ParseError::Truncated);
1091 }
1092 Ok((data[0] as u16 + 13, 1))
1093 }
1094 14 => {
1095 if data.len() < 2 {
1096 return Err(ParseError::Truncated);
1097 }
1098 Ok((u16::from_be_bytes([data[0], data[1]]) + 269, 2))
1099 }
1100 _ => Err(ParseError::InvalidOptionNibble),
1101 }
1102}
1103
1104pub(crate) fn source_len(full_source: bool) -> usize {
1105 if full_source { 32 } else { 3 }
1106}
1107
1108#[derive(Debug, PartialEq, Eq)]
1109pub struct UnsealedPacket<'a> {
1110 buf: &'a mut [u8],
1111 total_len: usize,
1112 body_range: Range<usize>,
1113 blind_addr_range: Option<Range<usize>>,
1114 mic_range: Range<usize>,
1115 sec_info_range: Range<usize>,
1116 aad_static_options: Range<usize>,
1117}
1118
1119impl<'a> UnsealedPacket<'a> {
1120 pub fn new(
1121 buf: &'a mut [u8],
1122 total_len: usize,
1123 body_range: Range<usize>,
1124 blind_addr_range: Option<Range<usize>>,
1125 mic_range: Range<usize>,
1126 sec_info_range: Range<usize>,
1127 aad_static_options: Range<usize>,
1128 ) -> Self {
1129 Self {
1130 buf,
1131 total_len,
1132 body_range,
1133 blind_addr_range,
1134 mic_range,
1135 sec_info_range,
1136 aad_static_options,
1137 }
1138 }
1139
1140 pub fn header(&self) -> Result<PacketHeader, ParseError> {
1141 PacketHeader::parse(self.as_bytes())
1142 }
1143
1144 pub fn body(&self) -> &[u8] {
1145 &self.buf[self.body_range.clone()]
1146 }
1147
1148 pub fn body_mut(&mut self) -> &mut [u8] {
1149 &mut self.buf[self.body_range.clone()]
1150 }
1151
1152 pub fn blind_addr_range(&self) -> Option<Range<usize>> {
1153 self.blind_addr_range.clone()
1154 }
1155
1156 pub fn blind_addr(&self) -> Option<&[u8]> {
1157 let range = self.blind_addr_range.clone()?;
1158 Some(&self.buf[range])
1159 }
1160
1161 pub fn mic_slot(&mut self) -> &mut [u8] {
1162 &mut self.buf[self.mic_range.clone()]
1163 }
1164
1165 pub fn as_bytes(&self) -> &[u8] {
1166 &self.buf[..self.total_len]
1167 }
1168
1169 pub fn as_bytes_mut(&mut self) -> &mut [u8] {
1170 &mut self.buf[..self.total_len]
1171 }
1172
1173 pub fn total_len(&self) -> usize {
1174 self.total_len
1175 }
1176
1177 pub fn sec_info_range(&self) -> Range<usize> {
1178 self.sec_info_range.clone()
1179 }
1180
1181 pub fn aad_static_options(&self) -> Range<usize> {
1182 self.aad_static_options.clone()
1183 }
1184}
1185
1186#[cfg(test)]
1187mod tests {
1188 use super::{NodeHint, PacketType, PayloadType, PublicKey, RouterHint};
1189 use crate::AddressParseError;
1190
1191 #[test]
1192 fn node_management_rides_only_unicast() {
1193 for packet_type in [
1194 PacketType::Unicast,
1195 PacketType::UnicastAckReq,
1196 PacketType::BlindUnicast,
1197 PacketType::BlindUnicastAckReq,
1198 ] {
1199 assert!(PayloadType::NodeManagement.allowed_for(packet_type));
1200 }
1201 for packet_type in [
1202 PacketType::Multicast,
1203 PacketType::Broadcast,
1204 PacketType::MacAck,
1205 ] {
1206 assert!(!PayloadType::NodeManagement.allowed_for(packet_type));
1207 }
1208 assert!(PayloadType::TextMessage.allowed_for(PacketType::Multicast));
1209 assert!(PayloadType::CoapOverUmsh.allowed_for(PacketType::Multicast));
1210 }
1211
1212 const EXAMPLE_B58: &str = "7NeD1xuPGwZgikCNUaPhmMB13miitwoAEYdzhkvQVu9o";
1214
1215 #[test]
1216 fn public_key_display_matches_reference_vectors() {
1217 assert_eq!(
1218 PublicKey([0u8; 32]).to_string(),
1219 "11111111111111111111111111111111111111111111"
1220 );
1221 assert_eq!(
1222 PublicKey([0xFFu8; 32]).to_string(),
1223 "JEKNVnkbo3jma5nREBBJCDoXFVeKkD56V3xKrvRmWxFG"
1224 );
1225 }
1226
1227 #[test]
1228 fn public_key_parses_base58_and_base16() {
1229 let key: PublicKey = EXAMPLE_B58.parse().unwrap();
1230 assert_eq!(key.to_string(), EXAMPLE_B58);
1231 assert_eq!(key.0[0], 0x5E);
1232
1233 let lower = format!("{key:x}");
1234 let upper = format!("{key:X}");
1235 assert_eq!(lower.len(), 64);
1236 assert_eq!(lower.parse::<PublicKey>().unwrap(), key);
1237 assert_eq!(upper.parse::<PublicKey>().unwrap(), key);
1238 }
1239
1240 #[test]
1241 fn public_key_parse_rejects_bad_input() {
1242 assert_eq!(
1243 "7NeD".parse::<PublicKey>(),
1244 Err(AddressParseError::InvalidLength)
1245 );
1246 let bad_hex = "gg".repeat(32);
1247 assert_eq!(
1248 bad_hex.parse::<PublicKey>(),
1249 Err(AddressParseError::InvalidCharacter)
1250 );
1251 }
1252
1253 #[test]
1254 fn node_hint_display_matches_reference_vectors() {
1255 assert_eq!(NodeHint([0x00, 0x00, 0x00]).to_string(), "1111");
1257 assert_eq!(NodeHint([0xFF, 0xFF, 0xFF]).to_string(), "JEKN");
1258 assert_eq!(NodeHint([0xA1, 0xB2, 0x03]).to_string(), "BtC5");
1259 assert_eq!(NodeHint([0x5E, 0xA1, 0xB2]).to_string(), "7NQL");
1260 assert_eq!(NodeHint([0x84, 0x81, 0x1B]).to_string(), "9v*");
1262 assert_eq!(NodeHint([0xFF, 0xFF, 0x94]).to_string(), "JE*");
1263 }
1264
1265 #[test]
1266 fn router_hint_display_matches_reference_vectors() {
1267 assert_eq!(RouterHint([0x00, 0x00]).to_string(), "111");
1268 assert_eq!(RouterHint([0xA1, 0xB2]).to_string(), "BtC");
1269 assert_eq!(RouterHint([0x5E, 0xA1]).to_string(), "7N*");
1270 assert_eq!(RouterHint([0x84, 0x81]).to_string(), "9v*");
1271 assert_eq!(RouterHint([0x00, 0x41]).to_string(), "1*");
1273 }
1274
1275 #[test]
1276 fn hint_renderings_prefix_full_address() {
1277 for i in 0..=255u32 {
1280 let mut key = [0u8; 32];
1281 for (j, byte) in key.iter_mut().enumerate() {
1282 *byte = (i.wrapping_mul(151).wrapping_add(j as u32 * 91)) as u8;
1283 }
1284 let key = PublicKey(key);
1285 let full = key.to_string();
1286 for rendered in [key.hint().to_string(), key.router_hint().to_string()] {
1287 let verified = rendered.strip_suffix('*').unwrap_or(&rendered);
1288 assert!(
1289 full.starts_with(verified),
1290 "hint {rendered} does not prefix {full}"
1291 );
1292 }
1293 }
1294 }
1295}