/*
 * Base 64 encoding functions
 *
 * SPDX-FileType: SOURCE
 * SPDX-FileCopyrightText: Michael Bäuerle
 * SPDX-License-Identifier: BSD-2-Clause
 */

#include <assert.h>

#include "libbasexx-0/base64_encode.h"


/* US-ASCII Pad character according to RFC 4648 Section 3.2 */
#define BXX0_I_PAD  0x3D

#define BXX0_I_ENCODE_ALPHABET_SIZE  64U


/* ========================================================================== */
/*
 * Base 64 alphabet access
 *
 * Independent of execution character set.
 *
 * Return Base 64 alphabet US-ASCII character.
 */
static unsigned char bxx0_i_base64_alphabet(const unsigned char index)
{
    static const unsigned char alphabet[BXX0_I_ENCODE_ALPHABET_SIZE] =
    {
        0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,
        0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50,
        0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,
        0x59, 0x5A, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66,
        0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E,
        0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76,
        0x77, 0x78, 0x79, 0x7A, 0x30, 0x31, 0x32, 0x33,
        0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x2B, 0x2F
    };

    assert(BXX0_I_ENCODE_ALPHABET_SIZE > index);
    return alphabet[index];
}


/* ========================================================================== */
/* Read three octets from "triple" and write four characters to "quad" */
static void bxx0_i_base64_encode(unsigned char       *quad,
                                 const unsigned char *triple)
{
    unsigned char index;

    index    = (triple[0] & 0xFC) >> 2;
    quad[0]  = bxx0_i_base64_alphabet(index);

    index    = (triple[0] & 0x03) << 4;
    index   |= ((triple[1] & 0xF0) >> 4);
    quad[1]  = bxx0_i_base64_alphabet(index);

    index    = (triple[1] & 0x0F) << 2;
    index   |= ((triple[2] & 0xC0) >> 6);
    quad[2]  = bxx0_i_base64_alphabet(index);

    index    = triple[2] & 0x3F;
    quad[3]  = bxx0_i_base64_alphabet(index);
}


/* ========================================================================== */
/*
 * Read "len_rem" octets from "rem" and write up to four characters to "quad"
 *
 * The value for "len_rem" must be 1 or 2 (the potential remaining incomplete
 * triplet at the end of the input data).
 */
static void bxx0_i_base64_encpad(unsigned char       *quad,
                                 const unsigned char *rem, const size_t len_rem)
{
    /* C90 does not allow aggregate initializer with non-constant expressions */
    unsigned char triple[3] = { 0, 0, 0 };

    triple[0]     = rem[0];
    if (2U == len_rem)
        triple[1] = rem[1];

    bxx0_i_base64_encode(quad, triple);

    if (1U == len_rem)
        quad[2] = BXX0_I_PAD;
    quad[3]     = BXX0_I_PAD;
}


/* ========================================================================== */
signed char bxx0_base64_encode(unsigned char       *out, size_t *len_out,
                               const unsigned char *in , size_t *len_in,
                               const unsigned char  flags)
{
    signed char  ret      = 0;
    size_t       triples  = *len_in / 3U;
    const size_t len_rem  = *len_in % 3U;
    const size_t quads    = len_rem ? triples + 1U : triples;
    size_t       len_data = 4U * quads;
    size_t       i_out    = 0;
    size_t       i_in     = 0;

    if (0U == *len_in)
        return ret;

    /* Prevent overflow of output buffer */
    if ((*len_out / 4U) < quads)
        return BXX0_BASE64_ENCODE_ERROR_SIZE;

    for (; triples; --triples, i_in += 3U, i_out += 4U)
        bxx0_i_base64_encode(&out[i_out], &in[i_in]);

    if (0U != len_rem)
    {
        assert((1U == len_rem) || (2U == len_rem));
        bxx0_i_base64_encpad(&out[i_out], &in[i_in], len_rem);
        if (BXX0_BASE64_ENCODE_FLAG_NOPAD & flags)
        {
            len_data -= (3U - len_rem);
            ret |= BXX0_BASE64_ENCODE_FLAG_NOPAD;
        }
    }

    *len_in   = 0;
    *len_out -= len_data;
    return ret;
}
