blob: de457028992cc3238eae60cbb946eaf562682467 [file] [log] [blame]
//! Implementation of the fragment headers from [RFC 4944 § 5.3].
//!
//! [RFC 4944 § 5.3]: https://datatracker.ietf.org/doc/html/rfc4944#section-5.3
use super::{DISPATCH_FIRST_FRAGMENT_HEADER, DISPATCH_FRAGMENT_HEADER};
use crate::wire::{Error, Result};
use crate::wire::{Ieee802154Address, Ieee802154Repr};
use byteorder::{ByteOrder, NetworkEndian};
/// Key used for identifying all the link fragments that belong to the same packet.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Key {
pub(crate) ll_src_addr: Ieee802154Address,
pub(crate) ll_dst_addr: Ieee802154Address,
pub(crate) datagram_size: u16,
pub(crate) datagram_tag: u16,
}
/// A read/write wrapper around a 6LoWPAN Fragment header.
/// [RFC 4944 § 5.3] specifies the format of the header.
///
/// A First Fragment header has the following format:
/// ```txt
/// 1 2 3
/// 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
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// |1 1 0 0 0| datagram_size | datagram_tag |
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// ```
///
/// Subsequent fragment headers have the following format:
/// ```txt
/// 1 2 3
/// 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
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// |1 1 1 0 0| datagram_size | datagram_tag |
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// |datagram_offset|
/// +-+-+-+-+-+-+-+-+
/// ```
///
/// [RFC 4944 § 5.3]: https://datatracker.ietf.org/doc/html/rfc4944#section-5.3
#[derive(Debug, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Packet<T: AsRef<[u8]>> {
buffer: T,
}
pub const FIRST_FRAGMENT_HEADER_SIZE: usize = 4;
pub const NEXT_FRAGMENT_HEADER_SIZE: usize = 5;
mod field {
use crate::wire::field::*;
pub const DISPATCH: usize = 0;
pub const DATAGRAM_SIZE: Field = 0..2;
pub const DATAGRAM_TAG: Field = 2..4;
pub const DATAGRAM_OFFSET: usize = 4;
pub const FIRST_FRAGMENT_REST: Rest = super::FIRST_FRAGMENT_HEADER_SIZE..;
pub const NEXT_FRAGMENT_REST: Rest = super::NEXT_FRAGMENT_HEADER_SIZE..;
}
impl<T: AsRef<[u8]>> Packet<T> {
/// Input a raw octet buffer with a 6LoWPAN Fragment header structure.
pub const fn new_unchecked(buffer: T) -> Self {
Self { buffer }
}
/// Shorthand for a combination of [new_unchecked] and [check_len].
///
/// [new_unchecked]: #method.new_unchecked
/// [check_len]: #method.check_len
pub fn new_checked(buffer: T) -> Result<Self> {
let packet = Self::new_unchecked(buffer);
packet.check_len()?;
let dispatch = packet.dispatch();
if dispatch != DISPATCH_FIRST_FRAGMENT_HEADER && dispatch != DISPATCH_FRAGMENT_HEADER {
return Err(Error);
}
Ok(packet)
}
/// Ensure that no accessor method will panic if called.
/// Returns `Err(Error)` if the buffer is too short.
pub fn check_len(&self) -> Result<()> {
let buffer = self.buffer.as_ref();
if buffer.is_empty() {
return Err(Error);
}
match self.dispatch() {
DISPATCH_FIRST_FRAGMENT_HEADER if buffer.len() >= FIRST_FRAGMENT_HEADER_SIZE => Ok(()),
DISPATCH_FIRST_FRAGMENT_HEADER if buffer.len() < FIRST_FRAGMENT_HEADER_SIZE => {
Err(Error)
}
DISPATCH_FRAGMENT_HEADER if buffer.len() >= NEXT_FRAGMENT_HEADER_SIZE => Ok(()),
DISPATCH_FRAGMENT_HEADER if buffer.len() < NEXT_FRAGMENT_HEADER_SIZE => Err(Error),
_ => Err(Error),
}
}
/// Consumes the frame, returning the underlying buffer.
pub fn into_inner(self) -> T {
self.buffer
}
/// Return the dispatch field.
pub fn dispatch(&self) -> u8 {
let raw = self.buffer.as_ref();
raw[field::DISPATCH] >> 3
}
/// Return the total datagram size.
pub fn datagram_size(&self) -> u16 {
let raw = self.buffer.as_ref();
NetworkEndian::read_u16(&raw[field::DATAGRAM_SIZE]) & 0b111_1111_1111
}
/// Return the datagram tag.
pub fn datagram_tag(&self) -> u16 {
let raw = self.buffer.as_ref();
NetworkEndian::read_u16(&raw[field::DATAGRAM_TAG])
}
/// Return the datagram offset.
pub fn datagram_offset(&self) -> u8 {
match self.dispatch() {
DISPATCH_FIRST_FRAGMENT_HEADER => 0,
DISPATCH_FRAGMENT_HEADER => {
let raw = self.buffer.as_ref();
raw[field::DATAGRAM_OFFSET]
}
_ => unreachable!(),
}
}
/// Returns `true` when this header is from the first fragment of a link.
pub fn is_first_fragment(&self) -> bool {
self.dispatch() == DISPATCH_FIRST_FRAGMENT_HEADER
}
/// Returns the key for identifying the packet it belongs to.
pub fn get_key(&self, ieee802154_repr: &Ieee802154Repr) -> Key {
Key {
ll_src_addr: ieee802154_repr.src_addr.unwrap(),
ll_dst_addr: ieee802154_repr.dst_addr.unwrap(),
datagram_size: self.datagram_size(),
datagram_tag: self.datagram_tag(),
}
}
}
impl<'a, T: AsRef<[u8]> + ?Sized> Packet<&'a T> {
/// Return the payload.
pub fn payload(&self) -> &'a [u8] {
match self.dispatch() {
DISPATCH_FIRST_FRAGMENT_HEADER => {
let raw = self.buffer.as_ref();
&raw[field::FIRST_FRAGMENT_REST]
}
DISPATCH_FRAGMENT_HEADER => {
let raw = self.buffer.as_ref();
&raw[field::NEXT_FRAGMENT_REST]
}
_ => unreachable!(),
}
}
}
impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
fn set_dispatch_field(&mut self, value: u8) {
let raw = self.buffer.as_mut();
raw[field::DISPATCH] = (raw[field::DISPATCH] & !(0b11111 << 3)) | (value << 3);
}
fn set_datagram_size(&mut self, size: u16) {
let raw = self.buffer.as_mut();
let mut v = NetworkEndian::read_u16(&raw[field::DATAGRAM_SIZE]);
v = (v & !0b111_1111_1111) | size;
NetworkEndian::write_u16(&mut raw[field::DATAGRAM_SIZE], v);
}
fn set_datagram_tag(&mut self, tag: u16) {
let raw = self.buffer.as_mut();
NetworkEndian::write_u16(&mut raw[field::DATAGRAM_TAG], tag);
}
fn set_datagram_offset(&mut self, offset: u8) {
let raw = self.buffer.as_mut();
raw[field::DATAGRAM_OFFSET] = offset;
}
}
/// A high-level representation of a 6LoWPAN Fragment header.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum Repr {
FirstFragment { size: u16, tag: u16 },
Fragment { size: u16, tag: u16, offset: u8 },
}
impl core::fmt::Display for Repr {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Repr::FirstFragment { size, tag } => {
write!(f, "FirstFrag size={size} tag={tag}")
}
Repr::Fragment { size, tag, offset } => {
write!(f, "NthFrag size={size} tag={tag} offset={offset}")
}
}
}
}
#[cfg(feature = "defmt")]
impl defmt::Format for Repr {
fn format(&self, fmt: defmt::Formatter) {
match self {
Repr::FirstFragment { size, tag } => {
defmt::write!(fmt, "FirstFrag size={} tag={}", size, tag);
}
Repr::Fragment { size, tag, offset } => {
defmt::write!(fmt, "NthFrag size={} tag={} offset={}", size, tag, offset);
}
}
}
}
impl Repr {
/// Parse a 6LoWPAN Fragment header.
pub fn parse<T: AsRef<[u8]>>(packet: &Packet<T>) -> Result<Self> {
let size = packet.datagram_size();
let tag = packet.datagram_tag();
match packet.dispatch() {
DISPATCH_FIRST_FRAGMENT_HEADER => Ok(Self::FirstFragment { size, tag }),
DISPATCH_FRAGMENT_HEADER => Ok(Self::Fragment {
size,
tag,
offset: packet.datagram_offset(),
}),
_ => Err(Error),
}
}
/// Returns the length of the Fragment header.
pub const fn buffer_len(&self) -> usize {
match self {
Self::FirstFragment { .. } => field::FIRST_FRAGMENT_REST.start,
Self::Fragment { .. } => field::NEXT_FRAGMENT_REST.start,
}
}
/// Emit a high-level representation into a 6LoWPAN Fragment header.
pub fn emit<T: AsRef<[u8]> + AsMut<[u8]>>(&self, packet: &mut Packet<T>) {
match self {
Self::FirstFragment { size, tag } => {
packet.set_dispatch_field(DISPATCH_FIRST_FRAGMENT_HEADER);
packet.set_datagram_size(*size);
packet.set_datagram_tag(*tag);
}
Self::Fragment { size, tag, offset } => {
packet.set_dispatch_field(DISPATCH_FRAGMENT_HEADER);
packet.set_datagram_size(*size);
packet.set_datagram_tag(*tag);
packet.set_datagram_offset(*offset);
}
}
}
}