| //! 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 Extension 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 = 2; |
| 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!(), |
| } |
| } |
| |
| /// Return the length field. |
| pub fn length(&self) -> u8 { |
| self.buffer.as_ref()[1 + self.next_header_size()] |
| } |
| |
| /// 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. |
| NextHeader::Uncompressed(IpProtocol::from(self.buffer.as_ref()[1])) |
| } |
| } |
| |
| /// 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(); |
| let len = self.length() as usize; |
| &self.buffer.as_ref()[start..][..len] |
| } |
| } |
| |
| 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(); |
| let len = self.length() as usize; |
| &mut self.buffer.as_mut()[start..][..len] |
| } |
| |
| /// 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 { |
| pub ext_header_id: ExtHeaderId, |
| pub next_header: NextHeader, |
| pub 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.length(), |
| }) |
| } |
| |
| /// 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 representation 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 format: |
| /// ```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]), |
| checksum_caps: &ChecksumCapabilities, |
| ) { |
| packet.set_dispatch_field(); |
| packet.set_ports(self.src_port, self.dst_port); |
| emit_payload(packet.payload_mut()); |
| |
| if checksum_caps.udp.tx() { |
| 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_nh_inlined() { |
| let bytes = [0xe2, 0x3a, 0x6, 0x3, 0x0, 0xff, 0x0, 0x0, 0x0]; |
| |
| let packet = ExtHeaderPacket::new_checked(&bytes[..]).unwrap(); |
| assert_eq!(packet.next_header_size(), 1); |
| assert_eq!(packet.length(), 6); |
| assert_eq!(packet.dispatch_field(), DISPATCH_EXT_HEADER); |
| assert_eq!(packet.extension_header_id(), ExtHeaderId::RoutingHeader); |
| assert_eq!( |
| packet.next_header(), |
| NextHeader::Uncompressed(IpProtocol::Icmpv6) |
| ); |
| |
| assert_eq!(packet.payload(), [0x03, 0x00, 0xff, 0x00, 0x00, 0x00]); |
| } |
| |
| #[test] |
| fn ext_header_nh_elided() { |
| 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.length(), 6); |
| assert_eq!(packet.dispatch_field(), DISPATCH_EXT_HEADER); |
| assert_eq!(packet.extension_header_id(), ExtHeaderId::RoutingHeader); |
| assert_eq!(packet.next_header(), NextHeader::Compressed); |
| |
| 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[..]), |
| &ChecksumCapabilities::default(), |
| ); |
| |
| 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!"); |
| } |
| } |