packages feed

base64-2.0: test/simd-native.c

/* Copyright (c) 2026, Emily Pillmore. BSD-3-Clause; see LICENSE. */

/*
 * Standalone differential/bounds checks; link with cbits/base64_simd.c.
 * Recommended: -std=c99 -O2 -Wall -Wextra -Werror
 *              -fsanitize=address,undefined -fno-omit-frame-pointer
 * Repeat with -DHS_BASE64_SIMD_FORCE_SCALAR to test fallback compilation.
 */
#ifndef _DEFAULT_SOURCE
#define _DEFAULT_SOURCE 1
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#if defined(__unix__) || defined(__APPLE__)
#include <sys/mman.h>
#include <unistd.h>
#define HS_BASE64_GUARD_PAGES 1
#endif

#include "base64_simd.h"

#define CHECK(condition) do { \
    if (!(condition)) { \
        fprintf(stderr, "%s:%d: check failed: %s\n", __FILE__, __LINE__, #condition); \
        exit(EXIT_FAILURE); \
    } \
} while (0)

static const uint8_t alphabets[2][65] = {
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
};

static size_t encode_block;
static size_t decode_block;
static size_t checks;

static void make_table(const uint8_t *alphabet, uint8_t *table)
{
    memset(table, 255, 256);
    for (size_t i = 0; i < 64; i++) {
        table[alphabet[i]] = (uint8_t)i;
    }
    table['='] = 64;
}

static size_t encode_prefix(size_t len)
{
    return encode_block == 0 ? 0 : len / encode_block * encode_block;
}

static size_t decode_prefix(const uint8_t *src, size_t len, const uint8_t *table)
{
    size_t consumed = 0;

    if (decode_block == 0 || len < 4) {
        return 0;
    }
    while (len - consumed >= decode_block + 4) {
        for (size_t i = 0; i < decode_block; i++) {
            if (table[src[consumed + i]] >= 64) {
                return consumed;
            }
        }
        consumed += decode_block;
    }
    return consumed;
}

static void scalar_encode(const uint8_t *src, size_t len, uint8_t *dst,
                          const uint8_t *alphabet)
{
    for (size_t i = 0; i < len; i += 3) {
        const unsigned triple = ((unsigned)src[i] << 16) |
                                ((unsigned)src[i + 1] << 8) | src[i + 2];
        *dst++ = alphabet[(triple >> 18) & 63];
        *dst++ = alphabet[(triple >> 12) & 63];
        *dst++ = alphabet[(triple >> 6) & 63];
        *dst++ = alphabet[triple & 63];
    }
}

static void scalar_decode(const uint8_t *src, size_t len, uint8_t *dst,
                          const uint8_t *table)
{
    for (size_t i = 0; i < len; i += 4) {
        const unsigned quartet = ((unsigned)table[src[i]] << 18) |
                                 ((unsigned)table[src[i + 1]] << 12) |
                                 ((unsigned)table[src[i + 2]] << 6) |
                                 table[src[i + 3]];
        *dst++ = (uint8_t)(quartet >> 16);
        *dst++ = (uint8_t)(quartet >> 8);
        *dst++ = (uint8_t)quartet;
    }
}

static void check_canary(const uint8_t *data, size_t len)
{
    for (size_t i = 0; i < len; i++) {
        CHECK(data[i] == 0xa5);
    }
}

static void check_encode(const uint8_t *src, size_t len, size_t offset,
                         const uint8_t *alphabet)
{
    const size_t expected = encode_prefix(len);
    const size_t output_len = expected / 3 * 4;
    uint8_t *output = malloc(offset + output_len + 16);
    uint8_t *reference = malloc(output_len + 1);
    size_t consumed;

    CHECK(output != NULL && reference != NULL);
    memset(output, 0xa5, offset + output_len + 16);
    scalar_encode(src, expected, reference, alphabet);
    consumed = hs_base64_simd_encode(src, len, output + offset, alphabet);
    CHECK(consumed == expected);
    CHECK(consumed <= len && consumed % 3 == 0);
    CHECK(memcmp(output + offset, reference, output_len) == 0);
    check_canary(output, offset);
    check_canary(output + offset + output_len, 16);
    free(reference);
    free(output);
    checks++;
}

static void check_decode(const uint8_t *src, size_t len, size_t offset,
                         const uint8_t *table)
{
    const size_t expected = decode_prefix(src, len, table);
    const size_t output_len = expected / 4 * 3;
    uint8_t *output = malloc(offset + output_len + 16);
    uint8_t *reference = malloc(output_len + 1);
    size_t consumed;

    CHECK(output != NULL && reference != NULL);
    memset(output, 0xa5, offset + output_len + 16);
    scalar_decode(src, expected, reference, table);
    consumed = hs_base64_simd_decode(src, len, output + offset, table);
    CHECK(consumed == expected);
    CHECK(consumed % 4 == 0 && consumed <= len);
    CHECK(len < 4 ? consumed == 0 : len - consumed >= 4);
    CHECK(memcmp(output + offset, reference, output_len) == 0);
    check_canary(output, offset);
    check_canary(output + offset + output_len, 16);
    free(reference);
    free(output);
    checks++;
}

