packages feed

wireform-core-0.2.0.0: cbits/base64.c

/*
 * RFC 4648 base64 encode / decode.
 *
 * Two SIMD-accelerated primitives, in line with the rest of
 * wireform-core:
 *
 *   * 'hs_base64_encode': SSSE3 (via simde) inner loop, 12 input
 *     bytes -> 16 output chars per iteration, scalar prologue /
 *     epilogue for the tail.
 *   * 'hs_base64_decode': SSE2 (via simde) pre-scan, 16 chars at a
 *     time, rejects any window containing a high-bit byte (which
 *     by construction is outside the base64 alphabet); the actual
 *     sextet extraction then runs through a tight scalar loop on
 *     a 256-entry decode table.  This is the same shape every
 *     wireform format takes for "validate-then-decode" SIMD: see
 *     'hs_proto_validate_utf8_fast' / 'hs_find_byte'.
 *
 * The encode SIMD path uses the well-known Mula / alfredklomp
 * formula (no LUT in memory; ASCII offset is derived from the
 * 6-bit value via a single PSHUFB on a 16-byte lookup).
 *
 * The decoder is kept scalar in the sextet-extraction step
 * because the typical wireform call site (SHA-1 = 28 base64
 * chars; proto3 JSON bytes fields = O(100s)) does not benefit
 * enough from a Mula-style PSHUFB classifier to be worth the
 * table-debugging cost.  Encoding -- which is on the hot path
 * for big payloads via the proto3 JSON mapping -- stays SSSE3.
 */

#include <stdint.h>
#include <string.h>
#include <simde/x86/sse2.h>
#include <simde/x86/ssse3.h>

static const char b64_alphabet[64] =
  "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
  "abcdefghijklmnopqrstuvwxyz"
  "0123456789+/";

/* Reverse-lookup table: ASCII -> 6-bit value, or 0xFF for any byte
 * outside the standard base64 alphabet.  '=' maps to 0xFF too;
 * the tail handler checks for it explicitly. */
static const uint8_t b64_dec_table[256] = {
  /* 0x00..0x2A (43 entries) */
  255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
  255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
  255,255,255,255,255,255,255,255,255,255,255,
  /* 0x2B '+' = 62, 0x2C ',' invalid, 0x2D '-' invalid,
   * 0x2E '.' invalid, 0x2F '/' = 63 */
  62, 255, 255, 255, 63,
  /* 0x30..0x39 '0'..'9' = 52..61 */
  52, 53, 54, 55, 56, 57, 58, 59, 60, 61,
  /* 0x3A..0x40 (7 entries) */
  255, 255, 255, 255, 255, 255, 255,
  /* 0x41..0x5A 'A'..'Z' = 0..25 */
  0,  1,  2,  3,  4,  5,  6,  7,  8,  9,
  10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
  20, 21, 22, 23, 24, 25,
  /* 0x5B..0x60 (6 entries) */
  255, 255, 255, 255, 255, 255,
  /* 0x61..0x7A 'a'..'z' = 26..51 */
  26, 27, 28, 29, 30, 31, 32, 33, 34, 35,
  36, 37, 38, 39, 40, 41, 42, 43, 44, 45,
  46, 47, 48, 49, 50, 51,
  /* 0x7B..0xFF (133 entries) */
  255, 255, 255, 255, 255,
  255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
  255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
  255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
  255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
  255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
  255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
  255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
  255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255
};

/* ------------------------------------------------------------------
 * Length helpers (RFC 4648 sec 4).
 *
 * Encode: 3-byte triplets pad up to the next multiple of 4 chars.
 * Decode (upper bound): every 4 chars produce up to 3 bytes;
 * trailing '=' chars subtract one byte each.  The exact decoded
 * length is reported by hs_base64_decode itself; this helper just
 * sizes the output buffer.
 * ------------------------------------------------------------------ */

int hs_base64_encoded_length(int in_len)
{
    if (in_len < 0) return 0;
    return ((in_len + 2) / 3) * 4;
}

int hs_base64_decoded_max_length(int in_len)
{
    if (in_len < 0) return 0;
    return (in_len / 4) * 3;
}

/* ------------------------------------------------------------------
 * Encode
 *
 * Returns the number of bytes written to @out.  Always equals
 * hs_base64_encoded_length(in_len) (padded form).
 * ------------------------------------------------------------------ */

