|
@@ -1,2463 +0,0 @@
|
|
|
-//! 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;
|
|
|
-
|
|
|
-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)
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-pub mod frag {
|
|
|
- //! 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);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-#[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),
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-pub mod iphc {
|
|
|
- //! Implementation of IP Header Compression from [RFC 6282 § 3.1].
|
|
|
- //! It defines the compression of IPv6 headers.
|
|
|
- //!
|
|
|
- //! [RFC 6282 § 3.1]: https://datatracker.ietf.org/doc/html/rfc6282#section-3.1
|
|
|
-
|
|
|
- use super::{
|
|
|
- AddressContext, AddressMode, Error, NextHeader, Result, UnresolvedAddress,
|
|
|
- DISPATCH_IPHC_HEADER,
|
|
|
- };
|
|
|
- use crate::wire::{ieee802154::Address as LlAddress, ipv6, IpProtocol};
|
|
|
- use byteorder::{ByteOrder, NetworkEndian};
|
|
|
-
|
|
|
- mod field {
|
|
|
- use crate::wire::field::*;
|
|
|
-
|
|
|
- pub const IPHC_FIELD: Field = 0..2;
|
|
|
- }
|
|
|
-
|
|
|
- macro_rules! get_field {
|
|
|
- ($name:ident, $mask:expr, $shift:expr) => {
|
|
|
- fn $name(&self) -> u8 {
|
|
|
- let data = self.buffer.as_ref();
|
|
|
- let raw = NetworkEndian::read_u16(&data[field::IPHC_FIELD]);
|
|
|
- ((raw >> $shift) & $mask) as u8
|
|
|
- }
|
|
|
- };
|
|
|
- }
|
|
|
-
|
|
|
- macro_rules! set_field {
|
|
|
- ($name:ident, $mask:expr, $shift:expr) => {
|
|
|
- fn $name(&mut self, val: u8) {
|
|
|
- let data = &mut self.buffer.as_mut()[field::IPHC_FIELD];
|
|
|
- let mut raw = NetworkEndian::read_u16(data);
|
|
|
-
|
|
|
- raw = (raw & !($mask << $shift)) | ((val as u16) << $shift);
|
|
|
- NetworkEndian::write_u16(data, raw);
|
|
|
- }
|
|
|
- };
|
|
|
- }
|
|
|
-
|
|
|
- /// A read/write wrapper around a 6LoWPAN IPHC header.
|
|
|
- /// [RFC 6282 § 3.1] specifies the format of the header.
|
|
|
- ///
|
|
|
- /// The header always start with the following base format (from [RFC 6282 § 3.1.1]):
|
|
|
- /// ```txt
|
|
|
- /// 0 1
|
|
|
- /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
|
|
|
- /// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
|
- /// | 0 | 1 | 1 | TF |NH | HLIM |CID|SAC| SAM | M |DAC| DAM |
|
|
|
- /// +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|
|
|
- /// ```
|
|
|
- /// With:
|
|
|
- /// - TF: Traffic Class and Flow Label
|
|
|
- /// - NH: Next Header
|
|
|
- /// - HLIM: Hop Limit
|
|
|
- /// - CID: Context Identifier Extension
|
|
|
- /// - SAC: Source Address Compression
|
|
|
- /// - SAM: Source Address Mode
|
|
|
- /// - M: Multicast Compression
|
|
|
- /// - DAC: Destination Address Compression
|
|
|
- /// - DAM: Destination Address Mode
|
|
|
- ///
|
|
|
- /// Depending on the flags in the base format, the following fields are added to the header:
|
|
|
- /// - Traffic Class and Flow Label
|
|
|
- /// - Next Header
|
|
|
- /// - Hop Limit
|
|
|
- /// - IPv6 source address
|
|
|
- /// - IPv6 destinatino address
|
|
|
- ///
|
|
|
- /// [RFC 6282 § 3.1]: https://datatracker.ietf.org/doc/html/rfc6282#section-3.1
|
|
|
- /// [RFC 6282 § 3.1.1]: https://datatracker.ietf.org/doc/html/rfc6282#section-3.1.1
|
|
|
- #[derive(Debug, Clone)]
|
|
|
- #[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
|
|
- pub struct Packet<T: AsRef<[u8]>> {
|
|
|
- buffer: T,
|
|
|
- }
|
|
|
-
|
|
|
- impl<T: AsRef<[u8]>> Packet<T> {
|
|
|
- /// Input a raw octet buffer with a 6LoWPAN IPHC header structure.
|
|
|
- pub const fn new_unchecked(buffer: T) -> Self {
|
|
|
- Packet { 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()?;
|
|
|
- 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.len() < 2 {
|
|
|
- return Err(Error);
|
|
|
- }
|
|
|
-
|
|
|
- let mut offset = self.ip_fields_start()
|
|
|
- + self.traffic_class_size()
|
|
|
- + self.next_header_size()
|
|
|
- + self.hop_limit_size();
|
|
|
- offset += self.src_address_size();
|
|
|
- offset += self.dst_address_size();
|
|
|
-
|
|
|
- if offset as usize > buffer.len() {
|
|
|
- return Err(Error);
|
|
|
- }
|
|
|
-
|
|
|
- Ok(())
|
|
|
- }
|
|
|
-
|
|
|
- /// Consumes the frame, returning the underlying buffer.
|
|
|
- pub fn into_inner(self) -> T {
|
|
|
- self.buffer
|
|
|
- }
|
|
|
-
|
|
|
- /// Return the Next Header field.
|
|
|
- pub fn next_header(&self) -> NextHeader {
|
|
|
- let nh = self.nh_field();
|
|
|
-
|
|
|
- if nh == 1 {
|
|
|
- // The next header field is compressed.
|
|
|
- // It is also encoded using LOWPAN_NHC.
|
|
|
- NextHeader::Compressed
|
|
|
- } else {
|
|
|
- // The full 8 bits for Next Header are carried in-line.
|
|
|
- let start = (self.ip_fields_start() + self.traffic_class_size()) as usize;
|
|
|
-
|
|
|
- let data = self.buffer.as_ref();
|
|
|
- let nh = data[start..start + 1][0];
|
|
|
- NextHeader::Uncompressed(IpProtocol::from(nh))
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// Return the Hop Limit.
|
|
|
- pub fn hop_limit(&self) -> u8 {
|
|
|
- match self.hlim_field() {
|
|
|
- 0b00 => {
|
|
|
- let start = (self.ip_fields_start()
|
|
|
- + self.traffic_class_size()
|
|
|
- + self.next_header_size()) as usize;
|
|
|
-
|
|
|
- let data = self.buffer.as_ref();
|
|
|
- data[start..start + 1][0]
|
|
|
- }
|
|
|
- 0b01 => 1,
|
|
|
- 0b10 => 64,
|
|
|
- 0b11 => 255,
|
|
|
- _ => unreachable!(),
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// Return the Source Context Identifier.
|
|
|
- pub fn src_context_id(&self) -> Option<u8> {
|
|
|
- if self.cid_field() == 1 {
|
|
|
- let data = self.buffer.as_ref();
|
|
|
- Some(data[2] >> 4)
|
|
|
- } else {
|
|
|
- None
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// Return the Destination Context Identifier.
|
|
|
- pub fn dst_context_id(&self) -> Option<u8> {
|
|
|
- if self.cid_field() == 1 {
|
|
|
- let data = self.buffer.as_ref();
|
|
|
- Some(data[2] & 0x0f)
|
|
|
- } else {
|
|
|
- None
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// Return the ECN field (when it is inlined).
|
|
|
- pub fn ecn_field(&self) -> Option<u8> {
|
|
|
- match self.tf_field() {
|
|
|
- 0b00 | 0b01 | 0b10 => {
|
|
|
- let start = self.ip_fields_start() as usize;
|
|
|
- Some(self.buffer.as_ref()[start..][0] & 0b1100_0000)
|
|
|
- }
|
|
|
- 0b11 => None,
|
|
|
- _ => unreachable!(),
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// Return the DSCP field (when it is inlined).
|
|
|
- pub fn dscp_field(&self) -> Option<u8> {
|
|
|
- match self.tf_field() {
|
|
|
- 0b00 | 0b10 => {
|
|
|
- let start = self.ip_fields_start() as usize;
|
|
|
- Some(self.buffer.as_ref()[start..][0] & 0b111111)
|
|
|
- }
|
|
|
- 0b01 | 0b11 => None,
|
|
|
- _ => unreachable!(),
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// Return the flow label field (when it is inlined).
|
|
|
- pub fn flow_label_field(&self) -> Option<u16> {
|
|
|
- match self.tf_field() {
|
|
|
- 0b00 => {
|
|
|
- let start = self.ip_fields_start() as usize;
|
|
|
- Some(NetworkEndian::read_u16(
|
|
|
- &self.buffer.as_ref()[start..][2..4],
|
|
|
- ))
|
|
|
- }
|
|
|
- 0b01 => {
|
|
|
- let start = self.ip_fields_start() as usize;
|
|
|
- Some(NetworkEndian::read_u16(
|
|
|
- &self.buffer.as_ref()[start..][1..3],
|
|
|
- ))
|
|
|
- }
|
|
|
- 0b10 | 0b11 => None,
|
|
|
- _ => unreachable!(),
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// Return the Source Address.
|
|
|
- pub fn src_addr(&self) -> Result<UnresolvedAddress> {
|
|
|
- let start = (self.ip_fields_start()
|
|
|
- + self.traffic_class_size()
|
|
|
- + self.next_header_size()
|
|
|
- + self.hop_limit_size()) as usize;
|
|
|
-
|
|
|
- let data = self.buffer.as_ref();
|
|
|
- match (self.sac_field(), self.sam_field()) {
|
|
|
- (0, 0b00) => Ok(UnresolvedAddress::WithoutContext(AddressMode::FullInline(
|
|
|
- &data[start..][..16],
|
|
|
- ))),
|
|
|
- (0, 0b01) => Ok(UnresolvedAddress::WithoutContext(
|
|
|
- AddressMode::InLine64bits(&data[start..][..8]),
|
|
|
- )),
|
|
|
- (0, 0b10) => Ok(UnresolvedAddress::WithoutContext(
|
|
|
- AddressMode::InLine16bits(&data[start..][..2]),
|
|
|
- )),
|
|
|
- (0, 0b11) => Ok(UnresolvedAddress::WithoutContext(AddressMode::FullyElided)),
|
|
|
- (1, 0b00) => Ok(UnresolvedAddress::WithContext((
|
|
|
- 0,
|
|
|
- AddressMode::Unspecified,
|
|
|
- ))),
|
|
|
- (1, 0b01) => {
|
|
|
- if let Some(id) = self.src_context_id() {
|
|
|
- Ok(UnresolvedAddress::WithContext((
|
|
|
- id as usize,
|
|
|
- AddressMode::InLine64bits(&data[start..][..8]),
|
|
|
- )))
|
|
|
- } else {
|
|
|
- Err(Error)
|
|
|
- }
|
|
|
- }
|
|
|
- (1, 0b10) => {
|
|
|
- if let Some(id) = self.src_context_id() {
|
|
|
- Ok(UnresolvedAddress::WithContext((
|
|
|
- id as usize,
|
|
|
- AddressMode::InLine16bits(&data[start..][..2]),
|
|
|
- )))
|
|
|
- } else {
|
|
|
- Err(Error)
|
|
|
- }
|
|
|
- }
|
|
|
- (1, 0b11) => {
|
|
|
- if let Some(id) = self.src_context_id() {
|
|
|
- Ok(UnresolvedAddress::WithContext((
|
|
|
- id as usize,
|
|
|
- AddressMode::FullyElided,
|
|
|
- )))
|
|
|
- } else {
|
|
|
- Err(Error)
|
|
|
- }
|
|
|
- }
|
|
|
- _ => Err(Error),
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// Return the Destination Address.
|
|
|
- pub fn dst_addr(&self) -> Result<UnresolvedAddress> {
|
|
|
- let start = (self.ip_fields_start()
|
|
|
- + self.traffic_class_size()
|
|
|
- + self.next_header_size()
|
|
|
- + self.hop_limit_size()
|
|
|
- + self.src_address_size()) as usize;
|
|
|
-
|
|
|
- let data = self.buffer.as_ref();
|
|
|
- match (self.m_field(), self.dac_field(), self.dam_field()) {
|
|
|
- (0, 0, 0b00) => Ok(UnresolvedAddress::WithoutContext(AddressMode::FullInline(
|
|
|
- &data[start..][..16],
|
|
|
- ))),
|
|
|
- (0, 0, 0b01) => Ok(UnresolvedAddress::WithoutContext(
|
|
|
- AddressMode::InLine64bits(&data[start..][..8]),
|
|
|
- )),
|
|
|
- (0, 0, 0b10) => Ok(UnresolvedAddress::WithoutContext(
|
|
|
- AddressMode::InLine16bits(&data[start..][..2]),
|
|
|
- )),
|
|
|
- (0, 0, 0b11) => Ok(UnresolvedAddress::WithoutContext(AddressMode::FullyElided)),
|
|
|
- (0, 1, 0b00) => Ok(UnresolvedAddress::Reserved),
|
|
|
- (0, 1, 0b01) => {
|
|
|
- if let Some(id) = self.dst_context_id() {
|
|
|
- Ok(UnresolvedAddress::WithContext((
|
|
|
- id as usize,
|
|
|
- AddressMode::InLine64bits(&data[start..][..8]),
|
|
|
- )))
|
|
|
- } else {
|
|
|
- Err(Error)
|
|
|
- }
|
|
|
- }
|
|
|
- (0, 1, 0b10) => {
|
|
|
- if let Some(id) = self.dst_context_id() {
|
|
|
- Ok(UnresolvedAddress::WithContext((
|
|
|
- id as usize,
|
|
|
- AddressMode::InLine16bits(&data[start..][..2]),
|
|
|
- )))
|
|
|
- } else {
|
|
|
- Err(Error)
|
|
|
- }
|
|
|
- }
|
|
|
- (0, 1, 0b11) => {
|
|
|
- if let Some(id) = self.dst_context_id() {
|
|
|
- Ok(UnresolvedAddress::WithContext((
|
|
|
- id as usize,
|
|
|
- AddressMode::FullyElided,
|
|
|
- )))
|
|
|
- } else {
|
|
|
- Err(Error)
|
|
|
- }
|
|
|
- }
|
|
|
- (1, 0, 0b00) => Ok(UnresolvedAddress::WithoutContext(AddressMode::FullInline(
|
|
|
- &data[start..][..16],
|
|
|
- ))),
|
|
|
- (1, 0, 0b01) => Ok(UnresolvedAddress::WithoutContext(
|
|
|
- AddressMode::Multicast48bits(&data[start..][..6]),
|
|
|
- )),
|
|
|
- (1, 0, 0b10) => Ok(UnresolvedAddress::WithoutContext(
|
|
|
- AddressMode::Multicast32bits(&data[start..][..4]),
|
|
|
- )),
|
|
|
- (1, 0, 0b11) => Ok(UnresolvedAddress::WithoutContext(
|
|
|
- AddressMode::Multicast8bits(&data[start..][..1]),
|
|
|
- )),
|
|
|
- (1, 1, 0b00) => Ok(UnresolvedAddress::WithContext((
|
|
|
- 0,
|
|
|
- AddressMode::NotSupported,
|
|
|
- ))),
|
|
|
- (1, 1, 0b01 | 0b10 | 0b11) => Ok(UnresolvedAddress::Reserved),
|
|
|
- _ => Err(Error),
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- get_field!(dispatch_field, 0b111, 13);
|
|
|
- get_field!(tf_field, 0b11, 11);
|
|
|
- get_field!(nh_field, 0b1, 10);
|
|
|
- get_field!(hlim_field, 0b11, 8);
|
|
|
- get_field!(cid_field, 0b1, 7);
|
|
|
- get_field!(sac_field, 0b1, 6);
|
|
|
- get_field!(sam_field, 0b11, 4);
|
|
|
- get_field!(m_field, 0b1, 3);
|
|
|
- get_field!(dac_field, 0b1, 2);
|
|
|
- get_field!(dam_field, 0b11, 0);
|
|
|
-
|
|
|
- /// Return the start for the IP fields.
|
|
|
- fn ip_fields_start(&self) -> u8 {
|
|
|
- 2 + self.cid_size()
|
|
|
- }
|
|
|
-
|
|
|
- /// Get the size in octets of the traffic class field.
|
|
|
- fn traffic_class_size(&self) -> u8 {
|
|
|
- match self.tf_field() {
|
|
|
- 0b00 => 4,
|
|
|
- 0b01 => 3,
|
|
|
- 0b10 => 1,
|
|
|
- 0b11 => 0,
|
|
|
- _ => unreachable!(),
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// Get the size in octets of the next header field.
|
|
|
- fn next_header_size(&self) -> u8 {
|
|
|
- (self.nh_field() != 1) as u8
|
|
|
- }
|
|
|
-
|
|
|
- /// Get the size in octets of the hop limit field.
|
|
|
- fn hop_limit_size(&self) -> u8 {
|
|
|
- (self.hlim_field() == 0b00) as u8
|
|
|
- }
|
|
|
-
|
|
|
- /// Get the size in octets of the CID field.
|
|
|
- fn cid_size(&self) -> u8 {
|
|
|
- (self.cid_field() == 1) as u8
|
|
|
- }
|
|
|
-
|
|
|
- /// Get the size in octets of the source address.
|
|
|
- fn src_address_size(&self) -> u8 {
|
|
|
- match (self.sac_field(), self.sam_field()) {
|
|
|
- (0, 0b00) => 16, // The full address is carried in-line.
|
|
|
- (0, 0b01) => 8, // The first 64 bits are elided.
|
|
|
- (0, 0b10) => 2, // The first 112 bits are elided.
|
|
|
- (0, 0b11) => 0, // The address is fully elided.
|
|
|
- (1, 0b00) => 0, // The UNSPECIFIED address.
|
|
|
- (1, 0b01) => 8, // Address derived using context information.
|
|
|
- (1, 0b10) => 2, // Address derived using context information.
|
|
|
- (1, 0b11) => 0, // Address derived using context information.
|
|
|
- _ => unreachable!(),
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// Get the size in octets of the address address.
|
|
|
- fn dst_address_size(&self) -> u8 {
|
|
|
- match (self.m_field(), self.dac_field(), self.dam_field()) {
|
|
|
- (0, 0, 0b00) => 16, // The full address is carried in-line.
|
|
|
- (0, 0, 0b01) => 8, // The first 64 bits are elided.
|
|
|
- (0, 0, 0b10) => 2, // The first 112 bits are elided.
|
|
|
- (0, 0, 0b11) => 0, // The address is fully elided.
|
|
|
- (0, 1, 0b00) => 0, // Reserved.
|
|
|
- (0, 1, 0b01) => 8, // Address derived using context information.
|
|
|
- (0, 1, 0b10) => 2, // Address derived using context information.
|
|
|
- (0, 1, 0b11) => 0, // Address derived using context information.
|
|
|
- (1, 0, 0b00) => 16, // The full address is carried in-line.
|
|
|
- (1, 0, 0b01) => 6, // The address takes the form ffXX::00XX:XXXX:XXXX.
|
|
|
- (1, 0, 0b10) => 4, // The address takes the form ffXX::00XX:XXXX.
|
|
|
- (1, 0, 0b11) => 1, // The address takes the form ff02::00XX.
|
|
|
- (1, 1, 0b00) => 6, // Match Unicast-Prefix-based IPv6.
|
|
|
- (1, 1, 0b01) => 0, // Reserved.
|
|
|
- (1, 1, 0b10) => 0, // Reserved.
|
|
|
- (1, 1, 0b11) => 0, // Reserved.
|
|
|
- _ => unreachable!(),
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// Return the length of the header.
|
|
|
- pub fn header_len(&self) -> usize {
|
|
|
- let mut len = self.ip_fields_start();
|
|
|
- len += self.traffic_class_size();
|
|
|
- len += self.next_header_size();
|
|
|
- len += self.hop_limit_size();
|
|
|
- len += self.src_address_size();
|
|
|
- len += self.dst_address_size();
|
|
|
-
|
|
|
- len as usize
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- impl<'a, T: AsRef<[u8]> + ?Sized> Packet<&'a T> {
|
|
|
- /// Return a pointer to the payload.
|
|
|
- pub fn payload(&self) -> &'a [u8] {
|
|
|
- let mut len = self.ip_fields_start();
|
|
|
- len += self.traffic_class_size();
|
|
|
- len += self.next_header_size();
|
|
|
- len += self.hop_limit_size();
|
|
|
- len += self.src_address_size();
|
|
|
- len += self.dst_address_size();
|
|
|
-
|
|
|
- let len = len as usize;
|
|
|
-
|
|
|
- let data = self.buffer.as_ref();
|
|
|
- &data[len..]
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
|
|
|
- /// Set the dispatch field to `0b011`.
|
|
|
- fn set_dispatch_field(&mut self) {
|
|
|
- let data = &mut self.buffer.as_mut()[field::IPHC_FIELD];
|
|
|
- let mut raw = NetworkEndian::read_u16(data);
|
|
|
-
|
|
|
- raw = (raw & !(0b111 << 13)) | (0b11 << 13);
|
|
|
- NetworkEndian::write_u16(data, raw);
|
|
|
- }
|
|
|
-
|
|
|
- set_field!(set_tf_field, 0b11, 11);
|
|
|
- set_field!(set_nh_field, 0b1, 10);
|
|
|
- set_field!(set_hlim_field, 0b11, 8);
|
|
|
- set_field!(set_cid_field, 0b1, 7);
|
|
|
- set_field!(set_sac_field, 0b1, 6);
|
|
|
- set_field!(set_sam_field, 0b11, 4);
|
|
|
- set_field!(set_m_field, 0b1, 3);
|
|
|
- set_field!(set_dac_field, 0b1, 2);
|
|
|
- set_field!(set_dam_field, 0b11, 0);
|
|
|
-
|
|
|
- fn set_field(&mut self, idx: usize, value: &[u8]) {
|
|
|
- let raw = self.buffer.as_mut();
|
|
|
- raw[idx..idx + value.len()].copy_from_slice(value);
|
|
|
- }
|
|
|
-
|
|
|
- /// Set the Next Header.
|
|
|
- ///
|
|
|
- /// **NOTE**: `idx` is the offset at which the Next Header needs to be written to.
|
|
|
- fn set_next_header(&mut self, nh: NextHeader, mut idx: usize) -> usize {
|
|
|
- match nh {
|
|
|
- NextHeader::Uncompressed(nh) => {
|
|
|
- self.set_nh_field(0);
|
|
|
- self.set_field(idx, &[nh.into()]);
|
|
|
- idx += 1;
|
|
|
- }
|
|
|
- NextHeader::Compressed => self.set_nh_field(1),
|
|
|
- }
|
|
|
-
|
|
|
- idx
|
|
|
- }
|
|
|
-
|
|
|
- /// Set the Hop Limit.
|
|
|
- ///
|
|
|
- /// **NOTE**: `idx` is the offset at which the Next Header needs to be written to.
|
|
|
- fn set_hop_limit(&mut self, hl: u8, mut idx: usize) -> usize {
|
|
|
- match hl {
|
|
|
- 255 => self.set_hlim_field(0b11),
|
|
|
- 64 => self.set_hlim_field(0b10),
|
|
|
- 1 => self.set_hlim_field(0b01),
|
|
|
- _ => {
|
|
|
- self.set_hlim_field(0b00);
|
|
|
- self.set_field(idx, &[hl]);
|
|
|
- idx += 1;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- idx
|
|
|
- }
|
|
|
-
|
|
|
- /// Set the Source Address based on the IPv6 address and the Link-Local address.
|
|
|
- ///
|
|
|
- /// **NOTE**: `idx` is the offset at which the Next Header needs to be written to.
|
|
|
- fn set_src_address(
|
|
|
- &mut self,
|
|
|
- src_addr: ipv6::Address,
|
|
|
- ll_src_addr: Option<LlAddress>,
|
|
|
- mut idx: usize,
|
|
|
- ) -> usize {
|
|
|
- self.set_cid_field(0);
|
|
|
- self.set_sac_field(0);
|
|
|
- let src = src_addr.as_bytes();
|
|
|
- if src_addr == ipv6::Address::UNSPECIFIED {
|
|
|
- self.set_sac_field(1);
|
|
|
- self.set_sam_field(0b00);
|
|
|
- } else if src_addr.is_link_local() {
|
|
|
- // We have a link local address.
|
|
|
- // The remainder of the address can be elided when the context contains
|
|
|
- // a 802.15.4 short address or a 802.15.4 extended address which can be
|
|
|
- // converted to a eui64 address.
|
|
|
- let is_eui_64 = ll_src_addr
|
|
|
- .map(|addr| {
|
|
|
- addr.as_eui_64()
|
|
|
- .map(|addr| addr[..] == src[8..])
|
|
|
- .unwrap_or(false)
|
|
|
- })
|
|
|
- .unwrap_or(false);
|
|
|
-
|
|
|
- if src[8..14] == [0, 0, 0, 0xff, 0xfe, 0] {
|
|
|
- let ll = [src[14], src[15]];
|
|
|
-
|
|
|
- if ll_src_addr == Some(LlAddress::Short(ll)) {
|
|
|
- // We have the context from the 802.15.4 frame.
|
|
|
- // The context contains the short address.
|
|
|
- // We can elide the source address.
|
|
|
- self.set_sam_field(0b11);
|
|
|
- } else {
|
|
|
- // We don't have the context from the 802.15.4 frame.
|
|
|
- // We cannot elide the source address, however we can elide 112 bits.
|
|
|
- self.set_sam_field(0b10);
|
|
|
-
|
|
|
- self.set_field(idx, &src[14..]);
|
|
|
- idx += 2;
|
|
|
- }
|
|
|
- } else if is_eui_64 {
|
|
|
- // We have the context from the 802.15.4 frame.
|
|
|
- // The context contains the extended address.
|
|
|
- // We can elide the source address.
|
|
|
- self.set_sam_field(0b11);
|
|
|
- } else {
|
|
|
- // We cannot elide the source address, however we can elide 64 bits.
|
|
|
- self.set_sam_field(0b01);
|
|
|
-
|
|
|
- self.set_field(idx, &src[8..]);
|
|
|
- idx += 8;
|
|
|
- }
|
|
|
- } else {
|
|
|
- // We cannot elide anything.
|
|
|
- self.set_sam_field(0b00);
|
|
|
- self.set_field(idx, src);
|
|
|
- idx += 16;
|
|
|
- }
|
|
|
-
|
|
|
- idx
|
|
|
- }
|
|
|
-
|
|
|
- /// Set the Destination Address based on the IPv6 address and the Link-Local address.
|
|
|
- ///
|
|
|
- /// **NOTE**: `idx` is the offset at which the Next Header needs to be written to.
|
|
|
- fn set_dst_address(
|
|
|
- &mut self,
|
|
|
- dst_addr: ipv6::Address,
|
|
|
- ll_dst_addr: Option<LlAddress>,
|
|
|
- mut idx: usize,
|
|
|
- ) -> usize {
|
|
|
- self.set_dac_field(0);
|
|
|
- self.set_dam_field(0);
|
|
|
- self.set_m_field(0);
|
|
|
- let dst = dst_addr.as_bytes();
|
|
|
- if dst_addr.is_multicast() {
|
|
|
- self.set_m_field(1);
|
|
|
-
|
|
|
- if dst[1] == 0x02 && dst[2..15] == [0; 13] {
|
|
|
- self.set_dam_field(0b11);
|
|
|
-
|
|
|
- self.set_field(idx, &[dst[15]]);
|
|
|
- idx += 1;
|
|
|
- } else if dst[2..13] == [0; 11] {
|
|
|
- self.set_dam_field(0b10);
|
|
|
-
|
|
|
- self.set_field(idx, &[dst[1]]);
|
|
|
- idx += 1;
|
|
|
- self.set_field(idx, &dst[13..]);
|
|
|
- idx += 3;
|
|
|
- } else if dst[2..11] == [0; 9] {
|
|
|
- self.set_dam_field(0b01);
|
|
|
-
|
|
|
- self.set_field(idx, &[dst[1]]);
|
|
|
- idx += 1;
|
|
|
- self.set_field(idx, &dst[11..]);
|
|
|
- idx += 5;
|
|
|
- } else {
|
|
|
- self.set_dam_field(0b11);
|
|
|
-
|
|
|
- self.set_field(idx, dst);
|
|
|
- idx += 16;
|
|
|
- }
|
|
|
- } else if dst_addr.is_link_local() {
|
|
|
- let is_eui_64 = ll_dst_addr
|
|
|
- .map(|addr| {
|
|
|
- addr.as_eui_64()
|
|
|
- .map(|addr| addr[..] == dst[8..])
|
|
|
- .unwrap_or(false)
|
|
|
- })
|
|
|
- .unwrap_or(false);
|
|
|
-
|
|
|
- if dst[8..14] == [0, 0, 0, 0xff, 0xfe, 0] {
|
|
|
- let ll = [dst[14], dst[15]];
|
|
|
-
|
|
|
- if ll_dst_addr == Some(LlAddress::Short(ll)) {
|
|
|
- self.set_dam_field(0b11);
|
|
|
- } else {
|
|
|
- self.set_dam_field(0b10);
|
|
|
-
|
|
|
- self.set_field(idx, &dst[14..]);
|
|
|
- idx += 2;
|
|
|
- }
|
|
|
- } else if is_eui_64 {
|
|
|
- self.set_dam_field(0b11);
|
|
|
- } else {
|
|
|
- self.set_dam_field(0b01);
|
|
|
-
|
|
|
- self.set_field(idx, &dst[8..]);
|
|
|
- idx += 8;
|
|
|
- }
|
|
|
- } else {
|
|
|
- self.set_dam_field(0b00);
|
|
|
-
|
|
|
- self.set_field(idx, dst);
|
|
|
- idx += 16;
|
|
|
- }
|
|
|
-
|
|
|
- idx
|
|
|
- }
|
|
|
-
|
|
|
- /// Return a mutable pointer to the payload.
|
|
|
- pub fn payload_mut(&mut self) -> &mut [u8] {
|
|
|
- let mut len = self.ip_fields_start();
|
|
|
-
|
|
|
- len += self.traffic_class_size();
|
|
|
- len += self.next_header_size();
|
|
|
- len += self.hop_limit_size();
|
|
|
- len += self.src_address_size();
|
|
|
- len += self.dst_address_size();
|
|
|
-
|
|
|
- let len = len as usize;
|
|
|
-
|
|
|
- let data = self.buffer.as_mut();
|
|
|
- &mut data[len..]
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// A high-level representation of a 6LoWPAN IPHC header.
|
|
|
- #[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
|
|
- pub struct Repr {
|
|
|
- pub src_addr: ipv6::Address,
|
|
|
- pub ll_src_addr: Option<LlAddress>,
|
|
|
- pub dst_addr: ipv6::Address,
|
|
|
- pub ll_dst_addr: Option<LlAddress>,
|
|
|
- pub next_header: NextHeader,
|
|
|
- pub hop_limit: u8,
|
|
|
- // TODO(thvdveld): refactor the following fields into something else
|
|
|
- pub ecn: Option<u8>,
|
|
|
- pub dscp: Option<u8>,
|
|
|
- pub flow_label: Option<u16>,
|
|
|
- }
|
|
|
-
|
|
|
- impl core::fmt::Display for Repr {
|
|
|
- fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
|
|
- write!(
|
|
|
- f,
|
|
|
- "IPHC src={} dst={} nxt-hdr={} hop-limit={}",
|
|
|
- self.src_addr, self.dst_addr, self.next_header, self.hop_limit
|
|
|
- )
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- #[cfg(feature = "defmt")]
|
|
|
- impl defmt::Format for Repr {
|
|
|
- fn format(&self, fmt: defmt::Formatter) {
|
|
|
- defmt::write!(
|
|
|
- fmt,
|
|
|
- "IPHC src={} dst={} nxt-hdr={} hop-limit={}",
|
|
|
- self.src_addr,
|
|
|
- self.dst_addr,
|
|
|
- self.next_header,
|
|
|
- self.hop_limit
|
|
|
- );
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- impl Repr {
|
|
|
- /// Parse a 6LoWPAN IPHC header and return a high-level representation.
|
|
|
- ///
|
|
|
- /// The `ll_src_addr` and `ll_dst_addr` are the link-local addresses used for resolving the
|
|
|
- /// IPv6 packets.
|
|
|
- pub fn parse<T: AsRef<[u8]> + ?Sized>(
|
|
|
- packet: &Packet<&T>,
|
|
|
- ll_src_addr: Option<LlAddress>,
|
|
|
- ll_dst_addr: Option<LlAddress>,
|
|
|
- addr_context: &[AddressContext],
|
|
|
- ) -> Result<Self> {
|
|
|
- // Ensure basic accessors will work.
|
|
|
- packet.check_len()?;
|
|
|
-
|
|
|
- if packet.dispatch_field() != DISPATCH_IPHC_HEADER {
|
|
|
- // This is not an LOWPAN_IPHC packet.
|
|
|
- return Err(Error);
|
|
|
- }
|
|
|
-
|
|
|
- let src_addr = packet.src_addr()?.resolve(ll_src_addr, addr_context)?;
|
|
|
- let dst_addr = packet.dst_addr()?.resolve(ll_dst_addr, addr_context)?;
|
|
|
-
|
|
|
- Ok(Self {
|
|
|
- src_addr,
|
|
|
- ll_src_addr,
|
|
|
- dst_addr,
|
|
|
- ll_dst_addr,
|
|
|
- next_header: packet.next_header(),
|
|
|
- hop_limit: packet.hop_limit(),
|
|
|
- ecn: packet.ecn_field(),
|
|
|
- dscp: packet.dscp_field(),
|
|
|
- flow_label: packet.flow_label_field(),
|
|
|
- })
|
|
|
- }
|
|
|
-
|
|
|
- /// Return the length of a header that will be emitted from this high-level representation.
|
|
|
- pub fn buffer_len(&self) -> usize {
|
|
|
- let mut len = 0;
|
|
|
- len += 2; // The minimal header length
|
|
|
-
|
|
|
- len += match self.next_header {
|
|
|
- NextHeader::Compressed => 0, // The next header is compressed (we don't need to inline what the next header is)
|
|
|
- NextHeader::Uncompressed(_) => 1, // The next header field is inlined
|
|
|
- };
|
|
|
-
|
|
|
- // Hop Limit size
|
|
|
- len += match self.hop_limit {
|
|
|
- 255 | 64 | 1 => 0, // We can inline the hop limit
|
|
|
- _ => 1,
|
|
|
- };
|
|
|
-
|
|
|
- // Add the length of the source address
|
|
|
- len += if self.src_addr == ipv6::Address::UNSPECIFIED {
|
|
|
- 0
|
|
|
- } else if self.src_addr.is_link_local() {
|
|
|
- let src = self.src_addr.as_bytes();
|
|
|
- let ll = [src[14], src[15]];
|
|
|
-
|
|
|
- let is_eui_64 = self
|
|
|
- .ll_src_addr
|
|
|
- .map(|addr| {
|
|
|
- addr.as_eui_64()
|
|
|
- .map(|addr| addr[..] == src[8..])
|
|
|
- .unwrap_or(false)
|
|
|
- })
|
|
|
- .unwrap_or(false);
|
|
|
-
|
|
|
- if src[8..14] == [0, 0, 0, 0xff, 0xfe, 0] {
|
|
|
- if self.ll_src_addr == Some(LlAddress::Short(ll)) {
|
|
|
- 0
|
|
|
- } else {
|
|
|
- 2
|
|
|
- }
|
|
|
- } else if is_eui_64 {
|
|
|
- 0
|
|
|
- } else {
|
|
|
- 8
|
|
|
- }
|
|
|
- } else {
|
|
|
- 16
|
|
|
- };
|
|
|
-
|
|
|
- // Add the size of the destination header
|
|
|
- let dst = self.dst_addr.as_bytes();
|
|
|
- len += if self.dst_addr.is_multicast() {
|
|
|
- if dst[1] == 0x02 && dst[2..15] == [0; 13] {
|
|
|
- 1
|
|
|
- } else if dst[2..13] == [0; 11] {
|
|
|
- 4
|
|
|
- } else if dst[2..11] == [0; 9] {
|
|
|
- 6
|
|
|
- } else {
|
|
|
- 16
|
|
|
- }
|
|
|
- } else if self.dst_addr.is_link_local() {
|
|
|
- let is_eui_64 = self
|
|
|
- .ll_dst_addr
|
|
|
- .map(|addr| {
|
|
|
- addr.as_eui_64()
|
|
|
- .map(|addr| addr[..] == dst[8..])
|
|
|
- .unwrap_or(false)
|
|
|
- })
|
|
|
- .unwrap_or(false);
|
|
|
-
|
|
|
- if dst[8..14] == [0, 0, 0, 0xff, 0xfe, 0] {
|
|
|
- let ll = [dst[14], dst[15]];
|
|
|
-
|
|
|
- if self.ll_dst_addr == Some(LlAddress::Short(ll)) {
|
|
|
- 0
|
|
|
- } else {
|
|
|
- 2
|
|
|
- }
|
|
|
- } else if is_eui_64 {
|
|
|
- 0
|
|
|
- } else {
|
|
|
- 8
|
|
|
- }
|
|
|
- } else {
|
|
|
- 16
|
|
|
- };
|
|
|
-
|
|
|
- len += match (self.ecn, self.dscp, self.flow_label) {
|
|
|
- (Some(_), Some(_), Some(_)) => 4,
|
|
|
- (Some(_), None, Some(_)) => 3,
|
|
|
- (Some(_), Some(_), None) => 1,
|
|
|
- (None, None, None) => 0,
|
|
|
- _ => unreachable!(),
|
|
|
- };
|
|
|
-
|
|
|
- len
|
|
|
- }
|
|
|
-
|
|
|
- /// Emit a high-level representation into a 6LoWPAN IPHC header.
|
|
|
- pub fn emit<T: AsRef<[u8]> + AsMut<[u8]>>(&self, packet: &mut Packet<T>) {
|
|
|
- let idx = 2;
|
|
|
-
|
|
|
- packet.set_dispatch_field();
|
|
|
-
|
|
|
- // FIXME(thvdveld): we don't set anything from the traffic flow.
|
|
|
- packet.set_tf_field(0b11);
|
|
|
-
|
|
|
- let idx = packet.set_next_header(self.next_header, idx);
|
|
|
- let idx = packet.set_hop_limit(self.hop_limit, idx);
|
|
|
- let idx = packet.set_src_address(self.src_addr, self.ll_src_addr, idx);
|
|
|
- packet.set_dst_address(self.dst_addr, self.ll_dst_addr, idx);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- #[cfg(test)]
|
|
|
- mod test {
|
|
|
- use super::*;
|
|
|
-
|
|
|
- #[test]
|
|
|
- fn iphc_fields() {
|
|
|
- let bytes = [
|
|
|
- 0x7a, 0x33, // IPHC
|
|
|
- 0x3a, // Next header
|
|
|
- ];
|
|
|
-
|
|
|
- let packet = Packet::new_unchecked(bytes);
|
|
|
-
|
|
|
- assert_eq!(packet.dispatch_field(), 0b011);
|
|
|
- assert_eq!(packet.tf_field(), 0b11);
|
|
|
- assert_eq!(packet.nh_field(), 0b0);
|
|
|
- assert_eq!(packet.hlim_field(), 0b10);
|
|
|
- assert_eq!(packet.cid_field(), 0b0);
|
|
|
- assert_eq!(packet.sac_field(), 0b0);
|
|
|
- assert_eq!(packet.sam_field(), 0b11);
|
|
|
- assert_eq!(packet.m_field(), 0b0);
|
|
|
- assert_eq!(packet.dac_field(), 0b0);
|
|
|
- assert_eq!(packet.dam_field(), 0b11);
|
|
|
-
|
|
|
- assert_eq!(
|
|
|
- packet.next_header(),
|
|
|
- NextHeader::Uncompressed(IpProtocol::Icmpv6)
|
|
|
- );
|
|
|
-
|
|
|
- assert_eq!(packet.src_address_size(), 0);
|
|
|
- assert_eq!(packet.dst_address_size(), 0);
|
|
|
- assert_eq!(packet.hop_limit(), 64);
|
|
|
-
|
|
|
- assert_eq!(
|
|
|
- packet.src_addr(),
|
|
|
- Ok(UnresolvedAddress::WithoutContext(AddressMode::FullyElided))
|
|
|
- );
|
|
|
- assert_eq!(
|
|
|
- packet.dst_addr(),
|
|
|
- Ok(UnresolvedAddress::WithoutContext(AddressMode::FullyElided))
|
|
|
- );
|
|
|
-
|
|
|
- let bytes = [
|
|
|
- 0x7e, 0xf7, // IPHC,
|
|
|
- 0x00, // CID
|
|
|
- ];
|
|
|
-
|
|
|
- let packet = Packet::new_unchecked(bytes);
|
|
|
-
|
|
|
- assert_eq!(packet.dispatch_field(), 0b011);
|
|
|
- assert_eq!(packet.tf_field(), 0b11);
|
|
|
- assert_eq!(packet.nh_field(), 0b1);
|
|
|
- assert_eq!(packet.hlim_field(), 0b10);
|
|
|
- assert_eq!(packet.cid_field(), 0b1);
|
|
|
- assert_eq!(packet.sac_field(), 0b1);
|
|
|
- assert_eq!(packet.sam_field(), 0b11);
|
|
|
- assert_eq!(packet.m_field(), 0b0);
|
|
|
- assert_eq!(packet.dac_field(), 0b1);
|
|
|
- assert_eq!(packet.dam_field(), 0b11);
|
|
|
-
|
|
|
- assert_eq!(packet.next_header(), NextHeader::Compressed);
|
|
|
-
|
|
|
- assert_eq!(packet.src_address_size(), 0);
|
|
|
- assert_eq!(packet.dst_address_size(), 0);
|
|
|
- assert_eq!(packet.hop_limit(), 64);
|
|
|
-
|
|
|
- assert_eq!(
|
|
|
- packet.src_addr(),
|
|
|
- Ok(UnresolvedAddress::WithContext((
|
|
|
- 0,
|
|
|
- AddressMode::FullyElided
|
|
|
- )))
|
|
|
- );
|
|
|
- assert_eq!(
|
|
|
- packet.dst_addr(),
|
|
|
- Ok(UnresolvedAddress::WithContext((
|
|
|
- 0,
|
|
|
- AddressMode::FullyElided
|
|
|
- )))
|
|
|
- );
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-pub mod nhc {
|
|
|
- //! Implementation of Next Header Compression from [RFC 6282 § 4].
|
|
|
- //!
|
|
|
- //! [RFC 6282 § 4]: https://datatracker.ietf.org/doc/html/rfc6282#section-4
|
|
|
- use super::{Error, NextHeader, Result, DISPATCH_EXT_HEADER, DISPATCH_UDP_HEADER};
|
|
|
- use crate::{
|
|
|
- phy::ChecksumCapabilities,
|
|
|
- wire::{
|
|
|
- ip::{checksum, Address as IpAddress},
|
|
|
- ipv6,
|
|
|
- udp::Repr as UdpRepr,
|
|
|
- IpProtocol,
|
|
|
- },
|
|
|
- };
|
|
|
- use byteorder::{ByteOrder, NetworkEndian};
|
|
|
- use ipv6::Address;
|
|
|
-
|
|
|
- macro_rules! get_field {
|
|
|
- ($name:ident, $mask:expr, $shift:expr) => {
|
|
|
- fn $name(&self) -> u8 {
|
|
|
- let data = self.buffer.as_ref();
|
|
|
- let raw = &data[0];
|
|
|
- ((raw >> $shift) & $mask) as u8
|
|
|
- }
|
|
|
- };
|
|
|
- }
|
|
|
-
|
|
|
- macro_rules! set_field {
|
|
|
- ($name:ident, $mask:expr, $shift:expr) => {
|
|
|
- fn $name(&mut self, val: u8) {
|
|
|
- let data = self.buffer.as_mut();
|
|
|
- let mut raw = data[0];
|
|
|
- raw = (raw & !($mask << $shift)) | (val << $shift);
|
|
|
- data[0] = raw;
|
|
|
- }
|
|
|
- };
|
|
|
- }
|
|
|
-
|
|
|
- #[derive(Debug, Clone)]
|
|
|
- #[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
|
|
- /// A read/write wrapper around a 6LoWPAN_NHC Header.
|
|
|
- /// [RFC 6282 § 4.2] specifies the format of the header.
|
|
|
- ///
|
|
|
- /// The header has the following format:
|
|
|
- /// ```txt
|
|
|
- /// 0 1 2 3 4 5 6 7
|
|
|
- /// +---+---+---+---+---+---+---+---+
|
|
|
- /// | 1 | 1 | 1 | 0 | EID |NH |
|
|
|
- /// +---+---+---+---+---+---+---+---+
|
|
|
- /// ```
|
|
|
- ///
|
|
|
- /// With:
|
|
|
- /// - EID: the extension header ID
|
|
|
- /// - NH: Next Header
|
|
|
- ///
|
|
|
- /// [RFC 6282 § 4.2]: https://datatracker.ietf.org/doc/html/rfc6282#section-4.2
|
|
|
- pub enum NhcPacket {
|
|
|
- ExtHeader,
|
|
|
- UdpHeader,
|
|
|
- }
|
|
|
-
|
|
|
- impl NhcPacket {
|
|
|
- /// Returns the type of the Next Header header.
|
|
|
- /// This can either be an Extenstion header or an 6LoWPAN Udp header.
|
|
|
- ///
|
|
|
- /// # Errors
|
|
|
- /// Returns `[Error::Unrecognized]` when neither the Extension Header dispatch or the Udp
|
|
|
- /// 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] >> 4 == DISPATCH_EXT_HEADER {
|
|
|
- // We have a compressed IPv6 Extension Header.
|
|
|
- Ok(Self::ExtHeader)
|
|
|
- } else if raw[0] >> 3 == DISPATCH_UDP_HEADER {
|
|
|
- // We have a compressed UDP header.
|
|
|
- Ok(Self::UdpHeader)
|
|
|
- } else {
|
|
|
- Err(Error)
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- #[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
|
|
- #[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
|
|
- pub enum ExtHeaderId {
|
|
|
- HopByHopHeader,
|
|
|
- RoutingHeader,
|
|
|
- FragmentHeader,
|
|
|
- DestinationOptionsHeader,
|
|
|
- MobilityHeader,
|
|
|
- Header,
|
|
|
- Reserved,
|
|
|
- }
|
|
|
-
|
|
|
- impl From<ExtHeaderId> for IpProtocol {
|
|
|
- fn from(val: ExtHeaderId) -> Self {
|
|
|
- match val {
|
|
|
- ExtHeaderId::HopByHopHeader => Self::HopByHop,
|
|
|
- ExtHeaderId::RoutingHeader => Self::Ipv6Route,
|
|
|
- ExtHeaderId::FragmentHeader => Self::Ipv6Frag,
|
|
|
- ExtHeaderId::DestinationOptionsHeader => Self::Ipv6Opts,
|
|
|
- ExtHeaderId::MobilityHeader => Self::Unknown(0),
|
|
|
- ExtHeaderId::Header => Self::Unknown(0),
|
|
|
- ExtHeaderId::Reserved => Self::Unknown(0),
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// A read/write wrapper around a 6LoWPAN NHC Extension header.
|
|
|
- #[derive(Debug, Clone)]
|
|
|
- #[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
|
|
- pub struct ExtHeaderPacket<T: AsRef<[u8]>> {
|
|
|
- buffer: T,
|
|
|
- }
|
|
|
-
|
|
|
- impl<T: AsRef<[u8]>> ExtHeaderPacket<T> {
|
|
|
- /// Input a raw octet buffer with a 6LoWPAN NHC Extension Header structure.
|
|
|
- pub const fn new_unchecked(buffer: T) -> Self {
|
|
|
- ExtHeaderPacket { 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()?;
|
|
|
-
|
|
|
- if packet.eid_field() > 7 {
|
|
|
- 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);
|
|
|
- }
|
|
|
-
|
|
|
- let mut len = 1;
|
|
|
- len += self.next_header_size();
|
|
|
-
|
|
|
- if len <= buffer.len() {
|
|
|
- Ok(())
|
|
|
- } else {
|
|
|
- Err(Error)
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// Consumes the frame, returning the underlying buffer.
|
|
|
- pub fn into_inner(self) -> T {
|
|
|
- self.buffer
|
|
|
- }
|
|
|
-
|
|
|
- get_field!(dispatch_field, 0b1111, 4);
|
|
|
- get_field!(eid_field, 0b111, 1);
|
|
|
- get_field!(nh_field, 0b1, 0);
|
|
|
-
|
|
|
- /// Return the Extension Header ID.
|
|
|
- pub fn extension_header_id(&self) -> ExtHeaderId {
|
|
|
- match self.eid_field() {
|
|
|
- 0 => ExtHeaderId::HopByHopHeader,
|
|
|
- 1 => ExtHeaderId::RoutingHeader,
|
|
|
- 2 => ExtHeaderId::FragmentHeader,
|
|
|
- 3 => ExtHeaderId::DestinationOptionsHeader,
|
|
|
- 4 => ExtHeaderId::MobilityHeader,
|
|
|
- 5 | 6 => ExtHeaderId::Reserved,
|
|
|
- 7 => ExtHeaderId::Header,
|
|
|
- _ => unreachable!(),
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// Parse the next header field.
|
|
|
- pub fn next_header(&self) -> NextHeader {
|
|
|
- if self.nh_field() == 1 {
|
|
|
- NextHeader::Compressed
|
|
|
- } else {
|
|
|
- // The full 8 bits for Next Header are carried in-line.
|
|
|
- let start = 1;
|
|
|
-
|
|
|
- let data = self.buffer.as_ref();
|
|
|
- let nh = data[start];
|
|
|
- NextHeader::Uncompressed(IpProtocol::from(nh))
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// Return the size of the Next Header field.
|
|
|
- fn next_header_size(&self) -> usize {
|
|
|
- // If nh is set, then the Next Header is compressed using LOWPAN_NHC
|
|
|
- match self.nh_field() {
|
|
|
- 0 => 1,
|
|
|
- 1 => 0,
|
|
|
- _ => unreachable!(),
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- impl<'a, T: AsRef<[u8]> + ?Sized> ExtHeaderPacket<&'a T> {
|
|
|
- /// Return a pointer to the payload.
|
|
|
- pub fn payload(&self) -> &'a [u8] {
|
|
|
- let start = 2 + self.next_header_size();
|
|
|
- &self.buffer.as_ref()[start..]
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- impl<T: AsRef<[u8]> + AsMut<[u8]>> ExtHeaderPacket<T> {
|
|
|
- /// Return a mutable pointer to the payload.
|
|
|
- pub fn payload_mut(&mut self) -> &mut [u8] {
|
|
|
- let start = 2 + self.next_header_size();
|
|
|
- &mut self.buffer.as_mut()[start..]
|
|
|
- }
|
|
|
-
|
|
|
- /// Set the dispatch field to `0b1110`.
|
|
|
- fn set_dispatch_field(&mut self) {
|
|
|
- let data = self.buffer.as_mut();
|
|
|
- data[0] = (data[0] & !(0b1111 << 4)) | (DISPATCH_EXT_HEADER << 4);
|
|
|
- }
|
|
|
-
|
|
|
- set_field!(set_eid_field, 0b111, 1);
|
|
|
- set_field!(set_nh_field, 0b1, 0);
|
|
|
-
|
|
|
- /// Set the Extension Header ID field.
|
|
|
- fn set_extension_header_id(&mut self, ext_header_id: ExtHeaderId) {
|
|
|
- let id = match ext_header_id {
|
|
|
- ExtHeaderId::HopByHopHeader => 0,
|
|
|
- ExtHeaderId::RoutingHeader => 1,
|
|
|
- ExtHeaderId::FragmentHeader => 2,
|
|
|
- ExtHeaderId::DestinationOptionsHeader => 3,
|
|
|
- ExtHeaderId::MobilityHeader => 4,
|
|
|
- ExtHeaderId::Reserved => 5,
|
|
|
- ExtHeaderId::Header => 7,
|
|
|
- };
|
|
|
-
|
|
|
- self.set_eid_field(id);
|
|
|
- }
|
|
|
-
|
|
|
- /// Set the Next Header.
|
|
|
- fn set_next_header(&mut self, next_header: NextHeader) {
|
|
|
- match next_header {
|
|
|
- NextHeader::Compressed => self.set_nh_field(0b1),
|
|
|
- NextHeader::Uncompressed(nh) => {
|
|
|
- self.set_nh_field(0b0);
|
|
|
-
|
|
|
- let start = 1;
|
|
|
- let data = self.buffer.as_mut();
|
|
|
- data[start] = nh.into();
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// Set the length.
|
|
|
- fn set_length(&mut self, length: u8) {
|
|
|
- let start = 1 + self.next_header_size();
|
|
|
-
|
|
|
- let data = self.buffer.as_mut();
|
|
|
- data[start] = length;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// A high-level representation of an 6LoWPAN NHC Extension header.
|
|
|
- #[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
|
|
- #[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
|
|
- pub struct ExtHeaderRepr {
|
|
|
- ext_header_id: ExtHeaderId,
|
|
|
- next_header: NextHeader,
|
|
|
- length: u8,
|
|
|
- }
|
|
|
-
|
|
|
- impl ExtHeaderRepr {
|
|
|
- /// Parse a 6LoWPAN NHC Extension Header packet and return a high-level representation.
|
|
|
- pub fn parse<T: AsRef<[u8]> + ?Sized>(packet: &ExtHeaderPacket<&T>) -> Result<Self> {
|
|
|
- // Ensure basic accessors will work.
|
|
|
- packet.check_len()?;
|
|
|
-
|
|
|
- if packet.dispatch_field() != DISPATCH_EXT_HEADER {
|
|
|
- return Err(Error);
|
|
|
- }
|
|
|
-
|
|
|
- Ok(Self {
|
|
|
- ext_header_id: packet.extension_header_id(),
|
|
|
- next_header: packet.next_header(),
|
|
|
- length: packet.payload().len() as u8,
|
|
|
- })
|
|
|
- }
|
|
|
-
|
|
|
- /// Return the length of a header that will be emitted from this high-level representation.
|
|
|
- pub fn buffer_len(&self) -> usize {
|
|
|
- let mut len = 1; // The minimal header size
|
|
|
-
|
|
|
- if self.next_header != NextHeader::Compressed {
|
|
|
- len += 1;
|
|
|
- }
|
|
|
-
|
|
|
- len += 1; // The length
|
|
|
-
|
|
|
- len
|
|
|
- }
|
|
|
-
|
|
|
- /// Emit a high-level representaiton into a 6LoWPAN NHC Extension Header packet.
|
|
|
- pub fn emit<T: AsRef<[u8]> + AsMut<[u8]>>(&self, packet: &mut ExtHeaderPacket<T>) {
|
|
|
- packet.set_dispatch_field();
|
|
|
- packet.set_extension_header_id(self.ext_header_id);
|
|
|
- packet.set_next_header(self.next_header);
|
|
|
- packet.set_length(self.length);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- #[cfg(test)]
|
|
|
- mod tests {
|
|
|
- use super::*;
|
|
|
-
|
|
|
- use crate::wire::{Ipv6RoutingHeader, Ipv6RoutingRepr};
|
|
|
-
|
|
|
- #[cfg(feature = "proto-rpl")]
|
|
|
- use crate::wire::{
|
|
|
- Ipv6Option, Ipv6OptionRepr, Ipv6OptionsIterator, RplHopByHopRepr, RplInstanceId,
|
|
|
- };
|
|
|
-
|
|
|
- #[cfg(feature = "proto-rpl")]
|
|
|
- const RPL_HOP_BY_HOP_PACKET: [u8; 9] =
|
|
|
- [0xe0, 0x3a, 0x06, 0x63, 0x04, 0x00, 0x1e, 0x03, 0x00];
|
|
|
-
|
|
|
- const ROUTING_SR_PACKET: [u8; 32] = [
|
|
|
- 0xe3, 0x1e, 0x03, 0x03, 0x99, 0x30, 0x00, 0x00, 0x05, 0x00, 0x05, 0x00, 0x05, 0x00,
|
|
|
- 0x05, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00, 0x06, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00,
|
|
|
- 0x02, 0x00, 0x00, 0x00,
|
|
|
- ];
|
|
|
-
|
|
|
- #[test]
|
|
|
- #[cfg(feature = "proto-rpl")]
|
|
|
- fn test_rpl_hop_by_hop_option_deconstruct() {
|
|
|
- let header = ExtHeaderPacket::new_checked(&RPL_HOP_BY_HOP_PACKET).unwrap();
|
|
|
- assert_eq!(
|
|
|
- header.next_header(),
|
|
|
- NextHeader::Uncompressed(IpProtocol::Icmpv6)
|
|
|
- );
|
|
|
- assert_eq!(header.extension_header_id(), ExtHeaderId::HopByHopHeader);
|
|
|
-
|
|
|
- let options = header.payload();
|
|
|
- let mut options = Ipv6OptionsIterator::new(options);
|
|
|
- let rpl_repr = options.next().unwrap();
|
|
|
- let rpl_repr = rpl_repr.unwrap();
|
|
|
-
|
|
|
- match rpl_repr {
|
|
|
- Ipv6OptionRepr::Rpl(rpl) => {
|
|
|
- assert_eq!(
|
|
|
- rpl,
|
|
|
- RplHopByHopRepr {
|
|
|
- down: false,
|
|
|
- rank_error: false,
|
|
|
- forwarding_error: false,
|
|
|
- instance_id: RplInstanceId::from(0x1e),
|
|
|
- sender_rank: 0x0300,
|
|
|
- }
|
|
|
- );
|
|
|
- }
|
|
|
- _ => unreachable!(),
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- #[test]
|
|
|
- #[cfg(feature = "proto-rpl")]
|
|
|
- fn test_rpl_hop_by_hop_option_emit() {
|
|
|
- let repr = Ipv6OptionRepr::Rpl(RplHopByHopRepr {
|
|
|
- down: false,
|
|
|
- rank_error: false,
|
|
|
- forwarding_error: false,
|
|
|
- instance_id: RplInstanceId::from(0x1e),
|
|
|
- sender_rank: 0x0300,
|
|
|
- });
|
|
|
-
|
|
|
- let ext_hdr = ExtHeaderRepr {
|
|
|
- ext_header_id: ExtHeaderId::HopByHopHeader,
|
|
|
- next_header: NextHeader::Uncompressed(IpProtocol::Icmpv6),
|
|
|
- length: repr.buffer_len() as u8,
|
|
|
- };
|
|
|
-
|
|
|
- let mut buffer = vec![0u8; ext_hdr.buffer_len() + repr.buffer_len()];
|
|
|
- ext_hdr.emit(&mut ExtHeaderPacket::new_unchecked(
|
|
|
- &mut buffer[..ext_hdr.buffer_len()],
|
|
|
- ));
|
|
|
- repr.emit(&mut Ipv6Option::new_unchecked(
|
|
|
- &mut buffer[ext_hdr.buffer_len()..],
|
|
|
- ));
|
|
|
-
|
|
|
- assert_eq!(&buffer[..], RPL_HOP_BY_HOP_PACKET);
|
|
|
- }
|
|
|
-
|
|
|
- #[test]
|
|
|
- fn test_source_routing_deconstruct() {
|
|
|
- let header = ExtHeaderPacket::new_checked(&ROUTING_SR_PACKET).unwrap();
|
|
|
- assert_eq!(header.next_header(), NextHeader::Compressed);
|
|
|
- assert_eq!(header.extension_header_id(), ExtHeaderId::RoutingHeader);
|
|
|
-
|
|
|
- let routing_hdr = Ipv6RoutingHeader::new_checked(header.payload()).unwrap();
|
|
|
- let repr = Ipv6RoutingRepr::parse(&routing_hdr).unwrap();
|
|
|
- assert_eq!(
|
|
|
- repr,
|
|
|
- Ipv6RoutingRepr::Rpl {
|
|
|
- segments_left: 3,
|
|
|
- cmpr_i: 9,
|
|
|
- cmpr_e: 9,
|
|
|
- pad: 3,
|
|
|
- addresses: &[
|
|
|
- 0x05, 0x00, 0x05, 0x00, 0x05, 0x00, 0x05, 0x06, 0x00, 0x06, 0x00, 0x06,
|
|
|
- 0x00, 0x06, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00
|
|
|
- ],
|
|
|
- }
|
|
|
- );
|
|
|
- }
|
|
|
-
|
|
|
- #[test]
|
|
|
- fn test_source_routing_emit() {
|
|
|
- let routing_hdr = Ipv6RoutingRepr::Rpl {
|
|
|
- segments_left: 3,
|
|
|
- cmpr_i: 9,
|
|
|
- cmpr_e: 9,
|
|
|
- pad: 3,
|
|
|
- addresses: &[
|
|
|
- 0x05, 0x00, 0x05, 0x00, 0x05, 0x00, 0x05, 0x06, 0x00, 0x06, 0x00, 0x06, 0x00,
|
|
|
- 0x06, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00,
|
|
|
- ],
|
|
|
- };
|
|
|
-
|
|
|
- let ext_hdr = ExtHeaderRepr {
|
|
|
- ext_header_id: ExtHeaderId::RoutingHeader,
|
|
|
- next_header: NextHeader::Compressed,
|
|
|
- length: routing_hdr.buffer_len() as u8,
|
|
|
- };
|
|
|
-
|
|
|
- let mut buffer = vec![0u8; ext_hdr.buffer_len() + routing_hdr.buffer_len()];
|
|
|
- ext_hdr.emit(&mut ExtHeaderPacket::new_unchecked(
|
|
|
- &mut buffer[..ext_hdr.buffer_len()],
|
|
|
- ));
|
|
|
- routing_hdr.emit(&mut Ipv6RoutingHeader::new_unchecked(
|
|
|
- &mut buffer[ext_hdr.buffer_len()..],
|
|
|
- ));
|
|
|
-
|
|
|
- assert_eq!(&buffer[..], ROUTING_SR_PACKET);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// A read/write wrapper around a 6LoWPAN_NHC UDP frame.
|
|
|
- /// [RFC 6282 § 4.3] specifies the format of the header.
|
|
|
- ///
|
|
|
- /// The base header has the following formath:
|
|
|
- /// ```txt
|
|
|
- /// 0 1 2 3 4 5 6 7
|
|
|
- /// +---+---+---+---+---+---+---+---+
|
|
|
- /// | 1 | 1 | 1 | 1 | 0 | C | P |
|
|
|
- /// +---+---+---+---+---+---+---+---+
|
|
|
- /// With:
|
|
|
- /// - C: checksum, specifies if the checksum is elided.
|
|
|
- /// - P: ports, specifies if the ports are elided.
|
|
|
- /// ```
|
|
|
- ///
|
|
|
- /// [RFC 6282 § 4.3]: https://datatracker.ietf.org/doc/html/rfc6282#section-4.3
|
|
|
- #[derive(Debug, Clone)]
|
|
|
- #[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
|
|
- pub struct UdpNhcPacket<T: AsRef<[u8]>> {
|
|
|
- buffer: T,
|
|
|
- }
|
|
|
-
|
|
|
- impl<T: AsRef<[u8]>> UdpNhcPacket<T> {
|
|
|
- /// Input a raw octet buffer with a LOWPAN_NHC frame structure for UDP.
|
|
|
- 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()?;
|
|
|
- Ok(packet)
|
|
|
- }
|
|
|
-
|
|
|
- /// Ensure that no accessor method will panic if called.
|
|
|
- /// Returns `Err(Error::Truncated)` 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);
|
|
|
- }
|
|
|
-
|
|
|
- let index = 1 + self.ports_size() + self.checksum_size();
|
|
|
- if index > buffer.len() {
|
|
|
- return Err(Error);
|
|
|
- }
|
|
|
-
|
|
|
- Ok(())
|
|
|
- }
|
|
|
-
|
|
|
- /// Consumes the frame, returning the underlying buffer.
|
|
|
- pub fn into_inner(self) -> T {
|
|
|
- self.buffer
|
|
|
- }
|
|
|
-
|
|
|
- get_field!(dispatch_field, 0b11111, 3);
|
|
|
- get_field!(checksum_field, 0b1, 2);
|
|
|
- get_field!(ports_field, 0b11, 0);
|
|
|
-
|
|
|
- /// Returns the index of the start of the next header compressed fields.
|
|
|
- const fn nhc_fields_start(&self) -> usize {
|
|
|
- 1
|
|
|
- }
|
|
|
-
|
|
|
- /// Return the source port number.
|
|
|
- pub fn src_port(&self) -> u16 {
|
|
|
- match self.ports_field() {
|
|
|
- 0b00 | 0b01 => {
|
|
|
- // The full 16 bits are carried in-line.
|
|
|
- let data = self.buffer.as_ref();
|
|
|
- let start = self.nhc_fields_start();
|
|
|
-
|
|
|
- NetworkEndian::read_u16(&data[start..start + 2])
|
|
|
- }
|
|
|
- 0b10 => {
|
|
|
- // The first 8 bits are elided.
|
|
|
- let data = self.buffer.as_ref();
|
|
|
- let start = self.nhc_fields_start();
|
|
|
-
|
|
|
- 0xf000 + data[start] as u16
|
|
|
- }
|
|
|
- 0b11 => {
|
|
|
- // The first 12 bits are elided.
|
|
|
- let data = self.buffer.as_ref();
|
|
|
- let start = self.nhc_fields_start();
|
|
|
-
|
|
|
- 0xf0b0 + (data[start] >> 4) as u16
|
|
|
- }
|
|
|
- _ => unreachable!(),
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// Return the destination port number.
|
|
|
- pub fn dst_port(&self) -> u16 {
|
|
|
- match self.ports_field() {
|
|
|
- 0b00 => {
|
|
|
- // The full 16 bits are carried in-line.
|
|
|
- let data = self.buffer.as_ref();
|
|
|
- let idx = self.nhc_fields_start();
|
|
|
-
|
|
|
- NetworkEndian::read_u16(&data[idx + 2..idx + 4])
|
|
|
- }
|
|
|
- 0b01 => {
|
|
|
- // The first 8 bits are elided.
|
|
|
- let data = self.buffer.as_ref();
|
|
|
- let idx = self.nhc_fields_start();
|
|
|
-
|
|
|
- 0xf000 + data[idx] as u16
|
|
|
- }
|
|
|
- 0b10 => {
|
|
|
- // The full 16 bits are carried in-line.
|
|
|
- let data = self.buffer.as_ref();
|
|
|
- let idx = self.nhc_fields_start();
|
|
|
-
|
|
|
- NetworkEndian::read_u16(&data[idx + 1..idx + 1 + 2])
|
|
|
- }
|
|
|
- 0b11 => {
|
|
|
- // The first 12 bits are elided.
|
|
|
- let data = self.buffer.as_ref();
|
|
|
- let start = self.nhc_fields_start();
|
|
|
-
|
|
|
- 0xf0b0 + (data[start] & 0xff) as u16
|
|
|
- }
|
|
|
- _ => unreachable!(),
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// Return the checksum.
|
|
|
- pub fn checksum(&self) -> Option<u16> {
|
|
|
- if self.checksum_field() == 0b0 {
|
|
|
- // The first 12 bits are elided.
|
|
|
- let data = self.buffer.as_ref();
|
|
|
- let start = self.nhc_fields_start() + self.ports_size();
|
|
|
- Some(NetworkEndian::read_u16(&data[start..start + 2]))
|
|
|
- } else {
|
|
|
- // The checksum is elided and needs to be recomputed on the 6LoWPAN termination point.
|
|
|
- None
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // Return the size of the checksum field.
|
|
|
- pub(crate) fn checksum_size(&self) -> usize {
|
|
|
- match self.checksum_field() {
|
|
|
- 0b0 => 2,
|
|
|
- 0b1 => 0,
|
|
|
- _ => unreachable!(),
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// Returns the total size of both port numbers.
|
|
|
- pub(crate) fn ports_size(&self) -> usize {
|
|
|
- match self.ports_field() {
|
|
|
- 0b00 => 4, // 16 bits + 16 bits
|
|
|
- 0b01 => 3, // 16 bits + 8 bits
|
|
|
- 0b10 => 3, // 8 bits + 16 bits
|
|
|
- 0b11 => 1, // 4 bits + 4 bits
|
|
|
- _ => unreachable!(),
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- impl<'a, T: AsRef<[u8]> + ?Sized> UdpNhcPacket<&'a T> {
|
|
|
- /// Return a pointer to the payload.
|
|
|
- pub fn payload(&self) -> &'a [u8] {
|
|
|
- let start = 1 + self.ports_size() + self.checksum_size();
|
|
|
- &self.buffer.as_ref()[start..]
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- impl<T: AsRef<[u8]> + AsMut<[u8]>> UdpNhcPacket<T> {
|
|
|
- /// Return a mutable pointer to the payload.
|
|
|
- pub fn payload_mut(&mut self) -> &mut [u8] {
|
|
|
- let start = 1 + self.ports_size() + 2; // XXX(thvdveld): we assume we put the checksum inlined.
|
|
|
- &mut self.buffer.as_mut()[start..]
|
|
|
- }
|
|
|
-
|
|
|
- /// Set the dispatch field to `0b11110`.
|
|
|
- fn set_dispatch_field(&mut self) {
|
|
|
- let data = self.buffer.as_mut();
|
|
|
- data[0] = (data[0] & !(0b11111 << 3)) | (DISPATCH_UDP_HEADER << 3);
|
|
|
- }
|
|
|
-
|
|
|
- set_field!(set_checksum_field, 0b1, 2);
|
|
|
- set_field!(set_ports_field, 0b11, 0);
|
|
|
-
|
|
|
- fn set_ports(&mut self, src_port: u16, dst_port: u16) {
|
|
|
- let mut idx = 1;
|
|
|
-
|
|
|
- match (src_port, dst_port) {
|
|
|
- (0xf0b0..=0xf0bf, 0xf0b0..=0xf0bf) => {
|
|
|
- // We can compress both the source and destination ports.
|
|
|
- self.set_ports_field(0b11);
|
|
|
- let data = self.buffer.as_mut();
|
|
|
- data[idx] = (((src_port - 0xf0b0) as u8) << 4) & ((dst_port - 0xf0b0) as u8);
|
|
|
- }
|
|
|
- (0xf000..=0xf0ff, _) => {
|
|
|
- // We can compress the source port, but not the destination port.
|
|
|
- self.set_ports_field(0b10);
|
|
|
- let data = self.buffer.as_mut();
|
|
|
- data[idx] = (src_port - 0xf000) as u8;
|
|
|
- idx += 1;
|
|
|
-
|
|
|
- NetworkEndian::write_u16(&mut data[idx..idx + 2], dst_port);
|
|
|
- }
|
|
|
- (_, 0xf000..=0xf0ff) => {
|
|
|
- // We can compress the destination port, but not the source port.
|
|
|
- self.set_ports_field(0b01);
|
|
|
- let data = self.buffer.as_mut();
|
|
|
- NetworkEndian::write_u16(&mut data[idx..idx + 2], src_port);
|
|
|
- idx += 2;
|
|
|
- data[idx] = (dst_port - 0xf000) as u8;
|
|
|
- }
|
|
|
- (_, _) => {
|
|
|
- // We cannot compress any port.
|
|
|
- self.set_ports_field(0b00);
|
|
|
- let data = self.buffer.as_mut();
|
|
|
- NetworkEndian::write_u16(&mut data[idx..idx + 2], src_port);
|
|
|
- idx += 2;
|
|
|
- NetworkEndian::write_u16(&mut data[idx..idx + 2], dst_port);
|
|
|
- }
|
|
|
- };
|
|
|
- }
|
|
|
-
|
|
|
- fn set_checksum(&mut self, checksum: u16) {
|
|
|
- self.set_checksum_field(0b0);
|
|
|
- let idx = 1 + self.ports_size();
|
|
|
- let data = self.buffer.as_mut();
|
|
|
- NetworkEndian::write_u16(&mut data[idx..idx + 2], checksum);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// A high-level representation of a 6LoWPAN NHC UDP header.
|
|
|
- #[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
|
|
- #[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
|
|
- pub struct UdpNhcRepr(pub UdpRepr);
|
|
|
-
|
|
|
- impl<'a> UdpNhcRepr {
|
|
|
- /// Parse a 6LoWPAN NHC UDP packet and return a high-level representation.
|
|
|
- pub fn parse<T: AsRef<[u8]> + ?Sized>(
|
|
|
- packet: &UdpNhcPacket<&'a T>,
|
|
|
- src_addr: &ipv6::Address,
|
|
|
- dst_addr: &ipv6::Address,
|
|
|
- checksum_caps: &ChecksumCapabilities,
|
|
|
- ) -> Result<Self> {
|
|
|
- packet.check_len()?;
|
|
|
-
|
|
|
- if packet.dispatch_field() != DISPATCH_UDP_HEADER {
|
|
|
- return Err(Error);
|
|
|
- }
|
|
|
-
|
|
|
- if checksum_caps.udp.rx() {
|
|
|
- let payload_len = packet.payload().len();
|
|
|
- let chk_sum = !checksum::combine(&[
|
|
|
- checksum::pseudo_header(
|
|
|
- &IpAddress::Ipv6(*src_addr),
|
|
|
- &IpAddress::Ipv6(*dst_addr),
|
|
|
- crate::wire::ip::Protocol::Udp,
|
|
|
- payload_len as u32 + 8,
|
|
|
- ),
|
|
|
- packet.src_port(),
|
|
|
- packet.dst_port(),
|
|
|
- payload_len as u16 + 8,
|
|
|
- checksum::data(packet.payload()),
|
|
|
- ]);
|
|
|
-
|
|
|
- if let Some(checksum) = packet.checksum() {
|
|
|
- if chk_sum != checksum {
|
|
|
- return Err(Error);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- Ok(Self(UdpRepr {
|
|
|
- src_port: packet.src_port(),
|
|
|
- dst_port: packet.dst_port(),
|
|
|
- }))
|
|
|
- }
|
|
|
-
|
|
|
- /// Return the length of a packet that will be emitted from this high-level representation.
|
|
|
- pub fn header_len(&self) -> usize {
|
|
|
- let mut len = 1; // The minimal header size
|
|
|
-
|
|
|
- len += 2; // XXX We assume we will add the checksum at the end
|
|
|
-
|
|
|
- // Check if we can compress the source and destination ports
|
|
|
- match (self.src_port, self.dst_port) {
|
|
|
- (0xf0b0..=0xf0bf, 0xf0b0..=0xf0bf) => len + 1,
|
|
|
- (0xf000..=0xf0ff, _) | (_, 0xf000..=0xf0ff) => len + 3,
|
|
|
- (_, _) => len + 4,
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// Emit a high-level representation into a LOWPAN_NHC UDP header.
|
|
|
- pub fn emit<T: AsRef<[u8]> + AsMut<[u8]>>(
|
|
|
- &self,
|
|
|
- packet: &mut UdpNhcPacket<T>,
|
|
|
- src_addr: &Address,
|
|
|
- dst_addr: &Address,
|
|
|
- payload_len: usize,
|
|
|
- emit_payload: impl FnOnce(&mut [u8]),
|
|
|
- ) {
|
|
|
- packet.set_dispatch_field();
|
|
|
- packet.set_ports(self.src_port, self.dst_port);
|
|
|
- emit_payload(packet.payload_mut());
|
|
|
-
|
|
|
- let chk_sum = !checksum::combine(&[
|
|
|
- checksum::pseudo_header(
|
|
|
- &IpAddress::Ipv6(*src_addr),
|
|
|
- &IpAddress::Ipv6(*dst_addr),
|
|
|
- crate::wire::ip::Protocol::Udp,
|
|
|
- payload_len as u32 + 8,
|
|
|
- ),
|
|
|
- self.src_port,
|
|
|
- self.dst_port,
|
|
|
- payload_len as u16 + 8,
|
|
|
- checksum::data(packet.payload_mut()),
|
|
|
- ]);
|
|
|
-
|
|
|
- packet.set_checksum(chk_sum);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- impl core::ops::Deref for UdpNhcRepr {
|
|
|
- type Target = UdpRepr;
|
|
|
-
|
|
|
- fn deref(&self) -> &Self::Target {
|
|
|
- &self.0
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- impl core::ops::DerefMut for UdpNhcRepr {
|
|
|
- fn deref_mut(&mut self) -> &mut Self::Target {
|
|
|
- &mut self.0
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- #[cfg(test)]
|
|
|
- mod test {
|
|
|
- use super::*;
|
|
|
-
|
|
|
- #[test]
|
|
|
- fn ext_header_nhc_fields() {
|
|
|
- let bytes = [0xe3, 0x06, 0x03, 0x00, 0xff, 0x00, 0x00, 0x00];
|
|
|
-
|
|
|
- let packet = ExtHeaderPacket::new_checked(&bytes[..]).unwrap();
|
|
|
- assert_eq!(packet.next_header_size(), 0);
|
|
|
- assert_eq!(packet.dispatch_field(), DISPATCH_EXT_HEADER);
|
|
|
- assert_eq!(packet.extension_header_id(), ExtHeaderId::RoutingHeader);
|
|
|
-
|
|
|
- assert_eq!(packet.payload(), [0x03, 0x00, 0xff, 0x00, 0x00, 0x00]);
|
|
|
- }
|
|
|
-
|
|
|
- #[test]
|
|
|
- fn ext_header_emit() {
|
|
|
- let ext_header = ExtHeaderRepr {
|
|
|
- ext_header_id: ExtHeaderId::RoutingHeader,
|
|
|
- next_header: NextHeader::Compressed,
|
|
|
- length: 6,
|
|
|
- };
|
|
|
-
|
|
|
- let len = ext_header.buffer_len();
|
|
|
- let mut buffer = [0u8; 127];
|
|
|
- let mut packet = ExtHeaderPacket::new_unchecked(&mut buffer[..len]);
|
|
|
- ext_header.emit(&mut packet);
|
|
|
-
|
|
|
- assert_eq!(packet.dispatch_field(), DISPATCH_EXT_HEADER);
|
|
|
- assert_eq!(packet.next_header(), NextHeader::Compressed);
|
|
|
- assert_eq!(packet.extension_header_id(), ExtHeaderId::RoutingHeader);
|
|
|
- }
|
|
|
-
|
|
|
- #[test]
|
|
|
- fn udp_nhc_fields() {
|
|
|
- let bytes = [0xf0, 0x16, 0x2e, 0x22, 0x3d, 0x28, 0xc4];
|
|
|
-
|
|
|
- let packet = UdpNhcPacket::new_checked(&bytes[..]).unwrap();
|
|
|
- assert_eq!(packet.dispatch_field(), DISPATCH_UDP_HEADER);
|
|
|
- assert_eq!(packet.checksum(), Some(0x28c4));
|
|
|
- assert_eq!(packet.src_port(), 5678);
|
|
|
- assert_eq!(packet.dst_port(), 8765);
|
|
|
- }
|
|
|
-
|
|
|
- #[test]
|
|
|
- fn udp_emit() {
|
|
|
- let udp = UdpNhcRepr(UdpRepr {
|
|
|
- src_port: 0xf0b1,
|
|
|
- dst_port: 0xf001,
|
|
|
- });
|
|
|
-
|
|
|
- let payload = b"Hello World!";
|
|
|
-
|
|
|
- let src_addr = ipv6::Address::default();
|
|
|
- let dst_addr = ipv6::Address::default();
|
|
|
-
|
|
|
- let len = udp.header_len() + payload.len();
|
|
|
- let mut buffer = [0u8; 127];
|
|
|
- let mut packet = UdpNhcPacket::new_unchecked(&mut buffer[..len]);
|
|
|
- udp.emit(&mut packet, &src_addr, &dst_addr, payload.len(), |buf| {
|
|
|
- buf.copy_from_slice(&payload[..])
|
|
|
- });
|
|
|
-
|
|
|
- assert_eq!(packet.dispatch_field(), DISPATCH_UDP_HEADER);
|
|
|
- assert_eq!(packet.src_port(), 0xf0b1);
|
|
|
- assert_eq!(packet.dst_port(), 0xf001);
|
|
|
- assert_eq!(packet.payload_mut(), b"Hello World!");
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-#[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);
|
|
|
- }
|
|
|
-}
|