// Copyright 2015 Brian Smith.
// SPDX-License-Identifier: ISC
// Modifications copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC

//! Building blocks for parsing DER-encoded ASN.1 structures.
//!
//! This module contains the foundational parts of an ASN.1 DER parser.

use super::Positive;
use crate::error;

pub const CONSTRUCTED: u8 = 1 << 5;
pub const CONTEXT_SPECIFIC: u8 = 2 << 6;

#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[repr(u8)]
pub enum Tag {
    Boolean = 0x01,
    Integer = 0x02,
    BitString = 0x03,
    OctetString = 0x04,
    Null = 0x05,
    OID = 0x06,
    Sequence = CONSTRUCTED | 0x10, // 0x30
    UTCTime = 0x17,
    GeneralizedTime = 0x18,

    ContextSpecificConstructed0 = CONTEXT_SPECIFIC | CONSTRUCTED,
    ContextSpecificConstructed1 = CONTEXT_SPECIFIC | CONSTRUCTED | 1,
    ContextSpecificConstructed3 = CONTEXT_SPECIFIC | CONSTRUCTED | 3,
}

impl From<Tag> for usize {
    fn from(tag: Tag) -> Self {
        tag as Self
    }
}

impl From<Tag> for u8 {
    fn from(tag: Tag) -> Self {
        tag as Self
    } // XXX: narrowing conversion.
}

pub fn expect_tag_and_get_value<'a>(
    input: &mut untrusted::Reader<'a>,
    tag: Tag,
) -> Result<untrusted::Input<'a>, error::Unspecified> {
    let (actual_tag, inner) = read_tag_and_get_value(input)?;
    if usize::from(tag) != usize::from(actual_tag) {
        return Err(error::Unspecified);
    }
    Ok(inner)
}

pub fn read_tag_and_get_value<'a>(
    input: &mut untrusted::Reader<'a>,
) -> Result<(u8, untrusted::Input<'a>), error::Unspecified> {
    let tag = input.read_byte()?;
    if (tag & 0x1F) == 0x1F {
        return Err(error::Unspecified); // High tag number form is not allowed.
    }

    // If the high order bit of the first byte is set to zero then the length
    // is encoded in the seven remaining bits of that byte. Otherwise, those
    // seven bits represent the number of bytes used to encode the length.
    let length = match input.read_byte()? {
        n if (n & 0x80) == 0 => usize::from(n),
        0x81 => {
            let second_byte = input.read_byte()?;
            if second_byte < 128 {
                return Err(error::Unspecified); // Not the canonical encoding.
            }
            usize::from(second_byte)
        }
        0x82 => {
            let second_byte = usize::from(input.read_byte()?);
            let third_byte = usize::from(input.read_byte()?);
            let combined = (second_byte << 8) | third_byte;
            if combined < 256 {
                return Err(error::Unspecified); // Not the canonical encoding.
            }
            combined
        }
        _ => {
            return Err(error::Unspecified); // We don't support longer lengths.
        }
    };

    let inner = input.read_bytes(length)?;
    Ok((tag, inner))
}

pub fn bit_string_with_no_unused_bits<'a>(
    input: &mut untrusted::Reader<'a>,
) -> Result<untrusted::Input<'a>, error::Unspecified> {
    nested(input, Tag::BitString, error::Unspecified, |value| {
        let unused_bits_at_end = value.read_byte()?;
        if unused_bits_at_end != 0 {
            return Err(error::Unspecified);
        }
        Ok(value.read_bytes_to_end())
    })
}

// TODO: investigate taking decoder as a reference to reduce generated code
// size.
pub fn nested<'a, F, R, E: Copy>(
    input: &mut untrusted::Reader<'a>,
    tag: Tag,
    error: E,
    decoder: F,
) -> Result<R, E>
where
    F: FnOnce(&mut untrusted::Reader<'a>) -> Result<R, E>,
{
    let inner = expect_tag_and_get_value(input, tag).map_err(|_| error)?;
    inner.read_all(error, decoder)
}

fn nonnegative_integer<'a>(
    input: &mut untrusted::Reader<'a>,
    min_value: u8,
) -> Result<untrusted::Input<'a>, error::Unspecified> {
    // Verify that |input|, which has had any leading zero stripped off, is the
    // encoding of a value of at least |min_value|.
    fn check_minimum(input: untrusted::Input, min_value: u8) -> Result<(), error::Unspecified> {
        input.read_all(error::Unspecified, |input| {
            let first_byte = input.read_byte()?;
            if input.at_end() && first_byte < min_value {
                return Err(error::Unspecified);
            }
            let _: untrusted::Input = input.read_bytes_to_end();
            Ok(())
        })
    }

    let value = expect_tag_and_get_value(input, Tag::Integer)?;

    value.read_all(error::Unspecified, |input| {
        // Empty encodings are not allowed.
        let first_byte = input.read_byte()?;

        if first_byte == 0 {
            if input.at_end() {
                // |value| is the legal encoding of zero.
                if min_value > 0 {
                    return Err(error::Unspecified);
                }
                return Ok(value);
            }

            let r = input.read_bytes_to_end();
            r.read_all(error::Unspecified, |input| {
                let second_byte = input.read_byte()?;
                if (second_byte & 0x80) == 0 {
                    // A leading zero is only allowed when the value's high bit
                    // is set.
                    return Err(error::Unspecified);
                }
                let _: untrusted::Input = input.read_bytes_to_end();
                Ok(())
            })?;
            check_minimum(r, min_value)?;
            return Ok(r);
        }

        // Negative values are not allowed.
        if (first_byte & 0x80) != 0 {
            return Err(error::Unspecified);
        }

        let _: untrusted::Input = input.read_bytes_to_end();
        check_minimum(value, min_value)?;
        Ok(value)
    })
}

/// Parse as integer with a value in the in the range [0, 255], returning its
/// numeric value. This is typically used for parsing version numbers.
#[inline]
pub fn small_nonnegative_integer(input: &mut untrusted::Reader) -> Result<u8, error::Unspecified> {
    let value = nonnegative_integer(input, 0)?;
    value.read_all(error::Unspecified, |input| {
        let r = input.read_byte()?;
        Ok(r)
    })
}

/// Parses a positive DER integer, returning the big-endian-encoded value,
/// sans any leading zero byte.
pub fn positive_integer<'a>(
    input: &mut untrusted::Reader<'a>,
) -> Result<Positive<'a>, error::Unspecified> {
    Ok(Positive::new_non_empty_without_leading_zeros(
        nonnegative_integer(input, 1)?,
    ))
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::error;
    use untrusted::Input;

    fn with_good_i<F, R>(value: &[u8], f: F)
    where
        F: FnOnce(&mut untrusted::Reader) -> Result<R, error::Unspecified>,
    {
        let r = Input::from(value).read_all(error::Unspecified, f);
        assert!(r.is_ok());
    }

    fn with_bad_i<F, R>(value: &[u8], f: F)
    where
        F: FnOnce(&mut untrusted::Reader) -> Result<R, error::Unspecified>,
    {
        let r = Input::from(value).read_all(error::Unspecified, f);
        assert!(r.is_err());
    }

    static ZERO_INTEGER: &[u8] = &[0x02, 0x01, 0x00];

    static GOOD_POSITIVE_INTEGERS: &[(&[u8], u8)] = &[
        (&[0x02, 0x01, 0x01], 0x01),
        (&[0x02, 0x01, 0x02], 0x02),
        (&[0x02, 0x01, 0x7e], 0x7e),
        (&[0x02, 0x01, 0x7f], 0x7f),
        // Values that need to have an 0x00 prefix to disambiguate them from
        // them from negative values.
        (&[0x02, 0x02, 0x00, 0x80], 0x80),
        (&[0x02, 0x02, 0x00, 0x81], 0x81),
        (&[0x02, 0x02, 0x00, 0xfe], 0xfe),
        (&[0x02, 0x02, 0x00, 0xff], 0xff),
    ];

    #[allow(clippy::type_complexity)]
    static GOOD_BIG_POSITIVE_INTEGERS: &[((&[u8], &[u8]), (&[u8], &[u8]))] = &[
        ((&[0x02, 0x81, 129u8, 1], &[0; 128]), (&[1], &[0; 128])),
        ((&[0x02, 0x82, 0x01, 0x00, 1], &[0; 255]), (&[1], &[0; 255])),
    ];

    static BAD_NONNEGATIVE_INTEGERS: &[&[u8]] = &[
        &[],           // At end of input
        &[0x02],       // Tag only
        &[0x02, 0x00], // Empty value
        // Length mismatch
        &[0x02, 0x00, 0x01],
        &[0x02, 0x01],
        &[0x02, 0x01, 0x00, 0x01],
        &[0x02, 0x01, 0x01, 0x00], // Would be valid if last byte is ignored.
        &[0x02, 0x02, 0x01],
        // Negative values
        &[0x02, 0x01, 0x80],
        &[0x02, 0x01, 0xfe],
        &[0x02, 0x01, 0xff],
        // Values that have an unnecessary leading 0x00
        &[0x02, 0x02, 0x00, 0x00],
        &[0x02, 0x02, 0x00, 0x01],
        &[0x02, 0x02, 0x00, 0x02],
        &[0x02, 0x02, 0x00, 0x7e],
        &[0x02, 0x02, 0x00, 0x7f],
    ];

    #[test]
    fn test_small_nonnegative_integer() {
        with_good_i(ZERO_INTEGER, |input| {
            assert_eq!(small_nonnegative_integer(input)?, 0x00);
            Ok(())
        });
        for &(test_in, test_out) in GOOD_POSITIVE_INTEGERS.iter() {
            with_good_i(test_in, |input| {
                assert_eq!(small_nonnegative_integer(input)?, test_out);
                Ok(())
            });
        }
        for &test_in in BAD_NONNEGATIVE_INTEGERS.iter() {
            with_bad_i(test_in, |input| {
                let _: u8 = small_nonnegative_integer(input)?;
                Ok(())
            });
        }
    }

    #[test]
    fn test_positive_integer() {
        with_bad_i(ZERO_INTEGER, |input| {
            let _: Positive<'_> = positive_integer(input)?;
            Ok(())
        });
        for &(test_in, test_out) in GOOD_POSITIVE_INTEGERS.iter() {
            with_good_i(test_in, |input| {
                let test_out = [test_out];
                assert_eq!(
                    positive_integer(input)?
                        .big_endian_without_leading_zero_as_input()
                        .as_slice_less_safe(),
                    untrusted::Input::from(&test_out[..]).as_slice_less_safe()
                );
                Ok(())
            });
        }
        for &test_in in BAD_NONNEGATIVE_INTEGERS.iter() {
            with_bad_i(test_in, |input| {
                let _: Positive<'_> = positive_integer(input)?;
                Ok(())
            });
        }
    }

    #[test]
    fn test_tag() {
        let tgt = usize::from(Tag::GeneralizedTime);
        assert_eq!(0x18usize, tgt);

        let tgt = u8::from(Tag::GeneralizedTime);
        assert_eq!(0x18u8, tgt);

        let tgt = Tag::GeneralizedTime;
        assert_eq!(tgt, Tag::GeneralizedTime);
    }

    #[test]
    fn test_big() {
        for &((bytes_in_a, bytes_in_b), (bytes_out_a, bytes_out_b)) in
            GOOD_BIG_POSITIVE_INTEGERS.iter()
        {
            let mut bytes_in = Vec::new();
            bytes_in.extend(bytes_in_a);
            bytes_in.extend(bytes_in_b);
            let mut bytes_out: Vec<u8> = Vec::new();
            bytes_out.extend(bytes_out_a);
            bytes_out.extend(bytes_out_b);

            with_good_i(&bytes_in, |input| {
                let positive = positive_integer(input)?;
                let expected_bytes = positive.big_endian_without_leading_zero();
                assert_eq!(expected_bytes, &bytes_out);
                Ok(())
            });
        }
    }

    #[test]
    fn test_bit_string_with_no_unused_bits() {
        // Not a BitString
        let mut reader_bad = untrusted::Reader::new(Input::from(&[0x02, 0x01]));
        assert!(bit_string_with_no_unused_bits(&mut reader_bad).is_err());
        // Unused bits at end
        let mut reader_bad2 = untrusted::Reader::new(Input::from(&[0x03, 0x01, 0x01]));
        assert!(bit_string_with_no_unused_bits(&mut reader_bad2).is_err());

        let mut reader_good = untrusted::Reader::new(Input::from(&[0x03, 0x01, 0x00]));
        let input = bit_string_with_no_unused_bits(&mut reader_good).unwrap();
        let expected_result: &[u8] = &[];
        assert_eq!(expected_result, input.as_slice_less_safe());
    }
}


// Copyright 2018 Brian Smith.
// SPDX-License-Identifier: ISC
// Modifications copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC

#[cfg(test)]
use super::{
    der::Tag,
    writer::{write_copy, Accumulator, LengthMeasurement, Writer},
    Positive,
};

