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

use crate::{
    aead::{self, Aead},
    aes::Encrypt,
    aesgcm::{generic::AesGcm, NONCE_LEN, TAG_LEN},
    arch::*,
    block::{Block, Zeroed as _},
    ctr::x86::Ctr,
    ghash::x86::{hkey, precomputed::Array, GHash},
    testing::MAX_BLOCKS,
};

macro_rules! impl_target_features {
    ($name:ident, $features:literal) => {
        impl $name {
            #[inline]
            #[target_feature(enable = $features)]
            unsafe fn encrypt(
                &self,
                nonce: &[u8; NONCE_LEN],
                aad: &[u8],
                input: &mut [u8],
                tag: &mut [u8; TAG_LEN],
            ) -> aead::Result {
                self.0.encrypt(nonce, aad, input, tag)
            }

            #[inline]
            #[target_feature(enable = $features)]
            unsafe fn decrypt(
                &self,
                nonce: &[u8; NONCE_LEN],
                aad: &[u8],
                input: &mut [u8],
                tag: &[u8; TAG_LEN],
            ) -> aead::Result {
                self.0.decrypt(nonce, aad, input, tag)
            }
        }

        impl aead::Aead for $name {
            type Nonce = [u8; NONCE_LEN];
            type Tag = [u8; TAG_LEN];

            fn encrypt(
                &self,
                nonce: &[u8; NONCE_LEN],
                aad: &[u8],
                input: &mut [u8],
                tag: &mut [u8; TAG_LEN],
            ) -> aead::Result {
                unsafe {
                    debug_assert!(Avx2::is_supported());
                    Self::encrypt(self, nonce, aad, input, tag)
                }
            }

            fn decrypt(
                &self,
                nonce: &[u8; NONCE_LEN],
                aad: &[u8],
                input: &mut [u8],
                tag: &[u8; TAG_LEN],
            ) -> Result<(), aead::Error> {
                unsafe {
                    debug_assert!(Avx2::is_supported());
                    Self::decrypt(self, nonce, aad, input, tag)
                }
            }
        }
    };
}

macro_rules! aesgcm_impl {
    ($name:ident, $arch_name:literal, $features:literal) => {
        mod $name {
            use super::*;
            const BATCH_SIZE: usize = 6;

            pub struct Std(AesGcm<Wrapper<EncryptionKey>, GHash, Ctr, BATCH_SIZE>);
            impl_target_features!(Std, $features);

            impl Std {
                #[inline]
                #[target_feature(enable = $features)]
                pub unsafe fn new(key: [u8; KEY_LEN]) -> Self {
                    let Key { encrypt, .. } = Key::new(key);
                    let key = Wrapper(encrypt);
                    let mut ghash_key = __m128i::zeroed();
                    key.encrypt(&mut ghash_key);
                    let ghash = GHash::new(ghash_key.into_array());
                    let key = AesGcm::new(key, ghash);
                    Self(key)
                }
            }

            pub struct PreH(
                AesGcm<Wrapper<EncryptionKey>, Array<hkey::H, MAX_BLOCKS>, Ctr, BATCH_SIZE>,
            );
            impl_target_features!(PreH, $features);

            impl PreH {
                #[inline]
                #[target_feature(enable = $features)]
                pub unsafe fn new(key: [u8; KEY_LEN]) -> Self {
                    type GHash = Array<hkey::H, MAX_BLOCKS>;

                    let Key { encrypt, .. } = Key::new(key);
                    let key = Wrapper(encrypt);
                    let mut ghash_key = __m128i::zeroed();
                    key.encrypt(&mut ghash_key);
                    let ghash = GHash::new(ghash_key.into_array());
                    let key = AesGcm::new(key, ghash);
                    Self(key)
                }
            }

            pub struct PreHr(
                AesGcm<Wrapper<EncryptionKey>, Array<hkey::Hr, MAX_BLOCKS>, Ctr, BATCH_SIZE>,
            );
            impl_target_features!(PreHr, $features);

            impl PreHr {
                #[inline]
                #[target_feature(enable = $features)]
                pub unsafe fn new(key: [u8; KEY_LEN]) -> Self {
                    type GHash = Array<hkey::Hr, MAX_BLOCKS>;

                    let Key { encrypt, .. } = Key::new(key);
                    let key = Wrapper(encrypt);
                    let mut ghash_key = __m128i::zeroed();
                    key.encrypt(&mut ghash_key);
                    let ghash = GHash::new(ghash_key.into_array());
                    let key = AesGcm::new(key, ghash);
                    Self(key)
                }
            }

            pub fn implementations(impls: &mut Vec<Implementation>) {
                impls.push(Implementation {
                    name: concat!("s2n_quic/std/", $arch_name),
                    new: |key| unsafe { Box::new(Std::new(key)) },
                });

                impls.push(Implementation {
                    name: concat!("s2n_quic/pre_h/", $arch_name),
                    new: |key| unsafe { Box::new(PreH::new(key)) },
                });

                impls.push(Implementation {
                    name: concat!("s2n_quic/pre_hr/", $arch_name),
                    new: |key| unsafe { Box::new(PreHr::new(key)) },
                });
            }
        }
    };
}

macro_rules! impl_aesgcm {
    ($name:ident) => {
        pub mod $name {
            use super::*;
            use crate::{
                aes::{
                    x86::$name::{EncryptionKey, Key},
                    $name::{Key as Wrapper, KEY_LEN},
                },
                aesgcm::testing::$name::Implementation,
            };

            aesgcm_impl!(avx2, "avx2", "aes,avx2,pclmulqdq");

            pub fn implementations(impls: &mut Vec<Implementation>) {
                Avx2::call_supported(|| {
                    avx2::implementations(impls);
                });
            }
        }
    };
}

impl_aesgcm!(aes128);
impl_aesgcm!(aes256);


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

use crate::arch::Arch;
use lazy_static::lazy_static;

lazy_static! {
    static ref IS_SUPPORTED: bool = is_x86_feature_detected!("aes")
        && is_x86_feature_detected!("avx2")
        && is_x86_feature_detected!("pclmulqdq");
}

pub struct Avx2;

impl Arch for Avx2 {
    #[inline(always)]
    fn is_supported() -> bool {
        *IS_SUPPORTED
    }

    #[target_feature(enable = "aes,avx2,pclmulqdq")]
    #[inline]
    unsafe fn call<F: FnOnce() -> R, R>(f: F) -> R {
        debug_assert!(Self::is_supported());
        f()
    }
}


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

use crate::{
    arch::*,
    block::{Batch, BatchMut, Block, Zeroed},
};
use core::mem::size_of;
use s2n_quic_core::assume;

pub const LEN: usize = size_of::<__m128i>();

impl Block for __m128i {
    #[inline(always)]
    fn from_array(block: [u8; LEN]) -> Self {
        unsafe {
            debug_assert!(Avx2::is_supported());
            _mm_loadu_si128(block.as_ptr() as *const _)
        }
    }

    #[inline(always)]
    fn into_array(self) -> [u8; LEN] {
        unsafe { core::mem::transmute(self) }
    }

    #[inline(always)]
    fn xor(self, x: Self) -> Self {
        unsafe {
            debug_assert!(Avx2::is_supported());
            _mm_xor_si128(self, x)
        }
    }

    #[inline(always)]
    fn ct_ensure_eq(self, b: Self) -> Result<(), ()> {
        // The generated code for this is:
        //
        // ```
        // vmovdqa xmm0, xmmword ptr [rsi]
        // vpxor   xmm0, xmm0, xmmword ptr [rdi]
        // vptest  xmm0, xmm0
        // setne   al
        // ret
        // ```
        //
        // See: https://godbolt.org/z/Geoq9G83b
        //
        // We should be able assume both `vpxor` and `vptest` are constant time.
        //
        // By preventing inlining, we can ensure the compiler doesn't perform a direct jump based on the
        // `CF` and `ZF` flags at the caller location, but instead reads from the return value.
        #[inline(never)]
        #[target_feature(enable = "avx2")]
        unsafe fn avx_ct_eq(a: __m128i, b: __m128i) -> Result<(), ()> {
            let c = a.xor(b);
            let c: [u64; 2] = core::mem::transmute(c);
            let res = c[0] | c[1];
            if res == 0 {
                Ok(())
            } else {
                Err(())
            }
        }

        unsafe {
            debug_assert!(Avx2::is_supported());
            avx_ct_eq(self, b)
        }
    }
}

impl Batch for __m128i {
    type Block = __m128i;

    #[inline(always)]
    fn for_each<F: FnMut(usize, &__m128i)>(&self, mut f: F) {
        f(0, self);
    }
}

impl BatchMut for __m128i {
    #[inline(always)]
    fn update<F: FnMut(usize, &mut __m128i)>(&mut self, mut f: F) {
        f(0, self);
    }
}

impl Zeroed for __m128i {
    #[inline(always)]
    fn zeroed() -> Self {
        unsafe { core::mem::transmute([0u8; 16]) }
    }
}

pub trait M128iExt {
    fn reverse(self) -> Self;
    fn from_slice(bytes: &[u8]) -> Self;
    fn into_slice(self, bytes: &mut [u8]);
    fn mask(self, len: usize) -> Self;
}

impl M128iExt for __m128i {
    #[inline(always)]
    fn reverse(self) -> Self {
        unsafe {
            debug_assert!(Avx2::is_supported());

            let mask: [u8; 16] = [15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0];
            let mask = Self::from_array(mask);
            _mm_shuffle_epi8(self, mask)
        }
    }

    #[inline(always)]
    fn from_slice(bytes: &[u8]) -> Self {
        unsafe {
            debug_assert!(Avx2::is_supported());

            let mut array = [0u8; LEN];
            copy_128(bytes.as_ptr(), array.as_mut_ptr(), bytes.len());
            Self::from_array(array)
        }
    }

    #[inline(always)]
    fn into_slice(self, bytes: &mut [u8]) {
        unsafe {
            debug_assert!(Avx2::is_supported());
            assume!(bytes.len() <= LEN);
            copy_128(
                &self as *const _ as *const u8,
                bytes.as_mut_ptr(),
                bytes.len(),
            );
        }
    }

    #[inline(always)]
    fn mask(self, len: usize) -> Self {
        unsafe {
            debug_assert!(Avx2::is_supported());
            assume!(0 < len && len < LEN);

            // compute a mask that can be shifted to only include a `len` of bytes
            const MASK: [u8; 31] = {
                let mut mask = [0u8; 31];
                let mut idx = 0;
                // only fill in the first `LEN` bytes
                while idx < LEN {
                    mask[idx] = 0xff;
                    idx += 1;
                }
                mask
            };

            let offset = MASK.get_unchecked(LEN - len);
            let mask = _mm_loadu_si128(offset as *const _ as *const _);

            _mm_and_si128(self, mask)
        }
    }
}

/// Copies up to 16 bytes from `from` into `to`
///
/// This exists to avoid having to call memcpy
#[inline(always)]
unsafe fn copy_128(mut from: *const u8, mut to: *mut u8, mut len: usize) {
    macro_rules! copy {
        ($($len:expr),*) => {
            $(
                if let Some(next) = len.checked_sub($len) {
                    len = next;
                    *(to as *mut [u8; $len]) = *(from as *const [u8; $len]);
                    from = from.add($len);
                    to = to.add($len);
                }
            )*
        }
    }

    copy!(128, 64, 32, 16, 8, 4, 2, 1);
    let _ = from;
    let _ = to;
    let _ = len;
}

