/*
 * Unit tests for API functions of libbasexx
 *
 * SPDX-FileType: SOURCE
 * SPDX-FileCopyrightText: Michael Bäuerle
 * SPDX-License-Identifier: BSD-2-Clause
 */

/* Check for XSI extension on POSIX systems older than POSIX.1-2001 */
#if (defined _POSIX_C_SOURCE) && (_POSIX_C_SOURCE < 200112L)
#include <unistd.h>
#if _XOPEN_VERSION >= 500
#define _XOPEN_SOURCE  _XOPEN_VERSION
#endif  /* _XOPEN_VERSION >= 500 */
#endif  /* (defined _POSIX_C_SOURCE) && (_POSIX_C_SOURCE < 200112L) */

/* System headers */
#include <errno.h>
#include <stdio.h>
#include <stddef.h>
#include <string.h>

/* libbasexx */
#include "libbasexx-0/base64_encode.h"
#include "libbasexx-0/base64_decode.h"
#include "libbasexx-0/base64_imap.h"
#include "libbasexx-0/base64_url.h"
#include "libbasexx-0/basexx_ebcdic.h"
#include "libbasexx-0/basexx_version.h"

/* CHEAT unit test framework */
#ifndef __BASE_FILE__
#define __BASE_FILE__ BXX0_UTFILE
#endif  /* __BASE_FILE__ */
#include <cheat.h>


/* Used to process return values */
#define BXX0_I_TEST_OTHER   0
#define BXX0_I_TEST_ENCODE  1
#define BXX0_I_TEST_DECODE  2


#define BXX0_I_INBUF_SIZE   1032U  /* Multiple of three and four */
#define BXX0_I_OUTBUF_SIZE  ((BXX0_I_INBUF_SIZE / 3U) * 4U)


CHEAT_DECLARE(
    static signed char rv;
    static size_t      len_in_orig  CHEAT_COMMA len_in;
    static size_t      len_out_orig CHEAT_COMMA len_out;

    /* Input buffer */
    static unsigned char inbuf[BXX0_I_INBUF_SIZE];

    /* Output buffer */
    static unsigned char outbuf[BXX0_I_OUTBUF_SIZE];

    /* Fill input buffer with octet values representing the index */
    void PREPARE_INBUF(void)
    {
        size_t i;

        for (i = 0; 256 > i; ++i)
            inbuf[i] = i;
    }
)


/* Check return value and print debug output after error */
CHEAT_DECLARE(
    void CHECK_RV(int mode, signed char rv, signed char expected)
    {
        if (expected != rv)
        {
            fprintf(stderr, "rv: %d, ", (int)rv);
            fprintf(stderr, "len_in: %lu, len_out: %lu\n",
                    (unsigned long int)len_in, (unsigned long int)len_out);

            /* Print human readable error string */
            if (BXX0_I_TEST_ENCODE == mode)
            {
                switch (rv)
                {
                    case BXX0_BASE64_ENCODE_ERROR_SIZE:
                        fprintf(stderr, "Output buffer too small\n");
                        break;
                    default:
                        fprintf(stderr, "Unknown error\n");
                        break;
                }
            }
            else if (BXX0_I_TEST_DECODE == mode)
            {
                switch (rv)
                {
                    case BXX0_BASE64_DECODE_ERROR_SIZE:
                        fprintf(stderr, "Output buffer too small\n");
                        break;
                    case BXX0_BASE64_DECODE_ERROR_NAC:
                        fprintf(stderr, "Non-Alphabet character\n");
                        break;
                    case BXX0_BASE64_DECODE_ERROR_TAIL:
                        fprintf(stderr, "Invalid tail before padding\n");
                        break;
                    case BXX0_BASE64_DECODE_ERROR_PAD:
                        fprintf(stderr, "Invalid padding\n");
                        break;
                    case BXX0_BASE64_DECODE_ERROR_DAP:
                        fprintf(stderr, "Data after padding\n");
                        break;
                    default:
                        fprintf(stderr, "Unknown error\n");
                        break;
                }
            }
            else
                fprintf(stderr, "Failed\n");
        }

        cheat_assert(expected == rv);
    }
)


