#![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 MulticastScope {
    /// 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 MulticastScope {
    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 MLVDv2-capable routers multicast address].
    ///
    /// [all MLVDv2-capable routers multicast address]: https://tools.ietf.org/html/rfc3810#section-11
    pub const LINK_LOCAL_ALL_MLDV2_ROUTERS: Address = Address([
        0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x16,
    ]);

    /// 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 multicast_scope(&self) -> MulticastScope {
        if self.is_multicast() {
            return MulticastScope::from(self.as_bytes()[1] & 0b1111);
        }

        if self.is_link_local() {
            MulticastScope::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
            MulticastScope::Global
        } else {
            MulticastScope::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)]
pub(crate) 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;

    #[allow(unused)]
    pub(crate) const MOCK_IP_ADDR_1: Address =
        Address([0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
    #[allow(unused)]
    pub(crate) const MOCK_IP_ADDR_2: Address =
        Address([0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2]);
    #[allow(unused)]
    pub(crate) const MOCK_IP_ADDR_3: Address =
        Address([0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3]);
    #[allow(unused)]
    pub(crate) const MOCK_IP_ADDR_4: Address =
        Address([0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4]);
    #[allow(unused)]
    pub(crate) const MOCK_UNSPECIFIED: Address = Address::UNSPECIFIED;

    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).multicast_scope(),
            MulticastScope::InterfaceLocal
        );
        assert_eq!(
            Address::new(0xff02, 0, 0, 0, 0, 0, 0, 1).multicast_scope(),
            MulticastScope::LinkLocal
        );
        assert_eq!(
            Address::new(0xff03, 0, 0, 0, 0, 0, 0, 1).multicast_scope(),
            MulticastScope::Unknown
        );
        assert_eq!(
            Address::new(0xff04, 0, 0, 0, 0, 0, 0, 1).multicast_scope(),
            MulticastScope::AdminLocal
        );
        assert_eq!(
            Address::new(0xff05, 0, 0, 0, 0, 0, 0, 1).multicast_scope(),
            MulticastScope::SiteLocal
        );
        assert_eq!(
            Address::new(0xff08, 0, 0, 0, 0, 0, 0, 1).multicast_scope(),
            MulticastScope::OrganizationLocal
        );
        assert_eq!(
            Address::new(0xff0e, 0, 0, 0, 0, 0, 0, 1).multicast_scope(),
            MulticastScope::Global
        );

        assert_eq!(
            Address::LINK_LOCAL_ALL_NODES.multicast_scope(),
            MulticastScope::LinkLocal
        );

        // For source address selection, unicast addresses also have a scope:
        assert_eq!(LINK_LOCAL_ADDR.multicast_scope(), MulticastScope::LinkLocal);
        assert_eq!(
            GLOBAL_UNICAST_ADDR.multicast_scope(),
            MulticastScope::Global
        );
        assert_eq!(UNIQUE_LOCAL_ADDR.multicast_scope(), MulticastScope::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"
        );
    }
}