#[cfg(test)]
pub(crate) fn write_positive_integer(output: &mut dyn Accumulator, value: &Positive) {
    let first_byte = value.first_byte();
    let value = value.big_endian_without_leading_zero_as_input();
    write_tlv(output, Tag::Integer, |output| {
        if (first_byte & 0x80) != 0 {
            output.write_byte(0); // Disambiguate negative number.
        }
        write_copy(output, value);
    });
}

#[cfg(test)]
pub(crate) fn write_all(tag: Tag, write_value: &dyn Fn(&mut dyn Accumulator)) -> Box<[u8]> {
    let length = {
        let mut length = LengthMeasurement::zero();
        write_tlv(&mut length, tag, write_value);
        length
    };

    let mut output = Writer::with_capacity(&length);
    write_tlv(&mut output, tag, write_value);

    output.into()
}

#[cfg(test)]
#[allow(clippy::cast_possible_truncation)]
fn write_tlv<F>(output: &mut dyn Accumulator, tag: Tag, write_value: F)
where
    F: Fn(&mut dyn Accumulator),
{
    let length: usize = {
        let mut length = LengthMeasurement::zero();
        write_value(&mut length);
        length.into()
    };

    output.write_byte(tag as u8);
    if length < 0x80 {
        output.write_byte(length as u8);
    } else if length < 0x1_00 {
        output.write_byte(0x81);
        output.write_byte(length as u8);
    } else if length < 0x1_00_00 {
        output.write_byte(0x82);
        output.write_byte((length / 0x1_00) as u8);
        output.write_byte(length as u8);
    } else {
        unreachable!();
    };

    write_value(output);
}

#[cfg(test)]
mod tests {
    use crate::io::der::Tag;
    use crate::io::der_writer::{write_all, write_positive_integer};
    use crate::io::writer::{Accumulator, LengthMeasurement};
    use crate::io::Positive;
    use crate::rand::{generate, SystemRandom};

    const TEST_DATA_SIZE: usize = 100;
    #[test]
    fn test_write_positive_integer() {
        let mut data: [u8; TEST_DATA_SIZE] = generate(&SystemRandom::new()).unwrap().expose();
        data[0] |= 0x80; //negative
        let positive = Positive::new_non_empty_without_leading_zeros(untrusted::Input::from(&data));
        let mut length_measurement = LengthMeasurement::zero();
        write_positive_integer(&mut length_measurement, &positive);
        let measurement: usize = length_measurement.into();
        assert_eq!(measurement, TEST_DATA_SIZE + 3);
    }

    #[test]
    #[allow(clippy::cast_possible_truncation)]
    fn test_write_all() {
        let mut data: [u8; TEST_DATA_SIZE] = generate(&SystemRandom::new()).unwrap().expose();
        data[0] |= 0x80; //negative
        let positive = Positive::new_non_empty_without_leading_zeros(untrusted::Input::from(&data));
        let tag = Tag::Integer;

        let func: &dyn Fn(&mut dyn Accumulator) = &|output| {
            write_positive_integer(output, &positive);
        };

        let result = write_all(tag, &func);
        assert_eq!(TEST_DATA_SIZE + 5, result.len());
        assert_eq!(0x02, result[0]);
        assert_eq!((TEST_DATA_SIZE + 3) as u8, result[1]);
        assert_eq!(0x02, result[2]);
        assert_eq!((TEST_DATA_SIZE + 1) as u8, result[3]);
        assert_eq!(0x00, result[4]);

        println!("Result: {} {:?}", result.len(), result.as_ref());
    }
}


// Copyright 2018 Brian Smith.
// SPDX-License-Identifier: ISC
// Modifications copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC

//! Serialization and deserialization.

/// A serialized positive integer.
#[derive(Copy, Clone)]
pub struct Positive<'a>(untrusted::Input<'a>);

impl<'a> Positive<'a> {
    pub(crate) fn new_non_empty_without_leading_zeros(input: untrusted::Input<'a>) -> Self {
        debug_assert!(!input.is_empty());
        debug_assert!(input.len() == 1 || input.as_slice_less_safe()[0] != 0);
        Self(input)
    }

    /// Returns the value, ordered from significant byte to least significant
    /// byte, without any leading zeros. The result is guaranteed to be
    /// non-empty.
    #[inline]
    #[must_use]
    pub fn big_endian_without_leading_zero(&self) -> &'a [u8] {
        self.big_endian_without_leading_zero_as_input()
            .as_slice_less_safe()
    }

    #[inline]
    pub(crate) fn big_endian_without_leading_zero_as_input(&self) -> untrusted::Input<'a> {
        self.0
    }
}

impl Positive<'_> {
    /// Returns the first byte.
    ///
    /// Will not panic because the value is guaranteed to have at least one
    /// byte.
    #[must_use]
    pub fn first_byte(&self) -> u8 {
        // This won't panic because
        self.0.as_slice_less_safe()[0]
    }
}


// Copyright 2018 Brian Smith.
// SPDX-License-Identifier: ISC
// Modifications copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC

//! Serialization and deserialization.

/// Trait for structs that accumulate bytes for subsequent processing.
pub(crate) trait Accumulator {
    /// Write a single byte
    fn write_byte(&mut self, value: u8);
    /// Write a sequence of bytes
    fn write_bytes(&mut self, value: &[u8]);
}

pub(super) struct LengthMeasurement {
    len: usize,
}

impl From<LengthMeasurement> for usize {
    fn from(len_measurement: LengthMeasurement) -> Self {
        len_measurement.len
    }
}

impl LengthMeasurement {
    #[cfg(test)]
    pub fn zero() -> Self {
        Self { len: 0 }
    }
}

impl Accumulator for LengthMeasurement {
    fn write_byte(&mut self, _value: u8) {
        self.len += 1;
    }
    fn write_bytes(&mut self, value: &[u8]) {
        self.len += value.len();
    }
}

pub(super) struct Writer {
    bytes: Vec<u8>,
    requested_capacity: usize,
}

impl Writer {
    #[cfg(test)]
    pub(super) fn with_capacity(capacity: &LengthMeasurement) -> Self {
        Self {
            bytes: Vec::with_capacity(capacity.len),
            requested_capacity: capacity.len,
        }
    }
}

impl From<Writer> for Box<[u8]> {
    fn from(writer: Writer) -> Self {
        assert_eq!(writer.requested_capacity, writer.bytes.len());
        writer.bytes.into_boxed_slice()
    }
}

impl Accumulator for Writer {
    fn write_byte(&mut self, value: u8) {
        self.bytes.push(value);
    }
    fn write_bytes(&mut self, value: &[u8]) {
        self.bytes.extend(value);
    }
}

/// Write bytes from accumulator to `to_copy`.
#[cfg(test)]
pub(crate) fn write_copy(accumulator: &mut dyn Accumulator, to_copy: untrusted::Input) {
    accumulator.write_bytes(to_copy.as_slice_less_safe());
}

#[cfg(test)]
mod tests {
    use crate::io::writer::{write_copy, Accumulator, LengthMeasurement, Writer};
    use crate::rand::{generate, SecureRandom, SystemRandom};
    const TEST_DATA_SIZE: usize = 100;

    #[test]
    fn test_writer() {
        let mut writer = Writer::with_capacity(&LengthMeasurement {
            len: TEST_DATA_SIZE,
        });

        let data = test_accumulator(&mut writer);

        assert_eq!(writer.bytes.as_slice(), data.as_slice());
    }

    fn test_accumulator(accumulator: &mut dyn Accumulator) -> [u8; TEST_DATA_SIZE] {
        fn next_u32() -> u32 {
            let rng = SystemRandom::default();
            let mut bytes = [0u8; 4];
            rng.fill(&mut bytes).unwrap();
            u32::from_be_bytes(bytes)
        }

        let data: [u8; TEST_DATA_SIZE] = generate(&SystemRandom::new()).unwrap().expose();

        accumulator.write_byte(data[0]);

        let mut index = 1;
        while index < TEST_DATA_SIZE {
            let next_chunk_size = 1 + (next_u32() % 10) as usize;
            let mut next_index = index + next_chunk_size;
            if next_index > TEST_DATA_SIZE {
                next_index = TEST_DATA_SIZE;
            }
            accumulator.write_bytes(&data[index..next_index]);
            index = next_index;
        }

        data
    }

    #[test]
    fn test_length_measurement() {
        let mut length_measurement = LengthMeasurement::zero();

        let data = test_accumulator(&mut length_measurement);
        let acc_len: usize = length_measurement.into();

        assert_eq!(acc_len, data.len());
    }

    #[test]
    fn test_write_copy() {
        let mut length_measurement = LengthMeasurement::zero();
        let data: [u8; TEST_DATA_SIZE] = generate(&SystemRandom::new()).unwrap().expose();

        let input = untrusted::Input::from(&data);
        write_copy(&mut length_measurement, input);

        let acc_len: usize = length_measurement.into();

        assert_eq!(acc_len, data.len());
    }
}


// Copyright 2015-2016 Brian Smith.
// SPDX-License-Identifier: ISC
// Modifications copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC

use aws_lc_rs::{aead, error, test, test_file};

use aws_lc_rs::aead::{Nonce, NONCE_LEN};
use core::ops::RangeFrom;
use mirai_annotations::unrecoverable;

#[test]
fn aead_aes_gcm_128() {
    test_aead(
        &aead::AES_128_GCM,
        seal_with_key,
        open_with_key,
        test_file!("data/aead_aes_128_gcm_tests.txt"),
    );
    test_aead(
        &aead::AES_128_GCM,
        seal_with_less_safe_key,
        open_with_less_safe_key,
        test_file!("data/aead_aes_128_gcm_tests.txt"),
    );
}

#[test]
fn aead_aes_gcm_256() {
    test_aead(
        &aead::AES_256_GCM,
        seal_with_key,
        open_with_key,
        test_file!("data/aead_aes_256_gcm_tests.txt"),
    );
    test_aead(
        &aead::AES_256_GCM,
        seal_with_less_safe_key,
        open_with_less_safe_key,
        test_file!("data/aead_aes_256_gcm_tests.txt"),
    );
}

#[test]
fn aead_chacha20_poly1305() {
    test_aead(
        &aead::CHACHA20_POLY1305,
        seal_with_key,
        open_with_key,
        test_file!("data/aead_chacha20_poly1305_tests.txt"),
    );
    test_aead(
        &aead::CHACHA20_POLY1305,
        seal_with_less_safe_key,
        open_with_less_safe_key,
        test_file!("data/aead_chacha20_poly1305_tests.txt"),
    );
}

