//! 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, addr_context: &[AddressContext], ) -> Result { 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 { 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); } }