//! AES Key Locker Intrinsics
//!
//! The Intrinsics here correspond to those in the `keylockerintrin.h` C header.

use crate::core_arch::x86::__m128i;
use crate::ptr;

#[cfg(test)]
use stdarch_test::assert_instr;

#[repr(C, packed)]
struct EncodeKey128Output(u32, __m128i, __m128i, __m128i, __m128i, __m128i, __m128i);

#[repr(C, packed)]
struct EncodeKey256Output(
    u32,
    __m128i,
    __m128i,
    __m128i,
    __m128i,
    __m128i,
    __m128i,
    __m128i,
);

#[repr(C, packed)]
struct AesOutput(u8, __m128i);

#[repr(C, packed)]
struct WideAesOutput(
    u8,
    __m128i,
    __m128i,
    __m128i,
    __m128i,
    __m128i,
    __m128i,
    __m128i,
    __m128i,
);

#[allow(improper_ctypes)]
unsafe extern "unadjusted" {
    #[link_name = "llvm.x86.loadiwkey"]
    fn loadiwkey(integrity_key: __m128i, key_lo: __m128i, key_hi: __m128i, control: u32);

    #[link_name = "llvm.x86.encodekey128"]
    fn encodekey128(key_metadata: u32, key: __m128i) -> EncodeKey128Output;
    #[link_name = "llvm.x86.encodekey256"]
    fn encodekey256(key_metadata: u32, key_lo: __m128i, key_hi: __m128i) -> EncodeKey256Output;

    #[link_name = "llvm.x86.aesenc128kl"]
    fn aesenc128kl(data: __m128i, handle: *const u8) -> AesOutput;
    #[link_name = "llvm.x86.aesdec128kl"]
    fn aesdec128kl(data: __m128i, handle: *const u8) -> AesOutput;
    #[link_name = "llvm.x86.aesenc256kl"]
    fn aesenc256kl(data: __m128i, handle: *const u8) -> AesOutput;
    #[link_name = "llvm.x86.aesdec256kl"]
    fn aesdec256kl(data: __m128i, handle: *const u8) -> AesOutput;

    #[link_name = "llvm.x86.aesencwide128kl"]
    fn aesencwide128kl(
        handle: *const u8,
        i0: __m128i,
        i1: __m128i,
        i2: __m128i,
        i3: __m128i,
        i4: __m128i,
        i5: __m128i,
        i6: __m128i,
        i7: __m128i,
    ) -> WideAesOutput;
    #[link_name = "llvm.x86.aesdecwide128kl"]
    fn aesdecwide128kl(
        handle: *const u8,
        i0: __m128i,
        i1: __m128i,
        i2: __m128i,
        i3: __m128i,
        i4: __m128i,
        i5: __m128i,
        i6: __m128i,
        i7: __m128i,
    ) -> WideAesOutput;
    #[link_name = "llvm.x86.aesencwide256kl"]
    fn aesencwide256kl(
        handle: *const u8,
        i0: __m128i,
        i1: __m128i,
        i2: __m128i,
        i3: __m128i,
        i4: __m128i,
        i5: __m128i,
        i6: __m128i,
        i7: __m128i,
    ) -> WideAesOutput;
    #[link_name = "llvm.x86.aesdecwide256kl"]
    fn aesdecwide256kl(
        handle: *const u8,
        i0: __m128i,
        i1: __m128i,
        i2: __m128i,
        i3: __m128i,
        i4: __m128i,
        i5: __m128i,
        i6: __m128i,
        i7: __m128i,
    ) -> WideAesOutput;
}

/// Load internal wrapping key (IWKey). The 32-bit unsigned integer `control` specifies IWKey's KeySource
/// and whether backing up the key is permitted. IWKey's 256-bit encryption key is loaded from `key_lo`
/// and `key_hi`.
///
///  - `control[0]`: NoBackup bit. If set, the IWKey cannot be backed up.
///  - `control[1:4]`: KeySource bits. These bits specify the encoding method of the IWKey. The only
///    allowed values are `0` (AES GCM SIV wrapping algorithm with the specified key) and `1` (AES GCM
///    SIV wrapping algorithm with random keys enforced by hardware). After calling `_mm_loadiwkey` with
///    KeySource set to `1`, software must check `ZF` to ensure that the key was loaded successfully.
///    Using any other value may result in a General Protection Exception.
///  - `control[5:31]`: Reserved for future use, must be set to `0`.
///
/// Note that setting the NoBackup bit and using the KeySource value `1` requires hardware support. These
/// permissions can be found by calling `__cpuid(0x19)` and checking the `ECX[0:1]` bits. Failing to follow
/// these restrictions may result in a General Protection Exception.
///
/// [Intel's documentation](https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_loadiwkey)
#[inline]
#[target_feature(enable = "kl")]
#[stable(feature = "keylocker_x86", since = "1.89.0")]
#[cfg_attr(test, assert_instr(loadiwkey))]
pub unsafe fn _mm_loadiwkey(
    control: u32,
    integrity_key: __m128i,
    key_lo: __m128i,
    key_hi: __m128i,
) {
    loadiwkey(integrity_key, key_lo, key_hi, control);
}