#[allow(clippy::too_many_lines)]
fn test_aead<Seal, Open>(
    aead_alg: &'static aead::Algorithm,
    seal: Seal,
    open: Open,
    test_file: test::File,
) where
    Seal: Fn(
        &'static aead::Algorithm,
        &[u8],
        Nonce,
        aead::Aad<&[u8]>,
        &mut Vec<u8>,
    ) -> Result<(), error::Unspecified>,
    Open: for<'a> Fn(
        &'static aead::Algorithm,
        &[u8],
        Nonce,
        aead::Aad<&[u8]>,
        &'a mut [u8],
        RangeFrom<usize>,
    ) -> Result<&'a mut [u8], error::Unspecified>,
{
    // TLS record headers are 5 bytes long.
    // TLS explicit nonces for AES-GCM are 8 bytes long.
    static MINIMAL_IN_PREFIX_LENS: [usize; 36] = [
        // No input prefix to overwrite; i.e. the opening is exactly
        // "in place."
        0,
        1,
        2,
        // Proposed TLS 1.3 header (no explicit nonce).
        5,
        8,
        // Probably the most common use of a non-zero `in_prefix_len`
        // would be to write a decrypted TLS record over the top of the
        // TLS header and nonce.
        5 /* record header */ + 8, /* explicit nonce */
        // The stitched AES-GCM x86-64 code works on 6-block (96 byte)
        // units. Some of the ChaCha20 code is even weirder.
        15,  // The maximum partial AES block.
        16,  // One AES block.
        17,  // One byte more than a full AES block.
        31,  // 2 AES blocks or 1 ChaCha20 block, minus 1.
        32,  // Two AES blocks, one ChaCha20 block.
        33,  // 2 AES blocks or 1 ChaCha20 block, plus 1.
        47,  // Three AES blocks - 1.
        48,  // Three AES blocks.
        49,  // Three AES blocks + 1.
        63,  // Four AES blocks or two ChaCha20 blocks, minus 1.
        64,  // Four AES blocks or two ChaCha20 blocks.
        65,  // Four AES blocks or two ChaCha20 blocks, plus 1.
        79,  // Five AES blocks, minus 1.
        80,  // Five AES blocks.
        81,  // Five AES blocks, plus 1.
        95,  // Six AES blocks or three ChaCha20 blocks, minus 1.
        96,  // Six AES blocks or three ChaCha20 blocks.
        97,  // Six AES blocks or three ChaCha20 blocks, plus 1.
        111, // Seven AES blocks, minus 1.
        112, // Seven AES blocks.
        113, // Seven AES blocks, plus 1.
        127, // Eight AES blocks or four ChaCha20 blocks, minus 1.
        128, // Eight AES blocks or four ChaCha20 blocks.
        129, // Eight AES blocks or four ChaCha20 blocks, plus 1.
        143, // Nine AES blocks, minus 1.
        144, // Nine AES blocks.
        145, // Nine AES blocks, plus 1.
        255, // 16 AES blocks or 8 ChaCha20 blocks, minus 1.
        256, // 16 AES blocks or 8 ChaCha20 blocks.
        257, // 16 AES blocks or 8 ChaCha20 blocks, plus 1.
    ];

    test_aead_key_sizes(aead_alg);

    test::run(test_file, |section, test_case| {
        assert_eq!(section, "");
        let key_bytes = test_case.consume_bytes("KEY");
        let nonce_bytes = test_case.consume_bytes("NONCE");
        let plaintext = test_case.consume_bytes("IN");
        let aad = test_case.consume_bytes("AD");
        let mut ct = test_case.consume_bytes("CT");
        let tag = test_case.consume_bytes("TAG");
        let error = test_case.consume_optional_string("FAILS");

        match &error {
            Some(err) if err == "WRONG_NONCE_LENGTH" => {
                assert!(aead::Nonce::try_assume_unique_for_key(&nonce_bytes).is_err());
                return Ok(());
            }
            _ => (),
        };

        let mut s_in_out = plaintext.clone();
        let nonce = aead::Nonce::try_assume_unique_for_key(&nonce_bytes).unwrap();
        let s_result = seal(
            aead_alg,
            &key_bytes[..],
            nonce,
            aead::Aad::from(&aad[..]),
            &mut s_in_out,
        );

        ct.extend(tag);

        if s_result.is_ok() {
            assert_eq!(&ct, &s_in_out);
        }

        // In release builds, test all prefix lengths from 0 to 4096 bytes.
        // Debug builds are too slow for this, so for those builds, only
        // test a smaller subset.

        let mut more_comprehensive_in_prefix_lengths = [0; 4096];
        let in_prefix_lengths = if cfg!(debug_assertions) {
            &MINIMAL_IN_PREFIX_LENS[..]
        } else {
            #[allow(clippy::needless_range_loop)]
            for b in 0..more_comprehensive_in_prefix_lengths.len() {
                more_comprehensive_in_prefix_lengths[b] = b;
            }
            &more_comprehensive_in_prefix_lengths[..]
        };
        let mut o_in_out = vec![123u8; 4096];

        for &in_prefix_len in in_prefix_lengths.iter() {
            o_in_out.truncate(0);
            o_in_out.resize(in_prefix_len, 123);
            o_in_out.extend_from_slice(&ct[..]);

            let o_in_out_clone = o_in_out.clone();
            let nonce = aead::Nonce::try_assume_unique_for_key(&nonce_bytes).unwrap();
            let o_result = open(
                aead_alg,
                &key_bytes,
                nonce,
                aead::Aad::from(&aad[..]),
                &mut o_in_out,
                in_prefix_len..,
            );
            match error {
                None => {
                    assert!(s_result.is_ok());
                    assert!(o_result.is_ok(), "Not ok: {o_result:?}");
                    let result = o_result.unwrap();
                    assert_eq!(&plaintext[..], result);

                    for bad_func in [aead_open_bad_tag, aead_open_bad_nonce, aead_open_bad_aad] {
                        bad_func(
                            aead_alg,
                            &key_bytes,
                            &nonce_bytes,
                            aad.as_slice(),
                            &o_in_out_clone,
                            in_prefix_len,
                            &open,
                        );
                    }
                }
                Some(ref error) if error == "WRONG_NONCE_LENGTH" => {
                    assert_eq!(Err(error::Unspecified), s_result);
                    assert_eq!(Err(error::Unspecified), o_result);
                }
                Some(error) => {
                    unrecoverable!("Unexpected error test case: {}", error);
                }
            };
        }

        Ok(())
    });
}

fn aead_open_bad_tag<Open>(
    aead_alg: &'static aead::Algorithm,
    key_bytes: &[u8],
    nonce_bytes: &[u8],
    aad_bytes: &[u8],
    in_out: &[u8],
    in_prefix_len: usize,
    open: Open,
) where
    Open: for<'a> Fn(
        &'static aead::Algorithm,
        &[u8],
        Nonce,
        aead::Aad<&[u8]>,
        &'a mut [u8],
        RangeFrom<usize>,
    ) -> Result<&'a mut [u8], error::Unspecified>,
{
    let mut in_out = Vec::from(in_out);
    let in_out_len = in_out.len();
    in_out[in_out_len - 1] ^= 0x08;
    let nonce_bytes = Vec::from(nonce_bytes);
    let aad_bytes = Vec::from(aad_bytes);
    let nonce = Nonce::try_assume_unique_for_key(&nonce_bytes).unwrap();
    let aad = aead::Aad::from(aad_bytes.as_slice());
    let err_result = open(
        aead_alg,
        key_bytes,
        nonce,
        aad,
        &mut in_out,
        in_prefix_len..,
    );
    assert!(err_result.is_err());
}

fn aead_open_bad_nonce<Open>(
    aead_alg: &'static aead::Algorithm,
    key_bytes: &[u8],
    nonce_bytes: &[u8],
    aad_bytes: &[u8],
    in_out: &[u8],
    in_prefix_len: usize,
    open: Open,
) where
    Open: for<'a> Fn(
        &'static aead::Algorithm,
        &[u8],
        Nonce,
        aead::Aad<&[u8]>,
        &'a mut [u8],
        RangeFrom<usize>,
    ) -> Result<&'a mut [u8], error::Unspecified>,
{
    let mut in_out = Vec::from(in_out);
    let mut nonce_bytes = Vec::from(nonce_bytes);
    nonce_bytes[NONCE_LEN - 1] ^= 0x80;
    let aad_bytes = Vec::from(aad_bytes);
    let nonce = Nonce::try_assume_unique_for_key(&nonce_bytes).unwrap();
    let aad = aead::Aad::from(aad_bytes.as_slice());
    let err_result = open(
        aead_alg,
        key_bytes,
        nonce,
        aad,
        &mut in_out,
        in_prefix_len..,
    );
    assert!(err_result.is_err());
}

fn aead_open_bad_aad<Open>(
    aead_alg: &'static aead::Algorithm,
    key_bytes: &[u8],
    nonce_bytes: &[u8],
    aad_bytes: &[u8],
    in_out: &[u8],
    in_prefix_len: usize,
    open: Open,
) where
    Open: for<'a> Fn(
        &'static aead::Algorithm,
        &[u8],
        Nonce,
        aead::Aad<&[u8]>,
        &'a mut [u8],
        RangeFrom<usize>,
    ) -> Result<&'a mut [u8], error::Unspecified>,
{
    let mut in_out = Vec::from(in_out);
    let nonce_bytes = Vec::from(nonce_bytes);
    let mut aad_bytes = Vec::from(aad_bytes);
    let aad_len = aad_bytes.len();
    if aad_len == 0 {
        aad_bytes.push(0x08);
    } else {
        aad_bytes[aad_len - 1] ^= 0x08;
    }
    let nonce = Nonce::try_assume_unique_for_key(&nonce_bytes).unwrap();
    let aad = aead::Aad::from(aad_bytes.as_slice());
    let err_result = open(
        aead_alg,
        key_bytes,
        nonce,
        aad,
        &mut in_out,
        in_prefix_len..,
    );
    assert!(err_result.is_err());
}

fn seal_with_key(
    algorithm: &'static aead::Algorithm,
    key: &[u8],
    nonce: aead::Nonce,
    aad: aead::Aad<&[u8]>,
    in_out: &mut Vec<u8>,
) -> Result<(), error::Unspecified> {
    let mut s_key: aead::SealingKey<OneNonceSequence> = make_key(algorithm, key, nonce);
    s_key.seal_in_place_append_tag(aad, in_out)
}

fn open_with_key<'a>(
    algorithm: &'static aead::Algorithm,
    key: &[u8],
    nonce: Nonce,
    aad: aead::Aad<&[u8]>,
    in_out: &'a mut [u8],
    ciphertext_and_tag: RangeFrom<usize>,
) -> Result<&'a mut [u8], error::Unspecified> {
    let mut o_key: aead::OpeningKey<OneNonceSequence> = make_key(algorithm, key, nonce);
    o_key.open_within(aad, in_out, ciphertext_and_tag)
}

fn seal_with_less_safe_key(
    algorithm: &'static aead::Algorithm,
    key: &[u8],
    nonce: Nonce,
    aad: aead::Aad<&[u8]>,
    in_out: &mut Vec<u8>,
) -> Result<(), error::Unspecified> {
    let key = make_less_safe_key(algorithm, key);
    key.seal_in_place_append_tag(nonce, aad, in_out)
}

fn open_with_less_safe_key<'a>(
    algorithm: &'static aead::Algorithm,
    key: &[u8],
    nonce: aead::Nonce,
    aad: aead::Aad<&[u8]>,
    in_out: &'a mut [u8],
    ciphertext_and_tag: RangeFrom<usize>,
) -> Result<&'a mut [u8], error::Unspecified> {
    let key = make_less_safe_key(algorithm, key);
    key.open_within(nonce, aad, in_out, ciphertext_and_tag)
}

#[allow(clippy::range_plus_one)]
fn test_aead_key_sizes(aead_alg: &'static aead::Algorithm) {
    let key_len = aead_alg.key_len();
    let key_data = vec![1u8; key_len * 2];

    // Key is the right size.
    assert!(aead::UnboundKey::new(aead_alg, &key_data[..key_len]).is_ok());

    // Key is one byte too small.
    assert!(aead::UnboundKey::new(aead_alg, &key_data[..(key_len - 1)]).is_err());

    // Key is one byte too large.
    assert!(aead::UnboundKey::new(aead_alg, &key_data[..(key_len + 1)]).is_err());

    // Key is half the required size.
    assert!(aead::UnboundKey::new(aead_alg, &key_data[..(key_len / 2)]).is_err());

    // Key is twice the required size.
    assert!(aead::UnboundKey::new(aead_alg, &key_data[..(key_len * 2)]).is_err());

    // Key is empty.
    assert!(aead::UnboundKey::new(aead_alg, &[]).is_err());

    // Key is one byte.
    assert!(aead::UnboundKey::new(aead_alg, &[0]).is_err());
}

// Test that we reject non-standard nonce sizes.
#[allow(clippy::range_plus_one)]
#[test]
fn test_aead_nonce_sizes() {
    let nonce_len = aead::NONCE_LEN;
    let nonce = vec![0u8; nonce_len * 2];

    assert!(aead::Nonce::try_assume_unique_for_key(&nonce[..nonce_len]).is_ok());
    assert!(aead::Nonce::try_assume_unique_for_key(&nonce[..(nonce_len - 1)]).is_err());
    assert!(aead::Nonce::try_assume_unique_for_key(&nonce[..(nonce_len + 1)]).is_err());
    assert!(aead::Nonce::try_assume_unique_for_key(&nonce[..(nonce_len / 2)]).is_err());
    assert!(aead::Nonce::try_assume_unique_for_key(&nonce[..(nonce_len * 2)]).is_err());
    assert!(aead::Nonce::try_assume_unique_for_key(&[]).is_err());
    assert!(aead::Nonce::try_assume_unique_for_key(&nonce[..1]).is_err());
    assert!(aead::Nonce::try_assume_unique_for_key(&nonce[..16]).is_err()); // 128 bits.
}

#[allow(clippy::range_plus_one, clippy::cast_possible_truncation)]
#[test]
fn aead_chacha20_poly1305_openssh() {
    // TODO: test_aead_key_sizes(...);

    test::run(
        test_file!("data/aead_chacha20_poly1305_openssh_tests.txt"),
        |section, test_case| {
            assert_eq!(section, "");

            // XXX: `polyfill::convert` isn't available here.
            let key_bytes = {
                let as_vec = test_case.consume_bytes("KEY");
                let mut as_array = [0u8; aead::chacha20_poly1305_openssh::KEY_LEN];
                as_array.copy_from_slice(&as_vec);
                as_array
            };

            let sequence_number = test_case.consume_usize("SEQUENCE_NUMBER");
            assert_eq!(sequence_number as u32 as usize, sequence_number);
            let sequence_num = sequence_number as u32;
            let plaintext = test_case.consume_bytes("IN");
            let ct = test_case.consume_bytes("CT");
            let expected_tag = test_case.consume_bytes("TAG");

            // TODO: Add some tests for when things fail.
            //let error = test_case.consume_optional_string("FAILS");

            let mut tag = [0u8; aead::chacha20_poly1305_openssh::TAG_LEN];
            let mut s_in_out = plaintext.clone();
            let s_key = aead::chacha20_poly1305_openssh::SealingKey::new(&key_bytes);
            s_key.seal_in_place(sequence_num, &mut s_in_out[..], &mut tag);
            assert_eq!(&ct, &s_in_out);
            assert_eq!(&expected_tag, &tag);
            let o_key = aead::chacha20_poly1305_openssh::OpeningKey::new(&key_bytes);
            {
                let mut cipher_text_clone = Vec::from(&s_in_out[..]);
                let mut tag_clone = [0u8; aead::chacha20_poly1305_openssh::TAG_LEN];
                tag_clone.copy_from_slice(&tag);
                tag_clone[0] = 1;
                let o_result =
                    o_key.open_in_place(sequence_num, &mut cipher_text_clone[..], &tag_clone);
                assert!(o_result.is_err());
            }
            {
                let mut cipher_text_clone = Vec::from(&s_in_out[..]);
                let o_result = o_key.open_in_place(
                    sequence_num.checked_add(1).unwrap(),
                    &mut cipher_text_clone[..],
                    &tag,
                );
                assert!(o_result.is_err());
            }
            {
                let o_result = o_key.open_in_place(sequence_num, &mut s_in_out[..], &tag);
                assert_eq!(o_result, Ok(&plaintext[4..]));
            }
            assert_eq!(&s_in_out[..4], &ct[..4]);
            assert_eq!(&s_in_out[4..], &plaintext[4..]);

            Ok(())
        },
    );
}

