// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

use core::{fmt, hash::Hasher, num::Wrapping};

#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
mod x86;

/// Computes the [IP checksum](https://www.rfc-editor.org/rfc/rfc1071) over the given slice of bytes
#[inline]
pub fn checksum(data: &[u8]) -> u16 {
    let mut checksum = Checksum::default();
    checksum.write(data);
    checksum.finish()
}

/// Minimum size for a payload to be considered for platform-specific code
const LARGE_WRITE_LEN: usize = 32;

type Accumulator = u64;
type State = Wrapping<Accumulator>;

/// Platform-specific function for computing a checksum
type LargeWriteFn = for<'a> unsafe fn(&mut State, bytes: &'a [u8]) -> &'a [u8];

#[inline(always)]
fn write_sized_generic<'a, const MAX_LEN: usize, const CHUNK_LEN: usize>(
    state: &mut State,
    mut bytes: &'a [u8],
    on_chunk: impl Fn(&[u8; CHUNK_LEN], &mut Accumulator),
) -> &'a [u8] {
    //= https://www.rfc-editor.org/rfc/rfc1071#section-4.1
    //# The following "C" code algorithm computes the checksum with an inner
    //# loop that sums 16-bits at a time in a 32-bit accumulator.
    //#
    //# in 6
    //#    {
    //#        /* Compute Internet Checksum for "count" bytes
    //#         *         beginning at location "addr".
    //#         */
    //#    register long sum = 0;
    //#
    //#     while( count > 1 )  {
    //#        /*  This is the inner loop */
    //#            sum += * (unsigned short) addr++;
    //#            count -= 2;
    //#    }
    //#
    //#        /*  Add left-over byte, if any */
    //#    if( count > 0 )
    //#            sum += * (unsigned char *) addr;
    //#
    //#        /*  Fold 32-bit sum to 16 bits */
    //#    while (sum>>16)
    //#        sum = (sum & 0xffff) + (sum >> 16);
    //#
    //#    checksum = ~sum;
    //# }

    while bytes.len() >= MAX_LEN {
        let (chunks, remaining) = bytes.split_at(MAX_LEN);

        bytes = remaining;

        let mut sum = 0;
        // for each pair of bytes, interpret them as integers and sum them up
        for chunk in chunks.chunks_exact(CHUNK_LEN) {
            let chunk = unsafe {
                // SAFETY: chunks_exact always produces a slice of CHUNK_LEN
                debug_assert_eq!(chunk.len(), CHUNK_LEN);
                &*(chunk.as_ptr() as *const [u8; CHUNK_LEN])
            };
            on_chunk(chunk, &mut sum);
        }
        *state += sum;
    }

    bytes
}

/// Generic implementation of a function that computes a checksum over the given slice
#[inline(always)]
fn write_sized_generic_u16<'a, const LEN: usize>(state: &mut State, bytes: &'a [u8]) -> &'a [u8] {
    write_sized_generic::<LEN, 2>(
        state,
        bytes,
        #[inline(always)]
        |&bytes, acc| {
            *acc += u16::from_ne_bytes(bytes) as Accumulator;
        },
    )
}

#[inline(always)]
fn write_sized_generic_u32<'a, const LEN: usize>(state: &mut State, bytes: &'a [u8]) -> &'a [u8] {
    write_sized_generic::<LEN, 4>(
        state,
        bytes,
        #[inline(always)]
        |&bytes, acc| {
            *acc += u32::from_ne_bytes(bytes) as Accumulator;
        },
    )
}

/// Returns the most optimized function implementation for the current platform
#[inline]
#[cfg(all(feature = "once_cell", not(any(kani, miri))))]
fn probe_write_large() -> LargeWriteFn {
    static LARGE_WRITE_FN: once_cell::sync::Lazy<LargeWriteFn> = once_cell::sync::Lazy::new(|| {
        #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
        {
            if let Some(fun) = x86::probe() {
                return fun;
            }
        }

        write_sized_generic_u32::<16>
    });

    *LARGE_WRITE_FN
}

#[inline]
#[cfg(not(all(feature = "once_cell", not(any(kani, miri)))))]
fn probe_write_large() -> LargeWriteFn {
    write_sized_generic_u32::<16>
}

/// Computes the [IP checksum](https://www.rfc-editor.org/rfc/rfc1071) over an arbitrary set of inputs
#[derive(Clone, Copy)]
pub struct Checksum {
    state: State,
    partial_write: bool,
    write_large: LargeWriteFn,
}

impl Default for Checksum {
    fn default() -> Self {
        Self {
            state: Default::default(),
            partial_write: false,
            write_large: probe_write_large(),
        }
    }
}

impl fmt::Debug for Checksum {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let mut v = *self;
        v.carry();
        f.debug_tuple("Checksum").field(&v.finish()).finish()
    }
}

impl Checksum {
    /// Creates a checksum instance without enabling the native implementation
    #[inline]
    pub fn generic() -> Self {
        Self {
            state: Default::default(),
            partial_write: false,
            write_large: write_sized_generic_u32::<16>,
        }
    }

    /// Writes a single byte to the checksum state
    #[inline]
    fn write_byte(&mut self, byte: u8, shift: bool) {
        if shift {
            self.state += (byte as Accumulator) << 8;
        } else {
            self.state += byte as Accumulator;
        }
    }

    /// Carries all of the bits into a single 16 bit range
    #[inline]
    fn carry(&mut self) {
        #[cfg(kani)]
        self.carry_rfc();
        #[cfg(not(kani))]
        self.carry_optimized();
    }

    /// Carries all of the bits into a single 16 bit range
    ///
    /// This implementation is very similar to the way the RFC is written.
    #[inline]
    #[allow(dead_code)]
    fn carry_rfc(&mut self) {
        let mut state = self.state.0;

        for _ in 0..core::mem::size_of::<Accumulator>() {
            state = (state & 0xffff) + (state >> 16);
        }

        self.state.0 = state;
    }

    /// Carries all of the bits into a single 16 bit range
    ///
    /// This implementation was written after some optimization on the RFC version. It results in
    /// about half the instructions needed as the RFC.
    #[inline]
    #[allow(dead_code)]
    fn carry_optimized(&mut self) {
        let values: [u16; core::mem::size_of::<Accumulator>() / 2] = unsafe {
            // SAFETY: alignment of the State is >= of u16
            debug_assert!(core::mem::align_of::<State>() >= core::mem::align_of::<u16>());
            core::mem::transmute(self.state.0)
        };

        let mut sum = 0u16;

        for value in values {
            let (res, overflowed) = sum.overflowing_add(value);
            sum = res;
            if overflowed {
                sum += 1;
            }
        }

        self.state.0 = sum as _;
    }

    /// Writes bytes to the checksum and ensures any single byte remainders are padded
    #[inline]
    pub fn write_padded(&mut self, bytes: &[u8]) {
        self.write(bytes);

        // write a null byte if `bytes` wasn't 16-bit aligned
        if core::mem::take(&mut self.partial_write) {
            self.write_byte(0, cfg!(target_endian = "little"));
        }
    }

    /// Computes the final checksum
    #[inline]
    pub fn finish(self) -> u16 {
        self.finish_be().to_be()
    }

    #[inline]
    pub fn finish_be(mut self) -> u16 {
        self.carry();

        let value = self.state.0 as u16;
        let value = !value;

        // if value is 0, we need to set it to the max value to indicate the checksum was actually
        // computed
        if value == 0 {
            return 0xffff;
        }

        value
    }
}

impl Hasher for Checksum {
    #[inline]
    fn write(&mut self, mut bytes: &[u8]) {
        if bytes.is_empty() {
            return;
        }

        // Check to see if we have a partial write to flush
        if core::mem::take(&mut self.partial_write) {
            let (chunk, remaining) = bytes.split_at(1);
            bytes = remaining;

            // shift the byte if we're on little endian
            self.write_byte(chunk[0], cfg!(target_endian = "little"));
        }

        // Only delegate to the optimized platform function if the payload is big enough
        if bytes.len() >= LARGE_WRITE_LEN {
            bytes = unsafe { (self.write_large)(&mut self.state, bytes) };
        }

        // Fall back on the generic implementation to wrap things up
        //
        // NOTE: We don't use the u32 version with kani as it causes the verification time to
        // increase by quite a bit. We have a separate proof for the functional equivalence of
        // these two configurations.
        #[cfg(not(kani))]
        {
            bytes = write_sized_generic_u32::<4>(&mut self.state, bytes);
        }

        bytes = write_sized_generic_u16::<2>(&mut self.state, bytes);

        // if we only have a single byte left, write it to the state and mark it as a partial write
        if let Some(byte) = bytes.first().copied() {
            self.partial_write = true;
            self.write_byte(byte, cfg!(target_endian = "big"));
        }
    }

