//! Tests for ChaCha20 (IETF and "djb" versions) as well as XChaCha20

#![cfg(feature = "cipher")]

use chacha20::ChaCha20;

// IETF version of ChaCha20 (96-bit nonce)
cipher::stream_cipher_test!(chacha20_core, ChaCha20, "chacha20");
cipher::stream_cipher_seek_test!(chacha20_seek, ChaCha20);

mod overflow {
    use cipher::{NewCipher, StreamCipher, StreamCipherSeek};

    const OFFSET_256GB: u64 = 256u64 << 30;
    const OFFSET_256PB: u64 = 256u64 << 50;
    const OFFSET_1ZB: u128 = (64u128) << 64;

    #[test]
    fn bad_overflow_check1() {
        let mut cipher = chacha20::ChaCha20::new(&Default::default(), &Default::default());
        cipher
            .try_seek(OFFSET_256GB - 1)
            .expect("Couldn't seek to nearly 256GB");
        let mut data = [0u8; 1];
        cipher
            .try_apply_keystream(&mut data)
            .expect("Couldn't encrypt the last byte of 256GB");
        assert_eq!(cipher.try_current_pos::<u64>().unwrap(), OFFSET_256GB);
        let mut data = [0u8; 1];
        cipher
            .try_apply_keystream(&mut data)
            .expect_err("Could encrypt past the last byte of 256GB");
    }

    #[test]
    fn bad_overflow_check2() {
        let mut cipher = chacha20::ChaCha20::new(&Default::default(), &Default::default());
        cipher
            .try_seek(OFFSET_256GB - 1)
            .expect("Couldn't seek to nearly 256GB");
        let mut data = [0u8; 2];
        cipher
            .try_apply_keystream(&mut data)
            .expect_err("Could encrypt over the 256GB boundary");
    }

    #[test]
    fn bad_overflow_check3() {
        let mut cipher = chacha20::ChaCha20::new(&Default::default(), &Default::default());
        cipher
            .try_seek(OFFSET_256GB - 1)
            .expect("Couldn't seek to nearly 256GB");
        let mut data = [0u8; 1];
        cipher
            .try_apply_keystream(&mut data)
            .expect("Couldn't encrypt the last byte of 256GB");
        assert_eq!(cipher.try_current_pos::<u64>().unwrap(), OFFSET_256GB);
        let mut data = [0u8; 63];
        cipher
            .try_apply_keystream(&mut data)
            .expect_err("Could encrypt past the last byte of 256GB");
    }

    #[test]
    fn bad_overflow_check4() {
        let mut cipher = chacha20::ChaCha20::new(&Default::default(), &Default::default());
        cipher
            .try_seek(OFFSET_256GB - 1)
            .expect("Couldn't seek to nearly 256GB");
        let mut data = [0u8; 1];
        cipher
            .try_apply_keystream(&mut data)
            .expect("Couldn't encrypt the last byte of 256GB");
        assert_eq!(cipher.try_current_pos::<u64>().unwrap(), OFFSET_256GB);
        let mut data = [0u8; 64];
        cipher
            .try_apply_keystream(&mut data)
            .expect_err("Could encrypt past the last byte of 256GB");
    }

    #[test]
    fn bad_overflow_check5() {
        let mut cipher = chacha20::ChaCha20::new(&Default::default(), &Default::default());
        cipher
            .try_seek(OFFSET_256GB - 1)
            .expect("Couldn't seek to nearly 256GB");
        let mut data = [0u8; 1];
        cipher
            .try_apply_keystream(&mut data)
            .expect("Couldn't encrypt the last byte of 256GB");
        assert_eq!(cipher.try_current_pos::<u64>().unwrap(), OFFSET_256GB);
        let mut data = [0u8; 65];
        cipher
            .try_apply_keystream(&mut data)
            .expect_err("Could encrypt past the last byte of 256GB");
    }

    #[test]
    fn bad_overflow_check6() {
        let mut cipher = chacha20::ChaCha20::new(&Default::default(), &Default::default());
        cipher
            .try_seek(OFFSET_256GB)
            .expect_err("Could seek to 256GB");
    }

    #[test]
    fn bad_overflow_check7() {
        let mut cipher = chacha20::ChaCha20::new(&Default::default(), &Default::default());
        if let Ok(()) = cipher.try_seek(OFFSET_256GB + 63) {
            let mut data = [0u8; 1];
            cipher
                .try_apply_keystream(&mut data)
                .expect_err("Could encrypt the 64th byte past the 256GB boundary");
        }
    }

