wolfcrypt-ring-testing 0.2.1

Tests and benchmarks for wolfcrypt-ring-compat
use criterion::{criterion_group, criterion_main, Criterion};

#[derive(Debug)]
pub enum DigestAlgorithm {
    SHA1,
    SHA256,
    SHA384,
    SHA512,
    SHA512_256,
}

pub struct DigestConfig {
    algorithm: DigestAlgorithm,
}

impl DigestConfig {
    #[must_use]
    pub fn new(algorithm: DigestAlgorithm) -> DigestConfig {
        DigestConfig { algorithm }
    }
}

macro_rules! benchmark_digest {
    ( $pkg:ident ) => {
        paste::item! {
        mod [<$pkg _benchmarks>]  {

            use $pkg::{digest};

            use std::hint::black_box;
            use crate::DigestConfig;
            use digest::{Context, Digest};

            fn algorithm(config: &crate::DigestConfig) ->  &'static digest::Algorithm {
                black_box(match &config.algorithm {
                    crate::DigestAlgorithm::SHA1 => &digest::SHA1_FOR_LEGACY_USE_ONLY,
                    crate::DigestAlgorithm::SHA256 => &digest::SHA256,
                    crate::DigestAlgorithm::SHA384 => &digest::SHA384,
                    crate::DigestAlgorithm::SHA512 => &digest::SHA512,
                    crate::DigestAlgorithm::SHA512_256 => &digest::SHA512_256
                })
            }

            pub fn run_digest_incremental(config: &DigestConfig, chunk: &[u8]) {
                let mut ctx = Context::new(algorithm(&config));
                ctx.update(&chunk);
                let _: Digest = ctx.finish();
            }

            #[expect(unused_must_use)]
            pub fn run_digest_one_shot(config: &DigestConfig, chunk: &[u8]) {
                digest::digest(algorithm(&config), &chunk);
            }
        }
        }
    };
}

benchmark_digest!(wolfcrypt_ring_compat);
#[cfg(feature = "ring-benchmarks")]
benchmark_digest!(ring);

fn bench_sha1(c: &mut Criterion) {
    let config = DigestConfig::new(DigestAlgorithm::SHA1);
    bench_digest_one_shot(c, &config);
    bench_digest_incremental(c, &config);
}

fn bench_sha256(c: &mut Criterion) {
    let config = DigestConfig::new(DigestAlgorithm::SHA256);
    bench_digest_one_shot(c, &config);
    bench_digest_incremental(c, &config);
}

fn bench_sha384(c: &mut Criterion) {
    let config = DigestConfig::new(DigestAlgorithm::SHA384);
    bench_digest_one_shot(c, &config);
    bench_digest_incremental(c, &config);
}

fn bench_sha512(c: &mut Criterion) {
    let config = DigestConfig::new(DigestAlgorithm::SHA512);
    bench_digest_one_shot(c, &config);
    bench_digest_incremental(c, &config);
}

fn bench_sha512_256(c: &mut Criterion) {
    let config = DigestConfig::new(DigestAlgorithm::SHA512_256);
    bench_digest_one_shot(c, &config);
    bench_digest_incremental(c, &config);
}

const G_CHUNK_LENGTHS: [usize; 5] = [16, 256, 1350, 8192, 16384];

fn bench_digest_one_shot(c: &mut Criterion, config: &DigestConfig) {
    // Benchmark digest::digest one-shot.
    //
    // For the one-shot Rust API functions, we use the corresponding one-shot SHA functions
    // available in *wolfSSL* to avoid the latency of additional memory allocation.
    for &chunk_len in &G_CHUNK_LENGTHS {
        let chunk = vec![1u8; chunk_len];

        let bench_group_name =
            format!("DIGEST-{:?}-one-shot-{}-bytes", config.algorithm, chunk_len);
        let mut group = c.benchmark_group(bench_group_name);
        group.bench_function("wolfcrypt-ring-compat", |b| {
            b.iter(|| {
                wolfcrypt_ring_compat_benchmarks::run_digest_one_shot(config, &chunk);
            });
        });
        #[cfg(feature = "ring-benchmarks")]
        {
            group.bench_function("Ring", |b| {
                b.iter(|| {
                    ring_benchmarks::run_digest_one_shot(config, &chunk);
                });
            });
        }
    }
}

fn bench_digest_incremental(c: &mut Criterion, config: &DigestConfig) {
    // Benchmark incremental digest update/finish.
    //
    // For update/finish functions, we are consistently around 0.6 times slower on smaller
    // inputs against ring, while the difference drops off to around 1% slower on larger inputs
    // for SHA-{256, 384, 512, 512-256}. The same slower performance on smaller inputs also applies
    // for SHA-1, but the difference speeds up 2-3 times faster on larger inputs.
    // This difference is most likely caused by the additional memory allocation needed when
    // wielding the C `EVP_MD`/`EVP_MD_CTX` interfaces as mentioned in the original ring
    // implementation. Ring does the hashing block computations entirely in Rust.
    // https://github.com/briansmith/ring/blob/main/src/digest.rs#L21-L25
    for &chunk_len in &G_CHUNK_LENGTHS {
        let chunk = vec![1u8; chunk_len];

        let bench_group_name = format!(
            "DIGEST-{:?}-incremental-{}-bytes",
            config.algorithm, chunk_len
        );
        let mut group = c.benchmark_group(bench_group_name);

        group.bench_function("wolfcrypt-ring-compat", |b| {
            b.iter(|| {
                wolfcrypt_ring_compat_benchmarks::run_digest_incremental(config, &chunk);
            });
        });
        #[cfg(feature = "ring-benchmarks")]
        {
            group.bench_function("Ring", |b| {
                b.iter(|| {
                    ring_benchmarks::run_digest_incremental(config, &chunk);
                });
            });
        }
    }
}

criterion_group!(
    benches,
    bench_sha1,
    bench_sha256,
    bench_sha384,
    bench_sha512,
    bench_sha512_256
);

criterion_main!(benches);