/// Wrap a 128-bit AES key into a 384-bit key handle and stores it in `handle`. Returns the `control`
/// parameter used to create the IWKey.
///
///  - `key_params[0]`: If set, this key can only be used by the Kernel.
///  - `key_params[1]`: If set, this key can not be used to encrypt.
///  - `key_params[2]`: If set, this key can not be used to decrypt.
///  - `key_params[31:3]`: Reserved for future use, must be set to `0`.
///
/// Note that these restrictions need hardware support, and the supported restrictions can be found by
/// calling `__cpuid(0x19)` and checking the `EAX[0:2]` bits. Failing to follow these restrictions may
/// result in a General Protection Exception.
///
/// [Intel's documentation](https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_encodekey128_u32)
#[inline]
#[target_feature(enable = "kl")]
#[stable(feature = "keylocker_x86", since = "1.89.0")]
#[cfg_attr(test, assert_instr(encodekey128))]
pub unsafe fn _mm_encodekey128_u32(key_params: u32, key: __m128i, handle: *mut u8) -> u32 {
    let EncodeKey128Output(control, key0, key1, key2, _, _, _) = encodekey128(key_params, key);
    ptr::write_unaligned(handle.cast(), [key0, key1, key2]);
    control
}

/// Wrap a 256-bit AES key into a 512-bit key handle and stores it in `handle`. Returns the `control`
/// parameter used to create the IWKey.
///
///  - `key_params[0]`: If set, this key can only be used by the Kernel.
///  - `key_params[1]`: If set, this key can not be used to encrypt.
///  - `key_params[2]`: If set, this key can not be used to decrypt.
///  - `key_params[31:3]`: Reserved for future use, must be set to `0`.
///
/// Note that these restrictions need hardware support, and the supported restrictions can be found by
/// calling `__cpuid(0x19)` and checking the `EAX[0:2]` bits. Failing to follow these restrictions may
/// result in a General Protection Exception.
///
/// [Intel's documentation](https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_encodekey256_u32)
#[inline]
#[target_feature(enable = "kl")]
#[stable(feature = "keylocker_x86", since = "1.89.0")]
#[cfg_attr(test, assert_instr(encodekey256))]
pub unsafe fn _mm_encodekey256_u32(
    key_params: u32,
    key_lo: __m128i,
    key_hi: __m128i,
    handle: *mut u8,
) -> u32 {
    let EncodeKey256Output(control, key0, key1, key2, key3, _, _, _) =
        encodekey256(key_params, key_lo, key_hi);
    ptr::write_unaligned(handle.cast(), [key0, key1, key2, key3]);
    control
}

/// Encrypt 10 rounds of unsigned 8-bit integers in `input` using 128-bit AES key specified in the
/// 384-bit key handle `handle`. Store the resulting unsigned 8-bit integers into the corresponding
/// elements of `output`. Returns `0` if the operation was successful, and `1` if the operation failed
/// due to a handle violation.
///
/// [Intel's documentation](https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_aesenc128kl_u8)
#[inline]
#[target_feature(enable = "kl")]
#[stable(feature = "keylocker_x86", since = "1.89.0")]
#[cfg_attr(test, assert_instr(aesenc128kl))]
pub unsafe fn _mm_aesenc128kl_u8(output: *mut __m128i, input: __m128i, handle: *const u8) -> u8 {
    let AesOutput(status, result) = aesenc128kl(input, handle);
    *output = result;
    status
}