    #[inline]
    fn finish(&self) -> u64 {
        Self::finish(*self) as _
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use bolero::check;

    #[test]
    fn rfc_example_test() {
        //= https://www.rfc-editor.org/rfc/rfc1071#section-3
        //= type=test
        //# We now present explicit examples of calculating a simple 1's
        //# complement sum on a 2's complement machine.  The examples show the
        //# same sum calculated byte by bye, by 16-bits words in normal and
        //# swapped order, and 32 bits at a time in 3 different orders.  All
        //# numbers are in hex.
        //#
        //#               Byte-by-byte    "Normal"  Swapped
        //#                                 Order    Order
        //#
        //#     Byte 0/1:    00   01        0001      0100
        //#     Byte 2/3:    f2   03        f203      03f2
        //#     Byte 4/5:    f4   f5        f4f5      f5f4
        //#     Byte 6/7:    f6   f7        f6f7      f7f6
        //#                 ---  ---       -----     -----
        //#     Sum1:       2dc  1f0       2ddf0     1f2dc
        //#
        //#                  dc   f0        ddf0      f2dc
        //#     Carrys:       1    2           2         1
        //#                  --   --        ----      ----
        //#     Sum2:        dd   f2        ddf2      f2dd
        //#
        //#     Final Swap:  dd   f2        ddf2      ddf2
        let bytes = [0x00, 0x01, 0xf2, 0x03, 0xf4, 0xf5, 0xf6, 0xf7];

        let mut checksum = Checksum::default();
        checksum.write(&bytes);
        checksum.carry();

        assert_eq!((checksum.state.0 as u16).to_le_bytes(), [0xdd, 0xf2]);
        assert_eq!((!rfc_c_port(&bytes)).to_be_bytes(), [0xdd, 0xf2]);
    }

    fn rfc_c_port(data: &[u8]) -> u16 {
        //= https://www.rfc-editor.org/rfc/rfc1071#section-4.1
        //= type=test
        //# The following "C" code algorithm computes the checksum with an inner
        //# loop that sums 16-bits at a time in a 32-bit accumulator.
        //#
        //# in 6
        //#    {
        //#        /* Compute Internet Checksum for "count" bytes
        //#         *         beginning at location "addr".
        //#         */
        //#    register long sum = 0;
        //#
        //#     while( count > 1 )  {
        //#        /*  This is the inner loop */
        //#            sum += * (unsigned short) addr++;
        //#            count -= 2;
        //#    }
        //#
        //#        /*  Add left-over byte, if any */
        //#    if( count > 0 )
        //#            sum += * (unsigned char *) addr;
        //#
        //#        /*  Fold 32-bit sum to 16 bits */
        //#    while (sum>>16)
        //#        sum = (sum & 0xffff) + (sum >> 16);
        //#
        //#    checksum = ~sum;
        //# }

        let mut addr = data.as_ptr() as *const u8;
        let mut count = data.len();

        unsafe {
            let mut sum = 0u32;

            while count > 1 {
                let value = u16::from_be_bytes([*addr, *addr.add(1)]);
                sum = sum.wrapping_add(value as u32);
                addr = addr.add(2);
                count -= 2;
            }

            if count > 0 {
                let value = u16::from_be_bytes([*addr, 0]);
                sum = sum.wrapping_add(value as u32);
            }

            while sum >> 16 != 0 {
                sum = (sum & 0xffff) + (sum >> 16);
            }

            !(sum as u16)
        }
    }

    /// * Compares the implementation to a port of the C code defined in the RFC
    /// * Ensures partial writes are correctly handled, even if they're not at a 16 bit boundary
    #[test]
    #[cfg_attr(kani, kani::proof, kani::unwind(17), kani::solver(kissat))]
    fn differential() {
        #[cfg(any(kani, miri))]
        type Bytes = crate::testing::InlineVec<u8, 16>;
        #[cfg(not(any(kani, miri)))]
        type Bytes = Vec<u8>;

        check!()
            .with_type::<(usize, Bytes)>()
            .for_each(|(index, bytes)| {
                let index = if bytes.is_empty() {
                    0
                } else {
                    *index % bytes.len()
                };
                let (a, b) = bytes.split_at(index);
                let mut cs = Checksum::default();
                cs.write(a);
                cs.write(b);

                let mut rfc_value = rfc_c_port(bytes);
                if rfc_value == 0 {
                    rfc_value = 0xffff;
                }

                assert_eq!(rfc_value.to_be_bytes(), cs.finish().to_be_bytes());
            });
    }

    /// Shows that using the u32+u16 methods is the same as only using u16
    #[test]
    #[cfg_attr(kani, kani::proof, kani::unwind(9), kani::solver(kissat))]
    fn u32_u16_differential() {
        #[cfg(any(kani, miri))]
        type Bytes = crate::testing::InlineVec<u8, 8>;
        #[cfg(not(any(kani, miri)))]
        type Bytes = Vec<u8>;

        check!().with_type::<Bytes>().for_each(|bytes| {
            let a = {
                let mut cs = Checksum::generic();
                let bytes = write_sized_generic_u32::<4>(&mut cs.state, bytes);
                write_sized_generic_u16::<2>(&mut cs.state, bytes);
                cs.finish()
            };

            let b = {
                let mut cs = Checksum::generic();
                write_sized_generic_u16::<2>(&mut cs.state, bytes);
                cs.finish()
            };

            assert_eq!(a, b);
        });
    }

    /// Shows that RFC carry implementation is the same as the optimized version
    #[test]
    #[cfg_attr(kani, kani::proof, kani::unwind(9), kani::solver(kissat))]
    fn carry_differential() {
        check!().with_type::<u64>().cloned().for_each(|state| {
            let mut opt = Checksum::generic();
            opt.state.0 = state;
            opt.carry_optimized();

            let mut rfc = Checksum::generic();
            rfc.state.0 = state;
            rfc.carry_rfc();

            assert_eq!(opt.state.0, rfc.state.0);
        });
    }
}


// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

use crate::{
    connection, inet::ExplicitCongestionNotification, path::LocalAddress, time::Timestamp,
};

/// Header information for a datagram sent/received over the network
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Header<Path> {
    pub path: Path,
    pub ecn: ExplicitCongestionNotification,
}

/// Metadata for a datagram sent/received over the network
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct DatagramInfo {
    pub timestamp: Timestamp,
    pub payload_len: usize,
    pub ecn: ExplicitCongestionNotification,
    pub destination_connection_id: connection::LocalId,
    pub source_connection_id: Option<connection::PeerId>,
}

/// Additional metadata for a datagram sent/received over the network
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct AncillaryData {
    pub ecn: ExplicitCongestionNotification,
    pub local_address: LocalAddress,
    /// The network interface the datagram is sent/received on
    ///
    /// Correctly threading this value through to connections ensures packets end up on the same
    /// network interfaces and thereby have consistent MAC addresses.
    pub local_interface: Option<u32>,
    /// Set when the packet buffer is an aggregate of multiple received packets
    pub segment_size: u16,
}


// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

#[cfg(any(test, feature = "generator"))]
use bolero_generator::*;

//= https://www.rfc-editor.org/rfc/rfc3168#section-5
//# This document specifies that the Internet provide a congestion
//# indication for incipient congestion (as in RED and earlier work
//# [RJ90]) where the notification can sometimes be through marking
//# packets rather than dropping them.  This uses an ECN field in the IP
//# header with two bits, making four ECN codepoints, '00' to '11'.  The
//# ECN-Capable Transport (ECT) codepoints '10' and '01' are set by the
//# data sender to indicate that the end-points of the transport protocol
//# are ECN-capable; we call them ECT(0) and ECT(1) respectively.  The
//# phrase "the ECT codepoint" in this documents refers to either of the
//# two ECT codepoints.  Routers treat the ECT(0) and ECT(1) codepoints
//# as equivalent.  Senders are free to use either the ECT(0) or the
//# ECT(1) codepoint to indicate ECT, on a packet-by-packet basis.
//#
//# The use of both the two codepoints for ECT, ECT(0) and ECT(1), is
//# motivated primarily by the desire to allow mechanisms for the data
//# sender to verify that network elements are not erasing the CE
//# codepoint, and that data receivers are properly reporting to the
//# sender the receipt of packets with the CE codepoint set, as required
//# by the transport protocol.  Guidelines for the senders and receivers
//# to differentiate between the ECT(0) and ECT(1) codepoints will be
//# addressed in separate documents, for each transport protocol.  In
//# particular, this document does not address mechanisms for TCP end-
//# nodes to differentiate between the ECT(0) and ECT(1) codepoints.
//# Protocols and senders that only require a single ECT codepoint SHOULD
//# use ECT(0).
//#
//# The not-ECT codepoint '00' indicates a packet that is not using ECN.
//# The CE codepoint '11' is set by a router to indicate congestion to
//# the end nodes.  Routers that have a packet arriving at a full queue
//# drop the packet, just as they do in the absence of ECN.
//#
//#    +-----+-----+
//#    | ECN FIELD |
//#    +-----+-----+
//#      ECT   CE         [Obsolete] RFC 2481 names for the ECN bits.
//#       0     0         Not-ECT
//#       0     1         ECT(1)
//#       1     0         ECT(0)
//#       1     1         CE
//#
//#    Figure 1: The ECN Field in IP.

/// Explicit Congestion Notification
#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(any(test, feature = "generator"), derive(TypeGenerator))]
pub enum ExplicitCongestionNotification {
    /// The not-ECT codepoint '00' indicates a packet that is not using ECN.
    NotEct = 0b00,

    /// ECT(1) is set by the data sender to indicate that the end-points of the transport
    /// protocol are ECN-capable.
    Ect1 = 0b01,

    /// ECT(0) is set by the data sender to indicate that the end-points of the transport
    /// protocol are ECN-capable.
    /// Protocols and senders that only require a single ECT codepoint SHOULD use ECT(0).
    Ect0 = 0b10,

    /// The CE codepoint '11' is set by a router to indicate congestion to the end nodes.
    Ce = 0b11,
}

impl Default for ExplicitCongestionNotification {
    fn default() -> Self {
        Self::NotEct
    }
}

impl ExplicitCongestionNotification {
    /// Create a ExplicitCongestionNotification from the ECN field in the IP header
    pub fn new(ecn_field: u8) -> Self {
        match ecn_field & 0b11 {
            0b00 => ExplicitCongestionNotification::NotEct,
            0b01 => ExplicitCongestionNotification::Ect1,
            0b10 => ExplicitCongestionNotification::Ect0,
            0b11 => ExplicitCongestionNotification::Ce,
            _ => unreachable!(),
        }
    }

    /// Returns true if congestion was experienced by the peer
    pub fn congestion_experienced(self) -> bool {
        self == Self::Ce
    }

    /// Returns true if ECN is in use
    pub fn using_ecn(self) -> bool {
        self != Self::NotEct
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn new() {
        for ecn in &[
            ExplicitCongestionNotification::NotEct,
            ExplicitCongestionNotification::Ect1,
            ExplicitCongestionNotification::Ect0,
            ExplicitCongestionNotification::Ce,
        ] {
            assert_eq!(*ecn, ExplicitCongestionNotification::new(*ecn as u8));
        }
    }

    /// The most-significant 6 bits of the 8-bit traffic class field ECN markings are
    /// read from are used for the differentiated services code point. This test
    /// ensures we still parse ECN bits correctly even when the DSCP markings are present.
    #[test]
    fn dscp_markings() {
        for i in 0..u8::MAX {
            for ecn in &[
                ExplicitCongestionNotification::NotEct,
                ExplicitCongestionNotification::Ect1,
                ExplicitCongestionNotification::Ect0,
                ExplicitCongestionNotification::Ce,
            ] {
                assert_eq!(
                    *ecn,
                    ExplicitCongestionNotification::new(i << 2 | *ecn as u8)
                );
            }
        }
    }
}


// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

use crate::inet::Unspecified;
use core::fmt;

// NOTE: duvet doesn't know how to parse this RFC since it doesn't follow more modern formatting
//# https://www.rfc-editor.org/rfc/rfc826
//# Packet format:
//# --------------
//#
//# To communicate mappings from <protocol, address> pairs to 48.bit
//# Ethernet addresses, a packet format that embodies the Address
//# Resolution protocol is needed.  The format of the packet follows.
//#
//#    Ethernet transmission layer (not necessarily accessible to
//#         the user):
//#        48.bit: Ethernet address of destination
//#        48.bit: Ethernet address of sender
const MAC_LEN: usize = 48 / 8;

define_inet_type!(
    pub struct MacAddress {
        octets: [u8; MAC_LEN],
    }
);

impl fmt::Debug for MacAddress {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.debug_tuple("MacAddress")
            .field(&format_args!("{self}"))
            .finish()
    }
}

impl fmt::Display for MacAddress {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        let [a, b, c, d, e, f] = self.octets;
        write!(fmt, "{a:02x}:{b:02x}:{c:02x}:{d:02x}:{e:02x}:{f:02x}")
    }
}

impl MacAddress {
    pub const UNSPECIFIED: Self = Self {
        octets: [0; MAC_LEN],
    };
}

impl Unspecified for MacAddress {
    #[inline]
    fn is_unspecified(&self) -> bool {
        self.octets == [0; MAC_LEN]
    }
}

define_inet_type!(
    pub struct EtherType {
        id: [u8; 2],
    }
);

impl fmt::Debug for EtherType {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.debug_tuple("EtherType")
            .field(&format_args!("{self}"))
            .finish()
    }
}

impl fmt::Display for EtherType {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Self::IPV4 => "IPv4",
            Self::ARP => "ARP",
            Self::IPV6 => "IPv6",
            Self::VLAN => "VLAN",
            Self::PPP => "PPP",
            Self { id: [a, b] } => return write!(f, "[unknown 0x{a:02x}{b:02x}]"),
        }
        .fmt(f)
    }
}

macro_rules! impl_type {
    ($fun:ident, $cap:ident, $val:expr) => {
        pub const $cap: Self = Self { id: $val };

        #[inline]
        pub const fn $fun(self) -> bool {
            matches!(self, Self::$cap)
        }
    };
}

impl EtherType {
    // https://www.iana.org/assignments/ieee-802-numbers/ieee-802-numbers.xhtml#ieee-802-numbers-1
    // NOTE: these variants were added as the ones we think we'll need. feel free to add more as
    //       needed.
    impl_type!(is_ipv4, IPV4, [0x08, 0x00]);
    impl_type!(is_arp, ARP, [0x08, 0x06]);
    impl_type!(is_ipv6, IPV6, [0x86, 0xDD]);
    impl_type!(is_ppp, PPP, [0x88, 0x0B]);
    impl_type!(is_vlan, VLAN, [0x88, 0xA8]);
}

