//! AES-XTS tweakable block cipher (IEEE 1619, NIST SP 800-38E).
//!
//! XTS = XEX-based Tweaked codebook with ciphertext Stealing.
//!
//! XTS is the **disk encryption** mode of AES. Unlike GCM and CCM,
//! it is **not** an AEAD: there is no authentication tag, no
//! associated data, and no nonce-uniqueness assumption. Instead it
//! is a *length-preserving, deterministic, tweakable* cipher
//! designed for storage scenarios where:
//!
//! * Each storage unit (typically a 512-byte or 4096-byte sector)
//!   has a stable identifier — its **sector number** — which is
//!   used as the tweak.
//! * Encrypted sectors are written in place at the same byte
//!   position they had in plaintext (length-preserving).
//! * The ciphertext for sector `n` is independent of every other
//!   sector, so single-sector reads / writes are possible.
//!
//! Used by **LUKS2** (Linux full-disk encryption), **BitLocker**
//! (Windows since Vista), **FileVault** (macOS), **VeraCrypt**, and
//! the embedded SSD self-encryption layers (Opal SED, eDrive).
//!
//! # Construction (IEEE 1619 §5.3)
//!
//! Two AES keys: `K = K1 || K2` (so 32 bytes for XTS-AES-128 and
//! 64 bytes for XTS-AES-256). For each 16-byte block `j` of a
//! sector with sequence number `i`:
//!
//! ```text
//!     T = AES_K2(i)             ; encrypt the tweak (once per sector)
//!     for j in 0..n_blocks:
//!         C_j = AES_K1(P_j XOR T) XOR T
//!         T   = T * α  in GF(2^128)    ; α = 0x02
//! ```
//!
//! `i` is the data unit sequence number, **encoded as 16
//! little-endian bytes**. The multiplication by `α = 0x02` in
//! `GF(2^128)` reduces by `x^128 + x^7 + x^2 + x + 1` (the
//! standard XEX polynomial; `0x87` byte for the high-bit reduction).
//!
//! When the sector length is **not** a multiple of 16, the last
//! block uses **ciphertext stealing**: the last full block and the
//! short tail block are processed jointly so that the output is
//! the same length as the input. See [`AesXts::encrypt_sector`]
//! for the details.
//!
//! # API
//!
//! ```rust,ignore
//! use arcana::cipher::xts::AesXts;
//!
//! let key:    [u8; 32]   = /* K1 || K2, 32 bytes for XTS-AES-128 */;
//! let tweak:  [u8; 16]   = sector_number_le_padded;
//! let mut buf            = sector_plaintext.to_vec();
//!
//! let xts = AesXts::new(&key).unwrap();
//! xts.encrypt_sector(&tweak, &mut buf);
//! // ... write `buf` to disk ...
//! xts.decrypt_sector(&tweak, &mut buf);
//! ```
//!
//! # Key reuse warning
//!
//! XTS is **not** an AEAD. It does not detect tampering — flipping
//! one ciphertext bit just flips the same bit in plaintext (within
//! a single 16-byte block). Disk encryption tools deal with this
//! at a higher layer (file checksums, file system journals,
//! application-level MACs).
//!
//! XTS *does* offer per-sector independence: rewriting sector 100
//! does not affect sector 101. But within a sector, an attacker
//! who can corrupt ciphertext bytes can corrupt plaintext bytes
//! one-for-one. Don't use XTS for anything that needs end-to-end
//! integrity.
//!
//! # Tests
//!
//! Pinned against the standard IEEE 1619 test vectors (the same
//! ones used by OpenSSL, Crypto++, and BoringSSL).

use super::aes::Aes;
use crate::BlockCipher;

// ============================================================================
// GF(2^128) tweak multiplication by α (= 0x02), little-endian byte layout
// ============================================================================

/// Multiply a 16-byte tweak by `α = 0x02` in `GF(2^128)`, where the
/// reduction polynomial is `x^128 + x^7 + x^2 + x + 1` (the IEEE
/// 1619 XEX polynomial — note: this is the same poly as GCM but
/// with a *little-endian* byte ordering, which inverts the
/// semantics relative to GHASH).
///
/// In bit terms: shift the 128-bit integer left by 1 (so each byte
/// is shifted left by 1 with the carry coming from the previous
/// byte), and if the high bit overflowed, XOR `0x87` into byte 0.
fn gf128_mul_alpha(t: &mut [u8; 16]) {
    let mut carry: u8 = 0;
    for byte in t.iter_mut() {
        let new_carry = *byte >> 7;
        *byte = (*byte << 1) | carry;
        carry = new_carry;
    }
    if carry != 0 {
        t[0] ^= 0x87;
    }
}