/* Check size and print debug output after error */
CHEAT_DECLARE(
    void CHECK(size_t num, size_t expected)
    {
        if (expected != num)
        {
            fprintf(stderr, "num: %lu, expected: %lu\n",
                    (unsigned long int)num, (unsigned long int)expected);

            /* Print human readable error string */
            fprintf(stderr, "Failed\n");
        }

        cheat_assert(expected == num);
    }
)


/* Load conversion input data from file */
CHEAT_DECLARE(
    void LOAD_INPUT_DATA(unsigned char *buf, size_t len, size_t *len_in,
                         const char* pn)
    {
        FILE *fp = fopen(pn, "rb");

        cheat_assert(NULL != fp);
        if (NULL != fp)
        {
            int    rv_load = EOF;
            size_t i       = 0;

            *len_in = 0;
            for ( ; len > i; ++i)
            {
                rv_load = fgetc(fp);
                if (EOF == rv_load)
                    break;
                else
                {
                    buf[i] = (char)rv_load;
                    ++(*len_in);
                }
            }

            /* Check for EOF in reference data file */
            if (EOF != rv_load)
            {
                rv_load = fgetc(fp);
                cheat_assert(EOF == rv_load);
            }

            rv_load = fclose(fp);
            cheat_assert(0 == rv_load);
        }
    }
)


/* Compare conversion output with reference file */
CHEAT_DECLARE(
    void COMPARE_WITH_REFERENCE(const unsigned char *buf, size_t len,
                                const char* pn)
    {
        FILE *fp = fopen(pn, "rb");

        cheat_assert(NULL != fp);
        if (NULL != fp)
        {
            int    rv_compare = EOF;
            size_t i          = 0;

            /* Compare with reference data */
            for ( ; len > i; ++i)
            {
                unsigned int chk = (unsigned char)buf[i];

                rv_compare = fgetc(fp);
                cheat_assert(EOF != rv_compare);
                {
                    unsigned int ref = (unsigned char)rv_compare;

                    if (chk != (unsigned char)rv_compare)
                    {

                        fprintf( stderr, "Mismatch at index %u (0x%02X): "
                                 "Checked 0x%02X against Reference 0x%02X\n",
                                 (unsigned int) i, (unsigned int) i, chk, ref );
                    }
                    cheat_assert(chk == ref);
                }
            }

            /* Check for EOF in reference data file */
            rv_compare = fgetc(fp);
            cheat_assert(EOF == rv_compare);

            rv_compare = fclose(fp);
            cheat_assert(0 == rv_compare);
        }
    }
)


/* Prepare buffers with defined values */
CHEAT_SET_UP(
    (void)memset(inbuf , 42, BXX0_I_INBUF_SIZE);
    (void)memset(outbuf, 42, BXX0_I_OUTBUF_SIZE);
)


/* Print CHEAT version */
CHEAT_TEST(version,
    (void)cheat_print(stdout, "%s", 1, "Unit test framework: ");
    cheat_print_version();
    /* Yellow foreground color is unreadable with white background */
    (void)cheat_print(stdout,
                      "%s", 1, "Yellow foreground color patched out.\n\n");
)


/* === EBCDIC converter === */


CHEAT_TEST(basexx_ebcdic_to,
    (void)fputs("Testing bxx0_ebcdic_to() ... \n", stdout);

    LOAD_INPUT_DATA(inbuf, 256, &len_in, "reference/data.raw");
    cheat_yield();
    bxx0_ebcdic_to(inbuf, len_in);
    COMPARE_WITH_REFERENCE(inbuf, 256, "reference/data_to.ebcdic");
)


CHEAT_TEST(basexx_ebcdic_from,
    (void)fputs("Testing bxx0_ebcdic_from() ... \n", stdout);

    LOAD_INPUT_DATA(inbuf, 256, &len_in, "reference/data_to.ebcdic");
    cheat_yield();
    bxx0_ebcdic_from(inbuf, len_in);
    COMPARE_WITH_REFERENCE(inbuf, 256, "reference/data_from.ebcdic");
)


/* === Base 64 IMAP converter === */


