//! 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, 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 { 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 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> { buffer: T, } impl> ExtHeaderPacket { /// 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 { 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 + AsMut<[u8]>> ExtHeaderPacket { /// 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 + ?Sized>(packet: &ExtHeaderPacket<&T>) -> Result { // 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 + AsMut<[u8]>>(&self, packet: &mut ExtHeaderPacket) { 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> { buffer: T, } impl> UdpNhcPacket { /// 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 { 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 { 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 + AsMut<[u8]>> UdpNhcPacket { /// 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 + ?Sized>( packet: &UdpNhcPacket<&'a T>, src_addr: &ipv6::Address, dst_addr: &ipv6::Address, checksum_caps: &ChecksumCapabilities, ) -> Result { 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_v6( src_addr, 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 + AsMut<[u8]>>( &self, packet: &mut UdpNhcPacket, 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_v6( src_addr, 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::UNSPECIFIED; let dst_addr = ipv6::Address::UNSPECIFIED; 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!"); } }