0
    char * lpbitmap = NULL;
    BITMAPFILEHEADER   bmfHeader;
    BITMAPINFOHEADER   bi;

    bi.biSize = sizeof(BITMAPINFOHEADER);
    bi.biWidth = bmpScreen.bmWidth;
    bi.biHeight = bmpScreen.bmHeight;
    bi.biPlanes = 1;
    bi.biBitCount = 24;
    bi.biCompression = BI_RGB;
    bi.biSizeImage = 0;
    bi.biXPelsPerMeter = 0;
    bi.biYPelsPerMeter = 0;
    bi.biClrUsed = 0;
    bi.biClrImportant = 0;

    dwBmpSize = ((bmpScreen.bmWidth * bi.biBitCount + 31) / 32) * 4 * bmpScreen.bmHeight;
    hDIB = GlobalAlloc(GHND, dwBmpSize);
    lpbitmap = (char*)GlobalLock(hDIB);

I am using the windows GDI header file to create a bitmap, the goal is to convert the lpbitmap buffer to a base64 string so that it can be represented as such data:image/bmp;base64,Qk06

as far as I understand the bitmap is 24bit where every 3 chars represent RGB values example

How to convert each RGB value into a base64 value

Update I changed the bitcount to 24

loaded_dypper
  • 262
  • 3
  • 12
  • You need a function to convert the pixel bytes (`lpbitmap`) to base64. The answers in this post give examples: https://stackoverflow.com/questions/180947/base64-decode-snippet-in-c. – wohlstad Nov 04 '22 at 14:26
  • What is "qk06"? – Craig Estey Nov 04 '22 at 15:34
  • @CraigEstey it is a base64 value from an example i copied does not have anything to do with the question – loaded_dypper Nov 04 '22 at 15:52
  • I've written a few base64 encode/decode before. I've looked over the link wohlstad posted. The first/highest answer in that link seems to have a fairly simple implementation. It should be easy enough to eliminate the C++ `std::string` [cruft] to produce a C solution. Encode is very simple. Do you also need decode? – Craig Estey Nov 04 '22 at 17:08
  • @CraigEstey yes i do need to decode but i find the concept of converting a string to another string a bit confusing i would appreciate it if anyone could explain in simple steps what exactly is done no code needed – loaded_dypper Nov 04 '22 at 17:26

1 Answers1

1

Below is my conversion of LihO's C++ answer to C: Base64 decode snippet in C++

As I mentioned in the comments, I thought it would be simple. But,there wasn't a simple cookbook to give to someone not familiar with C++. So, to do the conversion properly, I did it myself.

And, there were a few issues:

  1. The answer did a lot of unnecessary buffer copying.
  2. Using std::string and std::vector, although pretty fast, are somewhat slower than using raw byte buffers.
  3. Using base64_chars.find is O(n) but if an inverse table is created/used, it can be O(1).
  4. The answer did not support line breaks (e.g. \n) as most base64 programs do.

Anyway, below are the source files I came up with. They can be compiled with:

cc -I. -o b64test b64test.c base64.c dbgcom.c

Note that the mmap mode (i.e. -m) is untested.


FILE: base64.h

// base64.h -- base64 definitions

#ifndef _base64_h_
#define _base64_h_

#include <dbgcom.h>

#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>

typedef struct {
    u32 buf_opt;                        // buffer options
    const char *buf_tag;                // buffer name
    byte *buf_base;                     // buffer pointer
    size_t buf_len;                     // buffer length
    int buf_fdmap;                      // mmap file descriptor
} b64buf_t;