// NOTE: duvet doesn't know how to parse this RFC since it doesn't follow more modern formatting
//# https://www.rfc-editor.org/rfc/rfc826
//# Packet format:
//# --------------
//#
//# To communicate mappings from <protocol, address> pairs to 48.bit
//# Ethernet addresses, a packet format that embodies the Address
//# Resolution protocol is needed.  The format of the packet follows.
//#
//#    Ethernet transmission layer (not necessarily accessible to
//#         the user):
//#        48.bit: Ethernet address of destination
//#        48.bit: Ethernet address of sender
//#        16.bit: Protocol type = ether_type$ADDRESS_RESOLUTION

define_inet_type!(
    pub struct Header {
        destination: MacAddress,
        source: MacAddress,
        ethertype: EtherType,
    }
);

impl fmt::Debug for Header {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.debug_struct("ethernet::Header")
            .field("destination", &self.destination)
            .field("source", &self.source)
            .field("ethertype", &self.ethertype)
            .finish()
    }
}

impl Header {
    /// Swaps the direction of the header
    #[inline]
    pub fn swap(&mut self) {
        core::mem::swap(&mut self.source, &mut self.destination)
    }

    #[inline]
    pub const fn destination(&self) -> &MacAddress {
        &self.destination
    }

    #[inline]
    pub fn destination_mut(&mut self) -> &mut MacAddress {
        &mut self.destination
    }

    #[inline]
    pub const fn source(&self) -> &MacAddress {
        &self.source
    }

    #[inline]
    pub fn source_mut(&mut self) -> &mut MacAddress {
        &mut self.source
    }

    #[inline]
    pub const fn ethertype(&self) -> &EtherType {
        &self.ethertype
    }

    #[inline]
    pub fn ethertype_mut(&mut self) -> &mut EtherType {
        &mut self.ethertype
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use bolero::check;
    use s2n_codec::DecoderBuffer;

    #[test]
    #[cfg_attr(miri, ignore)]
    fn snapshot_test() {
        let mut buffer = vec![0u8; core::mem::size_of::<Header>()];
        for (idx, byte) in buffer.iter_mut().enumerate() {
            *byte = idx as u8;
        }
        let decoder = DecoderBuffer::new(&buffer);
        let (header, _) = decoder.decode::<&Header>().unwrap();
        insta::assert_debug_snapshot!("snapshot_test", header);

        for byte in &mut buffer {
            *byte = 255;
        }
        let decoder = DecoderBuffer::new(&buffer);
        let (header, _) = decoder.decode::<&Header>().unwrap();
        insta::assert_debug_snapshot!("snapshot_filled_test", header);
    }

    #[test]
    fn header_round_trip_test() {
        check!().for_each(|buffer| {
            s2n_codec::assert_codec_round_trip_bytes!(Header, buffer);
        });
    }
}


// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

use crate::inet::{
    ipv4::{IpV4Address, SocketAddressV4},
    ipv6::{IpV6Address, SocketAddressV6},
    unspecified::Unspecified,
};
use core::fmt;

#[cfg(any(test, feature = "generator"))]
use bolero_generator::*;

/// An IP address, either IPv4 or IPv6.
///
/// Instead of using `std::net::IPAddr`, this implementation
/// is geared towards `no_std` environments and zerocopy decoding.
///
/// The size is also consistent across target operating systems.
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(any(test, feature = "generator"), derive(TypeGenerator))]
pub enum IpAddress {
    Ipv4(IpV4Address),
    Ipv6(IpV6Address),
}

impl IpAddress {
    /// Converts the IP address into IPv4 if it is mapped, otherwise the address is unchanged
    #[inline]
    #[must_use]
    pub fn unmap(self) -> Self {
        match self {
            Self::Ipv4(_) => self,
            Self::Ipv6(addr) => addr.unmap(),
        }
    }

    /// Converts the IP address into IPv6 if it is IPv4, otherwise the address is unchanged
    #[inline]
    #[must_use]
    pub fn to_ipv6_mapped(self) -> IpV6Address {
        match self {
            Self::Ipv4(addr) => addr.to_ipv6_mapped(),
            Self::Ipv6(addr) => addr,
        }
    }

    #[inline]
    #[must_use]
    pub fn with_port(self, port: u16) -> SocketAddress {
        match self {
            Self::Ipv4(addr) => addr.with_port(port).into(),
            Self::Ipv6(addr) => addr.with_port(port).into(),
        }
    }
}

impl From<IpV4Address> for IpAddress {
    fn from(ip: IpV4Address) -> Self {
        Self::Ipv4(ip)
    }
}

impl From<IpV6Address> for IpAddress {
    fn from(ip: IpV6Address) -> Self {
        Self::Ipv6(ip)
    }
}

impl Unspecified for IpAddress {
    fn is_unspecified(&self) -> bool {
        match self {
            Self::Ipv4(addr) => addr.is_unspecified(),
            Self::Ipv6(addr) => addr.is_unspecified(),
        }
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum IpAddressRef<'a> {
    IPv4(&'a IpV4Address),
    IPv6(&'a IpV6Address),
}

impl<'a> IpAddressRef<'a> {
    pub fn to_owned(self) -> IpAddress {
        match self {
            Self::IPv4(addr) => IpAddress::Ipv4(*addr),
            Self::IPv6(addr) => IpAddress::Ipv6(*addr),
        }
    }
}

impl<'a> From<&'a IpV4Address> for IpAddressRef<'a> {
    fn from(ip: &'a IpV4Address) -> Self {
        Self::IPv4(ip)
    }
}

impl<'a> From<&'a IpV6Address> for IpAddressRef<'a> {
    fn from(ip: &'a IpV6Address) -> Self {
        Self::IPv6(ip)
    }
}

/// An IP socket address, either IPv4 or IPv6, with a specific port.
///
/// Instead of using `std::net::SocketAddr`, this implementation
/// is geared towards `no_std` environments and zerocopy decoding.
///
/// The size is also consistent across target operating systems.
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(any(test, feature = "generator"), derive(TypeGenerator))]
pub enum SocketAddress {
    IpV4(SocketAddressV4),
    IpV6(SocketAddressV6),
}

impl SocketAddress {
    #[inline]
    pub fn ip(&self) -> IpAddress {
        match self {
            SocketAddress::IpV4(addr) => IpAddress::Ipv4(*addr.ip()),
            SocketAddress::IpV6(addr) => IpAddress::Ipv6(*addr.ip()),
        }
    }

    #[inline]
    pub fn port(&self) -> u16 {
        match self {
            SocketAddress::IpV4(addr) => addr.port(),
            SocketAddress::IpV6(addr) => addr.port(),
        }
    }

    #[inline]
    pub fn set_port(&mut self, port: u16) {
        match self {
            SocketAddress::IpV4(addr) => addr.set_port(port),
            SocketAddress::IpV6(addr) => addr.set_port(port),
        }
    }

    #[inline]
    pub const fn unicast_scope(&self) -> Option<UnicastScope> {
        match self {
            Self::IpV4(addr) => addr.unicast_scope(),
            Self::IpV6(addr) => addr.unicast_scope(),
        }
    }

    /// Converts the IP address into a IPv6 mapped address
    pub fn to_ipv6_mapped(self) -> SocketAddressV6 {
        match self {
            Self::IpV4(addr) => addr.to_ipv6_mapped(),
            Self::IpV6(addr) => addr,
        }
    }

    /// Converts the IP address into IPv4 if it is mapped, otherwise the address is unchanged
    #[inline]
    #[must_use]
    pub fn unmap(self) -> Self {
        match self {
            Self::IpV4(_) => self,
            Self::IpV6(addr) => addr.unmap(),
        }
    }
}

impl Default for SocketAddress {
    fn default() -> Self {
        SocketAddress::IpV4(Default::default())
    }
}

impl fmt::Display for SocketAddress {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        match self {
            SocketAddress::IpV4(addr) => write!(fmt, "{addr}"),
            SocketAddress::IpV6(addr) => write!(fmt, "{addr}"),
        }
    }
}

impl Unspecified for SocketAddress {
    fn is_unspecified(&self) -> bool {
        match self {
            SocketAddress::IpV4(addr) => addr.is_unspecified(),
            SocketAddress::IpV6(addr) => addr.is_unspecified(),
        }
    }
}

impl From<SocketAddressV4> for SocketAddress {
    fn from(addr: SocketAddressV4) -> Self {
        SocketAddress::IpV4(addr)
    }
}

impl From<SocketAddressV6> for SocketAddress {
    fn from(addr: SocketAddressV6) -> Self {
        SocketAddress::IpV6(addr)
    }
}

/// An IP socket address, either IPv4 or IPv6, with a specific port.
///
/// This is the borrowed version of `SocketAddress`, aimed at zerocopy
/// use cases.
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum SocketAddressRef<'a> {
    IpV4(&'a SocketAddressV4),
    IpV6(&'a SocketAddressV6),
}

impl<'a> SocketAddressRef<'a> {
    pub fn to_owned(self) -> SocketAddress {
        match self {
            Self::IpV4(addr) => SocketAddress::IpV4(*addr),
            Self::IpV6(addr) => SocketAddress::IpV6(*addr),
        }
    }
}

//= https://www.rfc-editor.org/rfc/rfc9000#section-21.5.6
//# Similarly, endpoints could regard a change in address to a link-local
//# address [RFC4291] or an address in a private-use range [RFC1918] from
//# a global, unique-local [RFC4193], or non-private address as a
//# potential attempt at request forgery.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum UnicastScope {
    Loopback,
    LinkLocal,
    Private,
    Global,
}

#[cfg(any(test, feature = "std"))]
mod std_conversion {
    use super::*;
    use std::net;

    impl net::ToSocketAddrs for SocketAddress {
        type Iter = std::iter::Once<net::SocketAddr>;

        fn to_socket_addrs(&self) -> std::io::Result<Self::Iter> {
            match self {
                Self::IpV4(addr) => addr.to_socket_addrs(),
                Self::IpV6(addr) => addr.to_socket_addrs(),
            }
        }
    }

    impl<'a> net::ToSocketAddrs for SocketAddressRef<'a> {
        type Iter = std::iter::Once<net::SocketAddr>;

        fn to_socket_addrs(&self) -> std::io::Result<Self::Iter> {
            match self {
                Self::IpV4(addr) => addr.to_socket_addrs(),
                Self::IpV6(addr) => addr.to_socket_addrs(),
            }
        }
    }

    impl From<SocketAddress> for net::SocketAddr {
        fn from(address: SocketAddress) -> Self {
            match address {
                SocketAddress::IpV4(addr) => addr.into(),
                SocketAddress::IpV6(addr) => addr.into(),
            }
        }
    }

    impl From<(net::IpAddr, u16)> for SocketAddress {
        fn from((ip, port): (net::IpAddr, u16)) -> Self {
            match ip {
                net::IpAddr::V4(ip) => Self::IpV4((ip, port).into()),
                net::IpAddr::V6(ip) => Self::IpV6((ip, port).into()),
            }
        }
    }

    impl<'a> From<SocketAddressRef<'a>> for net::SocketAddr {
        fn from(address: SocketAddressRef<'a>) -> Self {
            match address {
                SocketAddressRef::IpV4(addr) => addr.into(),
                SocketAddressRef::IpV6(addr) => addr.into(),
            }
        }
    }

    impl From<net::SocketAddr> for SocketAddress {
        fn from(addr: net::SocketAddr) -> Self {
            match addr {
                net::SocketAddr::V4(addr) => Self::IpV4(addr.into()),
                net::SocketAddr::V6(addr) => Self::IpV6(addr.into()),
            }
        }
    }
}

define_inet_type!(
    pub struct Protocol {
        id: u8,
    }
);

impl fmt::Debug for Protocol {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.debug_tuple("ip::Protocol")
            .field(&format_args!("{self}"))
            .finish()
    }
}