#[test]
fn test_aead_traits() {
    test::compile_time_assert_send::<aead::Tag>();
    test::compile_time_assert_sync::<aead::Tag>();
    test::compile_time_assert_send::<aead::UnboundKey>();
    test::compile_time_assert_sync::<aead::UnboundKey>();
    test::compile_time_assert_send::<aead::LessSafeKey>();
    test::compile_time_assert_sync::<aead::LessSafeKey>();
}

#[test]
fn test_aead_thread_safeness() {
    lazy_static::lazy_static! {
        /// Compute the Initial salt once, as the seed is constant
        static ref SECRET_KEY: aead::LessSafeKey = aead::LessSafeKey::new(
            aead::UnboundKey::new(&aead::AES_128_GCM, b"this is a test! ").unwrap(),
        );
    }
    use std::thread;

    let tag = SECRET_KEY
        .seal_in_place_separate_tag(
            aead::Nonce::try_assume_unique_for_key(&[0; aead::NONCE_LEN]).unwrap(),
            aead::Aad::empty(),
            &mut [],
        )
        .unwrap();

    let mut join_handles = Vec::new();
    for _ in 1..100 {
        let join_handle = thread::spawn(|| {
            SECRET_KEY
                .seal_in_place_separate_tag(
                    aead::Nonce::try_assume_unique_for_key(&[0; aead::NONCE_LEN]).unwrap(),
                    aead::Aad::empty(),
                    &mut [],
                )
                .unwrap()
        });
        join_handles.push(join_handle);
    }
    for handle in join_handles {
        let thread_tag = handle.join().unwrap();
        assert_eq!(thread_tag.as_ref(), tag.as_ref());
    }
}

#[test]
fn test_aead_key_debug() {
    let key_bytes = [0; 32];
    let nonce = [0; aead::NONCE_LEN];

    let key = aead::UnboundKey::new(&aead::AES_256_GCM, &key_bytes).unwrap();
    assert_eq!("UnboundKey { algorithm: AES_256_GCM }", format!("{key:?}"));

    let sealing_key: aead::SealingKey<OneNonceSequence> = make_key(
        &aead::AES_256_GCM,
        &key_bytes,
        aead::Nonce::try_assume_unique_for_key(&nonce).unwrap(),
    );
    assert_eq!(
        "SealingKey { algorithm: AES_256_GCM }",
        format!("{sealing_key:?}")
    );

    let opening_key: aead::OpeningKey<OneNonceSequence> = make_key(
        &aead::AES_256_GCM,
        &key_bytes,
        aead::Nonce::try_assume_unique_for_key(&nonce).unwrap(),
    );
    assert_eq!(
        "OpeningKey { algorithm: AES_256_GCM }",
        format!("{opening_key:?}")
    );
    let key: aead::LessSafeKey = make_less_safe_key(&aead::AES_256_GCM, &key_bytes);
    assert_eq!("LessSafeKey { algorithm: AES_256_GCM }", format!("{key:?}"));
}

// Based on bugs found while testing against rustls.
// Specifically related to tls12 API tests: https://github.com/rustls/rustls/blob/main/rustls/tests/api.rs
#[test]
#[allow(clippy::unit_cmp)]
fn rustls_bug() {
    const KEY: &[u8] = &[
        239, 90, 253, 140, 117, 97, 1, 139, 56, 128, 152, 217, 106, 97, 87, 101, 79, 81, 29, 233,
        96, 98, 201, 110, 206, 250, 122, 166, 21, 134, 58, 249,
    ];
    const NONCE: [u8; 12] = [43, 177, 114, 110, 129, 186, 1, 92, 12, 167, 248, 103];
    const AAD: &[u8] = &[0, 0, 0, 0, 0, 0, 0, 0, 22, 3, 3, 0, 16];
    const DATA: &[u8; 16] = &[
        20, 0, 0, 12, 92, 94, 146, 70, 164, 130, 7, 112, 227, 239, 243, 228,
    ];

    fn append(v: &mut Vec<u8>, extra: &[u8], data: &[u8], tag: &[u8]) {
        v.extend_from_slice(extra);
        v.extend_from_slice(data);
        v.extend_from_slice(tag);
    }

    let uk = aead::UnboundKey::new(&aead::AES_256_GCM, KEY).unwrap();
    let ring_uk = ring::aead::UnboundKey::new(&ring::aead::AES_256_GCM, KEY).unwrap();

    let lsk = aead::LessSafeKey::new(uk);
    let ring_lsk = ring::aead::LessSafeKey::new(ring_uk);

    let mut enc_data: Vec<u8> = vec![];
    enc_data.extend_from_slice(&DATA[..]);
    let tag = lsk
        .seal_in_place_separate_tag(
            aead::Nonce::assume_unique_for_key(NONCE),
            aead::Aad::from(AAD),
            &mut enc_data,
        )
        .unwrap();

    let mut ring_enc_data: Vec<u8> = vec![];
    ring_enc_data.extend_from_slice(&DATA[..]);
    let ring_tag = ring_lsk
        .seal_in_place_separate_tag(
            ring::aead::Nonce::assume_unique_for_key(NONCE),
            ring::aead::Aad::from(AAD),
            &mut ring_enc_data,
        )
        .unwrap();

    assert_eq!(ring_tag.as_ref(), tag.as_ref());
    assert_eq!(ring_enc_data.as_slice(), enc_data.as_slice());

    let prefix_bytes: &[u8] = &NONCE[4..];

    let mut enc_data_tag_vec: Vec<u8> = vec![];
    append(&mut enc_data_tag_vec, prefix_bytes, &enc_data, tag.as_ref());

    let mut ring_enc_data_tag_vec: Vec<u8> = vec![];
    append(
        &mut ring_enc_data_tag_vec,
        prefix_bytes,
        &ring_enc_data,
        ring_tag.as_ref(),
    );

    assert_eq!(ring_enc_data_tag_vec, enc_data_tag_vec);

    let len = lsk
        .open_within(
            aead::Nonce::assume_unique_for_key(NONCE),
            aead::Aad::from(AAD),
            &mut enc_data_tag_vec[..],
            prefix_bytes.len()..,
        )
        .unwrap()
        .len();

    let ring_len = ring_lsk
        .open_within(
            ring::aead::Nonce::assume_unique_for_key(NONCE),
            ring::aead::Aad::from(AAD),
            &mut ring_enc_data_tag_vec[..],
            prefix_bytes.len()..,
        )
        .unwrap()
        .len();

    assert_eq!(
        ring_enc_data_tag_vec.truncate(ring_len),
        enc_data_tag_vec.truncate(len)
    );
}

fn make_key<K: aead::BoundKey<OneNonceSequence>>(
    algorithm: &'static aead::Algorithm,
    key: &[u8],
    nonce: aead::Nonce,
) -> K {
    let key = aead::UnboundKey::new(algorithm, key).unwrap();
    let nonce_sequence = OneNonceSequence::new(nonce);
    K::new(key, nonce_sequence)
}

fn make_less_safe_key(algorithm: &'static aead::Algorithm, key: &[u8]) -> aead::LessSafeKey {
    let key = aead::UnboundKey::new(algorithm, key).unwrap();
    aead::LessSafeKey::new(key)
}

struct OneNonceSequence(Option<aead::Nonce>);

impl OneNonceSequence {
    /// Constructs the sequence allowing `advance()` to be called
    /// `allowed_invocations` times.
    fn new(nonce: aead::Nonce) -> Self {
        Self(Some(nonce))
    }
}

impl aead::NonceSequence for OneNonceSequence {
    fn advance(&mut self) -> Result<aead::Nonce, error::Unspecified> {
        self.0.take().ok_or(error::Unspecified)
    }
}


// Copyright 2015-2017 Brian Smith.
// SPDX-License-Identifier: ISC
// Modifications copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC

extern crate alloc;

use aws_lc_rs::{agreement, rand};

#[test]
fn agree_ephemeral_e2e() {
    let rng = rand::SystemRandom::new();

    for algorithm in [
        &agreement::ECDH_P256,
        &agreement::ECDH_P384,
        &agreement::ECDH_P521,
        &agreement::X25519,
    ] {
        let alice_private_key = agreement::EphemeralPrivateKey::generate(algorithm, &rng).unwrap();
        let alice_public_key = alice_private_key.compute_public_key().unwrap();

        let bob_private_key = agreement::EphemeralPrivateKey::generate(algorithm, &rng).unwrap();
        let bob_public_key = bob_private_key.compute_public_key().unwrap();

        let alice_shared_secret = {
            let mut secret: Vec<u8> = vec![];

            agreement::agree_ephemeral(
                alice_private_key,
                &agreement::UnparsedPublicKey::new(algorithm, bob_public_key),
                ring::error::Unspecified,
                |value| {
                    secret.extend_from_slice(value);
                    Ok(())
                },
            )
            .unwrap();

            secret
        };

        let bob_shared_secret = {
            let mut secret: Vec<u8> = vec![];

            agreement::agree_ephemeral(
                bob_private_key,
                &agreement::UnparsedPublicKey::new(algorithm, alice_public_key),
                ring::error::Unspecified,
                |value| {
                    secret.extend_from_slice(value);
                    Ok(())
                },
            )
            .unwrap();

            secret
        };

        assert_eq!(alice_shared_secret.as_slice(), bob_shared_secret.as_slice());
    }
}


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

extern crate core;

use aws_lc_rs::{aead, error, test};

use aead::{
    Aad, Algorithm, BoundKey, Nonce, NonceSequence, OpeningKey, SealingKey, UnboundKey,
    AES_128_GCM, AES_256_GCM, CHACHA20_POLY1305,
};
use aws_lc_rs::test::from_hex;
use error::Unspecified;

struct NotANonce(Vec<u8>);

impl NotANonce {
    fn from(value: Vec<u8>) -> Self {
        NotANonce(value)
    }
}

impl NonceSequence for NotANonce {
    fn advance(&mut self) -> Result<Nonce, Unspecified> {
        let mut nonce = [0u8; aead::NONCE_LEN];
        nonce.copy_from_slice(&self.0[0..aead::NONCE_LEN]);
        Ok(Nonce::assume_unique_for_key(nonce))
    }
}

struct AeadConfig {
    algorithm: &'static Algorithm,
    key: Vec<u8>,
    nonce: Vec<u8>,
    aad: String,
}

impl AeadConfig {
    fn new(algorithm: &'static Algorithm, key: &[u8], nonce: &[u8], aad: &str) -> AeadConfig {
        AeadConfig {
            algorithm,
            key: Vec::from(key),
            nonce: Vec::from(nonce),
            aad: String::from(aad),
        }
    }

    fn key(&self) -> UnboundKey {
        UnboundKey::new(self.algorithm, &self.key).unwrap()
    }
    fn aad(&self) -> Aad<String> {
        Aad::from(self.aad.clone())
    }
    fn nonce(&self) -> impl NonceSequence {
        NotANonce::from(self.nonce.clone())
    }
}

#[test]
fn test_aes_128_gcm() {
    let config = AeadConfig::new(
        &AES_128_GCM,
        &from_hex("d480429666d48b400633921c5407d1d1").unwrap(),
        &from_hex("5bf11a0951f0bfc7ea5c9e58").unwrap(),
        std::str::from_utf8(&from_hex("").unwrap()).unwrap(),
    );
    let mut in_out = from_hex("").unwrap();
    test_aead_separate_in_place(&config, &mut in_out).unwrap();
    test_aead_append_within(&config, &mut in_out).unwrap();
}

#[test]
fn test_aes_256_gcm() {
    let config = AeadConfig::new(
        &AES_256_GCM,
        &from_hex("e5ac4a32c67e425ac4b143c83c6f161312a97d88d634afdf9f4da5bd35223f01").unwrap(),
        &from_hex("5bf11a0951f0bfc7ea5c9e58").unwrap(),
        "123456789abcdef",
    );
    let mut in_out = from_hex("123456789abcdef0").unwrap();

    test_aead_separate_in_place(&config, &mut in_out).unwrap();
    test_aead_append_within(&config, &mut in_out).unwrap();
}

