smoltcp/wire/sixlowpan/
frag.rs

1//! Implementation of the fragment headers from [RFC 4944 § 5.3].
2//!
3//! [RFC 4944 § 5.3]: https://datatracker.ietf.org/doc/html/rfc4944#section-5.3
4
5use super::{DISPATCH_FIRST_FRAGMENT_HEADER, DISPATCH_FRAGMENT_HEADER};
6use crate::wire::{Error, Result};
7use crate::wire::{Ieee802154Address, Ieee802154Repr};
8use byteorder::{ByteOrder, NetworkEndian};
9
10/// Key used for identifying all the link fragments that belong to the same packet.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
12#[cfg_attr(feature = "defmt", derive(defmt::Format))]
13pub struct Key {
14    pub(crate) ll_src_addr: Ieee802154Address,
15    pub(crate) ll_dst_addr: Ieee802154Address,
16    pub(crate) datagram_size: u16,
17    pub(crate) datagram_tag: u16,
18}
19
20/// A read/write wrapper around a 6LoWPAN Fragment header.
21/// [RFC 4944 § 5.3] specifies the format of the header.
22///
23/// A First Fragment header has the following format:
24/// ```txt
25///                      1                   2                   3
26///  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
27/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
28/// |1 1 0 0 0|    datagram_size    |         datagram_tag          |
29/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
30/// ```
31///
32/// Subsequent fragment headers have the following format:
33/// ```txt
34///                      1                   2                   3
35///  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
36/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
37/// |1 1 1 0 0|    datagram_size    |         datagram_tag          |
38/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
39/// |datagram_offset|
40/// +-+-+-+-+-+-+-+-+
41/// ```
42///
43/// [RFC 4944 § 5.3]: https://datatracker.ietf.org/doc/html/rfc4944#section-5.3
44#[derive(Debug, Clone)]
45#[cfg_attr(feature = "defmt", derive(defmt::Format))]
46pub struct Packet<T: AsRef<[u8]>> {
47    buffer: T,
48}
49
50pub const FIRST_FRAGMENT_HEADER_SIZE: usize = 4;
51pub const NEXT_FRAGMENT_HEADER_SIZE: usize = 5;
52
53mod field {
54    use crate::wire::field::*;
55
56    pub const DISPATCH: usize = 0;
57    pub const DATAGRAM_SIZE: Field = 0..2;
58    pub const DATAGRAM_TAG: Field = 2..4;
59    pub const DATAGRAM_OFFSET: usize = 4;
60
61    pub const FIRST_FRAGMENT_REST: Rest = super::FIRST_FRAGMENT_HEADER_SIZE..;
62    pub const NEXT_FRAGMENT_REST: Rest = super::NEXT_FRAGMENT_HEADER_SIZE..;
63}
64
65impl<T: AsRef<[u8]>> Packet<T> {
66    /// Input a raw octet buffer with a 6LoWPAN Fragment header structure.
67    pub const fn new_unchecked(buffer: T) -> Self {
68        Self { buffer }
69    }
70
71    /// Shorthand for a combination of [new_unchecked] and [check_len].
72    ///
73    /// [new_unchecked]: #method.new_unchecked
74    /// [check_len]: #method.check_len
75    pub fn new_checked(buffer: T) -> Result<Self> {
76        let packet = Self::new_unchecked(buffer);
77        packet.check_len()?;
78
79        let dispatch = packet.dispatch();
80
81        if dispatch != DISPATCH_FIRST_FRAGMENT_HEADER && dispatch != DISPATCH_FRAGMENT_HEADER {
82            return Err(Error);
83        }
84
85        Ok(packet)
86    }
87
88    /// Ensure that no accessor method will panic if called.
89    /// Returns `Err(Error)` if the buffer is too short.
90    pub fn check_len(&self) -> Result<()> {
91        let buffer = self.buffer.as_ref();
92        if buffer.is_empty() {
93            return Err(Error);
94        }
95
96        match self.dispatch() {
97            DISPATCH_FIRST_FRAGMENT_HEADER if buffer.len() >= FIRST_FRAGMENT_HEADER_SIZE => Ok(()),
98            DISPATCH_FIRST_FRAGMENT_HEADER if buffer.len() < FIRST_FRAGMENT_HEADER_SIZE => {
99                Err(Error)
100            }
101            DISPATCH_FRAGMENT_HEADER if buffer.len() >= NEXT_FRAGMENT_HEADER_SIZE => Ok(()),
102            DISPATCH_FRAGMENT_HEADER if buffer.len() < NEXT_FRAGMENT_HEADER_SIZE => Err(Error),
103            _ => Err(Error),
104        }
105    }
106
107    /// Consumes the frame, returning the underlying buffer.
108    pub fn into_inner(self) -> T {
109        self.buffer
110    }
111
112    /// Return the dispatch field.
113    pub fn dispatch(&self) -> u8 {
114        let raw = self.buffer.as_ref();
115        raw[field::DISPATCH] >> 3
116    }
117
118    /// Return the total datagram size.
119    pub fn datagram_size(&self) -> u16 {
120        let raw = self.buffer.as_ref();
121        NetworkEndian::read_u16(&raw[field::DATAGRAM_SIZE]) & 0b111_1111_1111
122    }
123
124    /// Return the datagram tag.
125    pub fn datagram_tag(&self) -> u16 {
126        let raw = self.buffer.as_ref();
127        NetworkEndian::read_u16(&raw[field::DATAGRAM_TAG])
128    }
129
130    /// Return the datagram offset.
131    pub fn datagram_offset(&self) -> u8 {
132        match self.dispatch() {
133            DISPATCH_FIRST_FRAGMENT_HEADER => 0,
134            DISPATCH_FRAGMENT_HEADER => {
135                let raw = self.buffer.as_ref();
136                raw[field::DATAGRAM_OFFSET]
137            }
138            _ => unreachable!(),
139        }
140    }
141
142    /// Returns `true` when this header is from the first fragment of a link.
143    pub fn is_first_fragment(&self) -> bool {
144        self.dispatch() == DISPATCH_FIRST_FRAGMENT_HEADER
145    }
146
147    /// Returns the key for identifying the packet it belongs to.
148    pub fn get_key(&self, ieee802154_repr: &Ieee802154Repr) -> Key {
149        Key {
150            ll_src_addr: ieee802154_repr.src_addr.unwrap(),
151            ll_dst_addr: ieee802154_repr.dst_addr.unwrap(),
152            datagram_size: self.datagram_size(),
153            datagram_tag: self.datagram_tag(),
154        }
155    }
156}
157
158impl<'a, T: AsRef<[u8]> + ?Sized> Packet<&'a T> {
159    /// Return the payload.
160    pub fn payload(&self) -> &'a [u8] {
161        match self.dispatch() {
162            DISPATCH_FIRST_FRAGMENT_HEADER => {
163                let raw = self.buffer.as_ref();
164                &raw[field::FIRST_FRAGMENT_REST]
165            }
166            DISPATCH_FRAGMENT_HEADER => {
167                let raw = self.buffer.as_ref();
168                &raw[field::NEXT_FRAGMENT_REST]
169            }
170            _ => unreachable!(),
171        }
172    }
173}
174
175impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
176    fn set_dispatch_field(&mut self, value: u8) {
177        let raw = self.buffer.as_mut();
178        raw[field::DISPATCH] = (raw[field::DISPATCH] & !(0b11111 << 3)) | (value << 3);
179    }
180
181    fn set_datagram_size(&mut self, size: u16) {
182        let raw = self.buffer.as_mut();
183        let mut v = NetworkEndian::read_u16(&raw[field::DATAGRAM_SIZE]);
184        v = (v & !0b111_1111_1111) | size;
185
186        NetworkEndian::write_u16(&mut raw[field::DATAGRAM_SIZE], v);
187    }
188
189    fn set_datagram_tag(&mut self, tag: u16) {
190        let raw = self.buffer.as_mut();
191        NetworkEndian::write_u16(&mut raw[field::DATAGRAM_TAG], tag);
192    }
193
194    fn set_datagram_offset(&mut self, offset: u8) {
195        let raw = self.buffer.as_mut();
196        raw[field::DATAGRAM_OFFSET] = offset;
197    }
198}
199
200/// A high-level representation of a 6LoWPAN Fragment header.
201#[derive(Debug, PartialEq, Eq, Clone, Copy)]
202pub enum Repr {
203    FirstFragment { size: u16, tag: u16 },
204    Fragment { size: u16, tag: u16, offset: u8 },
205}
206
207impl core::fmt::Display for Repr {
208    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
209        match self {
210            Repr::FirstFragment { size, tag } => {
211                write!(f, "FirstFrag size={size} tag={tag}")
212            }
213            Repr::Fragment { size, tag, offset } => {
214                write!(f, "NthFrag size={size} tag={tag} offset={offset}")
215            }
216        }
217    }
218}
219
220#[cfg(feature = "defmt")]
221impl defmt::Format for Repr {
222    fn format(&self, fmt: defmt::Formatter) {
223        match self {
224            Repr::FirstFragment { size, tag } => {
225                defmt::write!(fmt, "FirstFrag size={} tag={}", size, tag);
226            }
227            Repr::Fragment { size, tag, offset } => {
228                defmt::write!(fmt, "NthFrag size={} tag={} offset={}", size, tag, offset);
229            }
230        }
231    }
232}
233
234impl Repr {
235    /// Parse a 6LoWPAN Fragment header.
236    pub fn parse<T: AsRef<[u8]>>(packet: &Packet<T>) -> Result<Self> {
237        packet.check_len()?;
238        let size = packet.datagram_size();
239        let tag = packet.datagram_tag();
240
241        match packet.dispatch() {
242            DISPATCH_FIRST_FRAGMENT_HEADER => Ok(Self::FirstFragment { size, tag }),
243            DISPATCH_FRAGMENT_HEADER => Ok(Self::Fragment {
244                size,
245                tag,
246                offset: packet.datagram_offset(),
247            }),
248            _ => Err(Error),
249        }
250    }
251
252    /// Returns the length of the Fragment header.
253    pub const fn buffer_len(&self) -> usize {
254        match self {
255            Self::FirstFragment { .. } => field::FIRST_FRAGMENT_REST.start,
256            Self::Fragment { .. } => field::NEXT_FRAGMENT_REST.start,
257        }
258    }
259
260    /// Emit a high-level representation into a 6LoWPAN Fragment header.
261    pub fn emit<T: AsRef<[u8]> + AsMut<[u8]>>(&self, packet: &mut Packet<T>) {
262        match self {
263            Self::FirstFragment { size, tag } => {
264                packet.set_dispatch_field(DISPATCH_FIRST_FRAGMENT_HEADER);
265                packet.set_datagram_size(*size);
266                packet.set_datagram_tag(*tag);
267            }
268            Self::Fragment { size, tag, offset } => {
269                packet.set_dispatch_field(DISPATCH_FRAGMENT_HEADER);
270                packet.set_datagram_size(*size);
271                packet.set_datagram_tag(*tag);
272                packet.set_datagram_offset(*offset);
273            }
274        }
275    }
276}