impl fmt::Display for Protocol {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Self::HOPOPT => "IPv6 Hop-by-Hop Option",
            Self::ICMP => "Internet Control Message",
            Self::IPV4 => "IPv4 Encapsulation",
            Self::TCP => "Transmission Control",
            Self::UDP => "User Datagram",
            Self::IPV6 => "IPv6 Encapsulation",
            Self::IPV6_ROUTE => "Routing Header for IPv6",
            Self::IPV6_FRAG => "Fragment Header for IPv6",
            Self::IPV6_ICMP => "ICMP for IPv6",
            Self::IPV6_NO_NXT => "No Next Header for IPv6",
            Self::IPV6_OPTS => "Destination Options for IPv6",
            Self::UDPLITE => "Lightweight User Datagram",
            Self { id } => return write!(f, "[unknown 0x{id:02x}]"),
        }
        .fmt(f)
    }
}

macro_rules! impl_p {
    ($fun:ident, $cap:ident, $val:literal) => {
        pub const $cap: Self = Self { id: $val };

        #[inline]
        pub const fn $fun(self) -> bool {
            self.id == $val
        }
    };
}

impl Protocol {
    // https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml
    // NOTE: these variants were added as the ones we think we'll need. feel free to add more as
    //       needed.
    impl_p!(is_hop_by_hop, HOPOPT, 0);
    impl_p!(is_icmp, ICMP, 1);
    impl_p!(is_ipv4, IPV4, 4);
    impl_p!(is_tcp, TCP, 6);
    impl_p!(is_udp, UDP, 17);
    impl_p!(is_ipv6, IPV6, 41);
    impl_p!(is_ipv6_route, IPV6_ROUTE, 43);
    impl_p!(is_ipv6_fragment, IPV6_FRAG, 44);
    impl_p!(is_ipv6_icmp, IPV6_ICMP, 58);
    impl_p!(is_ipv6_no_next, IPV6_NO_NXT, 59);
    impl_p!(is_ipv6_options, IPV6_OPTS, 60);
    impl_p!(is_udplite, UDPLITE, 136);
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::net::{SocketAddr, ToSocketAddrs};

    const TESTS: &[&str] = &[
        "127.0.0.1:80",
        "192.168.1.1:40",
        "255.255.255.255:12345",
        "[::]:443",
        "[::1]:123",
        "[2001:0db8:85a3:0001:0002:8a2e:0370:7334]:9000",
    ];

    #[test]
    //= https://www.rfc-editor.org/rfc/rfc5156#section-2.2
    //= type=test
    //# ::FFFF:0:0/96 are the IPv4-mapped addresses [RFC4291].
    fn to_ipv6_mapped_test() {
        for test in TESTS.iter() {
            // assert that this implementation matches the standard library
            let addr: SocketAddr = test.parse().unwrap();
            let address: SocketAddress = addr.into();
            let addr = match addr {
                SocketAddr::V4(addr) => {
                    let ip = addr.ip().to_ipv6_mapped();
                    (ip, addr.port()).into()
                }
                _ => addr,
            };
            let address = address.to_ipv6_mapped().into();
            assert_eq!(addr, address);
        }
    }

    #[test]
    fn unmap_test() {
        for test in TESTS.iter() {
            // assert that unmap correctly converts IPv4 mapped addresses
            let addr: SocketAddr = test.parse().unwrap();
            let address: SocketAddress = addr.into();
            let actual: SocketAddress = address.to_ipv6_mapped().into();
            let actual = actual.unmap();
            assert_eq!(address, actual);
        }
    }

    #[test]
    fn display_test() {
        for test in TESTS.iter() {
            // assert that this implementation matches the standard library
            let addr: SocketAddr = test.parse().unwrap();
            let address: SocketAddress = addr.into();
            assert_eq!(addr.to_string(), address.to_string());
        }
    }

    #[test]
    fn to_socket_addrs_test() {
        for test in TESTS.iter() {
            let addr: SocketAddr = test.parse().unwrap();
            let address: SocketAddress = addr.into();
            for address in address.to_socket_addrs().unwrap() {
                assert_eq!(addr, address);
            }
        }
    }
}


// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

use crate::inet::{
    ip,
    ipv6::{IpV6Address, SocketAddressV6},
    unspecified::Unspecified,
    ExplicitCongestionNotification,
};
use core::{fmt, mem::size_of};
use s2n_codec::zerocopy::U16;

//= https://www.rfc-editor.org/rfc/rfc791#section-2.3
//# Addresses are fixed length of four octets (32 bits).
const IPV4_LEN: usize = 32 / 8;

define_inet_type!(
    pub struct IpV4Address {
        octets: [u8; IPV4_LEN],
    }
);

impl IpV4Address {
    /// An unspecified IpV4Address
    pub const UNSPECIFIED: Self = Self {
        octets: [0; IPV4_LEN],
    };

    /// Returns the [`ip::UnicastScope`] for the given address
    ///
    /// See the [IANA Registry](https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml)
    /// for more details.
    ///
    /// ```
    /// use s2n_quic_core::inet::{IpV4Address, ip::UnicastScope::*};
    ///
    /// assert_eq!(IpV4Address::from([0, 0, 0, 0]).unicast_scope(), None);
    /// assert_eq!(IpV4Address::from([127, 0, 0, 1]).unicast_scope(), Some(Loopback));
    /// assert_eq!(IpV4Address::from([127, 1, 1, 1]).unicast_scope(), Some(Loopback));
    /// assert_eq!(IpV4Address::from([10, 0, 0, 1]).unicast_scope(), Some(Private));
    /// assert_eq!(IpV4Address::from([100, 64, 0, 1]).unicast_scope(), Some(Private));
    /// assert_eq!(IpV4Address::from([169, 254, 1, 2]).unicast_scope(), Some(LinkLocal));
    /// assert_eq!(IpV4Address::from([192, 0, 0, 1]).unicast_scope(), None);
    /// assert_eq!(IpV4Address::from([192, 0, 0, 9]).unicast_scope(), Some(Global));
    /// assert_eq!(IpV4Address::from([192, 0, 0, 10]).unicast_scope(), Some(Global));
    /// assert_eq!(IpV4Address::from([192, 0, 2, 1]).unicast_scope(), None);
    /// assert_eq!(IpV4Address::from([198, 18, 0, 0]).unicast_scope(), None);
    /// assert_eq!(IpV4Address::from([198, 19, 1, 1]).unicast_scope(), None);
    /// assert_eq!(IpV4Address::from([233, 252, 0, 1]).unicast_scope(), None);
    /// assert_eq!(IpV4Address::from([240, 255, 255, 255]).unicast_scope(), None);
    /// assert_eq!(IpV4Address::from([255, 255, 255, 255]).unicast_scope(), None);
    /// assert_eq!(IpV4Address::from([92, 88, 99, 123]).unicast_scope(), Some(Global));
    /// assert_eq!(IpV4Address::from([168, 254, 169, 253]).unicast_scope(), Some(Global));
    /// assert_eq!(IpV4Address::from([224, 0, 0, 1]).unicast_scope(), Some(Global));
    /// ```
    #[inline]
    pub const fn unicast_scope(self) -> Option<ip::UnicastScope> {
        use ip::UnicastScope::*;

        // https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml
        //
        // NOTE: Even though 192.88.99.0/24 is reserved for "6to4 Relay Anycast", it has been
        //       deprecated so this code considers it as `Global`.
        match self.octets {
            // NOTE: this RFC doesn't quite follow modern formatting so it doesn't parse with the
            // compliance tool
            // https://www.rfc-editor.org/rfc/rfc1122#section-3.2.1.3
            // (a)  { 0, 0 }
            //
            //     This host on this network.  MUST NOT be sent, except as
            //     a source address as part of an initialization procedure
            //     by which the host learns its own IP address.
            //
            //     See also Section 3.3.6 for a non-standard use of {0,0}.
            [0, 0, 0, 0] => None,

            // (b)  { 0, <Host-number> }
            //
            //     Specified host on this network.  It MUST NOT be sent,
            //     except as a source address as part of an initialization
            //     procedure by which the host learns its full IP address.
            [0, _, _, _] => None,

            // NOTE: this RFC doesn't quite follow modern formatting so it doesn't parse with the
            // compliance tool
            // https://www.rfc-editor.org/rfc/rfc1122#section-3.2.1.3
            // (g)  { 127, <any> }
            //
            //   Internal host loopback address.  Addresses of this form
            //   MUST NOT appear outside a host.
            [127, _, _, _] => Some(Loopback),

            //= https://www.rfc-editor.org/rfc/rfc1918#section-3
            //# The Internet Assigned Numbers Authority (IANA) has reserved the
            //# following three blocks of the IP address space for private internets:
            //#
            //# 10.0.0.0        -   10.255.255.255  (10/8 prefix)
            //# 172.16.0.0      -   172.31.255.255  (172.16/12 prefix)
            //# 192.168.0.0     -   192.168.255.255 (192.168/16 prefix)
            [10, _, _, _] => Some(Private),
            [172, 16..=31, _, _] => Some(Private),
            [192, 168, _, _] => Some(Private),

            //= https://www.rfc-editor.org/rfc/rfc6598#section-7
            //# The Shared Address Space address range is 100.64.0.0/10.
            [100, 64..=127, _, _] => {
                //= https://www.rfc-editor.org/rfc/rfc6598#section-1
                //# Shared Address Space is similar to [RFC1918] private address space in
                //# that it is not globally routable address space and can be used by
                //# multiple pieces of equipment.
                Some(Private)
            }

            //= https://www.rfc-editor.org/rfc/rfc3927#section-8
            //# The IANA has allocated the prefix 169.254/16 for the use described in
            //# this document.
            [169, 254, _, _] => Some(LinkLocal),

            //= https://www.rfc-editor.org/rfc/rfc7723#section-4.1
            //# +----------------------+-------------------------------------------+
            //# | Attribute            | Value                                     |
            //# +----------------------+-------------------------------------------+
            //# | Address Block        | 192.0.0.9/32                              |
            //# | Name                 | Port Control Protocol Anycast             |
            //# | RFC                  | RFC 7723 (this document)                  |
            //# | Allocation Date      | October 2015                              |
            //# | Termination Date     | N/A                                       |
            //# | Source               | True                                      |
            //# | Destination          | True                                      |
            //# | Forwardable          | True                                      |
            //# | Global               | True                                      |
            [192, 0, 0, 9] => Some(Global),

            //= https://www.rfc-editor.org/rfc/rfc8155#section-8.1
            //# +----------------------+-------------------------------------------+
            //# | Attribute            | Value                                     |
            //# +----------------------+-------------------------------------------+
            //# | Address Block        | 192.0.0.10/32                             |
            //# | Name                 | Traversal Using Relays around NAT Anycast |
            //# | RFC                  | RFC 8155                                  |
            //# | Allocation Date      | 2017-02                                   |
            //# | Termination Date     | N/A                                       |
            //# | Source               | True                                      |
            //# | Destination          | True                                      |
            //# | Forwardable          | True                                      |
            //# | Global               | True                                      |
            [192, 0, 0, 10] => Some(Global),

            //= https://www.rfc-editor.org/rfc/rfc6890#section-2.1
            //# Table 7 of this document records the assignment of an IPv4 address
            //# block (192.0.0.0/24) to IANA for IETF protocol assignments.
            [192, 0, 0, _] => None,

            //= https://www.rfc-editor.org/rfc/rfc2544#C.2.2
            //# The network addresses 192.18.0.0 through 198.19.255.255 are have been
            //# assigned to the BMWG by the IANA for this purpose.
            // NOTE: this range should be 198.18.0.0/15 as corrected by https://www.rfc-editor.org/errata/eid423
            [198, 18..=19, _, _] => None,

            //= https://www.rfc-editor.org/rfc/rfc5737#section-3
            //# The blocks 192.0.2.0/24 (TEST-NET-1), 198.51.100.0/24 (TEST-NET-2),
            //# and 203.0.113.0/24 (TEST-NET-3) are provided for use in
            //# documentation.
            [192, 0, 2, _] => None,
            [198, 51, 100, _] => None,
            [203, 0, 113, _] => None,

            //= https://www.rfc-editor.org/rfc/rfc6676#section-2
            //# For Any-Source Multicast (ASM), the IPv4 multicast addresses
            //# allocated for documentation purposes are 233.252.0.0 - 233.252.0.255
            //# (233.252.0.0/24).
            [233, 252, 0, _] => None,

            //= https://www.rfc-editor.org/rfc/rfc1112#section-4
            //# Class E IP addresses, i.e.,
            //# those with "1111" as their high-order four bits, are reserved for
            //# future addressing modes.

            //= https://www.rfc-editor.org/rfc/rfc919#section-7
            //# The address 255.255.255.255 denotes a broadcast on a local hardware
            //# network, which must not be forwarded.
            [240..=255, _, _, _] => None,

            // everything else is considered global
            _ => Some(Global),
        }
    }