#[test]
fn copy_128_test() {
    for i in 0..LEN {
        dbg!(i);

        let mut expected = [1u8; LEN];

        let mut source = [0u8; LEN];
        for (a, b) in source.iter_mut().zip(&mut expected).take(i) {
            *a = 2;
            *b = 2;
        }

        let mut dest = [1u8; LEN];
        unsafe {
            copy_128(source.as_ptr(), dest.as_mut_ptr(), i);
        }

        assert_eq!(dest, expected);
    }
}


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

use crate::{
    cipher_suite::{TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256},
    header_key::HeaderKey,
    hkdf, ring_aead as aead,
};
use core::fmt;
use s2n_quic_core::crypto::{self, CryptoError};

// ignore casing warnings in order to preserve the IANA name
#[allow(non_camel_case_types, clippy::all)]
pub enum NegotiatedCipherSuite {
    TLS_AES_256_GCM_SHA384(TLS_AES_256_GCM_SHA384),
    TLS_CHACHA20_POLY1305_SHA256(TLS_CHACHA20_POLY1305_SHA256),
    TLS_AES_128_GCM_SHA256(TLS_AES_128_GCM_SHA256),
}

/// Dispatch an operation to the negotiated cipher_suite
macro_rules! dispatch {
    ($self:ident, | $cipher_suite:ident | $expr:expr) => {
        match $self {
            Self::TLS_AES_256_GCM_SHA384($cipher_suite) => $expr,
            Self::TLS_CHACHA20_POLY1305_SHA256($cipher_suite) => $expr,
            Self::TLS_AES_128_GCM_SHA256($cipher_suite) => $expr,
        }
    };
}

impl From<TLS_AES_256_GCM_SHA384> for NegotiatedCipherSuite {
    fn from(cipher_suite: TLS_AES_256_GCM_SHA384) -> Self {
        Self::TLS_AES_256_GCM_SHA384(cipher_suite)
    }
}

impl From<TLS_CHACHA20_POLY1305_SHA256> for NegotiatedCipherSuite {
    fn from(cipher_suite: TLS_CHACHA20_POLY1305_SHA256) -> Self {
        Self::TLS_CHACHA20_POLY1305_SHA256(cipher_suite)
    }
}

impl From<TLS_AES_128_GCM_SHA256> for NegotiatedCipherSuite {
    fn from(cipher_suite: TLS_AES_128_GCM_SHA256) -> Self {
        Self::TLS_AES_128_GCM_SHA256(cipher_suite)
    }
}

impl NegotiatedCipherSuite {
    /// Create a cipher_suite with a given negotiated algorithm and secret
    pub fn new(algorithm: &aead::Algorithm, secret: hkdf::Prk) -> Option<(Self, HeaderKey)> {
        Some(match algorithm {
            _ if algorithm == &aead::AES_256_GCM => {
                let (cipher_suite, header_key) = TLS_AES_256_GCM_SHA384::new(secret);
                (cipher_suite.into(), header_key)
            }
            _ if algorithm == &aead::CHACHA20_POLY1305 => {
                let (cipher_suite, header_key) = TLS_CHACHA20_POLY1305_SHA256::new(secret);
                (cipher_suite.into(), header_key)
            }
            _ if algorithm == &aead::AES_128_GCM => {
                let (cipher_suite, header_key) = TLS_AES_128_GCM_SHA256::new(secret);
                (cipher_suite.into(), header_key)
            }
            _ => return None,
        })
    }

    /// Update the cipher_suite as defined in
    /// https://www.rfc-editor.org/rfc/rfc9001#section-6
    pub fn update(&self) -> Self {
        dispatch!(self, |cipher| cipher.update().into())
    }

    /// Updates the configured maximum transmission unit
    pub fn update_pmtu(&mut self, pmtu: u16) {
        dispatch!(self, |cipher| cipher.update_pmtu(pmtu))
    }
}

impl crypto::Key for NegotiatedCipherSuite {
    #[inline]
    fn decrypt(
        &self,
        packet_number: u64,
        header: &[u8],
        payload: &mut [u8],
    ) -> Result<(), CryptoError> {
        dispatch!(self, |cipher| cipher.decrypt(
            packet_number,
            header,
            payload
        ))
    }

    #[inline]
    fn encrypt(
        &self,
        packet_number: u64,
        header: &[u8],
        payload: &mut [u8],
    ) -> Result<(), CryptoError> {
        dispatch!(self, |cipher| cipher.encrypt(
            packet_number,
            header,
            payload
        ))
    }

    #[inline]
    fn tag_len(&self) -> usize {
        dispatch!(self, |cipher| cipher.tag_len())
    }

    #[inline]
    fn aead_confidentiality_limit(&self) -> u64 {
        dispatch!(self, |cipher| cipher.aead_confidentiality_limit())
    }

    #[inline]
    fn aead_integrity_limit(&self) -> u64 {
        dispatch!(self, |cipher| cipher.aead_integrity_limit())
    }

    #[inline]
    fn cipher_suite(&self) -> s2n_quic_core::crypto::tls::CipherSuite {
        dispatch!(self, |cipher| cipher.cipher_suite())
    }
}

impl fmt::Debug for NegotiatedCipherSuite {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        dispatch!(self, |cipher| cipher.fmt(f))
    }
}


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

macro_rules! key {
    ($name:ident, $ring_cipher:path, $key_size:expr, $tag_len:expr) => {
        pub mod $name {
            use super::super::$name::{KEY_LEN, NONCE_LEN, TAG_LEN};
            use crate::ring_aead::{self as aead, LessSafeKey, UnboundKey};
            use zeroize::Zeroize;

            pub struct Key {
                key: LessSafeKey,
            }

            impl Key {
                #[inline]
                pub fn new(secret: &[u8; KEY_LEN]) -> Self {
                    let unbound_key =
                        UnboundKey::new(&$ring_cipher, secret).expect("key size verified");
                    let key = LessSafeKey::new(unbound_key);
                    Self { key }
                }

                #[inline]
                #[allow(dead_code)] // this is to maintain compatibility between implementations
                pub fn should_update_pmtu(&self, _mtu: u16) -> bool {
                    // ring doesn't implement precomputed tables
                    false
                }

                #[inline]
                #[allow(dead_code)] // this is to maintain compatibility between implementations
                pub fn update(&self, secret: &[u8; KEY_LEN]) -> Self {
                    // no state to persist
                    Self::new(secret)
                }

                #[inline]
                #[allow(dead_code)] // this is to maintain compatibility between implementations
                pub fn update_pmtu(&mut self, _secret: &[u8; KEY_LEN], _mtu: u16) {
                    unimplemented!();
                }
            }

            impl Zeroize for Key {
                fn zeroize(&mut self) {
                    // ring doesn't provide a way to zeroize keys currently
                    // https://github.com/briansmith/ring/issues/15
                }
            }

            impl crate::aead::Aead for Key {
                type Nonce = [u8; NONCE_LEN];
                type Tag = [u8; TAG_LEN];

                #[inline]
                fn encrypt(
                    &self,
                    nonce: &[u8; NONCE_LEN],
                    aad: &[u8],
                    input: &mut [u8],
                    tag: &mut [u8; TAG_LEN],
                ) -> crate::aead::Result {
                    self.key.encrypt(nonce, aad, input, tag)
                }

                #[inline]
                fn decrypt(
                    &self,
                    nonce: &[u8; NONCE_LEN],
                    aad: &[u8],
                    input: &mut [u8],
                    tag: &[u8; TAG_LEN],
                ) -> crate::aead::Result {
                    self.key.decrypt(nonce, aad, input, tag)
                }
            }
        }
    };
}

key!(aes128_gcm, aead::AES_128_GCM, 128 / 8, 16);
key!(aes256_gcm, aead::AES_256_GCM, 256 / 8, 16);
key!(chacha20_poly1305, aead::CHACHA20_POLY1305, 256 / 8, 16);


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

use super::ring;

macro_rules! aesgcm {
    ($name:ident, $cipher:ident) => {
        pub mod $name {
            use super::{
                super::$name::{KEY_LEN, NONCE_LEN, TAG_LEN},
                ring,
            };
            use crate::{
                aead,
                aes::{
                    x86::$cipher::{EncryptionKey, Key as AesKey},
                    $cipher::Key as Wrapper,
                    Encrypt,
                },
                aesgcm::generic::AesGcm,
                arch::*,
                block::{Block, Zeroed as _, LEN as BLOCK_LEN},
                ctr::x86::Ctr,
                ghash::x86::{hkey, precomputed},
            };
            use zeroize::{Zeroize, ZeroizeOnDrop};

            // Even though the ring variant is quite large, it's not worth allocating since we will
            // likely allocate the precomputed table after a PMTU update.
            #[allow(clippy::large_enum_variant)]
            #[derive(Zeroize)]
            pub enum Key {
                Precomputed(PrecomputedKey),
                Ring(ring::$name::Key),
            }

            impl Key {
                #[inline]
                pub fn new(secret: &[u8; KEY_LEN]) -> Self {
                    // default to the ring implementation until the PMTU changes
                    let key = ring::$name::Key::new(secret);
                    Self::Ring(key)
                }

                pub fn should_update_pmtu(&self, mtu: u16) -> bool {
                    match self {
                        // if the precomputed key isn't supported, do nothing
                        _ if !Avx2::is_supported() => false,
                        // if we've already precomputed a larger key, do nothing
                        Self::Precomputed(key) if key.mtu >= mtu => false,
                        _ => true,
                    }
                }

                pub fn update(&self, secret: &[u8; KEY_LEN]) -> Self {
                    match self {
                        Self::Ring(_) => Self::new(secret),
                        Self::Precomputed(key) => Self::Precomputed(unsafe {
                            debug_assert!(Avx2::is_supported());
                            PrecomputedKey::new(secret, key.mtu)
                        }),
                    }
                }

                pub fn update_pmtu(&mut self, secret: &[u8; KEY_LEN], mtu: u16) {
                    debug_assert!(self.should_update_pmtu(mtu));

                    *self = Self::Precomputed(unsafe {
                        debug_assert!(Avx2::is_supported());
                        PrecomputedKey::new(secret, mtu)
                    })
                }
            }

            impl aead::Aead for Key {
                type Nonce = [u8; NONCE_LEN];
                type Tag = [u8; TAG_LEN];

                #[inline]
                fn encrypt(
                    &self,
                    nonce: &[u8; NONCE_LEN],
                    aad: &[u8],
                    input: &mut [u8],
                    tag: &mut [u8; TAG_LEN],
                ) -> aead::Result {
                    match self {
                        Self::Precomputed(key) => key.encrypt(nonce, aad, input, tag),
                        Self::Ring(key) => key.encrypt(nonce, aad, input, tag),
                    }
                }

                #[inline]
                fn decrypt(
                    &self,
                    nonce: &[u8; NONCE_LEN],
                    aad: &[u8],
                    input: &mut [u8],
                    tag: &[u8; TAG_LEN],
                ) -> aead::Result {
                    match self {
                        Self::Precomputed(key) => key.decrypt(nonce, aad, input, tag),
                        Self::Ring(key) => key.decrypt(nonce, aad, input, tag),
                    }
                }
            }

            type PrecomputedGHash = precomputed::Allocated<hkey::H>;
            type PrecomputedAesGcmKey = AesGcm<Wrapper<EncryptionKey>, PrecomputedGHash, Ctr, 6>;

            #[derive(Zeroize, ZeroizeOnDrop)]
            pub struct PrecomputedKey {
                key: PrecomputedAesGcmKey,
                mtu: u16,
            }

            impl PrecomputedKey {
                #[inline]
                #[target_feature(enable = "aes,avx2,pclmulqdq")]
                unsafe fn new(secret: &[u8; KEY_LEN], mtu: u16) -> Self {
                    debug_assert!(Avx2::is_supported());
                    let AesKey { encrypt, .. } = AesKey::new(*secret);
                    let key = Wrapper(encrypt);
                    let mut ghash_key = __m128i::zeroed();
                    key.encrypt(&mut ghash_key);
                    // round up to the next block size
                    let blocks = (mtu as usize + BLOCK_LEN - 1) / BLOCK_LEN;
                    let ghash = PrecomputedGHash::new(ghash_key.into_array(), blocks);
                    let key = AesGcm::new(key, ghash);
                    Self { key, mtu }
                }

                #[inline]
                #[target_feature(enable = "aes,avx2,pclmulqdq")]
                unsafe fn encrypt_impl(
                    &self,
                    nonce: &[u8; NONCE_LEN],
                    aad: &[u8],
                    input: &mut [u8],
                    tag: &mut [u8; TAG_LEN],
                ) -> aead::Result {
                    aead::Aead::encrypt(&self.key, nonce, aad, input, tag)
                }

                #[inline]
                #[target_feature(enable = "aes,avx2,pclmulqdq")]
                unsafe fn decrypt_impl(
                    &self,
                    nonce: &[u8; NONCE_LEN],
                    aad: &[u8],
                    input: &mut [u8],
                    tag: &[u8; TAG_LEN],
                ) -> aead::Result {
                    aead::Aead::decrypt(&self.key, nonce, aad, input, tag)
                }
            }

            impl aead::Aead for PrecomputedKey {
                type Nonce = [u8; NONCE_LEN];
                type Tag = [u8; TAG_LEN];

                #[inline]
                fn encrypt(
                    &self,
                    nonce: &[u8; NONCE_LEN],
                    aad: &[u8],
                    input: &mut [u8],
                    tag: &mut [u8; TAG_LEN],
                ) -> aead::Result {
                    unsafe {
                        debug_assert!(Avx2::is_supported());
                        self.encrypt_impl(nonce, aad, input, tag)
                    }
                }

                #[inline]
                fn decrypt(
                    &self,
                    nonce: &[u8; NONCE_LEN],
                    aad: &[u8],
                    input: &mut [u8],
                    tag: &[u8; TAG_LEN],
                ) -> aead::Result {
                    unsafe {
                        debug_assert!(Avx2::is_supported());
                        self.decrypt_impl(nonce, aad, input, tag)
                    }
                }
            }
        }
    };
}

