const-decoder 0.3.0

Constant functions for converting hex- and base64-encoded strings into bytes
Documentation
use bech32::{ToBase32, Variant};
use rand::{thread_rng, RngCore};

use const_decoder::{Decoder, Pem};

#[test]
fn reading_from_file_works() {
    const RAW_INPUT: &[u8] = include_bytes!("certificate.crt");
    const CERT: [u8; 888] = Pem::decode(RAW_INPUT);

    let expected = pem::parse(RAW_INPUT).unwrap().contents;
    assert_eq!(CERT, expected.as_slice());
}

fn fuzz_hex_decoder<const N: usize>(samples: usize) {
    const CUSTOM_HEX: Decoder = Decoder::custom("0123456789abcdef");

    let mut rng = thread_rng();
    for _ in 0..samples {
        let mut bytes = [0_u8; N];
        rng.fill_bytes(&mut bytes);

        let encoded = hex::encode(&bytes);
        let decoded = Decoder::Hex.decode::<N>(encoded.as_bytes());
        assert_eq!(decoded, bytes);

        let decoded_custom = CUSTOM_HEX.decode::<N>(encoded.as_bytes());
        assert_eq!(decoded_custom, bytes);

        let encoded_upper_case = hex::encode_upper(&bytes);
        let decoded_upper_case = Decoder::Hex.decode::<N>(encoded_upper_case.as_bytes());
        assert_eq!(decoded_upper_case, bytes);
    }
}

#[test]
fn hex_decoder_mini_fuzz() {
    fuzz_hex_decoder::<1>(50);
    fuzz_hex_decoder::<8>(10_000);
    fuzz_hex_decoder::<16>(10_000);
    fuzz_hex_decoder::<64>(10_000);
    fuzz_hex_decoder::<1024>(10_000);
}

fn fuzz_base64_decoder<const N: usize>(samples: usize) {
    let mut rng = thread_rng();
    for _ in 0..samples {
        let mut bytes = [0_u8; N];
        rng.fill_bytes(&mut bytes);

        let encoded = base64::encode(&bytes);
        let decoded = Decoder::Base64.decode::<N>(encoded.as_bytes());
        assert_eq!(decoded, bytes);

        let encoded_no_pad = base64::encode_config(&bytes, base64::STANDARD_NO_PAD);
        let decoded_no_pad = Decoder::Base64.decode::<N>(encoded_no_pad.as_bytes());
        assert_eq!(decoded_no_pad, bytes);
    }
}

#[test]
fn base64_decoder_mini_fuzz() {
    fuzz_base64_decoder::<1>(50);
    fuzz_base64_decoder::<8>(10_000);
    fuzz_base64_decoder::<16>(10_000);
    fuzz_base64_decoder::<24>(10_000);
    fuzz_base64_decoder::<64>(10_000);
    fuzz_base64_decoder::<1024>(10_000);
}

fn fuzz_base64url_decoder<const N: usize>(samples: usize) {
    let mut rng = thread_rng();
    for _ in 0..samples {
        let mut bytes = [0_u8; N];
        rng.fill_bytes(&mut bytes);

        let encoded = base64::encode_config(&bytes, base64::URL_SAFE);
        let decoded = Decoder::Base64Url.decode::<N>(encoded.as_bytes());
        assert_eq!(decoded, bytes);

        let encoded_no_pad = base64::encode_config(&bytes, base64::URL_SAFE_NO_PAD);
        let decoded_no_pad = Decoder::Base64Url.decode::<N>(encoded_no_pad.as_bytes());
        assert_eq!(decoded_no_pad, bytes);
    }
}

#[test]
fn base64url_decoder_mini_fuzz() {
    fuzz_base64url_decoder::<1>(50);
    fuzz_base64url_decoder::<8>(10_000);
    fuzz_base64url_decoder::<16>(10_000);
    fuzz_base64url_decoder::<24>(10_000);
    fuzz_base64url_decoder::<64>(10_000);
    fuzz_base64url_decoder::<1024>(10_000);
}

const BECH32: Decoder = Decoder::custom("qpzry9x8gf2tvdw0s3jn54khce6mua7l");

fn fuzz_bech32_decoder<const N: usize>(samples: usize) {
    let mut rng = thread_rng();
    for _ in 0..samples {
        let mut bytes = [0_u8; N];
        rng.fill_bytes(&mut bytes);

        let encoded = bech32::encode("bc", bytes.to_base32(), Variant::Bech32).unwrap();
        let data_part = &encoded.as_bytes()[3..(encoded.len() - 6)];
        let decoded = BECH32.decode::<N>(data_part);
        assert_eq!(decoded, bytes);
    }
}

#[test]
fn bech32_decoder_mini_fuzz() {
    fuzz_bech32_decoder::<1>(50);
    fuzz_bech32_decoder::<8>(10_000);
    fuzz_bech32_decoder::<16>(10_000);
    fuzz_bech32_decoder::<24>(10_000);
    fuzz_bech32_decoder::<64>(10_000);
}