    /// Converts the IP address into a IPv6 mapped address
    #[inline]
    pub const fn to_ipv6_mapped(self) -> IpV6Address {
        //= https://www.rfc-editor.org/rfc/rfc5156#section-2.2
        //# ::FFFF:0:0/96 are the IPv4-mapped addresses [RFC4291].
        let mut addr = [0; size_of::<IpV6Address>()];
        let [a, b, c, d] = self.octets;
        addr[10] = 0xFF;
        addr[11] = 0xFF;
        addr[12] = a;
        addr[13] = b;
        addr[14] = c;
        addr[15] = d;
        IpV6Address { octets: addr }
    }

    #[inline]
    pub fn with_port(self, port: u16) -> SocketAddressV4 {
        SocketAddressV4 {
            ip: self,
            port: port.into(),
        }
    }
}

impl fmt::Debug for IpV4Address {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        write!(fmt, "IPv4Address({self})")
    }
}

impl fmt::Display for IpV4Address {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        let octets = &self.octets;
        write!(
            fmt,
            "{}.{}.{}.{}",
            octets[0], octets[1], octets[2], octets[3]
        )
    }
}

impl Unspecified for IpV4Address {
    #[inline]
    fn is_unspecified(&self) -> bool {
        Self::UNSPECIFIED.eq(self)
    }
}

test_inet_snapshot!(ipv4, ipv4_snapshot_test, IpV4Address);

define_inet_type!(
    pub struct SocketAddressV4 {
        ip: IpV4Address,
        port: U16,
    }
);

impl SocketAddressV4 {
    pub const UNSPECIFIED: Self = Self {
        ip: IpV4Address::UNSPECIFIED,
        port: U16::ZERO,
    };

    #[inline]
    pub const fn ip(&self) -> &IpV4Address {
        &self.ip
    }

    #[inline]
    pub fn port(self) -> u16 {
        self.port.into()
    }

    #[inline]
    pub fn set_port(&mut self, port: u16) {
        self.port.set(port)
    }

    #[inline]
    pub const fn unicast_scope(&self) -> Option<ip::UnicastScope> {
        self.ip.unicast_scope()
    }

    /// Converts the IP address into a IPv6 mapped address
    #[inline]
    pub const fn to_ipv6_mapped(self) -> SocketAddressV6 {
        let ip = self.ip().to_ipv6_mapped();
        let port = self.port;
        SocketAddressV6 { ip, port }
    }
}

impl fmt::Debug for SocketAddressV4 {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        write!(fmt, "SocketAddressV4({self})")
    }
}

impl fmt::Display for SocketAddressV4 {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        write!(fmt, "{}:{}", self.ip, self.port)
    }
}

impl Unspecified for SocketAddressV4 {
    #[inline]
    fn is_unspecified(&self) -> bool {
        Self::UNSPECIFIED.eq(self)
    }
}

test_inet_snapshot!(socket_v4, socket_v4_snapshot_test, SocketAddressV4);

impl From<[u8; IPV4_LEN]> for IpV4Address {
    #[inline]
    fn from(octets: [u8; IPV4_LEN]) -> Self {
        Self { octets }
    }
}

impl From<IpV4Address> for [u8; IPV4_LEN] {
    #[inline]
    fn from(address: IpV4Address) -> Self {
        address.octets
    }
}

//= https://www.rfc-editor.org/rfc/rfc791.html#section-3.1
//#  A summary of the contents of the internet header follows:
//#
//#
//#    0                   1                   2                   3
//#    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
//#   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//#   |Version|  IHL  |Type of Service|          Total Length         |
//#   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//#   |         Identification        |Flags|      Fragment Offset    |
//#   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//#   |  Time to Live |    Protocol   |         Header Checksum       |
//#   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//#   |                       Source Address                          |
//#   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//#   |                    Destination Address                        |
//#   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//#   |                    Options                    |    Padding    |
//#   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

// Note that Options and Padding are variable length depending on the IHL field
// so they can't be included directly in the fixed-sized struct.
define_inet_type!(
    pub struct Header {
        vihl: Vihl,
        tos: Tos,
        total_len: U16,
        id: U16,
        flag_fragment: FlagFragment,
        ttl: u8,
        protocol: ip::Protocol,
        checksum: U16,
        source: IpV4Address,
        destination: IpV4Address,
    }
);

impl fmt::Debug for Header {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.debug_struct("ipv4::Header")
            .field("version", &self.vihl.version())
            .field("header_len", &self.vihl.header_len())
            .field("dscp", &self.tos.dscp())
            .field("ecn", &self.tos.ecn())
            .field("total_len", &self.total_len)
            .field("id", &format_args!("0x{:04x}", self.id.get()))
            .field("flags (reserved)", &self.flag_fragment.reserved())
            .field(
                "flags (don't fragment)",
                &self.flag_fragment.dont_fragment(),
            )
            .field(
                "flags (more fragments)",
                &self.flag_fragment.more_fragments(),
            )
            .field("fragment_offset", &self.flag_fragment.fragment_offset())
            .field("ttl", &self.ttl)
            .field("protocol", &self.protocol)
            .field("checksum", &format_args!("0x{:04x}", self.checksum.get()))
            .field("source", &self.source)
            .field("destination", &self.destination)
            .finish()
    }
}

impl Header {
    /// Swaps the direction of the header
    #[inline]
    pub fn swap(&mut self) {
        core::mem::swap(&mut self.source, &mut self.destination)
    }

    #[inline]
    pub const fn vihl(&self) -> &Vihl {
        &self.vihl
    }

    #[inline]
    pub fn vihl_mut(&mut self) -> &mut Vihl {
        &mut self.vihl
    }

    #[inline]
    pub const fn tos(&self) -> &Tos {
        &self.tos
    }

    #[inline]
    pub fn tos_mut(&mut self) -> &mut Tos {
        &mut self.tos
    }

    #[inline]
    pub const fn total_len(&self) -> &U16 {
        &self.total_len
    }

    #[inline]
    pub fn total_len_mut(&mut self) -> &mut U16 {
        &mut self.total_len
    }

    #[inline]
    pub const fn id(&self) -> &U16 {
        &self.id
    }

    #[inline]
    pub fn id_mut(&mut self) -> &mut U16 {
        &mut self.id
    }

    #[inline]
    pub const fn flag_fragment(&self) -> &FlagFragment {
        &self.flag_fragment
    }

    #[inline]
    pub fn flag_fragment_mut(&mut self) -> &mut FlagFragment {
        &mut self.flag_fragment
    }

    #[inline]
    pub const fn ttl(&self) -> &u8 {
        &self.ttl
    }

    #[inline]
    pub fn ttl_mut(&mut self) -> &mut u8 {
        &mut self.ttl
    }

    #[inline]
    pub const fn protocol(&self) -> &ip::Protocol {
        &self.protocol
    }

    #[inline]
    pub fn protocol_mut(&mut self) -> &mut ip::Protocol {
        &mut self.protocol
    }

    #[inline]
    pub const fn checksum(&self) -> &U16 {
        &self.checksum
    }

    #[inline]
    pub fn checksum_mut(&mut self) -> &mut U16 {
        &mut self.checksum
    }

    #[inline]
    pub const fn source(&self) -> &IpV4Address {
        &self.source
    }

    #[inline]
    pub fn source_mut(&mut self) -> &mut IpV4Address {
        &mut self.source
    }

    #[inline]
    pub const fn destination(&self) -> &IpV4Address {
        &self.destination
    }

    #[inline]
    pub fn destination_mut(&mut self) -> &mut IpV4Address {
        &mut self.destination
    }

    #[inline]
    pub fn update_checksum(&mut self) {
        use core::hash::Hasher;

        self.checksum.set(0);

        let bytes = self.as_bytes();

        let mut checksum = crate::inet::checksum::Checksum::generic();

        checksum.write(bytes);

        self.checksum.set_be(checksum.finish_be());
    }
}

// This struct covers the bits for version and IHL (header len).
//
// Rust doesn't have the ability to do arbitrary bit sized values so we have to round up to the
// nearest byte.
define_inet_type!(
    pub struct Vihl {
        value: u8,
    }
);

impl fmt::Debug for Vihl {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.debug_struct("Vihl")
            .field("version", &self.version())
            .field("header_len", &self.header_len())
            .finish()
    }
}

impl Vihl {
    #[inline]
    pub fn version(&self) -> u8 {
        self.value >> 4
    }

    #[inline]
    pub fn set_version(&mut self, value: u8) -> &mut Self {
        self.value = value << 4 | (self.value & 0x0F);
        self
    }

    #[inline]
    pub fn header_len(&self) -> u8 {
        self.value & 0x0F
    }

    #[inline]
    pub fn set_header_len(&mut self, value: u8) -> &mut Self {
        self.value = (self.value & 0xF0) | (value & 0x0F);
        self
    }
}

// This struct covers the bits for DSCP and ECN.
//
// Rust doesn't have the ability to do arbitrary bit sized values so we have to round up to the
// nearest byte.
define_inet_type!(
    pub struct Tos {
        value: u8,
    }
);

impl Tos {
    /// Differentiated Services Code Point
    #[inline]
    pub fn dscp(&self) -> u8 {
        self.value >> 2
    }

    #[inline]
    pub fn set_dscp(&mut self, value: u8) -> &mut Self {
        self.value = (value << 2) | (self.value & 0b11);
        self
    }

    #[inline]
    pub fn ecn(&self) -> ExplicitCongestionNotification {
        ExplicitCongestionNotification::new(self.value & 0b11)
    }

    #[inline]
    pub fn set_ecn(&mut self, ecn: ExplicitCongestionNotification) -> &mut Self {
        self.value = (self.value & !0b11) | ecn as u8;
        self
    }
}

impl fmt::Debug for Tos {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.debug_struct("ipv4::Tos")
            .field("dscp", &self.dscp())
            .field("ecn", &self.ecn())
            .finish()
    }
}

// This struct covers the bits for Flags and Fragment Offset.
//
// Rust doesn't have the ability to do arbitrary bit sized values so we have to round up to the
// nearest byte.
define_inet_type!(
    pub struct FlagFragment {
        value: U16,
    }
);

impl FlagFragment {
    const FRAGMENT_MASK: u16 = 0b0001_1111_1111_1111;

    #[inline]
    pub fn reserved(&self) -> bool {
        self.get(1 << 15)
    }

    pub fn set_reserved(&mut self, enabled: bool) -> &mut Self {
        self.set(1 << 15, enabled)
    }

    #[inline]
    pub fn dont_fragment(&self) -> bool {
        self.get(1 << 14)
    }

    #[inline]
    pub fn set_dont_fragment(&mut self, enabled: bool) -> &mut Self {
        self.set(1 << 14, enabled)
    }

    #[inline]
    pub fn more_fragments(&self) -> bool {
        self.get(1 << 13)
    }

    #[inline]
    pub fn set_more_fragments(&mut self, enabled: bool) -> &mut Self {
        self.set(1 << 13, enabled)
    }

    #[inline]
    pub fn fragment_offset(&self) -> u16 {
        self.value.get() & Self::FRAGMENT_MASK
    }

