/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:4;tab-width:4;coding:utf-8 -*-â
âvi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8                                :viâ
ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ¡
â Copyright The Mbed TLS Contributors                                          â
â                                                                              â
â Licensed under the Apache License, Version 2.0 (the "License");              â
â you may not use this file except in compliance with the License.             â
â You may obtain a copy of the License at                                      â
â                                                                              â
â     http://www.apache.org/licenses/LICENSE-2.0                               â
â                                                                              â
â Unless required by applicable law or agreed to in writing, software          â
â distributed under the License is distributed on an "AS IS" BASIS,            â
â WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.     â
â See the License for the specific language governing permissions and          â
â limitations under the License.                                               â
ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ*/
#include "libc/str/str.h"
#include "third_party/mbedtls/base64.h"
#include "third_party/mbedtls/common.h"
#include "third_party/mbedtls/platform.h"

asm(".ident\t\"\\n\\n\
Mbed TLS (Apache 2.0)\\n\
Copyright ARM Limited\\n\
Copyright Mbed TLS Contributors\"");
asm(".include \"libc/disclaimer.inc\"");
/* clang-format off */

#define ENC "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"

static const unsigned char base64_dec_map[128] =
{
    127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
    127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
    127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
    127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
    127, 127, 127,  62, 127, 127, 127,  63,  52,  53,
     54,  55,  56,  57,  58,  59,  60,  61, 127, 127,
    127,  64, 127, 127, 127,   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, 127, 127, 127, 127, 127, 127,  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, 127, 127, 127, 127, 127
};

#define BASE64_SIZE_T_MAX   ( (size_t) -1 ) /* SIZE_T_MAX is not standard */

/*
 * Constant flow conditional assignment to unsigned char 
*/
forceinline void mbedtls_base64_cond_assign_uchar( unsigned char * dest,
                                                   const unsigned char * const src,
                                                   unsigned char condition )
{
    /* Generate bitmask from condition, mask will either be 0xFF or 0 */
    unsigned char mask = ( condition | -condition );
    mask >>= 7;
    mask = -mask;
    *dest = ( ( *src ) & mask ) | ( ( *dest ) & ~mask );
}

/*
 * Constant flow conditional assignment to uint_32
 */
forceinline void mbedtls_base64_cond_assign_uint32( uint32_t * dest, const uint32_t src,
                                                    uint32_t condition )
{
    /* Generate bitmask from condition, mask will either be 0xFFFFFFFF or 0 */
    uint32_t mask = ( condition | -condition );
    mask >>= 31;
    mask = -mask;
    *dest = ( src & mask ) | ( ( *dest ) & ~mask );
}

/*
 * Constant flow check for equality
 */
forceinline unsigned char mbedtls_base64_eq( size_t in_a, size_t in_b )
{
    size_t difference = in_a ^ in_b;
    difference |= -difference;
    /* cope with the varying size of size_t per platform */
    difference >>= ( sizeof( difference ) * 8 - 1 );
    return (unsigned char) ( 1 ^ difference );
}

/*
 * Constant flow lookup into table.
 */
static inline unsigned char mbedtls_base64_table_lookup( const unsigned char * const table,
                                                         const size_t table_size,
                                                         const size_t table_index )
{
    return 0 <= table_index && table_index < table_size ? table[table_index] : 127;
    /* come on really? */
    size_t i;
    unsigned char result = 0;
    for( i = 0; i < table_size; ++i )
    {
        mbedtls_base64_cond_assign_uchar( &result, &table[i], mbedtls_base64_eq( i, table_index ) );
    }
    return result;
}

/**
 * \brief          Encode a buffer into base64 format
 *
 * \param dst      destination buffer
 * \param dlen     size of the destination buffer
 * \param olen     number of bytes written
 * \param src      source buffer
 * \param slen     amount of data to be encoded
 *
 * \return         0 if successful, or MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL.
 *                 *olen is always updated to reflect the amount
 *                 of data that has (or would have) been written.
 *                 If that length cannot be represented, then no data is
 *                 written to the buffer and *olen is set to the maximum
 *                 length representable as a size_t.
 *
 * \note           Call this function with dlen = 0 to obtain the
 *                 required buffer size in *olen
 */
int mbedtls_base64_encode( unsigned char *dst, size_t dlen, size_t *olen,
                           const unsigned char *src, size_t slen )
{
    unsigned w;
    size_t i, n;
    unsigned char *q;
    const unsigned char *p, *pe;
    if( !slen ) {
        if (dlen) *dst = 0;
        *olen = 0;
        return 0;
    }
    n = slen / 3 + ( slen % 3 != 0 );
    if( n > ( BASE64_SIZE_T_MAX - 1 ) / 4 ) {
        *olen = BASE64_SIZE_T_MAX;
        return MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL;
    }
    n *= 4;
    if( ( dlen < n + 1 ) || !dst ) {
        *olen = n + 1;
        return MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL;
    }
    for (q = dst, p = src, pe = p + slen; p < pe; p += 3) {
        w = p[0] << 020;
        if (p + 1 < pe) w |= p[1] << 010;
        if (p + 2 < pe) w |= p[2] << 000;
        *q++ = ENC[(w >> 18) & 077];
        *q++ = ENC[(w >> 12) & 077];
        *q++ = p + 1 < pe ? ENC[(w >> 6) & 077] : '=';
        *q++ = p + 2 < pe ? ENC[w & 077] : '=';
    }
    *olen = n;
    *q = 0;
    return 0;
}