#[test]
fn test_chacha20_poly1305() {
    let config = AeadConfig::new(
        &CHACHA20_POLY1305,
        &from_hex("808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f").unwrap(),
        &from_hex("070000004041424344454647").unwrap(),
        "123456789abcdef",
    );
    let mut in_out = from_hex("123456789abcdef0").unwrap();

    #[cfg(feature = "alloc")]
    test_aead_separate_in_place(&config, &mut in_out).unwrap();
    test_aead_append_within(&config, &mut in_out).unwrap();
}

fn test_aead_separate_in_place(
    config: &AeadConfig,
    in_out: &mut Vec<u8>,
) -> Result<Vec<u8>, String> {
    let mut sealing_key = SealingKey::new(config.key(), config.nonce());
    let mut opening_key = OpeningKey::new(config.key(), config.nonce());

    println!("Sealing Key: {sealing_key:?}");
    println!("Opening Key: {opening_key:?}");

    let plaintext = in_out.clone();
    println!("Plaintext: {plaintext:?}");
    let tag = sealing_key
        .seal_in_place_separate_tag(config.aad(), in_out.as_mut_slice())
        .map_err(|x| x.to_string())?;

    let cipher_text = in_out.clone();
    println!("Ciphertext: {cipher_text:?}");
    if !plaintext.is_empty() {
        assert_ne!(plaintext, cipher_text);
    }
    println!("Tag: {:?}", tag.as_ref());

    in_out.extend(tag.as_ref());
    let result_plaintext = opening_key
        .open_in_place(config.aad(), in_out)
        .map_err(|x| x.to_string())?;

    assert_eq!(plaintext, result_plaintext);

    println!("Roundtrip: {result_plaintext:?}");

    Ok(Vec::from(result_plaintext))
}

fn test_aead_append_within(config: &AeadConfig, in_out: &mut [u8]) -> Result<Vec<u8>, String> {
    let mut sealing_key = SealingKey::new(config.key(), config.nonce());
    let mut opening_key = OpeningKey::new(config.key(), config.nonce());

    println!("Sealing Key: {sealing_key:?}");
    println!("Opening Key: {opening_key:?}");

    let plaintext = in_out.to_owned();
    println!("Plaintext: {plaintext:?}");
    let mut sized_in_out = in_out.to_vec();
    #[allow(deprecated)]
    sealing_key
        .seal_in_place(config.aad(), &mut sized_in_out)
        .map_err(|x| x.to_string())?;

    let (cipher_text, tag_value) = sized_in_out.split_at_mut(plaintext.len());

    if !plaintext.is_empty() {
        assert_ne!(plaintext, cipher_text);
    }
    println!("Ciphertext: {cipher_text:?}");
    println!("Tag: {tag_value:?}");

    let result_plaintext = opening_key
        .open_within(config.aad(), &mut sized_in_out, 0..)
        .map_err(|x| x.to_string())?;

    assert_eq!(plaintext, result_plaintext);

    println!("Roundtrip: {result_plaintext:?}");

    Ok(Vec::from(result_plaintext))
}

#[test]
fn test_types() {
    test::compile_time_assert_send::<Algorithm>();
    test::compile_time_assert_sync::<Algorithm>();
    test::compile_time_assert_eq::<Algorithm>();

    test::compile_time_assert_send::<SealingKey<NotANonce>>();
    test::compile_time_assert_sync::<SealingKey<NotANonce>>();

    test::compile_time_assert_send::<OpeningKey<NotANonce>>();
    test::compile_time_assert_sync::<OpeningKey<NotANonce>>();
}

/*
}}}

mod test_aead {
    test_aead!(ring);
    test_aead!(aws_lc_rs);
}
*/


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

use aws_lc_rs::test::from_hex;

use aws_lc_rs::aead;

#[test]
fn test_openssh() {
    let key_bytes: [u8; aead::chacha20_poly1305_openssh::KEY_LEN] =
        from_hex("247a041f6780baf0de3741aa4436024b6a5169b0eab8a090a291f5900bf566a54ac2c64c0f38cab14a143111bc39d1873013f52f2e92062e375c1a5378ad9b32").unwrap().try_into().unwrap();
    let plaintext = from_hex("000000100f000000000000000000000000000000").unwrap();
    let sequence_num = 94;
    let ciphertext = from_hex("c87186a24d89e672f37df98a95d0a0653e9f0fe4").unwrap();
    let expected_tag = from_hex("075bc727e855c8d487bb3060c42267cd").unwrap();

    let mut tag = [0u8; aead::chacha20_poly1305_openssh::TAG_LEN];
    let mut s_in_out = plaintext.clone();

    let s_key = aead::chacha20_poly1305_openssh::SealingKey::new(&key_bytes);
    s_key.seal_in_place(sequence_num, &mut s_in_out[..], &mut tag);
    assert_eq!(&ciphertext, &s_in_out);
    assert_eq!(&expected_tag, &tag);
    let o_key = aead::chacha20_poly1305_openssh::OpeningKey::new(&key_bytes);

    {
        let o_result = o_key.open_in_place(sequence_num, &mut s_in_out[..], &tag);
        assert_eq!(o_result, Ok(&plaintext[4..]));
    }
    assert_eq!(&s_in_out[..4], &ciphertext[..4]);
    assert_eq!(&s_in_out[4..], &plaintext[4..]);
}


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

//use ring::{aead, error};
use aws_lc_rs::aead;

use aead::quic;
use aws_lc_rs::test::from_dirty_hex;

#[test]
fn test_quic_aes_128_gcm() {
    let key_bytes = from_dirty_hex("e8904ecc2e37a6e4cc02271e319c804b");
    let sample = from_dirty_hex("13484ec85dc4d36349697c7d4ea1a159");
    let mask = from_dirty_hex("67387ebf3a");

    let key = quic::HeaderProtectionKey::new(&quic::AES_128, &key_bytes).unwrap();

    assert_eq!(mask.as_ref(), key.new_mask(&sample).unwrap());
}

#[test]
fn test_quic_aes_256_gcm() {
    let key_bytes =
        from_dirty_hex("85af7213814aec7b92ace6284a906643912ec8853d00d158a927b8697c7ff585");
    let sample = from_dirty_hex("82a0db90f4cee12fa4afeddb74396cf6");
    let mask = from_dirty_hex("670897adf5");

    let key = quic::HeaderProtectionKey::new(&quic::AES_256, &key_bytes).unwrap();

    assert_eq!(mask.as_ref(), key.new_mask(&sample).unwrap());
}

#[test]
fn test_quic_chacha20() {
    let key_bytes =
        from_dirty_hex("59bdff7a5bcdaacf319d99646c6273ad96687d2c74ace678f15a1c710675bb23");
    let sample = from_dirty_hex("215a7c1688b4ab7d830dcd052aef9f3c");
    let mask = from_dirty_hex("6409a6196d");

    let key = quic::HeaderProtectionKey::new(&quic::CHACHA20, &key_bytes).unwrap();

    assert_eq!(mask.as_ref(), key.new_mask(&sample).unwrap());
}


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

use aws_lc_rs::cipher::{
    DecryptingKey, EncryptingKey, EncryptionContext, OperatingMode, PaddedBlockDecryptingKey,
    PaddedBlockEncryptingKey, UnboundCipherKey, AES_128, AES_256,
};
use aws_lc_rs::iv::FixedLength;
use aws_lc_rs::test::from_hex;

macro_rules! padded_cipher_kat {
    ($name:ident, $alg:expr, $mode:expr, $constructor:ident, $key:literal, $iv: literal, $plaintext:literal, $ciphertext:literal) => {
        #[test]
        fn $name() {
            let key = from_hex($key).unwrap();
            let input = from_hex($plaintext).unwrap();
            let expected_ciphertext = from_hex($ciphertext).unwrap();

            let iv = from_hex($iv).unwrap();
            let fixed_iv = FixedLength::try_from(iv.as_slice()).unwrap();
            let context = EncryptionContext::Iv128(fixed_iv);

            let unbound_key = UnboundCipherKey::new($alg, &key).unwrap();

            let encrypting_key = PaddedBlockEncryptingKey::$constructor(unbound_key).unwrap();
            assert_eq!($mode, encrypting_key.mode());
            assert_eq!($alg, encrypting_key.algorithm());
            let mut in_out = input.clone();
            let context = encrypting_key
                .less_safe_encrypt(&mut in_out, context)
                .unwrap();
            assert_eq!(expected_ciphertext.as_slice(), in_out.as_slice());

            let unbound_key2 = UnboundCipherKey::new($alg, &key).unwrap();
            let decrypting_key = PaddedBlockDecryptingKey::$constructor(unbound_key2).unwrap();
            assert_eq!($mode, decrypting_key.mode());
            assert_eq!($alg, decrypting_key.algorithm());
            let plaintext = decrypting_key.decrypt(&mut in_out, context).unwrap();
            assert_eq!(input.as_slice(), plaintext);
        }
    };
}

macro_rules! cipher_kat {
    ($name:ident, $alg:expr, $mode:expr, $constructor:ident, $key:literal, $iv: literal, $plaintext:literal, $ciphertext:literal) => {
        #[test]
        fn $name() {
            let key = from_hex($key).unwrap();
            let input = from_hex($plaintext).unwrap();
            let expected_ciphertext = from_hex($ciphertext).unwrap();

            let iv = from_hex($iv).unwrap();
            let fixed_iv = FixedLength::try_from(iv.as_slice()).unwrap();
            let context = EncryptionContext::Iv128(fixed_iv);

            let unbound_key = UnboundCipherKey::new($alg, &key).unwrap();

            let encrypting_key = EncryptingKey::$constructor(unbound_key).unwrap();
            assert_eq!($mode, encrypting_key.mode());
            assert_eq!($alg, encrypting_key.algorithm());
            let mut in_out = input.clone();
            let context = encrypting_key
                .less_safe_encrypt(in_out.as_mut_slice(), context)
                .unwrap();
            assert_eq!(expected_ciphertext.as_slice(), in_out);

            let unbound_key2 = UnboundCipherKey::new($alg, &key).unwrap();
            let decrypting_key = DecryptingKey::$constructor(unbound_key2).unwrap();
            assert_eq!($mode, decrypting_key.mode());
            assert_eq!($alg, decrypting_key.algorithm());
            let plaintext = decrypting_key.decrypt(&mut in_out, context).unwrap();
            assert_eq!(input.as_slice(), plaintext);
        }
    };
}

macro_rules! padded_cipher_rt {
    ($name:ident, $alg:expr, $mode:expr, $constructor:ident, $key:literal, $plaintext:literal) => {
        #[test]
        fn $name() {
            let key = from_hex($key).unwrap();
            let input = from_hex($plaintext).unwrap();
            let unbound_key = UnboundCipherKey::new($alg, &key).unwrap();

            let encrypting_key = PaddedBlockEncryptingKey::$constructor(unbound_key).unwrap();
            assert_eq!($mode, encrypting_key.mode());
            assert_eq!($alg, encrypting_key.algorithm());
            let mut in_out = input.clone();
            let context = encrypting_key.encrypt(&mut in_out).unwrap();

            let unbound_key2 = UnboundCipherKey::new($alg, &key).unwrap();
            let decrypting_key = PaddedBlockDecryptingKey::$constructor(unbound_key2).unwrap();
            assert_eq!($mode, decrypting_key.mode());
            assert_eq!($alg, decrypting_key.algorithm());
            let plaintext = decrypting_key.decrypt(&mut in_out, context).unwrap();
            assert_eq!(input.as_slice(), plaintext);
        }
    };
}

macro_rules! cipher_rt {
    ($name:ident, $alg:expr, $mode:expr, $constructor:ident, $key:literal, $plaintext:literal) => {
        #[test]
        fn $name() {
            let key = from_hex($key).unwrap();
            let input = from_hex($plaintext).unwrap();
            let unbound_key = UnboundCipherKey::new($alg, &key).unwrap();

            let encrypting_key = EncryptingKey::$constructor(unbound_key).unwrap();
            assert_eq!($mode, encrypting_key.mode());
            assert_eq!($alg, encrypting_key.algorithm());
            let mut in_out = input.clone();
            let context = encrypting_key.encrypt(in_out.as_mut_slice()).unwrap();

            let unbound_key2 = UnboundCipherKey::new($alg, &key).unwrap();
            let decrypting_key = DecryptingKey::$constructor(unbound_key2).unwrap();
            assert_eq!($mode, decrypting_key.mode());
            assert_eq!($alg, decrypting_key.algorithm());
            let plaintext = decrypting_key.decrypt(&mut in_out, context).unwrap();
            assert_eq!(input.as_slice(), plaintext);
        }
    };
}

padded_cipher_kat!(
    test_kat_aes_128_cbc_16_bytes,
    &AES_128,
    OperatingMode::CBC,
    cbc_pkcs7,
    "000102030405060708090a0b0c0d0e0f",
    "00000000000000000000000000000000",
    "00112233445566778899aabbccddeeff",
    "69c4e0d86a7b0430d8cdb78070b4c55a9e978e6d16b086570ef794ef97984232"
);