aesgcm!(aes128_gcm, aes128);
aesgcm!(aes256_gcm, aes256);

// re-export chacha until it's implemented in this crate
pub use super::ring::chacha20_poly1305;


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

use crate::{
    aesgcm::NONCE_LEN,
    arch::*,
    block::{x86::M128iExt, Block},
    ctr,
};

#[derive(Clone, Copy, Debug)]
pub struct Ctr(__m128i);

impl ctr::Ctr for Ctr {
    type Block = __m128i;

    #[inline(always)]
    fn new(nonce: &[u8; NONCE_LEN]) -> Self {
        // https://github.com/awslabs/aws-lc/blob/aed75eb04d322d101941e1377f274484f5e4f5b8/crypto/fipsmodule/modes/gcm.c#L249
        //
        // OPENSSL_memcpy(ctx->Yi.c, iv, 12);
        // ctx->Yi.c[15] = 1;
        let mut ctr = [0u8; 16];
        ctr[..12].copy_from_slice(nonce);
        ctr[15] = 1;
        let ctr = __m128i::from_array(ctr).reverse();
        Self(ctr)
    }

    #[inline(always)]
    fn block(&self) -> __m128i {
        self.0.reverse()
    }

    #[inline(always)]
    fn increment(&mut self) {
        unsafe {
            debug_assert!(Avx2::is_supported());
            let one = _mm_set_epi64x(0, 1);
            self.0 = _mm_add_epi64(self.0, one);
        }
    }
}


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

use crate::ghash::TAG_LEN;
use lazy_static::lazy_static;

pub struct Implementation {
    pub(crate) name: &'static str,
    pub(crate) new: fn(key: [u8; 16]) -> Box<dyn GHash>,
}

impl Implementation {
    pub fn name(&self) -> &'static str {
        self.name
    }

    #[allow(clippy::new_ret_no_self)]
    pub fn new(&self, key: [u8; 16]) -> Box<dyn GHash> {
        (self.new)(key)
    }
}

pub trait GHash {
    fn hash(&self, input: &[u8]) -> [u8; TAG_LEN];
}

lazy_static! {
    static ref IMPLEMENTATIONS: Vec<Implementation> = {
        let impls = vec![];

        #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
        let impls = super::x86::testing::implementations(impls);

        #[cfg(test)]
        let impls = rust_crypto::implementations(impls);

        impls
    };
}

pub fn implementations() -> &'static [Implementation] {
    &IMPLEMENTATIONS
}

#[cfg(test)]
mod rust_crypto;


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

use crate::{
    ghash::{testing, KEY_LEN},
    testing::Outcome,
};
use bolero::check;
use core::convert::TryInto;

fn ensure_match(impls: &[testing::Implementation], key: [u8; 16], input: &[u8]) {
    // make sure we don't go beyond the max payload len
    let input_len = input.len().min(crate::testing::MAX_PAYLOAD);
    let input = &input[..input_len];

    let mut outcomes = vec![];

    for imp in impls {
        let key = imp.new(key);
        let name = imp.name();
        let tag = key.hash(input);
        let outcome = Outcome { name, output: tag };
        outcomes.push(outcome);
    }

    let first = &outcomes[0].output;

    let all_match = outcomes[1..].iter().all(|res| first == &res.output);

    assert!(all_match, "{:#?}", outcomes);
}

#[test]
fn differential_test() {
    let impls = testing::implementations();

    assert!(!impls.is_empty());

    check!().for_each(|bytes| {
        if bytes.len() < KEY_LEN {
            return;
        }

        let (key, input) = bytes.split_at(KEY_LEN);
        let key = key.try_into().unwrap();

        ensure_match(impls, key, input);
    })
}


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

use crate::{
    arch::*,
    block::{x86::M128iExt, Batch, Block, Zeroed},
    ghash::KEY_LEN,
};
use zeroize::Zeroize;

mod algo;
pub mod hkey;
pub mod precomputed;

#[cfg(any(test, feature = "testing"))]
pub mod testing;

#[derive(Zeroize)]
pub struct GHash(hkey::H);

impl GHash {
    #[allow(dead_code)] // this is currently used in testing only
    #[inline(always)]
    pub fn new(key: [u8; KEY_LEN]) -> Self {
        use hkey::HKey;
        Self(hkey::H::new(__m128i::from_array(key)))
    }
}

impl super::GHash for GHash {
    type Block = __m128i;
    type State = __m128i;

    #[inline(always)]
    fn start(&self, _required_blocks: usize) -> Self::State {
        __m128i::zeroed()
    }

    #[inline(always)]
    fn update<B: Batch<Block = Self::Block>>(&self, state: &mut Self::State, block: &B) {
        let mut y = *state;
        block.for_each(
            #[inline(always)]
            |_idx, b| {
                y = self.0.mul(b.reverse().xor(y));
            },
        );
        *state = y;
    }

    #[inline(always)]
    fn finish(&self, state: Self::State) -> Self::Block {
        let y = state;

        y.reverse()
    }
}


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

use crate::{
    block::LEN as BLOCK_LEN,
    ghash::testing::{GHash, Implementation},
};
use ghash::{
    universal_hash::{KeyInit, UniversalHash},
    GHash as Impl,
};

impl GHash for Impl {
    fn hash(&self, input: &[u8]) -> [u8; BLOCK_LEN] {
        let mut state = self.clone();
        for block in input.chunks_exact(BLOCK_LEN) {
            state.update_padded(block);
        }
        state.finalize().into()
    }
}

pub fn implementations(mut impls: Vec<Implementation>) -> Vec<Implementation> {
    impls.push(Implementation {
        name: "RustCrypto",
        new: |key| Box::new(Impl::new(&key.into())),
    });
    impls
}


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

use crate::{
    arch::*,
    block::{Block, Zeroed},
};

// https://github.com/awslabs/aws-lc/blob/5833176448d48aff0c2dc4c1ab745649c769a7a6/crypto/cipher_extra/asm/aes128gcmsiv-x86_64.pl#L58
// poly:
// .quad 0x1, 0xc200000000000000
const POLYNOMIAL: __m128i = unsafe { core::mem::transmute([0x1u64, 0xc200000000000000]) };

// From https://github.com/awslabs/aws-lc/blob/aed75eb04d322d101941e1377f274484f5e4f5b8/crypto/fipsmodule/modes/asm/ghash-x86_64.pl#L717
#[inline(always)]
// This implementation is written to closely follow the original code
#[allow(unknown_lints, clippy::needless_late_init)]
pub unsafe fn init(mut h: __m128i) -> __m128i {
    let mut t1;
    let t2;
    let mut t3;

    // # <<1 twist
    // vpshufd		\$0b11111111,$Hkey,$T2	# broadcast uppermost dword
    t2 = _mm_shuffle_epi32(h, 0b11111111);
    // vpsrlq		\$63,$Hkey,$T1
    t1 = _mm_srli_epi64(h, 63);
    // vpsllq		\$1,$Hkey,$Hkey
    h = _mm_slli_epi64(h, 1);
    // vpxor		$T3,$T3,$T3		#
    t3 = __m128i::zeroed();
    // vpcmpgtd	$T2,$T3,$T3		# broadcast carry bit
    t3 = _mm_cmpgt_epi32(t3, t2);
    // vpslldq		\$8,$T1,$T1
    t1 = _mm_slli_si128(t1, 8);
    // vpor		$T1,$Hkey,$Hkey		# H<<=1
    h = _mm_or_si128(h, t1);

    // # magic reduction
    // vpand		.L0x1c2_polynomial(%rip),$T3,$T3
    t3 = _mm_and_si128(t3, POLYNOMIAL);
    // vpxor		$T3,$Hkey,$Hkey		# if(carry) H^=0x1c2_polynomial
    h = h.xor(t3);

    h
}