CHEAT_TEST(base64_imap_to,
    (void)fputs("Testing bxx0_base64_imap_to() ... \n", stdout);

    LOAD_INPUT_DATA(inbuf, 256, &len_in, "reference/data.raw");
    cheat_yield();
    bxx0_base64_imap_to(inbuf, len_in);
    CHECK(inbuf[0x2F], 0x2C);
    cheat_yield();
    inbuf[0x2F] = 0x2F;
    COMPARE_WITH_REFERENCE(inbuf, 256, "reference/data.raw");
)


CHEAT_TEST(base64_imap_from,
    (void)fputs("Testing bxx0_base64_imap_from() ... \n", stdout);

    LOAD_INPUT_DATA(inbuf, 256, &len_in, "reference/data.raw");
    cheat_yield();
    bxx0_base64_imap_from(inbuf, len_in);
    CHECK(inbuf[0x2C], 0x2F);
    cheat_yield();
    inbuf[0x2C] = 0x2C;
    COMPARE_WITH_REFERENCE(inbuf, 256, "reference/data.raw");
)


/* === Base 64 URL converter === */


CHEAT_TEST(base64_url_to,
    (void)fputs("Testing bxx0_base64_url_to() ... \n", stdout);

    LOAD_INPUT_DATA(inbuf, 256, &len_in, "reference/data.raw");
    cheat_yield();
    bxx0_base64_url_to(inbuf, len_in);
    CHECK(inbuf[0x2B], 0x2D);
    cheat_yield();
    CHECK(inbuf[0x2F], 0x5F);
    cheat_yield();
    inbuf[0x2B] = 0x2B;
    inbuf[0x2F] = 0x2F;
    COMPARE_WITH_REFERENCE(inbuf, 256, "reference/data.raw");
)


CHEAT_TEST(base64_url_from,
    (void)fputs("Testing bxx0_base64_url_from() ... \n", stdout);

    LOAD_INPUT_DATA(inbuf, 256, &len_in, "reference/data.raw");
    cheat_yield();
    bxx0_base64_url_from(inbuf, len_in);
    CHECK(inbuf[0x2D], 0x2B);
    cheat_yield();
    CHECK(inbuf[0x5F], 0x2F);
    cheat_yield();
    inbuf[0x2D] = 0x2D;
    inbuf[0x5F] = 0x5F;
    COMPARE_WITH_REFERENCE(inbuf, 256, "reference/data.raw");
)


/* === Base 64 encoder === */