static inline simde__m128i enc_reshuffle(simde__m128i in)
{
    /* Spread each 3-byte triplet into a 4-byte slot so the 6-bit
     * fields land in adjacent bytes. */
    in = simde_mm_shuffle_epi8(in, simde_mm_setr_epi8(
         1,  0,  2,  1,
         4,  3,  5,  4,
         7,  6,  8,  7,
        10,  9, 11, 10));

    simde__m128i t0 = simde_mm_and_si128(in, simde_mm_set1_epi32(0x0fc0fc00));
    simde__m128i t1 = simde_mm_mulhi_epu16(t0, simde_mm_set1_epi32(0x04000040));
    simde__m128i t2 = simde_mm_and_si128(in, simde_mm_set1_epi32(0x003f03f0));
    simde__m128i t3 = simde_mm_mullo_epi16(t2, simde_mm_set1_epi32(0x01000010));
    return simde_mm_or_si128(t1, t3);
}

static inline simde__m128i enc_translate(simde__m128i in)
{
    /* 6-bit value v -> ASCII char via a 16-entry PSHUFB lookup of
     * the offset to add to v.  Branchless. */
    simde__m128i lut = simde_mm_setr_epi8(
        65, 71, -4, -4, -4, -4, -4, -4,
        -4, -4, -4, -4, -19, -16, 0, 0);
    simde__m128i indices = simde_mm_subs_epu8(in, simde_mm_set1_epi8(51));
    simde__m128i mask    = simde_mm_cmpgt_epi8(in, simde_mm_set1_epi8(25));
    indices = simde_mm_sub_epi8(indices, mask);
    simde__m128i offsets = simde_mm_shuffle_epi8(lut, indices);
    return simde_mm_add_epi8(in, offsets);
}

int hs_base64_encode(const uint8_t *in, int in_len, uint8_t *out)
{
    int i = 0, j = 0;

    /* SIMD: 12 in -> 16 out per iter.  Need 16 readable input bytes
     * (we load 16 then mask out the upper 4) so guard accordingly. */
    while (in_len - i >= 16) {
        simde__m128i chunk = simde_mm_loadu_si128((const simde__m128i *)(in + i));
        simde__m128i v = enc_reshuffle(chunk);
        simde__m128i c = enc_translate(v);
        simde_mm_storeu_si128((simde__m128i *)(out + j), c);
        i += 12;
        j += 16;
    }

    /* Scalar tail: 3 in -> 4 out. */
    while (in_len - i >= 3) {
        uint32_t v = ((uint32_t)in[i]   << 16)
                   | ((uint32_t)in[i+1] <<  8)
                   |  (uint32_t)in[i+2];
        out[j+0] = (uint8_t)b64_alphabet[(v >> 18) & 0x3F];
        out[j+1] = (uint8_t)b64_alphabet[(v >> 12) & 0x3F];
        out[j+2] = (uint8_t)b64_alphabet[(v >>  6) & 0x3F];
        out[j+3] = (uint8_t)b64_alphabet[ v        & 0x3F];
        i += 3;
        j += 4;
    }

    /* RFC 4648 sec 4 padding. */
    int rem = in_len - i;
    if (rem == 1) {
        uint32_t v = (uint32_t)in[i] << 16;
        out[j+0] = (uint8_t)b64_alphabet[(v >> 18) & 0x3F];
        out[j+1] = (uint8_t)b64_alphabet[(v >> 12) & 0x3F];
        out[j+2] = (uint8_t)'=';
        out[j+3] = (uint8_t)'=';
        j += 4;
    } else if (rem == 2) {
        uint32_t v = ((uint32_t)in[i] << 16) | ((uint32_t)in[i+1] << 8);
        out[j+0] = (uint8_t)b64_alphabet[(v >> 18) & 0x3F];
        out[j+1] = (uint8_t)b64_alphabet[(v >> 12) & 0x3F];
        out[j+2] = (uint8_t)b64_alphabet[(v >>  6) & 0x3F];
        out[j+3] = (uint8_t)'=';
        j += 4;
    }
    return j;
}

/* ------------------------------------------------------------------
 * Decode
 *
 * Strict RFC 4648 decoder.  Input length must be a multiple of 4;
 * any out-of-alphabet byte (other than '=' in the trailing
 * position) produces -1.  Returns the number of output bytes
 * written on success.
 * ------------------------------------------------------------------ */