// From https://github.com/awslabs/aws-lc/blob/5833176448d48aff0c2dc4c1ab745649c769a7a6/crypto/cipher_extra/asm/aes128gcmsiv-x86_64.pl#L93
#[inline(always)]
// This implementation is written to closely follow the original code
#[allow(unknown_lints, clippy::needless_late_init)]
pub unsafe fn gfmul(a: __m128i, b: __m128i) -> __m128i {
    // #########################
    // # a = T
    let t = a;
    // # b = TMP0 - remains unchanged
    let tmp0 = b;
    // # res = T
    // # uses also TMP1,TMP2,TMP3,TMP4
    // # __m128i GFMUL(__m128i A, __m128i B);

    // my $T = "%xmm0";
    // my $TMP0 = "%xmm1";
    // my $TMP1 = "%xmm2";
    let mut tmp1;
    // my $TMP2 = "%xmm3";
    let mut tmp2;
    // my $TMP3 = "%xmm4";
    let mut tmp3;
    // my $TMP4 = "%xmm5";
    let mut tmp4;

    // vpclmulqdq  \$0x00, $TMP0, $T, $TMP1
    tmp1 = _mm_clmulepi64_si128(t, tmp0, 0x00);
    // vpclmulqdq  \$0x11, $TMP0, $T, $TMP4
    tmp4 = _mm_clmulepi64_si128(t, tmp0, 0x11);
    // vpclmulqdq  \$0x10, $TMP0, $T, $TMP2
    tmp2 = _mm_clmulepi64_si128(t, tmp0, 0x10);
    // vpclmulqdq  \$0x01, $TMP0, $T, $TMP3
    tmp3 = _mm_clmulepi64_si128(t, tmp0, 0x01);
    // vpxor       $TMP3, $TMP2, $TMP2
    tmp2 = tmp2.xor(tmp3);
    // vpslldq     \$8, $TMP2, $TMP3
    tmp3 = _mm_slli_si128(tmp2, 8);
    // vpsrldq     \$8, $TMP2, $TMP2
    tmp2 = _mm_srli_si128(tmp2, 8);
    // vpxor       $TMP3, $TMP1, $TMP1
    tmp1 = tmp1.xor(tmp3);
    // vpxor       $TMP2, $TMP4, $TMP4
    tmp4 = tmp4.xor(tmp2);

    reduce(tmp1, tmp4)
}

/// Reduction phase of gfmul
// From https://github.com/awslabs/aws-lc/blob/5833176448d48aff0c2dc4c1ab745649c769a7a6/crypto/cipher_extra/asm/aes128gcmsiv-x86_64.pl#L93
#[inline(always)]
// This implementation is written to closely follow the original code
#[allow(unknown_lints, clippy::needless_late_init)]
pub unsafe fn reduce(mut tmp1: __m128i, tmp4: __m128i) -> __m128i {
    // my $T = "%xmm0";
    let t;
    // my $TMP0 = "%xmm1";
    // my $TMP1 = "%xmm2";
    // my $TMP2 = "%xmm3";
    let mut tmp2;
    // my $TMP3 = "%xmm4";
    let mut tmp3;
    // my $TMP4 = "%xmm5";

    // vpclmulqdq  \$0x10, poly(%rip), $TMP1, $TMP2
    tmp2 = _mm_clmulepi64_si128(tmp1, POLYNOMIAL, 0x10);
    // vpshufd     \$78, $TMP1, $TMP3
    tmp3 = _mm_shuffle_epi32(tmp1, 78);
    // vpxor       $TMP3, $TMP2, $TMP1
    tmp1 = tmp2.xor(tmp3);
    // vpclmulqdq  \$0x10, poly(%rip), $TMP1, $TMP2
    tmp2 = _mm_clmulepi64_si128(tmp1, POLYNOMIAL, 0x10);
    // vpshufd     \$78, $TMP1, $TMP3
    tmp3 = _mm_shuffle_epi32(tmp1, 78);
    // vpxor       $TMP3, $TMP2, $TMP1
    tmp1 = tmp2.xor(tmp3);
    // vpxor       $TMP4, $TMP1, $T
    t = tmp1.xor(tmp4);
    // ret
    t
}


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

use crate::{
    arch::*,
    block::{x86::M128iExt, Block, Zeroed},
    ghash::x86::algo,
};
use zeroize::DefaultIsZeroes;

pub trait HKey: Copy + Zeroed {
    fn new(h: __m128i) -> Self;
    fn derive(&self, initial: &Self) -> Self;
    fn h(&self) -> __m128i;
    fn r(&self) -> __m128i;
}

#[derive(Clone, Copy)]
pub struct H(__m128i);

impl H {
    #[inline(always)]
    pub fn mul(self, y: __m128i) -> __m128i {
        unsafe {
            debug_assert!(Avx2::is_supported());
            algo::gfmul(self.0, y)
        }
    }
}

impl Default for H {
    #[inline(always)]
    fn default() -> Self {
        Self::zeroed()
    }
}

impl DefaultIsZeroes for H {}

impl Zeroed for H {
    #[inline(always)]
    fn zeroed() -> Self {
        Self(__m128i::zeroed())
    }
}

impl HKey for H {
    #[inline(always)]
    fn new(mut h: __m128i) -> Self {
        unsafe {
            debug_assert!(Avx2::is_supported());

            h = h.reverse();
            h = algo::init(h);

            Self(h)
        }
    }

    #[inline(always)]
    fn derive(&self, first: &Self) -> Self {
        Self(self.mul(first.0))
    }

    #[inline(always)]
    fn h(&self) -> __m128i {
        self.0
    }

    #[inline(always)]
    fn r(&self) -> __m128i {
        unsafe {
            debug_assert!(Avx2::is_supported());

            let h = self.0;
            let r = _mm_shuffle_epi32(h, 78);
            r.xor(h)
        }
    }
}

#[derive(Clone, Copy)]
pub struct Hr {
    h: H,
    r: __m128i,
}

impl Zeroed for Hr {
    #[inline(always)]
    fn zeroed() -> Self {
        Self {
            h: H::zeroed(),
            r: __m128i::zeroed(),
        }
    }
}

impl Default for Hr {
    #[inline(always)]
    fn default() -> Self {
        Self::zeroed()
    }
}

impl DefaultIsZeroes for Hr {}

impl HKey for Hr {
    #[inline(always)]
    fn new(h: __m128i) -> Self {
        let h = H::new(h);
        let r = h.r();
        Self { h, r }
    }

    #[inline(always)]
    fn derive(&self, first: &Self) -> Self {
        let h = self.h.derive(&first.h);
        let r = h.r();
        Self { h, r }
    }

    #[inline(always)]
    fn h(&self) -> __m128i {
        self.h.h()
    }

    #[inline(always)]
    fn r(&self) -> __m128i {
        self.r
    }
}


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

use crate::{
    arch::*,
    block::{x86::M128iExt, Batch, Block, Zeroed},
    ghash::{
        self,
        x86::{algo, hkey::HKey},
        KEY_LEN,
    },
};
use s2n_quic_core::assume;
use zeroize::{DefaultIsZeroes, Zeroize};

impl<P: Powers> ghash::GHash for P {
    type Block = __m128i;
    type State = State;

    #[inline(always)]
    fn start(&self, required_blocks: usize) -> Self::State {
        debug_assert!(self.capacity() >= required_blocks);
        State::new(required_blocks)
    }

    #[inline(always)]
    fn update<B: Batch<Block = Self::Block>>(&self, state: &mut Self::State, block: &B) {
        block.for_each(
            #[inline(always)]
            |_idx, b| {
                *state = state.update(self, b);
            },
        );
    }

    #[inline(always)]
    fn finish(&self, state: Self::State) -> Self::Block {
        state.finish()
    }
}

pub trait Powers {
    type HKey: HKey;

    fn power(&self, index: usize) -> &Self::HKey;
    fn capacity(&self) -> usize;
}

pub struct Allocated<H: HKey> {
    state: Box<[H]>,
}

impl<H: HKey> Allocated<H> {
    #[inline(always)]
    pub fn new(key: [u8; KEY_LEN], blocks: usize) -> Self {
        // initialize the powers (H^1, H^2, H^3, etc)
        let mut state = Vec::with_capacity(blocks);
        let mut current = H::new(__m128i::from_array(key));
        let first = current;
        state.push(first);

        // precompute the H value for each block
        for _ in 0..blocks {
            current = current.derive(&first);
            state.push(current);
        }

        let state = state.into_boxed_slice();

        Self { state }
    }
}

impl<H: HKey> Powers for Allocated<H> {
    type HKey = H;

    #[inline(always)]
    fn power(&self, index: usize) -> &H {
        unsafe {
            assume!(index < self.state.len());
            self.state.get_unchecked(index)
        }
    }

    #[inline(always)]
    fn capacity(&self) -> usize {
        self.state.len()
    }
}

impl<H: HKey + DefaultIsZeroes> Zeroize for Allocated<H> {
    #[inline]
    fn zeroize(&mut self) {
        // deref to a slice to we can take advantage of the bulk zeroization
        self.state.zeroize()
    }
}

pub struct Array<H: HKey, const N: usize> {
    state: [H; N],
}

impl<H: HKey, const N: usize> Array<H, N> {
    #[allow(dead_code)] // This is currently used in testing only
    #[inline(always)]
    pub fn new(key: [u8; KEY_LEN]) -> Self {
        // initialize the powers (H^1, H^2, H^3, etc)
        let mut state = [H::zeroed(); N];
        let mut current = H::new(__m128i::from_array(key));
        let first = current;
        state[0] = first;

        // precompute the H value for each block
        for power in state.iter_mut().skip(1) {
            current = current.derive(&first);
            *power = current;
        }

        Self { state }
    }
}

impl<H: HKey, const N: usize> Powers for Array<H, N> {
    type HKey = H;

    #[inline(always)]
    fn power(&self, index: usize) -> &H {
        unsafe {
            assume!(index < self.state.len());
            self.state.get_unchecked(index)
        }
    }

    #[inline(always)]
    fn capacity(&self) -> usize {
        self.state.len()
    }
}

impl<H: HKey + DefaultIsZeroes, const N: usize> Zeroize for Array<H, N> {
    #[inline]
    fn zeroize(&mut self) {
        // deref to a slice to we can take advantage of the bulk zeroization
        self.state.zeroize()
    }
}

#[derive(Clone, Copy, Zeroize)]
pub struct State {
    hi: __m128i,
    mid: __m128i,
    lo: __m128i,
    power: usize,
}

impl State {
    #[inline(always)]
    fn new(power: usize) -> Self {
        Self {
            hi: __m128i::zeroed(),
            mid: __m128i::zeroed(),
            lo: __m128i::zeroed(),
            power,
        }
    }

    #[inline(always)]
    // This implementation is written to closely follow the original code
    #[allow(unknown_lints, clippy::needless_late_init)]
    fn update<P: Powers>(&self, powers: &P, b: &__m128i) -> Self {
        unsafe {
            debug_assert!(Avx2::is_supported());
            assume!(
                self.power != 0,
                "update called more than requested capacity"
            );

            let power = self.power - 1;
            let hkey = powers.power(power);

            let b = b.reverse();

            let mut t;
            let h = hkey.h();

            t = _mm_clmulepi64_si128(h, b, 0x00);
            let lo = self.lo.xor(t);

            t = _mm_clmulepi64_si128(h, b, 0x11);
            let hi = self.hi.xor(t);

            t = _mm_shuffle_epi32(b, 78);
            t = t.xor(b);
            t = _mm_clmulepi64_si128(hkey.r(), t, 0x00);
            let mid = self.mid.xor(t);

            Self { hi, mid, lo, power }
        }
    }