CHEAT_TEST(base64_encode_generic,
    (void)fputs("Testing bxx0_base64_encode() in generic mode ... \n", stdout);

    len_in_orig = 256;
    LOAD_INPUT_DATA(inbuf, len_in_orig, &len_in, "reference/data.raw");
    cheat_yield();
    len_out = len_out_orig = BXX0_BASE64_ENCODE_LEN_OUT(len_in_orig);
    rv = bxx0_base64_encode(outbuf, &len_out, inbuf, &len_in, 0);
    CHECK_RV(BXX0_I_TEST_ENCODE, rv, 0);
    CHECK(len_in, 0);
    CHECK(len_out_orig - len_out, 344);
    cheat_yield();
    COMPARE_WITH_REFERENCE(outbuf, 344, "reference/data_generic.base64");
    cheat_yield();

    len_in_orig = 3;
    LOAD_INPUT_DATA(inbuf, len_in_orig, &len_in, "reference/123.raw");
    cheat_yield();
    len_out = len_out_orig = BXX0_BASE64_ENCODE_LEN_OUT(len_in_orig);
    rv = bxx0_base64_encode(outbuf, &len_out, inbuf, &len_in, 0);
    CHECK_RV(BXX0_I_TEST_ENCODE, rv, 0);
    CHECK(len_in, 0);
    CHECK(len_out_orig - len_out, 4);
    cheat_yield();
    COMPARE_WITH_REFERENCE(outbuf, 4, "reference/123.base64");

    len_in_orig = 4;
    LOAD_INPUT_DATA(inbuf, len_in_orig, &len_in, "reference/123X.raw");
    cheat_yield();
    len_out = len_out_orig = BXX0_BASE64_ENCODE_LEN_OUT(len_in_orig);
    rv = bxx0_base64_encode(outbuf, &len_out, inbuf, &len_in, 0);
    CHECK_RV(BXX0_I_TEST_ENCODE, rv, 0);
    CHECK(len_in, 0);
    CHECK(len_out_orig - len_out, 8);
    cheat_yield();
    COMPARE_WITH_REFERENCE(outbuf, 8, "reference/123X.base64");

    len_in_orig = 5;
    LOAD_INPUT_DATA(inbuf, len_in_orig, &len_in, "reference/123XY.raw");
    cheat_yield();
    len_out = len_out_orig = BXX0_BASE64_ENCODE_LEN_OUT(len_in_orig);
    rv = bxx0_base64_encode(outbuf, &len_out, inbuf, &len_in, 0);
    CHECK_RV(BXX0_I_TEST_ENCODE, rv, 0);
    CHECK(len_in, 0);
    CHECK(len_out_orig - len_out, 8);
    cheat_yield();
    COMPARE_WITH_REFERENCE(outbuf, 8, "reference/123XY.base64");

    len_in_orig = 6;
    LOAD_INPUT_DATA(inbuf, len_in_orig, &len_in, "reference/123XYZ.raw");
    cheat_yield();
    len_out = len_out_orig = BXX0_BASE64_ENCODE_LEN_OUT(len_in_orig);
    rv = bxx0_base64_encode(outbuf, &len_out, inbuf, &len_in, 0);
    CHECK_RV(BXX0_I_TEST_ENCODE, rv, 0);
    CHECK(len_in, 0);
    CHECK(len_out_orig - len_out, 8);
    cheat_yield();
    COMPARE_WITH_REFERENCE(outbuf, 8, "reference/123XYZ.base64");
)


CHEAT_TEST(base64_encode_buffer,
    (void)fputs("Testing bxx0_base64_encode() with too small buffer ... \n",
                stdout);

    len_in_orig = 256;
    LOAD_INPUT_DATA(inbuf, len_in_orig, &len_in, "reference/data.raw");
    cheat_yield();
    len_out_orig = BXX0_BASE64_ENCODE_LEN_OUT(len_in_orig);
    --len_out_orig;
    len_out = len_out_orig;
    rv = bxx0_base64_encode(outbuf, &len_out, inbuf, &len_in, 0);
    CHECK_RV(BXX0_I_TEST_ENCODE, rv, BXX0_BASE64_ENCODE_ERROR_SIZE);
    CHECK(len_in, len_in_orig);
    CHECK(len_out_orig - len_out, 0);
)


CHEAT_TEST(base64_encode_nodata,
    (void)fputs("Testing bxx0_base64_encode() with no data ... \n", stdout);

    len_in = len_in_orig = 0;
    len_out = len_out_orig = BXX0_BASE64_ENCODE_LEN_OUT(len_in_orig);
    rv = bxx0_base64_encode(outbuf, &len_out, inbuf, &len_in, 0);
    CHECK_RV(BXX0_I_TEST_ENCODE, rv, 0);
    CHECK(len_in, len_in_orig);
    CHECK(len_out, len_out_orig);
)


CHEAT_TEST(base64_encode_nopad,
    (void)fputs("Testing bxx0_base64_encode() without padding ... \n", stdout);

    /* No padding required for generic format */
    len_in_orig = 6;
    LOAD_INPUT_DATA(inbuf, len_in_orig, &len_in, "reference/123XYZ.raw");
    cheat_yield();
    len_out = len_out_orig = BXX0_BASE64_ENCODE_LEN_OUT(len_in_orig);
    rv = bxx0_base64_encode(outbuf, &len_out, inbuf, &len_in,
                            BXX0_BASE64_ENCODE_FLAG_NOPAD);
    CHECK_RV(BXX0_I_TEST_ENCODE, rv, 0);  /* Flag must be absent */
    CHECK(len_in, 0);
    CHECK(len_out_orig - len_out, 8);
    cheat_yield();
    COMPARE_WITH_REFERENCE(outbuf, 8, "reference/123XYZ.base64");
    cheat_yield();

    /* Padding required for generic format */
    len_in_orig = 256;
    LOAD_INPUT_DATA(inbuf, len_in_orig, &len_in, "reference/data.raw");
    cheat_yield();
    len_out = len_out_orig = BXX0_BASE64_ENCODE_LEN_OUT(len_in_orig);
    rv = bxx0_base64_encode(outbuf, &len_out, inbuf, &len_in,
                            BXX0_BASE64_ENCODE_FLAG_NOPAD);
    CHECK_RV(BXX0_I_TEST_ENCODE, rv, BXX0_BASE64_ENCODE_FLAG_NOPAD);
    CHECK(len_in, 0);
    CHECK(len_out_orig - len_out, 342);
    cheat_yield();
    COMPARE_WITH_REFERENCE(outbuf, 342, "reference/data_nopad.base64");
)