static void test_lengths_and_alignment(void)
{
    uint8_t table[256];
    uint8_t source[1056];
    uint8_t encoded[1408];

    for (size_t alphabet = 0; alphabet < 2; alphabet++) {
        make_table(alphabets[alphabet], table);
        for (size_t offset = 0; offset < 32; offset++) {
            for (size_t i = 0; i < sizeof(source); i++) {
                source[i] = (uint8_t)(i * 73 + offset * 19);
            }
            scalar_encode(source, sizeof(source), encoded, alphabets[alphabet]);
            for (size_t len = 0; len <= 1024; len++) {
                check_encode(source + offset, len, 31 - offset, alphabets[alphabet]);
                check_decode(encoded + offset, len, 31 - offset, table);
            }
        }
    }
}

static void test_all_byte_values(void)
{
    uint8_t source[257];
    uint8_t encoded[344];
    uint8_t table[256];

    for (size_t alphabet = 0; alphabet < 2; alphabet++) {
        make_table(alphabets[alphabet], table);
        for (unsigned value = 0; value <= 255; value++) {
            memset(source, (int)value, sizeof(source));
            check_encode(source, sizeof(source), 1, alphabets[alphabet]);
            scalar_encode(source, 255, encoded, alphabets[alphabet]);
            memcpy(encoded + 340, "AAAA", 4);
            check_decode(encoded, sizeof(encoded), 1, table);
        }
    }
}

static void test_invalid_and_padding_stops(void)
{
    uint8_t source[580];
    uint8_t table[256];

    for (size_t alphabet = 0; alphabet < 2; alphabet++) {
        make_table(alphabets[alphabet], table);
        for (size_t position = 0; position < sizeof(source); position++) {
            for (unsigned value = 0; value <= 255; value++) {
                memset(source, 'A', sizeof(source));
                source[position] = (uint8_t)value;
                check_decode(source, sizeof(source), position % 32, table);
            }
        }
        /* In particular, every kind of final padding stays with the caller. */
        memset(source, '/', sizeof(source));
        for (size_t padding = 0; padding <= 4; padding++) {
            memset(source, alphabets[alphabet][63], sizeof(source));
            memset(source + sizeof(source) - padding, '=', padding);
            check_decode(source, sizeof(source), 0, table);
        }
    }
}

/* Fixed-seed xorshift exercises byte combinations outside repeated/cyclic data. */
static uint32_t next_random(uint32_t *state)
{
    uint32_t value = *state;
    value ^= value << 13;
    value ^= value >> 17;
    value ^= value << 5;
    *state = value;
    return value;
}

static void test_random_mixed_inputs(void)
{
    uint8_t source[6144];
    uint8_t encoded[8192];
    uint8_t table[256];
    uint32_t state = UINT32_C(0x6d253fb9);

    for (size_t alphabet = 0; alphabet < 2; alphabet++) {
        make_table(alphabets[alphabet], table);
        for (size_t sample = 0; sample < 256; sample++) {
            const size_t len = next_random(&state) % (sizeof(source) + 1);
            const size_t encoded_len = len / 3 * 4;
            const size_t offset = next_random(&state) % 32;

            for (size_t i = 0; i < len; i++) {
                source[i] = (uint8_t)next_random(&state);
            }
            check_encode(source, len, offset, alphabets[alphabet]);
            scalar_encode(source, len / 3 * 3, encoded, alphabets[alphabet]);
            check_decode(encoded, encoded_len, offset, table);
            if (encoded_len > 0) {
                const size_t position = next_random(&state) % encoded_len;
                encoded[position] = (uint8_t)next_random(&state);
                check_decode(encoded, encoded_len, offset, table);
            }
        }
    }
}