    #[test]
    fn xchacha_256gb() {
        let mut cipher = chacha20::XChaCha20::new(&Default::default(), &Default::default());
        cipher
            .try_seek(OFFSET_256GB - 1)
            .expect("Couldn't seek to nearly 256GB");
        let mut data = [0u8; 1];
        cipher
            .try_apply_keystream(&mut data)
            .expect("Couldn't encrypt the last byte of 256GB");
        assert_eq!(cipher.try_current_pos::<u64>().unwrap(), OFFSET_256GB);
        let mut data = [0u8; 1];
        cipher
            .try_apply_keystream(&mut data)
            .expect("Couldn't encrypt past the last byte of 256GB");
    }

    #[test]
    fn xchacha_upper_limit() {
        let mut cipher = chacha20::XChaCha20::new(&Default::default(), &Default::default());
        cipher
            .try_seek(OFFSET_1ZB - 1)
            .expect("Couldn't seek to nearly 1 zebibyte");
        let mut data = [0u8; 1];
        cipher
            .try_apply_keystream(&mut data)
            .expect("Couldn't encrypt the last byte of 1 zebibyte");
        let mut data = [0u8; 1];
        cipher
            .try_apply_keystream(&mut data)
            .expect_err("Could encrypt past 1 zebibyte");
    }

    #[test]
    fn xchacha_has_a_big_counter() {
        let mut cipher = chacha20::XChaCha20::new(&Default::default(), &Default::default());
        cipher.try_seek(OFFSET_256PB).expect("Could seek to 256PB");
        let mut data = [0u8; 1];
        cipher
            .try_apply_keystream(&mut data)
            .expect("Couldn't encrypt the next byte after 256PB");
    }

    #[cfg(feature = "legacy")]
    #[test]
    fn legacy_256gb() {
        let mut cipher = chacha20::ChaCha20Legacy::new(&Default::default(), &Default::default());
        cipher
            .try_seek(OFFSET_256GB - 1)
            .expect("Couldn't seek to nearly 256GB");
        let mut data = [0u8; 1];
        cipher
            .try_apply_keystream(&mut data)
            .expect("Couldn't encrypt the last byte of 256GB");
        assert_eq!(cipher.try_current_pos::<u64>().unwrap(), OFFSET_256GB);
        let mut data = [0u8; 1];
        cipher
            .try_apply_keystream(&mut data)
            .expect("Couldn't encrypt past the last byte of 256GB");
    }

    #[cfg(feature = "legacy")]
    #[test]
    fn legacy_upper_limit() {
        let mut cipher = chacha20::ChaCha20Legacy::new(&Default::default(), &Default::default());
        cipher
            .try_seek(OFFSET_1ZB - 1)
            .expect("Couldn't seek to nearly 1 zebibyte");
        let mut data = [0u8; 1];
        cipher
            .try_apply_keystream(&mut data)
            .expect("Couldn't encrypt the last byte of 1 zebibyte");
        let mut data = [0u8; 1];
        cipher
            .try_apply_keystream(&mut data)
            .expect_err("Could encrypt past 1 zebibyte");
    }

    #[cfg(feature = "legacy")]
    #[test]
    fn legacy_has_a_big_counter() {
        let mut cipher = chacha20::ChaCha20Legacy::new(&Default::default(), &Default::default());
        cipher.try_seek(OFFSET_256PB).expect("Could seek to 256PB");
        let mut data = [0u8; 1];
        cipher
            .try_apply_keystream(&mut data)
            .expect("Couldn't encrypt the next byte after 256PB");
    }
}

mod chacha20test {
    use chacha20::{
        cipher::{NewCipher, StreamCipher},
        ChaCha20, Key, Nonce,
    };
    use hex_literal::hex;

    //
    // ChaCha20 test vectors from:
    // <https://datatracker.ietf.org/doc/html/rfc8439#section-2.4.2>
    //

    const KEY: [u8; 32] = hex!("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f");

    const IV: [u8; 12] = hex!("000000000000004a00000000");

    const PLAINTEXT: [u8; 114] = hex!(
        "
        4c616469657320616e642047656e746c
        656d656e206f662074686520636c6173
        73206f66202739393a20496620492063
        6f756c64206f6666657220796f75206f
        6e6c79206f6e652074697020666f7220
        746865206675747572652c2073756e73
        637265656e20776f756c642062652069
        742e
        "
    );