padded_cipher_kat!(
    test_kat_aes_256_cbc_15_bytes,
    &AES_256,
    OperatingMode::CBC,
    cbc_pkcs7,
    "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
    "00000000000000000000000000000000",
    "00112233445566778899aabbccddee",
    "2ddfb635a651a43f582997966840ca0c"
);

cipher_kat!(
    test_kat_aes_128_ctr_16_bytes,
    &AES_128,
    OperatingMode::CTR,
    ctr,
    "000102030405060708090a0b0c0d0e0f",
    "00000000000000000000000000000000",
    "00112233445566778899aabbccddeeff",
    "c6b01904c3da3df5e7d62bd96d153686"
);

cipher_kat!(
    test_kat_aes_256_ctr_15_bytes,
    &AES_256,
    OperatingMode::CTR,
    ctr,
    "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
    "00000000000000000000000000000000",
    "00112233445566778899aabbccddee",
    "f28122856e1cf9a7216a30d111f399"
);

cipher_kat!(
    test_kat_aes_128_ctr_15_bytes,
    &AES_128,
    OperatingMode::CTR,
    ctr,
    "244828580821c1652582c76e34d299f5",
    "093145d5af233f46072a5eb5adc11aa1",
    "3ee38cec171e6cf466bf0df98aa0e1",
    "bd7d928f60e3422d96b3f8cd614eb2"
);

cipher_kat!(
    test_kat_aes_256_ctr_15_bytes_2,
    &AES_256,
    OperatingMode::CTR,
    ctr,
    "0857db8240ea459bdf660b4cced66d1f2d3734ff2de7b81e92740e65e7cc6a1d",
    "f028ecb053f801102d11fccc9d303a27",
    "eca7285d19f3c20e295378460e8729",
    "b5098e5e788de6ac2f2098eb2fc6f8"
);

padded_cipher_kat!(
    test_kat_aes_128_cbc_15_bytes,
    &AES_128,
    OperatingMode::CBC,
    cbc_pkcs7,
    "053304bb3899e1d99db9d29343ea782d",
    "b5313560244a4822c46c2a0c9d0cf7fd",
    "a3e4c990356c01f320043c3d8d6f43",
    "ad96993f248bd6a29760ec7ccda95ee1"
);

padded_cipher_kat!(
    test_kat_aes_128_cbc_16_bytes_2,
    &AES_128,
    OperatingMode::CBC,
    cbc_pkcs7,
    "95af71f1c63e4a1d0b0b1a27fb978283",
    "89e40797dca70197ff87d3dbb0ef2802",
    "aece7b5e3c3df1ffc9802d2dfe296dc7",
    "301b5dab49fb11e919d0d39970d06739301919743304f23f3cbc67d28564b25b"
);

padded_cipher_kat!(
    test_kat_aes_256_cbc_16_bytes,
    &AES_256,
    OperatingMode::CBC,
    cbc_pkcs7,
    "d4a8206dcae01242f9db79a4ecfe277d0f7bb8ccbafd8f9809adb39f35aa9b41",
    "24f6076548fb9d93c8f7ed9f6e661ef9",
    "a39c1fdf77ea3e1f18178c0ec237c70a",
    "f1af484830a149ee0387b854d65fe87ca0e62efc1c8e6909d4b9ab8666470453"
);

padded_cipher_rt!(
    test_rt_aes_128_cbc_16_bytes,
    &AES_128,
    OperatingMode::CBC,
    cbc_pkcs7,
    "000102030405060708090a0b0c0d0e0f",
    "00112233445566778899aabbccddeeff"
);

padded_cipher_rt!(
    test_rt_aes_256_cbc_15_bytes,
    &AES_256,
    OperatingMode::CBC,
    cbc_pkcs7,
    "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
    "00112233445566778899aabbccddee"
);

cipher_rt!(
    test_rt_aes_128_ctr_16_bytes,
    &AES_128,
    OperatingMode::CTR,
    ctr,
    "000102030405060708090a0b0c0d0e0f",
    "00112233445566778899aabbccddeeff"
);

cipher_rt!(
    test_rt_aes_128_ctr_17_bytes,
    &AES_128,
    OperatingMode::CTR,
    ctr,
    "000102030405060708090a0b0c0d0e0f",
    "00112233445566778899aabbccddeeff01"
);

cipher_rt!(
    test_rt_aes_256_ctr_15_bytes,
    &AES_256,
    OperatingMode::CTR,
    ctr,
    "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
    "00112233445566778899aabbccddee"
);

cipher_rt!(
    test_rt_aes_128_ctr_15_bytes,
    &AES_128,
    OperatingMode::CTR,
    ctr,
    "244828580821c1652582c76e34d299f5",
    "3ee38cec171e6cf466bf0df98aa0e1"
);

cipher_rt!(
    test_rt_aes_256_ctr_15_bytes_2,
    &AES_256,
    OperatingMode::CTR,
    ctr,
    "0857db8240ea459bdf660b4cced66d1f2d3734ff2de7b81e92740e65e7cc6a1d",
    "eca7285d19f3c20e295378460e8729"
);

cipher_rt!(
    test_rt_aes_256_ctr_17_bytes,
    &AES_256,
    OperatingMode::CTR,
    ctr,
    "0857db8240ea459bdf660b4cced66d1f2d3734ff2de7b81e92740e65e7cc6a1d",
    "eca7285d19f3c20e295378460e872934"
);

padded_cipher_rt!(
    test_rt_aes_128_cbc_15_bytes,
    &AES_128,
    OperatingMode::CBC,
    cbc_pkcs7,
    "053304bb3899e1d99db9d29343ea782d",
    "a3e4c990356c01f320043c3d8d6f43"
);

padded_cipher_rt!(
    test_rt_aes_128_cbc_16_bytes_2,
    &AES_128,
    OperatingMode::CBC,
    cbc_pkcs7,
    "95af71f1c63e4a1d0b0b1a27fb978283",
    "aece7b5e3c3df1ffc9802d2dfe296dc7"
);

padded_cipher_rt!(
    test_rt_128_cbc_17_bytes,
    &AES_128,
    OperatingMode::CBC,
    cbc_pkcs7,
    "95af71f1c63e4a1d0b0b1a27fb978283",
    "aece7b5e3c3df1ffc9802d2dfe296dc734"
);

padded_cipher_rt!(
    test_rt_aes_256_cbc_16_bytes,
    &AES_256,
    OperatingMode::CBC,
    cbc_pkcs7,
    "d4a8206dcae01242f9db79a4ecfe277d0f7bb8ccbafd8f9809adb39f35aa9b41",
    "a39c1fdf77ea3e1f18178c0ec237c70a"
);

padded_cipher_rt!(
    test_rt_aes_256_cbc_17_bytes,
    &AES_256,
    OperatingMode::CBC,
    cbc_pkcs7,
    "d4a8206dcae01242f9db79a4ecfe277d0f7bb8ccbafd8f9809adb39f35aa9b41",
    "a39c1fdf77ea3e1f18178c0ec237c70a34"
);


// Copyright 2015-2017 Brian Smith.
// SPDX-License-Identifier: ISC
// Modifications copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC

use aws_lc_rs::{digest, test, test_file};

/// Test vectors from `BoringSSL`, `Go`, and other sources.
#[test]
fn digest_misc() {
    test::run(test_file!("data/digest_tests.txt"), |section, test_case| {
        assert_eq!(section, "");
        let digest_alg = test_case.consume_digest_alg("Hash").unwrap();
        let input = test_case.consume_bytes("Input");
        let repeat = test_case.consume_usize("Repeat");
        let expected = test_case.consume_bytes("Output");

        let mut ctx = digest::Context::new(digest_alg);
        let mut data = Vec::new();
        for _ in 0..repeat {
            ctx.update(&input);
            data.extend(&input);
        }
        let actual_from_chunks = ctx.finish();
        assert_eq!(&expected, &actual_from_chunks.as_ref());

        let actual_from_one_shot = digest::digest(digest_alg, &data);
        assert_eq!(&expected, &actual_from_one_shot.as_ref());

        Ok(())
    });
}

mod digest_shavs {
    use aws_lc_rs::{digest, test};

    #[derive(Clone, Copy, PartialEq, Eq)]
    enum TestType {
        Fips202,
        Fips180_4,
    }

    fn run_known_answer_test(
        digest_alg: &'static digest::Algorithm,
        test_file: test::File,
        test_type: TestType,
    ) {
        let section_name = &match test_type {
            TestType::Fips180_4 => format!("L = {}", digest_alg.output_len),
            TestType::Fips202 => format!("L = {}", 8 * digest_alg.output_len),
        };
        test::run(test_file, |section, test_case| {
            assert_eq!(section_name, section);
            let len_bits = test_case.consume_usize("Len");

            let mut msg = test_case.consume_bytes("Msg");
            // The "msg" field contains the dummy value "00" when the
            // length is zero.
            if len_bits == 0 {
                assert_eq!(msg, &[0u8]);
                msg.truncate(0);
            }
            assert_eq!(msg.len().checked_mul(8).unwrap(), len_bits);
            let expected = test_case.consume_bytes("MD");
            let actual = digest::digest(digest_alg, &msg);
            assert_eq!(&expected, &actual.as_ref());

            Ok(())
        });
    }

    macro_rules! shavs_tests {
        ( $file_name:ident, $algorithm_name:ident, $test_type:expr ) => {
            #[allow(non_snake_case)]
            mod $algorithm_name {
                use super::{
                    run_known_answer_test, run_monte_carlo_fips_180_4_test,
                    run_monte_carlo_fips_202_test, TestType,
                };
                use aws_lc_rs::{digest, test_file};

                #[cfg(target_arch = "wasm32")]
                use wasm_bindgen_test::wasm_bindgen_test as test;

                #[test]
                fn short_msg_known_answer_test() {
                    run_known_answer_test(
                        &digest::$algorithm_name,
                        test_file!(concat!(
                            "../third_party/NIST/SHAVS/",
                            stringify!($file_name),
                            "ShortMsg.rsp"
                        )),
                        $test_type,
                    );
                }
                #[test]
                fn long_msg_known_answer_test() {
                    run_known_answer_test(
                        &digest::$algorithm_name,
                        test_file!(concat!(
                            "../third_party/NIST/SHAVS/",
                            stringify!($file_name),
                            "LongMsg.rsp"
                        )),
                        $test_type,
                    );
                }

                #[test]
                fn monte_carlo_test() {
                    if $test_type == TestType::Fips180_4 {
                        run_monte_carlo_fips_180_4_test(
                            &digest::$algorithm_name,
                            test_file!(concat!(
                                "../third_party/NIST/SHAVS/",
                                stringify!($file_name),
                                "Monte.rsp"
                            )),
                        );
                    } else {
                        run_monte_carlo_fips_202_test(
                            &digest::$algorithm_name,
                            test_file!(concat!(
                                "../third_party/NIST/SHAVS/",
                                stringify!($file_name),
                                "Monte.rsp"
                            )),
                        );
                    }
                }
            }
        };
    }

    #[allow(clippy::cast_sign_loss)]
    fn run_monte_carlo_fips_180_4_test(
        digest_alg: &'static digest::Algorithm,
        test_file: test::File,
    ) {
        let section_name = format!("L = {}", digest_alg.output_len);

        let mut expected_count: isize = -1;
        let mut seed = Vec::with_capacity(digest_alg.output_len);

        test::run(test_file, |section, test_case| {
            assert_eq!(section_name, section);

            if expected_count == -1 {
                seed.extend(test_case.consume_bytes("Seed"));
                expected_count = 0;
                return Ok(());
            }

            assert!(expected_count >= 0);
            let actual_count = test_case.consume_usize("COUNT");
            assert_eq!(expected_count as usize, actual_count);
            expected_count += 1;

            let expected_md = test_case.consume_bytes("MD");

            let mut mds = Vec::with_capacity(4);
            mds.push(seed.clone());
            mds.push(seed.clone());
            mds.push(seed.clone());
            for _ in 0..1000 {
                let mut ctx = digest::Context::new(digest_alg);
                ctx.update(&mds[0]);
                ctx.update(&mds[1]);
                ctx.update(&mds[2]);
                let md_i = ctx.finish();
                #[allow(let_underscore_drop)]
                let _: Vec<u8> = mds.remove(0);
                mds.push(Vec::from(md_i.as_ref()));
            }
            let md_j = mds.last().unwrap();
            assert_eq!(&expected_md, md_j);
            seed = md_j.clone();

            Ok(())
        });

        assert_eq!(expected_count, 100);
    }