/// Decrypt 10 rounds of unsigned 8-bit integers in `input` using 128-bit AES key specified in the
/// 384-bit key handle `handle`. Store the resulting unsigned 8-bit integers into the corresponding
/// elements of `output`. Returns `0` if the operation was successful, and `1` if the operation failed
/// due to a handle violation.
///
/// [Intel's documentation](https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_aesdec128kl_u8)
#[inline]
#[target_feature(enable = "kl")]
#[stable(feature = "keylocker_x86", since = "1.89.0")]
#[cfg_attr(test, assert_instr(aesdec128kl))]
pub unsafe fn _mm_aesdec128kl_u8(output: *mut __m128i, input: __m128i, handle: *const u8) -> u8 {
    let AesOutput(status, result) = aesdec128kl(input, handle);
    *output = result;
    status
}

/// Encrypt 14 rounds of unsigned 8-bit integers in `input` using 256-bit AES key specified in the
/// 512-bit key handle `handle`. Store the resulting unsigned 8-bit integers into the corresponding
/// elements of `output`. Returns `0` if the operation was successful, and `1` if the operation failed
/// due to a handle violation.
///
/// [Intel's documentation](https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_aesenc256kl_u8)
#[inline]
#[target_feature(enable = "kl")]
#[stable(feature = "keylocker_x86", since = "1.89.0")]
#[cfg_attr(test, assert_instr(aesenc256kl))]
pub unsafe fn _mm_aesenc256kl_u8(output: *mut __m128i, input: __m128i, handle: *const u8) -> u8 {
    let AesOutput(status, result) = aesenc256kl(input, handle);
    *output = result;
    status
}

/// Decrypt 14 rounds of unsigned 8-bit integers in `input` using 256-bit AES key specified in the
/// 512-bit key handle `handle`. Store the resulting unsigned 8-bit integers into the corresponding
/// elements of `output`. Returns `0` if the operation was successful, and `1` if the operation failed
/// due to a handle violation.
///
/// [Intel's documentation](https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_aesdec256kl_u8)
#[inline]
#[target_feature(enable = "kl")]
#[stable(feature = "keylocker_x86", since = "1.89.0")]
#[cfg_attr(test, assert_instr(aesdec256kl))]
pub unsafe fn _mm_aesdec256kl_u8(output: *mut __m128i, input: __m128i, handle: *const u8) -> u8 {
    let AesOutput(status, result) = aesdec256kl(input, handle);
    *output = result;
    status
}

/// Encrypt 10 rounds of 8 groups of unsigned 8-bit integers in `input` using 128-bit AES key specified
/// in the 384-bit key handle `handle`. Store the resulting unsigned 8-bit integers into the corresponding
/// elements of `output`. Returns `0` if the operation was successful, and `1` if the operation failed
/// due to a handle violation.
///
/// [Intel's documentation](https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_aesencwide128kl_u8)
#[inline]
#[target_feature(enable = "widekl")]
#[stable(feature = "keylocker_x86", since = "1.89.0")]
#[cfg_attr(test, assert_instr(aesencwide128kl))]
pub unsafe fn _mm_aesencwide128kl_u8(
    output: *mut __m128i,
    input: *const __m128i,
    handle: *const u8,
) -> u8 {
    let input = &*ptr::slice_from_raw_parts(input, 8);
    let WideAesOutput(status, out0, out1, out2, out3, out4, out5, out6, out7) = aesencwide128kl(
        handle, input[0], input[1], input[2], input[3], input[4], input[5], input[6], input[7],
    );
    *output.cast() = [out0, out1, out2, out3, out4, out5, out6, out7];
    status
}

/// Decrypt 10 rounds of 8 groups of unsigned 8-bit integers in `input` using 128-bit AES key specified
/// in the 384-bit key handle `handle`. Store the resulting unsigned 8-bit integers into the corresponding
/// elements of `output`. Returns `0` if the operation was successful, and `1` if the operation failed
/// due to a handle violation.
///
/// [Intel's documentation](https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_aesdecwide128kl_u8)
#[inline]
#[target_feature(enable = "widekl")]
#[stable(feature = "keylocker_x86", since = "1.89.0")]
#[cfg_attr(test, assert_instr(aesdecwide128kl))]
pub unsafe fn _mm_aesdecwide128kl_u8(
    output: *mut __m128i,
    input: *const __m128i,
    handle: *const u8,
) -> u8 {
    let input = &*ptr::slice_from_raw_parts(input, 8);
    let WideAesOutput(status, out0, out1, out2, out3, out4, out5, out6, out7) = aesdecwide128kl(
        handle, input[0], input[1], input[2], input[3], input[4], input[5], input[6], input[7],
    );
    *output.cast() = [out0, out1, out2, out3, out4, out5, out6, out7];
    status
}