/* SSE2 fast-path probe: does this 16-byte window contain any byte
 * with the high bit set?  Any such byte is by construction outside
 * the base64 alphabet, so we can short-circuit. */
static inline int dec_window_high_bits(const uint8_t *in)
{
    simde__m128i chunk = simde_mm_loadu_si128((const simde__m128i *)in);
    return simde_mm_movemask_epi8(chunk);
}

int hs_base64_decode(const uint8_t *in, int in_len, uint8_t *out)
{
    if (in_len < 0 || (in_len & 3) != 0) return -1;
    if (in_len == 0) return 0;

    int i = 0, j = 0;
    int main_len = in_len - 4;  /* tail quartet handled separately */

    /* SIMD pre-scan + scalar decode of the main body.
     *
     * The SIMD step rejects any 16-byte window containing a byte
     * with the high bit set (cheap PMOVMSKB + branch); the scalar
     * step then decodes 4 quartets = 12 bytes from each clean
     * window.  This gives us the bulk of the SIMD win (early
     * rejection of garbage input) without the table-debugging
     * overhead of a full PSHUFB classifier. */
    while (main_len - i >= 16) {
        if (dec_window_high_bits(in + i) != 0) return -1;
        for (int k = 0; k < 16; k += 4) {
            uint8_t a = b64_dec_table[in[i+k+0]];
            uint8_t b = b64_dec_table[in[i+k+1]];
            uint8_t c = b64_dec_table[in[i+k+2]];
            uint8_t d = b64_dec_table[in[i+k+3]];
            if ((a | b | c | d) >= 64) return -1;
            uint32_t v = ((uint32_t)a << 18)
                       | ((uint32_t)b << 12)
                       | ((uint32_t)c <<  6)
                       |  (uint32_t)d;
            out[j+0] = (uint8_t)(v >> 16);
            out[j+1] = (uint8_t)(v >>  8);
            out[j+2] = (uint8_t) v;
            j += 3;
        }
        i += 16;
    }

    /* Scalar middle: remaining full quartets before the tail. */
    while (i < main_len) {
        uint8_t a = b64_dec_table[in[i+0]];
        uint8_t b = b64_dec_table[in[i+1]];
        uint8_t c = b64_dec_table[in[i+2]];
        uint8_t d = b64_dec_table[in[i+3]];
        if ((a | b | c | d) >= 64) return -1;
        uint32_t v = ((uint32_t)a << 18)
                   | ((uint32_t)b << 12)
                   | ((uint32_t)c <<  6)
                   |  (uint32_t)d;
        out[j+0] = (uint8_t)(v >> 16);
        out[j+1] = (uint8_t)(v >>  8);
        out[j+2] = (uint8_t) v;
        i += 4;
        j += 3;
    }

    /* Tail quartet: may contain '=' padding. */
    {
        uint8_t a = b64_dec_table[in[i+0]];
        uint8_t b = b64_dec_table[in[i+1]];
        if (a >= 64 || b >= 64) return -1;
        if (in[i+2] == (uint8_t)'=') {
            if (in[i+3] != (uint8_t)'=') return -1;
            uint32_t v = ((uint32_t)a << 18) | ((uint32_t)b << 12);
            out[j+0] = (uint8_t)(v >> 16);
            j += 1;
        } else if (in[i+3] == (uint8_t)'=') {
            uint8_t c = b64_dec_table[in[i+2]];
            if (c >= 64) return -1;
            uint32_t v = ((uint32_t)a << 18)
                       | ((uint32_t)b << 12)
                       | ((uint32_t)c <<  6);
            out[j+0] = (uint8_t)(v >> 16);
            out[j+1] = (uint8_t)(v >>  8);
            j += 2;
        } else {
            uint8_t c = b64_dec_table[in[i+2]];
            uint8_t d = b64_dec_table[in[i+3]];
            if (c >= 64 || d >= 64) return -1;
            uint32_t v = ((uint32_t)a << 18)
                       | ((uint32_t)b << 12)
                       | ((uint32_t)c <<  6)
                       |  (uint32_t)d;
            out[j+0] = (uint8_t)(v >> 16);
            out[j+1] = (uint8_t)(v >>  8);
            out[j+2] = (uint8_t) v;
            j += 3;
        }
        i += 4;
    }
    (void)i;
    return j;
}