/* $Id: ncbi_base64.c 567710 2018-07-23 17:27:22Z lavr $
 * ===========================================================================
 *
 *                            PUBLIC DOMAIN NOTICE
 *               National Center for Biotechnology Information
 *
 *  This software/database is a "United States Government Work" under the
 *  terms of the United States Copyright Act.  It was written as part of
 *  the author's official duties as a United States Government employee and
 *  thus cannot be copyrighted.  This software/database is freely available
 *  to the public for use. The National Library of Medicine and the U.S.
 *  Government have not placed any restriction on its use or reproduction.
 *
 *  Although all reasonable efforts have been taken to ensure the accuracy
 *  and reliability of the software and data, the NLM and the U.S.
 *  Government do not and cannot warrant the performance or results that
 *  may be obtained by using this software or data. The NLM and the U.S.
 *  Government disclaim all warranties, express or implied, including
 *  warranties of performance, merchantability or fitness for any particular
 *  purpose.
 *
 *  Please cite the author in any work or product based on this material.
 *
 * ===========================================================================
 *
 * Author:  Anton Lavrentiev
 *          Dmitry Kazimirov (base64url variant)
 *
 * File Description:
 *   BASE-64 Encoding/Decoding
 *
 */

#include "ncbi_base64.h"


extern void BASE64_Encode
(const void* src_buf,
 size_t      src_size,
 size_t*     src_read,
 void*       dst_buf,
 size_t      dst_size,
 size_t*     dst_written,
 size_t*     line_len)
{
    static const unsigned char kSyms[] =
        "ABCDEFGHIJKLMNOPQRSTUVWXYZ" /*26*/
        "abcdefghijklmnopqrstuvwxyz" /*52*/
        "0123456789+/";              /*64*/
    const size_t max_len = line_len ? *line_len : 76;
    const size_t max_src =
        ((dst_size - (max_len ? dst_size/(max_len + 1) : 0)) >> 2) * 3;
    unsigned char* src = (unsigned char*) src_buf;
    unsigned char* dst = (unsigned char*) dst_buf;
    size_t len = 0, i = 0, j = 0;
    unsigned char temp = 0, c;
    unsigned char shift = 2;
    if (!max_src  ||  !src_size) {
        *src_read    = 0;
        *dst_written = 0;
        if (dst_size > 0) {
            *dst = '\0';
        }
        return;
    }
    if (src_size > max_src) {
        src_size = max_src;
    }
    c = src[0];
    for (;;) {
        unsigned char bits = (c >> shift) & 0x3F;
        if (max_len  &&  len >= max_len) {
            dst[j++] = '\n';
            len = 0;
        }
        _ASSERT((size_t)(temp | bits) < sizeof(kSyms) - 1);
        dst[j++] = kSyms[temp | bits];
        len++;
        if (i >= src_size) {
            break;
        }
        shift = (unsigned char)(shift + 2);  // shift += 2;
        shift &= 7;
        temp = (c << (8 - shift)) & 0x3F;
        if (shift) {
            c = ++i < src_size ? src[i] : 0;
        } else if (i + 1 == src_size) {
            i++;
        }
    }
    _ASSERT(j <= dst_size);
    *src_read = i;
    for (i = 0; i < (3 - src_size % 3) % 3; i++) {
        if (max_len  &&  len >= max_len) {
            dst[j++] = '\n';
            len = 0;
        }
        dst[j++] = '=';
        len++;
    }
    _ASSERT(j <= dst_size);
    *dst_written = j;
    if (j < dst_size) {
        dst[j] = '\0';
    }
}