    #[inline]
    pub fn set_fragment_offset(&mut self, offset: u16) -> &mut Self {
        self.value
            .set(self.value.get() & !Self::FRAGMENT_MASK | offset & Self::FRAGMENT_MASK);
        self
    }

    #[inline]
    fn get(&self, mask: u16) -> bool {
        self.value.get() & mask == mask
    }

    #[inline]
    fn set(&mut self, mask: u16, enabled: bool) -> &mut Self {
        let value = self.value.get();
        let value = if enabled { value | mask } else { value & !mask };
        self.value.set(value);
        self
    }
}

impl fmt::Debug for FlagFragment {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.debug_struct("ipv4::FlagFragment")
            .field("reserved", &self.reserved())
            .field("dont_fragment", &self.dont_fragment())
            .field("more_fragments", &self.more_fragments())
            .field("fragment_offset", &self.fragment_offset())
            .finish()
    }
}

#[cfg(any(test, feature = "std"))]
mod std_conversion {
    use super::*;
    use std::net;

    impl From<net::Ipv4Addr> for IpV4Address {
        fn from(address: net::Ipv4Addr) -> Self {
            (&address).into()
        }
    }

    impl From<&net::Ipv4Addr> for IpV4Address {
        fn from(address: &net::Ipv4Addr) -> Self {
            address.octets().into()
        }
    }

    impl From<IpV4Address> for net::Ipv4Addr {
        fn from(address: IpV4Address) -> Self {
            address.octets.into()
        }
    }

    impl From<net::SocketAddrV4> for SocketAddressV4 {
        fn from(address: net::SocketAddrV4) -> Self {
            let ip = address.ip().into();
            let port = address.port().into();
            Self { ip, port }
        }
    }

    impl From<(net::Ipv4Addr, u16)> for SocketAddressV4 {
        fn from((ip, port): (net::Ipv4Addr, u16)) -> Self {
            Self::new(ip, port)
        }
    }

    impl From<SocketAddressV4> for net::SocketAddrV4 {
        fn from(address: SocketAddressV4) -> Self {
            let ip = address.ip.into();
            let port = address.port.into();
            Self::new(ip, port)
        }
    }

    impl From<&SocketAddressV4> for net::SocketAddrV4 {
        fn from(address: &SocketAddressV4) -> Self {
            let ip = address.ip.into();
            let port = address.port.into();
            Self::new(ip, port)
        }
    }

    impl From<SocketAddressV4> for net::SocketAddr {
        fn from(address: SocketAddressV4) -> Self {
            let addr: net::SocketAddrV4 = address.into();
            addr.into()
        }
    }

    impl From<&SocketAddressV4> for net::SocketAddr {
        fn from(address: &SocketAddressV4) -> Self {
            let addr: net::SocketAddrV4 = address.into();
            addr.into()
        }
    }

    impl net::ToSocketAddrs for SocketAddressV4 {
        type Iter = std::iter::Once<net::SocketAddr>;

        fn to_socket_addrs(&self) -> std::io::Result<Self::Iter> {
            let ip = self.ip.into();
            let port = self.port.into();
            let addr = net::SocketAddrV4::new(ip, port);
            Ok(std::iter::once(addr.into()))
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use bolero::{check, generator::*};
    use s2n_codec::{DecoderBuffer, DecoderBufferMut};

    /// Asserts the Scope returned matches a known implementation
    #[test]
    #[cfg_attr(kani, kani::proof, kani::unwind(5), kani::solver(kissat))]
    fn scope_test() {
        let g = gen::<[u8; 4]>().map_gen(IpV4Address::from);
        check!().with_generator(g).cloned().for_each(|subject| {
            use ip::UnicastScope::*;

            let expected = std::net::Ipv4Addr::from(subject);

            // Several IP methods are blocked behind `feature(ip)`: https://github.com/rust-lang/rust/issues/27709
            //
            // Use the `ip_network` crate to fill any gaps
            let network = ip_network::Ipv4Network::from(expected);

            match subject.unicast_scope() {
                Some(Global) => {
                    // ip_network has a bug in the `is_global` logic. Remove this once its fixed
                    // and published
                    // https://github.com/JakubOnderka/ip_network/pull/7
                    if subject.octets == [192, 0, 0, 9] || subject.octets == [192, 0, 0, 10] {
                        return;
                    }

                    assert!(network.is_global());
                }
                Some(Private) => {
                    assert!(expected.is_private() || network.is_shared_address_space());
                }
                Some(Loopback) => {
                    assert!(expected.is_loopback());
                }
                Some(LinkLocal) => {
                    assert!(expected.is_link_local());
                }
                None => {
                    assert!(
                        expected.is_broadcast()
                            || expected.is_multicast()
                            || expected.is_documentation()
                            || network.is_benchmarking()
                            || network.is_ietf_protocol_assignments()
                            || network.is_reserved()
                            || network.is_local_identification()
                            || network.is_unspecified()
                    );
                }
            }
        })
    }

    #[test]
    #[cfg_attr(miri, ignore)]
    fn snapshot_test() {
        let mut buffer = vec![0u8; core::mem::size_of::<Header>()];
        for (idx, byte) in buffer.iter_mut().enumerate() {
            *byte = idx as u8;
        }
        let decoder = DecoderBuffer::new(&buffer);
        let (header, _) = decoder.decode::<&Header>().unwrap();
        insta::assert_debug_snapshot!("snapshot_test", header);

        for byte in &mut buffer {
            *byte = 255;
        }
        let decoder = DecoderBuffer::new(&buffer);
        let (header, _) = decoder.decode::<&Header>().unwrap();
        insta::assert_debug_snapshot!("snapshot_filled_test", header);
    }

    #[test]
    #[cfg_attr(kani, kani::proof, kani::unwind(5), kani::solver(kissat))]
    fn header_getter_setter_test() {
        check!().with_type::<Header>().for_each(|expected| {
            let mut buffer = [255u8; core::mem::size_of::<Header>()];
            let decoder = DecoderBufferMut::new(&mut buffer);
            let (header, _) = decoder.decode::<&mut Header>().unwrap();
            {
                // use all of the getters and setters to copy over each field
                header
                    .vihl_mut()
                    .set_version(expected.vihl().version())
                    .set_header_len(expected.vihl().header_len());
                header
                    .tos_mut()
                    .set_dscp(expected.tos().dscp())
                    .set_ecn(expected.tos().ecn());
                header.id_mut().set(expected.id().get());
                header.total_len_mut().set(expected.total_len().get());
                header
                    .flag_fragment_mut()
                    .set_reserved(expected.flag_fragment().reserved())
                    .set_dont_fragment(expected.flag_fragment().dont_fragment())
                    .set_more_fragments(expected.flag_fragment().more_fragments())
                    .set_fragment_offset(expected.flag_fragment().fragment_offset());
                *header.ttl_mut() = *expected.ttl();
                *header.protocol_mut() = *expected.protocol();
                header.checksum_mut().set(expected.checksum().get());
                *header.source_mut() = *expected.source();
                *header.destination_mut() = *expected.destination();
            }

            let decoder = DecoderBuffer::new(&buffer);
            let (actual, _) = decoder.decode::<&Header>().unwrap();
            {
                // make sure all of the values match
                assert_eq!(expected, actual);
                assert_eq!(expected.vihl(), actual.vihl());
                assert_eq!(expected.vihl().version(), actual.vihl().version());
                assert_eq!(expected.vihl().header_len(), actual.vihl().header_len());
                assert_eq!(expected.tos(), actual.tos());
                assert_eq!(expected.tos().dscp(), actual.tos().dscp());
                assert_eq!(expected.tos().ecn(), actual.tos().ecn());
                assert_eq!(expected.id(), actual.id());
                assert_eq!(expected.total_len(), actual.total_len());
                assert_eq!(expected.flag_fragment(), actual.flag_fragment());
                assert_eq!(
                    expected.flag_fragment().reserved(),
                    actual.flag_fragment().reserved()
                );
                assert_eq!(
                    expected.flag_fragment().dont_fragment(),
                    actual.flag_fragment().dont_fragment()
                );
                assert_eq!(
                    expected.flag_fragment().more_fragments(),
                    actual.flag_fragment().more_fragments()
                );
                assert_eq!(expected.ttl(), actual.ttl());
                assert_eq!(expected.protocol(), actual.protocol());
                assert_eq!(expected.checksum(), actual.checksum());
                assert_eq!(expected.source(), actual.source());
                assert_eq!(expected.destination(), actual.destination());
            }
        })
    }

    #[test]
    fn header_round_trip_test() {
        check!().for_each(|buffer| {
            s2n_codec::assert_codec_round_trip_bytes!(Header, buffer);
        });
    }
}


// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

use crate::inet::{
    ip, ipv4::IpV4Address, unspecified::Unspecified, ExplicitCongestionNotification, IpAddress,
    SocketAddress, SocketAddressV4,
};
use core::fmt;
use s2n_codec::zerocopy::U16;

//= https://www.rfc-editor.org/rfc/rfc2373#section-2.0
//# IPv6 addresses are 128-bit identifiers for interfaces and sets of interfaces.
const IPV6_LEN: usize = 128 / 8;

define_inet_type!(
    pub struct IpV6Address {
        octets: [u8; IPV6_LEN],
    }
);

impl IpV6Address {
    /// An unspecified IpV6Address
    pub const UNSPECIFIED: Self = Self {
        octets: [0; IPV6_LEN],
    };

    #[inline]
    pub const fn segments(&self) -> [u16; 8] {
        let octets = &self.octets;
        [
            u16::from_be_bytes([octets[0], octets[1]]),
            u16::from_be_bytes([octets[2], octets[3]]),
            u16::from_be_bytes([octets[4], octets[5]]),
            u16::from_be_bytes([octets[6], octets[7]]),
            u16::from_be_bytes([octets[8], octets[9]]),
            u16::from_be_bytes([octets[10], octets[11]]),
            u16::from_be_bytes([octets[12], octets[13]]),
            u16::from_be_bytes([octets[14], octets[15]]),
        ]
    }

    /// Converts the IP address into IPv4 if it is mapped, otherwise the address is unchanged
    #[inline]
    pub const fn unmap(self) -> IpAddress {
        match self.segments() {
            // special-case unspecified and loopback
            [0, 0, 0, 0, 0, 0, 0, 0] => IpAddress::Ipv6(self),
            [0, 0, 0, 0, 0, 0, 0, 1] => IpAddress::Ipv6(self),

            //= https://www.rfc-editor.org/rfc/rfc4291#section-2.5.5.1
            //# The format of the "IPv4-Compatible IPv6 address" is as
            //# follows:
            //#
            //# |                80 bits               | 16 |      32 bits        |
            //# +--------------------------------------+--------------------------+
            //# |0000..............................0000|0000|    IPv4 address     |
            //# +--------------------------------------+----+---------------------+

            //= https://www.rfc-editor.org/rfc/rfc4291#section-2.5.5.2
            //# The format of the "IPv4-mapped IPv6
            //# address" is as follows:
            //#
            //# |                80 bits               | 16 |      32 bits        |
            //# +--------------------------------------+--------------------------+
            //# |0000..............................0000|FFFF|    IPv4 address     |
            //# +--------------------------------------+----+---------------------+

            //= https://www.rfc-editor.org/rfc/rfc6052#section-2.1
            //# This document reserves a "Well-Known Prefix" for use in an
            //# algorithmic mapping.  The value of this IPv6 prefix is:
            //#
            //#   64:ff9b::/96
            [0, 0, 0, 0, 0, 0, ab, cd]
            | [0, 0, 0, 0, 0, 0xffff, ab, cd]
            | [0x64, 0xff9b, 0, 0, 0, 0, ab, cd] => {
                let [a, b] = u16::to_be_bytes(ab);
                let [c, d] = u16::to_be_bytes(cd);
                IpAddress::Ipv4(IpV4Address {
                    octets: [a, b, c, d],
                })
            }
            _ => IpAddress::Ipv6(self),
        }
    }

