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