/* === Base 64 decoder === */


CHEAT_TEST(base64_decode_generic,
    (void)fputs("Testing bxx0_base64_decode() in generic mode ... \n", stdout);

    len_in_orig = 344;
    LOAD_INPUT_DATA(inbuf, len_in_orig, &len_in,
                    "reference/data_generic.base64");
    cheat_yield();
    len_out = len_out_orig = BXX0_BASE64_DECODE_LEN_OUT(len_in_orig);
    rv = bxx0_base64_decode(outbuf, &len_out, inbuf, &len_in, 0);
    CHECK_RV(BXX0_I_TEST_DECODE, rv, 0);
    CHECK(len_in, 0);
    CHECK(len_out_orig - len_out, 256);
    cheat_yield();
    COMPARE_WITH_REFERENCE(outbuf, 256, "reference/data.raw");
    cheat_yield();

    /* Data without padding is no error, but the tail is not consumed */
    len_in =  len_in_orig  = 342;
    len_out = len_out_orig = BXX0_BASE64_DECODE_LEN_OUT(len_in_orig);
    rv = bxx0_base64_decode(outbuf, &len_out, inbuf, &len_in, 0);
    CHECK_RV(BXX0_I_TEST_DECODE, rv, 0);
    CHECK(len_in, 2);                    /* Tail was not consumed? */
    CHECK(len_out_orig - len_out, 255);  /* One octet from tail is missing? */
    cheat_yield();
    /* Last byte of output buffer was not overwritten and must still match */
    COMPARE_WITH_REFERENCE(outbuf, 256, "reference/data.raw");
)


CHEAT_TEST(base64_decode_nopad,
    (void)fputs("Testing bxx0_base64_decode() without padding ... \n", stdout);

    /* No padding */
    len_in_orig = 8;
    LOAD_INPUT_DATA(inbuf, len_in_orig, &len_in, "reference/123XYZ.base64");
    cheat_yield();
    len_out = len_out_orig = BXX0_BASE64_DECODE_LEN_OUT(len_in_orig);
    rv = bxx0_base64_decode(outbuf, &len_out, inbuf, &len_in,
                            BXX0_BASE64_DECODE_FLAG_NOPAD);
    CHECK_RV(BXX0_I_TEST_DECODE, rv, 0);  /* Flag must be absent */
    CHECK(len_in, 0);
    COMPARE_WITH_REFERENCE(outbuf, 6, "reference/123XYZ.raw");
    cheat_yield();

    /* Padding */
    len_in_orig = 344;
    LOAD_INPUT_DATA(inbuf, len_in_orig, &len_in,
                    "reference/data_generic.base64");
    cheat_yield();
    len_in =  len_in_orig  = 342;
    len_out = len_out_orig = BXX0_BASE64_DECODE_LEN_OUT(len_in_orig);
    rv = bxx0_base64_decode(outbuf, &len_out, inbuf, &len_in,
                            BXX0_BASE64_DECODE_FLAG_NOPAD);
    CHECK_RV(BXX0_I_TEST_DECODE, rv, BXX0_BASE64_DECODE_FLAG_NOPAD);
    CHECK(len_in, 0);
    COMPARE_WITH_REFERENCE(outbuf, 256, "reference/data.raw");
)


