//! SM4_CCM
//!
//! https://datatracker.ietf.org/doc/html/rfc8998

use ccm::{
    aead::{array::typenum::Unsigned, AeadCore, AeadInOut, KeyInit, KeySizeUser},
    consts::{U12, U16},
    Ccm,
    Nonce,
    Tag,
};
use sm4::Sm4;

pub struct Sm4Ccm(Ccm<Sm4, U16, U12>);

impl Sm4Ccm {
    pub fn new(key: &[u8]) -> Sm4Ccm {
        Sm4Ccm(Ccm::new_from_slice(key).expect("Sm4Ccm"))
    }

    pub fn key_size() -> usize {
        <Ccm<Sm4, U16, U12> as KeySizeUser>::KeySize::to_usize()
    }

    pub fn nonce_size() -> usize {
        <Ccm<Sm4, U16, U12> as AeadCore>::NonceSize::to_usize()
    }

    pub fn tag_size() -> usize {
        <Ccm<Sm4, U16, U12> as AeadCore>::TagSize::to_usize()
    }

    pub fn encrypt(&self, nonce: &[u8], plaintext_in_ciphertext_out: &mut [u8]) {
        let nonce = Nonce::<U12>::try_from(nonce).expect("SM4_CCM nonce");
        let (plaintext, out_tag) =
            plaintext_in_ciphertext_out.split_at_mut(plaintext_in_ciphertext_out.len() - Self::tag_size());
        let tag = self
            .0
            .encrypt_inout_detached(&nonce, &[], plaintext.into())
            .expect("SM4_CCM encrypt");
        out_tag.copy_from_slice(tag.as_slice())
    }

    pub fn decrypt(&self, nonce: &[u8], ciphertext_in_plaintext_out: &mut [u8]) -> bool {
        let nonce = Nonce::<U12>::try_from(nonce).expect("SM4_CCM nonce");
        let (ciphertext, in_tag) =
            ciphertext_in_plaintext_out.split_at_mut(ciphertext_in_plaintext_out.len() - Self::tag_size());
        let in_tag = match Tag::<U16>::try_from(&*in_tag) {
            Ok(t) => t,
            Err(_) => return false,
        };
        self.0.decrypt_inout_detached(&nonce, &[], ciphertext.into(), &in_tag).is_ok()
    }
}

#[cfg(test)]
mod test {
    use ccm::{
        aead::{Aead, KeyInit, Payload},
        consts::{U12, U16},
        Ccm,
    };
    use sm4::Sm4;

    #[test]
    fn test_sm4_ccm() {
        let iv = hex::decode("00001234567800000000ABCD").unwrap();
        let key = hex::decode("0123456789ABCDEFFEDCBA9876543210").unwrap();
        let plain_text = hex::decode("AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDEEEEEEEEEEEEEEEEFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEAAAAAAAAAAAAAAAA").unwrap();
        let aad = hex::decode("FEEDFACEDEADBEEFFEEDFACEDEADBEEFABADDAD2").unwrap();
        let mut cipher_text = hex::decode("48AF93501FA62ADBCD414CCE6034D895DDA1BF8F132F042098661572E7483094FD12E518CE062C98ACEE28D95DF4416BED31A2F04476C18BB40C84A74B97DC5B").unwrap();
        let mut tag = hex::decode("16842D4FA186F56AB33256971FA110F4").unwrap();
        cipher_text.append(&mut tag); // postfix tag

        let nonce = super::Nonce::<U12>::try_from(&iv[..]).unwrap();

        let cipher = Ccm::<Sm4, U16, U12>::new_from_slice(&key).unwrap();
        let plain_text_payload = Payload {
            msg: &plain_text,
            aad: &aad,
        };
        let result = cipher.encrypt(&nonce, plain_text_payload).unwrap();

        assert_eq!(result, cipher_text);
    }
}
