philbin 1.0.1

A pure Rust AEGIS library with SIMD and runtime CPU detection
Documentation
use anyhow::Result;
use pastey::paste;
use rstest::*;
use similar_asserts::assert_eq;
use test_utils::{
  AegisTestCase, aegis128l, aegis128x2, aegis128x4, aegis256, aegis256x2,
  aegis256x4,
};

use crate::{
  arch::api::aegis_struct,
  arch::*,
  auth_tag::AuthTag128,
  base::aegis::Aegis,
  easy::{AssociatedData, Ciphertext, CiphertextMut, Plaintext, PlaintextMut},
};

// Takes a SIMD level literal and an AEGIS algo variant literal.
// Expands to two quick tests: one encryption, one decryption.
//
// These tests are NOT meant to verify the correctness of the shared AEGIS
// algo code; that's the job of the integration tests in `tests/` dir.
//
// The job of these tests is to verify that the SIMD structs work. These types
// have tiny amounts of code; if we see the expected AEGIS outputs when using
// each one, we're good.
//
// NOTE: These tests perform a runtime check for CPU support. If the necessary
// instruction set is not supported, the test body is skipped
// _but the test is marked as passed_. Ideally it would be marked as _skipped_,
// but I don't think that's possible to do at runtime; the libtest runner only
// supports tests being marked as skipped at compile-time.
//
// We _could_ use compile-time CPU detection for tests and then run tests with
// `RUSTFLAGS="-C target-cpu=native"`, but then the integration tests would
// also skip the runtime CPU detection paths (which is BAD since runtime
// detection is the prod code path).
//
// Call example:
//
//   gen_arch_tests!(Sse2, aegis128l)
macro_rules! gen_arch_quick_tests {
  (
    $simd:ident, // One of: Avx512, Avx2, Avx, Sse2, None
    $algo:ident  // One of: aegis128l, aegis128x2, aegis128x4,
                 //         aegis256, aegis256x2, aegis256x4
  ) => {
    paste! { // Helps with identifier construction (the [< ... >] parts)
      #[rstest]
      fn [<$algo:lower _encryption_ $simd:lower>](
        #[from([<$algo:lower>]::example)] expected: AegisTestCase)
          -> Result<()> {
        let Some(simd) = SimdLevel::supported::<$simd>() else {
          return Ok(());
        };

        type Key = crate::facade::aegis_key_type!($algo);
        type Nonce = crate::facade::aegis_nonce_type!($algo);

        let mut actual_ciphertext = vec![0; expected.plaintext.len()];
        let actual_tag: AuthTag128 =
          <aegis_struct!($algo, $simd)>::encrypt_to_slice_detached(
          simd,
          Plaintext::new(&expected.plaintext),
          AssociatedData::new(&expected.associated_data),
          &Key::from_bytes(expected.key)?,
          Nonce::new(*expected.nonce.as_array().unwrap())?,
          CiphertextMut::new(&mut actual_ciphertext),
        )?;
        assert_eq!(
          expected.ciphertext,
          actual_ciphertext[..expected.ciphertext.len()]
        );
        assert_eq!(expected.tag128, actual_tag);

        Ok(())
      }

      #[rstest]
      fn [<$algo:lower _decryption_ $simd:lower>](
        #[from([<$algo:lower>]::example)] expected: AegisTestCase)
          -> Result<()> {
        let Some(simd) = SimdLevel::supported::<$simd>() else {
          return Ok(());
        };

        type Key = crate::facade::aegis_key_type!($algo);
        type Nonce = crate::facade::aegis_nonce_type!($algo);

        let mut actual_plaintext = vec![0; expected.ciphertext.len()];
        <aegis_struct!($algo, $simd)>::decrypt_to_slice_detached(
          simd,
          Ciphertext::new(&expected.ciphertext),
          &expected.tag128,
          AssociatedData::new(&expected.associated_data),
          &Key::from_bytes(expected.key)?,
          Nonce::new(*expected.nonce.as_array().unwrap())?,
          PlaintextMut::new(&mut actual_plaintext),
        )?;
        assert_eq!(expected.plaintext, actual_plaintext);

        Ok(())
      }
    }
  };
}

// Aegis-128 variants
gen_arch_quick_tests!(Sse2, aegis128l);
gen_arch_quick_tests!(Sse2, aegis128x2);
gen_arch_quick_tests!(Sse2, aegis128x4);
gen_arch_quick_tests!(Avx, aegis128l);
gen_arch_quick_tests!(Avx, aegis128x2);
gen_arch_quick_tests!(Avx, aegis128x4);
gen_arch_quick_tests!(Avx2, aegis128x2);
gen_arch_quick_tests!(Avx2, aegis128x4);
gen_arch_quick_tests!(Avx512, aegis128x4);

// Aegis-256 variants
gen_arch_quick_tests!(Sse2, aegis256);
gen_arch_quick_tests!(Sse2, aegis256x2);
gen_arch_quick_tests!(Sse2, aegis256x4);
gen_arch_quick_tests!(Avx, aegis256);
gen_arch_quick_tests!(Avx, aegis256x2);
gen_arch_quick_tests!(Avx, aegis256x4);
gen_arch_quick_tests!(Avx2, aegis256x2);
gen_arch_quick_tests!(Avx2, aegis256x4);
gen_arch_quick_tests!(Avx512, aegis256x4);

// These are actually tests for the `Fallback` path, but we include them here
// because the `gen_arch_quick_tests` works _perfectly_ for that use-case too.
gen_arch_quick_tests!(Fallback, aegis128l);
gen_arch_quick_tests!(Fallback, aegis128x2);
gen_arch_quick_tests!(Fallback, aegis128x4);

gen_arch_quick_tests!(Fallback, aegis256);
gen_arch_quick_tests!(Fallback, aegis256x2);
gen_arch_quick_tests!(Fallback, aegis256x4);