CHEAT_TEST(base64_decode_nopad_invtail,
    (void)fputs("Testing bxx0_base64_decode() without padding and invalid tail"
                " ... \n", stdout);

    len_in_orig = 12;
    LOAD_INPUT_DATA(inbuf, len_in_orig, &len_in, "reference/invtail.base64");
    cheat_yield();
    len_in  = len_in_orig = 11;
    len_out = len_out_orig = BXX0_BASE64_DECODE_LEN_OUT(len_in_orig);
    rv = bxx0_base64_decode(outbuf, &len_out, inbuf, &len_in,
                            BXX0_BASE64_DECODE_FLAG_NOPAD);
    CHECK_RV(BXX0_I_TEST_DECODE, rv, BXX0_BASE64_DECODE_ERROR_TAIL);
    cheat_yield();

    /* With flag to accept unused bits with nonzero value in tail */
    len_in = len_in_orig = 11;
    len_out = len_out_orig = BXX0_BASE64_DECODE_LEN_OUT(len_in_orig);
    rv = bxx0_base64_decode(outbuf, &len_out, inbuf, &len_in,
                            BXX0_BASE64_DECODE_FLAG_NOPAD |
                            BXX0_BASE64_DECODE_FLAG_INVTAIL);
    CHECK_RV(BXX0_I_TEST_DECODE, rv, BXX0_BASE64_DECODE_FLAG_NOPAD |
                                     BXX0_BASE64_DECODE_FLAG_INVTAIL);
    CHECK(len_in_orig - len_in, 11);
    CHECK(len_out_orig - len_out, 8);  /* Two octets from tail are missing? */
    cheat_yield();
    COMPARE_WITH_REFERENCE(outbuf, 8, "reference/invtail.raw");
    cheat_yield();

    /* With too short tail */
    len_in = len_in_orig = 9;
    len_out = len_out_orig = BXX0_BASE64_DECODE_LEN_OUT(len_in_orig);
    rv = bxx0_base64_decode(outbuf, &len_out, inbuf, &len_in,
                            BXX0_BASE64_DECODE_FLAG_NOPAD);
    CHECK_RV(BXX0_I_TEST_DECODE, rv, BXX0_BASE64_DECODE_ERROR_TAIL);
)


CHEAT_TEST(base64_decode_invtail,
    (void)fputs("Testing bxx0_base64_decode() with invalid tail ... \n",
                stdout);

    /* Data with short tail */
    len_in_orig = 344;
    LOAD_INPUT_DATA(inbuf, len_in_orig, &len_in,
                    "reference/data_invtail.base64");
    cheat_yield();
    len_out = len_out_orig = BXX0_BASE64_DECODE_LEN_OUT(len_in_orig);
    rv = bxx0_base64_decode(outbuf, &len_out, inbuf, &len_in, 0);
    CHECK_RV(BXX0_I_TEST_DECODE, rv, BXX0_BASE64_DECODE_ERROR_TAIL);
    CHECK(len_in, 4);
    CHECK(len_out_orig - len_out, 255);  /* One octet from tail is missing? */
    cheat_yield();

    /* With flag to accept unused bits with nonzero value in tail */
    len_in =  len_in_orig;
    len_out = len_out_orig;
    rv = bxx0_base64_decode(outbuf, &len_out, inbuf, &len_in,
                            BXX0_BASE64_DECODE_FLAG_INVTAIL);
    CHECK_RV(BXX0_I_TEST_DECODE, rv, BXX0_BASE64_DECODE_FLAG_INVTAIL);
    CHECK(len_in, 0);
    CHECK(len_out_orig - len_out, 256);
    cheat_yield();
    COMPARE_WITH_REFERENCE(outbuf, 256, "reference/data.raw");
    cheat_yield();

    /* Data with long tail */
    len_in_orig = 12;
    LOAD_INPUT_DATA(inbuf, len_in_orig, &len_in, "reference/invtail.base64");
    cheat_yield();
    len_out = len_out_orig = BXX0_BASE64_DECODE_LEN_OUT(len_in_orig);
    rv = bxx0_base64_decode(outbuf, &len_out, inbuf, &len_in, 0);
    CHECK_RV(BXX0_I_TEST_DECODE, rv, BXX0_BASE64_DECODE_ERROR_TAIL);
    CHECK(len_in, 4);
    CHECK(len_out_orig - len_out, 6);  /* Two octets from tail are missing? */
    cheat_yield();

    /* With flag to accept unused bits with nonzero value in tail */
    len_in =  len_in_orig;
    len_out = len_out_orig;
    rv = bxx0_base64_decode(outbuf, &len_out, inbuf, &len_in,
                            BXX0_BASE64_DECODE_FLAG_INVTAIL);
    CHECK_RV(BXX0_I_TEST_DECODE, rv, BXX0_BASE64_DECODE_FLAG_INVTAIL);
    CHECK(len_in, 0);
    CHECK(len_out_orig - len_out, 8);
    cheat_yield();
    COMPARE_WITH_REFERENCE(outbuf, 8, "reference/invtail.raw");
)