    #[inline(always)]
    fn finish(self) -> __m128i {
        let State {
            mut hi,
            mut mid,
            mut lo,
            power,
        } = self;

        unsafe {
            debug_assert!(Avx2::is_supported());
            assume!(
                power == 0,
                "ghash update count incorrect: remaining {}",
                power
            );

            mid = mid.xor(hi);
            mid = mid.xor(lo);
            lo = lo.xor(_mm_slli_si128(mid, 8));
            hi = hi.xor(_mm_srli_si128(mid, 8));

            let tag = algo::reduce(lo, hi);
            tag.reverse()
        }
    }
}


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

use crate::{
    aes::BLOCK_LEN,
    arch::*,
    block::Block,
    ghash::{
        self,
        testing::{GHash, Implementation},
        x86::{self, hkey, precomputed::Array},
    },
    testing::MAX_BLOCKS,
};
use core::{convert::TryInto, marker::PhantomData};
pub struct Impl<G, A>(G, PhantomData<A>)
where
    G: ghash::GHash<Block = __m128i>,
    A: Arch;

impl<G, A> Impl<G, A>
where
    G: ghash::GHash<Block = __m128i>,
    A: Arch,
{
    fn new(key: G) -> Self {
        Self(key, PhantomData)
    }
}

impl<G, A> GHash for Impl<G, A>
where
    G: ghash::GHash<Block = __m128i>,
    A: Arch,
{
    fn hash(&self, input: &[u8]) -> [u8; BLOCK_LEN] {
        unsafe {
            A::call(
                #[inline(always)]
                || {
                    let blocks = input.len() / BLOCK_LEN;

                    let mut state = self.0.start(blocks);

                    for block in input.chunks_exact(BLOCK_LEN) {
                        let block: [u8; BLOCK_LEN] = block.try_into().unwrap();
                        self.0.update(&mut state, &__m128i::from_array(block));
                    }
                    self.0.finish(state).into_array()
                },
            )
        }
    }
}

pub fn implementations(mut impls: Vec<Implementation>) -> Vec<Implementation> {
    Avx2::call_supported(|| {
        impls.push(Implementation {
            name: "s2n_quic/std/avx2",
            new: |key| {
                let ghash = x86::GHash::new(key);
                Box::new(<Impl<_, Avx2>>::new(ghash))
            },
        });
        impls.push(Implementation {
            name: "s2n_quic/pre_h/avx2",
            new: |key| {
                let ghash = <Array<hkey::H, MAX_BLOCKS>>::new(key);
                Box::new(<Impl<_, Avx2>>::new(ghash))
            },
        });
        impls.push(Implementation {
            name: "s2n_quic/pre_hr/avx2",
            new: |key| {
                let ghash = <Array<hkey::Hr, MAX_BLOCKS>>::new(key);
                Box::new(<Impl<_, Avx2>>::new(ghash))
            },
        });
    });
    impls
}


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

struct ConnectionMeta {
    #[builder(crate::endpoint::Type)]
    endpoint_type: EndpointType,

    id: u64,

    #[builder(crate::time::Timestamp)]
    timestamp: crate::event::Timestamp,
}

struct EndpointMeta {
    #[builder(crate::endpoint::Type)]
    endpoint_type: EndpointType,

    #[builder(crate::time::Timestamp)]
    timestamp: crate::event::Timestamp,
}

struct ConnectionInfo {}

// https://tools.ietf.org/id/draft-marx-qlog-event-definitions-quic-h3-02#5.3.3
struct TransportParameters<'a> {
    original_destination_connection_id: Option<ConnectionId<'a>>,
    initial_source_connection_id: Option<ConnectionId<'a>>,
    retry_source_connection_id: Option<ConnectionId<'a>>,
    stateless_reset_token: Option<&'a [u8]>,
    preferred_address: Option<PreferredAddress<'a>>,
    migration_support: bool,
    max_idle_timeout: Duration,
    ack_delay_exponent: u8,
    max_ack_delay: Duration,
    max_udp_payload_size: u64,
    active_connection_id_limit: u64,
    initial_max_stream_data_bidi_local: u64,
    initial_max_stream_data_bidi_remote: u64,
    initial_max_stream_data_uni: u64,
    initial_max_streams_bidi: u64,
    initial_max_streams_uni: u64,
    max_datagram_frame_size: u64,
}

struct PreferredAddress<'a> {
    ipv4_address: Option<SocketAddress<'a>>,
    ipv6_address: Option<SocketAddress<'a>>,
    connection_id: ConnectionId<'a>,
    stateless_reset_token: &'a [u8],
}

impl<'a> IntoEvent<builder::PreferredAddress<'a>>
    for &'a crate::transport::parameters::PreferredAddress
{
    #[inline]
    fn into_event(self) -> builder::PreferredAddress<'a> {
        builder::PreferredAddress {
            ipv4_address: self.ipv4_address.as_ref().map(|addr| addr.into_event()),
            ipv6_address: self.ipv6_address.as_ref().map(|addr| addr.into_event()),
            connection_id: self.connection_id.into_event(),
            stateless_reset_token: self.stateless_reset_token.as_ref(),
        }
    }
}

impl<'a> IntoEvent<builder::SocketAddress<'a>> for &'a crate::inet::ipv4::SocketAddressV4 {
    #[inline]
    fn into_event(self) -> builder::SocketAddress<'a> {
        builder::SocketAddress::IpV4 {
            ip: &self.ip.octets,
            port: self.port.into(),
        }
    }
}

impl<'a> IntoEvent<builder::SocketAddress<'a>> for &'a crate::inet::ipv6::SocketAddressV6 {
    #[inline]
    fn into_event(self) -> builder::SocketAddress<'a> {
        builder::SocketAddress::IpV6 {
            ip: &self.ip.octets,
            port: self.port.into(),
        }
    }
}

impl IntoEvent<bool> for &crate::transport::parameters::MigrationSupport {
    #[inline]
    fn into_event(self) -> bool {
        match self {
            crate::transport::parameters::MigrationSupport::Enabled => true,
            crate::transport::parameters::MigrationSupport::Disabled => false,
        }
    }
}

#[builder_derive(derive(Copy))]
struct Path<'a> {
    local_addr: SocketAddress<'a>,
    local_cid: ConnectionId<'a>,
    remote_addr: SocketAddress<'a>,
    remote_cid: ConnectionId<'a>,
    id: u64,
    is_active: bool,
}

#[derive(Clone)]
#[builder_derive(derive(Copy))]
struct ConnectionId<'a> {
    bytes: &'a [u8],
}

impl<'a> core::fmt::Debug for ConnectionId<'a> {
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
        write!(f, "0x")?;
        for byte in self.bytes {
            write!(f, "{byte:02x}")?;
        }
        Ok(())
    }
}

macro_rules! impl_conn_id {
    ($name:ident) => {
        impl<'a> IntoEvent<builder::ConnectionId<'a>> for &'a crate::connection::id::$name {
            #[inline]
            fn into_event(self) -> builder::ConnectionId<'a> {
                builder::ConnectionId {
                    bytes: self.as_bytes(),
                }
            }
        }
    };
}

impl_conn_id!(LocalId);
impl_conn_id!(PeerId);
impl_conn_id!(UnboundedId);
impl_conn_id!(InitialId);

#[derive(Clone)]
#[builder_derive(derive(Copy))]
enum SocketAddress<'a> {
    IpV4 { ip: &'a [u8; 4], port: u16 },
    IpV6 { ip: &'a [u8; 16], port: u16 },
}

impl<'a> core::fmt::Debug for SocketAddress<'a> {
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
        match self {
            Self::IpV4 { ip, port } => {
                let addr = crate::inet::SocketAddressV4::new(**ip, *port);
                write!(f, "{addr}")?;
            }
            Self::IpV6 { ip, port } => {
                let addr = crate::inet::SocketAddressV6::new(**ip, *port);
                write!(f, "{addr}")?;
            }
        }
        Ok(())
    }
}

impl<'a> core::fmt::Display for SocketAddress<'a> {
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
        match self {
            Self::IpV4 { ip, port } => {
                let addr = crate::inet::SocketAddressV4::new(**ip, *port);
                addr.fmt(f)?;
            }
            Self::IpV6 { ip, port } => {
                let addr = crate::inet::SocketAddressV6::new(**ip, *port);
                addr.fmt(f)?;
            }
        }
        Ok(())
    }
}

impl<'a> SocketAddress<'a> {
    #[inline]
    pub fn ip(&self) -> &'a [u8] {
        match self {
            Self::IpV4 { ip, .. } => &ip[..],
            Self::IpV6 { ip, .. } => &ip[..],
        }
    }

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

impl<'a> IntoEvent<api::SocketAddress<'a>> for &'a crate::inet::SocketAddress {
    #[inline]
    fn into_event(self) -> api::SocketAddress<'a> {
        match self {
            crate::inet::SocketAddress::IpV4(addr) => api::SocketAddress::IpV4 {
                ip: &addr.ip.octets,
                port: addr.port.into(),
            },
            crate::inet::SocketAddress::IpV6(addr) => api::SocketAddress::IpV6 {
                ip: &addr.ip.octets,
                port: addr.port.into(),
            },
        }
    }
}

impl<'a> IntoEvent<builder::SocketAddress<'a>> for &'a crate::inet::SocketAddress {
    #[inline]
    fn into_event(self) -> builder::SocketAddress<'a> {
        match self {
            crate::inet::SocketAddress::IpV4(addr) => addr.into_event(),
            crate::inet::SocketAddress::IpV6(addr) => addr.into_event(),
        }
    }
}

#[cfg(feature = "std")]
impl From<SocketAddress<'_>> for std::net::SocketAddr {
    #[inline]
    fn from(address: SocketAddress) -> Self {
        use std::net;
        match address {
            SocketAddress::IpV4 { ip, port } => {
                let ip = net::IpAddr::V4(net::Ipv4Addr::from(*ip));
                Self::new(ip, port)
            }
            SocketAddress::IpV6 { ip, port } => {
                let ip = net::IpAddr::V6(net::Ipv6Addr::from(*ip));
                Self::new(ip, port)
            }
        }
    }
}

#[cfg(feature = "std")]
impl From<&SocketAddress<'_>> for std::net::SocketAddr {
    #[inline]
    fn from(address: &SocketAddress) -> Self {
        use std::net;
        match address {
            SocketAddress::IpV4 { ip, port } => {
                let ip = net::IpAddr::V4(net::Ipv4Addr::from(**ip));
                Self::new(ip, *port)
            }
            SocketAddress::IpV6 { ip, port } => {
                let ip = net::IpAddr::V6(net::Ipv6Addr::from(**ip));
                Self::new(ip, *port)
            }
        }
    }
}

enum DuplicatePacketError {
    /// The packet number was already received and is a duplicate.
    Duplicate,

    /// The received packet number was outside the range of tracked packet numbers.
    ///
    /// This can happen when packets are heavily delayed or reordered. Currently, the maximum
    /// amount of reordering is limited to 128 packets. For example, if packet number `142`
    /// is received, the allowed range would be limited to `14-142`. If an endpoint received
    /// packet `< 14`, it would trigger this event.
    TooOld,
}

impl IntoEvent<builder::DuplicatePacketError> for crate::packet::number::SlidingWindowError {
    #[inline]
    fn into_event(self) -> builder::DuplicatePacketError {
        use crate::packet::number::SlidingWindowError;
        match self {
            SlidingWindowError::TooOld => builder::DuplicatePacketError::TooOld {},
            SlidingWindowError::Duplicate => builder::DuplicatePacketError::Duplicate {},
        }
    }
}