extern int/*bool*/ BASE64_Decode
(const void* src_buf,
 size_t      src_size,
 size_t*     src_read,
 void*       dst_buf,
 size_t      dst_size,
 size_t*     dst_written)
{
    const unsigned char* src = (const unsigned char*) src_buf;
    unsigned char*       dst = (unsigned char*)       dst_buf;
    size_t i = 0, j = 0, k = 0, l;
    unsigned int temp = 0;
    if (src_size < 4  ||  dst_size < 3) {
        *src_read    = 0;
        *dst_written = 0;
        return 0/*false*/;
    }
    for (;;) {
        int/*bool*/  ok = i < src_size ? 1/*true*/ : 0/*false*/;
        unsigned char c = ok ? src[i++] : '=';
        if (c == '=') {
            c  = 64; /*end*/
        } else if (c >= 'A'  &&  c <= 'Z') {
            c = (unsigned char)(c - 'A');         /* c -= 'A'; */
        } else if (c >= 'a'  &&  c <= 'z') {
            c = (unsigned char)(c - ('a' - 26));  /* c -= 'a' - 26; */
        } else if (c >= '0'  &&  c <= '9') {
            c = (unsigned char)(c - ('0' - 52));  /* c -= '0' - 52; */
        } else if (c == '+') {
            c  = 62;
        } else if (c == '/') {
            c  = 63;
        } else {
            continue;
        }
        temp <<= 6;
        temp  |= c & 0x3F;
        if (!(++k & 3)  ||  c == 64) {
            if (c == 64) {
                if (k < 2) {
                    if (ok) {
                        /* pushback leading '=' */
                        --i;
                    }
                    break;
                }
                switch (k) {
                case 2:
                    temp >>= 4;
                    break;
                case 3:
                    temp >>= 10;
                    break;
                case 4:
                    temp >>= 8;
                    break;
                default:
                    _ASSERT(0);
                    break;
                }
                l = 4 - k;
                while (l > 0) {
                    /* eat up '='-padding */
                    if (i >= src_size)
                        break;
                    if (src[i] == '=')
                        --l;
                    else if (src[i] != '\r'  &&  src[i] != '\n')
                        break;
                    ++i;
                }
            } else {
                k = 0;
            }
            switch (k) {
            case 0:
                dst[j++] = (unsigned char)((temp & 0xFF0000) >> 16);
                /*FALLTHRU*/;
            case 4:
                dst[j++] = (unsigned char)((temp & 0xFF00) >> 8);
                /*FALLTHRU*/
            case 3:
                dst[j++] = (unsigned char)(temp & 0xFF);
                break;
            default:
                break;
            }
            if (j + 3 >= dst_size  ||  c == 64) {
                break;
            }
            temp = 0;
        }
    }
    *src_read    = i;
    *dst_written = j;
    return i  &&  j ? 1/*true*/ : 0/*false*/;
}


#ifdef NCBI_CXX_TOOLKIT

static const unsigned char base64url_alphabet[] =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";

extern EBase64_Result base64url_encode(const void* src_buf, size_t src_size,
    void* dst_buf, size_t dst_size, size_t* output_len)
{
    const unsigned char* src;
    unsigned char* dst;
    const unsigned char* offset;

    size_t result_len = ((src_size << 2) + 2) / 3;

    if (output_len != NULL)
        *output_len = result_len;

    if (result_len > dst_size)
        return eBase64_BufferTooSmall;

    src = (const unsigned char*) src_buf;
    dst = (unsigned char*) dst_buf;

    while (src_size > 2) {
        *dst++ = base64url_alphabet[*src >> 2];
        offset = base64url_alphabet + ((*src & 003) << 4);
        *dst++ = offset[*++src >> 4];
        offset = base64url_alphabet + ((*src & 017) << 2);
        *dst++ = offset[*++src >> 6];
        *dst++ = base64url_alphabet[*src++ & 077];
        src_size -= 3;
    }

    if (src_size > 0) {
        *dst = base64url_alphabet[*src >> 2];
        offset = base64url_alphabet + ((*src & 003) << 4);
        if (src_size == 1)
            *++dst = *offset;
        else { /* src_size == 2 */
            *++dst = offset[*++src >> 4];
            *++dst = base64url_alphabet[(*src & 017) << 2];
        }
    }

    return eBase64_OK;
}

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

#define XLAT_BASE64_CHAR(var)                                     \
    if ((signed char)(var = base64url_decode_table[*src++]) < 0)  \
        return eBase64_InvalidInput;

extern EBase64_Result base64url_decode(const void* src_buf, size_t src_size,
    void* dst_buf, size_t dst_size, size_t* output_len)
{
    unsigned char* dst;
    const unsigned char* src;
    unsigned char src_ch0, src_ch1;

    size_t result_len = (src_size * 3) >> 2;

    if (output_len != NULL)
        *output_len = result_len;

    if (result_len > dst_size)
        return eBase64_BufferTooSmall;

    src = (const unsigned char*) src_buf;
    dst = (unsigned char*)       dst_buf;

    while (src_size > 3) {
        XLAT_BASE64_CHAR(src_ch0);
        XLAT_BASE64_CHAR(src_ch1);
        *dst++ = (unsigned char)(src_ch0 << 2 | src_ch1 >> 4);
        XLAT_BASE64_CHAR(src_ch0);
        *dst++ = (unsigned char)(src_ch1 << 4 | src_ch0 >> 2);
        XLAT_BASE64_CHAR(src_ch1);
        *dst++ = (unsigned char)(src_ch0 << 6 | src_ch1);
        src_size -= 4;
    }

    if (src_size > 1) {
        XLAT_BASE64_CHAR(src_ch0);
        XLAT_BASE64_CHAR(src_ch1);
        *dst++ = (unsigned char)(src_ch0 << 2 | src_ch1 >> 4);
        if (src_size > 2) {
            XLAT_BASE64_CHAR(src_ch0);
            *dst = (unsigned char)(src_ch1 << 4 | src_ch0 >> 2);
        }
    } else if (src_size == 1)
        return eBase64_InvalidInput;

    return eBase64_OK;
}

#endif /*NCBI_CXX_TOOLKIT*/