CHEAT_TEST(base64_decode_invpad,
    (void)fputs("Testing bxx0_base64_decode() with invalid padding ... \n",
                stdout);

    /* Pad character without tail */
    len_in_orig = 9;
    LOAD_INPUT_DATA(inbuf, len_in_orig, &len_in, "reference/invpad_1.base64");
    cheat_yield();
    len_out = len_out_orig = BXX0_BASE64_DECODE_LEN_OUT(len_in_orig);
    rv = bxx0_base64_decode(outbuf, &len_out, inbuf, &len_in, 0);
    CHECK_RV(BXX0_I_TEST_DECODE, rv, BXX0_BASE64_DECODE_ERROR_PAD);
    CHECK(len_in, 1);
    CHECK(len_out_orig - len_out, 6);
    cheat_yield();
    COMPARE_WITH_REFERENCE(outbuf, 6, "reference/invpad_1.raw");
    cheat_yield();

    /* Stray Pad character (terminates conversion) */
    len_in_orig = 9;
    LOAD_INPUT_DATA(inbuf, len_in_orig, &len_in, "reference/invpad_2.base64");
    cheat_yield();
    len_out = len_out_orig = BXX0_BASE64_DECODE_LEN_OUT(len_in_orig);
    rv = bxx0_base64_decode(outbuf, &len_out, inbuf, &len_in,
                            BXX0_BASE64_DECODE_FLAG_INVTAIL);
    CHECK_RV(BXX0_I_TEST_DECODE, rv, BXX0_BASE64_DECODE_ERROR_PAD);
    CHECK(len_in, 5);
    CHECK(len_out_orig - len_out, 3);
    COMPARE_WITH_REFERENCE(outbuf, 3, "reference/invpad_2.raw");
)


CHEAT_TEST(base64_decode_buffer,
    (void)fputs("Testing bxx0_base64_decode() with too small buffer ... \n",
                stdout);

    len_in_orig = 344;
    LOAD_INPUT_DATA(inbuf, len_in_orig, &len_in, "reference/data_generic.base64");
    cheat_yield();
    len_out_orig = BXX0_BASE64_DECODE_LEN_OUT(len_in_orig);
    --len_out_orig;
    len_out = len_out_orig;
    rv = bxx0_base64_decode(outbuf, &len_out, inbuf, &len_in, 0);
    CHECK_RV(BXX0_I_TEST_DECODE, rv, BXX0_BASE64_DECODE_ERROR_SIZE);
    CHECK(len_in, len_in_orig);
    CHECK(len_out_orig - len_out, 0);
)