    /// Returns the [`ip::UnicastScope`] for the given address
    ///
    /// See the [IANA Registry](https://www.iana.org/assignments/ipv6-address-space/ipv6-address-space.xhtml)
    /// for more details.
    ///
    /// ```
    /// use s2n_quic_core::inet::{IpV4Address, IpV6Address, ip::UnicastScope::*};
    ///
    /// assert_eq!(IpV6Address::from([0, 0, 0, 0, 0, 0, 0, 0]).unicast_scope(), None);
    /// assert_eq!(IpV6Address::from([0, 0, 0, 0, 0, 0, 0, 1]).unicast_scope(), Some(Loopback));
    /// assert_eq!(IpV6Address::from([0xff0e, 0, 0, 0, 0, 0, 0, 0]).unicast_scope(), None);
    /// assert_eq!(IpV6Address::from([0xfe80, 0, 0, 0, 0, 0, 0, 0]).unicast_scope(), Some(LinkLocal));
    /// assert_eq!(IpV6Address::from([0xfc02, 0, 0, 0, 0, 0, 0, 0]).unicast_scope(), Some(Private));
    /// // documentation
    /// assert_eq!(IpV6Address::from([0x2001, 0xdb8, 0, 0, 0, 0, 0, 0]).unicast_scope(), None);
    /// // benchmarking
    /// assert_eq!(IpV6Address::from([0x2001, 0x0200, 0, 0, 0, 0, 0, 0]).unicast_scope(), None);
    /// // IPv4-mapped address
    /// assert_eq!(IpV4Address::from([92, 88, 99, 123]).to_ipv6_mapped().unicast_scope(), Some(Global));
    /// ```
    #[inline]
    pub const fn unicast_scope(self) -> Option<ip::UnicastScope> {
        use ip::UnicastScope::*;

        // If this is an IpV4 ip, delegate to that implementation
        if let IpAddress::Ipv4(ip) = self.unmap() {
            return ip.unicast_scope();
        }

        // https://www.iana.org/assignments/ipv6-address-space/ipv6-address-space.xhtml
        match self.segments() {
            //= https://www.rfc-editor.org/rfc/rfc4291#section-2.5.2
            //# The address 0:0:0:0:0:0:0:0 is called the unspecified address.
            [0, 0, 0, 0, 0, 0, 0, 0] => None,

            //= https://www.rfc-editor.org/rfc/rfc4291#section-2.5.3
            //# The unicast address 0:0:0:0:0:0:0:1 is called the loopback address.
            [0, 0, 0, 0, 0, 0, 0, 1] => Some(Loopback),

            //= https://www.rfc-editor.org/rfc/rfc6666#section-4
            //# Per this document, IANA has recorded the allocation of the IPv6
            //# address prefix 0100::/64 as a Discard-Only Prefix in the "Internet
            //# Protocol Version 6 Address Space" and added the prefix to the "IANA
            //# IPv6 Special Purpose Address Registry" [IANA-IPV6REG].
            [0x0100, 0, 0, 0, ..] => None,

            //= https://www.rfc-editor.org/rfc/rfc7723#section-4.2
            //# +----------------------+-------------------------------------------+
            //# | Attribute            | Value                                     |
            //# +----------------------+-------------------------------------------+
            //# | Address Block        | 2001:1::1/128                             |
            //# | Name                 | Port Control Protocol Anycast             |
            //# | RFC                  | RFC 7723 (this document)                  |
            //# | Allocation Date      | October 2015                              |
            //# | Termination Date     | N/A                                       |
            //# | Source               | True                                      |
            //# | Destination          | True                                      |
            //# | Forwardable          | True                                      |
            //# | Global               | True                                      |
            //# | Reserved-by-Protocol | False                                     |
            //# +----------------------+-------------------------------------------+
            [0x2001, 0x1, 0, 0, 0, 0, 0, 0x1] => Some(Global),

            //= https://www.rfc-editor.org/rfc/rfc8155#section-8.2
            //# +----------------------+-------------------------------------------+
            //# | Attribute            | Value                                     |
            //# +----------------------+-------------------------------------------+
            //# | Address Block        | 2001:1::2/128                             |
            //# | Name                 | Traversal Using Relays around NAT Anycast |
            //# | RFC                  | RFC 8155                                  |
            //# | Allocation Date      | 2017-02                                   |
            //# | Termination Date     | N/A                                       |
            //# | Source               | True                                      |
            //# | Destination          | True                                      |
            //# | Forwardable          | True                                      |
            //# | Global               | True                                      |
            //# | Reserved-by-Protocol | False                                     |
            //# +----------------------+-------------------------------------------+
            [0x2001, 0x1, 0, 0, 0, 0, 0, 0x2] => Some(Global),

            //= https://www.rfc-editor.org/rfc/rfc6890#section-2.2.3
            //# +----------------------+---------------------------+
            //# | Attribute            | Value                     |
            //# +----------------------+---------------------------+
            //# | Address Block        | 2001::/23                 |
            //# | Name                 | IETF Protocol Assignments |
            //# | RFC                  | [RFC2928]                 |
            //# | Allocation Date      | September 2000            |
            //# | Termination Date     | N/A                       |
            //# | Source               | False[1]                  |
            //# | Destination          | False[1]                  |
            //# | Forwardable          | False[1]                  |
            //# | Global               | False[1]                  |
            //# | Reserved-by-Protocol | False                     |
            //# +----------------------+---------------------------+
            [0x2001, 0x0..=0x01ff, ..] => None,

            //= https://www.rfc-editor.org/rfc/rfc5180#section-8
            //# The IANA has allocated 2001:0200::/48 for IPv6 benchmarking, which is
            //# a 48-bit prefix from the RFC 4773 pool.
            [0x2001, 0x0200, 0, ..] => None,

            //= https://www.rfc-editor.org/rfc/rfc3849#section-4
            //# IANA is to record the allocation of the IPv6 global unicast address
            //# prefix  2001:DB8::/32 as a documentation-only prefix  in the IPv6
            //# address registry.
            [0x2001, 0xdb8, ..] => None,

            //= https://www.rfc-editor.org/rfc/rfc4193#section-8
            //# The IANA has assigned the FC00::/7 prefix to "Unique Local Unicast".
            [0xfc00..=0xfdff, ..] => {
                //= https://www.rfc-editor.org/rfc/rfc4193#section-1
                //# They are not
                //# expected to be routable on the global Internet.  They are routable
                //# inside of a more limited area such as a site.  They may also be
                //# routed between a limited set of sites.
                Some(Private)
            }

            //= https://www.rfc-editor.org/rfc/rfc4291#section-2.5.6
            //# Link-Local addresses have the following format:
            //# |   10     |
            //# |  bits    |         54 bits         |          64 bits           |
            //# +----------+-------------------------+----------------------------+
            //# |1111111010|           0             |       interface ID         |
            //# +----------+-------------------------+----------------------------+
            [0xfe80..=0xfebf, ..] => Some(LinkLocal),

            //= https://www.rfc-editor.org/rfc/rfc4291#section-2.7
            //# binary 11111111 at the start of the address identifies the address
            //# as being a multicast address.
            [0xff00..=0xffff, ..] => None,

            // Everything else is considered globally-reachable
            _ => Some(Global),
        }
    }

    #[inline]
    pub fn with_port(self, port: u16) -> SocketAddressV6 {
        SocketAddressV6 {
            ip: self,
            port: port.into(),
        }
    }
}

impl fmt::Debug for IpV6Address {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        write!(fmt, "IPv6Address({self})")
    }
}

impl fmt::Display for IpV6Address {
    #[allow(clippy::many_single_char_names)]
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        match self.segments() {
            [0, 0, 0, 0, 0, 0, 0, 0] => write!(fmt, "::"),
            [0, 0, 0, 0, 0, 0, 0, 1] => write!(fmt, "::1"),
            // Ipv4 Compatible address
            [0, 0, 0, 0, 0, 0, g, h] => write!(
                fmt,
                "::{}.{}.{}.{}",
                (g >> 8) as u8,
                g as u8,
                (h >> 8) as u8,
                h as u8
            ),
            // Ipv4-Mapped address
            [0, 0, 0, 0, 0, 0xffff, g, h] => write!(
                fmt,
                "::ffff:{}.{}.{}.{}",
                (g >> 8) as u8,
                g as u8,
                (h >> 8) as u8,
                h as u8
            ),
            // TODO better formatting
            [a, b, c, d, e, f, g, h] => {
                write!(fmt, "{a:x}:{b:x}:{c:x}:{d:x}:{e:x}:{f:x}:{g:x}:{h:x}")
            }
        }
    }
}

impl Unspecified for IpV6Address {
    #[inline]
    fn is_unspecified(&self) -> bool {
        Self::UNSPECIFIED.eq(self)
    }
}

test_inet_snapshot!(ipv6, ipv6_snapshot_test, IpV6Address);

define_inet_type!(
    pub struct SocketAddressV6 {
        ip: IpV6Address,
        port: U16,
    }
);

impl SocketAddressV6 {
    /// An unspecified SocketAddressV6
    pub const UNSPECIFIED: Self = Self {
        ip: IpV6Address::UNSPECIFIED,
        port: U16::ZERO,
    };

    #[inline]
    pub const fn ip(&self) -> &IpV6Address {
        &self.ip
    }

    #[inline]
    pub fn port(&self) -> u16 {
        self.port.into()
    }

    #[inline]
    pub fn set_port(&mut self, port: u16) {
        self.port.set(port)
    }

    /// Converts the IP address into IPv4 if it is mapped, otherwise the address is unchanged
    #[inline]
    pub fn unmap(self) -> SocketAddress {
        match self.ip.unmap() {
            IpAddress::Ipv4(addr) => SocketAddressV4::new(addr, self.port).into(),
            IpAddress::Ipv6(_) => self.into(),
        }
    }

    #[inline]
    pub const fn unicast_scope(&self) -> Option<ip::UnicastScope> {
        self.ip.unicast_scope()
    }
}

impl fmt::Debug for SocketAddressV6 {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        write!(fmt, "SocketAddressV6({self})")
    }
}

impl fmt::Display for SocketAddressV6 {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        write!(fmt, "[{}]:{:?}", self.ip, self.port)
    }
}

impl Unspecified for SocketAddressV6 {
    #[inline]
    fn is_unspecified(&self) -> bool {
        Self::UNSPECIFIED.eq(self)
    }
}

test_inet_snapshot!(socket_v6, socket_v6_snapshot_test, SocketAddressV6);

impl From<[u8; IPV6_LEN]> for IpV6Address {
    #[inline]
    fn from(octets: [u8; IPV6_LEN]) -> Self {
        Self { octets }
    }
}

impl From<[u16; IPV6_LEN / 2]> for IpV6Address {
    #[inline]
    fn from(octets: [u16; IPV6_LEN / 2]) -> Self {
        macro_rules! convert {
            ($($segment:ident),*) => {{
                let [$($segment),*] = octets;
                $(
                    let $segment = u16::to_be_bytes($segment);
                )*
                Self {
                    octets: [
                        $(
                            $segment[0],
                            $segment[1],
                        )*
                    ]
                }
            }}
        }
        convert!(a, b, c, d, e, f, g, h)
    }
}

impl From<IpV6Address> for [u8; IPV6_LEN] {
    #[inline]
    fn from(v: IpV6Address) -> Self {
        v.octets
    }
}

impl From<IpV6Address> for [u16; IPV6_LEN / 2] {
    #[inline]
    fn from(v: IpV6Address) -> Self {
        v.segments()
    }
}

//= https://www.rfc-editor.org/rfc/rfc8200#section-3
//#   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//#   |Version| Traffic Class |           Flow Label                  |
//#   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//#   |         Payload Length        |  Next Header  |   Hop Limit   |
//#   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//#   |                                                               |
//#   +                                                               +
//#   |                                                               |
//#   +                         Source Address                        +
//#   |                                                               |
//#   +                                                               +
//#   |                                                               |
//#   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//#   |                                                               |
//#   +                                                               +
//#   |                                                               |
//#   +                      Destination Address                      +
//#   |                                                               |
//#   +                                                               +
//#   |                                                               |
//#   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