// ============================================================================
// Public API
// ============================================================================

/// AES-XTS state. Holds the two AES key schedules `K1` (for the
/// data block encryption) and `K2` (for the tweak encryption).
///
/// Construction is the bottleneck — both key schedules are
/// expanded once at `new` and reused for every sector.
pub struct AesXts {
    /// AES instance with key `K1`, used for the data-path
    /// `AES_K1(P_j XOR T)` step.
    k1: Aes,
    /// AES instance with key `K2`, used for the tweak-path
    /// `T = AES_K2(i)` step (called exactly once per sector).
    k2: Aes,
}

impl AesXts {
    /// Initialise XTS with a concatenated key `K = K1 || K2`.
    ///
    /// Accepts:
    /// * 32 bytes -- XTS-AES-128 (each half is a 16-byte AES-128 key)
    /// * 64 bytes -- XTS-AES-256 (each half is a 32-byte AES-256 key)
    ///
    /// Returns `None` if the key length is invalid or if `K1 == K2`
    /// (the IEEE 1619 spec mandates the two halves be distinct, since
    /// `K1 == K2` collapses XTS to a degenerate variant of XEX with
    /// a tweak that's just `AES_K(i)` and exposes a known-plaintext
    /// distinguishing attack).
    pub fn new(key: &[u8]) -> Option<Self> {
        let half = match key.len() {
            32 => 16,
            64 => 32,
            _ => return None,
        };
        let (k1_bytes, k2_bytes) = key.split_at(half);
        // IEEE 1619 §5.1: K1 != K2.
        if k1_bytes == k2_bytes {
            return None;
        }
        Some(Self {
            k1: <Aes as BlockCipher>::new(k1_bytes),
            k2: <Aes as BlockCipher>::new(k2_bytes),
        })
    }

    /// Encrypt one sector in place.
    ///
    /// `tweak` is 16 bytes (little-endian encoding of the sector
    /// sequence number; pad with zeros for sequence numbers smaller
    /// than 128 bits, which is the common case).
    ///
    /// `data` may be any length **>= 16 bytes** (XTS is not defined
    /// for < 16 bytes — fewer than one block has nothing to "steal"
    /// from). Returns silently with the data unchanged if the
    /// length is below 16. For lengths that are multiples of 16, it
    /// is plain XEX. Otherwise the last full block and the partial
    /// tail are joined via ciphertext stealing per IEEE 1619 §5.3.2.
    pub fn encrypt_sector(&self, tweak: &[u8; 16], data: &mut [u8]) {
        if data.len() < 16 {
            return;
        }

        // Step 1: encrypt the tweak with K2.
        let mut t = *tweak;
        self.k2.encrypt_block(&mut t);

        // Step 2: process all but possibly the last two blocks
        // straightforwardly. If the data length is a multiple of
        // 16, we'll process all blocks in this loop and skip the
        // ciphertext-stealing step. Otherwise we'll stop one block
        // early and feed the trailing two blocks (one full + one
        // short) into the stealing path.
        let n = data.len();
        let full_blocks = n / 16;
        let tail = n % 16;
        let blocks_in_main = if tail == 0 { full_blocks } else { full_blocks - 1 };

        for j in 0..blocks_in_main {
            let off = j * 16;
            let mut block = [0u8; 16];
            block.copy_from_slice(&data[off..off + 16]);
            // P XOR T
            for i in 0..16 {
                block[i] ^= t[i];
            }
            // AES_K1(...)
            self.k1.encrypt_block(&mut block);
            // ... XOR T
            for i in 0..16 {
                block[i] ^= t[i];
            }
            data[off..off + 16].copy_from_slice(&block);

            // Advance the tweak.
            gf128_mul_alpha(&mut t);
        }

        // Step 3: ciphertext stealing for the last 1.x blocks (if any).
        if tail > 0 {
            // We are at the second-to-last full block (offset
            // `(full_blocks - 1) * 16`). Encrypt it with the
            // current tweak `t`.
            let last_full_off = (full_blocks - 1) * 16;
            let mut block = [0u8; 16];
            block.copy_from_slice(&data[last_full_off..last_full_off + 16]);
            for i in 0..16 {
                block[i] ^= t[i];
            }
            self.k1.encrypt_block(&mut block);
            for i in 0..16 {
                block[i] ^= t[i];
            }
            // `block` is now the encrypted "next-to-last" block;
            // it will become the *last* output block after stealing.

            // The tail block (length `tail`) takes the first `tail`
            // bytes of `block` to round itself out to 16 bytes;
            // the original tail bytes XOR-replace the high `tail`
            // bytes of `block`. (See IEEE 1619 §5.3.2 figure for
            // a graphical view.)
            let tail_off = full_blocks * 16;
            let mut cc = [0u8; 16];
            // High `16 - tail` bytes of `cc` come from the encrypted
            // last full block (the "stolen" bytes).
            cc[tail..].copy_from_slice(&block[tail..]);
            // Low `tail` bytes of `cc` come from the original tail
            // plaintext.
            cc[..tail].copy_from_slice(&data[tail_off..tail_off + tail]);

            // Advance the tweak (one more α-multiply for the
            // "stolen" final block).
            gf128_mul_alpha(&mut t);

            // Encrypt `cc` with the advanced tweak.
            for i in 0..16 {
                cc[i] ^= t[i];
            }
            self.k1.encrypt_block(&mut cc);
            for i in 0..16 {
                cc[i] ^= t[i];
            }

            // Write outputs:
            // - The "encrypted-tweaked-stolen" block `cc` is the
            //   new last full block at offset `last_full_off`.
            // - The first `tail` bytes of `block` (the original
            //   penultimate-block ciphertext) become the tail of
            //   the output, at offset `tail_off`.
            data[last_full_off..last_full_off + 16].copy_from_slice(&cc);
            data[tail_off..tail_off + tail].copy_from_slice(&block[..tail]);
        }
    }