    const KEYSTREAM: [u8; 114] = hex!(
        "
        224f51f3401bd9e12fde276fb8631ded8c131f823d2c06
        e27e4fcaec9ef3cf788a3b0aa372600a92b57974cded2b
        9334794cba40c63e34cdea212c4cf07d41b769a6749f3f
        630f4122cafe28ec4dc47e26d4346d70b98c73f3e9c53a
        c40c5945398b6eda1a832c89c167eacd901d7e2bf363
        "
    );

    const CIPHERTEXT: [u8; 114] = hex!(
        "
        6e2e359a2568f98041ba0728dd0d6981
        e97e7aec1d4360c20a27afccfd9fae0b
        f91b65c5524733ab8f593dabcd62b357
        1639d624e65152ab8f530c359f0861d8
        07ca0dbf500d6a6156a38e088a22b65e
        52bc514d16ccf806818ce91ab7793736
        5af90bbf74a35be6b40b8eedf2785e42
        874d
        "
    );

    #[test]
    fn chacha20_keystream() {
        let mut cipher = ChaCha20::new(&Key::from(KEY), &Nonce::from(IV));

        // The test vectors omit the first 64-bytes of the keystream
        let mut prefix = [0u8; 64];
        cipher.apply_keystream(&mut prefix);

        let mut buf = [0u8; 114];
        cipher.apply_keystream(&mut buf);
        assert_eq!(&buf[..], &KEYSTREAM[..]);
    }

    #[test]
    fn chacha20_encryption() {
        let mut cipher = ChaCha20::new(&Key::from(KEY), &Nonce::from(IV));
        let mut buf = PLAINTEXT.clone();

        // The test vectors omit the first 64-bytes of the keystream
        let mut prefix = [0u8; 64];
        cipher.apply_keystream(&mut prefix);

        cipher.apply_keystream(&mut buf);
        assert_eq!(&buf[..], &CIPHERTEXT[..]);
    }
}

#[rustfmt::skip]
mod xchacha20 {
    use chacha20::{Key, XChaCha20, XNonce};
    use cipher::{NewCipher, StreamCipher};
    use hex_literal::hex;

    cipher::stream_cipher_seek_test!(xchacha20_seek, XChaCha20);

    //
    // XChaCha20 test vectors from:
    // <https://tools.ietf.org/id/draft-arciszewski-xchacha-03.html#rfc.appendix.A.3.2>
    //

