blob: 03a5218a0d96d1f9a039c2897e8d146ca93a96df [file] [log] [blame]
//! Implementation of [RFC 6282] which specifies a compression format for IPv6 datagrams over
//! IEEE802.154-based networks.
//!
//! [RFC 6282]: https://datatracker.ietf.org/doc/html/rfc6282
use super::{Error, Result};
use crate::wire::ieee802154::Address as LlAddress;
use crate::wire::ipv6;
use crate::wire::IpProtocol;
pub mod frag;
pub mod iphc;
pub mod nhc;
const ADDRESS_CONTEXT_LENGTH: usize = 8;
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct AddressContext(pub [u8; ADDRESS_CONTEXT_LENGTH]);
/// The representation of an unresolved address. 6LoWPAN compression of IPv6 addresses can be with
/// and without context information. The decompression with context information is not yet
/// implemented.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum UnresolvedAddress<'a> {
WithoutContext(AddressMode<'a>),
WithContext((usize, AddressMode<'a>)),
Reserved,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum AddressMode<'a> {
/// The full address is carried in-line.
FullInline(&'a [u8]),
/// The first 64-bits of the address are elided. The value of those bits
/// is the link-local prefix padded with zeros. The remaining 64 bits are
/// carried in-line.
InLine64bits(&'a [u8]),
/// The first 112 bits of the address are elided. The value of the first
/// 64 bits is the link-local prefix padded with zeros. The following 64 bits
/// are 0000:00ff:fe00:XXXX, where XXXX are the 16 bits carried in-line.
InLine16bits(&'a [u8]),
/// The address is fully elided. The first 64 bits of the address are
/// the link-local prefix padded with zeros. The remaining 64 bits are
/// computed from the encapsulating header (e.g., 802.15.4 or IPv6 source address)
/// as specified in Section 3.2.2.
FullyElided,
/// The address takes the form ffXX::00XX:XXXX:XXXX
Multicast48bits(&'a [u8]),
/// The address takes the form ffXX::00XX:XXXX.
Multicast32bits(&'a [u8]),
/// The address takes the form ff02::00XX.
Multicast8bits(&'a [u8]),
/// The unspecified address.
Unspecified,
NotSupported,
}
const LINK_LOCAL_PREFIX: [u8; 2] = [0xfe, 0x80];
const EUI64_MIDDLE_VALUE: [u8; 2] = [0xff, 0xfe];
impl<'a> UnresolvedAddress<'a> {
pub fn resolve(
self,
ll_address: Option<LlAddress>,
addr_context: &[AddressContext],
) -> Result<ipv6::Address> {
let mut bytes = [0; 16];
let copy_context = |index: usize, bytes: &mut [u8]| -> Result<()> {
if index >= addr_context.len() {
return Err(Error);
}
let context = addr_context[index];
bytes[..ADDRESS_CONTEXT_LENGTH].copy_from_slice(&context.0);
Ok(())
};
match self {
UnresolvedAddress::WithoutContext(mode) => match mode {
AddressMode::FullInline(addr) => Ok(ipv6::Address::from_bytes(addr)),
AddressMode::InLine64bits(inline) => {
bytes[0..2].copy_from_slice(&LINK_LOCAL_PREFIX[..]);
bytes[8..].copy_from_slice(inline);
Ok(ipv6::Address::from_bytes(&bytes[..]))
}
AddressMode::InLine16bits(inline) => {
bytes[0..2].copy_from_slice(&LINK_LOCAL_PREFIX[..]);
bytes[11..13].copy_from_slice(&EUI64_MIDDLE_VALUE[..]);
bytes[14..].copy_from_slice(inline);
Ok(ipv6::Address::from_bytes(&bytes[..]))
}
AddressMode::FullyElided => {
bytes[0..2].copy_from_slice(&LINK_LOCAL_PREFIX[..]);
match ll_address {
Some(LlAddress::Short(ll)) => {
bytes[11..13].copy_from_slice(&EUI64_MIDDLE_VALUE[..]);
bytes[14..].copy_from_slice(&ll);
}
Some(addr @ LlAddress::Extended(_)) => match addr.as_eui_64() {
Some(addr) => bytes[8..].copy_from_slice(&addr),
None => return Err(Error),
},
Some(LlAddress::Absent) => return Err(Error),
None => return Err(Error),
}
Ok(ipv6::Address::from_bytes(&bytes[..]))
}
AddressMode::Multicast48bits(inline) => {
bytes[0] = 0xff;
bytes[1] = inline[0];
bytes[11..].copy_from_slice(&inline[1..][..5]);
Ok(ipv6::Address::from_bytes(&bytes[..]))
}
AddressMode::Multicast32bits(inline) => {
bytes[0] = 0xff;
bytes[1] = inline[0];
bytes[13..].copy_from_slice(&inline[1..][..3]);
Ok(ipv6::Address::from_bytes(&bytes[..]))
}
AddressMode::Multicast8bits(inline) => {
bytes[0] = 0xff;
bytes[1] = 0x02;
bytes[15] = inline[0];
Ok(ipv6::Address::from_bytes(&bytes[..]))
}
_ => Err(Error),
},
UnresolvedAddress::WithContext(mode) => match mode {
(_, AddressMode::Unspecified) => Ok(ipv6::Address::UNSPECIFIED),
(index, AddressMode::InLine64bits(inline)) => {
copy_context(index, &mut bytes[..])?;
bytes[16 - inline.len()..].copy_from_slice(inline);
Ok(ipv6::Address::from_bytes(&bytes[..]))
}
(index, AddressMode::InLine16bits(inline)) => {
copy_context(index, &mut bytes[..])?;
bytes[16 - inline.len()..].copy_from_slice(inline);
Ok(ipv6::Address::from_bytes(&bytes[..]))
}
(index, AddressMode::FullyElided) => {
match ll_address {
Some(LlAddress::Short(ll)) => {
bytes[11..13].copy_from_slice(&EUI64_MIDDLE_VALUE[..]);
bytes[14..].copy_from_slice(&ll);
}
Some(addr @ LlAddress::Extended(_)) => match addr.as_eui_64() {
Some(addr) => bytes[8..].copy_from_slice(&addr),
None => return Err(Error),
},
Some(LlAddress::Absent) => return Err(Error),
None => return Err(Error),
}
copy_context(index, &mut bytes[..])?;
Ok(ipv6::Address::from_bytes(&bytes[..]))
}
_ => Err(Error),
},
UnresolvedAddress::Reserved => Err(Error),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum SixlowpanPacket {
FragmentHeader,
IphcHeader,
}
const DISPATCH_FIRST_FRAGMENT_HEADER: u8 = 0b11000;
const DISPATCH_FRAGMENT_HEADER: u8 = 0b11100;
const DISPATCH_IPHC_HEADER: u8 = 0b011;
const DISPATCH_UDP_HEADER: u8 = 0b11110;
const DISPATCH_EXT_HEADER: u8 = 0b1110;
impl SixlowpanPacket {
/// Returns the type of the 6LoWPAN header.
/// This can either be a fragment header or an IPHC header.
///
/// # Errors
/// Returns `[Error::Unrecognized]` when neither the Fragment Header dispatch or the IPHC
/// dispatch is recognized.
pub fn dispatch(buffer: impl AsRef<[u8]>) -> Result<Self> {
let raw = buffer.as_ref();
if raw.is_empty() {
return Err(Error);
}
if raw[0] >> 3 == DISPATCH_FIRST_FRAGMENT_HEADER || raw[0] >> 3 == DISPATCH_FRAGMENT_HEADER
{
Ok(Self::FragmentHeader)
} else if raw[0] >> 5 == DISPATCH_IPHC_HEADER {
Ok(Self::IphcHeader)
} else {
Err(Error)
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum NextHeader {
Compressed,
Uncompressed(IpProtocol),
}
impl core::fmt::Display for NextHeader {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
NextHeader::Compressed => write!(f, "compressed"),
NextHeader::Uncompressed(protocol) => write!(f, "{protocol}"),
}
}
}
#[cfg(feature = "defmt")]
impl defmt::Format for NextHeader {
fn format(&self, fmt: defmt::Formatter) {
match self {
NextHeader::Compressed => defmt::write!(fmt, "compressed"),
NextHeader::Uncompressed(protocol) => defmt::write!(fmt, "{}", protocol),
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn sixlowpan_fragment_emit() {
let repr = frag::Repr::FirstFragment {
size: 0xff,
tag: 0xabcd,
};
let buffer = [0u8; 4];
let mut packet = frag::Packet::new_unchecked(buffer);
assert_eq!(repr.buffer_len(), 4);
repr.emit(&mut packet);
assert_eq!(packet.datagram_size(), 0xff);
assert_eq!(packet.datagram_tag(), 0xabcd);
assert_eq!(packet.into_inner(), [0xc0, 0xff, 0xab, 0xcd]);
let repr = frag::Repr::Fragment {
size: 0xff,
tag: 0xabcd,
offset: 0xcc,
};
let buffer = [0u8; 5];
let mut packet = frag::Packet::new_unchecked(buffer);
assert_eq!(repr.buffer_len(), 5);
repr.emit(&mut packet);
assert_eq!(packet.datagram_size(), 0xff);
assert_eq!(packet.datagram_tag(), 0xabcd);
assert_eq!(packet.into_inner(), [0xe0, 0xff, 0xab, 0xcd, 0xcc]);
}
#[test]
fn sixlowpan_three_fragments() {
use crate::wire::ieee802154::Frame as Ieee802154Frame;
use crate::wire::ieee802154::Repr as Ieee802154Repr;
use crate::wire::Ieee802154Address;
let key = frag::Key {
ll_src_addr: Ieee802154Address::Extended([50, 147, 130, 47, 40, 8, 62, 217]),
ll_dst_addr: Ieee802154Address::Extended([26, 11, 66, 66, 66, 66, 66, 66]),
datagram_size: 307,
datagram_tag: 63,
};
let frame1: &[u8] = &[
0x41, 0xcc, 0x92, 0xef, 0xbe, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x0b, 0x1a, 0xd9,
0x3e, 0x08, 0x28, 0x2f, 0x82, 0x93, 0x32, 0xc1, 0x33, 0x00, 0x3f, 0x6e, 0x33, 0x02,
0x35, 0x3d, 0xf0, 0xd2, 0x5f, 0x1b, 0x39, 0xb4, 0x6b, 0x4c, 0x6f, 0x72, 0x65, 0x6d,
0x20, 0x69, 0x70, 0x73, 0x75, 0x6d, 0x20, 0x64, 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x73,
0x69, 0x74, 0x20, 0x61, 0x6d, 0x65, 0x74, 0x2c, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x65,
0x63, 0x74, 0x65, 0x74, 0x75, 0x72, 0x20, 0x61, 0x64, 0x69, 0x70, 0x69, 0x73, 0x63,
0x69, 0x6e, 0x67, 0x20, 0x65, 0x6c, 0x69, 0x74, 0x2e, 0x20, 0x41, 0x6c, 0x69, 0x71,
0x75, 0x61, 0x6d, 0x20, 0x64, 0x75, 0x69, 0x20, 0x6f, 0x64, 0x69, 0x6f, 0x2c, 0x20,
0x69, 0x61, 0x63, 0x75, 0x6c, 0x69, 0x73, 0x20, 0x76, 0x65, 0x6c, 0x20, 0x72,
];
let ieee802154_frame = Ieee802154Frame::new_checked(frame1).unwrap();
let ieee802154_repr = Ieee802154Repr::parse(&ieee802154_frame).unwrap();
let sixlowpan_frame =
SixlowpanPacket::dispatch(ieee802154_frame.payload().unwrap()).unwrap();
let frag = if let SixlowpanPacket::FragmentHeader = sixlowpan_frame {
frag::Packet::new_checked(ieee802154_frame.payload().unwrap()).unwrap()
} else {
unreachable!()
};
assert_eq!(frag.datagram_size(), 307);
assert_eq!(frag.datagram_tag(), 0x003f);
assert_eq!(frag.datagram_offset(), 0);
assert_eq!(frag.get_key(&ieee802154_repr), key);
let frame2: &[u8] = &[
0x41, 0xcc, 0x93, 0xef, 0xbe, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x0b, 0x1a, 0xd9,
0x3e, 0x08, 0x28, 0x2f, 0x82, 0x93, 0x32, 0xe1, 0x33, 0x00, 0x3f, 0x11, 0x75, 0x74,
0x72, 0x75, 0x6d, 0x20, 0x61, 0x74, 0x2c, 0x20, 0x74, 0x72, 0x69, 0x73, 0x74, 0x69,
0x71, 0x75, 0x65, 0x20, 0x6e, 0x6f, 0x6e, 0x20, 0x6e, 0x75, 0x6e, 0x63, 0x20, 0x65,
0x72, 0x61, 0x74, 0x20, 0x63, 0x75, 0x72, 0x61, 0x65, 0x2e, 0x20, 0x4c, 0x6f, 0x72,
0x65, 0x6d, 0x20, 0x69, 0x70, 0x73, 0x75, 0x6d, 0x20, 0x64, 0x6f, 0x6c, 0x6f, 0x72,
0x20, 0x73, 0x69, 0x74, 0x20, 0x61, 0x6d, 0x65, 0x74, 0x2c, 0x20, 0x63, 0x6f, 0x6e,
0x73, 0x65, 0x63, 0x74, 0x65, 0x74, 0x75, 0x72, 0x20, 0x61, 0x64, 0x69, 0x70, 0x69,
0x73, 0x63, 0x69, 0x6e, 0x67, 0x20, 0x65, 0x6c, 0x69, 0x74,
];
let ieee802154_frame = Ieee802154Frame::new_checked(frame2).unwrap();
let ieee802154_repr = Ieee802154Repr::parse(&ieee802154_frame).unwrap();
let sixlowpan_frame =
SixlowpanPacket::dispatch(ieee802154_frame.payload().unwrap()).unwrap();
let frag = if let SixlowpanPacket::FragmentHeader = sixlowpan_frame {
frag::Packet::new_checked(ieee802154_frame.payload().unwrap()).unwrap()
} else {
unreachable!()
};
assert_eq!(frag.datagram_size(), 307);
assert_eq!(frag.datagram_tag(), 0x003f);
assert_eq!(frag.datagram_offset(), 136 / 8);
assert_eq!(frag.get_key(&ieee802154_repr), key);
let frame3: &[u8] = &[
0x41, 0xcc, 0x94, 0xef, 0xbe, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x0b, 0x1a, 0xd9,
0x3e, 0x08, 0x28, 0x2f, 0x82, 0x93, 0x32, 0xe1, 0x33, 0x00, 0x3f, 0x1d, 0x2e, 0x20,
0x41, 0x6c, 0x69, 0x71, 0x75, 0x61, 0x6d, 0x20, 0x64, 0x75, 0x69, 0x20, 0x6f, 0x64,
0x69, 0x6f, 0x2c, 0x20, 0x69, 0x61, 0x63, 0x75, 0x6c, 0x69, 0x73, 0x20, 0x76, 0x65,
0x6c, 0x20, 0x72, 0x75, 0x74, 0x72, 0x75, 0x6d, 0x20, 0x61, 0x74, 0x2c, 0x20, 0x74,
0x72, 0x69, 0x73, 0x74, 0x69, 0x71, 0x75, 0x65, 0x20, 0x6e, 0x6f, 0x6e, 0x20, 0x6e,
0x75, 0x6e, 0x63, 0x20, 0x65, 0x72, 0x61, 0x74, 0x20, 0x63, 0x75, 0x72, 0x61, 0x65,
0x2e, 0x20, 0x0a,
];
let ieee802154_frame = Ieee802154Frame::new_checked(frame3).unwrap();
let ieee802154_repr = Ieee802154Repr::parse(&ieee802154_frame).unwrap();
let sixlowpan_frame =
SixlowpanPacket::dispatch(ieee802154_frame.payload().unwrap()).unwrap();
let frag = if let SixlowpanPacket::FragmentHeader = sixlowpan_frame {
frag::Packet::new_checked(ieee802154_frame.payload().unwrap()).unwrap()
} else {
unreachable!()
};
assert_eq!(frag.datagram_size(), 307);
assert_eq!(frag.datagram_tag(), 0x003f);
assert_eq!(frag.datagram_offset(), 232 / 8);
assert_eq!(frag.get_key(&ieee802154_repr), key);
}
}