    /// Decrypt one sector in place.
    ///
    /// Inverse of [`Self::encrypt_sector`]. Same length / tweak conventions.
    pub fn decrypt_sector(&self, tweak: &[u8; 16], data: &mut [u8]) {
        if data.len() < 16 {
            return;
        }

        // Encrypt the tweak with K2 (same as on encrypt -- the
        // tweak path is symmetric).
        let mut t = *tweak;
        self.k2.encrypt_block(&mut t);

        let n = data.len();
        let full_blocks = n / 16;
        let tail = n % 16;
        let blocks_in_main = if tail == 0 { full_blocks } else { full_blocks - 1 };

        for j in 0..blocks_in_main {
            let off = j * 16;
            let mut block = [0u8; 16];
            block.copy_from_slice(&data[off..off + 16]);
            for i in 0..16 {
                block[i] ^= t[i];
            }
            self.k1.decrypt_block(&mut block);
            for i in 0..16 {
                block[i] ^= t[i];
            }
            data[off..off + 16].copy_from_slice(&block);

            gf128_mul_alpha(&mut t);
        }

        // Ciphertext stealing on decrypt: same shape, but the
        // tweak for the second-to-last full block is **the
        // ADVANCED tweak**, not the current one. We compute the
        // advanced tweak first, decrypt the last full block with
        // it, recover the stolen bytes, then decrypt the
        // (penultimate) block with the un-advanced tweak.
        if tail > 0 {
            let last_full_off = (full_blocks - 1) * 16;
            let tail_off = full_blocks * 16;

            // Save the current tweak; we'll need it after the
            // advance for the second-to-last block.
            let mut t_advanced = t;
            gf128_mul_alpha(&mut t_advanced);

            // Decrypt the last full block (the one at last_full_off,
            // which currently holds the "encrypted-stolen" block)
            // with the *advanced* tweak.
            let mut block = [0u8; 16];
            block.copy_from_slice(&data[last_full_off..last_full_off + 16]);
            for i in 0..16 {
                block[i] ^= t_advanced[i];
            }
            self.k1.decrypt_block(&mut block);
            for i in 0..16 {
                block[i] ^= t_advanced[i];
            }
            // `block` now contains: low `tail` bytes = original
            // penultimate plaintext, high `16 - tail` bytes = the
            // bytes that were "stolen" from the encrypted second-
            // to-last block.

            // Reconstruct the second-to-last ciphertext block by
            // taking the low `tail` bytes from the on-disk tail
            // followed by the high `16 - tail` bytes of `block`.
            let mut cc = [0u8; 16];
            cc[..tail].copy_from_slice(&data[tail_off..tail_off + tail]);
            cc[tail..].copy_from_slice(&block[tail..]);

            // Decrypt `cc` with the un-advanced tweak.
            for i in 0..16 {
                cc[i] ^= t[i];
            }
            self.k1.decrypt_block(&mut cc);
            for i in 0..16 {
                cc[i] ^= t[i];
            }

            // Write outputs.
            data[last_full_off..last_full_off + 16].copy_from_slice(&cc);
            data[tail_off..tail_off + tail].copy_from_slice(&block[..tail]);
        }
    }
}