    const KEY: [u8; 32] = hex!("
        808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f
    ");

    const IV: [u8; 24] = hex!("
        404142434445464748494a4b4c4d4e4f5051525354555658
    ");

    const PLAINTEXT: [u8; 304] = hex!("
        5468652064686f6c65202870726f6e6f756e6365642022646f6c652229206973
        20616c736f206b6e6f776e2061732074686520417369617469632077696c6420
        646f672c2072656420646f672c20616e642077686973746c696e6720646f672e
        2049742069732061626f7574207468652073697a65206f662061204765726d61
        6e20736865706865726420627574206c6f6f6b73206d6f7265206c696b652061
        206c6f6e672d6c656767656420666f782e205468697320686967686c7920656c
        757369766520616e6420736b696c6c6564206a756d70657220697320636c6173
        736966696564207769746820776f6c7665732c20636f796f7465732c206a6163
        6b616c732c20616e6420666f78657320696e20746865207461786f6e6f6d6963
        2066616d696c792043616e696461652e
    ");

    const KEYSTREAM: [u8; 304] = hex!("
        29624b4b1b140ace53740e405b2168540fd7d630c1f536fecd722fc3cddba7f4
        cca98cf9e47e5e64d115450f9b125b54449ff76141ca620a1f9cfcab2a1a8a25
        5e766a5266b878846120ea64ad99aa479471e63befcbd37cd1c22a221fe46221
        5cf32c74895bf505863ccddd48f62916dc6521f1ec50a5ae08903aa259d9bf60
        7cd8026fba548604f1b6072d91bc91243a5b845f7fd171b02edc5a0a84cf28dd
        241146bc376e3f48df5e7fee1d11048c190a3d3deb0feb64b42d9c6fdeee290f
        a0e6ae2c26c0249ea8c181f7e2ffd100cbe5fd3c4f8271d62b15330cb8fdcf00
        b3df507ca8c924f7017b7e712d15a2eb5c50484451e54e1b4b995bd8fdd94597
        bb94d7af0b2c04df10ba0890899ed9293a0f55b8bafa999264035f1d4fbe7fe0
        aafa109a62372027e50e10cdfecca127
    ");

    const CIPHERTEXT: [u8; 304] = hex!("
        7d0a2e6b7f7c65a236542630294e063b7ab9b555a5d5149aa21e4ae1e4fbce87
        ecc8e08a8b5e350abe622b2ffa617b202cfad72032a3037e76ffdcdc4376ee05
        3a190d7e46ca1de04144850381b9cb29f051915386b8a710b8ac4d027b8b050f
        7cba5854e028d564e453b8a968824173fc16488b8970cac828f11ae53cabd201
        12f87107df24ee6183d2274fe4c8b1485534ef2c5fbc1ec24bfc3663efaa08bc
        047d29d25043532db8391a8a3d776bf4372a6955827ccb0cdd4af403a7ce4c63
        d595c75a43e045f0cce1f29c8b93bd65afc5974922f214a40b7c402cdb91ae73
        c0b63615cdad0480680f16515a7ace9d39236464328a37743ffc28f4ddb324f4
        d0f5bbdc270c65b1749a6efff1fbaa09536175ccd29fb9e6057b307320d31683
        8a9c71f70b5b5907a66f7ea49aadc409
    ");

    #[test]
    fn xchacha20_keystream() {
        let mut cipher = XChaCha20::new(&Key::from(KEY), &XNonce::from(IV));

        // The test vectors omit the first 64-bytes of the keystream
        let mut prefix = [0u8; 64];
        cipher.apply_keystream(&mut prefix);

        let mut buf = [0u8; 304];
        cipher.apply_keystream(&mut buf);
        assert_eq!(&buf[..], &KEYSTREAM[..]);
    }

    #[test]
    fn xchacha20_encryption() {
        let mut cipher = XChaCha20::new(&Key::from(KEY), &XNonce::from(IV));
        let mut buf = PLAINTEXT.clone();

        // The test vectors omit the first 64-bytes of the keystream
        let mut prefix = [0u8; 64];
        cipher.apply_keystream(&mut prefix);

        cipher.apply_keystream(&mut buf);
        assert_eq!(&buf[..], &CIPHERTEXT[..]);
    }
}

// Legacy "djb" version of ChaCha20 (64-bit nonce)
#[cfg(feature = "legacy")]
#[rustfmt::skip]
mod legacy {
    use chacha20::{ChaCha20Legacy, Key, LegacyNonce};
    use cipher::{NewCipher, StreamCipher, StreamCipherSeek};
    use hex_literal::hex;

    cipher::stream_cipher_test!(chacha20_legacy_core, ChaCha20Legacy, "chacha20-legacy");
    cipher::stream_cipher_seek_test!(chacha20_legacy_seek, ChaCha20Legacy);

    const KEY_LONG: [u8; 32] = hex!("
        0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20
    ");

    const IV_LONG: [u8; 8] = hex!("0301040105090206");

    const EXPECTED_LONG: [u8; 256] = hex!("
        deeb6b9d06dff3e091bf3ad4f4d492b6dd98246f69691802e466e03bad235787
        0f1c6c010b6c2e650c4bf58d2d35c72ab639437069a384e03100078cc1d735a0
        db4e8f474ee6291460fd9197c77ed87b4c64e0d9ac685bd1c56cce021f3819cd
        13f49c9a3053603602582a060e59c2fbee90ab0bf7bb102d819ced03969d3bae
        71034fe598246583336aa744d8168e5dfff5c6d10270f125a4130e719717e783
        c0858b6f7964437173ea1d7556c158bc7a99e74a34d93da6bf72ac9736a215ac
        aefd4ec031f3f13f099e3d811d83a2cf1d544a68d2752409cc6be852b0511a2e
        32f69aa0be91b30981584a1c56ce7546cca24d8cfdfca525d6b15eea83b6b686
    ");

    #[test]
    #[ignore]
    fn chacha20_offsets() {
        for idx in 0..256 {
            for middle in idx..256 {
                for last in middle..256 {
                    let mut cipher =
                        ChaCha20Legacy::new(&Key::from(KEY_LONG), &LegacyNonce::from(IV_LONG));
                    let mut buf = [0; 256];

                    cipher.seek(idx as u64);
                    cipher.apply_keystream(&mut buf[idx..middle]);
                    cipher.apply_keystream(&mut buf[middle..last]);

                    for k in idx..last {
                        assert_eq!(buf[k], EXPECTED_LONG[k])
                    }
                }
            }
        }
    }
}