/// Encrypt 14 rounds of 8 groups of unsigned 8-bit integers in `input` using 256-bit AES key specified
/// in the 512-bit key handle `handle`. Store the resulting unsigned 8-bit integers into the corresponding
/// elements of `output`. Returns `0` if the operation was successful, and `1` if the operation failed
/// due to a handle violation.
///
/// [Intel's documentation](https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_aesencwide256kl_u8)
#[inline]
#[target_feature(enable = "widekl")]
#[stable(feature = "keylocker_x86", since = "1.89.0")]
#[cfg_attr(test, assert_instr(aesencwide256kl))]
pub unsafe fn _mm_aesencwide256kl_u8(
    output: *mut __m128i,
    input: *const __m128i,
    handle: *const u8,
) -> u8 {
    let input = &*ptr::slice_from_raw_parts(input, 8);
    let WideAesOutput(status, out0, out1, out2, out3, out4, out5, out6, out7) = aesencwide256kl(
        handle, input[0], input[1], input[2], input[3], input[4], input[5], input[6], input[7],
    );
    *output.cast() = [out0, out1, out2, out3, out4, out5, out6, out7];
    status
}

/// Decrypt 14 rounds of 8 groups of unsigned 8-bit integers in `input` using 256-bit AES key specified
/// in the 512-bit key handle `handle`. Store the resulting unsigned 8-bit integers into the corresponding
/// elements of `output`. Returns `0` if the operation was successful, and `1` if the operation failed
/// due to a handle violation.
///
/// [Intel's documentation](https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_aesdecwide256kl_u8)
#[inline]
#[target_feature(enable = "widekl")]
#[stable(feature = "keylocker_x86", since = "1.89.0")]
#[cfg_attr(test, assert_instr(aesdecwide256kl))]
pub unsafe fn _mm_aesdecwide256kl_u8(
    output: *mut __m128i,
    input: *const __m128i,
    handle: *const u8,
) -> u8 {
    let input = &*ptr::slice_from_raw_parts(input, 8);
    let WideAesOutput(status, out0, out1, out2, out3, out4, out5, out6, out7) = aesdecwide256kl(
        handle, input[0], input[1], input[2], input[3], input[4], input[5], input[6], input[7],
    );
    *output.cast() = [out0, out1, out2, out3, out4, out5, out6, out7];
    status
}

#[cfg(test)]
mod tests {
    use crate::core_arch::x86::*;
    use stdarch_test::simd_test;

    #[target_feature(enable = "kl")]
    unsafe fn encodekey128() -> [u8; 48] {
        let mut handle = [0; 48];
        let _ = _mm_encodekey128_u32(0, _mm_setzero_si128(), handle.as_mut_ptr());
        handle
    }

    #[target_feature(enable = "kl")]
    unsafe fn encodekey256() -> [u8; 64] {
        let mut handle = [0; 64];
        let _ = _mm_encodekey256_u32(
            0,
            _mm_setzero_si128(),
            _mm_setzero_si128(),
            handle.as_mut_ptr(),
        );
        handle
    }

    #[simd_test(enable = "kl")]
    unsafe fn test_mm_encodekey128_u32() {
        encodekey128();
    }

    #[simd_test(enable = "kl")]
    unsafe fn test_mm_encodekey256_u32() {
        encodekey256();
    }

    #[simd_test(enable = "kl")]
    unsafe fn test_mm_aesenc128kl_u8() {
        let mut buffer = _mm_setzero_si128();
        let key = encodekey128();

        for _ in 0..100 {
            let status = _mm_aesenc128kl_u8(&mut buffer, buffer, key.as_ptr());
            assert_eq!(status, 0);
        }
        for _ in 0..100 {
            let status = _mm_aesdec128kl_u8(&mut buffer, buffer, key.as_ptr());
            assert_eq!(status, 0);
        }

        assert_eq_m128i(buffer, _mm_setzero_si128());
    }

    #[simd_test(enable = "kl")]
    unsafe fn test_mm_aesdec128kl_u8() {
        let mut buffer = _mm_setzero_si128();
        let key = encodekey128();

        for _ in 0..100 {
            let status = _mm_aesdec128kl_u8(&mut buffer, buffer, key.as_ptr());
            assert_eq!(status, 0);
        }
        for _ in 0..100 {
            let status = _mm_aesenc128kl_u8(&mut buffer, buffer, key.as_ptr());
            assert_eq!(status, 0);
        }

        assert_eq_m128i(buffer, _mm_setzero_si128());
    }