define_inet_type!(
    pub struct Header {
        vtcfl: Vtcfl,
        payload_len: U16,
        next_header: ip::Protocol,
        hop_limit: u8,
        source: IpV6Address,
        destination: IpV6Address,
    }
);

impl fmt::Debug for Header {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.debug_struct("ipv6::Header")
            .field("version", &self.vtcfl.version())
            .field("dscp", &self.vtcfl.dscp())
            .field("ecn", &self.vtcfl.ecn())
            .field(
                "flow_label",
                &format_args!("0x{:05x}", self.vtcfl.flow_label()),
            )
            .field("payload_len", &self.payload_len)
            .field("next_header", &self.next_header)
            .field("hop_limit", &self.hop_limit)
            .field("source", &self.source)
            .field("destination", &self.destination)
            .finish()
    }
}

impl Header {
    /// Swaps the direction of the header
    #[inline]
    pub fn swap(&mut self) {
        core::mem::swap(&mut self.source, &mut self.destination)
    }

    #[inline]
    pub const fn vtcfl(&self) -> &Vtcfl {
        &self.vtcfl
    }

    #[inline]
    pub fn vtcfl_mut(&mut self) -> &mut Vtcfl {
        &mut self.vtcfl
    }

    #[inline]
    pub const fn payload_len(&self) -> &U16 {
        &self.payload_len
    }

    #[inline]
    pub fn payload_len_mut(&mut self) -> &mut U16 {
        &mut self.payload_len
    }

    #[inline]
    pub const fn next_header(&self) -> &ip::Protocol {
        &self.next_header
    }

    #[inline]
    pub fn next_header_mut(&mut self) -> &mut ip::Protocol {
        &mut self.next_header
    }

    #[inline]
    pub const fn hop_limit(&self) -> &u8 {
        &self.hop_limit
    }

    #[inline]
    pub fn hop_limit_mut(&mut self) -> &mut u8 {
        &mut self.hop_limit
    }

    #[inline]
    pub const fn source(&self) -> &IpV6Address {
        &self.source
    }

    #[inline]
    pub fn source_mut(&mut self) -> &mut IpV6Address {
        &mut self.source
    }

    #[inline]
    pub const fn destination(&self) -> &IpV6Address {
        &self.destination
    }

    #[inline]
    pub fn destination_mut(&mut self) -> &mut IpV6Address {
        &mut self.destination
    }
}

// This struct covers the bits for Version, Traffic Class, and Flow Label.
//
// Rust doesn't have the ability to do arbitrary bit sized values so we have to round up to the
// nearest byte.
define_inet_type!(
    pub struct Vtcfl {
        octets: [u8; 4],
    }
);

impl fmt::Debug for Vtcfl {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.debug_struct("ipv6::Vtf")
            .field("version", &self.version())
            .field("dscp", &self.dscp())
            .field("ecn", &self.ecn())
            .field("flow_label", &format_args!("0x{:05x}", self.flow_label()))
            .finish()
    }
}

impl Vtcfl {
    #[inline]
    pub const fn version(&self) -> u8 {
        self.octets[0] >> 4
    }

    #[inline]
    pub fn set_version(&mut self, version: u8) -> &mut Self {
        self.octets[0] = version << 4 | self.octets[0] & 0x0F;
        self
    }

    #[inline]
    pub fn dscp(&self) -> u8 {
        let value = self.octets[0] << 4 | self.octets[1] >> 4;
        value >> 2
    }

    #[inline]
    pub fn set_dscp(&mut self, value: u8) -> &mut Self {
        let value = value << 2;
        self.octets[0] = self.octets[0] & 0xF0 | (value >> 4);
        self.octets[1] = (value << 4) | self.octets[1] & 0b11_1111;
        self
    }

    #[inline]
    pub fn ecn(&self) -> ExplicitCongestionNotification {
        ExplicitCongestionNotification::new(self.octets[1] >> 4 & 0b11)
    }

    #[inline]
    pub fn set_ecn(&mut self, ecn: ExplicitCongestionNotification) -> &mut Self {
        self.octets[1] = (self.octets[1] & !(0b11 << 4)) | (ecn as u8) << 4;
        self
    }

    #[inline]
    pub const fn flow_label(&self) -> u32 {
        u32::from_be_bytes([0, self.octets[1] & 0x0F, self.octets[2], self.octets[3]])
    }

    #[inline]
    pub fn set_flow_label(&mut self, flow_label: u32) -> &mut Self {
        let bytes = flow_label.to_be_bytes();
        self.octets[1] = self.octets[1] & 0xF0 | bytes[1] & 0x0F;
        self.octets[2] = bytes[2];
        self.octets[3] = bytes[3];
        self
    }
}

#[cfg(any(test, feature = "std"))]
mod std_conversion {
    use super::*;
    use std::net;

    impl From<net::Ipv6Addr> for IpV6Address {
        fn from(address: net::Ipv6Addr) -> Self {
            (&address).into()
        }
    }

    impl From<&net::Ipv6Addr> for IpV6Address {
        fn from(address: &net::Ipv6Addr) -> Self {
            address.octets().into()
        }
    }

    impl From<IpV6Address> for net::Ipv6Addr {
        fn from(address: IpV6Address) -> Self {
            address.octets.into()
        }
    }

    impl From<net::SocketAddrV6> for SocketAddressV6 {
        fn from(address: net::SocketAddrV6) -> Self {
            let ip = address.ip().into();
            let port = address.port().into();
            Self { ip, port }
        }
    }

    impl From<(net::Ipv6Addr, u16)> for SocketAddressV6 {
        fn from((ip, port): (net::Ipv6Addr, u16)) -> Self {
            Self::new(ip, port)
        }
    }

    impl From<SocketAddressV6> for net::SocketAddrV6 {
        fn from(address: SocketAddressV6) -> Self {
            let ip = address.ip.into();
            let port = address.port.into();
            Self::new(ip, port, 0, 0)
        }
    }

    impl From<&SocketAddressV6> for net::SocketAddrV6 {
        fn from(address: &SocketAddressV6) -> Self {
            let ip = address.ip.into();
            let port = address.port.into();
            Self::new(ip, port, 0, 0)
        }
    }

    impl From<SocketAddressV6> for net::SocketAddr {
        fn from(address: SocketAddressV6) -> Self {
            let addr: net::SocketAddrV6 = address.into();
            addr.into()
        }
    }

    impl From<&SocketAddressV6> for net::SocketAddr {
        fn from(address: &SocketAddressV6) -> Self {
            let addr: net::SocketAddrV6 = address.into();
            addr.into()
        }
    }

    impl net::ToSocketAddrs for SocketAddressV6 {
        type Iter = std::iter::Once<net::SocketAddr>;

        fn to_socket_addrs(&self) -> std::io::Result<Self::Iter> {
            let ip = self.ip.into();
            let port = self.port.into();
            let addr = net::SocketAddrV6::new(ip, port, 0, 0);
            Ok(std::iter::once(addr.into()))
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use bolero::{check, generator::*};
    use s2n_codec::{DecoderBuffer, DecoderBufferMut};

    /// Asserts the UnicastScope returned matches a known implementation
    #[test]
    #[cfg_attr(kani, kani::proof, kani::unwind(17), kani::solver(kissat))]
    fn scope_test() {
        let g = gen::<[u8; 16]>().map_gen(IpV6Address::from);
        check!().with_generator(g).cloned().for_each(|subject| {
            use ip::UnicastScope::*;

            // the ipv4 scopes are tested elsewhere there so we just make sure the scopes match
            if let IpAddress::Ipv4(ipv4) = subject.unmap() {
                assert_eq!(ipv4.unicast_scope(), subject.unicast_scope());
                return;
            }

            let expected = std::net::Ipv6Addr::from(subject);
            let network = ip_network::Ipv6Network::from(expected);

            match subject.unicast_scope() {
                Some(Global) => {
                    // Site-local addresses are deprecated but the `ip_network` still partitions
                    // them out
                    // See: https://datatracker.ietf.org/doc/html/rfc3879

                    assert!(network.is_global() || network.is_unicast_site_local());
                }
                Some(Private) => {
                    assert!(network.is_unique_local());
                }
                Some(Loopback) => {
                    assert!(expected.is_loopback());
                }
                Some(LinkLocal) => {
                    assert!(network.is_unicast_link_local());
                }
                None => {
                    assert!(
                        expected.is_multicast()
                            || expected.is_unspecified()
                            // Discard space
                            || subject.segments()[0] == 0x0100
                            // IETF Reserved
                            || subject.segments()[0] == 0x2001
                    );
                }
            }
        })
    }

    #[test]
    #[cfg_attr(miri, ignore)]
    fn snapshot_test() {
        let mut buffer = vec![0u8; core::mem::size_of::<Header>()];
        for (idx, byte) in buffer.iter_mut().enumerate() {
            *byte = idx as u8;
        }
        let decoder = DecoderBuffer::new(&buffer);
        let (header, _) = decoder.decode::<&Header>().unwrap();
        insta::assert_debug_snapshot!("snapshot_test", header);

        for byte in &mut buffer {
            *byte = 255;
        }
        let decoder = DecoderBuffer::new(&buffer);
        let (header, _) = decoder.decode::<&Header>().unwrap();
        insta::assert_debug_snapshot!("snapshot_filled_test", header);
    }

    #[test]
    #[cfg_attr(kani, kani::proof, kani::unwind(17), kani::solver(kissat))]
    fn header_getter_setter_test() {
        check!().with_type::<Header>().for_each(|expected| {
            let mut buffer = [255u8; core::mem::size_of::<Header>()];
            let decoder = DecoderBufferMut::new(&mut buffer);
            let (header, _) = decoder.decode::<&mut Header>().unwrap();
            {
                // use all of the getters and setters to copy over each field
                header
                    .vtcfl_mut()
                    .set_version(expected.vtcfl().version())
                    .set_dscp(expected.vtcfl().dscp())
                    .set_ecn(expected.vtcfl().ecn())
                    .set_flow_label(expected.vtcfl().flow_label());
                *header.hop_limit_mut() = *expected.hop_limit();
                *header.next_header_mut() = *expected.next_header();
                header.payload_len_mut().set(expected.payload_len().get());
                *header.source_mut() = *expected.source();
                *header.destination_mut() = *expected.destination();
            }

            let decoder = DecoderBuffer::new(&buffer);
            let (actual, _) = decoder.decode::<&Header>().unwrap();
            {
                // make sure all of the values match
                assert_eq!(expected.vtcfl().version(), expected.vtcfl().version());
                assert_eq!(expected.vtcfl().dscp(), expected.vtcfl().dscp());
                assert_eq!(expected.vtcfl().ecn(), expected.vtcfl().ecn());
                assert_eq!(expected.vtcfl().flow_label(), expected.vtcfl().flow_label());
                assert_eq!(
                    expected.vtcfl(),
                    actual.vtcfl(),
                    "\nexpected: {:?}\n  actual: {:?}",
                    expected.as_bytes(),
                    actual.as_bytes()
                );
                assert_eq!(expected.hop_limit(), actual.hop_limit());
                assert_eq!(expected.next_header(), actual.next_header());
                assert_eq!(expected.payload_len(), actual.payload_len());
                assert_eq!(expected.source(), actual.source());
                assert_eq!(expected.destination(), actual.destination());
                assert_eq!(
                    expected,
                    actual,
                    "\nexpected: {:?}\n  actual: {:?}",
                    expected.as_bytes(),
                    actual.as_bytes()
                );
            }
        })
    }

    #[test]
    fn header_round_trip_test() {
        check!().for_each(|buffer| {
            s2n_codec::assert_codec_round_trip_bytes!(Header, buffer);
        });
    }
}