umsh_core/
packet.rs

1use core::ops::Range;
2
3use crate::{EncodeError, ParseError, options::OptionDecoder};
4
5/// Current UMSH packet version encoded in the FCF high bits.
6pub const UMSH_VERSION: u8 = 0b11;
7
8/// Packet class encoded in the frame-control field.
9#[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    /// Decode a packet type from the three packet-type bits in the FCF.
24    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    /// Return whether packets of this type carry SECINFO and a MIC.
38    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    /// Return whether this packet type requests a MAC ACK.
50    pub fn ack_requested(self) -> bool {
51        matches!(self, Self::UnicastAckReq | Self::BlindUnicastAckReq)
52    }
53
54    /// Return whether this packet type participates in mesh routing/forwarding.
55    pub fn is_routable(self) -> bool {
56        !matches!(self, Self::Reserved5)
57    }
58}
59
60/// Application payload type carried inside the MAC body.
61///
62/// `Empty` is a special out-of-band value used when the frame carries no
63/// application payload bytes at all, meaning there is no payload-type byte on
64/// the wire. All other variants correspond to the leading typed-payload byte.
65#[derive(Clone, Copy, Debug, PartialEq, Eq)]
66#[repr(u8)]
67pub enum PayloadType {
68    /// No application payload and no payload-type byte on the wire.
69    Empty = 0xFF,
70    /// Explicit application-agnostic payload type byte `0`.
71    Unspecified = 0,
72    /// Node-identity payload.
73    NodeIdentity = 1,
74    /// MAC command payload.
75    MacCommand = 2,
76    /// Text-message payload.
77    TextMessage = 3,
78    /// Chat-room management payload.
79    ChatRoomMessage = 5,
80    /// CoAP-over-UMSH payload.
81    CoapOverUmsh = 7,
82    /// Node-management payload.
83    NodeManagement = 8,
84}
85
86impl PayloadType {
87    /// Convert a raw payload-type byte into a known payload type.
88    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    /// Return whether this payload type is valid for the given MAC packet type.
102    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!(
108                packet_type,
109                PacketType::Unicast
110                    | PacketType::UnicastAckReq
111                    | PacketType::BlindUnicast
112                    | PacketType::BlindUnicastAckReq
113                    | PacketType::Multicast
114            ),
115            Self::TextMessage | Self::CoapOverUmsh | Self::NodeManagement => matches!(
116                packet_type,
117                PacketType::Unicast
118                    | PacketType::UnicastAckReq
119                    | PacketType::BlindUnicast
120                    | PacketType::BlindUnicastAckReq
121                    | PacketType::Multicast
122            ),
123            Self::ChatRoomMessage => matches!(
124                packet_type,
125                PacketType::Unicast
126                    | PacketType::UnicastAckReq
127                    | PacketType::BlindUnicast
128                    | PacketType::BlindUnicastAckReq
129            ),
130        }
131    }
132}
133
134/// Frame-control field wrapper.
135#[derive(Clone, Copy, Debug, PartialEq, Eq)]
136pub struct Fcf(pub u8);
137
138impl Fcf {
139    /// Build an FCF from structured flags.
140    pub const fn new(
141        packet_type: PacketType,
142        full_source: bool,
143        options_present: bool,
144        flood_hops_present: bool,
145    ) -> Self {
146        Self(
147            (UMSH_VERSION << 6)
148                | ((packet_type as u8) << 3)
149                | ((full_source as u8) << 2)
150                | ((options_present as u8) << 1)
151                | flood_hops_present as u8,
152        )
153    }
154
155    /// Return the encoded protocol version.
156    pub const fn version(self) -> u8 {
157        self.0 >> 6
158    }
159
160    /// Return the encoded packet type.
161    pub const fn packet_type(self) -> PacketType {
162        PacketType::from_bits((self.0 >> 3) & 0x07)
163    }
164
165    /// Return whether the source address is the full 32-byte public key.
166    pub const fn full_source(self) -> bool {
167        self.0 & 0x04 != 0
168    }
169
170    /// Return whether an option block is present.
171    pub const fn options_present(self) -> bool {
172        self.0 & 0x02 != 0
173    }
174
175    /// Return whether a flood-hop byte is present.
176    pub const fn flood_hops_present(self) -> bool {
177        self.0 & 0x01 != 0
178    }
179}
180
181/// Security-control field wrapper.
182#[derive(Clone, Copy, Debug, PartialEq, Eq)]
183pub struct Scf(pub u8);
184
185impl Scf {
186    /// Build an SCF from structured flags.
187    pub const fn new(encrypted: bool, mic_size: MicSize, salt_present: bool) -> Self {
188        Self(((encrypted as u8) << 7) | ((mic_size as u8) << 5) | ((salt_present as u8) << 4))
189    }
190
191    /// Return whether the body is encrypted in place.
192    pub const fn encrypted(self) -> bool {
193        self.0 & 0x80 != 0
194    }
195
196    /// Decode the configured MIC size.
197    pub fn mic_size(self) -> Result<MicSize, ParseError> {
198        MicSize::from_bits((self.0 >> 5) & 0x03)
199    }
200
201    /// Return whether a salt field follows the frame counter.
202    pub const fn salt_present(self) -> bool {
203        self.0 & 0x10 != 0
204    }
205
206    /// Return whether the reserved low nibble is valid for the current spec.
207    pub const fn reserved_valid(self) -> bool {
208        self.0 & 0x0F == 0
209    }
210}
211
212/// Authenticator size carried by secured packets.
213#[derive(Clone, Copy, Debug, PartialEq, Eq)]
214pub enum MicSize {
215    Mic4 = 0,
216    Mic8 = 1,
217    Mic12 = 2,
218    Mic16 = 3,
219}
220
221impl MicSize {
222    /// Return the on-wire byte length of this MIC size.
223    pub const fn byte_len(self) -> usize {
224        match self {
225            Self::Mic4 => 4,
226            Self::Mic8 => 8,
227            Self::Mic12 => 12,
228            Self::Mic16 => 16,
229        }
230    }
231
232    /// Decode a MIC size from its two SCF bits.
233    pub fn from_bits(value: u8) -> Result<Self, ParseError> {
234        match value {
235            0 => Ok(Self::Mic4),
236            1 => Ok(Self::Mic8),
237            2 => Ok(Self::Mic12),
238            3 => Ok(Self::Mic16),
239            other => Err(ParseError::InvalidMicSize(other)),
240        }
241    }
242}
243
244/// Combined remaining/accumulated flood-hop counters.
245#[derive(Clone, Copy, Debug, PartialEq, Eq)]
246pub struct FloodHops(pub u8);
247
248impl FloodHops {
249    /// Construct a flood-hop value if both nibbles fit in four bits.
250    pub fn new(remaining: u8, accumulated: u8) -> Option<Self> {
251        if remaining <= 0x0F && accumulated <= 0x0F {
252            Some(Self((remaining << 4) | accumulated))
253        } else {
254            None
255        }
256    }
257
258    /// Remaining forward-hop budget.
259    pub const fn remaining(self) -> u8 {
260        self.0 >> 4
261    }
262
263    /// Number of hops already consumed.
264    pub const fn accumulated(self) -> u8 {
265        self.0 & 0x0F
266    }
267
268    /// Return the next forwarded hop count.
269    pub fn decremented(self) -> Self {
270        let remaining = self.remaining();
271        if remaining == 0 {
272            self
273        } else {
274            Self::new(remaining - 1, self.accumulated().saturating_add(1)).unwrap_or(self)
275        }
276    }
277}
278
279/// Three-byte node hint derived from a public key.
280#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
281pub struct NodeHint(pub [u8; 3]);
282
283impl NodeHint {
284    /// Derive the hint from the first three public-key bytes.
285    pub fn from_public_key(key: &PublicKey) -> Self {
286        Self([key.0[0], key.0[1], key.0[2]])
287    }
288}
289
290/// Two-byte router hint used in learned routes.
291#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
292pub struct RouterHint(pub [u8; 2]);
293
294impl RouterHint {
295    /// Derive the router hint from the first two public-key bytes.
296    pub fn from_public_key(key: &PublicKey) -> Self {
297        Self([key.0[0], key.0[1]])
298    }
299}
300
301/// Two-byte multicast channel identifier.
302#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
303pub struct ChannelId(pub [u8; 2]);
304
305/// Node public key, which also acts as the full network address.
306#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
307pub struct PublicKey(pub [u8; 32]);
308
309impl PublicKey {
310    /// Return the node hint associated with this key.
311    pub fn hint(&self) -> NodeHint {
312        NodeHint::from_public_key(self)
313    }
314
315    /// Return the router hint associated with this key.
316    pub fn router_hint(&self) -> RouterHint {
317        RouterHint::from_public_key(self)
318    }
319}
320
321/// Raw 32-byte multicast channel secret.
322#[derive(Clone, Copy, zeroize::Zeroize)]
323pub struct ChannelKey(pub [u8; 32]);
324
325impl PartialEq for ChannelKey {
326    fn eq(&self, other: &Self) -> bool {
327        self.0 == other.0
328    }
329}
330
331impl Eq for ChannelKey {}
332
333impl core::fmt::Debug for ChannelKey {
334    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
335        f.write_str("ChannelKey([redacted])")
336    }
337}
338
339/// Source address supplied while constructing packets.
340#[derive(Clone, Copy, Debug, PartialEq, Eq)]
341pub enum SourceAddr<'a> {
342    Hint(NodeHint),
343    Full(&'a PublicKey),
344}
345
346impl SourceAddr<'_> {
347    /// Return the hint form of this address.
348    pub fn hint(&self) -> NodeHint {
349        match self {
350            Self::Hint(hint) => *hint,
351            Self::Full(key) => key.hint(),
352        }
353    }
354}
355
356/// Decoded SECINFO structure.
357#[derive(Clone, Copy, Debug, PartialEq, Eq)]
358pub struct SecInfo {
359    pub scf: Scf,
360    pub frame_counter: u32,
361    pub salt: Option<u16>,
362}
363
364impl SecInfo {
365    /// Return the SECINFO on-wire length.
366    pub fn wire_len(&self) -> usize {
367        if self.salt.is_some() { 7 } else { 5 }
368    }
369
370    /// Encode SECINFO into `buf` and return the number of bytes written.
371    pub fn encode(&self, buf: &mut [u8]) -> Result<usize, EncodeError> {
372        let needed = self.wire_len();
373        if buf.len() < needed {
374            return Err(EncodeError::BufferTooSmall);
375        }
376        buf[0] = self.scf.0;
377        buf[1..5].copy_from_slice(&self.frame_counter.to_be_bytes());
378        if let Some(salt) = self.salt {
379            buf[5..7].copy_from_slice(&salt.to_be_bytes());
380        }
381        Ok(needed)
382    }
383
384    /// Decode SECINFO from the start of `buf`.
385    pub fn decode(buf: &[u8]) -> Result<Self, ParseError> {
386        if buf.len() < 5 {
387            return Err(ParseError::Truncated);
388        }
389        let scf = Scf(buf[0]);
390        if !scf.reserved_valid() {
391            return Err(ParseError::InvalidScfReserved);
392        }
393        let salt = if scf.salt_present() {
394            if buf.len() < 7 {
395                return Err(ParseError::Truncated);
396            }
397            Some(u16::from_be_bytes([buf[5], buf[6]]))
398        } else {
399            None
400        };
401        Ok(Self {
402            scf,
403            frame_counter: u32::from_be_bytes([buf[1], buf[2], buf[3], buf[4]]),
404            salt,
405        })
406    }
407}
408
409/// Known packet-option numbers.
410#[derive(Debug, Clone, Copy, PartialEq, Eq)]
411pub enum OptionNumber {
412    RegionCode,
413    TraceRoute,
414    SourceRoute,
415    OperatorCallsign,
416    MinRssi,
417    RouteRetry,
418    StationCallsign,
419    MinSnr,
420    Unknown(u16),
421}
422
423impl OptionNumber {
424    /// Return the numeric option number used on the wire.
425    pub fn as_u16(self) -> u16 {
426        match self {
427            Self::RegionCode => 11,
428            Self::TraceRoute => 2,
429            Self::SourceRoute => 3,
430            Self::OperatorCallsign => 4,
431            Self::MinRssi => 5,
432            Self::RouteRetry => 6,
433            Self::StationCallsign => 7,
434            Self::MinSnr => 9,
435            Self::Unknown(value) => value,
436        }
437    }
438
439    /// Return whether the option is critical when unknown.
440    pub fn is_critical(self) -> bool {
441        self.as_u16() & 1 != 0
442    }
443
444    /// Return whether the option is considered dynamic for AAD purposes.
445    pub fn is_dynamic(self) -> bool {
446        self.as_u16() & 2 != 0
447    }
448}
449
450impl From<u16> for OptionNumber {
451    fn from(value: u16) -> Self {
452        match value {
453            2 => Self::TraceRoute,
454            3 => Self::SourceRoute,
455            4 => Self::OperatorCallsign,
456            5 => Self::MinRssi,
457            6 => Self::RouteRetry,
458            7 => Self::StationCallsign,
459            9 => Self::MinSnr,
460            11 => Self::RegionCode,
461            other => Self::Unknown(other),
462        }
463    }
464}
465
466/// Parsed source-address location used by zero-copy packet processing.
467#[derive(Clone, Copy, Debug, PartialEq, Eq)]
468pub enum SourceAddrRef {
469    Hint(NodeHint),
470    FullKeyAt { offset: usize },
471    Encrypted { offset: usize, len: usize },
472    None,
473}
474
475/// Parsed packet header with borrowed ranges into the original frame.
476#[derive(Clone, Debug, PartialEq, Eq)]
477pub struct PacketHeader {
478    pub fcf: Fcf,
479    pub options_range: Range<usize>,
480    pub flood_hops: Option<FloodHops>,
481    pub dst: Option<NodeHint>,
482    pub channel: Option<ChannelId>,
483    pub ack_dst: Option<NodeHint>,
484    pub source: SourceAddrRef,
485    pub sec_info: Option<SecInfo>,
486    pub body_range: Range<usize>,
487    pub mic_range: Range<usize>,
488    pub total_len: usize,
489}
490
491impl PacketHeader {
492    /// Parse a complete on-wire packet header and compute payload/MIC ranges.
493    pub fn parse(buf: &[u8]) -> Result<Self, ParseError> {
494        if buf.is_empty() {
495            return Err(ParseError::Truncated);
496        }
497
498        let fcf = Fcf(buf[0]);
499        if fcf.version() != UMSH_VERSION {
500            return Err(ParseError::InvalidVersion(fcf.version()));
501        }
502
503        let mut cursor = 1;
504        let options_range = if fcf.options_present() {
505            let len = scan_options_field(&buf[cursor..])?;
506            let range = cursor..cursor + len;
507            cursor += len;
508            range
509        } else {
510            cursor..cursor
511        };
512
513        let flood_hops = if fcf.flood_hops_present() {
514            if cursor >= buf.len() {
515                return Err(ParseError::Truncated);
516            }
517            let fh = FloodHops(buf[cursor]);
518            cursor += 1;
519            Some(fh)
520        } else {
521            None
522        };
523
524        let packet_type = fcf.packet_type();
525        let mut dst = None;
526        let mut channel = None;
527        let mut ack_dst = None;
528        let mut source = SourceAddrRef::None;
529        let mut sec_info = None;
530
531        match packet_type {
532            PacketType::Broadcast => {
533                let src_len = source_len(fcf.full_source());
534                source = if fcf.full_source() {
535                    ensure_len(buf, cursor, 32)?;
536                    SourceAddrRef::FullKeyAt { offset: cursor }
537                } else {
538                    ensure_len(buf, cursor, 3)?;
539                    SourceAddrRef::Hint(NodeHint([buf[cursor], buf[cursor + 1], buf[cursor + 2]]))
540                };
541                cursor += src_len;
542                Ok(Self {
543                    fcf,
544                    options_range,
545                    flood_hops,
546                    dst,
547                    channel,
548                    ack_dst,
549                    source,
550                    sec_info,
551                    body_range: cursor..buf.len(),
552                    mic_range: buf.len()..buf.len(),
553                    total_len: buf.len(),
554                })
555            }
556            PacketType::MacAck => {
557                ensure_len(buf, cursor, 3)?;
558                ack_dst = Some(NodeHint([buf[cursor], buf[cursor + 1], buf[cursor + 2]]));
559                cursor += 3;
560                ensure_len(buf, cursor, 8)?;
561                Ok(Self {
562                    fcf,
563                    options_range,
564                    flood_hops,
565                    dst,
566                    channel,
567                    ack_dst,
568                    source,
569                    sec_info,
570                    body_range: cursor..cursor + 8,
571                    mic_range: cursor..cursor + 8,
572                    total_len: cursor + 8,
573                })
574            }
575            PacketType::Unicast | PacketType::UnicastAckReq => {
576                ensure_len(buf, cursor, 3)?;
577                dst = Some(NodeHint([buf[cursor], buf[cursor + 1], buf[cursor + 2]]));
578                cursor += 3;
579                let src_len = source_len(fcf.full_source());
580                source = if fcf.full_source() {
581                    ensure_len(buf, cursor, 32)?;
582                    SourceAddrRef::FullKeyAt { offset: cursor }
583                } else {
584                    ensure_len(buf, cursor, 3)?;
585                    SourceAddrRef::Hint(NodeHint([buf[cursor], buf[cursor + 1], buf[cursor + 2]]))
586                };
587                cursor += src_len;
588                let parsed_sec = SecInfo::decode(&buf[cursor..])?;
589                let sec_len = parsed_sec.wire_len();
590                sec_info = Some(parsed_sec);
591                cursor += sec_len;
592                let mic_len = parsed_sec.scf.mic_size()?.byte_len();
593                ensure_len(buf, cursor, mic_len)?;
594                let mic_start = buf
595                    .len()
596                    .checked_sub(mic_len)
597                    .ok_or(ParseError::Truncated)?;
598                if mic_start < cursor {
599                    return Err(ParseError::Truncated);
600                }
601                Ok(Self {
602                    fcf,
603                    options_range,
604                    flood_hops,
605                    dst,
606                    channel,
607                    ack_dst,
608                    source,
609                    sec_info,
610                    body_range: cursor..mic_start,
611                    mic_range: mic_start..buf.len(),
612                    total_len: buf.len(),
613                })
614            }
615            PacketType::Multicast => {
616                ensure_len(buf, cursor, 2)?;
617                channel = Some(ChannelId([buf[cursor], buf[cursor + 1]]));
618                cursor += 2;
619                let parsed_sec = SecInfo::decode(&buf[cursor..])?;
620                let sec_len = parsed_sec.wire_len();
621                sec_info = Some(parsed_sec);
622                cursor += sec_len;
623                let mic_len = parsed_sec.scf.mic_size()?.byte_len();
624                let mic_start = buf
625                    .len()
626                    .checked_sub(mic_len)
627                    .ok_or(ParseError::Truncated)?;
628                if mic_start < cursor {
629                    return Err(ParseError::Truncated);
630                }
631                if parsed_sec.scf.encrypted() {
632                    let src_len = source_len(fcf.full_source());
633                    source = SourceAddrRef::Encrypted {
634                        offset: cursor,
635                        len: src_len,
636                    };
637                    Ok(Self {
638                        fcf,
639                        options_range,
640                        flood_hops,
641                        dst,
642                        channel,
643                        ack_dst,
644                        source,
645                        sec_info,
646                        body_range: cursor..mic_start,
647                        mic_range: mic_start..buf.len(),
648                        total_len: buf.len(),
649                    })
650                } else {
651                    let src_len = source_len(fcf.full_source());
652                    source = if fcf.full_source() {
653                        ensure_len(buf, cursor, 32)?;
654                        SourceAddrRef::FullKeyAt { offset: cursor }
655                    } else {
656                        ensure_len(buf, cursor, 3)?;
657                        SourceAddrRef::Hint(NodeHint([
658                            buf[cursor],
659                            buf[cursor + 1],
660                            buf[cursor + 2],
661                        ]))
662                    };
663                    cursor += src_len;
664                    Ok(Self {
665                        fcf,
666                        options_range,
667                        flood_hops,
668                        dst,
669                        channel,
670                        ack_dst,
671                        source,
672                        sec_info,
673                        body_range: cursor..mic_start,
674                        mic_range: mic_start..buf.len(),
675                        total_len: buf.len(),
676                    })
677                }
678            }
679            PacketType::BlindUnicast | PacketType::BlindUnicastAckReq => {
680                ensure_len(buf, cursor, 2)?;
681                channel = Some(ChannelId([buf[cursor], buf[cursor + 1]]));
682                cursor += 2;
683                let parsed_sec = SecInfo::decode(&buf[cursor..])?;
684                let sec_len = parsed_sec.wire_len();
685                sec_info = Some(parsed_sec);
686                cursor += sec_len;
687                let src_len = source_len(fcf.full_source());
688                ensure_len(buf, cursor, 3 + src_len)?;
689                if parsed_sec.scf.encrypted() {
690                    source = SourceAddrRef::Encrypted {
691                        offset: cursor + 3,
692                        len: src_len,
693                    };
694                    cursor += 3 + src_len;
695                } else {
696                    dst = Some(NodeHint([buf[cursor], buf[cursor + 1], buf[cursor + 2]]));
697                    cursor += 3;
698                    source = if fcf.full_source() {
699                        ensure_len(buf, cursor, 32)?;
700                        SourceAddrRef::FullKeyAt { offset: cursor }
701                    } else {
702                        ensure_len(buf, cursor, 3)?;
703                        SourceAddrRef::Hint(NodeHint([
704                            buf[cursor],
705                            buf[cursor + 1],
706                            buf[cursor + 2],
707                        ]))
708                    };
709                    cursor += src_len;
710                }
711                let mic_len = parsed_sec.scf.mic_size()?.byte_len();
712                let mic_start = buf
713                    .len()
714                    .checked_sub(mic_len)
715                    .ok_or(ParseError::Truncated)?;
716                if mic_start < cursor {
717                    return Err(ParseError::Truncated);
718                }
719                Ok(Self {
720                    fcf,
721                    options_range,
722                    flood_hops,
723                    dst,
724                    channel,
725                    ack_dst,
726                    source,
727                    sec_info,
728                    body_range: cursor..mic_start,
729                    mic_range: mic_start..buf.len(),
730                    total_len: buf.len(),
731                })
732            }
733            PacketType::Reserved5 => Err(ParseError::MalformedOption),
734        }
735    }
736
737    /// Convenience accessor for the decoded packet type.
738    pub fn packet_type(&self) -> PacketType {
739        self.fcf.packet_type()
740    }
741
742    /// Return whether the packet requests a MAC ACK.
743    pub fn ack_requested(&self) -> bool {
744        self.packet_type().ack_requested()
745    }
746
747    /// Return whether the parsed packet is a beacon broadcast with empty body.
748    pub fn is_beacon(&self) -> bool {
749        self.packet_type() == PacketType::Broadcast && self.body_range.is_empty()
750    }
751}
752
753#[derive(Clone, Debug, Default, PartialEq, Eq)]
754pub struct ParsedOptions {
755    pub region_code: Option<[u8; 2]>,
756    pub source_route: Option<Range<usize>>,
757    pub trace_route: Option<Range<usize>>,
758    pub min_rssi: Option<i16>,
759    pub min_snr: Option<i8>,
760    pub route_retry: bool,
761    pub has_unknown_critical: bool,
762}
763
764impl ParsedOptions {
765    pub fn extract(buf: &[u8], range: Range<usize>) -> Result<Self, ParseError> {
766        let mut parsed = Self::default();
767        if range.is_empty() {
768            return Ok(parsed);
769        }
770        let options = &buf[range.clone()];
771        for entry in OptionDecoder::new(options) {
772            let (number, value) = entry?;
773            let relative_start = unsafe { value.as_ptr().offset_from(options.as_ptr()) } as usize;
774            let value_start = range.start + relative_start;
775            let value_range = value_start..value_start + value.len();
776            match OptionNumber::from(number) {
777                OptionNumber::RegionCode if value.len() == 2 => {
778                    parsed.region_code = Some([value[0], value[1]]);
779                }
780                OptionNumber::TraceRoute => parsed.trace_route = Some(value_range),
781                OptionNumber::SourceRoute => parsed.source_route = Some(value_range),
782                OptionNumber::RouteRetry if value.is_empty() => parsed.route_retry = true,
783                OptionNumber::MinRssi if value.len() == 2 => {
784                    parsed.min_rssi = Some(i16::from_be_bytes([value[0], value[1]]));
785                }
786                OptionNumber::MinSnr if value.len() == 1 => parsed.min_snr = Some(value[0] as i8),
787                OptionNumber::Unknown(raw) if raw & 1 != 0 => parsed.has_unknown_critical = true,
788                _ => {}
789            }
790        }
791        Ok(parsed)
792    }
793}
794
795pub fn iter_options<'a>(buf: &'a [u8], range: Range<usize>) -> OptionDecoder<'a> {
796    OptionDecoder::new(&buf[range])
797}
798
799pub fn feed_aad(header: &PacketHeader, packet_buf: &[u8], mut sink: impl FnMut(&[u8])) {
800    sink(&packet_buf[..1]);
801    for option in iter_options(packet_buf, header.options_range.clone()) {
802        let Ok((number, value)) = option else {
803            return;
804        };
805        let option_number = OptionNumber::from(number);
806        if option_number.is_dynamic() {
807            continue;
808        }
809        let mut tl = [0u8; 4];
810        tl[..2].copy_from_slice(&number.to_be_bytes());
811        tl[2..].copy_from_slice(&(value.len() as u16).to_be_bytes());
812        sink(&tl);
813        sink(value);
814    }
815
816    if let Some(dst) = header.dst {
817        sink(&dst.0);
818    }
819    if let Some(channel) = header.channel {
820        sink(&channel.0);
821    }
822    match header.source {
823        SourceAddrRef::Hint(hint) => sink(&hint.0),
824        SourceAddrRef::FullKeyAt { offset } => sink(&packet_buf[offset..offset + 32]),
825        SourceAddrRef::Encrypted { .. } | SourceAddrRef::None => {}
826    }
827    if let Some(sec_info) = header.sec_info {
828        let mut buf = [0u8; 7];
829        let Ok(len) = sec_info.encode(&mut buf) else {
830            return;
831        };
832        sink(&buf[..len]);
833    }
834}
835
836fn ensure_len(buf: &[u8], offset: usize, len: usize) -> Result<(), ParseError> {
837    if buf.len() < offset + len {
838        Err(ParseError::Truncated)
839    } else {
840        Ok(())
841    }
842}
843
844fn scan_options_field(data: &[u8]) -> Result<usize, ParseError> {
845    let mut decoder = OptionDecoder::new(data);
846    while let Some(result) = decoder.next() {
847        result?;
848    }
849
850    Ok(data.len() - decoder.remainder().len())
851}
852
853pub(crate) fn source_len(full_source: bool) -> usize {
854    if full_source { 32 } else { 3 }
855}
856
857#[derive(Debug, PartialEq, Eq)]
858pub struct UnsealedPacket<'a> {
859    buf: &'a mut [u8],
860    total_len: usize,
861    body_range: Range<usize>,
862    blind_addr_range: Option<Range<usize>>,
863    mic_range: Range<usize>,
864    sec_info_range: Range<usize>,
865    aad_static_options: Range<usize>,
866}
867
868impl<'a> UnsealedPacket<'a> {
869    pub fn new(
870        buf: &'a mut [u8],
871        total_len: usize,
872        body_range: Range<usize>,
873        blind_addr_range: Option<Range<usize>>,
874        mic_range: Range<usize>,
875        sec_info_range: Range<usize>,
876        aad_static_options: Range<usize>,
877    ) -> Self {
878        Self {
879            buf,
880            total_len,
881            body_range,
882            blind_addr_range,
883            mic_range,
884            sec_info_range,
885            aad_static_options,
886        }
887    }
888
889    pub fn header(&self) -> Result<PacketHeader, ParseError> {
890        PacketHeader::parse(self.as_bytes())
891    }
892
893    pub fn body(&self) -> &[u8] {
894        &self.buf[self.body_range.clone()]
895    }
896
897    pub fn body_mut(&mut self) -> &mut [u8] {
898        &mut self.buf[self.body_range.clone()]
899    }
900
901    pub fn blind_addr_range(&self) -> Option<Range<usize>> {
902        self.blind_addr_range.clone()
903    }
904
905    pub fn blind_addr(&self) -> Option<&[u8]> {
906        let range = self.blind_addr_range.clone()?;
907        Some(&self.buf[range])
908    }
909
910    pub fn mic_slot(&mut self) -> &mut [u8] {
911        &mut self.buf[self.mic_range.clone()]
912    }
913
914    pub fn as_bytes(&self) -> &[u8] {
915        &self.buf[..self.total_len]
916    }
917
918    pub fn as_bytes_mut(&mut self) -> &mut [u8] {
919        &mut self.buf[..self.total_len]
920    }
921
922    pub fn total_len(&self) -> usize {
923        self.total_len
924    }
925
926    pub fn sec_info_range(&self) -> Range<usize> {
927        self.sec_info_range.clone()
928    }
929
930    pub fn aad_static_options(&self) -> Range<usize> {
931        self.aad_static_options.clone()
932    }
933}