diff options
Diffstat (limited to 'src/wire/sixlowpan/mod.rs')
-rw-r--r-- | src/wire/sixlowpan/mod.rs | 371 |
1 files changed, 371 insertions, 0 deletions
diff --git a/src/wire/sixlowpan/mod.rs b/src/wire/sixlowpan/mod.rs new file mode 100644 index 0000000..03a5218 --- /dev/null +++ b/src/wire/sixlowpan/mod.rs @@ -0,0 +1,371 @@ +//! 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); + } +} |