//! v0.8 W3 — SM4-CCM Known-Answer Tests.
//!
//! Cross-validate `sm4::mode_ccm` byte-for-byte against the OpenSSL 3.x
//! EVP `SM4-CCM` cipher (OID `1.2.156.10197.1.104.9`, GB/T 0009 /
//! RFC 8998).
//!
//! ## Provenance
//!
//! The vectors below were generated on 2026-05-15 by the
//! `sm4_ccm_oracle.c` harness against OpenSSL 3.6.2 (Homebrew
//! `openssl@3`). See [`docs/v0.8-ccm-kat-sourcing.md`] for the
//! sourcing rationale, the harness source, and the parametric-
//! coverage matrix (7 nonce lengths, 7 tag lengths, AAD-length
//! boundaries). The eight scenarios below cover representative
//! points across the parametric space.
//!
//! ## Re-generate (auditor-reproducible)
//!
//! ```text
//! cc -O2 -Wall -I$(brew --prefix openssl@3)/include sm4_ccm_oracle.c \
//!    $(brew --prefix openssl@3)/lib/libcrypto.dylib -o sm4_ccm_oracle
//!
//! # baseline
//! ./sm4_ccm_oracle enc \
//!   0123456789abcdeffedcba9876543210 \
//!   000102030405060708090a0b \
//!   feedfacedeadbeeffeedfacedeadbeef \
//!   aaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbcccccccccccccccc \
//!   16
//! # → bdc07260cb3c00cd1144bb9bd492353fbc9391492c2fc81a 575b95ef901b592c5f6dc0235c897a3c
//! ```

#![cfg(feature = "sm4-aead")]

use gmcrypto_core::sm4::mode_ccm;

struct CcmVector {
    name: &'static str,
    key: [u8; 16],
    nonce: &'static [u8],
    aad: &'static [u8],
    plaintext: &'static [u8],
    tag_len: usize,
    /// Expected ct ‖ tag concatenation (matches OpenSSL output shape).
    expected: &'static [u8],
}

const KEY_A: [u8; 16] = [
    0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10,
];

// 12-byte nonces (most-common shape).
const NONCE_12: [u8; 12] = [
    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
];
// 7-byte nonce (RFC 3610 minimum).
const NONCE_7: [u8; 7] = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06];
// 13-byte nonce (RFC 3610 maximum).
const NONCE_13: [u8; 13] = [
    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12,
];

