Skip to content

File syn_base64.c

File List > src > syntropic > util > syn_base64.c

Go to the documentation of this file

#if __has_include("syn_config.h")
#include "syn_config.h"
#endif

#if !defined(SYN_USE_BASE64) || SYN_USE_BASE64

#include "syn_base64.h"

#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>

static const char B64_STD_TABLE[] =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static const char B64_URL_TABLE[] =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";

#define B64_VAL_INVALID 0xFFU
#define B64_VAL_PAD 0xFEU
#define B64_VAL_WS 0xFDU

static uint8_t decode_char(char c, bool url_mode)
{
    if (c >= 'A' && c <= 'Z') {
        return (uint8_t)(c - 'A');
    }
    if (c >= 'a' && c <= 'z') {
        return (uint8_t)(c - 'a' + 26);
    }
    if (c >= '0' && c <= '9') {
        return (uint8_t)(c - '0' + 52);
    }
    if (c == '+' && !url_mode) {
        return 62U;
    }
    if (c == '-' && url_mode) {
        return 62U;
    }
    if (c == '/' && !url_mode) {
        return 63U;
    }
    if (c == '_' && url_mode) {
        return 63U;
    }
    if (c == '=') {
        return B64_VAL_PAD;
    }
    if (c == ' ' || c == '\t' || c == '\r' || c == '\n') {
        return B64_VAL_WS;
    }
    return B64_VAL_INVALID;
}

bool syn_base64_encode(const uint8_t *src, size_t src_len, char *dst, size_t dst_size,
                       size_t *out_len)
{
    if (dst == NULL || (src == NULL && src_len > 0)) {
        return false;
    }

    size_t req_len = syn_base64_calc_encoded_size(src_len);
    if (dst_size < req_len) {
        return false;
    }

    size_t in_pos = 0;
    size_t out_pos = 0;

    while (in_pos + 2U < src_len) {
        uint32_t triple = ((uint32_t)src[in_pos] << 16U) | ((uint32_t)src[in_pos + 1U] << 8U) |
                          (uint32_t)src[in_pos + 2U];
        dst[out_pos++] = B64_STD_TABLE[(triple >> 18U) & 0x3FU];
        dst[out_pos++] = B64_STD_TABLE[(triple >> 12U) & 0x3FU];
        dst[out_pos++] = B64_STD_TABLE[(triple >> 6U) & 0x3FU];
        dst[out_pos++] = B64_STD_TABLE[triple & 0x3FU];
        in_pos += 3U;
    }

    if (in_pos < src_len) {
        size_t rem = src_len - in_pos;
        if (rem == 1U) {
            uint32_t triple = (uint32_t)src[in_pos] << 16U;
            dst[out_pos++] = B64_STD_TABLE[(triple >> 18U) & 0x3FU];
            dst[out_pos++] = B64_STD_TABLE[(triple >> 12U) & 0x3FU];
            dst[out_pos++] = '=';
            dst[out_pos++] = '=';
        } else { /* rem == 2 */
            uint32_t triple = ((uint32_t)src[in_pos] << 16U) | ((uint32_t)src[in_pos + 1U] << 8U);
            dst[out_pos++] = B64_STD_TABLE[(triple >> 18U) & 0x3FU];
            dst[out_pos++] = B64_STD_TABLE[(triple >> 12U) & 0x3FU];
            dst[out_pos++] = B64_STD_TABLE[(triple >> 6U) & 0x3FU];
            dst[out_pos++] = '=';
        }
    }

    dst[out_pos] = '\0';
    if (out_len != NULL) {
        *out_len = out_pos;
    }
    return true;
}

bool syn_base64url_encode(const uint8_t *src, size_t src_len, char *dst, size_t dst_size,
                          bool with_padding, size_t *out_len)
{
    if (dst == NULL || (src == NULL && src_len > 0)) {
        return false;
    }

    size_t in_pos = 0;
    size_t out_pos = 0;

    size_t req_len =
        with_padding ? syn_base64_calc_encoded_size(src_len) : (((src_len * 4U) + 2U) / 3U) + 1U;
    if (dst_size < req_len) {
        return false;
    }

    while (in_pos + 2U < src_len) {
        uint32_t triple = ((uint32_t)src[in_pos] << 16U) | ((uint32_t)src[in_pos + 1U] << 8U) |
                          (uint32_t)src[in_pos + 2U];
        dst[out_pos++] = B64_URL_TABLE[(triple >> 18U) & 0x3FU];
        dst[out_pos++] = B64_URL_TABLE[(triple >> 12U) & 0x3FU];
        dst[out_pos++] = B64_URL_TABLE[(triple >> 6U) & 0x3FU];
        dst[out_pos++] = B64_URL_TABLE[triple & 0x3FU];
        in_pos += 3U;
    }

    if (in_pos < src_len) {
        size_t rem = src_len - in_pos;
        if (rem == 1U) {
            uint32_t triple = (uint32_t)src[in_pos] << 16U;
            dst[out_pos++] = B64_URL_TABLE[(triple >> 18U) & 0x3FU];
            dst[out_pos++] = B64_URL_TABLE[(triple >> 12U) & 0x3FU];
            if (with_padding) {
                dst[out_pos++] = '=';
                dst[out_pos++] = '=';
            }
        } else { /* rem == 2 */
            uint32_t triple = ((uint32_t)src[in_pos] << 16U) | ((uint32_t)src[in_pos + 1U] << 8U);
            dst[out_pos++] = B64_URL_TABLE[(triple >> 18U) & 0x3FU];
            dst[out_pos++] = B64_URL_TABLE[(triple >> 12U) & 0x3FU];
            dst[out_pos++] = B64_URL_TABLE[(triple >> 6U) & 0x3FU];
            if (with_padding) {
                dst[out_pos++] = '=';
            }
        }
    }

    dst[out_pos] = '\0';
    if (out_len != NULL) {
        *out_len = out_pos;
    }
    return true;
}

static bool base64_decode_internal(const char *src, size_t src_len, uint8_t *dst, size_t dst_size,
                                   size_t *out_len, bool url_mode)
{
    if (dst == NULL || src == NULL) {
        return false;
    }

    if (src_len == 0U) {
        src_len = strlen(src);
    }

    uint32_t block = 0U;
    int bits = 0;
    size_t out_pos = 0;
    bool pad_seen = false;

    for (size_t i = 0; i < src_len; i++) {
        char c = src[i];
        uint8_t val = decode_char(c, url_mode);

        if (val == B64_VAL_WS) {
            continue;
        }

        if (val == B64_VAL_INVALID) {
            return false;
        }

        if (val == B64_VAL_PAD) {
            pad_seen = true;
            continue;
        }

        if (pad_seen) {
            /* Non-padding character encountered after padding '=' */
            return false;
        }

        block = (block << 6U) | (uint32_t)val;
        bits += 6;

        if (bits >= 8) {
            bits -= 8;
            if (out_pos >= dst_size) {
                return false;
            }
            dst[out_pos++] = (uint8_t)((block >> bits) & 0xFFU);
        }
    }

    if (out_len != NULL) {
        *out_len = out_pos;
    }
    return true;
}

bool syn_base64_decode(const char *src, size_t src_len, uint8_t *dst, size_t dst_size,
                       size_t *out_len)
{
    return base64_decode_internal(src, src_len, dst, dst_size, out_len, false);
}

bool syn_base64url_decode(const char *src, size_t src_len, uint8_t *dst, size_t dst_size,
                          size_t *out_len)
{
    return base64_decode_internal(src, src_len, dst, dst_size, out_len, true);
}

#endif /* SYN_USE_BASE64 */