CHEAT_TEST(base64_decode_concat,
    (void)fputs("Testing bxx0_base64_decode() with concatenation ... \n",
                stdout);

    len_in_orig = 344;
    LOAD_INPUT_DATA(inbuf, len_in_orig, &len_in,
                    "reference/data_generic.base64");
    cheat_yield();
    LOAD_INPUT_DATA(&inbuf[len_in_orig], len_in_orig, &len_in,
                    "reference/data_generic.base64");
    cheat_yield();
    len_in = len_in_orig *= 2;
    len_out = len_out_orig = BXX0_BASE64_DECODE_LEN_OUT(len_in_orig);
    rv = bxx0_base64_decode(outbuf, &len_out, inbuf, &len_in, 0);
    CHECK_RV(BXX0_I_TEST_DECODE, rv, BXX0_BASE64_DECODE_ERROR_DAP);
    CHECK(len_in, 344);
    CHECK(len_out_orig - len_out, 256);
    cheat_yield();
    COMPARE_WITH_REFERENCE(outbuf, 256, "reference/data.raw");
    cheat_yield();

    /* With flag to accept concatenation */
    len_in = len_in_orig;
    len_out = len_out_orig;
    rv = bxx0_base64_decode(outbuf, &len_out, inbuf, &len_in,
                            BXX0_BASE64_DECODE_FLAG_CONCAT);
    CHECK(len_in, 0);
    CHECK(len_out_orig - len_out, 512);
    cheat_yield();
    COMPARE_WITH_REFERENCE(outbuf, 512, "reference/data_concat.raw");
)


CHEAT_TEST(base64_decode_nac,
    (void)fputs("Testing bxx0_base64_decode() with Non-Alphabet characters "
                "... \n", stdout);

    len_in_orig = 346;
    LOAD_INPUT_DATA(inbuf, len_in_orig, &len_in, "reference/data_nac.base64");
    cheat_yield();
    len_out_orig = BXX0_BASE64_DECODE_LEN_OUT(len_in_orig);
    len_out = len_out_orig;
    rv = bxx0_base64_decode(outbuf, &len_out, inbuf, &len_in, 0);
    CHECK_RV(BXX0_I_TEST_DECODE, rv, BXX0_BASE64_DECODE_ERROR_NAC);
    CHECK(len_in, len_in_orig - 4U);
    CHECK(len_out_orig - len_out, 3);
    cheat_yield();

    /* With flag to ignore Non-Alphabet characters */
    len_in  = len_in_orig;
    len_out = len_out_orig;
    rv = bxx0_base64_decode(outbuf, &len_out, inbuf, &len_in,
                            BXX0_BASE64_DECODE_FLAG_IGNORE);
    CHECK_RV(BXX0_I_TEST_DECODE, rv, BXX0_BASE64_DECODE_FLAG_IGNORE);
    CHECK(len_in, 0);
    CHECK(len_out_orig - len_out, 256);
    cheat_yield();
    COMPARE_WITH_REFERENCE(outbuf, 256, "reference/data.raw");
)


/* === Base 64 encoder and decoder === */


CHEAT_TEST(base64_pattern,
    unsigned long int pattern = 1;  /* Shifted through all bits of triplet */
    unsigned int      i       = 0;

    (void)fputs("Testing Base 64 encode/decode with bit patterns ... \n",
                stdout);

    for (; 24U > i; ++i)
    {
        unsigned char patternbuf[3];

        patternbuf[0] = inbuf[0] = (pattern >> 16) & 0xFFU;
        patternbuf[1] = inbuf[1] = (pattern >> 8)  & 0xFFU;
        patternbuf[2] = inbuf[2] =  pattern        & 0xFFU;

        /* Encode pattern to output buffer */
        len_in_orig  = len_in  = 3;
        len_out_orig = len_out = BXX0_BASE64_ENCODE_LEN_OUT(len_in);
        rv = bxx0_base64_encode(outbuf, &len_out, inbuf, &len_in, 0);
        CHECK_RV(BXX0_I_TEST_ENCODE, rv, 0);
        CHECK(len_in, 0);
        CHECK(len_out_orig - len_out, 4);
        cheat_yield();

        /* Decode pattern back to input buffer */
        (void)memset(inbuf, 0xFF, 3);
        len_out_orig = len_out = 4;
        len_in_orig  = len_in  = BXX0_BASE64_DECODE_LEN_OUT(len_out);
        rv = bxx0_base64_decode(inbuf, &len_in, outbuf, &len_out, 0);
        CHECK_RV(BXX0_I_TEST_DECODE, rv, 0);
        CHECK(len_out, 0);
        CHECK(len_in_orig - len_in, 3);
        cheat_yield();

        /* Compare with original pattern */
        CHECK(memcmp(inbuf, patternbuf, 3), 0);
        cheat_yield();

        /* Next pattern */
        pattern <<= 1;
    }
)