    #[allow(clippy::cast_sign_loss)]
    fn run_monte_carlo_fips_202_test(
        digest_alg: &'static digest::Algorithm,
        test_file: test::File,
    ) {
        let section_name = format!("L = {}", 8 * digest_alg.output_len);

        let mut expected_count: isize = -1;
        let mut seed = Vec::with_capacity(digest_alg.output_len);

        test::run(test_file, |section, test_case| {
            assert_eq!(section_name, section);

            if expected_count == -1 {
                seed.extend(test_case.consume_bytes("Seed"));
                expected_count = 0;
                return Ok(());
            }

            assert!(expected_count >= 0);
            let actual_count = test_case.consume_usize("COUNT");
            assert_eq!(expected_count as usize, actual_count);
            expected_count += 1;

            let expected_md = test_case.consume_bytes("MD");

            let mut prev_md = seed.clone();
            for _ in 0..1000 {
                let mut ctx = digest::Context::new(digest_alg);
                ctx.update(&prev_md);
                prev_md = Vec::from(ctx.finish().as_ref());
            }

            assert_eq!(&expected_md, &prev_md);
            seed = prev_md;

            Ok(())
        });

        assert_eq!(expected_count, 100);
    }

    shavs_tests!(SHA1, SHA1_FOR_LEGACY_USE_ONLY, TestType::Fips180_4);
    shavs_tests!(SHA256, SHA256, TestType::Fips180_4);
    shavs_tests!(SHA384, SHA384, TestType::Fips180_4);
    shavs_tests!(SHA512, SHA512, TestType::Fips180_4);
    shavs_tests!(SHA3_256, SHA3_256, TestType::Fips202);
    shavs_tests!(SHA3_384, SHA3_384, TestType::Fips202);
    shavs_tests!(SHA3_512, SHA3_512, TestType::Fips202);
}

/// Test some ways in which `Context::update` and/or `Context::finish`
/// could go wrong by testing every combination of updating three inputs
/// that vary from zero bytes to one byte larger than the block length.
///
/// These are not run in dev (debug) builds because they are too slow.
macro_rules! test_i_u_f {
    ( $test_name:ident, $alg:expr) => {
        #[cfg(not(debug_assertions))]
        // TODO: Get this working on WebAssembly
        #[cfg(not(target_arch = "wasm32"))]
        #[test]
        fn $test_name() {
            let mut input = [0; (digest::MAX_BLOCK_LEN + 1) * 3];
            let max = $alg.block_len + 1;
            for i in 0..(max * 3) {
                input[i] = (i & 0xff) as u8;
            }

            for i in 0..max {
                for j in 0..max {
                    for k in 0..max {
                        let part1 = &input[..i];
                        let part2 = &input[i..(i + j)];
                        let part3 = &input[(i + j)..(i + j + k)];

                        let mut ctx = digest::Context::new(&$alg);
                        ctx.update(part1);
                        ctx.update(part2);
                        ctx.update(part3);
                        let i_u_f = ctx.finish();

                        let one_shot = digest::digest(&$alg, &input[..(i + j + k)]);

                        assert_eq!(i_u_f.as_ref(), one_shot.as_ref());
                    }
                }
            }
        }
    };
}
test_i_u_f!(digest_test_i_u_f_sha1, digest::SHA1_FOR_LEGACY_USE_ONLY);
test_i_u_f!(digest_test_i_u_f_sha256, digest::SHA256);
test_i_u_f!(digest_test_i_u_f_sha384, digest::SHA384);
test_i_u_f!(digest_test_i_u_f_sha512, digest::SHA512);

/// See <https://bugzilla.mozilla.org/show_bug.cgi?id=610162/>. This tests the
/// calculation of 8GB of the byte 123.
///
/// You can verify the expected values in many ways. One way is
/// `python ~/p/write_big.py`, where `write_big.py` is:
///
/// ```python
/// chunk = bytearray([123] * (16 * 1024))
/// with open('tempfile', 'w') as f:
/// for i in xrange(0, 8 * 1024 * 1024 * 1024, len(chunk)):
///     f.write(chunk)
/// ```
/// Then:
///
/// ```sh
/// sha1sum -b tempfile
/// sha256sum -b tempfile
/// sha384sum -b tempfile
/// sha512sum -b tempfile
/// ```
///
/// This is not run in dev (debug) builds because it is too slow.
macro_rules! test_large_digest {
    ( $test_name:ident, $alg:expr, $len:expr, $expected:expr) => {
        // TODO: get this working on WebAssembly.
        #[cfg(not(debug_assertions))]
        #[cfg(not(target_arch = "wasm32"))]
        #[test]
        fn $test_name() {
            let chunk = vec![123u8; 16 * 1024];
            let chunk_len = chunk.len() as u64;
            let mut ctx = digest::Context::new(&$alg);
            let mut hashed = 0u64;
            loop {
                ctx.update(&chunk);
                hashed += chunk_len;
                if hashed >= 8u64 * 1024 * 1024 * 1024 {
                    break;
                }
            }
            let calculated = ctx.finish();
            let expected: [u8; $len] = $expected;
            assert_eq!(&expected[..], calculated.as_ref());
        }
    };
}

// XXX: This test is too slow on Android ARM.
#[cfg(any(not(target_os = "android"), not(target_arch = "arm")))]
test_large_digest!(
    digest_test_large_digest_sha1,
    digest::SHA1_FOR_LEGACY_USE_ONLY,
    160 / 8,
    [
        0xCA, 0xC3, 0x4C, 0x31, 0x90, 0x5B, 0xDE, 0x3B, 0xE4, 0x0D, 0x46, 0x6D, 0x70, 0x76, 0xAD,
        0x65, 0x3C, 0x20, 0xE4, 0xBD
    ]
);
test_large_digest!(
    digest_test_large_digest_sha256,
    digest::SHA256,
    256 / 8,
    [
        0x8D, 0xD1, 0x6D, 0xD8, 0xB2, 0x5A, 0x29, 0xCB, 0x7F, 0xB9, 0xAE, 0x86, 0x72, 0xE9, 0xCE,
        0xD6, 0x65, 0x4C, 0xB6, 0xC3, 0x5C, 0x58, 0x21, 0xA7, 0x07, 0x97, 0xC5, 0xDD, 0xAE, 0x5C,
        0x68, 0xBD
    ]
);
test_large_digest!(
    digest_test_large_digest_sha384,
    digest::SHA384,
    384 / 8,
    [
        0x3D, 0xFE, 0xC1, 0xA9, 0xD0, 0x9F, 0x08, 0xD5, 0xBB, 0xE8, 0x7C, 0x9E, 0xE0, 0x0A, 0x87,
        0x0E, 0xB0, 0xEA, 0x8E, 0xEA, 0xDB, 0x82, 0x36, 0xAE, 0x74, 0xCF, 0x9F, 0xDC, 0x86, 0x1C,
        0xE3, 0xE9, 0xB0, 0x68, 0xCD, 0x19, 0x3E, 0x39, 0x90, 0x02, 0xE1, 0x58, 0x5D, 0x66, 0xC4,
        0x55, 0x11, 0x9B
    ]
);
test_large_digest!(
    digest_test_large_digest_sha512,
    digest::SHA512,
    512 / 8,
    [
        0xFC, 0x8A, 0x98, 0x20, 0xFC, 0x82, 0xD8, 0x55, 0xF8, 0xFF, 0x2F, 0x6E, 0xAE, 0x41, 0x60,
        0x04, 0x08, 0xE9, 0x49, 0xD7, 0xCD, 0x1A, 0xED, 0x22, 0xEB, 0x55, 0xE1, 0xFD, 0x80, 0x50,
        0x3B, 0x01, 0x2F, 0xC6, 0xF4, 0x33, 0x86, 0xFB, 0x60, 0x75, 0x2D, 0xA5, 0xA9, 0x93, 0xE7,
        0x00, 0x45, 0xA8, 0x49, 0x1A, 0x6B, 0xEC, 0x9C, 0x98, 0xC8, 0x19, 0xA6, 0xA9, 0x88, 0x3E,
        0x2F, 0x09, 0xB9, 0x9A
    ]
);

// TODO: test_large_digest!(digest_test_large_digest_sha512_256,
//                            digest::SHA512_256, 256 / 8, [ ... ]);

#[test]
fn test_fmt_algorithm() {
    assert_eq!("SHA1", &format!("{:?}", digest::SHA1_FOR_LEGACY_USE_ONLY));
    assert_eq!("SHA256", &format!("{:?}", digest::SHA256));
    assert_eq!("SHA384", &format!("{:?}", digest::SHA384));
    assert_eq!("SHA512", &format!("{:?}", digest::SHA512));
    assert_eq!("SHA512_256", &format!("{:?}", digest::SHA512_256));
}

#[test]
fn digest_test_fmt() {
    assert_eq!(
        "SHA1:b7e23ec29af22b0b4e41da31e868d57226121c84",
        &format!(
            "{:?}",
            digest::digest(&digest::SHA1_FOR_LEGACY_USE_ONLY, b"hello, world")
        )
    );
    assert_eq!(
        "SHA256:09ca7e4eaa6e8ae9c7d261167129184883644d\
         07dfba7cbfbc4c8a2e08360d5b",
        &format!("{:?}", digest::digest(&digest::SHA256, b"hello, world"))
    );
    assert_eq!(
        "SHA384:1fcdb6059ce05172a26bbe2a3ccc88ed5a8cd5\
         fc53edfd9053304d429296a6da23b1cd9e5c9ed3bb34f0\
         0418a70cdb7e",
        &format!("{:?}", digest::digest(&digest::SHA384, b"hello, world"))
    );
    assert_eq!(
        "SHA512:8710339dcb6814d0d9d2290ef422285c9322b7\
         163951f9a0ca8f883d3305286f44139aa374848e4174f5\
         aada663027e4548637b6d19894aec4fb6c46a139fbf9",
        &format!("{:?}", digest::digest(&digest::SHA512, b"hello, world"))
    );

    assert_eq!(
        "SHA512_256:11f2c88c04f0a9c3d0970894ad2472505e\
         0bc6e8c7ec46b5211cd1fa3e253e62",
        &format!("{:?}", digest::digest(&digest::SHA512_256, b"hello, world"))
    );
}


// Copyright 2015-2016 Brian Smith.
// SPDX-License-Identifier: ISC
// Modifications copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC

use aws_lc_rs::{
    rand,
    signature::{self, KeyPair},
    test, test_file,
};
use mirai_annotations::unrecoverable;

#[test]
fn ecdsa_traits() {
    test::compile_time_assert_send::<signature::EcdsaKeyPair>();
    test::compile_time_assert_sync::<signature::EcdsaKeyPair>();
    test::compile_time_assert_send::<signature::Signature>();
    test::compile_time_assert_sync::<signature::Signature>();
    test::compile_time_assert_send::<signature::UnparsedPublicKey<&[u8]>>();
    test::compile_time_assert_sync::<signature::UnparsedPublicKey<&[u8]>>();
    test::compile_time_assert_send::<signature::UnparsedPublicKey<Vec<u8>>>();
    test::compile_time_assert_sync::<signature::UnparsedPublicKey<Vec<u8>>>();
}

#[test]
fn ecdsa_from_pkcs8_test() {
    test::run(
        test_file!("data/ecdsa_from_pkcs8_tests.txt"),
        |section, test_case| {
            assert_eq!(section, "");

            let curve_name = test_case.consume_string("Curve");
            let ((this_fixed, this_asn1), (other_fixed, other_asn1)) = match curve_name.as_str() {
                "P-256" => (
                    (
                        &signature::ECDSA_P256_SHA256_FIXED_SIGNING,
                        &signature::ECDSA_P256_SHA256_ASN1_SIGNING,
                    ),
                    (
                        &signature::ECDSA_P384_SHA384_FIXED_SIGNING,
                        &signature::ECDSA_P384_SHA384_ASN1_SIGNING,
                    ),
                ),
                "P-384" => (
                    (
                        &signature::ECDSA_P384_SHA384_FIXED_SIGNING,
                        &signature::ECDSA_P384_SHA384_ASN1_SIGNING,
                    ),
                    (
                        &signature::ECDSA_P256_SHA256_FIXED_SIGNING,
                        &signature::ECDSA_P256_SHA256_ASN1_SIGNING,
                    ),
                ),
                "P-521" => (
                    (
                        &signature::ECDSA_P521_SHA512_FIXED_SIGNING,
                        &signature::ECDSA_P521_SHA512_ASN1_SIGNING,
                    ),
                    (
                        &signature::ECDSA_P384_SHA384_FIXED_SIGNING,
                        &signature::ECDSA_P384_SHA384_ASN1_SIGNING,
                    ),
                ),
                _ => unreachable!(),
            };

            let input = test_case.consume_bytes("Input");

            let error = test_case.consume_optional_string("Error");

            match (
                signature::EcdsaKeyPair::from_pkcs8(this_fixed, &input),
                error.clone(),
            ) {
                (Ok(_), None) => (),
                (Err(e), None) => panic!(
                    "Failed with error \"{}\", but expected to succeed. Input: {}",
                    e,
                    test::to_hex(&input)
                ),
                (Ok(_), Some(e)) => panic!(
                    "Succeeded, but expected error \"{}\" - Input: {}",
                    e,
                    test::to_hex(&input)
                ),
                (Err(actual), Some(expected)) => assert_eq!(
                    format!("{actual}"),
                    expected,
                    "Input: {}",
                    test::to_hex(&input)
                ),
            };

            match (
                signature::EcdsaKeyPair::from_pkcs8(this_asn1, &input),
                error,
            ) {
                (Ok(_), None) => (),
                (Err(e), None) => {
                    unrecoverable!("Failed with error \"{}\", but expected to succeed", e);
                }
                (Ok(_), Some(e)) => unrecoverable!("Succeeded, but expected error \"{}\"", e),
                (Err(actual), Some(expected)) => assert_eq!(format!("{actual}"), expected),
            };

            assert!(
                signature::EcdsaKeyPair::from_pkcs8(other_fixed, &input).is_err(),
                "Input: {}",
                test::to_hex(&input)
            );
            assert!(
                signature::EcdsaKeyPair::from_pkcs8(other_asn1, &input).is_err(),
                "Input: {}",
                test::to_hex(&input)
            );

            Ok(())
        },
    );
}