// ============================================================================
// Tests (IEEE 1619 / Crypto++ pinned vectors)
// ============================================================================

#[cfg(test)]
mod tests {
    use super::*;

    fn hex(s: &str) -> Vec<u8> {
        let s: String = s.chars().filter(|c| !c.is_whitespace()).collect();
        assert!(s.len() % 2 == 0);
        (0..s.len())
            .step_by(2)
            .map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap())
            .collect()
    }

    fn hex_arr<const N: usize>(s: &str) -> [u8; N] {
        let v = hex(s);
        assert_eq!(v.len(), N);
        let mut out = [0u8; N];
        out.copy_from_slice(&v);
        out
    }

    /// **IEEE 1619 / Crypto++ test vector 1** -- the all-zero canary.
    ///
    /// Key:        00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    ///             00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    /// Tweak (i):  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    /// Plain:      00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    ///             00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    /// Cipher:     91 7c f6 9e bd 68 b2 ec 9b 9f e9 a3 ea dd a6 92
    ///             cd 43 d2 f5 95 98 ed 85 8c 02 c2 65 2f bf 92 2e
    ///
    /// This vector is a sharp test for two classes of bugs:
    /// (1) any state contamination from a non-zero K1, K2, or
    /// initial tweak would change the output; (2) the K1 == K2
    /// rejection: this vector intentionally uses K1 == K2 == 0,
    /// which our `new` rightly rejects -- so we have to construct
    /// the AES instances directly to exercise it. We do that via
    /// the test-only path below.
    ///
    /// Note: for the public API, the K1 == K2 == 0 case is
    /// (rightly) refused. We bypass that here only to validate the
    /// raw construction against the canonical reference.
    #[test]
    fn ieee1619_vector_1_all_zero() {
        let zero = [0u8; 16];
        let xts = AesXts {
            k1: <Aes as BlockCipher>::new(&zero),
            k2: <Aes as BlockCipher>::new(&zero),
        };
        let tweak: [u8; 16] = [0u8; 16];
        let mut data = [0u8; 32];
        xts.encrypt_sector(&tweak, &mut data);

        let expected = hex("917cf69ebd68b2ec9b9fe9a3eadda692\
             cd43d2f59598ed858c02c2652fbf922e");
        assert_eq!(data.to_vec(), expected);

        // Round-trip
        xts.decrypt_sector(&tweak, &mut data);
        assert_eq!(data, [0u8; 32]);
    }

    /// **IEEE 1619 / Crypto++ test vector 2.**
    ///
    /// Key:    11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11
    ///         22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22
    /// Tweak:  33 33 33 33 33 00 00 00 00 00 00 00 00 00 00 00
    /// Plain:  44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44
    ///         44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44
    /// Cipher: c4 54 18 5e 6a 16 93 6e 39 33 40 38 ac ef 83 8b
    ///         fb 18 6f ff 74 80 ad c4 28 93 82 ec d6 d3 94 f0
    #[test]
    fn ieee1619_vector_2() {
        let key = hex("11111111111111111111111111111111\
             22222222222222222222222222222222");
        let tweak: [u8; 16] = hex_arr("33333333330000000000000000000000");
        let mut data = hex("44444444444444444444444444444444\
             44444444444444444444444444444444");

        let xts = AesXts::new(&key).unwrap();
        xts.encrypt_sector(&tweak, &mut data);

        let expected = hex("c454185e6a16936e39334038acef838b\
             fb186fff7480adc4289382ecd6d394f0");
        assert_eq!(data, expected);

        // Round-trip
        xts.decrypt_sector(&tweak, &mut data);
        let original = hex("44444444444444444444444444444444\
             44444444444444444444444444444444");
        assert_eq!(data, original);
    }

    /// **IEEE 1619 / Crypto++ test vector 3** -- different K1, same
    /// K2 / tweak / plaintext as vector 2 to cross-check that K1
    /// is actually consumed in the data path.
    ///
    /// Key:    ff fe fd fc fb fa f9 f8 f7 f6 f5 f4 f3 f2 f1 f0
    ///         22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22
    /// Tweak:  33 33 33 33 33 00 00 00 00 00 00 00 00 00 00 00
    /// Plain:  44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44
    ///         44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44
    /// Cipher: af 85 33 6b 59 7a fc 1a 90 0b 2e b2 1e c9 49 d2
    ///         92 df 4c 04 7e 0b 21 53 21 86 a5 97 1a 22 7a 89
    #[test]
    fn ieee1619_vector_3() {
        let key = hex("fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0\
             22222222222222222222222222222222");
        let tweak: [u8; 16] = hex_arr("33333333330000000000000000000000");
        let mut data = hex("44444444444444444444444444444444\
             44444444444444444444444444444444");

        let xts = AesXts::new(&key).unwrap();
        xts.encrypt_sector(&tweak, &mut data);

        let expected = hex("af85336b597afc1a900b2eb21ec949d2\
             92df4c047e0b21532186a5971a227a89");
        assert_eq!(data, expected);
    }

    /// Build a 32-byte XTS key with K1 != K2 (the IEEE 1619 spec
    /// requires the two halves to be distinct, and our `new` enforces
    /// it). Used by every property test below.
    fn distinct_key_32() -> [u8; 32] {
        let mut k = [0u8; 32];
        for i in 0..16 {
            k[i] = i as u8 ^ 0x42;
        }
        for i in 16..32 {
            k[i] = i as u8 ^ 0xa5;
        }
        k
    }

    /// Round-trip on a multi-block sector (4 full blocks = 64 bytes).
    /// Tests the tweak advance / `gf128_mul_alpha` path across more
    /// than two blocks.
    #[test]
    fn xts_multi_block_roundtrip() {
        let key = distinct_key_32();
        let tweak = [0xa5u8; 16];
        let original: Vec<u8> = (0..64).map(|i| i as u8).collect();
        let mut data = original.clone();

        let xts = AesXts::new(&key).unwrap();
        xts.encrypt_sector(&tweak, &mut data);
        assert_ne!(data, original);

        xts.decrypt_sector(&tweak, &mut data);
        assert_eq!(data, original);
    }

    /// Round-trip with ciphertext stealing: 17 bytes (1 full + 1 byte).
    #[test]
    fn xts_ciphertext_stealing_17_bytes() {
        let key = distinct_key_32();
        let tweak = [0xa5u8; 16];
        let original: Vec<u8> = (0..17).map(|i| (i * 11) as u8).collect();
        let mut data = original.clone();

        let xts = AesXts::new(&key).unwrap();
        xts.encrypt_sector(&tweak, &mut data);
        assert_eq!(data.len(), 17, "XTS must be length-preserving");
        assert_ne!(data, original);

        xts.decrypt_sector(&tweak, &mut data);
        assert_eq!(data, original);
    }

    /// Round-trip with ciphertext stealing: 31 bytes (1 full + 15 bytes).
    /// Exercises the maximum tail length.
    #[test]
    fn xts_ciphertext_stealing_31_bytes() {
        let key = distinct_key_32();
        let tweak = [0xa5u8; 16];
        let original: Vec<u8> = (0..31).map(|i| (i * 7) as u8).collect();
        let mut data = original.clone();

        let xts = AesXts::new(&key).unwrap();
        xts.encrypt_sector(&tweak, &mut data);
        assert_eq!(data.len(), 31);

        xts.decrypt_sector(&tweak, &mut data);
        assert_eq!(data, original);
    }

    /// Round-trip with ciphertext stealing: 100 bytes (6 full + 4 bytes).
    /// Tests stealing combined with multiple full blocks.
    #[test]
    fn xts_ciphertext_stealing_100_bytes() {
        let key = distinct_key_32();
        let tweak = [0xa5u8; 16];
        let original: Vec<u8> = (0..100).map(|i| (i ^ 0x5a) as u8).collect();
        let mut data = original.clone();

        let xts = AesXts::new(&key).unwrap();
        xts.encrypt_sector(&tweak, &mut data);
        assert_eq!(data.len(), 100);

        xts.decrypt_sector(&tweak, &mut data);
        assert_eq!(data, original);
    }

    /// Different tweaks must produce different ciphertexts for the
    /// same plaintext + key. This is the *whole point* of XTS:
    /// per-sector independence.
    #[test]
    fn xts_different_tweaks_differ() {
        let key = distinct_key_32();
        let tweak1 = [0u8; 16];
        let mut tweak2 = [0u8; 16];
        tweak2[0] = 1;
        let original: Vec<u8> = (0..32).map(|i| i as u8).collect();

        let xts = AesXts::new(&key).unwrap();

        let mut data1 = original.clone();
        xts.encrypt_sector(&tweak1, &mut data1);

        let mut data2 = original.clone();
        xts.encrypt_sector(&tweak2, &mut data2);

        assert_ne!(data1, data2);
    }

    /// XTS-AES-256 round-trip (64-byte concatenated key).
    #[test]
    fn xts_aes256_roundtrip() {
        let mut key = [0u8; 64];
        for i in 0..32 {
            key[i] = i as u8;
        }
        for i in 32..64 {
            key[i] = (i + 0x80) as u8;
        }
        let tweak = [0x11u8; 16];
        let original: Vec<u8> = (0..48).map(|i| i as u8).collect();
        let mut data = original.clone();

        let xts = AesXts::new(&key).unwrap();
        xts.encrypt_sector(&tweak, &mut data);
        xts.decrypt_sector(&tweak, &mut data);
        assert_eq!(data, original);
    }

    /// Parameter validation: invalid key lengths are rejected.
    #[test]
    fn xts_rejects_invalid_key_lengths() {
        for bad_len in [0, 1, 15, 16, 17, 24, 31, 33, 48, 63, 65, 128] {
            let key = vec![0u8; bad_len];
            assert!(AesXts::new(&key).is_none(), "key length {} should be rejected", bad_len);
        }
        // Valid lengths.
        // We need K1 != K2 to actually construct, so use distinct halves.
        let mut k32 = [0u8; 32];
        k32[16] = 1;
        assert!(AesXts::new(&k32).is_some());
        let mut k64 = [0u8; 64];
        k64[32] = 1;
        assert!(AesXts::new(&k64).is_some());
    }

    /// Parameter validation: K1 == K2 is rejected per IEEE 1619 §5.1.
    #[test]
    fn xts_rejects_k1_eq_k2() {
        // 32-byte key with K1 == K2 == all 1's
        let k32 = [0x11u8; 32];
        assert!(AesXts::new(&k32).is_none());
        // 64-byte key with K1 == K2 == all 1's
        let k64 = [0x11u8; 64];
        assert!(AesXts::new(&k64).is_none());
    }

    /// gf128_mul_alpha sanity test: shifting `0x01 || 0...0` should
    /// give `0x02 || 0...0`. Shifting `0x80 || 0...0` (highest byte
    /// of byte 0) should give `0x00 || 0...0` with no carry, so we
    /// also test `0...0 || 0x80` which sets the high bit overall.
    #[test]
    fn gf128_mul_alpha_basic() {
        // Test 1: shift 0x01 -> 0x02
        let mut t = [0u8; 16];
        t[0] = 0x01;
        gf128_mul_alpha(&mut t);
        let mut expected = [0u8; 16];
        expected[0] = 0x02;
        assert_eq!(t, expected);

        // Test 2: shift the highest bit of the last byte -- this
        // is the bit that overflows out of the 128-bit register and
        // triggers the `0x87` reduction XOR on byte 0.
        let mut t = [0u8; 16];
        t[15] = 0x80;
        gf128_mul_alpha(&mut t);
        let mut expected = [0u8; 16];
        expected[0] = 0x87;
        assert_eq!(t, expected);
    }
}