/**
 * \brief          Decode a base64-formatted buffer
 *
 * \param dst      destination buffer (can be NULL for checking size)
 * \param dlen     size of the destination buffer
 * \param olen     number of bytes written
 * \param src      source buffer
 * \param slen     amount of data to be decoded
 *
 * \return         0 if successful, MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL, or
 *                 MBEDTLS_ERR_BASE64_INVALID_CHARACTER if the input data is
 *                 not correct. *olen is always updated to reflect the amount
 *                 of data that has (or would have) been written.
 *
 * \note           Call this function with *dst = NULL or dlen = 0 to obtain
 *                 the required buffer size in *olen
 */
int mbedtls_base64_decode( unsigned char *dst, size_t dlen, size_t *olen,
                           const unsigned char *src, size_t slen )
{
    size_t i, n;
    uint32_t j, x;
    unsigned char *p;
    unsigned char dec_map_lookup;
    /* First pass: check for validity and get output length */
    for( i = n = j = 0; i < slen; i++ )
    {
        /* Skip spaces before checking for EOL */
        x = 0;
        while( i < slen && src[i] == ' ' )
        {
            ++i;
            ++x;
        }
        /* Spaces at end of buffer are OK */
        if( i == slen )
            break;
        if( ( slen - i ) >= 2 &&
            src[i] == '\r' && src[i + 1] == '\n' )
            continue;
        if( src[i] == '\n' )
            continue;
        /* Space inside a line is an error */
        if( x != 0 )
            return( MBEDTLS_ERR_BASE64_INVALID_CHARACTER );
        if( src[i] == '=' && ++j > 2 )
            return( MBEDTLS_ERR_BASE64_INVALID_CHARACTER );
        dec_map_lookup = mbedtls_base64_table_lookup( base64_dec_map, sizeof( base64_dec_map ), src[i] );
        if( src[i] > 127 || dec_map_lookup == 127 )
            return( MBEDTLS_ERR_BASE64_INVALID_CHARACTER );
        if( dec_map_lookup < 64 && j != 0 )
            return( MBEDTLS_ERR_BASE64_INVALID_CHARACTER );
        n++;
    }
    if( n == 0 )
    {
        *olen = 0;
        return( 0 );
    }
    /* The following expression is to calculate the following formula without
     * risk of integer overflow in n:
     *     n = ( ( n * 6 ) + 7 ) >> 3;
     */
    n = ( 6 * ( n >> 3 ) ) + ( ( 6 * ( n & 0x7 ) + 7 ) >> 3 );
    n -= j;
    if( dst == NULL || dlen < n )
    {
        *olen = n;
        return( MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL );
    }
   for( j = 3, n = x = 0, p = dst; i > 0; i--, src++ )
   {
        if( *src == '\r' || *src == '\n' || *src == ' ' )
            continue;
        dec_map_lookup = mbedtls_base64_table_lookup( base64_dec_map, sizeof( base64_dec_map ), *src );
        mbedtls_base64_cond_assign_uint32( &j, j - 1, mbedtls_base64_eq( dec_map_lookup, 64 ) );
        x  = ( x << 6 ) | ( dec_map_lookup & 0x3F );
        if( ++n == 4 )
        {
            n = 0;
            if( j > 0 ) *p++ = (unsigned char)( x >> 16 );
            if( j > 1 ) *p++ = (unsigned char)( x >>  8 );
            if( j > 2 ) *p++ = (unsigned char)( x       );
        }
    }
    *olen = p - dst;
    return( 0 );
}

#if defined(MBEDTLS_SELF_TEST)

static const unsigned char base64_test_dec[64] =
{
    0x24, 0x48, 0x6E, 0x56, 0x87, 0x62, 0x5A, 0xBD,
    0xBF, 0x17, 0xD9, 0xA2, 0xC4, 0x17, 0x1A, 0x01,
    0x94, 0xED, 0x8F, 0x1E, 0x11, 0xB3, 0xD7, 0x09,
    0x0C, 0xB6, 0xE9, 0x10, 0x6F, 0x22, 0xEE, 0x13,
    0xCA, 0xB3, 0x07, 0x05, 0x76, 0xC9, 0xFA, 0x31,
    0x6C, 0x08, 0x34, 0xFF, 0x8D, 0xC2, 0x6C, 0x38,
    0x00, 0x43, 0xE9, 0x54, 0x97, 0xAF, 0x50, 0x4B,
    0xD1, 0x41, 0xBA, 0x95, 0x31, 0x5A, 0x0B, 0x97
};

static const unsigned char base64_test_enc[] =
    "JEhuVodiWr2/F9mixBcaAZTtjx4Rs9cJDLbpEG8i7hPK"
    "swcFdsn6MWwINP+Nwmw4AEPpVJevUEvRQbqVMVoLlw==";

/**
 * \brief          Checkup routine
 *
 * \return         0 if successful, or 1 if the test failed
 */
int mbedtls_base64_self_test( int verbose )
{
    size_t len;
    const unsigned char *src;
    unsigned char buffer[128];
    if( verbose != 0 )
        mbedtls_printf( "  Base64 encoding test: " );
    src = base64_test_dec;
    if( mbedtls_base64_encode( buffer, sizeof( buffer ), &len, src, 64 ) != 0 ||
         timingsafe_bcmp( base64_test_enc, buffer, 88 ) != 0 )
    {
        if( verbose != 0 )
            mbedtls_printf( "failed\n" );
        return( 1 );
    }
    if( verbose != 0 )
        mbedtls_printf( "passed\n  Base64 decoding test: " );
    src = base64_test_enc;
    if( mbedtls_base64_decode( buffer, sizeof( buffer ), &len, src, 88 ) != 0 ||
         timingsafe_bcmp( base64_test_dec, buffer, 64 ) != 0 )
    {
        if( verbose != 0 )
            mbedtls_printf( "failed\n" );
        return( 1 );
    }
    if( verbose != 0 )
        mbedtls_printf( "passed\n\n" );
    return( 0 );
}

#endif /* MBEDTLS_SELF_TEST */