const VECTORS: &[CcmVector] = &[
    CcmVector {
        name: "baseline",
        key: KEY_A,
        nonce: &NONCE_12,
        aad: &[
            0xfe, 0xed, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef, 0xfe, 0xed, 0xfa, 0xce, 0xde, 0xad,
            0xbe, 0xef,
        ],
        plaintext: &[
            0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb,
            0xbb, 0xbb, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc,
        ],
        tag_len: 16,
        expected: &[
            0xbd, 0xc0, 0x72, 0x60, 0xcb, 0x3c, 0x00, 0xcd, 0x11, 0x44, 0xbb, 0x9b, 0xd4, 0x92,
            0x35, 0x3f, 0xbc, 0x93, 0x91, 0x49, 0x2c, 0x2f, 0xc8, 0x1a, // ct (24 bytes)
            0x57, 0x5b, 0x95, 0xef, 0x90, 0x1b, 0x59, 0x2c, 0x5f, 0x6d, 0xc0, 0x23, 0x5c, 0x89,
            0x7a, 0x3c, // tag (16 bytes)
        ],
    },
    CcmVector {
        name: "min-nonce",
        key: KEY_A,
        nonce: &NONCE_7,
        aad: &[],
        plaintext: &[
            0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e,
            0x4f, 0x50,
        ],
        tag_len: 16,
        expected: &[
            0xb5, 0x33, 0x8d, 0x80, 0x6d, 0x18, 0xd6, 0xd3, 0xbd, 0x87, 0xbc, 0xe7, 0x91, 0x1e,
            0x51, 0xdb, // ct
            0x3b, 0x95, 0x12, 0x87, 0xfb, 0x93, 0x2c, 0x84, 0x4e, 0xe4, 0x99, 0xb1, 0xc6, 0x84,
            0x62, 0x4f, // tag
        ],
    },
    CcmVector {
        name: "max-nonce",
        key: KEY_A,
        nonce: &NONCE_13,
        aad: &[],
        plaintext: &[
            0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e,
            0x4f, 0x50,
        ],
        tag_len: 16,
        expected: &[
            0xb3, 0x53, 0xf3, 0x60, 0xc0, 0x1c, 0xcf, 0x65, 0x8b, 0x38, 0xa3, 0xbb, 0x31, 0xa9,
            0x19, 0xc3, // ct
            0x73, 0x28, 0x2c, 0x47, 0xc8, 0xa7, 0x6f, 0x64, 0xe4, 0x70, 0x0b, 0x34, 0x9a, 0x86,
            0x8f, 0x1c, // tag
        ],
    },
    CcmVector {
        name: "tag-4",
        key: KEY_A,
        nonce: &NONCE_12,
        aad: &[0xfe, 0xed, 0x41, 0x42, 0x43, 0x44, 0x45],
        plaintext: &[
            0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e,
            0x4f, 0x50,
        ],
        tag_len: 4,
        expected: &[
            0x56, 0x28, 0x9b, 0x8e, 0x24, 0xd0, 0xed, 0x2f, 0xe3, 0xb5, 0x4b, 0x6c, 0x22, 0x67,
            0xc1, 0xd4, // ct
            0x53, 0x5f, 0xc3, 0x95, // tag (4 bytes)
        ],
    },
    CcmVector {
        name: "tag-10",
        key: KEY_A,
        nonce: &NONCE_12,
        aad: &[0xfe, 0xed, 0x41, 0x42, 0x43, 0x44, 0x45],
        plaintext: &[
            0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e,
            0x4f, 0x50,
        ],
        tag_len: 10,
        expected: &[
            0x56, 0x28, 0x9b, 0x8e, 0x24, 0xd0, 0xed, 0x2f, 0xe3, 0xb5, 0x4b, 0x6c, 0x22, 0x67,
            0xc1, 0xd4, // ct
            0xe9, 0x9e, 0xf9, 0x6d, 0x08, 0x80, 0xf0, 0xa8, 0xaa, 0x7b, // tag (10 bytes)
        ],
    },
    CcmVector {
        name: "empty-pt-with-aad",
        key: KEY_A,
        nonce: &NONCE_12,
        aad: &[0xfe, 0xed, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef],
        plaintext: &[],
        tag_len: 16,
        expected: &[
            0x2a, 0xfa, 0xf4, 0x07, 0xa1, 0xf5, 0x84, 0xcd, 0x4d, 0xe3, 0x01, 0xdc, 0xff, 0xa8,
            0x48, 0xec, // tag only
        ],
    },
    CcmVector {
        name: "empty-aad-and-pt",
        key: KEY_A,
        nonce: &NONCE_12,
        aad: &[],
        plaintext: &[],
        tag_len: 16,
        expected: &[
            0x98, 0xd9, 0xfe, 0x4d, 0x81, 0x89, 0x09, 0xe9, 0x80, 0xf4, 0x14, 0xf8, 0x46, 0xaa,
            0x09, 0xf4, // tag only
        ],
    },
    CcmVector {
        name: "long-aad-crosses-block",
        key: KEY_A,
        nonce: &NONCE_12,
        aad: &[
            0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
            0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, 0x11, 0x22,
        ],
        plaintext: &[
            0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e,
            0x4f, 0x50,
        ],
        tag_len: 16,
        expected: &[
            0x56, 0x28, 0x9b, 0x8e, 0x24, 0xd0, 0xed, 0x2f, 0xe3, 0xb5, 0x4b, 0x6c, 0x22, 0x67,
            0xc1, 0xd4, // ct
            0x0e, 0x5d, 0x34, 0x45, 0xff, 0x30, 0x74, 0xe6, 0xa6, 0x31, 0x1d, 0x0b, 0xae, 0xb8,
            0x69, 0x21, // tag
        ],
    },
];

#[test]
fn encrypt_matches_openssl_vectors() {
    for v in VECTORS {
        let got = mode_ccm::encrypt(&v.key, v.nonce, v.aad, v.plaintext, v.tag_len)
            .unwrap_or_else(|| panic!("encrypt returned None for scenario {:?}", v.name));
        assert_eq!(
            got, v.expected,
            "OpenSSL ct||tag divergence for scenario {:?}",
            v.name,
        );
    }
}

#[test]
fn decrypt_matches_openssl_vectors() {
    for v in VECTORS {
        let recovered = mode_ccm::decrypt(&v.key, v.nonce, v.aad, v.expected, v.tag_len);
        assert_eq!(
            recovered.as_deref(),
            Some(v.plaintext),
            "decrypt failed for scenario {:?}",
            v.name,
        );
    }
}

#[test]
fn tampered_tag_fails_for_all_vectors() {
    for v in VECTORS {
        let mut bad = v.expected.to_vec();
        let last = bad.len() - 1;
        bad[last] ^= 0x01;
        assert!(
            mode_ccm::decrypt(&v.key, v.nonce, v.aad, &bad, v.tag_len).is_none(),
            "tag-tamper not detected for scenario {:?}",
            v.name,
        );
    }
}

#[test]
fn tampered_aad_fails() {
    let v = &VECTORS[0]; // "baseline"
    let bad_aad = b"different aad value!";
    assert!(
        mode_ccm::decrypt(&v.key, v.nonce, bad_aad, v.expected, v.tag_len).is_none(),
        "AAD-tamper not detected for baseline",
    );
}