struct EcnCounts {
    /// A variable-length integer representing the total number of packets
    /// received with the ECT(0) codepoint.
    ect_0_count: u64,

    /// A variable-length integer representing the total number of packets
    /// received with the ECT(1) codepoint.
    ect_1_count: u64,

    /// A variable-length integer representing the total number of packets
    /// received with the CE codepoint.
    ce_count: u64,
}

impl IntoEvent<builder::EcnCounts> for crate::frame::ack::EcnCounts {
    #[inline]
    fn into_event(self) -> builder::EcnCounts {
        builder::EcnCounts {
            ect_0_count: self.ect_0_count.into_event(),
            ect_1_count: self.ect_1_count.into_event(),
            ce_count: self.ce_count.into_event(),
        }
    }
}

//= https://tools.ietf.org/id/draft-marx-qlog-event-definitions-quic-h3-02#A.7
enum Frame {
    Padding,
    Ping,
    Ack {
        ecn_counts: Option<EcnCounts>,
        largest_acknowledged: u64,
        ack_range_count: u64,
    },
    ResetStream {
        id: u64,
        error_code: u64,
        final_size: u64,
    },
    StopSending {
        id: u64,
        error_code: u64,
    },
    Crypto {
        offset: u64,
        len: u16,
    },
    NewToken,
    Stream {
        id: u64,
        offset: u64,
        len: u16,
        is_fin: bool,
    },
    MaxData {
        value: u64,
    },
    MaxStreamData {
        stream_type: StreamType,
        id: u64,
        value: u64,
    },
    MaxStreams {
        stream_type: StreamType,
        value: u64,
    },
    DataBlocked {
        data_limit: u64,
    },
    StreamDataBlocked {
        stream_id: u64,
        stream_data_limit: u64,
    },
    StreamsBlocked {
        stream_type: StreamType,
        stream_limit: u64,
    },
    NewConnectionId,
    RetireConnectionId,
    PathChallenge,
    PathResponse,
    ConnectionClose,
    HandshakeDone,
    Datagram {
        len: u16,
    },
}

impl IntoEvent<builder::Frame> for &crate::frame::Padding {
    #[inline]
    fn into_event(self) -> builder::Frame {
        builder::Frame::Padding {}
    }
}

impl IntoEvent<builder::Frame> for &crate::frame::Ping {
    #[inline]
    fn into_event(self) -> builder::Frame {
        builder::Frame::Ping {}
    }
}

impl<AckRanges: crate::frame::ack::AckRanges> IntoEvent<builder::Frame>
    for &crate::frame::Ack<AckRanges>
{
    #[inline]
    fn into_event(self) -> builder::Frame {
        builder::Frame::Ack {
            ecn_counts: self.ecn_counts.map(|val| val.into_event()),
            largest_acknowledged: self.largest_acknowledged().into_event(),
            ack_range_count: self.ack_ranges().len() as u64,
        }
    }
}

impl IntoEvent<builder::Frame> for &crate::frame::ResetStream {
    #[inline]
    fn into_event(self) -> builder::Frame {
        builder::Frame::ResetStream {
            id: self.stream_id.as_u64(),
            error_code: self.application_error_code.as_u64(),
            final_size: self.final_size.as_u64(),
        }
    }
}

impl IntoEvent<builder::Frame> for &crate::frame::StopSending {
    #[inline]
    fn into_event(self) -> builder::Frame {
        builder::Frame::StopSending {
            id: self.stream_id.as_u64(),
            error_code: self.application_error_code.as_u64(),
        }
    }
}

impl<'a> IntoEvent<builder::Frame> for &crate::frame::NewToken<'a> {
    #[inline]
    fn into_event(self) -> builder::Frame {
        builder::Frame::NewToken {}
    }
}

impl IntoEvent<builder::Frame> for &crate::frame::MaxData {
    #[inline]
    fn into_event(self) -> builder::Frame {
        builder::Frame::MaxData {
            value: self.maximum_data.as_u64(),
        }
    }
}

impl IntoEvent<builder::Frame> for &crate::frame::MaxStreamData {
    #[inline]
    fn into_event(self) -> builder::Frame {
        builder::Frame::MaxStreamData {
            id: self.stream_id.as_u64(),
            stream_type: crate::stream::StreamId::from_varint(self.stream_id)
                .stream_type()
                .into_event(),
            value: self.maximum_stream_data.as_u64(),
        }
    }
}

impl IntoEvent<builder::Frame> for &crate::frame::MaxStreams {
    #[inline]
    fn into_event(self) -> builder::Frame {
        builder::Frame::MaxStreams {
            stream_type: self.stream_type.into_event(),
            value: self.maximum_streams.as_u64(),
        }
    }
}

impl IntoEvent<builder::Frame> for &crate::frame::DataBlocked {
    #[inline]
    fn into_event(self) -> builder::Frame {
        builder::Frame::DataBlocked {
            data_limit: self.data_limit.as_u64(),
        }
    }
}

impl IntoEvent<builder::Frame> for &crate::frame::StreamDataBlocked {
    #[inline]
    fn into_event(self) -> builder::Frame {
        builder::Frame::StreamDataBlocked {
            stream_id: self.stream_id.as_u64(),
            stream_data_limit: self.stream_data_limit.as_u64(),
        }
    }
}

impl IntoEvent<builder::Frame> for &crate::frame::StreamsBlocked {
    #[inline]
    fn into_event(self) -> builder::Frame {
        builder::Frame::StreamsBlocked {
            stream_type: self.stream_type.into_event(),
            stream_limit: self.stream_limit.as_u64(),
        }
    }
}

impl<'a> IntoEvent<builder::Frame> for &crate::frame::NewConnectionId<'a> {
    #[inline]
    fn into_event(self) -> builder::Frame {
        builder::Frame::NewConnectionId {}
    }
}

impl IntoEvent<builder::Frame> for &crate::frame::RetireConnectionId {
    #[inline]
    fn into_event(self) -> builder::Frame {
        builder::Frame::RetireConnectionId {}
    }
}

impl<'a> IntoEvent<builder::Frame> for &crate::frame::PathChallenge<'a> {
    #[inline]
    fn into_event(self) -> builder::Frame {
        builder::Frame::PathChallenge {}
    }
}

impl<'a> IntoEvent<builder::Frame> for &crate::frame::PathResponse<'a> {
    #[inline]
    fn into_event(self) -> builder::Frame {
        builder::Frame::PathResponse {}
    }
}

impl<'a> IntoEvent<builder::Frame> for &crate::frame::ConnectionClose<'a> {
    #[inline]
    fn into_event(self) -> builder::Frame {
        builder::Frame::ConnectionClose {}
    }
}

impl IntoEvent<builder::Frame> for &crate::frame::HandshakeDone {
    #[inline]
    fn into_event(self) -> builder::Frame {
        builder::Frame::HandshakeDone {}
    }
}

impl<Data> IntoEvent<builder::Frame> for &crate::frame::Stream<Data>
where
    Data: s2n_codec::EncoderValue,
{
    #[inline]
    fn into_event(self) -> builder::Frame {
        builder::Frame::Stream {
            id: self.stream_id.as_u64(),
            offset: self.offset.as_u64(),
            len: self.data.encoding_size() as _,
            is_fin: self.is_fin,
        }
    }
}

impl<Data> IntoEvent<builder::Frame> for &crate::frame::Crypto<Data>
where
    Data: s2n_codec::EncoderValue,
{
    #[inline]
    fn into_event(self) -> builder::Frame {
        builder::Frame::Crypto {
            offset: self.offset.as_u64(),
            len: self.data.encoding_size() as _,
        }
    }
}

impl<Data> IntoEvent<builder::Frame> for &crate::frame::Datagram<Data>
where
    Data: s2n_codec::EncoderValue,
{
    #[inline]
    fn into_event(self) -> builder::Frame {
        builder::Frame::Datagram {
            len: self.data.encoding_size() as _,
        }
    }
}

enum StreamType {
    Bidirectional,
    Unidirectional,
}

impl IntoEvent<builder::StreamType> for &crate::stream::StreamType {
    #[inline]
    fn into_event(self) -> builder::StreamType {
        match self {
            crate::stream::StreamType::Bidirectional => builder::StreamType::Bidirectional {},
            crate::stream::StreamType::Unidirectional => builder::StreamType::Unidirectional {},
        }
    }
}

//= https://tools.ietf.org/id/draft-marx-qlog-event-definitions-quic-h3-02#A.2
//
//= https://tools.ietf.org/id/draft-marx-qlog-event-definitions-quic-h3-02#A.4
enum PacketHeader {
    Initial { number: u64, version: u32 },
    Handshake { number: u64, version: u32 },
    ZeroRtt { number: u64, version: u32 },
    OneRtt { number: u64 },
    Retry { version: u32 },
    // The Version field of a Version Negotiation packet MUST be set to 0x00000000.
    VersionNegotiation,
    StatelessReset,
}

impl builder::PacketHeader {
    #[inline]
    pub fn new(
        packet_number: crate::packet::number::PacketNumber,
        version: u32,
    ) -> builder::PacketHeader {
        use crate::packet::number::PacketNumberSpace;
        use builder::PacketHeader;

        match packet_number.space() {
            PacketNumberSpace::Initial => PacketHeader::Initial {
                number: packet_number.into_event(),
                version,
            },
            PacketNumberSpace::Handshake => PacketHeader::Handshake {
                number: packet_number.into_event(),
                version,
            },
            PacketNumberSpace::ApplicationData => PacketHeader::OneRtt {
                number: packet_number.into_event(),
            },
        }
    }
}

enum KeyType {
    Initial,
    Handshake,
    ZeroRtt,
    OneRtt { generation: u16 },
}

/// A context from which the event is being emitted
///
/// An event can occur in the context of an Endpoint or Connection
enum Subject {
    Endpoint,

    //= https://tools.ietf.org/id/draft-marx-qlog-event-definitions-quic-h3-02#4
    //# it is recommended to use
    //# QUIC's Original Destination Connection ID (ODCID, the CID chosen by
    //# the client when first contacting the server)
    /// This maps to an internal connection id, which is a stable identifier across CID changes.
    Connection {
        id: u64,
    },
}

/// An endpoint may be either a Server or a Client
#[exhaustive]
enum EndpointType {
    Server,
    Client,
}

impl core::fmt::Display for EndpointType {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        match self {
            Self::Client {} => write!(f, "client"),
            Self::Server {} => write!(f, "server"),
        }
    }
}

impl IntoEvent<api::EndpointType> for crate::endpoint::Type {
    #[inline]
    fn into_event(self) -> api::EndpointType {
        match self {
            Self::Client => api::EndpointType::Client {},
            Self::Server => api::EndpointType::Server {},
        }
    }
}

impl IntoEvent<builder::EndpointType> for crate::endpoint::Type {
    #[inline]
    fn into_event(self) -> builder::EndpointType {
        match self {
            Self::Client => builder::EndpointType::Client {},
            Self::Server => builder::EndpointType::Server {},
        }
    }
}