static void test_tight_allocations(void)
{
    uint8_t table[256];

    for (size_t alphabet = 0; alphabet < 2; alphabet++) {
        make_table(alphabets[alphabet], table);
        for (size_t len = 1; len <= 1024; len++) {
            uint8_t *source = malloc(len);
            const size_t encode_len = encode_prefix(len) / 3 * 4;
            uint8_t *encoded = malloc(encode_len == 0 ? 1 : encode_len);
            size_t decode_len;
            uint8_t *decoded;

            CHECK(source != NULL && encoded != NULL);
            memset(source, 0xfb, len);
            CHECK(hs_base64_simd_encode(source, len, encoded, alphabets[alphabet]) ==
                  encode_prefix(len));
            memset(source, alphabets[alphabet][63], len);
            decode_len = decode_prefix(source, len, table) / 4 * 3;
            decoded = malloc(decode_len == 0 ? 1 : decode_len);
            CHECK(decoded != NULL);
            CHECK(hs_base64_simd_decode(source, len, decoded, table) ==
                  decode_prefix(source, len, table));
            free(decoded);
            free(encoded);
            free(source);
            checks += 2;
        }
    }
}

#if defined(HS_BASE64_GUARD_PAGES)
/* Guard pages independently catch over-wide intrinsic loads/stores. */
static uint8_t *guarded_buffer(size_t page_size)
{
    uint8_t *mapping = mmap(NULL, page_size * 3, PROT_NONE,
                            MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    CHECK(mapping != MAP_FAILED);
    CHECK(mprotect(mapping + page_size, page_size, PROT_READ | PROT_WRITE) == 0);
    return mapping;
}

static void test_guard_pages(void)
{
    const long raw_page_size = sysconf(_SC_PAGESIZE);
    size_t page_size;
    uint8_t *source_mapping;
    uint8_t *output_mapping;
    uint8_t table[256];

    CHECK(raw_page_size > 0);
    page_size = (size_t)raw_page_size;
    source_mapping = guarded_buffer(page_size);
    output_mapping = guarded_buffer(page_size);

    for (size_t alphabet = 0; alphabet < 2; alphabet++) {
        make_table(alphabets[alphabet], table);
        for (size_t len = 0; len <= 1024; len++) {
            for (size_t at_end = 0; at_end < 2; at_end++) {
                uint8_t *source = source_mapping + page_size +
                                  (at_end ? page_size - len : 0);
                const size_t encode_len = encode_prefix(len) / 3 * 4;
                uint8_t *output = output_mapping + page_size +
                                  (at_end ? page_size - encode_len : 0);
                size_t expected;
                size_t decode_len;

                memset(source, 0xfb, len);
                CHECK(hs_base64_simd_encode(source, len, output, alphabets[alphabet]) ==
                      encode_prefix(len));
                memset(source, alphabets[alphabet][63], len);
                expected = decode_prefix(source, len, table);
                decode_len = expected / 4 * 3;
                output = output_mapping + page_size + (at_end ? page_size - decode_len : 0);
                CHECK(hs_base64_simd_decode(source, len, output, table) == expected);
                checks += 2;
            }
        }
    }
    CHECK(munmap(source_mapping, page_size * 3) == 0);
    CHECK(munmap(output_mapping, page_size * 3) == 0);
}
#endif

int main(int argc, char **argv)
{
    const int backend = hs_base64_simd_backend();

    if (argc != 1) {
        if (argc != 3 || strcmp(argv[1], "--require-backend") != 0 ||
            strlen(argv[2]) != 1 || argv[2][0] < '0' || argv[2][0] > '3') {
            fprintf(stderr, "usage: %s [--require-backend 0|1|2|3]\n", argv[0]);
            return EXIT_FAILURE;
        }
        if (backend != argv[2][0] - '0') {
            fprintf(stderr, "required SIMD backend %c, detected %d\n", argv[2][0], backend);
            return EXIT_FAILURE;
        }
    }
    CHECK(backend >= 0 && backend <= 3);
#if defined(HS_BASE64_SIMD_FORCE_SCALAR)
    CHECK(backend == 0);
#elif defined(HS_BASE64_SIMD_FORCE_SSSE3) && defined(__x86_64__)
    CHECK(backend == 0 || backend == 1);
#elif defined(__aarch64__) && defined(__ARM_NEON) && \
      (defined(__GNUC__) || defined(__clang__)) && !defined(HS_BASE64_SIMD_EMULATED_X86)
    CHECK(backend == 2);
#endif
    encode_block = backend == 3 ? 24 : backend == 2 ? 48 : backend == 1 ? 12 : 0;
    decode_block = backend == 3 ? 32 : backend == 2 ? 64 : backend == 1 ? 16 : 0;
    printf("SIMD backend: %d (%s)\n", backend,
           backend == 3 ? "AVX2" : backend == 2 ? "NEON" :
           backend == 1 ? "SSSE3" : "scalar fallback");
    test_lengths_and_alignment();
    test_all_byte_values();
    test_invalid_and_padding_stops();
    test_random_mixed_inputs();
    test_tight_allocations();
#if defined(HS_BASE64_GUARD_PAGES)
    test_guard_pages();
#endif
    printf("Passed %zu differential/bounds checks.\n", checks);
    return EXIT_SUCCESS;
}