summaryrefslogtreecommitdiff
path: root/src/wire/sixlowpan/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/wire/sixlowpan/mod.rs')
-rw-r--r--src/wire/sixlowpan/mod.rs371
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);
+ }
+}