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