// Verify that, at least, we generate PKCS#8 documents that we can read.
#[test]
fn ecdsa_generate_pkcs8_test() {
    let rng = rand::SystemRandom::new();

    for alg in &[
        &signature::ECDSA_P256_SHA256_ASN1_SIGNING,
        &signature::ECDSA_P256_SHA256_FIXED_SIGNING,
        &signature::ECDSA_P384_SHA384_ASN1_SIGNING,
        &signature::ECDSA_P384_SHA384_FIXED_SIGNING,
        &signature::ECDSA_P384_SHA3_384_ASN1_SIGNING,
        &signature::ECDSA_P384_SHA3_384_FIXED_SIGNING,
        &signature::ECDSA_P521_SHA512_FIXED_SIGNING,
        &signature::ECDSA_P521_SHA512_ASN1_SIGNING,
        &signature::ECDSA_P521_SHA3_512_FIXED_SIGNING,
        &signature::ECDSA_P521_SHA3_512_ASN1_SIGNING,
    ] {
        let pkcs8 = signature::EcdsaKeyPair::generate_pkcs8(alg, &rng).unwrap();
        println!();
        for b in pkcs8.as_ref() {
            print!("{:02x}", *b);
        }
        println!();
        println!();

        signature::EcdsaKeyPair::from_pkcs8(alg, pkcs8.as_ref()).unwrap();
    }
}

#[test]
fn signature_ecdsa_verify_asn1_test() {
    test_signature_ecdsa_verify_asn1(test_file!("data/ecdsa_verify_asn1_tests.txt"));
}

#[test]
fn signature_ecdsa_verify_asn1_sha3_test() {
    test_signature_ecdsa_verify_asn1(test_file!("data/ecdsa_verify_asn1_sha3_tests.txt"));
}

fn test_signature_ecdsa_verify_asn1(data_file: test::File) {
    test::run(data_file, |section, test_case| {
        assert_eq!(section, "");

        let curve_name = test_case.consume_string("Curve");
        let digest_name = test_case.consume_string("Digest");
        let msg = test_case.consume_bytes("Msg");
        let public_key = test_case.consume_bytes("Q");
        let sig = test_case.consume_bytes("Sig");
        let is_valid = test_case.consume_string("Result") == "P (0 )";

        let alg = match (curve_name.as_str(), digest_name.as_str()) {
            ("P-256", "SHA256") => &signature::ECDSA_P256_SHA256_ASN1,
            ("P-256", "SHA384") => &signature::ECDSA_P256_SHA384_ASN1,
            ("P-384", "SHA256") => &signature::ECDSA_P384_SHA256_ASN1,
            ("P-384", "SHA384") => &signature::ECDSA_P384_SHA384_ASN1,
            ("P-384", "SHA3-384") => &signature::ECDSA_P384_SHA3_384_ASN1,
            ("P-521", "SHA512") => &signature::ECDSA_P521_SHA512_ASN1,
            ("P-521", "SHA3-512") => &signature::ECDSA_P521_SHA3_512_ASN1,
            _ => {
                panic!("Unsupported curve+digest: {curve_name}+{digest_name}");
            }
        };

        let actual_result = signature::UnparsedPublicKey::new(alg, &public_key).verify(&msg, &sig);
        assert_eq!(actual_result.is_ok(), is_valid);

        Ok(())
    });
}

#[test]
fn signature_ecdsa_verify_fixed_test() {
    test_signature_ecdsa_verify_fixed(test_file!("data/ecdsa_verify_fixed_tests.txt"));
}

#[test]
fn signature_ecdsa_verify_sha3_fixed_test() {
    test_signature_ecdsa_verify_fixed(test_file!("data/ecdsa_verify_fixed_sha3_tests.txt"));
}

fn test_signature_ecdsa_verify_fixed(data_file: test::File) {
    test::run(data_file, |section, test_case| {
        assert_eq!(section, "");

        let curve_name = test_case.consume_string("Curve");
        let digest_name = test_case.consume_string("Digest");

        let msg = test_case.consume_bytes("Msg");
        let public_key = test_case.consume_bytes("Q");
        let sig = test_case.consume_bytes("Sig");
        let expected_result = test_case.consume_string("Result");

        let alg = match (curve_name.as_str(), digest_name.as_str()) {
            ("P-256", "SHA256") => &signature::ECDSA_P256_SHA256_FIXED,
            ("P-384", "SHA384") => &signature::ECDSA_P384_SHA384_FIXED,
            ("P-384", "SHA3-384") => &signature::ECDSA_P384_SHA3_384_FIXED,
            ("P-521", "SHA512") => &signature::ECDSA_P521_SHA512_FIXED,
            ("P-521", "SHA3-512") => &signature::ECDSA_P521_SHA3_512_FIXED,
            _ => {
                unrecoverable!("Unsupported curve+digest: {}+{}", curve_name, digest_name);
            }
        };

        let is_valid = expected_result == "P (0 )";

        let actual_result = signature::UnparsedPublicKey::new(alg, &public_key).verify(&msg, &sig);
        assert_eq!(actual_result.is_ok(), is_valid);

        Ok(())
    });
}

#[test]
fn ecdsa_test_public_key_coverage() {
    const PRIVATE_KEY: &[u8] = include_bytes!("data/ecdsa_test_private_key_p256.p8");
    const PUBLIC_KEY: &[u8] = include_bytes!("data/ecdsa_test_public_key_p256.der");
    const PUBLIC_KEY_DEBUG: &str = include_str!("data/ecdsa_test_public_key_p256_debug.txt");

    let key_pair = signature::EcdsaKeyPair::from_pkcs8(
        &signature::ECDSA_P256_SHA256_FIXED_SIGNING,
        PRIVATE_KEY,
    )
    .unwrap();

    // Test `AsRef<[u8]>`
    assert_eq!(key_pair.public_key().as_ref(), PUBLIC_KEY);

    // Test `Clone`.
    #[allow(let_underscore_drop, clippy::let_underscore_untyped)]
    let _ = key_pair.public_key().clone();

    // Test `Copy`.
    #[allow(let_underscore_drop)]
    let _: <signature::EcdsaKeyPair as KeyPair>::PublicKey = *key_pair.public_key();

    // Test `Debug`.
    assert_eq!(PUBLIC_KEY_DEBUG, format!("{:?}", key_pair.public_key()));
    assert_eq!(
        format!("EcdsaKeyPair {{ public_key: {:?} }}", key_pair.public_key()),
        format!("{key_pair:?}")
    );
}

#[test]
fn signature_ecdsa_sign_fixed_sign_and_verify_test() {
    test_signature_ecdsa_sign_fixed_sign_and_verify(test_file!("data/ecdsa_sign_fixed_tests.txt"));
}

#[test]
fn signature_ecdsa_sign_fixed_sign_and_verify_sha3_test() {
    test_signature_ecdsa_sign_fixed_sign_and_verify(test_file!(
        "data/ecdsa_sign_fixed_sha3_tests.txt"
    ));
}

// This test is not a known-answer test, though it re-uses the known-answer
// test vectors. Because the nonce is randomized, the signature will be
// different each time. Because of that, here we simply verify that the
// signature verifies correctly.
fn test_signature_ecdsa_sign_fixed_sign_and_verify(data_file: test::File) {
    let rng = rand::SystemRandom::new();

    test::run(data_file, |section, test_case| {
        assert_eq!(section, "");

        let curve_name = test_case.consume_string("Curve");
        let digest_name = test_case.consume_string("Digest");

        let msg = test_case.consume_bytes("Msg");
        let d = test_case.consume_bytes("d");
        let q = test_case.consume_bytes("Q");

        // Ignored since the actual signature will use a randomized nonce.
        let _k = test_case.consume_optional_bytes("k");
        let _expected_result = test_case.consume_bytes("Sig");

        let (signing_alg, verification_alg) = match (curve_name.as_str(), digest_name.as_str()) {
            ("P-256", "SHA256") => (
                &signature::ECDSA_P256_SHA256_FIXED_SIGNING,
                &signature::ECDSA_P256_SHA256_FIXED,
            ),
            ("P-384", "SHA384") => (
                &signature::ECDSA_P384_SHA384_FIXED_SIGNING,
                &signature::ECDSA_P384_SHA384_FIXED,
            ),
            ("P-384", "SHA3-384") => (
                &signature::ECDSA_P384_SHA3_384_FIXED_SIGNING,
                &signature::ECDSA_P384_SHA3_384_FIXED,
            ),
            ("P-521", "SHA512") => (
                &signature::ECDSA_P521_SHA512_FIXED_SIGNING,
                &signature::ECDSA_P521_SHA512_FIXED,
            ),
            ("P-521", "SHA3-512") => (
                &signature::ECDSA_P521_SHA3_512_FIXED_SIGNING,
                &signature::ECDSA_P521_SHA3_512_FIXED,
            ),
            _ => {
                panic!("Unsupported curve+digest: {curve_name}+{digest_name}");
            }
        };

        let private_key =
            signature::EcdsaKeyPair::from_private_key_and_public_key(signing_alg, &d, &q).unwrap();

        let signature = private_key.sign(&rng, &msg).unwrap();

        let public_key = signature::UnparsedPublicKey::new(verification_alg, q);
        let vfy_result = public_key.verify(&msg, signature.as_ref());
        assert!(vfy_result.is_ok());

        Ok(())
    });
}

#[test]
fn signature_ecdsa_sign_asn1_test() {
    test_signature_ecdsa_sign_asn1(test_file!("data/ecdsa_sign_asn1_tests.txt"));
}

#[test]
fn signature_ecdsa_sign_asn1_sha3_test() {
    test_signature_ecdsa_sign_asn1(test_file!("data/ecdsa_sign_asn1_sha3_tests.txt"));
}

// This test is not a known-answer test, though it re-uses the known-answer
// test vectors. Because the nonce is randomized, the signature will be
// different each time. Because of that, here we simply verify that the
// signature verifies correctly.
fn test_signature_ecdsa_sign_asn1(data_file: test::File) {
    let rng = rand::SystemRandom::new();

    test::run(data_file, |section, test_case| {
        assert_eq!(section, "");

        let curve_name = test_case.consume_string("Curve");
        let digest_name = test_case.consume_string("Digest");

        let msg = test_case.consume_bytes("Msg");
        let d = test_case.consume_bytes("d");
        let q = test_case.consume_bytes("Q");

        // Ignored since the actual signature will use a randomized nonce.
        let _k = test_case.consume_optional_bytes("k");
        let _expected_result = test_case.consume_bytes("Sig");

        let (signing_alg, verification_alg) = match (curve_name.as_str(), digest_name.as_str()) {
            ("P-256", "SHA256") => (
                &signature::ECDSA_P256_SHA256_ASN1_SIGNING,
                &signature::ECDSA_P256_SHA256_ASN1,
            ),
            ("P-384", "SHA384") => (
                &signature::ECDSA_P384_SHA384_ASN1_SIGNING,
                &signature::ECDSA_P384_SHA384_ASN1,
            ),
            ("P-384", "SHA3-384") => (
                &signature::ECDSA_P384_SHA3_384_ASN1_SIGNING,
                &signature::ECDSA_P384_SHA3_384_ASN1,
            ),
            ("P-521", "SHA512") => (
                &signature::ECDSA_P521_SHA512_ASN1_SIGNING,
                &signature::ECDSA_P521_SHA512_ASN1,
            ),
            ("P-521", "SHA3-512") => (
                &signature::ECDSA_P521_SHA3_512_ASN1_SIGNING,
                &signature::ECDSA_P521_SHA3_512_ASN1,
            ),
            _ => {
                panic!("Unsupported curve+digest: {curve_name}+{digest_name}");
            }
        };

        let private_key =
            signature::EcdsaKeyPair::from_private_key_and_public_key(signing_alg, &d, &q).unwrap();

        let signature = private_key.sign(&rng, &msg).unwrap();

        let public_key = signature::UnparsedPublicKey::new(verification_alg, q);
        assert_eq!(public_key.verify(&msg, signature.as_ref()), Ok(()));

        Ok(())
    });
}