enum DatagramDropReason {
    /// There was an error while attempting to decode the datagram.
    DecodingFailed,
    /// There was an error while parsing the Retry token.
    InvalidRetryToken,
    /// The peer specified an unsupported QUIC version.
    UnsupportedVersion,
    /// The peer sent an invalid Destination Connection Id.
    InvalidDestinationConnectionId,
    /// The peer sent an invalid Source Connection Id.
    InvalidSourceConnectionId,
    /// The Destination Connection Id is unknown and does not map to a Connection.
    ///
    /// Connections are mapped to Destination Connections Ids (DCID) and packets
    /// in a Datagram are routed to a connection based on the DCID in the first
    /// packet. If a Connection is not found for the specified DCID then the
    /// datagram can not be processed and is dropped.
    UnknownDestinationConnectionId,
    /// The connection attempt was rejected.
    RejectedConnectionAttempt,
    /// A datagram was received from an unknown server address.
    UnknownServerAddress,
    /// The peer initiated a connection migration before the handshake was confirmed.
    ConnectionMigrationDuringHandshake,
    /// The attempted connection migration was rejected.
    RejectedConnectionMigration,
    /// The maximum number of paths per connection was exceeded.
    PathLimitExceeded,
    /// The peer initiated a connection migration without supplying enough connection IDs to use.
    InsufficientConnectionIds,
}

enum KeySpace {
    Initial {},
    Handshake {},
    ZeroRtt {},
    OneRtt {},
}

enum PacketDropReason<'a> {
    /// A connection error occurred and is no longer able to process packets.
    ConnectionError { path: Path<'a> },
    /// The handshake needed to be complete before processing the packet.
    ///
    /// To ensure the connection stays secure, short packets can only be processed
    /// once the handshake has completed.
    HandshakeNotComplete { path: Path<'a> },
    /// The packet contained a version which did not match the version negotiated
    /// during the handshake.
    VersionMismatch { version: u32, path: Path<'a> },
    /// A datagram contained more than one destination connection ID, which is
    /// not allowed.
    ConnectionIdMismatch {
        packet_cid: &'a [u8],
        path: Path<'a>,
    },
    /// There was a failure when attempting to remove header protection.
    UnprotectFailed { space: KeySpace, path: Path<'a> },
    /// There was a failure when attempting to decrypt the packet.
    DecryptionFailed {
        path: Path<'a>,
        packet_header: PacketHeader,
    },
    /// Packet decoding failed.
    ///
    /// The payload is decoded one packet at a time. If decoding fails
    /// then the remaining packets are also discarded.
    DecodingFailed { path: Path<'a> },
    /// The client received a non-empty retry token.
    NonEmptyRetryToken { path: Path<'a> },
    /// A Retry packet was discarded.
    RetryDiscarded {
        reason: RetryDiscardReason<'a>,
        path: Path<'a>,
    },
}

#[deprecated(note = "use on_rx_ack_range_dropped event instead")]
enum AckAction {
    /// Ack range for received packets was dropped due to space constraints
    ///
    /// For the purpose of processing Acks, RX packet numbers are stored as
    /// packet_number ranges in an IntervalSet; only lower and upper bounds
    /// are stored instead of individual packet_numbers. Ranges are merged
    /// when possible so only disjointed ranges are stored.
    ///
    /// When at `capacity`, the lowest packet_number range is dropped.
    RxAckRangeDropped {
        /// The packet number range which was dropped
        packet_number_range: core::ops::RangeInclusive<u64>,
        /// The number of disjoint ranges the IntervalSet can store
        capacity: usize,
        /// The store packet_number range in the IntervalSet
        stored_range: core::ops::RangeInclusive<u64>,
    },
}

enum RetryDiscardReason<'a> {
    /// Received a Retry packet with SCID field equal to DCID field.
    ScidEqualsDcid { cid: &'a [u8] },
    /// A client only processes at most one Retry packet.
    RetryAlreadyProcessed,
    /// The client discards Retry packets if a valid Initial packet
    /// has been received and processed.
    InitialAlreadyProcessed,
    /// The Retry packet received contained an invalid retry integrity tag
    InvalidIntegrityTag,
}

enum MigrationDenyReason {
    BlockedPort,
    PortScopeChanged,
    IpScopeChange,
    ConnectionMigrationDisabled,
}

/// The current state of the ECN controller for the path
enum EcnState {
    /// ECN capability is being actively tested
    Testing,
    /// ECN capability has been tested, but not validated yet
    Unknown,
    /// ECN capability testing has failed validation
    Failed,
    /// ECN capability has been confirmed
    Capable,
}

/// Events tracking the progress of handshake status
enum HandshakeStatus {
    /// The handshake has completed.
    Complete,
    /// The handshake has been confirmed.
    Confirmed,
    /// A HANDSHAKE_DONE frame was delivered or received.
    ///
    /// A Client endpoint receives a HANDSHAKE_DONE frame and
    /// only a Server is allowed to send the HANDSHAKE_DONE
    /// frame.
    HandshakeDoneAcked,

    /// A HANDSHAKE_DONE frame was declared lost.
    ///
    /// The Server is responsible for re-transmitting the
    /// HANDSHAKE_DONE frame until it is acked by the peer.
    HandshakeDoneLost,
}

/// The source that caused a congestion event
enum CongestionSource {
    /// Explicit Congestion Notification
    Ecn,
    /// One or more packets were detected lost
    PacketLoss,
}

#[allow(non_camel_case_types)] // we prefer to match the standard identifier
enum CipherSuite {
    TLS_AES_128_GCM_SHA256,
    TLS_AES_256_GCM_SHA384,
    TLS_CHACHA20_POLY1305_SHA256,
    Unknown,
}

impl CipherSuite {
    #[inline]
    pub fn as_str(&self) -> &'static str {
        match self {
            Self::TLS_AES_128_GCM_SHA256 {} => "TLS_AES_128_GCM_SHA256",
            Self::TLS_AES_256_GCM_SHA384 {} => "TLS_AES_256_GCM_SHA384",
            Self::TLS_CHACHA20_POLY1305_SHA256 {} => "TLS_CHACHA20_POLY1305_SHA256",
            Self::Unknown {} => "UNKNOWN",
        }
    }
}

enum PathChallengeStatus {
    Validated,
    Abandoned,
}

/// The reason the slow start congestion controller state has been exited
enum SlowStartExitCause {
    /// A packet was determined lost
    PacketLoss,
    /// An Explicit Congestion Notification: Congestion Experienced marking was received
    Ecn,
    /// The round trip time estimate was updated
    Rtt,
    /// Slow Start exited due to a reason other than those above
    ///
    /// With the Cubic congestion controller, this reason is used after the initial exiting of
    /// Slow Start, when the previously determined Slow Start threshold is exceed by the
    /// congestion window.
    Other,
}

/// The reason the MTU was updated
enum MtuUpdatedCause {
    /// The MTU was initialized with the default value
    NewPath,
    /// An MTU probe was acknowledged by the peer
    ProbeAcknowledged,
    /// A blackhole was detected
    Blackhole,
}

/// A bandwidth delivery rate estimate with associated metadata
struct RateSample {
    /// The length of the sampling interval
    interval: Duration,
    /// The amount of data in bytes marked as delivered over the sampling interval
    delivered_bytes: u64,
    /// The amount of data in bytes marked as lost over the sampling interval
    lost_bytes: u64,
    /// The number of packets marked as explicit congestion experienced over the sampling interval
    ecn_ce_count: u64,
    /// PacketInfo::is_app_limited from the most recent acknowledged packet
    is_app_limited: bool,
    /// PacketInfo::delivered_bytes from the most recent acknowledged packet
    prior_delivered_bytes: u64,
    /// PacketInfo::bytes_in_flight from the most recent acknowledged packet
    bytes_in_flight: u32,
    /// PacketInfo::lost_bytes from the most recent acknowledged packet
    prior_lost_bytes: u64,
    /// PacketInfo::ecn_ce_count from the most recent acknowledged packet
    prior_ecn_ce_count: u64,
    /// The delivery rate for this rate sample
    delivery_rate_bytes_per_second: u64,
}

// The BBR congestion controller State
enum BbrState {
    Startup,
    Drain,
    ProbeBwDown,
    ProbeBwCruise,
    ProbeBwRefill,
    ProbeBwUp,
    ProbeRtt,
}


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

#[event("transport:application_protocol_information")]
//= https://tools.ietf.org/id/draft-marx-qlog-event-definitions-quic-h3-02#5.3.2
//# QUIC implementations each have their own list of application level
//# protocols and versions thereof they support.
/// Application level protocol
struct ApplicationProtocolInformation<'a> {
    chosen_application_protocol: &'a [u8],
}

#[event("transport:server_name_information")]
/// Server Name was negotiated for the connection
struct ServerNameInformation<'a> {
    chosen_server_name: &'a str,
}

#[event("transport:packet_sent")]
//= https://tools.ietf.org/id/draft-marx-qlog-event-definitions-quic-h3-02#5.3.5
/// Packet was sent by a connection
struct PacketSent {
    packet_header: PacketHeader,
    packet_len: usize,
}

#[event("transport:packet_received")]
//= https://tools.ietf.org/id/draft-marx-qlog-event-definitions-quic-h3-02#5.3.6
/// Packet was received by a connection
struct PacketReceived {
    packet_header: PacketHeader,
}

#[event("connectivity:active_path_updated")]
//= https://tools.ietf.org/id/draft-marx-qlog-event-definitions-quic-h3-02#5.1.8
/// Active path was updated
struct ActivePathUpdated<'a> {
    // TODO: many events seem to require PacketHeader. Make it more ergonomic
    // to include this field.
    // packet_header: PacketHeader,
    previous: Path<'a>,
    active: Path<'a>,
}

#[event("transport:path_created")]
/// A new path was created
struct PathCreated<'a> {
    active: Path<'a>,
    new: Path<'a>,
}

#[event("transport:frame_sent")]
//= https://tools.ietf.org/id/draft-marx-qlog-event-definitions-quic-h3-02#5.3.5
// This diverges a bit from the qlog spec, which prefers to log data as part of the
// packet events.
/// Frame was sent
struct FrameSent {
    packet_header: PacketHeader,
    path_id: u64,
    frame: Frame,
}

#[event("transport:frame_received")]
//= https://tools.ietf.org/id/draft-marx-qlog-event-definitions-quic-h3-02#5.3.6
// This diverges a bit from the qlog spec, which prefers to log data as part of the
// packet events.
/// Frame was received
struct FrameReceived<'a> {
    packet_header: PacketHeader,
    path: Path<'a>,
    frame: Frame,
}

#[event("recovery:packet_lost")]
//= https://tools.ietf.org/id/draft-marx-qlog-event-definitions-quic-h3-02#5.4.5
/// Packet was lost
struct PacketLost<'a> {
    packet_header: PacketHeader,
    path: Path<'a>,
    bytes_lost: u16,
    is_mtu_probe: bool,
}

#[event("recovery:metrics_updated")]
//= https://tools.ietf.org/id/draft-marx-qlog-event-definitions-quic-h3-02#5.4.2
/// Recovery metrics updated
struct RecoveryMetrics<'a> {
    path: Path<'a>,
    min_rtt: Duration,
    smoothed_rtt: Duration,
    latest_rtt: Duration,
    rtt_variance: Duration,
    max_ack_delay: Duration,
    pto_count: u32,
    congestion_window: u32,
    bytes_in_flight: u32,
    congestion_limited: bool,
}

#[event("recovery:congestion")]
/// Congestion (ECN or packet loss) has occurred
struct Congestion<'a> {
    path: Path<'a>,
    source: CongestionSource,
}