#define B64BUFDEF(_sym) \
    b64buf_t _sym = { .buf_tag = #_sym }

typedef struct {
    //u32 ctl_opt;                      // options
    const char *ctl_tag;                // control name

    b64buf_t ctl_inb;                   // input buffer
    b64buf_t ctl_out;                   // output buffer

    int ctl_wrapnow;                    // last output char was newline
    int ctl_wraplen;                    // output wrap length
    int ctl_wrapmax;                    // output wrap max

    int ctl_fdchk;                      // file cross check
} b64ctl_t;

#define B64CTLDEF(_sym) \
    b64ctl_t _sym = { .ctl_tag = #_sym }

#define B64MMAP     (1u << 0)           // 1=use mmap on output
#define B64TRIM     (1u << 1)           // 1=trim output file

// b64encode -- encode raw data to base64
// RETURNS actual output length
size_t
b64encode(b64ctl_t *ctl);

// b64decode -- decode base64 into raw binary
// RETURNS: actual output length
size_t
b64decode(b64ctl_t *ctl);

// b64bufdup -- safe buffer duplicate
void
b64bufdup(b64buf_t *dst,const b64buf_t *src);

// b64bufput -- output buffer to file
void
b64bufput(const b64buf_t *bufp,const char *ofile);

// b64bufget -- input file to buffer
void
b64bufget(b64buf_t *bufp,const char *ifile);

// b64setup -- passive setup
void
b64setup(b64ctl_t *ctl,u32 opt,const char *tag);

// b64init_encode -- initialize for encode
// RETURNS: 0=okay, -1=error
int
b64init_encode(b64ctl_t *ctl,const b64buf_t *inb,int wrapmax);

// b64init_decode -- initialize for decode
// RETURNS: 0=okay, -1=error
int
b64init_decode(b64ctl_t *ctl,b64buf_t *inb);

// b64destroy -- destroy/deinit output buffer
void
b64destroy(b64ctl_t *ctl);

// b64bufrls -- destroy/deinit output buffer
void
b64bufrls(b64buf_t *buf,const char *who);

#endif

FILE: base64.c

// base64.c -- base 64 encoder/decoder

#include <base64.h>

#include <ctype.h>
#include <sys/stat.h>
#include <sys/mman.h>

static const char *base64_chars =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    "abcdefghijklmnopqrstuvwxyz"
    "0123456789+/";

#ifndef B64DECODE_FAST
#define B64DECODE_FAST      1
#endif

#if B64DECODE_FAST
static byte decode_fast[256];
#endif

#define FMT         "%2.2X/%c"
#define PRT(_c)     _c,PRTABLE(_c)

#define PUT4(_idx,_val) \
    arr4[_idx] = _val

#define PUTE(_idx,_val) \
    arr4[_idx] = base64_chars[_val]

#define OUTLEN(_buf,_ptr) \
    (_ptr - (byte *) (_buf)->buf_base)

// encode_xcheck -- cross-check encoding
#if B64ENCODE_XCHECK
#define encode_xcheck(_ctl,_arr4,_a4siz,_arr3,_a3siz,_pad) \
    _encode_xcheck(_ctl,_arr4,_a4siz,_arr3,_a3siz,_pad)
static void
_encode_xcheck(b64ctl_t *ctl,
    const byte *arr4,int a4siz,
    const byte *arr3,int a3siz,int pad)
{
    int xlen;
    int oidx;
    byte buf[a4siz];

    dbgprt(ZPXHOWCHR,"encode_xcheck: ENTER a4siz=%d a3siz=%d pad=%d\n",
        a4siz,a3siz,pad);

    xlen = read(ctl->ctl_fdchk,buf,a4siz);
    if (xlen != a4siz)
        sysfault("encode_xcheck: read error -- a4siz=%d xlen=%d\n",
            a4siz,xlen);

    // check for error/mismatch
    int errany = 0;
    for (oidx = 0;  oidx < a4siz;  ++oidx) {
        errany = (arr4[oidx] != buf[oidx]);
        if (errany)
            break;
    }

    do {
        // only print if verbose mode or we have an error
        if (! XVERBOSE) {
            if (! errany)
                break;
        }

        int totsiz = a4siz;
        if (a3siz > totsiz)
            totsiz = a3siz;

        for (oidx = 0;  oidx < totsiz;  ++oidx) {
            byte bp = 0;
            byte a4 = 0;
            byte a3 = 0;

            size_t o4off = 0;
            size_t o3off = 0;

            if (oidx < a4siz) {
                bp = buf[oidx];
                a4 = arr4[oidx];
                o4off = OUTLEN(&ctl->ctl_out,arr4);
            }

            if (oidx < a3siz) {
                a3 = arr3[oidx];
                o3off = OUTLEN(&ctl->ctl_inb,arr3);
            }

            int errcur = (arr4[oidx] != buf[oidx]);

            printf("encode_xcheck: arr=%8.8zX/",o4off);
            if (pad)
                printf("censored");
            else
                printf("%8.8zX",o3off);

            printf(" oidx=%d exp=" FMT " act=" FMT " arr3=" FMT,
                oidx,PRT(bp),PRT(a4),PRT(a3));

            if (errany)
                printf(" ANY");
            if (errcur)
                printf("/CUR");

            printf("\n");
        }

        if (errany)
            sysfault("encode_xcheck: fault\n");
    } while (0);

    dbgprt(ZPXHOWCHR,"encode_xcheck: EXIT\n");
}
#else
#define encode_xcheck(_ctl,_arr4,_a4siz,_arr3,_a3siz,_pad) \
    do { } while (0)
#endif

// encode_wrap -- perform encoding and inject newline
static inline_nodebug byte *
encode_wrap(b64ctl_t *ctl,byte *arr4)
{

    dbgprt(ZPXHOWCHR,"encode_wrap: ENTER ctl_wraplen=%d ctl_wrapmax=%d\n",
        ctl->ctl_wraplen,ctl->ctl_wrapmax);

    do {
        // bug out if wrap disabled
        if (ctl->ctl_wrapmax <= 0)
            break;

        // wait for wrap
        ctl->ctl_wraplen += 4;

        ctl->ctl_wrapnow = 0;
        if (ctl->ctl_wraplen < ctl->ctl_wrapmax)
            break;

        ctl->ctl_wraplen = 0;
        ctl->ctl_wrapnow = 1;

        PUT4(0,'\n');
        encode_xcheck(ctl,arr4,1,NULL,0,-1);
        ++arr4;
    } while (0);

    dbgprt(ZPXHOWCHR,"encode_wrap: EXIT ctl_wraplen=%d ctl_wrapnow=%d\n",
        ctl->ctl_wraplen,ctl->ctl_wrapnow);

    return arr4;
}

// encode_putx -- perform encoding
static inline_nodebug byte *
encode_putx(b64ctl_t *ctl,byte *arr4,const byte *arr3,int padlen)
{

    dbgprt(ZPXHOWCHR,"b64encode: LOOP arr3=(" FMT " " FMT " " FMT ") padlen=%d\n",
        PRT(arr3[0]),PRT(arr3[1]),PRT(arr3[2]),padlen);

    PUTE(0,(arr3[0] & 0xfc) >> 2);
    PUTE(1,((arr3[0] & 0x03) << 4) + ((arr3[1] & 0xf0) >> 4));
    PUTE(2,((arr3[1] & 0x0f) << 2) + ((arr3[2] & 0xc0) >> 6));
    PUTE(3,arr3[2] & 0x3f);

    switch (padlen) {
    case 2:
        PUT4(2,'=');
        // fall through
    case 1:
        PUT4(3,'=');
        break;
    }

    encode_xcheck(ctl,arr4,4,arr3,3,padlen);
    arr4 += 4;

    return arr4;
}

// is_base64 -- ensure char is a valid base64 encoding char
#if ! B64DECODE_FAST
static inline int
is_base64(byte c)
{
    return (isalnum(c) || (c == '+') || (c == '/'));
}
#endif

// decode_find -- convert encoded base64 char into binary byte
static inline byte
decode_find(byte chr)
{
    byte out;

#if B64DECODE_FAST
    out = decode_fast[chr];
#else
    const char *find = strchr(base64_chars,chr);
    out = find - base64_chars;
#endif

    dbgprt(ZPXHOWCHR,"decode_find: XLAT out=" FMT " chr=" FMT "\n",
        PRT(out),PRT(chr));

    if (out == 0xFF)
        sysfault("b64decode: NONBASE64 chr=" FMT "\n",PRT(chr));

    return out;
}

// decode_put -- convert 4 byte encoded base64 cell into 3 binary bytes
static inline_nodebug byte *
decode_put(byte *arr3,byte *arr4,int a4len)
{
    int idx;
    int a3len;

    dbgprt(ZPXHOWCHR,"decode_put: ENTER a4len=%d\n",a4len);

    // convert to binary codes
    idx = 0;
    for (;  idx < a4len;  ++idx)
        arr4[idx] = decode_find(arr4[idx]);

    // add zero padding
#if 0
    for (;  idx < 4;  ++idx)
        arr4[idx] = 0;
#endif

    a3len = (a4len * 3) / 4;

    // store 3 binary chars for 4 byte cell
    arr3[0] = (arr4[0] << 2) + ((arr4[1] & 0x30) >> 4);
    arr3[1] = ((arr4[1] & 0xf) << 4) + ((arr4[2] & 0x3c) >> 2);
    arr3[2] = ((arr4[2] & 0x3) << 6) + arr4[3];

#if B64VERBOSE
    for (idx = 0;  idx < a3len;  ++idx)
        dbgprt(ZPXHOWCHR,"decode_put: arr3[%d]=" FMT "\n",PRT(arr3[idx]));
#endif

    // advance output pointer
    arr3 += a3len;

    dbgprt(ZPXHOWCHR,"decode_put: EXIT a3len=%d\n",a3len);

    return arr3;
}

// finish -- finish an operation
static size_t
finish(b64ctl_t *ctl,byte *out)
{
    b64buf_t *buf = &ctl->ctl_out;
    size_t outlen = OUTLEN(buf,out);

    if (outlen > buf->buf_len)
        sysfault("finish: buffer overflow -- buf_len=%zu outlen=%zu\n",
            buf->buf_len,outlen);

    buf->buf_len = outlen;

    return outlen;
}

// b64encode -- encode raw data to base64
// RETURNS actual output length
size_t
b64encode(b64ctl_t *ctl)
{
    const byte *inb = ctl->ctl_inb.buf_base;
    ssize_t inblen = ctl->ctl_inb.buf_len;
    byte *arr4 = ctl->ctl_out.buf_base;
    int anyflg = (inblen > 0);

    dbgprt(ZPXHOWB64,"b64encode: ENTER inb=%p inblen=%zu\n",inb,inblen);

    for (;  inblen >= 3;  inblen -= 3, inb += 3) {
        arr4 = encode_putx(ctl,arr4,inb,0);
        arr4 = encode_wrap(ctl,arr4);
    }

    if (inblen) {
        byte arr3[3];
        u32 j = 0;
        int padlen = 3 - inblen;

        dbgprt(ZPXHOWB64,"b64encode: POST inblen=%zu padlen=%d\n",
            inblen,padlen);

        // copy over valid bytes
        for (;  inblen > 0;  --inblen, ++j)
            arr3[j] = inb[j];

        // add zeroes as padding?
        for (;  j < 3;  ++j)
            arr3[j] = 0;

        // add final padding
        arr4 = encode_putx(ctl,arr4,arr3,padlen);
        arr4 = encode_wrap(ctl,arr4);
    }

    // sequence should end with newline
    do {
        dbgprt(ZPXHOWB64,"b64encode: WRAP anyflg=%d ctl_wrapnow=%d\n",
            anyflg,ctl->ctl_wrapnow);

        // no newline generation
        if (ctl->ctl_wrapmax <= 0)
            break;

        // no legit data
        if (! anyflg)
            break;

        // don't double up newlines if we just did a newline
        if (ctl->ctl_wrapnow)
            break;

        PUT4(0,'\n');
        encode_xcheck(ctl,arr4,1,NULL,0,0);
        ++arr4;
    } while (0);

    // always add EOS
#if 0
    *arr4 = 0;
#endif

    size_t outlen = finish(ctl,arr4);

    dbgprt(ZPXHOWB64,"b64encode: EXIT outlen=%zu\n",outlen);

    return outlen;
}

// b64decode -- decode base64 into raw binary
// RETURNS: actual output length
size_t
b64decode(b64ctl_t *ctl)
{
    const char *inb = (const char *) ctl->ctl_inb.buf_base;
    size_t inblen = ctl->ctl_inb.buf_len;
    size_t inbidx = 0;
    byte *arr3 = ctl->ctl_out.buf_base;
    int outidx = 0;
    byte arr4[4];

    dbgprt(ZPXHOWB64,"b64decode: ENTER inb=%p\n",inb);

    for (inbidx = 0;  inbidx < inblen;  ++inbidx) {
        int chr = inb[inbidx];
        dbgprt(ZPXHOWCHR,"b64decode: LOOP chr=" FMT "\n",PRT(chr));

        // stop when we hit a pad char
        if (chr == '=')
            break;

        // ignore newlines
        if (chr == '\n')
            continue;

        // not a valid char
#if ! B64DECODE_FAST
        if (! is_base64(chr)) {
            sysfault("b64decode: NONBASE64 chr=" FMT "\n",PRT(chr));
            break;
        }
#endif

        arr4[outidx++] = chr;

        if (outidx == 4) {
            arr3 = decode_put(arr3,arr4,4);
            outidx = 0;
        }
    }

    if (outidx > 0) {
        dbgprt(ZPXHOWB64,"b64decode: POST outidx=%d inblen=%zu\n",
            outidx,inblen);

        // fill remainder with pad/zeroes
        for (inbidx = outidx;  inbidx < 4;  ++inbidx)
            arr4[inbidx] = 0;

        arr3 = decode_put(arr3,arr4,outidx);
    }

    size_t outlen = finish(ctl,arr3);

    dbgprt(ZPXHOWB64,"b64decode: EXIT outlen=%zu\n",outlen);

    return outlen;
}

// b64bufdup -- safe buffer duplicate
void
b64bufdup(b64buf_t *dst,const b64buf_t *src)
{
    const char *tag = dst->buf_tag;

    dbgprt(ZPXHOWB64,"b64bufdup: ENTER dst=%s/%p src=%s/%p\n",
        dst->buf_tag,dst->buf_base,src->buf_tag,src->buf_base);

    if (dst->buf_base != NULL)
        sysfault("b64bufdup: non-null -- dst=%s buf_base=%p src=%s\n",
            tag,dst->buf_base,src->buf_tag);

    *dst = *src;

    dst->buf_tag = tag;

    dbgprt(ZPXHOWB64,"b64bufdup: EXIT\n");
}

// b64bufput -- output buffer to file
void
b64bufput(const b64buf_t *bufp,const char *ofile)
{
    int fd;
    b64buf_t buf_;
    b64buf_t *buf = &buf_;
    ssize_t xlen;

    *buf = *bufp;

    printf("b64bufput: %s buf_base=%p buf_len=%zu\n",
        ofile,buf->buf_base,buf->buf_len);

    do {
        if (buf->buf_opt & B64MMAP)
            break;

        fd = open(ofile,O_WRONLY | O_TRUNC | O_CREAT,0644);
        if (fd < 0)
            sysfault("b64bufput: unable to open '%s' -- %s\n",
                ofile,strerror(errno));

        for (;  buf->buf_len > 0;
            buf->buf_len -= xlen, buf->buf_base += xlen) {
            xlen = write(fd,buf->buf_base,buf->buf_len);
            if (xlen < 0)
                sysfault("bufput: write error -- %s\n",strerror(errno));
        }

        CLOSEME(fd);
    } while (0);
}

// b64bufget -- input file to buffer
void
b64bufget(b64buf_t *buf,const char *ifile)
{
    struct stat st;
    size_t cap;
    int fd;
    const char *etag = NULL;
    ssize_t xlen;

    do {
        fd = open(ifile,O_RDONLY);
        if (fd < 0) {
            etag = "open";
            break;
        }

        if (buf->buf_opt & B64MMAP) {
            buf->buf_fdmap = fd;

            // get file size
            if (fstat(fd,&st) < 0) {
                etag = "fstat";
                break;
            }

            buf->buf_len = st.st_size;

            buf->buf_base = mmap(NULL,buf->buf_len,PROT_READ,
                MAP_SHARED,buf->buf_fdmap,0);
            if (buf->buf_base == MAP_FAILED) {
                etag = "mmap";
                break;
            }

            break;
        }

        cap = 0;
        buf->buf_len = 0;
        buf->buf_base = NULL;

        while (1) {
            if (buf->buf_len >= cap) {
                cap += 4096;
                buf->buf_base = realloc(buf->buf_base,cap + 1);
                if (buf->buf_base == NULL) {
                    etag = "realloc";
                    break;
                }
            }

            xlen = read(fd,&buf->buf_base[buf->buf_len],cap - buf->buf_len);
            if (xlen < 0) {
                etag = "read";
                break;
            }

            if (xlen == 0)
                break;

            buf->buf_len += xlen;
        }

    } while (0);

    if (etag != NULL)
        sysfault("b64bufget: unable to %s '%s' -- %s\n",
            etag,ifile,strerror(errno));

    do {
        if (buf->buf_opt & B64MMAP)
            break;

        CLOSEME(buf->buf_fdmap);

        cap = buf->buf_len;
        buf->buf_base[cap] = 0;

        buf->buf_base = realloc(buf->buf_base,cap + 1);
        if (buf->buf_base == NULL)
            sysfault("b64bufget: realloc fail -- %s\n",strerror(errno));
    } while (0);

    printf("b64bufget: %s buf_base=%p buf_len=%zu\n",
        ifile,buf->buf_base,buf->buf_len);
}

// b64setup -- passive setup
void
b64setup(b64ctl_t *ctl,u32 opt,const char *tag)
{
    b64buf_t *buf;

    memset(ctl,0,sizeof(*ctl));

    ctl->ctl_tag = tag;

    buf = &ctl->ctl_out;
    buf->buf_fdmap = -1;
    buf->buf_opt = opt;
    buf->buf_tag = "ctl_out";

    buf = &ctl->ctl_inb;
    buf->buf_fdmap = -1;
    buf->buf_opt = opt;
    buf->buf_tag = "ctl_inb";

    ctl->ctl_fdchk = -1;
}

// init_outbuf -- initialize output buffer
static int
init_outbuf(b64ctl_t *ctl,size_t outlen)
{
    b64buf_t *buf = &ctl->ctl_out;
    int err = -1;

    dbgprt(ZPXHOWB64,"init_outbuf: ENTER ctl=%s buf=%s buf_base=%p buf_len=%zu outlen=%zu\n",
        ctl->ctl_tag,buf->buf_tag,buf->buf_base,buf->buf_len,outlen);

    buf->buf_len = outlen;

    do {
        if (! (buf->buf_opt & B64MMAP)) {
            buf->buf_base = realloc(buf->buf_base,outlen);

            if (buf->buf_base == NULL)
                break;

            err = 0;
            break;
        }

        if (ftruncate(buf->buf_fdmap,outlen) < 0)
            break;

        buf->buf_base = mmap(NULL,outlen,PROT_READ | PROT_WRITE,
            MAP_SHARED,buf->buf_fdmap,0);
        if (buf->buf_base == MAP_FAILED) {
            buf->buf_base = NULL;
            break;
        }

        err = 0;
    } while (0);

    dbgprt(ZPXHOWB64,"init_outbuf: EXIT err=%d\n",err);

    return err;
}

// b64init_encode -- initialize for encode
// RETURNS: 0=okay, -1=error
int
b64init_encode(b64ctl_t *ctl,const b64buf_t *inb,int wrapmax)
{
    size_t outlen;

    // copy in the buffer
    b64bufdup(&ctl->ctl_inb,inb);

    // we need for 4 bytes of output for each 3 bytes of input
    outlen = inb->buf_len;
    outlen *= 4;
    outlen /= 3;
    outlen += 4;

    // space for padding
    outlen += 4;

    // we wrap with newline every N bytes
    if (wrapmax == 0)
        wrapmax = 76;
    ctl->ctl_wrapmax = wrapmax;
    if (wrapmax > 0)
        outlen += (outlen + wrapmax) / wrapmax;

    return init_outbuf(ctl,outlen);
}

#if B64DECODE_FAST
// init_fast -- initialize fast decode lookup
static void
init_fast(void)
{
    const char *cp = base64_chars;

    memset(decode_fast,0xFF,sizeof(decode_fast));

    for (int chr = *cp;  chr != 0;  ++cp, chr = *cp) {
        size_t off = cp - base64_chars;
        decode_fast[chr] = off;
        dbgprt(ZPXHOWB64,"init_fast: FASTINIT chr='%c' off=%zu\n",
            chr,off);
    }
}
#endif

// b64init_decode -- initialize for decode
// RETURNS: 0=okay, -1=error
int
b64init_decode(b64ctl_t *ctl,b64buf_t *inb)
{
    size_t outlen;
    int err;

    dbgprt(ZPXHOWB64,"b64init_decode: ENTER buf_len=%zu\n",inb->buf_len);

#if B64DECODE_FAST
    if (decode_fast['B'] == 0)
        init_fast();
#endif

    if (inb != &ctl->ctl_inb)
        ctl->ctl_inb = *inb;
    inb = &ctl->ctl_inb;

    // caller may already know the input length, but ...
#if 0
    if (inb->buf_len == 0) {
        inb->buf_len = strlen(inb->buf_base);
        dbgprt(ZPXHOWB64,"b64init_decode: STRLEN buf_len=%zu\n",inb->buf_len);
    }
#endif

    // we need for 3 bytes of output for each 4 bytes of input
    outlen = inb->buf_len;
    outlen *= 3;
    outlen += 3;
    outlen /= 4;

    err = init_outbuf(ctl,outlen);

    dbgprt(ZPXHOWB64,"b64init_decode: EXIT err=%d outlen=%zu\n",err,outlen);

    return err;
}

// b64destroy -- destroy/deinit output buffer
void
b64destroy(b64ctl_t *ctl)
{
    b64bufrls(&ctl->ctl_out,"ctl_out");
    b64bufrls(&ctl->ctl_inb,"ctl_inb");
}

// b64bufrls -- destroy/deinit output buffer
void
b64bufrls(b64buf_t *buf,const char *who)
{
    size_t outlen = buf->buf_len;
    int err = -1;

    dbgprt(ZPXHOWB64,"b64bufrls: ENTER buf_opt=%8.8X buf_base=%p outlen=%zu (from %s)\n",
        buf->buf_opt,buf->buf_base,outlen,who);

    do {
        if (! (buf->buf_opt & B64MMAP)) {
            FREEME(buf->buf_base);
            err = 0;
            break;
        }

        if (munmap(buf->buf_base,outlen) < 0)
            break;

        if (buf->buf_opt & B64TRIM) {
            if (ftruncate(buf->buf_fdmap,outlen) < 0)
                break;
        }

        CLOSEME(buf->buf_fdmap);
    } while (0);

    if (err < 0)
        exit(1);

    buf->buf_base = NULL;
    buf->buf_len = 0;

    dbgprt(ZPXHOWB64,"b64bufrls: EXIT\n");
}

FILE: b64test.c

// b64test.c -- test program for base64

#include <base64.h>

int opt_a;                              // exhaustive test
int opt_L;                              // binary file length
int opt_k;                              // keep temp files
int opt_m;                              // use mmap
u32 opt_R = 1;                          // random seed
int opt_T;                              // number of tests
int opt_W;                              // wrap length

int tstno = 1;                          // current test number

#define ALLFILES(_cmd) \
    _cmd(bin) \
    _cmd(encexp) \
    _cmd(encact) \
    _cmd(dcdexp) \
    _cmd(dcdact)

#define FILEDEF(_pre) \
    const char *_pre##_file = #_pre ".TMP";
ALLFILES(FILEDEF)

#define FILERM(_pre) \
    unlink(_pre##_file);

// initial random binary buffer
B64BUFDEF(binbuf);

// encoding control
B64BUFDEF(encbuf);
B64CTLDEF(ctlenc);

// decoding control
B64BUFDEF(dcdbuf);
B64CTLDEF(ctldcd);

int
doexec(const char *cmd)
{

    printf("DOEXEC: %s\n",cmd);

    int ignflg = (cmd[0] == '!');
    if (ignflg)
        ++cmd;

    int code = system(cmd);

    if (code && (! ignflg))
        exit(1);

    return code;
}

int
xrand(void)
{
    return rand_r(&opt_R);
}

// doclean -- remove temp files
void
doclean(void)
{
    do {
        if (opt_k)
            break;
        ALLFILES(FILERM);
    } while (0);
}

void
dotest(int binlen)
{
    int err;
    u32 opt = 0;
    b64ctl_t *ctl;
    b64buf_t *buf;
    char cmd[1000];

    printf("\n");
    for (int col = 1;  col <= 80;  ++col)
        putchar('-');
    printf("\n");

    dbgprt(ZPXHOWB64,"dotest: ENTER binlen=%d\n",binlen);

    buf = &binbuf;
    if (binlen <= 0)
        buf->buf_len = -binlen;
    else
        buf->buf_len = xrand() % binlen;

    printf("dotest: %d -- R=%u L=%8.8zX/%zu\n",
        tstno,opt_R,buf->buf_len,buf->buf_len);

    doclean();

    if (opt_m) {
        opt |= B64MMAP;
        opt |= B64TRIM;
    }

    // NOTE: this gets freed in the b64destroy for ctlenc below
    buf->buf_base = NULL;

    buf->buf_base = realloc(buf->buf_base,buf->buf_len);
    if (buf->buf_base == NULL)
        sysfault("dotest: realloc failure -- %s\n",strerror(errno));

    // get a random buffer
    for (size_t bufidx = 0;  bufidx < buf->buf_len;  ++bufidx)
        buf->buf_base[bufidx] = xrand();

    // dump it for base64 program
    b64bufput(buf,bin_file);

    // have base64 encode it
    sprintf(cmd,"base64 %s > %s",bin_file,encexp_file);
    doexec(cmd);

    ctl = &ctlenc;
    b64setup(ctl,opt,"ctlenc");

    err = b64init_encode(ctl,buf,opt_W);
    if (err < 0)
        sysfault("dotest: b64init_encode failure -- %s\n",strerror(errno));

#if B64ENCODE_XCHECK
    ctl->ctl_fdchk = open(encexp_file,O_RDONLY);
#endif

    // have our routine encode it
    b64encode(ctl);

#if B64ENCODE_XCHECK
    CLOSEME(ctl->ctl_fdchk);
#endif

    // save it to a file
    b64bufput(&ctl->ctl_out,encact_file);

    b64destroy(ctl);

    // compare them
    sprintf(cmd,"diff -u %s %s",encexp_file,encact_file);
    doexec(cmd);

    // decode it
    sprintf(cmd,"base64 -d %s > %s",encexp_file,dcdexp_file);
    doexec(cmd);

    // passive setup
    ctl = &ctldcd;
    b64setup(ctl,opt,"ctldcd");

    // load up the encoded file into the decode input buffer
    b64bufget(&dcdbuf,encact_file);

    // decode the data
    err = b64init_decode(ctl,&dcdbuf);
    if (err < 0)
        sysfault("dotest: b64init_decode failure -- %s\n",strerror(errno));

    // have our routine decode it
    b64decode(ctl);

    b64bufput(&ctl->ctl_out,dcdact_file);
    b64destroy(ctl);

    // compare them
    sprintf(cmd,"cmp %s %s",dcdexp_file,dcdact_file);
    doexec(cmd);

    // final temp file cleanup
    doclean();

    printf("dotest: complete\n");

    dbgprt(ZPXHOWB64,"dotest: EXIT\n");
}

int
main(int argc,char **argv)
{
    char *cp;

    --argc;
    ++argv;

    setlinebuf(stdout);

    // use a special output directory
    do {
        cp = getenv("GENDIR");
        if (cp == NULL)
            break;

        printf("GENDIR = %s\n",cp);
        if (chdir(cp) < 0)
            sysfault("main: chdir fault -- %s\n",strerror(errno));
    } while (0);

    // parse options
    for (;  argc > 0;  --argc, ++argv) {
        cp = *argv;
        if (*cp != '-')
            break;

        cp += 2;
        switch (cp[-1]) {
        case 'a':
            opt_a = (*cp != 0) ? atoi(cp) : 1;
            break;

        case 'L':
            opt_L = (*cp != 0) ? atoi(cp) : 1000;
            break;

        case 'k':
            opt_k = ! opt_k;
            break;

        case 'm':
            opt_m = ! opt_m;
            break;

        case 'R':
            opt_R = (*cp != 0) ? atoi(cp) : 1;
            break;

        case 'T':
            opt_T = (*cp != 0) ? atoi(cp) : 100;
            break;

        case 'W':
            opt_W = atoi(cp);
            break;
        }
    }

    // ensure at least one test
    if (opt_T <= 0)
        opt_T = 1;

    do {
        // ensure we have a non-zero length
        if (opt_L == 0)
            opt_L = 100;

        // do random testing
        if (opt_a < 0) {
            for (;  tstno <= opt_T;  ++tstno)
                dotest(opt_L);
            break;
        }

        // get absolute value
        if (opt_L < 0)
            opt_L = -opt_L;

        // ensure we get at least one test
        if (opt_L < opt_a)
            opt_L = opt_a;

        // do a range of explicit lengths
        for (int binlen = opt_a;  binlen <= opt_L;  ++binlen, ++tstno)
            dotest(-binlen);
    } while (0);

    printf("%d tests completed\n",tstno);

    return 0;
}

FILE: dbgcom.h

// dbgflg.h -- base/debug definitions

#ifndef _dbgflg_h_
#define _dbgflg_h_

#include <stdio.h>
#include <stdlib.h>

typedef unsigned char byte;
typedef unsigned int u32;

#define ZPXHOWALL(_cmd) \
    _cmd(ZPXHOWB64) \
    _cmd(ZPXHOWCHR) \
    _cmd(ZPXHOWTST)

#define _DBGENUM(_sym) \
    _sym,
enum {
    ZPXHOWANY,
    ZPXHOWALL(_DBGENUM)
    ZPXHOWMAX
};
byte dbgflg[ZPXHOWMAX];

static inline byte
dbgok(int lvl)
{

    return dbgflg[lvl];
}

#if DEBUG || _USE_ZPRT_
#define XVERBOSE    1
#define dbgprt(_lvl,_fmt...) \
    do { \
        if (! dbgok(_lvl)) \
            break; \
        int sverr = errno; \
        printf(_fmt); \
        errno = sverr; \
    } while (0)
#define inline_nodebug          /**/
#else
#define XVERBOSE    0
#define inline_nodebug          inline
#define dbgprt(_fmt...) \
    do { } while (0)
#endif

#define sysfault(_fmt...) \
    do { \
        printf(_fmt); \
        exit(1); \
    } while (0)

#define RNGE(_val,_lo,_hi) \
    (((_val) >= (_lo)) && ((_val) <= (_hi)))

#define PRTABLE(_val) \
    RNGE(_val,0x20,0x7E) ? (_val) : '.'

#define CLOSEME(_fd) \
    do { \
        if (_fd < 0) \
            break; \
        dbgprt(ZPXHOWB64,"CLOSEME: fd=%d (from %d)\n",_fd,__LINE__); \
        close(_fd); \
        _fd = -1; \
    } while (0)

#define FREEME(_ptr) \
    do { \
        dbgprt(ZPXHOWB64,"FREEME: ptr=%p (from %d)\n",_ptr,__LINE__); \
        free(_ptr); \
        _ptr = NULL; \
    } while (0)

#endif

FILE: dbgcom.c

// dbgcom.c -- tracing setup

#include <dbgcom.h>

struct dbgdef {
    int dbg_lvl;
    const char *dbg_sym;
};

#define _ZPXDEF(_sym) \
    { .dbg_lvl = _sym, .dbg_sym = #_sym },
struct dbgdef dbgdef[] = {
    ZPXHOWALL(_ZPXDEF)
    { .dbg_sym = NULL }
};

void __attribute__((constructor))
dbginit(void)
{
    struct dbgdef *dbg = dbgdef;

    for (;  dbg->dbg_sym != NULL;  ++dbg) {
        char *cp = getenv(dbg->dbg_sym);

        if (cp == NULL)
            continue;

        if (! atoi(cp))
            continue;

        dbgflg[dbg->dbg_lvl] = 1;
        dbgflg[ZPXHOWANY] = 1;
    }
}
Craig Estey
  • 30,627
  • 4
  • 24
  • 48