    #[simd_test(enable = "kl")]
    unsafe fn test_mm_aesenc256kl_u8() {
        let mut buffer = _mm_setzero_si128();
        let key = encodekey256();

        for _ in 0..100 {
            let status = _mm_aesenc256kl_u8(&mut buffer, buffer, key.as_ptr());
            assert_eq!(status, 0);
        }
        for _ in 0..100 {
            let status = _mm_aesdec256kl_u8(&mut buffer, buffer, key.as_ptr());
            assert_eq!(status, 0);
        }

        assert_eq_m128i(buffer, _mm_setzero_si128());
    }

    #[simd_test(enable = "kl")]
    unsafe fn test_mm_aesdec256kl_u8() {
        let mut buffer = _mm_setzero_si128();
        let key = encodekey256();

        for _ in 0..100 {
            let status = _mm_aesdec256kl_u8(&mut buffer, buffer, key.as_ptr());
            assert_eq!(status, 0);
        }
        for _ in 0..100 {
            let status = _mm_aesenc256kl_u8(&mut buffer, buffer, key.as_ptr());
            assert_eq!(status, 0);
        }

        assert_eq_m128i(buffer, _mm_setzero_si128());
    }

    #[simd_test(enable = "widekl")]
    unsafe fn test_mm_aesencwide128kl_u8() {
        let mut buffer = [_mm_setzero_si128(); 8];
        let key = encodekey128();

        for _ in 0..100 {
            let status = _mm_aesencwide128kl_u8(buffer.as_mut_ptr(), buffer.as_ptr(), key.as_ptr());
            assert_eq!(status, 0);
        }
        for _ in 0..100 {
            let status = _mm_aesdecwide128kl_u8(buffer.as_mut_ptr(), buffer.as_ptr(), key.as_ptr());
            assert_eq!(status, 0);
        }

        for elem in buffer {
            assert_eq_m128i(elem, _mm_setzero_si128());
        }
    }

    #[simd_test(enable = "widekl")]
    unsafe fn test_mm_aesdecwide128kl_u8() {
        let mut buffer = [_mm_setzero_si128(); 8];
        let key = encodekey128();

        for _ in 0..100 {
            let status = _mm_aesdecwide128kl_u8(buffer.as_mut_ptr(), buffer.as_ptr(), key.as_ptr());
            assert_eq!(status, 0);
        }
        for _ in 0..100 {
            let status = _mm_aesencwide128kl_u8(buffer.as_mut_ptr(), buffer.as_ptr(), key.as_ptr());
            assert_eq!(status, 0);
        }

        for elem in buffer {
            assert_eq_m128i(elem, _mm_setzero_si128());
        }
    }

    #[simd_test(enable = "widekl")]
    unsafe fn test_mm_aesencwide256kl_u8() {
        let mut buffer = [_mm_setzero_si128(); 8];
        let key = encodekey256();

        for _ in 0..100 {
            let status = _mm_aesencwide256kl_u8(buffer.as_mut_ptr(), buffer.as_ptr(), key.as_ptr());
            assert_eq!(status, 0);
        }
        for _ in 0..100 {
            let status = _mm_aesdecwide256kl_u8(buffer.as_mut_ptr(), buffer.as_ptr(), key.as_ptr());
            assert_eq!(status, 0);
        }

        for elem in buffer {
            assert_eq_m128i(elem, _mm_setzero_si128());
        }
    }

    #[simd_test(enable = "widekl")]
    unsafe fn test_mm_aesdecwide256kl_u8() {
        let mut buffer = [_mm_setzero_si128(); 8];
        let key = encodekey256();

        for _ in 0..100 {
            let status = _mm_aesdecwide256kl_u8(buffer.as_mut_ptr(), buffer.as_ptr(), key.as_ptr());
            assert_eq!(status, 0);
        }
        for _ in 0..100 {
            let status = _mm_aesencwide256kl_u8(buffer.as_mut_ptr(), buffer.as_ptr(), key.as_ptr());
            assert_eq!(status, 0);
        }

        for elem in buffer {
            assert_eq_m128i(elem, _mm_setzero_si128());
        }
    }
}