#[event("recovery:ack_processed")]
#[deprecated(note = "use on_rx_ack_range_dropped event instead")]
/// Events related to ACK processing
struct AckProcessed<'a> {
    action: AckAction,
    path: Path<'a>,
}

#[event("recovery:rx_ack_range_dropped")]
/// Ack range for received packets was dropped due to space constraints
///
/// For the purpose of processing Acks, RX packet numbers are stored as
/// packet_number ranges in an IntervalSet; only lower and upper bounds
/// are stored instead of individual packet_numbers. Ranges are merged
/// when possible so only disjointed ranges are stored.
///
/// When at `capacity`, the lowest packet_number range is dropped.
struct RxAckRangeDropped<'a> {
    path: Path<'a>,
    /// The packet number range which was dropped
    packet_number_range: core::ops::RangeInclusive<u64>,
    /// The number of disjoint ranges the IntervalSet can store
    capacity: usize,
    /// The store packet_number range in the IntervalSet
    stored_range: core::ops::RangeInclusive<u64>,
}

#[event("recovery:ack_range_received")]
/// ACK range was received
struct AckRangeReceived<'a> {
    packet_header: PacketHeader,
    path: Path<'a>,
    ack_range: RangeInclusive<u64>,
}

#[event("recovery:ack_range_sent")]
/// ACK range was sent
struct AckRangeSent {
    packet_header: PacketHeader,
    path_id: u64,
    ack_range: RangeInclusive<u64>,
}

#[event("transport:packet_dropped")]
/// Packet was dropped with the given reason
struct PacketDropped<'a> {
    reason: PacketDropReason<'a>,
}

#[event("security:key_update")]
//= https://tools.ietf.org/id/draft-marx-qlog-event-definitions-quic-h3-02#5.2.1
/// Crypto key updated
struct KeyUpdate {
    key_type: KeyType,
    cipher_suite: CipherSuite,
}

#[event("security:key_space_discarded")]
struct KeySpaceDiscarded {
    space: KeySpace,
}

#[event("connectivity:connection_started")]
//= https://tools.ietf.org/id/draft-marx-qlog-event-definitions-quic-h3-02#5.1.2
/// Connection started
struct ConnectionStarted<'a> {
    path: Path<'a>,
}

#[event("connectivity:connection_closed")]
//= https://tools.ietf.org/id/draft-marx-qlog-event-definitions-quic-h3-02#5.1.3
/// Connection closed
struct ConnectionClosed {
    error: crate::connection::Error,
}

#[event("transport:duplicate_packet")]
/// Duplicate packet received
struct DuplicatePacket<'a> {
    packet_header: PacketHeader,
    path: Path<'a>,
    error: DuplicatePacketError,
}

#[event("transport:transport_parameters_received")]
/// Transport parameters received by connection
struct TransportParametersReceived<'a> {
    transport_parameters: TransportParameters<'a>,
}

#[event("transport:datagram_sent")]
//= https://tools.ietf.org/id/draft-marx-qlog-event-definitions-quic-h3-02#5.3.10
/// Datagram sent by a connection
struct DatagramSent {
    len: u16,
    /// The GSO offset at which this datagram was written
    ///
    /// If this value is greater than 0, it indicates that this datagram has been sent with other
    /// segments in a single buffer.
    ///
    /// See the [Linux kernel documentation](https://www.kernel.org/doc/html/latest/networking/segmentation-offloads.html#generic-segmentation-offload) for more details.
    gso_offset: usize,
}

#[event("transport:datagram_received")]
//= https://tools.ietf.org/id/draft-marx-qlog-event-definitions-quic-h3-02#5.3.11
/// Datagram received by a connection
struct DatagramReceived {
    len: u16,
}

#[event("transport:datagram_dropped")]
//= https://tools.ietf.org/id/draft-marx-qlog-event-definitions-quic-h3-02#5.3.12
/// Datagram dropped by a connection
struct DatagramDropped {
    len: u16,
    reason: DatagramDropReason,
}

#[event("connectivity:connection_id_updated")]
//= https://tools.ietf.org/id/draft-marx-qlog-event-definitions-quic-h3-02#5.1.4
/// ConnectionId updated
struct ConnectionIdUpdated<'a> {
    path_id: u64,
    /// The endpoint that updated its connection id
    cid_consumer: crate::endpoint::Location,
    previous: ConnectionId<'a>,
    current: ConnectionId<'a>,
}

#[event("recovery:ecn_state_changed")]
struct EcnStateChanged<'a> {
    path: Path<'a>,
    state: EcnState,
}

#[event("connectivity:connection_migration_denied")]
struct ConnectionMigrationDenied {
    reason: MigrationDenyReason,
}

#[event("connectivity:handshake_status_updated")]
struct HandshakeStatusUpdated {
    status: HandshakeStatus,
}

#[event("connectivity:path_challenge_updated")]
/// Path challenge updated
struct PathChallengeUpdated<'a> {
    path_challenge_status: PathChallengeStatus,
    path: Path<'a>,
    challenge_data: &'a [u8],
}

#[event("tls:client_hello")]
struct TlsClientHello<'a> {
    payload: &'a [&'a [u8]],
}

#[event("tls:server_hello")]
struct TlsServerHello<'a> {
    payload: &'a [&'a [u8]],
}

#[event("transport:rx_stream_progress")]
struct RxStreamProgress {
    bytes: usize,
}

#[event("transport:tx_stream_progress")]
struct TxStreamProgress {
    bytes: usize,
}

#[event("connectivity::keep_alive_timer_expired")]
pub struct KeepAliveTimerExpired {
    timeout: Duration,
}

#[event("connectivity:mtu_updated")]
/// The maximum transmission unit (MTU) for the path has changed
struct MtuUpdated {
    path_id: u64,
    mtu: u16,
    cause: MtuUpdatedCause,
}

#[event("recovery:slow_start_exited")]
/// The slow start congestion controller state has been exited
struct SlowStartExited {
    path_id: u64,
    cause: SlowStartExitCause,
    congestion_window: u32,
}

#[event("recovery:delivery_rate_sampled")]
/// A new delivery rate sample has been generated
/// Note: This event is only recorded for congestion controllers that support
///       bandwidth estimates, such as BBR
struct DeliveryRateSampled {
    path_id: u64,
    rate_sample: RateSample,
}

#[event("recovery:pacing_rate_updated")]
/// The pacing rate has been updated
struct PacingRateUpdated {
    path_id: u64,
    bytes_per_second: u64,
    burst_size: u32,
    pacing_gain: f32,
}

#[event("recovery:bbr_state_changed")]
/// The BBR state has changed
struct BbrStateChanged {
    path_id: u64,
    state: BbrState,
}


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

#[event("transport::version_information")]
#[subject(endpoint)]
//= https://tools.ietf.org/id/draft-marx-qlog-event-definitions-quic-h3-02#5.3.1
//# QUIC endpoints each have their own list of of QUIC versions they
//# support.
/// QUIC version
struct VersionInformation<'a> {
    server_versions: &'a [u32],
    client_versions: &'a [u32],
    chosen_version: Option<u32>,
}

#[event("transport:packet_sent")]
#[subject(endpoint)]
//= https://tools.ietf.org/id/draft-marx-qlog-event-definitions-quic-h3-02#5.3.5
/// Packet was sent by the endpoint
struct EndpointPacketSent {
    packet_header: PacketHeader,
}

#[event("transport:packet_received")]
#[subject(endpoint)]
//= https://tools.ietf.org/id/draft-marx-qlog-event-definitions-quic-h3-02#5.3.6
/// Packet was received by the endpoint
struct EndpointPacketReceived {
    packet_header: PacketHeader,
}

#[event("transport:datagram_sent")]
#[subject(endpoint)]
//= https://tools.ietf.org/id/draft-marx-qlog-event-definitions-quic-h3-02#5.3.10
/// Datagram sent by the endpoint
struct EndpointDatagramSent {
    len: u16,
    /// The GSO offset at which this datagram was written
    ///
    /// If this value is greater than 0, it indicates that this datagram has been sent with other
    /// segments in a single buffer.
    ///
    /// See the [Linux kernel documentation](https://www.kernel.org/doc/html/latest/networking/segmentation-offloads.html#generic-segmentation-offload) for more details.
    gso_offset: usize,
}

#[event("transport:datagram_received")]
#[subject(endpoint)]
//= https://tools.ietf.org/id/draft-marx-qlog-event-definitions-quic-h3-02#5.3.11
/// Datagram received by the endpoint
struct EndpointDatagramReceived {
    len: u16,
}

#[event("transport:datagram_dropped")]
#[subject(endpoint)]
//= https://tools.ietf.org/id/draft-marx-qlog-event-definitions-quic-h3-02#5.3.12
/// Datagram dropped by the endpoint
struct EndpointDatagramDropped {
    len: u16,
    reason: DatagramDropReason,
}

#[event("transport:connection_attempt_failed")]
#[subject(endpoint)]
struct EndpointConnectionAttemptFailed {
    error: crate::connection::Error,
}


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

#[event("platform:tx")]
#[subject(endpoint)]
/// Emitted when the platform sends at least one packet
struct PlatformTx {
    /// The number of packets sent
    count: usize,
}

#[event("platform:tx_error")]
#[subject(endpoint)]
/// Emitted when the platform returns an error while sending datagrams
struct PlatformTxError {
    /// The error code returned by the platform
    errno: i32,
}

#[cfg(feature = "std")]
impl From<PlatformTxError> for std::io::Error {
    fn from(error: PlatformTxError) -> Self {
        Self::from_raw_os_error(error.errno)
    }
}

#[event("platform:rx")]
#[subject(endpoint)]
/// Emitted when the platform receives at least one packet
struct PlatformRx {
    /// The number of packets received
    count: usize,
}

#[event("platform:rx_error")]
#[subject(endpoint)]
/// Emitted when the platform returns an error while receiving datagrams
struct PlatformRxError {
    /// The error code returned by the platform
    errno: i32,
}

#[cfg(feature = "std")]
impl From<PlatformRxError> for std::io::Error {
    fn from(error: PlatformRxError) -> Self {
        Self::from_raw_os_error(error.errno)
    }
}

#[event("platform:feature_configured")]
#[subject(endpoint)]
/// Emitted when a platform feature is configured
struct PlatformFeatureConfigured {
    configuration: PlatformFeatureConfiguration,
}

enum PlatformFeatureConfiguration {
    /// Emitted when segment offload was configured
    Gso {
        /// The maximum number of segments that can be sent in a single GSO packet
        ///
        /// If this value not greater than 1, GSO is disabled.
        max_segments: usize,
    },
    /// Emitted when receive segment offload was configured
    Gro { enabled: bool },
    /// Emitted when ECN support is configured
    Ecn { enabled: bool },
    /// Emitted when the maximum transmission unit is configured
    MaxMtu { mtu: u16 },
}

#[event("platform:event_loop_wakeup")]
#[subject(endpoint)]
struct PlatformEventLoopWakeup {
    timeout_expired: bool,
    rx_ready: bool,
    tx_ready: bool,
    application_wakeup: bool,
}

#[event("platform:event_loop_sleep")]
#[subject(endpoint)]
struct PlatformEventLoopSleep {
    /// The next time at which the event loop will wake
    timeout: Option<core::time::Duration>,
    /// The amount of time spent processing endpoint events in a single event loop
    processing_duration: core::time::Duration,
}