diff options
Diffstat (limited to 'src/wire/ipv6.rs')
-rw-r--r-- | src/wire/ipv6.rs | 1449 |
1 files changed, 1449 insertions, 0 deletions
diff --git a/src/wire/ipv6.rs b/src/wire/ipv6.rs new file mode 100644 index 0000000..236600d --- /dev/null +++ b/src/wire/ipv6.rs @@ -0,0 +1,1449 @@ +#![deny(missing_docs)] + +use byteorder::{ByteOrder, NetworkEndian}; +use core::fmt; + +use super::{Error, Result}; +use crate::wire::ip::pretty_print_ip_payload; +#[cfg(feature = "proto-ipv4")] +use crate::wire::ipv4; + +pub use super::IpProtocol as Protocol; + +/// Minimum MTU required of all links supporting IPv6. See [RFC 8200 § 5]. +/// +/// [RFC 8200 § 5]: https://tools.ietf.org/html/rfc8200#section-5 +pub const MIN_MTU: usize = 1280; + +/// Size of IPv6 adderess in octets. +/// +/// [RFC 8200 § 2]: https://www.rfc-editor.org/rfc/rfc4291#section-2 +pub const ADDR_SIZE: usize = 16; + +/// Size of IPv4-mapping prefix in octets. +/// +/// [RFC 8200 § 2]: https://www.rfc-editor.org/rfc/rfc4291#section-2 +pub const IPV4_MAPPED_PREFIX_SIZE: usize = ADDR_SIZE - 4; // 4 == ipv4::ADDR_SIZE , cannot DRY here because of dependency on a IPv4 module which is behind the feature + +/// The [scope] of an address. +/// +/// [scope]: https://www.rfc-editor.org/rfc/rfc4291#section-2.7 +#[repr(u8)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum Scope { + /// Interface Local scope + InterfaceLocal = 0x1, + /// Link local scope + LinkLocal = 0x2, + /// Administratively configured + AdminLocal = 0x4, + /// Single site scope + SiteLocal = 0x5, + /// Organization scope + OrganizationLocal = 0x8, + /// Global scope + Global = 0xE, + /// Unknown scope + Unknown = 0xFF, +} + +impl From<u8> for Scope { + fn from(value: u8) -> Self { + match value { + 0x1 => Self::InterfaceLocal, + 0x2 => Self::LinkLocal, + 0x4 => Self::AdminLocal, + 0x5 => Self::SiteLocal, + 0x8 => Self::OrganizationLocal, + 0xE => Self::Global, + _ => Self::Unknown, + } + } +} + +/// A sixteen-octet IPv6 address. +#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Default)] +pub struct Address(pub [u8; ADDR_SIZE]); + +impl Address { + /// The [unspecified address]. + /// + /// [unspecified address]: https://tools.ietf.org/html/rfc4291#section-2.5.2 + pub const UNSPECIFIED: Address = Address([0x00; ADDR_SIZE]); + + /// The link-local [all nodes multicast address]. + /// + /// [all nodes multicast address]: https://tools.ietf.org/html/rfc4291#section-2.7.1 + pub const LINK_LOCAL_ALL_NODES: Address = Address([ + 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, + ]); + + /// The link-local [all routers multicast address]. + /// + /// [all routers multicast address]: https://tools.ietf.org/html/rfc4291#section-2.7.1 + pub const LINK_LOCAL_ALL_ROUTERS: Address = Address([ + 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, + ]); + + /// The link-local [all RPL nodes multicast address]. + /// + /// [all RPL nodes multicast address]: https://www.rfc-editor.org/rfc/rfc6550.html#section-20.19 + pub const LINK_LOCAL_ALL_RPL_NODES: Address = Address([ + 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x1a, + ]); + + /// The [loopback address]. + /// + /// [loopback address]: https://tools.ietf.org/html/rfc4291#section-2.5.3 + pub const LOOPBACK: Address = Address([ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, + ]); + + /// The prefix used in [IPv4-mapped addresses]. + /// + /// [IPv4-mapped addresses]: https://www.rfc-editor.org/rfc/rfc4291#section-2.5.5.2 + pub const IPV4_MAPPED_PREFIX: [u8; IPV4_MAPPED_PREFIX_SIZE] = + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff]; + + /// Construct an IPv6 address from parts. + #[allow(clippy::too_many_arguments)] + pub const fn new( + a0: u16, + a1: u16, + a2: u16, + a3: u16, + a4: u16, + a5: u16, + a6: u16, + a7: u16, + ) -> Address { + Address([ + (a0 >> 8) as u8, + a0 as u8, + (a1 >> 8) as u8, + a1 as u8, + (a2 >> 8) as u8, + a2 as u8, + (a3 >> 8) as u8, + a3 as u8, + (a4 >> 8) as u8, + a4 as u8, + (a5 >> 8) as u8, + a5 as u8, + (a6 >> 8) as u8, + a6 as u8, + (a7 >> 8) as u8, + a7 as u8, + ]) + } + + /// Construct an IPv6 address from a sequence of octets, in big-endian. + /// + /// # Panics + /// The function panics if `data` is not sixteen octets long. + pub fn from_bytes(data: &[u8]) -> Address { + let mut bytes = [0; ADDR_SIZE]; + bytes.copy_from_slice(data); + Address(bytes) + } + + /// Construct an IPv6 address from a sequence of words, in big-endian. + /// + /// # Panics + /// The function panics if `data` is not 8 words long. + pub fn from_parts(data: &[u16]) -> Address { + assert!(data.len() >= 8); + let mut bytes = [0; ADDR_SIZE]; + for (word_idx, chunk) in bytes.chunks_mut(2).enumerate() { + NetworkEndian::write_u16(chunk, data[word_idx]); + } + Address(bytes) + } + + /// Write a IPv6 address to the given slice. + /// + /// # Panics + /// The function panics if `data` is not 8 words long. + pub fn write_parts(&self, data: &mut [u16]) { + assert!(data.len() >= 8); + for (i, chunk) in self.0.chunks(2).enumerate() { + data[i] = NetworkEndian::read_u16(chunk); + } + } + + /// Return an IPv6 address as a sequence of octets, in big-endian. + pub const fn as_bytes(&self) -> &[u8] { + &self.0 + } + + /// Query whether the IPv6 address is an [unicast address]. + /// + /// [unicast address]: https://tools.ietf.org/html/rfc4291#section-2.5 + pub fn is_unicast(&self) -> bool { + !(self.is_multicast() || self.is_unspecified()) + } + + /// Query whether the IPv6 address is a [global unicast address]. + /// + /// [global unicast address]: https://datatracker.ietf.org/doc/html/rfc3587 + pub const fn is_global_unicast(&self) -> bool { + (self.0[0] >> 5) == 0b001 + } + + /// Query whether the IPv6 address is a [multicast address]. + /// + /// [multicast address]: https://tools.ietf.org/html/rfc4291#section-2.7 + pub const fn is_multicast(&self) -> bool { + self.0[0] == 0xff + } + + /// Query whether the IPv6 address is the [unspecified address]. + /// + /// [unspecified address]: https://tools.ietf.org/html/rfc4291#section-2.5.2 + pub fn is_unspecified(&self) -> bool { + self.0 == [0x00; ADDR_SIZE] + } + + /// Query whether the IPv6 address is in the [link-local] scope. + /// + /// [link-local]: https://tools.ietf.org/html/rfc4291#section-2.5.6 + pub fn is_link_local(&self) -> bool { + self.0[0..8] == [0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] + } + + /// Query whether the IPv6 address is a [Unique Local Address] (ULA). + /// + /// [Unique Local Address]: https://tools.ietf.org/html/rfc4193 + pub fn is_unique_local(&self) -> bool { + (self.0[0] & 0b1111_1110) == 0xfc + } + + /// Query whether the IPv6 address is the [loopback address]. + /// + /// [loopback address]: https://tools.ietf.org/html/rfc4291#section-2.5.3 + pub fn is_loopback(&self) -> bool { + *self == Self::LOOPBACK + } + + /// Query whether the IPv6 address is an [IPv4 mapped IPv6 address]. + /// + /// [IPv4 mapped IPv6 address]: https://tools.ietf.org/html/rfc4291#section-2.5.5.2 + pub fn is_ipv4_mapped(&self) -> bool { + self.0[..IPV4_MAPPED_PREFIX_SIZE] == Self::IPV4_MAPPED_PREFIX + } + + #[cfg(feature = "proto-ipv4")] + /// Convert an IPv4 mapped IPv6 address to an IPv4 address. + pub fn as_ipv4(&self) -> Option<ipv4::Address> { + if self.is_ipv4_mapped() { + Some(ipv4::Address::from_bytes( + &self.0[IPV4_MAPPED_PREFIX_SIZE..], + )) + } else { + None + } + } + + /// Helper function used to mask an address given a prefix. + /// + /// # Panics + /// This function panics if `mask` is greater than 128. + pub(super) fn mask(&self, mask: u8) -> [u8; ADDR_SIZE] { + assert!(mask <= 128); + let mut bytes = [0u8; ADDR_SIZE]; + let idx = (mask as usize) / 8; + let modulus = (mask as usize) % 8; + let (first, second) = self.0.split_at(idx); + bytes[0..idx].copy_from_slice(first); + if idx < ADDR_SIZE { + let part = second[0]; + bytes[idx] = part & (!(0xff >> modulus) as u8); + } + bytes + } + + /// The solicited node for the given unicast address. + /// + /// # Panics + /// This function panics if the given address is not + /// unicast. + pub fn solicited_node(&self) -> Address { + assert!(self.is_unicast()); + Address([ + 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, + self.0[13], self.0[14], self.0[15], + ]) + } + + /// Return the scope of the address. + pub(crate) fn scope(&self) -> Scope { + if self.is_multicast() { + return Scope::from(self.as_bytes()[1] & 0b1111); + } + + if self.is_link_local() { + Scope::LinkLocal + } else if self.is_unique_local() || self.is_global_unicast() { + // ULA are considered global scope + // https://www.rfc-editor.org/rfc/rfc6724#section-3.1 + Scope::Global + } else { + Scope::Unknown + } + } + + /// Convert to an `IpAddress`. + /// + /// Same as `.into()`, but works in `const`. + pub const fn into_address(self) -> super::IpAddress { + super::IpAddress::Ipv6(self) + } +} + +#[cfg(feature = "std")] +impl From<::std::net::Ipv6Addr> for Address { + fn from(x: ::std::net::Ipv6Addr) -> Address { + Address(x.octets()) + } +} + +#[cfg(feature = "std")] +impl From<Address> for ::std::net::Ipv6Addr { + fn from(Address(x): Address) -> ::std::net::Ipv6Addr { + x.into() + } +} + +impl fmt::Display for Address { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if self.is_ipv4_mapped() { + return write!( + f, + "::ffff:{}.{}.{}.{}", + self.0[IPV4_MAPPED_PREFIX_SIZE + 0], + self.0[IPV4_MAPPED_PREFIX_SIZE + 1], + self.0[IPV4_MAPPED_PREFIX_SIZE + 2], + self.0[IPV4_MAPPED_PREFIX_SIZE + 3] + ); + } + + // The string representation of an IPv6 address should + // collapse a series of 16 bit sections that evaluate + // to 0 to "::" + // + // See https://tools.ietf.org/html/rfc4291#section-2.2 + // for details. + enum State { + Head, + HeadBody, + Tail, + TailBody, + } + let mut words = [0u16; 8]; + self.write_parts(&mut words); + let mut state = State::Head; + for word in words.iter() { + state = match (*word, &state) { + // Once a u16 equal to zero write a double colon and + // skip to the next non-zero u16. + (0, &State::Head) | (0, &State::HeadBody) => { + write!(f, "::")?; + State::Tail + } + // Continue iterating without writing any characters until + // we hit a non-zero value. + (0, &State::Tail) => State::Tail, + // When the state is Head or Tail write a u16 in hexadecimal + // without the leading colon if the value is not 0. + (_, &State::Head) => { + write!(f, "{word:x}")?; + State::HeadBody + } + (_, &State::Tail) => { + write!(f, "{word:x}")?; + State::TailBody + } + // Write the u16 with a leading colon when parsing a value + // that isn't the first in a section + (_, &State::HeadBody) | (_, &State::TailBody) => { + write!(f, ":{word:x}")?; + state + } + } + } + Ok(()) + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for Address { + fn format(&self, f: defmt::Formatter) { + if self.is_ipv4_mapped() { + return defmt::write!( + f, + "::ffff:{}.{}.{}.{}", + self.0[IPV4_MAPPED_PREFIX_SIZE + 0], + self.0[IPV4_MAPPED_PREFIX_SIZE + 1], + self.0[IPV4_MAPPED_PREFIX_SIZE + 2], + self.0[IPV4_MAPPED_PREFIX_SIZE + 3] + ); + } + + // The string representation of an IPv6 address should + // collapse a series of 16 bit sections that evaluate + // to 0 to "::" + // + // See https://tools.ietf.org/html/rfc4291#section-2.2 + // for details. + enum State { + Head, + HeadBody, + Tail, + TailBody, + } + let mut words = [0u16; 8]; + self.write_parts(&mut words); + let mut state = State::Head; + for word in words.iter() { + state = match (*word, &state) { + // Once a u16 equal to zero write a double colon and + // skip to the next non-zero u16. + (0, &State::Head) | (0, &State::HeadBody) => { + defmt::write!(f, "::"); + State::Tail + } + // Continue iterating without writing any characters until + // we hit a non-zero value. + (0, &State::Tail) => State::Tail, + // When the state is Head or Tail write a u16 in hexadecimal + // without the leading colon if the value is not 0. + (_, &State::Head) => { + defmt::write!(f, "{:x}", word); + State::HeadBody + } + (_, &State::Tail) => { + defmt::write!(f, "{:x}", word); + State::TailBody + } + // Write the u16 with a leading colon when parsing a value + // that isn't the first in a section + (_, &State::HeadBody) | (_, &State::TailBody) => { + defmt::write!(f, ":{:x}", word); + state + } + } + } + } +} + +#[cfg(feature = "proto-ipv4")] +/// Convert the given IPv4 address into a IPv4-mapped IPv6 address +impl From<ipv4::Address> for Address { + fn from(address: ipv4::Address) -> Self { + let mut b = [0_u8; ADDR_SIZE]; + b[..Self::IPV4_MAPPED_PREFIX.len()].copy_from_slice(&Self::IPV4_MAPPED_PREFIX); + b[Self::IPV4_MAPPED_PREFIX.len()..].copy_from_slice(&address.0); + Self(b) + } +} + +/// A specification of an IPv6 CIDR block, containing an address and a variable-length +/// subnet masking prefix length. +#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Default)] +pub struct Cidr { + address: Address, + prefix_len: u8, +} + +impl Cidr { + /// The [solicited node prefix]. + /// + /// [solicited node prefix]: https://tools.ietf.org/html/rfc4291#section-2.7.1 + pub const SOLICITED_NODE_PREFIX: Cidr = Cidr { + address: Address([ + 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x00, + 0x00, 0x00, + ]), + prefix_len: 104, + }; + + /// Create an IPv6 CIDR block from the given address and prefix length. + /// + /// # Panics + /// This function panics if the prefix length is larger than 128. + pub const fn new(address: Address, prefix_len: u8) -> Cidr { + assert!(prefix_len <= 128); + Cidr { + address, + prefix_len, + } + } + + /// Return the address of this IPv6 CIDR block. + pub const fn address(&self) -> Address { + self.address + } + + /// Return the prefix length of this IPv6 CIDR block. + pub const fn prefix_len(&self) -> u8 { + self.prefix_len + } + + /// Query whether the subnetwork described by this IPv6 CIDR block contains + /// the given address. + pub fn contains_addr(&self, addr: &Address) -> bool { + // right shift by 128 is not legal + if self.prefix_len == 0 { + return true; + } + + self.address.mask(self.prefix_len) == addr.mask(self.prefix_len) + } + + /// Query whether the subnetwork described by this IPV6 CIDR block contains + /// the subnetwork described by the given IPv6 CIDR block. + pub fn contains_subnet(&self, subnet: &Cidr) -> bool { + self.prefix_len <= subnet.prefix_len && self.contains_addr(&subnet.address) + } +} + +impl fmt::Display for Cidr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + // https://tools.ietf.org/html/rfc4291#section-2.3 + write!(f, "{}/{}", self.address, self.prefix_len) + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for Cidr { + fn format(&self, f: defmt::Formatter) { + defmt::write!(f, "{}/{=u8}", self.address, self.prefix_len); + } +} + +/// A read/write wrapper around an Internet Protocol version 6 packet buffer. +#[derive(Debug, PartialEq, Eq, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Packet<T: AsRef<[u8]>> { + buffer: T, +} + +// Ranges and constants describing the IPv6 header +// +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |Version| Traffic Class | Flow Label | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Payload Length | Next Header | Hop Limit | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | | +// + + +// | | +// + Source Address + +// | | +// + + +// | | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | | +// + + +// | | +// + Destination Address + +// | | +// + + +// | | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// +// See https://tools.ietf.org/html/rfc2460#section-3 for details. +mod field { + use crate::wire::field::*; + // 4-bit version number, 8-bit traffic class, and the + // 20-bit flow label. + pub const VER_TC_FLOW: Field = 0..4; + // 16-bit value representing the length of the payload. + // Note: Options are included in this length. + pub const LENGTH: Field = 4..6; + // 8-bit value identifying the type of header following this + // one. Note: The same numbers are used in IPv4. + pub const NXT_HDR: usize = 6; + // 8-bit value decremented by each node that forwards this + // packet. The packet is discarded when the value is 0. + pub const HOP_LIMIT: usize = 7; + // IPv6 address of the source node. + pub const SRC_ADDR: Field = 8..24; + // IPv6 address of the destination node. + pub const DST_ADDR: Field = 24..40; +} + +/// Length of an IPv6 header. +pub const HEADER_LEN: usize = field::DST_ADDR.end; + +impl<T: AsRef<[u8]>> Packet<T> { + /// Create a raw octet buffer with an IPv6 packet structure. + #[inline] + pub const fn new_unchecked(buffer: T) -> Packet<T> { + Packet { buffer } + } + + /// Shorthand for a combination of [new_unchecked] and [check_len]. + /// + /// [new_unchecked]: #method.new_unchecked + /// [check_len]: #method.check_len + #[inline] + pub fn new_checked(buffer: T) -> Result<Packet<T>> { + 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. + /// + /// The result of this check is invalidated by calling [set_payload_len]. + /// + /// [set_payload_len]: #method.set_payload_len + #[inline] + pub fn check_len(&self) -> Result<()> { + let len = self.buffer.as_ref().len(); + if len < field::DST_ADDR.end || len < self.total_len() { + Err(Error) + } else { + Ok(()) + } + } + + /// Consume the packet, returning the underlying buffer. + #[inline] + pub fn into_inner(self) -> T { + self.buffer + } + + /// Return the header length. + #[inline] + pub const fn header_len(&self) -> usize { + // This is not a strictly necessary function, but it makes + // code more readable. + field::DST_ADDR.end + } + + /// Return the version field. + #[inline] + pub fn version(&self) -> u8 { + let data = self.buffer.as_ref(); + data[field::VER_TC_FLOW.start] >> 4 + } + + /// Return the traffic class. + #[inline] + pub fn traffic_class(&self) -> u8 { + let data = self.buffer.as_ref(); + ((NetworkEndian::read_u16(&data[0..2]) & 0x0ff0) >> 4) as u8 + } + + /// Return the flow label field. + #[inline] + pub fn flow_label(&self) -> u32 { + let data = self.buffer.as_ref(); + NetworkEndian::read_u24(&data[1..4]) & 0x000fffff + } + + /// Return the payload length field. + #[inline] + pub fn payload_len(&self) -> u16 { + let data = self.buffer.as_ref(); + NetworkEndian::read_u16(&data[field::LENGTH]) + } + + /// Return the payload length added to the known header length. + #[inline] + pub fn total_len(&self) -> usize { + self.header_len() + self.payload_len() as usize + } + + /// Return the next header field. + #[inline] + pub fn next_header(&self) -> Protocol { + let data = self.buffer.as_ref(); + Protocol::from(data[field::NXT_HDR]) + } + + /// Return the hop limit field. + #[inline] + pub fn hop_limit(&self) -> u8 { + let data = self.buffer.as_ref(); + data[field::HOP_LIMIT] + } + + /// Return the source address field. + #[inline] + pub fn src_addr(&self) -> Address { + let data = self.buffer.as_ref(); + Address::from_bytes(&data[field::SRC_ADDR]) + } + + /// Return the destination address field. + #[inline] + pub fn dst_addr(&self) -> Address { + let data = self.buffer.as_ref(); + Address::from_bytes(&data[field::DST_ADDR]) + } +} + +impl<'a, T: AsRef<[u8]> + ?Sized> Packet<&'a T> { + /// Return a pointer to the payload. + #[inline] + pub fn payload(&self) -> &'a [u8] { + let data = self.buffer.as_ref(); + let range = self.header_len()..self.total_len(); + &data[range] + } +} + +impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> { + /// Set the version field. + #[inline] + pub fn set_version(&mut self, value: u8) { + let data = self.buffer.as_mut(); + // Make sure to retain the lower order bits which contain + // the higher order bits of the traffic class + data[0] = (data[0] & 0x0f) | ((value & 0x0f) << 4); + } + + /// Set the traffic class field. + #[inline] + pub fn set_traffic_class(&mut self, value: u8) { + let data = self.buffer.as_mut(); + // Put the higher order 4-bits of value in the lower order + // 4-bits of the first byte + data[0] = (data[0] & 0xf0) | ((value & 0xf0) >> 4); + // Put the lower order 4-bits of value in the higher order + // 4-bits of the second byte + data[1] = (data[1] & 0x0f) | ((value & 0x0f) << 4); + } + + /// Set the flow label field. + #[inline] + pub fn set_flow_label(&mut self, value: u32) { + let data = self.buffer.as_mut(); + // Retain the lower order 4-bits of the traffic class + let raw = (((data[1] & 0xf0) as u32) << 16) | (value & 0x0fffff); + NetworkEndian::write_u24(&mut data[1..4], raw); + } + + /// Set the payload length field. + #[inline] + pub fn set_payload_len(&mut self, value: u16) { + let data = self.buffer.as_mut(); + NetworkEndian::write_u16(&mut data[field::LENGTH], value); + } + + /// Set the next header field. + #[inline] + pub fn set_next_header(&mut self, value: Protocol) { + let data = self.buffer.as_mut(); + data[field::NXT_HDR] = value.into(); + } + + /// Set the hop limit field. + #[inline] + pub fn set_hop_limit(&mut self, value: u8) { + let data = self.buffer.as_mut(); + data[field::HOP_LIMIT] = value; + } + + /// Set the source address field. + #[inline] + pub fn set_src_addr(&mut self, value: Address) { + let data = self.buffer.as_mut(); + data[field::SRC_ADDR].copy_from_slice(value.as_bytes()); + } + + /// Set the destination address field. + #[inline] + pub fn set_dst_addr(&mut self, value: Address) { + let data = self.buffer.as_mut(); + data[field::DST_ADDR].copy_from_slice(value.as_bytes()); + } + + /// Return a mutable pointer to the payload. + #[inline] + pub fn payload_mut(&mut self) -> &mut [u8] { + let range = self.header_len()..self.total_len(); + let data = self.buffer.as_mut(); + &mut data[range] + } +} + +impl<'a, T: AsRef<[u8]> + ?Sized> fmt::Display for Packet<&'a T> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match Repr::parse(self) { + Ok(repr) => write!(f, "{repr}"), + Err(err) => { + write!(f, "IPv6 ({err})")?; + Ok(()) + } + } + } +} + +impl<T: AsRef<[u8]>> AsRef<[u8]> for Packet<T> { + fn as_ref(&self) -> &[u8] { + self.buffer.as_ref() + } +} + +/// A high-level representation of an Internet Protocol version 6 packet header. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct Repr { + /// IPv6 address of the source node. + pub src_addr: Address, + /// IPv6 address of the destination node. + pub dst_addr: Address, + /// Protocol contained in the next header. + pub next_header: Protocol, + /// Length of the payload including the extension headers. + pub payload_len: usize, + /// The 8-bit hop limit field. + pub hop_limit: u8, +} + +impl Repr { + /// Parse an Internet Protocol version 6 packet and return a high-level representation. + pub fn parse<T: AsRef<[u8]> + ?Sized>(packet: &Packet<&T>) -> Result<Repr> { + // Ensure basic accessors will work + packet.check_len()?; + if packet.version() != 6 { + return Err(Error); + } + Ok(Repr { + src_addr: packet.src_addr(), + dst_addr: packet.dst_addr(), + next_header: packet.next_header(), + payload_len: packet.payload_len() as usize, + hop_limit: packet.hop_limit(), + }) + } + + /// Return the length of a header that will be emitted from this high-level representation. + pub const fn buffer_len(&self) -> usize { + // This function is not strictly necessary, but it can make client code more readable. + field::DST_ADDR.end + } + + /// Emit a high-level representation into an Internet Protocol version 6 packet. + pub fn emit<T: AsRef<[u8]> + AsMut<[u8]>>(&self, packet: &mut Packet<T>) { + // Make no assumptions about the original state of the packet buffer. + // Make sure to set every byte. + packet.set_version(6); + packet.set_traffic_class(0); + packet.set_flow_label(0); + packet.set_payload_len(self.payload_len as u16); + packet.set_hop_limit(self.hop_limit); + packet.set_next_header(self.next_header); + packet.set_src_addr(self.src_addr); + packet.set_dst_addr(self.dst_addr); + } +} + +impl fmt::Display for Repr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "IPv6 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, + "IPv6 src={} dst={} nxt_hdr={} hop_limit={}", + self.src_addr, + self.dst_addr, + self.next_header, + self.hop_limit + ) + } +} + +use crate::wire::pretty_print::{PrettyIndent, PrettyPrint}; + +// TODO: This is very similar to the implementation for IPv4. Make +// a way to have less copy and pasted code here. +impl<T: AsRef<[u8]>> PrettyPrint for Packet<T> { + fn pretty_print( + buffer: &dyn AsRef<[u8]>, + f: &mut fmt::Formatter, + indent: &mut PrettyIndent, + ) -> fmt::Result { + let (ip_repr, payload) = match Packet::new_checked(buffer) { + Err(err) => return write!(f, "{indent}({err})"), + Ok(ip_packet) => match Repr::parse(&ip_packet) { + Err(_) => return Ok(()), + Ok(ip_repr) => { + write!(f, "{indent}{ip_repr}")?; + (ip_repr, ip_packet.payload()) + } + }, + }; + + pretty_print_ip_payload(f, indent, ip_repr, payload) + } +} + +#[cfg(test)] +mod test { + use super::Error; + use super::{Address, Cidr}; + use super::{Packet, Protocol, Repr}; + use crate::wire::pretty_print::PrettyPrinter; + + #[cfg(feature = "proto-ipv4")] + use crate::wire::ipv4::Address as Ipv4Address; + + const LINK_LOCAL_ADDR: Address = Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1); + const UNIQUE_LOCAL_ADDR: Address = Address::new(0xfd00, 0, 0, 201, 1, 1, 1, 1); + const GLOBAL_UNICAST_ADDR: Address = Address::new(0x2001, 0xdb8, 0x3, 0, 0, 0, 0, 1); + + #[test] + fn test_basic_multicast() { + assert!(!Address::LINK_LOCAL_ALL_ROUTERS.is_unspecified()); + assert!(Address::LINK_LOCAL_ALL_ROUTERS.is_multicast()); + assert!(!Address::LINK_LOCAL_ALL_ROUTERS.is_link_local()); + assert!(!Address::LINK_LOCAL_ALL_ROUTERS.is_loopback()); + assert!(!Address::LINK_LOCAL_ALL_ROUTERS.is_unique_local()); + assert!(!Address::LINK_LOCAL_ALL_ROUTERS.is_global_unicast()); + assert!(!Address::LINK_LOCAL_ALL_NODES.is_unspecified()); + assert!(Address::LINK_LOCAL_ALL_NODES.is_multicast()); + assert!(!Address::LINK_LOCAL_ALL_NODES.is_link_local()); + assert!(!Address::LINK_LOCAL_ALL_NODES.is_loopback()); + assert!(!Address::LINK_LOCAL_ALL_NODES.is_unique_local()); + assert!(!Address::LINK_LOCAL_ALL_NODES.is_global_unicast()); + } + + #[test] + fn test_basic_link_local() { + assert!(!LINK_LOCAL_ADDR.is_unspecified()); + assert!(!LINK_LOCAL_ADDR.is_multicast()); + assert!(LINK_LOCAL_ADDR.is_link_local()); + assert!(!LINK_LOCAL_ADDR.is_loopback()); + assert!(!LINK_LOCAL_ADDR.is_unique_local()); + assert!(!LINK_LOCAL_ADDR.is_global_unicast()); + } + + #[test] + fn test_basic_loopback() { + assert!(!Address::LOOPBACK.is_unspecified()); + assert!(!Address::LOOPBACK.is_multicast()); + assert!(!Address::LOOPBACK.is_link_local()); + assert!(Address::LOOPBACK.is_loopback()); + assert!(!Address::LOOPBACK.is_unique_local()); + assert!(!Address::LOOPBACK.is_global_unicast()); + } + + #[test] + fn test_unique_local() { + assert!(!UNIQUE_LOCAL_ADDR.is_unspecified()); + assert!(!UNIQUE_LOCAL_ADDR.is_multicast()); + assert!(!UNIQUE_LOCAL_ADDR.is_link_local()); + assert!(!UNIQUE_LOCAL_ADDR.is_loopback()); + assert!(UNIQUE_LOCAL_ADDR.is_unique_local()); + assert!(!UNIQUE_LOCAL_ADDR.is_global_unicast()); + } + + #[test] + fn test_global_unicast() { + assert!(!GLOBAL_UNICAST_ADDR.is_unspecified()); + assert!(!GLOBAL_UNICAST_ADDR.is_multicast()); + assert!(!GLOBAL_UNICAST_ADDR.is_link_local()); + assert!(!GLOBAL_UNICAST_ADDR.is_loopback()); + assert!(!GLOBAL_UNICAST_ADDR.is_unique_local()); + assert!(GLOBAL_UNICAST_ADDR.is_global_unicast()); + } + + #[test] + fn test_address_format() { + assert_eq!("ff02::1", format!("{}", Address::LINK_LOCAL_ALL_NODES)); + assert_eq!("fe80::1", format!("{LINK_LOCAL_ADDR}")); + assert_eq!( + "fe80::7f00:0:1", + format!( + "{}", + Address::new(0xfe80, 0, 0, 0, 0, 0x7f00, 0x0000, 0x0001) + ) + ); + assert_eq!("::", format!("{}", Address::UNSPECIFIED)); + assert_eq!("::1", format!("{}", Address::LOOPBACK)); + + #[cfg(feature = "proto-ipv4")] + assert_eq!( + "::ffff:192.168.1.1", + format!("{}", Address::from(Ipv4Address::new(192, 168, 1, 1))) + ); + } + + #[test] + fn test_new() { + assert_eq!( + Address::new(0xff02, 0, 0, 0, 0, 0, 0, 1), + Address::LINK_LOCAL_ALL_NODES + ); + assert_eq!( + Address::new(0xff02, 0, 0, 0, 0, 0, 0, 2), + Address::LINK_LOCAL_ALL_ROUTERS + ); + assert_eq!(Address::new(0, 0, 0, 0, 0, 0, 0, 1), Address::LOOPBACK); + assert_eq!(Address::new(0, 0, 0, 0, 0, 0, 0, 0), Address::UNSPECIFIED); + assert_eq!(Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 1), LINK_LOCAL_ADDR); + } + + #[test] + fn test_from_parts() { + assert_eq!( + Address::from_parts(&[0xff02, 0, 0, 0, 0, 0, 0, 1]), + Address::LINK_LOCAL_ALL_NODES + ); + assert_eq!( + Address::from_parts(&[0xff02, 0, 0, 0, 0, 0, 0, 2]), + Address::LINK_LOCAL_ALL_ROUTERS + ); + assert_eq!( + Address::from_parts(&[0, 0, 0, 0, 0, 0, 0, 1]), + Address::LOOPBACK + ); + assert_eq!( + Address::from_parts(&[0, 0, 0, 0, 0, 0, 0, 0]), + Address::UNSPECIFIED + ); + assert_eq!( + Address::from_parts(&[0xfe80, 0, 0, 0, 0, 0, 0, 1]), + LINK_LOCAL_ADDR + ); + } + + #[test] + fn test_write_parts() { + let mut bytes = [0u16; 8]; + { + Address::LOOPBACK.write_parts(&mut bytes); + assert_eq!(Address::LOOPBACK, Address::from_parts(&bytes)); + } + { + Address::LINK_LOCAL_ALL_ROUTERS.write_parts(&mut bytes); + assert_eq!(Address::LINK_LOCAL_ALL_ROUTERS, Address::from_parts(&bytes)); + } + { + LINK_LOCAL_ADDR.write_parts(&mut bytes); + assert_eq!(LINK_LOCAL_ADDR, Address::from_parts(&bytes)); + } + } + + #[test] + fn test_mask() { + let addr = Address::new(0x0123, 0x4567, 0x89ab, 0, 0, 0, 0, 1); + assert_eq!( + addr.mask(11), + [0x01, 0x20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ); + assert_eq!( + addr.mask(15), + [0x01, 0x22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ); + assert_eq!( + addr.mask(26), + [0x01, 0x23, 0x45, 0x40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ); + assert_eq!( + addr.mask(128), + [0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1] + ); + assert_eq!( + addr.mask(127), + [0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + ); + } + + #[cfg(feature = "proto-ipv4")] + #[test] + fn test_is_ipv4_mapped() { + assert!(!Address::UNSPECIFIED.is_ipv4_mapped()); + assert!(Address::from(Ipv4Address::new(192, 168, 1, 1)).is_ipv4_mapped()); + } + + #[cfg(feature = "proto-ipv4")] + #[test] + fn test_as_ipv4() { + assert_eq!(None, Address::UNSPECIFIED.as_ipv4()); + + let ipv4 = Ipv4Address::new(192, 168, 1, 1); + assert_eq!(Some(ipv4), Address::from(ipv4).as_ipv4()); + } + + #[cfg(feature = "proto-ipv4")] + #[test] + fn test_from_ipv4_address() { + assert_eq!( + Address([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 192, 168, 1, 1]), + Address::from(Ipv4Address::new(192, 168, 1, 1)) + ); + assert_eq!( + Address([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 222, 1, 41, 90]), + Address::from(Ipv4Address::new(222, 1, 41, 90)) + ); + } + + #[test] + fn test_cidr() { + // fe80::1/56 + // 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, + // 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + let cidr = Cidr::new(LINK_LOCAL_ADDR, 56); + + let inside_subnet = [ + // fe80::2 + [ + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x02, + ], + // fe80::1122:3344:5566:7788 + [ + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, + 0x77, 0x88, + ], + // fe80::ff00:0:0:0 + [ + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, + ], + // fe80::ff + [ + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xff, + ], + ]; + + let outside_subnet = [ + // fe80:0:0:101::1 + [ + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, + ], + // ::1 + [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, + ], + // ff02::1 + [ + 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, + ], + // ff02::2 + [ + 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x02, + ], + ]; + + let subnets = [ + // fe80::ffff:ffff:ffff:ffff/65 + ( + [ + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, + ], + 65, + ), + // fe80::1/128 + ( + [ + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, + ], + 128, + ), + // fe80::1234:5678/96 + ( + [ + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, + 0x34, 0x56, 0x78, + ], + 96, + ), + ]; + + let not_subnets = [ + // fe80::101:ffff:ffff:ffff:ffff/55 + ( + [ + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, + ], + 55, + ), + // fe80::101:ffff:ffff:ffff:ffff/56 + ( + [ + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, + ], + 56, + ), + // fe80::101:ffff:ffff:ffff:ffff/57 + ( + [ + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, + ], + 57, + ), + // ::1/128 + ( + [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, + ], + 128, + ), + ]; + + for addr in inside_subnet.iter().map(|a| Address::from_bytes(a)) { + assert!(cidr.contains_addr(&addr)); + } + + for addr in outside_subnet.iter().map(|a| Address::from_bytes(a)) { + assert!(!cidr.contains_addr(&addr)); + } + + for subnet in subnets.iter().map(|&(a, p)| Cidr::new(Address(a), p)) { + assert!(cidr.contains_subnet(&subnet)); + } + + for subnet in not_subnets.iter().map(|&(a, p)| Cidr::new(Address(a), p)) { + assert!(!cidr.contains_subnet(&subnet)); + } + + let cidr_without_prefix = Cidr::new(LINK_LOCAL_ADDR, 0); + assert!(cidr_without_prefix.contains_addr(&Address::LOOPBACK)); + } + + #[test] + #[should_panic(expected = "length")] + fn test_from_bytes_too_long() { + let _ = Address::from_bytes(&[0u8; 15]); + } + + #[test] + #[should_panic(expected = "data.len() >= 8")] + fn test_from_parts_too_long() { + let _ = Address::from_parts(&[0u16; 7]); + } + + #[test] + fn test_scope() { + use super::*; + assert_eq!( + Address::new(0xff01, 0, 0, 0, 0, 0, 0, 1).scope(), + Scope::InterfaceLocal + ); + assert_eq!( + Address::new(0xff02, 0, 0, 0, 0, 0, 0, 1).scope(), + Scope::LinkLocal + ); + assert_eq!( + Address::new(0xff03, 0, 0, 0, 0, 0, 0, 1).scope(), + Scope::Unknown + ); + assert_eq!( + Address::new(0xff04, 0, 0, 0, 0, 0, 0, 1).scope(), + Scope::AdminLocal + ); + assert_eq!( + Address::new(0xff05, 0, 0, 0, 0, 0, 0, 1).scope(), + Scope::SiteLocal + ); + assert_eq!( + Address::new(0xff08, 0, 0, 0, 0, 0, 0, 1).scope(), + Scope::OrganizationLocal + ); + assert_eq!( + Address::new(0xff0e, 0, 0, 0, 0, 0, 0, 1).scope(), + Scope::Global + ); + + assert_eq!(Address::LINK_LOCAL_ALL_NODES.scope(), Scope::LinkLocal); + + // For source address selection, unicast addresses also have a scope: + assert_eq!(LINK_LOCAL_ADDR.scope(), Scope::LinkLocal); + assert_eq!(GLOBAL_UNICAST_ADDR.scope(), Scope::Global); + assert_eq!(UNIQUE_LOCAL_ADDR.scope(), Scope::Global); + } + + static REPR_PACKET_BYTES: [u8; 52] = [ + 0x60, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x11, 0x40, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, + 0x0c, 0x02, 0x4e, 0xff, 0xff, 0xff, 0xff, + ]; + static REPR_PAYLOAD_BYTES: [u8; 12] = [ + 0x00, 0x01, 0x00, 0x02, 0x00, 0x0c, 0x02, 0x4e, 0xff, 0xff, 0xff, 0xff, + ]; + + const fn packet_repr() -> Repr { + Repr { + src_addr: Address([ + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, + ]), + dst_addr: Address::LINK_LOCAL_ALL_NODES, + next_header: Protocol::Udp, + payload_len: 12, + hop_limit: 64, + } + } + + #[test] + fn test_packet_deconstruction() { + let packet = Packet::new_unchecked(&REPR_PACKET_BYTES[..]); + assert_eq!(packet.check_len(), Ok(())); + assert_eq!(packet.version(), 6); + assert_eq!(packet.traffic_class(), 0); + assert_eq!(packet.flow_label(), 0); + assert_eq!(packet.total_len(), 0x34); + assert_eq!(packet.payload_len() as usize, REPR_PAYLOAD_BYTES.len()); + assert_eq!(packet.next_header(), Protocol::Udp); + assert_eq!(packet.hop_limit(), 0x40); + assert_eq!( + packet.src_addr(), + Address([ + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01 + ]) + ); + assert_eq!(packet.dst_addr(), Address::LINK_LOCAL_ALL_NODES); + assert_eq!(packet.payload(), &REPR_PAYLOAD_BYTES[..]); + } + + #[test] + fn test_packet_construction() { + let mut bytes = [0xff; 52]; + let mut packet = Packet::new_unchecked(&mut bytes[..]); + // Version, Traffic Class, and Flow Label are not + // byte aligned. make sure the setters and getters + // do not interfere with each other. + packet.set_version(6); + assert_eq!(packet.version(), 6); + packet.set_traffic_class(0x99); + assert_eq!(packet.version(), 6); + assert_eq!(packet.traffic_class(), 0x99); + packet.set_flow_label(0x54321); + assert_eq!(packet.traffic_class(), 0x99); + assert_eq!(packet.flow_label(), 0x54321); + packet.set_payload_len(0xc); + packet.set_next_header(Protocol::Udp); + packet.set_hop_limit(0xfe); + packet.set_src_addr(Address::LINK_LOCAL_ALL_ROUTERS); + packet.set_dst_addr(Address::LINK_LOCAL_ALL_NODES); + packet + .payload_mut() + .copy_from_slice(&REPR_PAYLOAD_BYTES[..]); + let mut expected_bytes = [ + 0x69, 0x95, 0x43, 0x21, 0x00, 0x0c, 0x11, 0xfe, 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; + let start = expected_bytes.len() - REPR_PAYLOAD_BYTES.len(); + expected_bytes[start..].copy_from_slice(&REPR_PAYLOAD_BYTES[..]); + assert_eq!(packet.check_len(), Ok(())); + assert_eq!(&*packet.into_inner(), &expected_bytes[..]); + } + + #[test] + fn test_overlong() { + let mut bytes = vec![]; + bytes.extend(&REPR_PACKET_BYTES[..]); + bytes.push(0); + + assert_eq!( + Packet::new_unchecked(&bytes).payload().len(), + REPR_PAYLOAD_BYTES.len() + ); + assert_eq!( + Packet::new_unchecked(&mut bytes).payload_mut().len(), + REPR_PAYLOAD_BYTES.len() + ); + } + + #[test] + fn test_total_len_overflow() { + let mut bytes = vec![]; + bytes.extend(&REPR_PACKET_BYTES[..]); + Packet::new_unchecked(&mut bytes).set_payload_len(0x80); + + assert_eq!(Packet::new_checked(&bytes).unwrap_err(), Error); + } + + #[test] + fn test_repr_parse_valid() { + let packet = Packet::new_unchecked(&REPR_PACKET_BYTES[..]); + let repr = Repr::parse(&packet).unwrap(); + assert_eq!(repr, packet_repr()); + } + + #[test] + fn test_repr_parse_bad_version() { + let mut bytes = vec![0; 40]; + let mut packet = Packet::new_unchecked(&mut bytes[..]); + packet.set_version(4); + packet.set_payload_len(0); + let packet = Packet::new_unchecked(&*packet.into_inner()); + assert_eq!(Repr::parse(&packet), Err(Error)); + } + + #[test] + fn test_repr_parse_smaller_than_header() { + let mut bytes = vec![0; 40]; + let mut packet = Packet::new_unchecked(&mut bytes[..]); + packet.set_version(6); + packet.set_payload_len(39); + let packet = Packet::new_unchecked(&*packet.into_inner()); + assert_eq!(Repr::parse(&packet), Err(Error)); + } + + #[test] + fn test_repr_parse_smaller_than_payload() { + let mut bytes = vec![0; 40]; + let mut packet = Packet::new_unchecked(&mut bytes[..]); + packet.set_version(6); + packet.set_payload_len(1); + let packet = Packet::new_unchecked(&*packet.into_inner()); + assert_eq!(Repr::parse(&packet), Err(Error)); + } + + #[test] + fn test_basic_repr_emit() { + let repr = packet_repr(); + let mut bytes = vec![0xff; repr.buffer_len() + REPR_PAYLOAD_BYTES.len()]; + let mut packet = Packet::new_unchecked(&mut bytes); + repr.emit(&mut packet); + packet.payload_mut().copy_from_slice(&REPR_PAYLOAD_BYTES); + assert_eq!(&*packet.into_inner(), &REPR_PACKET_BYTES[..]); + } + + #[test] + fn test_pretty_print() { + assert_eq!( + format!( + "{}", + PrettyPrinter::<Packet<&'static [u8]>>::new("\n", &&REPR_PACKET_BYTES[..]) + ), + "\nIPv6 src=fe80::1 dst=ff02::1 nxt_hdr=UDP hop_limit=64\n \\ UDP src=1 dst=2 len=4" + ); + } +} |