codec, baseconv: Cleanup of the various binary encoding functions.
authorMark Wooding <mdw@distorted.org.uk>
Sun, 3 May 2009 00:44:57 +0000 (01:44 +0100)
committerMark Wooding <mdw@distorted.org.uk>
Sat, 20 Oct 2012 11:40:07 +0000 (12:40 +0100)
The new codec interface provides an object-based interface to binary
encodings.

The baseconv code is a new unified implementation of Base64, Base32 and
hex encoding and decoding, built from a digit-at-a-time base conversion
core.  It implements a number of variations on the basic encodings,
including the URL-and-filename-safe variant.

I've adapted the current tests to use the new test program, but that's
not long for this world.

18 files changed:
codec/Makefile.am
codec/base32.3 [deleted file]
codec/base32.c [deleted file]
codec/base32.h
codec/base64.3
codec/base64.c [deleted file]
codec/base64.h
codec/baseconv.c [new file with mode: 0644]
codec/bincode.1 [new file with mode: 0644]
codec/bincode.c [new file with mode: 0644]
codec/codec.3 [new file with mode: 0644]
codec/codec.c [new file with mode: 0644]
codec/codec.h [new file with mode: 0644]
codec/hex.3 [deleted file]
codec/hex.c [deleted file]
codec/hex.h
codec/null-codec.c [new file with mode: 0644]
debian/changelog

index 71a4e48..968ebf9 100644 (file)
@@ -32,66 +32,54 @@ libcodec_la_SOURCES  =
 ###--------------------------------------------------------------------------
 ### Component files.
 
+## Codec base.
+pkginclude_HEADERS     += codec.h
+libcodec_la_SOURCES    += codec.c
+LIBMANS                        += codec.3
+
+## null
+libcodec_la_SOURCES    += null-codec.c
+
+## Simple binary base-conversion codecs.
+pkginclude_HEADERS     += base64.h base32.h hex.h
+libcodec_la_SOURCES    += baseconv.c
+LIBMANS                        += base64.3
+
 ## form-urlencoded
 pkginclude_HEADERS     += url.h
 libcodec_la_SOURCES    += url.c
 LIBMANS                        += url.3
 
-## base64
-pkginclude_HEADERS     += base64.h
-libcodec_la_SOURCES    += base64.c
-LIBMANS                        += base64.3
-
-EXTRA_DIST             += base64.in base64.ref
-CLEANFILES             += base64.out
-tests:: base64.t base64.in base64.ref
-       ./base64.t <$(srcdir)/base64.in >base64.out
-       cmp base64.out $(srcdir)/base64.ref
-       ./base64.t -d <$(srcdir)/base64.ref >base64.out
-       cmp base64.out $(srcdir)/base64.in
-       @echo "base64 OK"
-
-check_PROGRAMS         += base64.t
-base64_t_SOURCES        = base64.c
-base64_t_CPPFLAGS       = $(TEST_CPPFLAGS)
-base64_t_LDFLAGS        = -static
-
-## base32
-pkginclude_HEADERS     += base32.h
-libcodec_la_SOURCES    += base32.c
-LIBMANS                        += base32.3
-
-EXTRA_DIST             += base32.in base32.ref
-CLEANFILES             += base32.out
-tests:: base32.t base32.in base32.ref
-       ./base32.t <$(srcdir)/base32.in >base32.out
-       cmp base32.out $(srcdir)/base32.ref
-       ./base32.t -d <$(srcdir)/base32.ref >base32.out
-       cmp base32.out $(srcdir)/base32.in
-       @echo "base32 OK"
-
-check_PROGRAMS         += base32.t
-base32_t_SOURCES        = base32.c
-base32_t_CPPFLAGS       = $(TEST_CPPFLAGS)
-base32_t_LDFLAGS        = -static
+## Test program.
+bin_PROGRAMS           += bincode
+PROGMANS               += bincode.1
 
-## hex
-pkginclude_HEADERS     += hex.h
-libcodec_la_SOURCES    += hex.c
-LIBMANS                        += hex.3
+bincode_SOURCES                 = bincode.c
+bincode_LDADD           = libcodec.la
+bincode_LDADD          += ../mem/libmem.la
+bincode_LDADD          += ../struct/libstruct.la
+bincode_LDADD          += $(UTIL_LIBS)
 
-EXTRA_DIST             += hex.in hex.ref
-CLEANFILES             += hex.out
-tests:: hex.t hex.in hex.ref
-       ./hex.t <$(srcdir)/hex.in >hex.out
-       cmp hex.out $(srcdir)/hex.ref
-       ./hex.t -d <$(srcdir)/hex.ref >hex.out
-       cmp hex.out $(srcdir)/hex.in
-       @echo "hex OK"
+EXTRA_DIST             += base64.in base32.in hex.in
+EXTRA_DIST             += base64.ref base32.ref hex.ref
+CLEANFILES             += base64.out base32.out hex.out
 
-check_PROGRAMS         += hex.t
-hex_t_SOURCES           = hex.c
-hex_t_CPPFLAGS          = $(TEST_CPPFLAGS)
-hex_t_LDFLAGS           = -static
+TEST_CODECS             = base64 base32 hex
+tests:: bincode
+       set -e; \
+       for codec in $(TEST_CODECS); do \
+         case $$codec in \
+           hex) flags=lowerc,ignjunk width=64 ;; \
+           base32) flags=ignjunk width=32 ;; \
+           *) flags=ignjunk width=64 ;; \
+         esac; \
+         ./bincode -e -i'\t' -f$$flags -m$$width -o$$codec.out $$codec \
+                 $(srcdir)/$$codec.in; \
+         cmp $$codec.out $(srcdir)/$$codec.ref; \
+         ./bincode -d -fignjunk -f$$flags -o$$codec.out $$codec \
+                 $(srcdir)/$$codec.ref; \
+         cmp $$codec.out $(srcdir)/$$codec.in; \
+         echo "$$codec OK"; \
+       done
 
 ###----- That's all, folks --------------------------------------------------
diff --git a/codec/base32.3 b/codec/base32.3
deleted file mode 100644 (file)
index 13d57c8..0000000
+++ /dev/null
@@ -1,95 +0,0 @@
-.\" -*-nroff-*-
-.TH base32 3 "28 September 2004" "Straylight/Edgeware" "mLib utilities library"
-.SH NAME
-base32 \- conversion to and from base32 encoding
-.\" @base32_encode
-.\" @base32_decode
-.\" @base32_init
-.SH SYNOPSIS
-.nf
-.B "#include <mLib/base32.h>"
-
-.BI "void base32_encode(base32_ctx *" ctx ,
-.BI "                   const void *" p ", size_t " sz ,
-.BI "                   dstr *" d );
-.BI "void base32_decode(base32_ctx *" ctx ,
-.BI "                   const void *" p ", size_t " sz ,
-.BI "                   dstr *" d );
-.BI "void base32_init(base32_ctx *" ctx );
-.fi
-.SH DESCRIPTION
-The
-.B base32
-functions perform base32 encoding and decoding of arbitrary binary
-strings.  The base32 encoding is defined by RFC2938.
-.PP
-Before encoding or decoding a string, a
-.I context
-(of type
-.BR base32_ctx )
-must be initialized, by passing it to
-.BR base32_init .
-The context contains data which must be retained between calls to encode
-or decode substrings.  The
-.B base32_init
-function sets up initial values for the data, and sets up defaults for
-the output formatting settings (see below).
-.PP
-Encoding of a string is performed by the
-.B base32_encode
-function.  It is passed a pointer to a context block
-.IR ctx ,
-the input substring to encode passed by address
-.I p
-and length
-.IR sz ,
-and a pointer to a dynamic string
-.I d
-in which to write its output (see
-.BR dstr (3)
-for details on dynamic strings).  Once all the input data has been
-passed through
-.B base32_encode
-it is necessary to flush the final few bytes of output.  This is
-achieved by passing
-.B base32_encode
-a null pointer as its source argument.  It is an error to attempt to
-continue encoding after flushing output.
-.PP
-The output of the
-.B base32_encode
-function is formatted into lines using values from the context
-structure.  The
-.B indent
-member is a pointer to a null-terminated string which is used to
-separate the output lines.  The default indent string contains only a
-newline character.  The
-.B maxline
-member gives the maximum length of line that
-.B base32_encode
-is allowed to produce.  If this is not a multiple of 4, it is rounded
-up to the next highest multiple of four before use.  A value of zero
-instructs
-.B base32_encode
-not to perform line splitting: the output will be a single (possibly
-very long) output line.  The default maximum line length is 72
-characters.  You may set these parameters by direct assignment to the
-context structure once it has been initialized.
-.PP
-Decoding is performed similarly by the
-.B base32_decode
-function.  The comments above about flushing output apply equally to
-decoding.
-.PP
-Decoding ignores all whitespace characters in the encoded string.  It
-also ignores
-.RB ` = '
-characters in the string and works out the final block length
-automatically based on the input size.
-.SH "SEE ALSO"
-.BR base64 (3),
-.BR dstr (3),
-.BR hex (3),
-.BR mLib (3).
-.SH AUTHOR
-Mark Wooding, <mdw@distorted.org.uk>
diff --git a/codec/base32.c b/codec/base32.c
deleted file mode 100644 (file)
index bbf6974..0000000
+++ /dev/null
@@ -1,298 +0,0 @@
-/* -*-c-*-
- *
- * Base32 encoding and decoding.
- *
- * (c) 1997 Straylight/Edgeware
- */
-
-/*----- Licensing notice --------------------------------------------------*
- *
- * This file is part of the mLib utilities library.
- *
- * mLib is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Library General Public License as
- * published by the Free Software Foundation; either version 2 of the
- * License, or (at your option) any later version.
- *
- * mLib is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Library General Public License for more details.
- *
- * You should have received a copy of the GNU Library General Public
- * License along with mLib; if not, write to the Free
- * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
- * MA 02111-1307, USA.
- */
-
-/*----- Header files ------------------------------------------------------*/
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "base32.h"
-#include "dstr.h"
-
-/*----- Important tables --------------------------------------------------*/
-
-static const char encodemap[] = { "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567" };
-
-static const signed char decodemap[] = {
-  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  /* 0x */
-  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  /* 1x */
-  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  /* 2x */
-  -1, -1, 26, 27, 28, 29, 30, 31, -1, -1, -1, -1, -1, -1, -1, -1,  /* 3x */
-  -1,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,  /* 4x */
-  15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,  /* 5x */
-  -1,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,  /* 6x */
-  15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,  /* 7x */
-};
-
-/*----- Main code ---------------------------------------------------------*/
-
-/* --- @base32_encode@ --- *
- *
- * Arguments:  @base32_ctx *ctx@ = pointer to a context block
- *             @const void *p@ = pointer to a source buffer
- *             @size_t sz@ = size of the source buffer
- *             @dstr *d@ = pointer to destination string
- *
- * Returns:    ---
- *
- * Use:                Encodes a binary string in base32.  To flush out the final
- *             few characters (if necessary), pass a null source pointer.
- */
-
-void base32_encode(base32_ctx *ctx,
-                  const void *p, size_t sz,
-                  dstr *d)
-{
-  if (p) {
-    unsigned long accl = ctx->accl, acch = ctx->acch;
-    unsigned qsz = ctx->qsz;
-    const unsigned char *src = p;
-
-    while (sz) {
-      acch = (acch << 8) | ((accl >> 24) & 0xff);
-      accl = (accl << 8) | *src++;
-      qsz++;
-      sz--;
-      if (qsz == 5) {
-       DPUTC(d, encodemap[(acch >> 3) & 0x1f]);
-       DPUTC(d, encodemap[((acch << 2) & 0x1c) | ((accl >> 30) & 0x03)]);
-       DPUTC(d, encodemap[(accl >> 25) & 0x1f]);
-       DPUTC(d, encodemap[(accl >> 20) & 0x1f]);
-       DPUTC(d, encodemap[(accl >> 15) & 0x1f]);
-       DPUTC(d, encodemap[(accl >> 10) & 0x1f]);
-       DPUTC(d, encodemap[(accl >>  5) & 0x1f]);
-       DPUTC(d, encodemap[(accl >>  0) & 0x1f]);
-       ctx->lnlen += 8;
-       if (ctx->maxline && ctx->lnlen >= ctx->maxline) {
-         dstr_puts(d, ctx->indent);
-         ctx->lnlen = 0;
-       }
-       qsz = 0;
-       accl = acch = 0;
-      }
-    }
-
-    ctx->acch = acch; ctx->accl = accl;
-    ctx->qsz = qsz;
-  } else {
-    unsigned long accl = ctx->accl, acch = ctx->acch;
-    unsigned qsz = ctx->qsz;
-
-    if (qsz) {
-      unsigned pad = 5 - qsz;
-      if (pad > 3) {
-       acch = accl << (pad * 8 - 32);
-       accl = 0;
-      } else {
-       acch = (acch << (8 * pad)) | ((accl & 0xffffffff) >> (32 - 8 * pad));
-       accl = accl << (8 * pad);
-      }
-      qsz = 40;
-      pad *= 8;
-      while (qsz > pad) {
-       DPUTC(d, encodemap[(acch >> 3) & 0x1f]);
-       acch = (acch << 5) | ((accl >> 27) & 0x1f);
-       accl = (accl << 5);
-       qsz -= 5;
-      }
-      while (qsz) {
-       DPUTC(d, '=');
-       qsz -= 5;
-      }
-      ctx->lnlen += 8;
-    }
-    ctx->qsz = 0;
-    ctx->acch = ctx->accl = 0;
-  }
-}
-
-/* --- @base32_decode@ --- *
- *
- * Arguments:  @base32_ctx *ctx@ = pointer to a context block
- *             @const void *p@ = pointer to a source buffer
- *             @size_t sz@ = size of the source buffer
- *             @dstr *d@ = pointer to destination string
- *
- * Returns:    ---
- *
- * Use:                Decodes a binary string in base32.  To flush out the final
- *             few characters (if necessary), pass a null source pointer.
- */
-
-void base32_decode(base32_ctx *ctx,
-                  const void *p, size_t sz,
-                  dstr *d)
-{
-  if (p) {
-    unsigned long accl = ctx->accl, acch = ctx->acch;
-    unsigned qsz = ctx->qsz;
-    const char *src = p;
-    int ch;
-
-    while (sz) {
-
-      /* --- Get the next character and convert it --- */
-
-      ch = *src++;
-      if (ch >= 128 || ch < 0)
-       ch = -1;
-      else
-       ch = decodemap[ch];
-      sz--;
-      if (ch == -1)
-       continue;
-
-      /* --- Bung it in the accumulator --- */
-
-      acch = (acch << 5) | ((accl >> 27) & 0x1f);
-      accl = (accl << 5) | ch;
-      qsz++;
-
-      /* --- Maybe write out a completed triplet --- */
-
-      if (qsz == 8) {
-       DPUTC(d, (acch >>  0) & 0xff);
-       DPUTC(d, (accl >> 24) & 0xff);
-       DPUTC(d, (accl >> 16) & 0xff);
-       DPUTC(d, (accl >>  8) & 0xff);
-       DPUTC(d, (accl >>  0) & 0xff);
-       acch = accl = 0;
-       qsz = 0;
-      }
-    }
-
-    ctx->acch = acch; ctx->accl = accl;
-    ctx->qsz = qsz;
-  } else {
-
-    /* --- Notes about the tail-end bits --- *
-     *
-     * I'll use the queue size to work out how many tail-end bytes I ought to
-     * write.  This isn't strictly right, but it's easier.
-     */
-
-    unsigned long acch = ctx->acch, accl = ctx->accl;
-    unsigned qsz = ctx->qsz;
-
-    /* --- Now fiddle with everything else --- *
-     *
-     * There's a bodge here for invalid encodings which have a funny number
-     * of quintets in the final group.  I'm not sure this is really worth
-     * having, but it might save some unexpected behaviour.  (Not that you
-     * won't still get unexpected behaviour if the stream is completely
-     * empty, of course.)
-     */
-
-    if (qsz) {
-      unsigned pad = 8 - qsz;
-      if (pad > 6) {
-       acch = accl << (5 * pad - 32);
-       accl = 0;
-      } else {
-       acch = (acch << (5 * pad)) | ((accl & 0xffffffff) >> (32 - 5 * pad));
-       accl = accl << (5 * pad);
-      }
-
-      qsz *= 5;
-      while (qsz >= 8) {
-       DPUTC(d, acch & 0xff);
-       acch = accl >> 24;
-       accl <<= 8;
-       qsz -= 8;
-      }
-    }
-
-    /* --- That seems to be good enough --- */
-
-    ctx->qsz = 0;
-    ctx->acch = ctx->accl = 0;
-  }
-}
-
-/* --- @base32_init@ --- *
- *
- * Arguments:  @base32_ctx *ctx@ = pointer to context block to initialize
- *
- * Returns:    ---
- *
- * Use:                Initializes a base32 context properly.
- */
-
-void base32_init(base32_ctx *ctx)
-{
-  ctx->accl = ctx->acch = 0;
-  ctx->qsz = 0;
-  ctx->lnlen = 0;
-  ctx->indent = "\n";
-  ctx->maxline = 72;
-}
-
-/*----- Test driver code --------------------------------------------------*/
-
-#ifdef TEST_RIG
-
-int main(int argc, char *argv[])
-{
-  unsigned char buf[BUFSIZ];
-  dstr d = DSTR_INIT;
-  base32_ctx ctx;
-  void (*proc)(base32_ctx *, const void *, size_t, dstr *);
-  size_t sz;
-
-  base32_init(&ctx);
-
-  if (argc > 1 && strcmp(argv[1], "-d") == 0)
-    proc = base32_decode;
-  else {
-    proc = base32_encode;
-    putchar('\t');
-    ctx.indent = "\n\t";
-    ctx.maxline = 32;
-  }
-
-  do {
-    sz = fread(buf, 1, sizeof(buf), stdin);
-    if (sz) {
-      proc(&ctx, buf, sz, &d);
-      dstr_write(&d, stdout);
-      dstr_destroy(&d);
-    }
-  } while (sz == sizeof(buf));
-
-  proc(&ctx, 0, 0, &d);
-  dstr_write(&d, stdout);
-
-  if (proc == base32_encode)
-    putchar('\n');
-
-  return (0);
-}
-
-#endif
-
-/*----- That's all, folks -------------------------------------------------*/
index a6333e9..d2d66f1 100644 (file)
@@ -95,6 +95,10 @@ extern void base32_decode(base32_ctx */*ctx*/,
 
 extern void base32_init(base32_ctx */*ctx*/);
 
+/*----- Codec object interface --------------------------------------------*/
+
+extern const codec_class base32_class, base32hex_class;
+
 /*----- That's all, folks -------------------------------------------------*/
 
 #ifdef __cplusplus
index 9f5474a..ddab190 100644 (file)
@@ -1,13 +1,21 @@
 .\" -*-nroff-*-
 .TH base64 3 "20 June 1999" "Straylight/Edgeware" "mLib utilities library"
 .SH NAME
-base64 \- conversion to and from base64 encoding
+base64, base32, hex \- obsolete binary encoding functions
 .\" @base64_encode
 .\" @base64_decode
 .\" @base64_init
+.\" @base32_encode
+.\" @base32_decode
+.\" @base32_init
+.\" @hex_encode
+.\" @hex_decode
+.\" @hex_init
 .SH SYNOPSIS
 .nf
 .B "#include <mLib/base64.h>"
+.B "#include <mLib/base32.h>"
+.B "#include <mLib/hex.h>"
 
 .BI "void base64_encode(base64_ctx *" ctx ,
 .BI "                   const void *" p ", size_t " sz ,
@@ -16,27 +24,59 @@ base64 \- conversion to and from base64 encoding
 .BI "                   const void *" p ", size_t " sz ,
 .BI "                   dstr *" d );
 .BI "void base64_init(base64_ctx *" ctx );
+
+.BI "void base32_encode(base32_ctx *" ctx ,
+.BI "                   const void *" p ", size_t " sz ,
+.BI "                   dstr *" d );
+.BI "void base32_decode(base32_ctx *" ctx ,
+.BI "                   const void *" p ", size_t " sz ,
+.BI "                   dstr *" d );
+.BI "void base32_init(base32_ctx *" ctx );
+
+.BI "void hex_encode(hex_ctx *" ctx ,
+.BI "                const void *" p ", size_t " sz ,
+.BI "                dstr *" d );
+.BI "void hex_decode(hex_ctx *" ctx ,
+.BI "                const void *" p ", size_t " sz ,
+.BI "                dstr *" d );
+.BI "void hex_init(hex_ctx *" ctx );
 .fi
 .SH DESCRIPTION
 The
-.B base64
-functions perform base64 encoding and decoding of arbitrary binary
-strings.  The base64 encoding is defined by RFC2045.
+.BR base64 ,
+.B base32
+and
+.B hex
+functions perform encoding and decoding of arbitrary binary strings, as
+defined by RFC4648, but without error reporting.  These functions are
+obsolete, and new applications should use the
+.BR codec (3)
+interface, which provides more encoding and decoding options, and proper
+error detection.
+.PP
+The interfaces to these sets of functions is very similar: in
+the following description,
+.I prefix
+stands for one of
+.BR base64 ,
+.BR base32 ,
+or
+.BR hex .
 .PP
 Before encoding or decoding a string, a
 .I context
 (of type
-.BR base64_ctx )
+.IB prefix _ctx \fR)
 must be initialized, by passing it to
-.BR base64_init .
+.IB prefix _init \fR.
 The context contains data which must be retained between calls to encode
 or decode substrings.  The
-.B base64_init
+.IB prefix _init
 function sets up initial values for the data, and sets up defaults for
 the output formatting settings (see below).
 .PP
 Encoding of a string is performed by the
-.B base64_encode
+.IB prefix _encode
 function.  It is passed a pointer to a context block
 .IR ctx ,
 the input substring to encode passed by address
@@ -49,15 +89,15 @@ in which to write its output (see
 .BR dstr (3)
 for details on dynamic strings).  Once all the input data has been
 passed through
-.B base64_encode
+.IB prefix _encode
 it is necessary to flush the final few bytes of output.  This is
 achieved by passing
-.B base64_encode
+.I prefix _encode
 a null pointer as its source argument.  It is an error to attempt to
 continue encoding after flushing output.
 .PP
 The output of the
-.B base64_encode
+.IB prefix _encode
 function is formatted into lines using values from the context
 structure.  The
 .B indent
@@ -66,30 +106,28 @@ separate the output lines.  The default indent string contains only a
 newline character.  The
 .B maxline
 member gives the maximum length of line that
-.B base64_encode
+.IB prefix _encode
 is allowed to produce.  If this is not a multiple of 4, it is rounded
 up to the next highest multiple of four before use.  A value of zero
 instructs
-.B base64_encode
+.IB prefix _encode
 not to perform line splitting: the output will be a single (possibly
 very long) output line.  The default maximum line length is 72
 characters.  You may set these parameters by direct assignment to the
 context structure once it has been initialized.
 .PP
 Decoding is performed similarly by the
-.B base64_decode
+.IB prefix _decode
 function.  The comments above about flushing output apply equally to
 decoding.
 .PP
-Decoding ignores all whitespace characters in the encoded string.  It
-also ignores
+Decoding ignores all errors.  In particular, whitespace is ignored, and
+in the case of Base64 and Base32 encodings, it also ignores
 .RB ` = '
-characters in the string and works out the final block length
-automatically based on the input size.
+characters in the string.
 .SH "SEE ALSO"
-.BR base32 (3),
+.BR codec (3),
 .BR dstr (3),
-.BR hex (3),
 .BR mLib (3).
 .SH AUTHOR
 Mark Wooding, <mdw@distorted.org.uk>
diff --git a/codec/base64.c b/codec/base64.c
deleted file mode 100644 (file)
index ae3c987..0000000
+++ /dev/null
@@ -1,287 +0,0 @@
-/* -*-c-*-
- *
- * Base64 encoding and decoding.
- *
- * (c) 1997 Straylight/Edgeware
- */
-
-/*----- Licensing notice --------------------------------------------------*
- *
- * This file is part of the mLib utilities library.
- *
- * mLib is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Library General Public License as
- * published by the Free Software Foundation; either version 2 of the
- * License, or (at your option) any later version.
- *
- * mLib is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Library General Public License for more details.
- *
- * You should have received a copy of the GNU Library General Public
- * License along with mLib; if not, write to the Free
- * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
- * MA 02111-1307, USA.
- */
-
-/*----- Header files ------------------------------------------------------*/
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "base64.h"
-#include "dstr.h"
-
-/*----- Important tables --------------------------------------------------*/
-
-static const char encodemap[] = { "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
-                                 "abcdefghijklmnopqrstuvwxyz"
-                                 "0123456789+/" };
-
-static const signed char decodemap[] = {
-  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  /* 0x */
-  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  /* 1x */
-  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,  /* 2x */
-  52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,  /* 3x */
-  -1,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,  /* 4x */
-  15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,  /* 5x */
-  -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36 ,37, 38, 39, 40,  /* 6x */
-  41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1   /* 7x */
-};
-
-/*----- Main code ---------------------------------------------------------*/
-
-/* --- @base64_encode@ --- *
- *
- * Arguments:  @base64_ctx *ctx@ = pointer to a context block
- *             @const void *p@ = pointer to a source buffer
- *             @size_t sz@ = size of the source buffer
- *             @dstr *d@ = pointer to destination string
- *
- * Returns:    ---
- *
- * Use:                Encodes a binary string in base64.  To flush out the final
- *             few characters (if necessary), pass a null source pointer.
- */
-
-void base64_encode(base64_ctx *ctx,
-                  const void *p, size_t sz,
-                  dstr *d)
-{
-  if (p) {
-    unsigned long acc = ctx->acc;
-    unsigned qsz = ctx->qsz;
-    const unsigned char *src = p;
-
-    while (sz) {
-      acc = (acc << 8) | *src++;
-      qsz++;
-      sz--;
-      if (qsz == 3) {
-       DPUTC(d, encodemap[(acc >> 18) & 0x3f]);
-       DPUTC(d, encodemap[(acc >> 12) & 0x3f]);
-       DPUTC(d, encodemap[(acc >>  6) & 0x3f]);
-       DPUTC(d, encodemap[(acc >>  0) & 0x3f]);
-       ctx->lnlen += 4;
-       if (ctx->maxline && ctx->lnlen >= ctx->maxline) {
-         dstr_puts(d, ctx->indent);
-         ctx->lnlen = 0;
-       }
-       qsz = 0;
-       acc = 0;
-      }
-    }
-
-    ctx->acc = acc;
-    ctx->qsz = qsz;
-  } else {
-    unsigned long acc = ctx->acc;
-    unsigned qsz = ctx->qsz;
-
-    switch (qsz) {
-      case 0:
-       break;
-      case 1:
-       acc <<= 16;
-       DPUTC(d, encodemap[(acc >> 18) & 0x3f]);
-       DPUTC(d, encodemap[(acc >> 12) & 0x3f]);
-       DPUTC(d, '=');
-       DPUTC(d, '=');
-       ctx->lnlen += 4;
-       break;
-      case 2:
-       acc <<= 8;
-       DPUTC(d, encodemap[(acc >> 18) & 0x3f]);
-       DPUTC(d, encodemap[(acc >> 12) & 0x3f]);
-       DPUTC(d, encodemap[(acc >>  6) & 0x3f]);
-       DPUTC(d, '=');
-       ctx->lnlen += 4;
-       break;
-    }
-    ctx->qsz = 0;
-    ctx->acc = 0;
-  }
-}
-
-/* --- @base64_decode@ --- *
- *
- * Arguments:  @base64_ctx *ctx@ = pointer to a context block
- *             @const void *p@ = pointer to a source buffer
- *             @size_t sz@ = size of the source buffer
- *             @dstr *d@ = pointer to destination string
- *
- * Returns:    ---
- *
- * Use:                Decodes a binary string in base64.  To flush out the final
- *             few characters (if necessary), pass a null source pointer.
- */
-
-void base64_decode(base64_ctx *ctx,
-                  const void *p, size_t sz,
-                  dstr *d)
-{
-  if (p) {
-    unsigned long acc = ctx->acc;
-    unsigned qsz = ctx->qsz;
-    const char *src = p;
-    int ch;
-
-    while (sz) {
-
-      /* --- Get the next character and convert it --- */
-
-      ch = *src++;
-      if (ch >= 128 || ch < 0)
-       ch = -1;
-      else
-       ch = decodemap[ch];
-      sz--;
-      if (ch == -1)
-       continue;
-
-      /* --- Bung it in the accumulator --- */
-
-      acc = (acc << 6) | ch;
-      qsz++;
-
-      /* --- Maybe write out a completed triplet --- */
-
-      if (qsz == 4) {
-       DPUTC(d, (acc >> 16) & 0xff);
-       DPUTC(d, (acc >>  8) & 0xff);
-       DPUTC(d, (acc >>  0) & 0xff);
-       acc = 0;
-       qsz = 0;
-      }
-    }
-
-    ctx->acc = acc;
-    ctx->qsz = qsz;
-  } else {
-
-    /* --- Notes about the tail-end bits --- *
-     *
-     * Ending Base64 decoding is messy.  The reference I'm using to define
-     * the encoding, RFC1521 section 5.2, is a little hazy on exactly what to
-     * do at the end.  It explains that I'm meant to ignore spurious `='
-     * characters, and points out that I'm not guaranteed to see anything
-     * interesting at the end.  I'll play safe here, and ignore all `='
-     * characters, relying on my client to work out when to stop feeding me
-     * data.  I'll use the queue size to work out how many tail-end bytes
-     * I ought to write.
-     */
-
-    unsigned long acc = ctx->acc;
-    unsigned qsz = ctx->qsz;
-
-    /* --- Now fiddle with everything else --- *
-     *
-     * There's a bodge here for invalid encodings which have only one hextet
-     * in the final group.  I'm not sure this is really worth having, but it
-     * might save some unexpected behaviour.  (Not that you won't still get
-     * unexpected behaviour if the stream is completely empty, of course.)
-     */
-
-    if (qsz) {
-      acc <<= 6 * (4 - qsz);
-      qsz *= 6;
-      if (qsz < 8)
-       qsz = 8;
-      while (qsz >= 8) {
-       DPUTC(d, (acc >> 16) & 0xff);
-       acc <<= 8;
-       qsz -= 8;
-      }
-    }
-
-    /* --- That seems to be good enough --- */
-
-    ctx->qsz = 0;
-    ctx->acc = 0;
-  }
-}
-
-/* --- @base64_init@ --- *
- *
- * Arguments:  @base64_ctx *ctx@ = pointer to context block to initialize
- *
- * Returns:    ---
- *
- * Use:                Initializes a base64 context properly.
- */
-
-void base64_init(base64_ctx *ctx)
-{
-  ctx->acc = 0;
-  ctx->qsz = 0;
-  ctx->lnlen = 0;
-  ctx->indent = "\n";
-  ctx->maxline = 72;
-}
-
-/*----- Test driver code --------------------------------------------------*/
-
-#ifdef TEST_RIG
-
-int main(int argc, char *argv[])
-{
-  unsigned char buf[BUFSIZ];
-  dstr d = DSTR_INIT;
-  base64_ctx ctx;
-  void (*proc)(base64_ctx *, const void *, size_t, dstr *);
-  size_t sz;
-
-  base64_init(&ctx);
-
-  if (argc > 1 && strcmp(argv[1], "-d") == 0)
-    proc = base64_decode;
-  else {
-    proc = base64_encode;
-    putchar('\t');
-    ctx.indent = "\n\t";
-    ctx.maxline = 64;
-  }
-
-  do {
-    sz = fread(buf, 1, sizeof(buf), stdin);
-    if (sz) {
-      proc(&ctx, buf, sz, &d);
-      dstr_write(&d, stdout);
-      dstr_destroy(&d);
-    }
-  } while (sz == sizeof(buf));
-
-  proc(&ctx, 0, 0, &d);
-  dstr_write(&d, stdout);
-
-  if (proc == base64_encode)
-    putchar('\n');
-
-  return (0);
-}
-
-#endif
-
-/*----- That's all, folks -------------------------------------------------*/
index 4a0a19d..e868781 100644 (file)
 
 /*----- Header files ------------------------------------------------------*/
 
+#ifndef MLIB_CODEC_H
+#  include "codec.h"
+#endif
+
 #ifndef MLIB_DSTR_H
 #  include "dstr.h"
 #endif
@@ -44,7 +48,7 @@ typedef struct base64_ctx {
   unsigned long acc;                   /* Accumulator for output data */
   unsigned qsz;                                /* Length of data queued */
   unsigned lnlen;                      /* Length of the current line */
-  const char *indent;                  /* Newline-and-indent string */
+  char *indent;                                /* Newline-and-indent string */
   unsigned maxline;                    /* Maximum permitted line length */
 } base64_ctx;
 
@@ -95,6 +99,10 @@ extern void base64_decode(base64_ctx */*ctx*/,
 
 extern void base64_init(base64_ctx */*ctx*/);
 
+/*----- Codec object interface --------------------------------------------*/
+
+extern const codec_class base64_class, file64_class, base64url_class;
+
 /*----- That's all, folks -------------------------------------------------*/
 
 #ifdef __cplusplus
diff --git a/codec/baseconv.c b/codec/baseconv.c
new file mode 100644 (file)
index 0000000..1d50735
--- /dev/null
@@ -0,0 +1,491 @@
+/* -*-c-*-
+ *
+ * Binary base-conversion encoding and decoding (base64, base32, etc.)
+ *
+ * (c) 1997 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "alloc.h"
+#include "codec.h"
+#include "dstr.h"
+#include "sub.h"
+
+#include "base64.h"
+#include "base32.h"
+#include "hex.h"
+
+/*----- Important tables --------------------------------------------------*/
+
+/* --- Magic constants --- */
+
+#define NV -1                          /* Not valid */
+#define PC -2                          /* Padding character */
+#define NL -3                          /* Newline character */
+
+/* --- Base64 --- */
+
+static const char
+  encodemap_base64[] =    { "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+                           "abcdefghijklmnopqrstuvwxyz"
+                           "0123456789+/" },
+  encodemap_file64[] =    { "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+                           "abcdefghijklmnopqrstuvwxyz"
+                           "0123456789+%" },
+  encodemap_base64url[] = { "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+                           "abcdefghijklmnopqrstuvwxyz"
+                           "0123456789-_" };
+
+static const signed char decodemap_base64[] = {
+  NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NL, NV, NV, NL, NV, NV,  /* 0x */
+  NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV,  /* 1x */
+  NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, 62, NV, NV, NV, 63,  /* 2x */
+  52, 53, 54, 55, 56, 57, 58, 59, 60, 61, NV, NV, NV, PC, NV, NV,  /* 3x */
+  NV,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,  /* 4x */
+  15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, NV, NV, NV, NV, NV,  /* 5x */
+  NV, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36 ,37, 38, 39, 40,  /* 6x */
+  41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, NV, NV, NV, NV, NV   /* 7x */
+}, decodemap_file64[] = {
+  NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NL, NV, NV, NL, NV, NV,  /* 0x */
+  NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV,  /* 1x */
+  NV, NV, NV, NV, NV, 63, NV, NV, NV, NV, NV, 62, NV, NV, NV, NV,  /* 2x */
+  52, 53, 54, 55, 56, 57, 58, 59, 60, 61, NV, NV, NV, PC, NV, NV,  /* 3x */
+  NV,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,  /* 4x */
+  15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, NV, NV, NV, NV, NV,  /* 5x */
+  NV, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36 ,37, 38, 39, 40,  /* 6x */
+  41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, NV, NV, NV, NV, NV   /* 7x */
+}, decodemap_base64url[] = {
+  NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NL, NV, NV, NL, NV, NV,  /* 0x */
+  NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV,  /* 1x */
+  NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, 62, NV, NV,  /* 2x */
+  52, 53, 54, 55, 56, 57, 58, 59, 60, 61, NV, NV, NV, PC, NV, NV,  /* 3x */
+  NV,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,  /* 4x */
+  15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, NV, NV, NV, NV, 63,  /* 5x */
+  NV, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36 ,37, 38, 39, 40,  /* 6x */
+  41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, NV, NV, NV, NV, NV   /* 7x */
+};
+
+/* --- Base32 --- */
+
+static const char
+  encodemap_base32[]   =    { "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567" },
+  encodemap_base32hex[] =   { "0123456789ABCDEFGHIJKLMNOPQRSTUV" };
+
+static const signed char decodemap_base32[] = {
+  NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NL, NV, NV, NL, NV, NV,  /* 0x */
+  NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV,  /* 1x */
+  NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV,  /* 2x */
+  NV, NV, 26, 27, 28, 29, 30, 31, NV, NV, NV, NV, NV, PC, NV, NV,  /* 3x */
+  NV,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,  /* 4x */
+  15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, NV, NV, NV, NV, NV,  /* 5x */
+  NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV,  /* 6x */
+  NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV,  /* 7x */
+}, decodemap_base32hex[] = {
+  NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NL, NV, NV, NL, NV, NV,  /* 0x */
+  NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV,  /* 1x */
+  NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV,  /* 2x */
+   0,  1,  2,  3,  4,  5,  6,  7,  8,  9, NV, NV, NV, PC, NV, NV,  /* 3x */
+  NV, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,  /* 4x */
+  25, 26, 27, 28, 29, 30, 31, NV, NV, NV, NV, NV, NV, NV, NV, NV,  /* 5x */
+  NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV,  /* 6x */
+  NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV,  /* 7x */
+};
+
+/* --- Hex --- */
+
+static const char
+  encodemap_hex[] =   { "0123456789ABCDEF" };
+
+static const signed char decodemap_hex[] = {
+  NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NL, NV, NV, NL, NV, NV,  /* 0x */
+  NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV,  /* 1x */
+  NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV,  /* 2x */
+   0,  1,  2,  3,  4,  5,  6,  7,  8,  9, NV, NV, NV, NV, NV, NV,  /* 3x */
+  NV, 10, 11, 12, 13, 14, 15, NV, NV, NV, NV, NV, NV, NV, NV, NV,  /* 4x */
+  NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV,  /* 5x */
+  NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV,  /* 6x */
+  NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV, NV,  /* 7x */
+};
+
+/*----- Base conversion macros --------------------------------------------*/
+
+/* --- @BASECONV@ --- *
+ *
+ * Arguments:  @x@ = an input digit of width @IWD@ bits
+ *             @iwd@ = input digit width in bits
+ *             @owd@ = output digit width in bits
+ *             @put@ = function or macro to output a digit
+ *
+ * Use:                Inserts the bits of @x@ into an accumulator.  As digits @y@
+ *             of with @owd@ become ready, @put(y)@ is invoked to emit them.
+ */
+
+#define BASECONV(x, iwd, owd, put) do {                                        \
+  a = (a << iwd) | x;                                                  \
+  nb += iwd;                                                           \
+  while (nb >= owd) {                                                  \
+    nb -= owd;                                                         \
+    put((a >> nb) & ((1 << owd) - 1));                                 \
+  }                                                                    \
+} while (0)
+
+/* --- @BASECONV_FLUSH@ --- *
+ *
+ * Arguments:  @iwd@ = input digit width in bits
+ *             @owd@ = output digit width in bits
+ *             @put@ = function or macro to output a digit
+ *
+ * Use:                Flushes remaining digits from the base-conversion shift
+ *             register.  The bits in the shift register are padded on the
+ *             right with zeros.  Digits of width @owd@ are emitted by
+ *             invoking @put@.
+ */
+
+#define BASECONV_FLUSH(iwd, owd, put) do {                             \
+  if (nb) {                                                            \
+    while (nb < owd) { a <<= iwd; nb += iwd; }                         \
+    nb -= owd;                                                         \
+    put((a >> nb) & ((1 << owd) - 1));                                 \
+  }                                                                    \
+} while (0)
+
+/* --- @BASECONV_PAD@ --- *
+ *
+ * Arguments:  @iwd@ = input digit width in bits
+ *             @owd@ = output digit width in bits
+ *             @pad@ = function or macro to output padding
+ *
+ * Use:                Invokes @pad@ sufficiently often to realign the shift
+ *             register.
+ */
+
+#define BASECONV_PAD(iwd, owd, pad) do {                               \
+  for (;;) {                                                           \
+    while (nb >= owd) { pad; nb -= owd; }                              \
+    if (!nb) break;                                                    \
+    nb += iwd;                                                         \
+  }                                                                    \
+} while (0)
+
+#define NULL_PAD(iwd, owd, pad) do ; while (0)
+
+/*----- Lists of things to make -------------------------------------------*/
+
+#define CODECS(_)                                                      \
+  /* NAME,      CTXN,    ACC */                                                \
+  _(base64,    base64, acc)                                            \
+  _(file64,    base64, acc)                                            \
+  _(base64url, base64, acc)                                            \
+  _(base32,    base32, accl)                                           \
+  _(base32hex, base32, accl)                                           \
+  _(hex,       hex,    acc)
+
+#define CTXS(_)                                                                \
+  /* CTXN,     WD,     ACC */                                          \
+  _(base64,    6,      acc)                                            \
+  _(base32,    5,      accl)                                           \
+  _(hex,       4,      acc)
+
+#define base64_PADDING BASECONV_PAD
+#define base64_FLAGMASK ~(CDCF_LOWERC | CDCF_IGNCASE)
+#define base64_FLAGXOR 0
+#define base64_OLDFLAGS CDCF_IGNJUNK
+
+#define base32_PADDING BASECONV_PAD
+#define base32_FLAGMASK ~0
+#define base32_FLAGXOR 0
+#define base32_OLDFLAGS CDCF_IGNJUNK
+
+#define hex_PADDING NULL_PAD
+#define hex_FLAGMASK ~0
+#define hex_FLAGXOR 0
+#define hex_OLDFLAGS (CDCF_IGNJUNK | CDCF_LOWERC)
+
+/*----- Data structures ---------------------------------------------------*/
+
+#define OBJ(ctxn, wd, acc)                                             \
+                                                                       \
+typedef struct ctxn##_codec {                                          \
+  codec c;                                                             \
+  ctxn##_ctx ctx;                                                      \
+  const char *encodemap;                                               \
+  const signed char *decodemap;                                                \
+} ctxn##_codec;
+
+CTXS(OBJ)
+
+/*----- State packing -----------------------------------------------------*
+ *
+ * These macros convert between the state required by the new encoding and
+ * decoding core and the old externally-visible context structures.  It's
+ * unpleasant, I know; maybe we can drop the old interface later.
+ */
+
+enum {
+  ST_MAIN,                             /* Main decoding state */
+  ST_PAD,                              /* Decoding trailing padding */
+  ST_END                               /* Finished decoding */
+};
+
+#define STATE_UNPACK(acc)                                              \
+  unsigned long a = (ctx->acc >> 0) & 0xffff;                          \
+  unsigned nb = (ctx->acc >> 16) & 0xff;                               \
+  unsigned st = (ctx->acc >> 24) & 0xff;                               \
+  unsigned f = ctx->qsz;
+
+#define STATE_PACK(acc) do {                                           \
+  ctx->acc = (((a & 0xffff) << 0) |                                    \
+             (((unsigned long)nb & 0xff) << 16) |                      \
+             (((unsigned long)st & 0xff) << 24));                      \
+} while (0)
+
+/*----- Main encoder and decoder ------------------------------------------*/
+
+#define WRAP(stuff) do {                                               \
+  if (maxln && lnlen >= maxln) {                                       \
+    dstr_puts(d, ctx->indent);                                         \
+    lnlen = 0;                                                         \
+  }                                                                    \
+  stuff                                                                        \
+  lnlen++;                                                             \
+} while (0)
+
+#define PUTWRAP(x) WRAP({                                              \
+  char ch = encodemap[x];                                              \
+  if (f & CDCF_LOWERC) ch = tolower((unsigned char)ch);                        \
+  DPUTC(d, ch);                                                                \
+})
+
+#define PADWRAP WRAP({ DPUTC(d, '='); })
+
+#define PUTRAW(x) DPUTC(d, x)
+
+#define ENCODER(ctxn, wd, acc)                                         \
+                                                                       \
+/* --- @CTXN_doencode@ --- *                                           \
+ *                                                                     \
+ * Arguments:  @CTXN_ctx *ctx@ = pointer to a context block            \
+ *             @const char *encodemap@ = pointer to encoding map       \
+ *             @const unsigned char *p@ = pointer to a source buffer   \
+ *             @size_t sz@ = size of the source buffer                 \
+ *             @dstr *d@ = pointer to destination string               \
+ *                                                                     \
+ * Returns:    Zero on success, or @CDCERR_@ error code.               \
+ *                                                                     \
+ * Use:                Main encoder function.                                  \
+ */                                                                    \
+                                                                       \
+static int ctxn##_doencode(ctxn##_ctx *ctx, const char *encodemap,     \
+                          const unsigned char *p, size_t sz, dstr *d)  \
+{                                                                      \
+  STATE_UNPACK(acc);                                                   \
+  const unsigned char *l = p + sz;                                     \
+  unsigned lnlen = ctx->lnlen, maxln = ctx->maxline;                   \
+                                                                       \
+  if (p) {                                                             \
+    while (p < l) BASECONV(*p++, 8, wd, PUTWRAP);                      \
+  } else {                                                             \
+    BASECONV_FLUSH(8, wd, PUTWRAP);                                    \
+    if (!(f & CDCF_NOEQPAD)) ctxn##_PADDING(8, wd, PADWRAP);           \
+  }                                                                    \
+                                                                       \
+  STATE_PACK(acc);                                                     \
+  ctx->lnlen = lnlen;                                                  \
+  return (0);                                                          \
+}                                                                      \
+                                                                       \
+/* --- @CTXN_dodecode@ --- *                                           \
+ *                                                                     \
+ * Arguments:  @CTXN_ctx *ctx@ = pointer to a context block            \
+ *             @const signed char *decodemap@ = pointer to decode map  \
+ *             @const char *p@ = pointer to a source buffer            \
+ *             @size_t sz@ = size of the source buffer                 \
+ *             @dstr *d@ = pointer to destination string               \
+ *                                                                     \
+ * Returns:    Zero on success, or @CDCERR_@ error code.               \
+ *                                                                     \
+ * Use:                Main decoder function.                                  \
+ */                                                                    \
+                                                                       \
+static int ctxn##_dodecode(ctxn##_ctx *ctx,                            \
+                          const signed char *decodemap,                \
+                          const unsigned char *p, size_t sz, dstr *d)  \
+{                                                                      \
+  STATE_UNPACK(acc);                                                   \
+  const unsigned char *l = p + sz;                                     \
+  int ch;                                                              \
+  int x;                                                               \
+                                                                       \
+  if (p) {                                                             \
+    while (p < l) {                                                    \
+      ch = *p++;                                                       \
+      switch (f & (CDCF_LOWERC | CDCF_IGNCASE)) {                      \
+       case 0:                                                         \
+         break;                                                        \
+       case CDCF_LOWERC:                                               \
+         if (isupper(ch)) goto badch;                                  \
+       default:                                                        \
+         ch = toupper(ch);                                             \
+      }                                                                        \
+      x = decodemap[ch];                                               \
+      switch (x) {                                                     \
+       case NV:                                                        \
+       badch:                                                          \
+         if (!(f & CDCF_IGNINVCH)) return (CDCERR_INVCH);              \
+         break;                                                        \
+       case PC:                                                        \
+         if (f & CDCF_IGNEQMID) break;                                 \
+         if (f & CDCF_NOEQPAD) goto badch;                             \
+         if (st == ST_MAIN &&                                          \
+             !(f & CDCF_IGNZPAD) && (a & ((1 << nb) - 1)))             \
+           return (CDCERR_INVZPAD);                                    \
+         st = ST_PAD;                                                  \
+         if (!(f & CDCF_IGNEQPAD)) {                                   \
+           if (!nb) return (CDCERR_INVEQPAD);                          \
+           nb = (nb + wd)%8;                                           \
+           st = ST_PAD;                                                \
+         }                                                             \
+         break;                                                        \
+       case NL:                                                        \
+         if (f & CDCF_IGNNEWL) break;                                  \
+         return (CDCERR_INVCH);                                        \
+       default:                                                        \
+         if (st != ST_MAIN)                                            \
+           return (CDCERR_INVEQPAD);                                   \
+         BASECONV(x, wd, 8, PUTRAW);                                   \
+         break;                                                        \
+      }                                                                        \
+    }                                                                  \
+  } else {                                                             \
+    if (st == ST_MAIN &&                                               \
+       !(f & CDCF_IGNZPAD) && (a & ((1 << nb) - 1)))                   \
+      return (CDCERR_INVZPAD);                                         \
+    if (!(f & (CDCF_IGNEQPAD | CDCF_IGNEQMID | CDCF_NOEQPAD)) && nb)   \
+      return (CDCERR_INVEQPAD);                                                \
+  }                                                                    \
+                                                                       \
+  STATE_PACK(acc);                                                     \
+  return (0);                                                          \
+}
+
+CTXS(ENCODER)
+
+/*----- Codec implementation ----------------------------------------------*/
+
+#define OPS(ctxn, wd, acc)                                             \
+                                                                       \
+static int ctxn##_enc(codec *c, const void *p, size_t sz, dstr *d)     \
+{                                                                      \
+  ctxn##_codec *bc = (ctxn##_codec *)c;                                        \
+  return (ctxn##_doencode(&bc->ctx, bc->encodemap, p, sz, d));         \
+}                                                                      \
+                                                                       \
+static int ctxn##_dec(codec *c, const void *p, size_t sz, dstr *d)     \
+{                                                                      \
+  ctxn##_codec *bc = (ctxn##_codec *)c;                                        \
+  return (ctxn##_dodecode(&bc->ctx, bc->decodemap, p, sz, d));         \
+}                                                                      \
+                                                                       \
+static void ctxn##_destroy(codec *c)                                   \
+{                                                                      \
+  ctxn##_codec *bc = (ctxn##_codec *)c;                                        \
+  if (bc->ctx.indent) xfree((/*unconst*/ char *)bc->ctx.indent);       \
+  DESTROY(bc);                                                         \
+}                                                                      \
+                                                                       \
+static codec *ctxn##_docreate(unsigned flags,                          \
+                             const char *indent, unsigned maxline,     \
+                             const codec_ops *ops,                     \
+                             const char *encodemap,                    \
+                             const signed char *decodemap)             \
+{                                                                      \
+  ctxn##_codec *bc = CREATE(ctxn##_codec);                             \
+  bc->c.ops = ops;                                                     \
+  bc->ctx.acc = 0;                                                     \
+  bc->ctx.qsz = (flags & ctxn##_FLAGMASK) ^ ctxn##_FLAGXOR;            \
+  bc->ctx.lnlen = 0;                                                   \
+  bc->ctx.indent = indent ? xstrdup(indent) : 0;                       \
+  bc->ctx.maxline = maxline;                                           \
+  bc->encodemap = encodemap;                                           \
+  bc->decodemap = decodemap;                                           \
+  return (&bc->c);                                                     \
+}
+
+CTXS(OPS)
+
+#define CLASS(name, ctxn, acc)                                         \
+                                                                       \
+static const codec_ops                                                 \
+  name##_encode_ops = { &name##_class, ctxn##_enc, ctxn##_destroy },   \
+  name##_decode_ops = { &name##_class, ctxn##_dec, ctxn##_destroy };   \
+                                                                       \
+static codec *name##_encoder(unsigned flags,                           \
+                            const char *indent, unsigned maxline)      \
+{                                                                      \
+  return ctxn##_docreate(flags, indent, maxline,                       \
+                        &name##_encode_ops,                            \
+                        encodemap_##name,                              \
+                        decodemap_##name);                             \
+}                                                                      \
+                                                                       \
+static codec *name##_decoder(unsigned flags)                           \
+{                                                                      \
+  return ctxn##_docreate(flags, 0, 0,                                  \
+                        &name##_decode_ops,                            \
+                        encodemap_##name,                              \
+                        decodemap_##name);                             \
+}                                                                      \
+                                                                       \
+const codec_class name##_class =                                       \
+  { #name, name##_encoder, name##_decoder };
+
+CODECS(CLASS)
+
+/*----- Compatibility veneers ---------------------------------------------*/
+
+#define COMPAT(ctxn, wd, acc)                                          \
+                                                                       \
+void ctxn##_encode(ctxn##_ctx *ctx, const void *p, size_t sz, dstr *d) \
+  { ctxn##_doencode(ctx, encodemap_##ctxn, p, sz, d); }                        \
+                                                                       \
+void ctxn##_decode(ctxn##_ctx *ctx, const void *p, size_t sz, dstr *d) \
+  { ctxn##_dodecode(ctx, decodemap_##ctxn, p, sz, d); }                        \
+                                                                       \
+void ctxn##_init(ctxn##_ctx *ctx)                                      \
+{                                                                      \
+  ctx->acc = 0;                                                                \
+  ctx->qsz = (ctxn##_OLDFLAGS & ctxn##_FLAGMASK) ^ ctxn##_FLAGXOR;     \
+  ctx->lnlen = 0;                                                      \
+  ctx->indent = "\n";                                                  \
+  ctx->maxline = 72;                                                   \
+}
+
+CTXS(COMPAT)
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/codec/bincode.1 b/codec/bincode.1
new file mode 100644 (file)
index 0000000..7914f75
--- /dev/null
@@ -0,0 +1,107 @@
+.\" nroff
+.TH bincode 1 "9 January 2009" "Straylight/Edgeware" "mLib utilities library"
+.SH NAME
+bincode \- binary-to-text encoding and decoding
+.SH SYNOPSIS
+.B bincode
+.RB [ \-de]
+.RB [ \-f
+.IR flags ]
+.RB [ \-i
+.IR indent ]
+.RB [ \-m
+.IR maxline ]
+.RB [ \-o
+.IR output ]
+.RI [ file ...]
+.SH DESCRIPTION
+The
+.B bincode
+program encodes binary data as plain text (suitable, for example, for
+use in email), and recovers binary data from its encoding.
+.PP
+The options are as follows.
+.TP
+.B "\-h, \-\-help"
+Print a help message to standard output and exit successfully.
+.TP
+.B "\-v, \-\-version"
+Print the program's version number to standard output and exit
+successfully.
+.TP
+.B "\-u, \-\-usage"
+Print a one-line usage summary to standard output and exit successfully.
+.TP
+.BI "\-d, \-\-decode"
+Read encoded data and write the result of decoding it.
+.TP
+.BI "\-e, \-\-encode"
+Read raw binary data and write the result of encoding.
+.TP
+.BI "\-f, \-\-flags=" flags
+Set encoding/decoding flags.  The
+.I flags
+are a list of comma-separated flag names, each preceded by an optional
+.RB ` + '
+to set the flag (the default) or
+.RB ` \- '
+to clear it.  The flag names are as listed in
+.BR codec (3),
+but in lower case, and without the
+.RB ` CDCF_ '
+prefix; e.g.,
+.B CDCF_IGNNEWL
+may be specified as
+.RB ` ignnewl '.
+This option may be repeated: the options are scanned left-to-right.  The
+only flag set by default is
+.RB ` ignnewl '.
+.TP
+.BI "\-i, \-\-indent=" indent
+Insert the
+.I indent
+string before each line.  The string may contain simple
+backslash-escapes:
+.RB ` \ea ',
+.RB ` \eb ',
+.RB ` \ef ',
+.RB ` \en ',
+.RB ` \er ',
+.RB ` \et ', and
+.RB ` \ev '
+respectively stand for alert, backspace, form-feed, newline, carriage
+return, and horizontal and vertical tab.  A backslash preceding any
+other character yields that character; hence, to include a backslash,
+write a double backslash.  The default is the empty string: i.e., just
+end each line with a newline character.
+.TP
+.BI "\-m, \-\-maxline=" maxline
+Set the maximum output line length to
+.I maxline
+when encoding.  The limit is ignored when decoding.  If
+.I maxline
+is zero, then no line splitting is performed.
+.TP
+.BI "\-o, \-\-output=" output
+Write the (encoded or decoded) output to
+.IR output .
+The default is to write to standard output.  On platforms where it makes
+a difference, the output file is opened in text mode when encoding, and
+in binary mode when decoding.
+.PP
+The input to be encoded or decoded is the concatenation of the specified
+.IR file s.
+If no files are listed, then standard input is read.  A
+.I file
+which is a single
+.RB ` \- '
+also means to read standard input.  On systems where it makes a
+difference, named files are read in binary mode when encoding and in
+text mode when decoding.
+.PP
+If an error is encountered, the output may be partially written.
+.SH "SEE ALSO"
+.BR codec (3).
+.SH "AUTHOR"
+Mark Wooding, <mdw@distorted.org.uk>
+
diff --git a/codec/bincode.c b/codec/bincode.c
new file mode 100644 (file)
index 0000000..0a7d5c5
--- /dev/null
@@ -0,0 +1,293 @@
+/* -*-c-*-
+ *
+ * Common test driver for encoding and decoding
+ *
+ * (c) 2009 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include "config.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "codec.h"
+#include "dstr.h"
+#include "mdwopt.h"
+#include "quis.h"
+#include "report.h"
+
+#include "base64.h"
+#include "base32.h"
+#include "hex.h"
+
+/*----- Static variables --------------------------------------------------*/
+
+static const codec_class *cctab[] = {
+  &base64_class,
+  &base64url_class,
+  &file64_class,
+  &base32_class,
+  &base32hex_class,
+  &hex_class,
+  &null_codec_class,
+  0,
+};
+
+static const struct { const char *name; unsigned f; } flagtab[] = {
+  { "lowerc",          CDCF_LOWERC },
+  { "igncase",         CDCF_IGNCASE },
+  { "noeqpad",         CDCF_NOEQPAD },
+  { "igneqpad",                CDCF_IGNEQPAD },
+  { "igneqmid",                CDCF_IGNEQMID },
+  { "ignzpad",         CDCF_IGNZPAD },
+  { "ignnewl",         CDCF_IGNNEWL },
+  { "igninvch",                CDCF_IGNINVCH },
+  { "ignjunk",         CDCF_IGNJUNK },
+  { 0,                 0, }
+};
+
+/*----- Main code ---------------------------------------------------------*/
+
+static void usage(FILE *fp)
+{
+  pquis(fp,
+       "Usage: $ [-de] [-f FLAGS] [-i INDENT] [-m MAXLINE] [-o OUTPUT]\n"
+       "\tCODEC [FILE ...]\n");
+}
+
+static void version(FILE *fp)
+  { pquis(fp, "$, mLib version " VERSION "\n"); }
+
+static void help(FILE *fp)
+{
+  int i;
+
+  version(fp);
+  fputc('\n', fp);
+  usage(fp);
+  fputs("\n\
+Encodes and decodes binary files.  Options provided:\n\
+\n\
+-h, --help             Show this help text.\n\
+-v, --version          Show the program's version number.\n\
+-u, --usage            Show a terse usage message.\n\
+\n\
+-d, --decode           Decode binary FILEs.\n\
+-e, --encode           Encode binary FILEs (default).\n\
+-f, --flags=FLAGS      Set encoding/decoding FLAGS.\n\
+-i, --indent=INDENT    Indent each line with INDENT.\n\
+-m, --maxline=MAXLINE  Limit line length to (about) MAXLINE.\n\
+-o, --output=OUTPUT    Write encoded/decoded data to OUTPUT.\n\
+\n", fp);
+#define ENUM(label, tab, end, getname) do {                            \
+  fputs(label ":", fp);                                                        \
+  for (i = 0; tab[i]end; i++) fprintf(fp, " %s", tab[i]getname);       \
+  fputc('\n', fp);                                                     \
+} while (0)
+  ENUM("Codecs", cctab, != 0, ->name);
+  ENUM("Flags", flagtab, .name, .name);
+#undef ENUM
+}
+
+static void code(codec *c, const char *ifile, FILE *ifp, FILE *ofp)
+{
+  dstr d = DSTR_INIT;
+  char buf[4096];
+  size_t n;
+  int err;
+
+  do {
+    n = fread(buf, 1, sizeof(buf), ifp);
+    DRESET(&d);
+    if ((err = c->ops->code(c, buf, n, &d)) != 0)
+      die(EXIT_FAILURE, "decoding error: %s", codec_strerror(err));
+  } while (fwrite(d.buf, 1, d.len, ofp) == d.len && n == sizeof(buf));
+  if (ferror(ifp))
+    die(EXIT_FAILURE, "error reading `%s': %s", ifile, strerror(errno));
+}
+
+int main(int argc, char *argv[])
+{
+  enum { encode, decode };
+  int mode = encode;
+  const codec_class **cc;
+  codec *c;
+  const char *indent = "";
+  const char *imode, *omode, *ofile = 0;
+  unsigned maxline = 64;
+  unsigned f = CDCF_IGNNEWL;
+  const char *p;
+  char *q;
+  FILE *ifp, *ofp = stdout;
+  dstr d = DSTR_INIT;
+  int i;
+  int sense;
+  size_t n;
+  int err;
+
+#define f_bogus 32768u
+
+  ego(argv[0]);
+
+  for (;;) {
+    static const struct option opts[] = {
+      { "help",                0,              0,      'h' },
+      { "version",     0,              0,      'v' },
+      { "usage",       0,              0,      'u' },
+      { "encode",      0,              0,      'e' },
+      { "decode",      0,              0,      'd' },
+      { "indent",      OPTF_ARGREQ,    0,      'i' },
+      { "maxline",     OPTF_ARGREQ,    0,      'm' },
+      { "output",      OPTF_ARGREQ,    0,      'o' },
+      { "flags",       OPTF_ARGREQ,    0,      'f' },
+      { 0,             0,              0,      0 }
+    };
+
+    if ((i = mdwopt(argc, argv, "hvu" "edf:i:m:o:", opts, 0, 0, 0)) < 0)
+      break;
+    switch (i) {
+      case 'h': help(stdout); exit(0);
+      case 'v': version(stdout); exit(0);
+      case 'u': usage(stdout); exit(0);
+
+      case 'e': mode = encode; break;
+      case 'd': mode = decode; break;
+      case 'i': indent = optarg; break;
+      case 'o': ofile = optarg; break;
+
+      case 'm':
+       errno = 0;
+       maxline = strtoul(optarg, &q, 0);
+       if (*q || errno) die(EXIT_FAILURE, "bad integer `%s'", optarg);
+       break;
+
+      case 'f':
+       p = optarg;
+       while (*p) {
+         if (*p == '-') { sense = 0; p++; }
+         else if (*p == '+') { sense = 1; p++; }
+         else sense = 1;
+         n = strcspn(p, ",");
+         for (i = 0; flagtab[i].name; i++) {
+           if (strlen(flagtab[i].name) == n &&
+               strncmp(flagtab[i].name, p, n) == 0)
+             goto found;
+         }
+         die(EXIT_FAILURE, "unknown flag `%.*s'", (int)n, p);
+       found:
+         if (sense) f |= flagtab[i].f;
+         else f &= ~flagtab[i].f;
+         p += n;
+         if (*p == ',') p++;
+       }
+       break;
+
+      default: f |= f_bogus; break;
+    }
+  }
+  argv += optind; argc -= optind;
+  if ((f & f_bogus) || !argc) { usage(stderr); exit(EXIT_FAILURE); }
+
+  for (cc = cctab;; cc++) {
+    if (!*cc) die(EXIT_FAILURE, "unknown codec `%s'", *argv);
+    else if (strcmp(*argv, (*cc)->name) == 0) break;
+  }
+  argv++; argc--;
+
+  switch (mode) {
+    case encode:
+      DPUTC(&d, '\n');
+      for (p = indent;; p++) {
+       switch (*p) {
+         case '\\':
+           p++;
+           switch (*p) {
+             case 'a': DPUTC(&d, '\n'); break;
+             case 'b': DPUTC(&d, '\b'); break;
+             case 'f': DPUTC(&d, '\f'); break;
+             case 'n': DPUTC(&d, '\n'); break;
+             case 'r': DPUTC(&d, '\r'); break;
+             case 't': DPUTC(&d, '\t'); break;
+             case 'v': DPUTC(&d, '\v'); break;
+             case 0: goto done_indent; break;
+             default: DPUTC(&d, *p); break;
+           }
+           break;
+         case 0:
+           goto done_indent;
+         default:
+           DPUTC(&d, *p);
+           break;
+       }
+      }
+    done_indent:
+      DPUTZ(&d);
+      c = (*cc)->encoder(f, d.buf, maxline);
+      imode = "rb"; omode = "w";
+      break;
+    case decode:
+      c = (*cc)->decoder(f);
+      imode = "r"; omode = "wb";
+      break;
+    default:
+      abort();
+  }
+
+  if (ofile && (ofp = fopen(ofile, omode)) == 0) {
+    die(EXIT_FAILURE, "couldn't open `%s' for writing: %s",
+       ofile, strerror(errno));
+  }
+
+  if (mode == encode) fwrite(d.buf + 1, 1, d.len - 1, ofp);
+  if (!argc)
+    code(c, "<stdin>", stdin, ofp);
+  else for (i = 0; i < argc; i++) {
+    if (strcmp(argv[i], "-") == 0)
+      code(c, "<stdin>", stdin, ofp);
+    else if ((ifp = fopen(argv[i], imode)) == 0) {
+      die(EXIT_FAILURE, "couldn't open `%s' for reading: %s",
+         argv[i], strerror(errno));
+    } else {
+      code(c, argv[i], ifp, ofp);
+      fclose(ifp);
+    }
+  }
+  DRESET(&d);
+  if ((err = c->ops->code(c, 0, 0, &d)) != 0)
+    die(EXIT_FAILURE, "decoding error: %s", codec_strerror(err));
+  if (mode == encode) DPUTC(&d, '\n');
+  fwrite(d.buf, 1, d.len, ofp);
+  c->ops->destroy(c); DDESTROY(&d);
+
+  if (ferror(ofp) || fflush(ofp) || fclose(ofp)) {
+    die(EXIT_FAILURE, "error writing to `%s': %s",
+       ofile ? ofile : "<stdout>", strerror(errno));
+  }
+  return (0);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/codec/codec.3 b/codec/codec.3
new file mode 100644 (file)
index 0000000..96174f3
--- /dev/null
@@ -0,0 +1,256 @@
+.\" -*-nroff-*-
+.TH codec 3 "9 January 2009" "Straylight/Edgeware" "mLib utilities library"
+.SH NAME
+codec \- binary encoding and decoding
+.\" @codec_class
+.\" @codec_strerror
+.\" @null_codec_class
+.\" @base64_class
+.\" @file64_class
+.\" @base64url_class
+.\" @base32_class
+.\" @base32hex_class
+.\" @hex_class
+.SH SYNOPSIS
+.nf
+.B "#include <mLib/codec.h>"
+.B "#include <mLib/base64.h>"
+.B "#include <mLib/base32.h>"
+.B "#include <mLib/hex.h>"
+
+.B "codec_class null_codec_class;"
+.B "codec_class base64_class, file64_class, base64url_class;"
+.B "codec_class base32_class, base32hex_class;"
+.B "codec_class hex_class;"
+
+.BI "const char *codec_strerror(int " err ");"
+.fi
+.SH DESCRIPTION
+The
+.B codec
+system provides an object-based interface to functions which encode
+binary data as plain text and decode the result to recover the original
+binary data.  The interface makes it easy to support multiple encodings
+and select an appropriate one at runtime.
+.SS "The codec_class structure"
+The
+.B codec_class
+structure represents a particular encoding format.  The structure has
+the following members.
+.TP
+.B "const char *name"
+The name of the class, as a null-terminated string.  The name should not
+contain whitespace characters.
+.TP
+.BI "codec *(*encoder)(unsigned " flags ", const char *" indent ", unsigned " maxline ")"
+Pointer to a function which constructs a new encoder object, of type
+.BR codec .
+The
+.I flags
+configure the behaviour of the object; the
+.I indent
+string is written to separate lines of output; the integer
+.I maxline
+is the maximum length of line to be produced, or zero to forbid line
+breaking.
+.TP
+.BI "codec *(*decoder)(unsigned " flags ")"
+Pointer to a function which constructs a new decoder object, also of
+type
+.BR codec .
+The
+.I flags
+configure the behaviour of the object.
+.PP
+The
+.I flags
+to the
+.B encoder
+and
+.B decoder
+functions have the following meanings.
+.TP
+.B CDCF_LOWERC
+For codecs which produce output using a single alphabetic case (e.g.,
+.BR base32 ,
+.BR hex ),
+emit and accept only lower case; the default to emit and accept only
+upper case, for compatibility with RFC4648.  If the codec usually
+produces mixed-case output, then this flag is ignored.
+.TP
+.B CDCF_IGNCASE
+For codecs which produce output using a single alphabetic case, ignore
+the case of the input when decoding.  If the codec usually produces
+mixed-case output, then this flag is ignored.
+.TP
+.B CDCF_NOEQPAD
+For codecs which usually pad their output (e.g.,
+.BR base64 ,
+.BR base32 ),
+do not emit or accept padding characters.  If the codec does not usually
+produce padding, or the padding is not redundant, then this flag is
+ignored.
+.TP
+.B CDCF_IGNEQPAD
+For codecs which usually pad their output, do not treat incorrect (e.g.,
+missing or excessive) padding as an error when decoding.  If the codec
+does not usually produce padding, or the padding is required for
+unambiguous decoding, then this flag is ignored.
+.TP
+.B CDCF_IGNEQMID
+For codecs which usually pad their output, ignore padding characters
+wherever they may appear when decoding.  Usually padding characters
+indicate the end of the input, and further input characters are
+considered erroneous.  If the codec does not usually produce padding, or
+it is impossible to resume decoding correctly having seen padding
+characters, then this flag is ignored.
+.TP
+.B CDCF_IGNZPAD
+For codecs which need to pad their input, ignore unusual padding bits
+when decoding.  (This is not at all the same thing as the padding
+characters controlled by the flags above: they deal with padding the
+length of the encoding
+.I output
+up to a suitable multiple of characters; this option deals with padding
+of the
+.I input
+prior to encoding.)  If the codec does not add padding bits, or specific
+values are required for unambiguous decoding, then this flag is ignored.
+.TP
+.B CDCF_IGNNEWL
+Ignore newline (and carriage-return) characters when decoding: the
+default for RFC4648 codecs is to reject newline characters.  If these
+characters are significant in the encoding, then this flag is ignored.
+.TP
+.B CDCF_IGNINVCH
+Ignore any other invalid characters appearing in the input when
+decoding.
+.TP
+.B CDCF_IGNJUNK
+Ignore all `junk' in the input.  This should suppress almost all
+decoding errors.
+.PP
+If you do not set any of the
+.BR CDCF_IGN ...
+flags, a decoder should only accept the exact encoding that the
+corresponding encoder would produce (with
+.I maxline
+= 0 to inhibit line-breaking).
+.SS "The codec and codec_ops structures"
+The
+.B codec
+structure represents the state of an encoder or decoder, as returned by
+the
+.B encoder
+and
+.B decoder
+functions described above, contains a single member.
+.TP
+.B "const codec_ops *ops"
+Pointer to a
+.B codec_ops
+structure which contains operations and metadata for use with the
+encoder or decoder.
+.PP
+The
+.B codec_ops
+structure contains the following members.
+.TP
+.B "const codec_class *c"
+Pointer back to the
+.B codec_class
+which was used to construct the
+.B codec
+object.
+.TP
+.BI "int (*code)(codec *" c ", const void *" p ", size_t " sz ", dstr *" d ")"
+Encode or decode, using the codec
+.I c ,
+the data in the buffer at address
+.I p
+and continuing for
+.I sz
+bytes, appending the output to the dynamic string
+.I d
+(see
+.BR dstr (3)).
+If the operation was successful, the function returns zero; otherwise it
+returns a nonzero error code, as described below.
+.TP
+.BI "void (*destroy)(codec *" c ")"
+Destroy the codec object
+.IR c ,
+freeing any resources it may hold.
+.PP
+A codec may buffer its input (e.g., if needs to see more in order to
+decide what output to produce next); it may also need to take special
+action at the end of the input (e.g., flushing buffers, and applying
+padding).  To signal the codec that there is no more input, call the
+.B code
+function with a null
+.I p
+pointer.  It will then write any final output to
+.IR d .
+.PP
+The following error conditions may be reported.
+.TP
+.B CDCERR_INVCH
+An invalid character was encountered while decoding.  This includes
+encoutering padding characters if padding is disabled using the
+.B CDCF_NOEQPAD
+flag.
+.TP
+.B CDCERR_INVEQPAD
+Invalid padding characters (e.g., wrong characters, or too few, too
+many, or none at all) were found during decoding.  This may also
+indicate that the input is truncated, even if the codec does not usually
+perform output padding.
+.TP
+.B CDCERR_INVZPAD
+Invalid padding bits were found during decoding.
+.PP
+The
+.B codec_strerror
+function converts these error codes to brief, (moderately)
+human-readable strings.
+.SS "Provided codecs"
+The library provides a number of standard codecs.
+.TP
+.B base64
+Implements Base64 encoding, as defined by RFC4648.  Output is
+mixed-case, so the
+.B CDCF_LOWERC
+and
+.B CDCF_IGNCASE
+flags are ignored.
+.TP
+.B safe64
+Implements a variant of the Base64 encoding which uses
+.RB ` % '
+in place of
+.RB ` / ',
+so that its output is suitable for use as a Unix filename.
+.TP
+.B base64url
+Implements the filename- and URL-safe variant of Base64 encoding, as
+defined by RFC4648.
+.TP
+.B base32
+Implements Base32 encoding, as defined by RFC4648.  Output is in upper
+case by default.
+.TP
+.B base32hex
+Implements the extended-hex variant of Base32, as defined by RFC4648.
+This encoding has the property that the encoding preserves the ordering
+of messages if padding is suppressed.
+.TP
+.B hex
+Implements hex encoding, defined by RFC4648 under the name Base16.  For
+compatibility with that specification, output is in upper case by
+default.
+.SH "SEE ALSO"
+.BR bincode (1),
+.BR dstr (3),
+.BR mLib (3).
+.SH AUTHOR
+Mark Wooding, <mdw@distorted.org.uk>
diff --git a/codec/codec.c b/codec/codec.c
new file mode 100644 (file)
index 0000000..8c284be
--- /dev/null
@@ -0,0 +1,58 @@
+/* -*-c-*-
+ *
+ * Binary codec utilities
+ *
+ * (c) 2009 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include "codec.h"
+#include "macros.h"
+
+/*----- Static variables --------------------------------------------------*/
+
+static const char *errtab[] = {
+#define ERR(name, text) text,
+  CODEC_ERRORS(ERR)
+};
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @codec_strerror@ --- *
+ *
+ * Arguments:  @int err@ = error code from codec
+ *
+ * Returns:    Pointer to error text.
+ *
+ * Use:                Converts error codes to human-readable strings.
+ */
+
+const char *codec_strerror(int err)
+{
+  if (err < 0 || err >= N(errtab))
+    return ("Error code out of range");
+  return (errtab[err]);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/codec/codec.h b/codec/codec.h
new file mode 100644 (file)
index 0000000..2c3e694
--- /dev/null
@@ -0,0 +1,109 @@
+/* -*-c-*-
+ *
+ * Abstract interface to codecs
+ *
+ * (c) 2009 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+#ifndef MLIB_CODEC_H
+#define MLIB_CODEC_H
+
+#ifdef __cplusplus
+  extern "C" {
+#endif
+
+/*----- Header files ------------------------------------------------------*/
+
+#ifndef MLIB_DSTR_H
+#  include "dstr.h"
+#endif
+
+/*----- Data structures ---------------------------------------------------*/
+
+typedef struct codec {
+  const struct codec_ops *ops;
+} codec;
+
+typedef struct codec_class {
+  const char *name;
+  codec *(*encoder)(unsigned /*flags*/,
+                   const char */*indent*/, unsigned /*maxline*/);
+  codec *(*decoder)(unsigned /*flags*/);
+
+#define CDCF_LOWERC 1u                 /* Prefer lower case */
+#define CDCF_IGNCASE 2u                        /* Ignore case on input */
+#define CDCF_NOEQPAD 4u                        /* No `=' padding */
+#define CDCF_IGNEQPAD 8u               /* Ignore `=' pad errors on input */
+#define CDCF_IGNEQMID 16u              /* Ignore pad chars on input */
+#define CDCF_IGNZPAD 32u               /* Ignore zero padding on input */
+#define CDCF_IGNNEWL 64u               /* Ignore newlines on input */
+#define CDCF_IGNINVCH 128u             /* Ignore invalid chars on input */
+
+#define CDCF_IGNJUNK                   /* Ignore all bad things */     \
+  (CDCF_IGNEQMID | CDCF_IGNZPAD |                                      \
+   CDCF_IGNCASE | CDCF_IGNNEWL | CDCF_IGNINVCH)
+
+} codec_class;
+
+#define CODEC_ERRORS(_)                                                        \
+  _(OK,                "No error")                                             \
+  _(INVCH,     "Invalid character")                                    \
+  _(INVEQPAD,  "Invalid padding character")                            \
+  _(INVZPAD,   "Nonzero padding bits")
+enum {
+#define DECLERR(name, text) CDCERR_##name,
+  CODEC_ERRORS(DECLERR)
+#undef DECLERR
+  CDCERR__LIMIT
+};
+
+typedef struct codec_ops {
+  const codec_class *c;
+  int (*code)(codec */*c*/, const void */*p*/, size_t /*sz*/, dstr */*d*/);
+  void (*destroy)(codec */*c*/);
+} codec_ops;
+
+/*----- Functions provided ------------------------------------------------*/
+
+/* --- @codec_strerror@ --- *
+ *
+ * Arguments:  @int err@ = error code from codec
+ *
+ * Returns:    Pointer to error text.
+ *
+ * Use:                Converts error codes to human-readable strings.
+ */
+
+const char *codec_strerror(int /*err*/);
+
+/*----- Null codec --------------------------------------------------------*/
+
+extern const codec_class null_codec_class;
+
+/*----- That's all, folks -------------------------------------------------*/
+
+#ifdef __cplusplus
+  }
+#endif
+
+#endif
diff --git a/codec/hex.3 b/codec/hex.3
deleted file mode 100644 (file)
index b4e58b5..0000000
+++ /dev/null
@@ -1,91 +0,0 @@
-.\" -*-nroff-*-
-.TH hex 3 "20 June 1999" "Straylight/Edgeware" "mLib utilities library"
-.SH NAME
-base64 \- conversion to and from base64 encoding
-.\" @hex_encode
-.\" @hex_decode
-.\" @hex_init
-.SH SYNOPSIS
-.nf
-.B "#include <mLib/hex.h>"
-
-.BI "void hex_encode(hex_ctx *" ctx ,
-.BI "                const void *" p ", size_t " sz ,
-.BI "                dstr *" d );
-.BI "void hex_decode(hex_ctx *" ctx ,
-.BI "                const void *" p ", size_t " sz ,
-.BI "                dstr *" d );
-.BI "void hex_init(hex_ctx *" ctx );
-.fi
-.SH DESCRIPTION
-The
-.B hex
-functions perform hex encoding and decoding of arbitrary binary
-strings.
-.PP
-Before encoding or decoding a string, a
-.I context
-(of type
-.BR hex_ctx )
-must be initialized, by passing it to
-.BR hex_init .
-The context contains data which must be retained between calls to encode
-or decode substrings.  The
-.B hex_init
-function sets up initial values for the data, and sets up defaults for
-the output formatting settings (see below).
-.PP
-Encoding of a string is performed by the
-.B hex_encode
-function.  It is passed a pointer to a context block
-.IR ctx ,
-the input substring to encode passed by address
-.I p
-and length
-.IR sz ,
-and a pointer to a dynamic string
-.I d
-in which to write its output (see
-.BR dstr (3)
-for details on dynamic strings).  Once all the input data has been
-passed through
-.B hex_encode
-it is necessary to flush the final few bytes of output.  This is
-achieved by passing
-.B hex_encode
-a null pointer as its source argument.  It is an error to attempt to
-continue encoding after flushing output.
-.PP
-The output of the
-.B hex_encode
-function is formatted into lines using values from the context
-structure.  The
-.B indent
-member is a pointer to a null-terminated string which is used to
-separate the output lines.  The default indent string contains only a
-newline character.  The
-.B maxline
-member gives the maximum length of line that
-.B hex_encode
-is allowed to produce.  If this is not a multiple of 2, it is rounded
-up to the next highest multiple of two before use.  A value of zero
-instructs
-.B hex_encode
-not to perform line splitting: the output will be a single (possibly
-very long) output line.  The default maximum line length is 72
-characters.  You may set these parameters by direct assignment to the
-context structure once it has been initialized.
-.PP
-Decoding is performed similarly by the
-.B hex_decode
-function.  The comments above about flushing output apply equally to
-decoding.
-.PP
-Decoding ignores all whitespace characters in the encoded string.
-.SH "SEE ALSO"
-.BR base34 (3),
-.BR base64 (3),
-.BR dstr (3),
-.BR mLib (3).
-.SH AUTHOR
-Mark Wooding, <mdw@distorted.org.uk>
diff --git a/codec/hex.c b/codec/hex.c
deleted file mode 100644 (file)
index 2fcb61e..0000000
+++ /dev/null
@@ -1,202 +0,0 @@
-/* -*-c-*-
- *
- * Hexadecimal encoding and decoding.
- *
- * (c) 2001 Straylight/Edgeware
- */
-
-/*----- Licensing notice --------------------------------------------------*
- *
- * This file is part of the mLib utilities library.
- *
- * mLib is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Library General Public License as
- * published by the Free Software Foundation; either version 2 of the
- * License, or (at your option) any later version.
- *
- * mLib is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Library General Public License for more details.
- *
- * You should have received a copy of the GNU Library General Public
- * License along with mLib; if not, write to the Free
- * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
- * MA 02111-1307, USA.
- */
-
-/*----- Header files ------------------------------------------------------*/
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "dstr.h"
-#include "hex.h"
-
-/*----- Important tables --------------------------------------------------*/
-
-static const char encodemap[] = { "0123456789abcdef" };
-
-static const signed char decodemap[] = {
-  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  /* 0x */
-  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  /* 1x */
-  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  /* 2x */
-   0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  1, -1, -1, -1, -1, -1,  /* 3x */
-  -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,  /* 4x */
-  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  /* 5x */
-  -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,  /* 6x */
-  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  /* 7x */
- };
-
-/*----- Main code ---------------------------------------------------------*/
-
-/* --- @hex_encode@ --- *
- *
- * Arguments:  @hex_ctx *ctx@ = pointer to a context block
- *             @const void *p@ = pointer to a source buffer
- *             @size_t sz@ = size of the source buffer
- *             @dstr *d@ = pointer to destination string
- *
- * Returns:    ---
- *
- * Use:                Encodes a binary string in hex.
- */
-
-void hex_encode(hex_ctx *ctx, const void *p, size_t sz,        dstr *d)
-{
-  if (p) {
-    const unsigned char *src = p;
-
-    while (sz) {
-      unsigned x = *src++;
-      sz--;
-      DPUTC(d, encodemap[(x >> 4) & 0xf]);
-      DPUTC(d, encodemap[(x >> 0) & 0xf]);
-      ctx->lnlen += 2;
-      if (ctx->maxline && ctx->lnlen >= ctx->maxline) {
-       dstr_puts(d, ctx->indent);
-       ctx->lnlen = 0;
-      }
-    }
-  }
-}
-
-/* --- @hex_decode@ --- *
- *
- * Arguments:  @hex_ctx *ctx@ = pointer to a context block
- *             @const void *p@ = pointer to a source buffer
- *             @size_t sz@ = size of the source buffer
- *             @dstr *d@ = pointer to destination string
- *
- * Returns:    ---
- *
- * Use:                Decodes a binary string in hex.  Pass in a null source
- *             pointer when you thing you've finished.
- */
-
-void hex_decode(hex_ctx *ctx, const void *p, size_t sz, dstr *d)
-{
-  if (p) {
-    unsigned long acc = ctx->acc;
-    unsigned qsz = ctx->qsz;
-    const char *src = p;
-    int ch;
-
-    while (sz) {
-
-      /* --- Get the next character and convert it --- */
-
-      ch = *src++;
-      if (ch >= 128 || ch < 0)
-       ch = -1;
-      else
-       ch = decodemap[ch];
-      sz--;
-      if (ch == -1)
-       continue;
-
-      /* --- Bung it in the accumulator --- */
-
-      acc = (acc << 4) | ch;
-      qsz++;
-
-      /* --- Maybe write out a completed triplet --- */
-
-      if (qsz == 2) {
-       DPUTC(d, acc & 0xff);
-       acc = 0;
-       qsz = 0;
-      }
-    }
-
-    ctx->acc = acc;
-    ctx->qsz = qsz;
-  } else {
-    if (ctx->qsz)
-      DPUTC(d, ctx->acc << 4);
-  }
-}
-
-/* --- @hex_init@ --- *
- *
- * Arguments:  @hex_ctx *ctx@ = pointer to context block to initialize
- *
- * Returns:    ---
- *
- * Use:                Initializes a hex context properly.
- */
-
-void hex_init(hex_ctx *ctx)
-{
-  ctx->acc = 0;
-  ctx->qsz = 0;
-  ctx->lnlen = 0;
-  ctx->indent = "\n";
-  ctx->maxline = 72;
-}
-
-/*----- Test driver code --------------------------------------------------*/
-
-#ifdef TEST_RIG
-
-int main(int argc, char *argv[])
-{
-  unsigned char buf[BUFSIZ];
-  dstr d = DSTR_INIT;
-  hex_ctx ctx;
-  void (*proc)(hex_ctx *, const void *, size_t, dstr *);
-  size_t sz;
-
-  hex_init(&ctx);
-
-  if (argc > 1 && strcmp(argv[1], "-d") == 0)
-    proc = hex_decode;
-  else {
-    proc = hex_encode;
-    putchar('\t');
-    ctx.indent = "\n\t";
-    ctx.maxline = 64;
-  }
-
-  do {
-    sz = fread(buf, 1, sizeof(buf), stdin);
-    if (sz) {
-      proc(&ctx, buf, sz, &d);
-      dstr_write(&d, stdout);
-      dstr_destroy(&d);
-    }
-  } while (sz == sizeof(buf));
-
-  proc(&ctx, 0, 0, &d);
-  dstr_write(&d, stdout);
-
-  if (proc == hex_encode)
-    putchar('\n');
-
-  return (0);
-}
-
-#endif
-
-/*----- That's all, folks -------------------------------------------------*/
index 3fee0ea..c36ae31 100644 (file)
@@ -92,6 +92,10 @@ extern void hex_decode(hex_ctx */*ctx*/, const void */*p*/, size_t /*sz*/,
 
 extern void hex_init(hex_ctx */*ctx*/);
 
+/*----- Codec object interface --------------------------------------------*/
+
+extern const codec_class hex_class;
+
 /*----- That's all, folks -------------------------------------------------*/
 
 #ifdef __cplusplus
diff --git a/codec/null-codec.c b/codec/null-codec.c
new file mode 100644 (file)
index 0000000..1f5493e
--- /dev/null
@@ -0,0 +1,67 @@
+/* -*-c-*-
+ *
+ * Null codec implementation
+ *
+ * (c) 2009 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the mLib utilities library.
+ *
+ * mLib is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * mLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with mLib; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <string.h>
+
+#include "codec.h"
+#include "sub.h"
+
+/*----- Data structures ---------------------------------------------------*/
+
+typedef struct null_codec {
+  codec c;
+} null_codec;
+
+/*----- Main code ---------------------------------------------------------*/
+
+static int encdec(codec *c, const void *p, size_t sz, dstr *d)
+  { DPUTM(d, p, sz); return (0); }
+
+static void destroy(codec *c)
+  { null_codec *nc = (null_codec *)c; DESTROY(nc); }
+
+static const codec_ops ops = { &null_codec_class, encdec, destroy };
+
+static codec *encoder(unsigned flags, const char *indent, unsigned maxlen)
+{
+  null_codec *nc = CREATE(null_codec);
+  nc->c.ops = &ops;
+  return (&nc->c);
+}
+
+static codec *decoder(unsigned flags)
+{
+  null_codec *nc = CREATE(null_codec);
+  nc->c.ops = &ops;
+  return (&nc->c);
+}
+
+const codec_class null_codec_class = { "null", encoder, decoder };
+
+/*----- That's all, folks -------------------------------------------------*/
index fc49b4a..6c72744 100644 (file)
@@ -3,8 +3,11 @@ mlib (2.2.0~pre1) experimental; urgency=low
   * Major internal reorganization.
   * Ship precomputed tables and provide partial support for
     cross-compilation.
+  * Overhaul of binary-to-text coding: now has better error handling, and
+    a single unified engine.  Also provides a program, bincode(1), for
+    exercising the system.
 
- --
+ -- Mark Wooding <mdw@distorted.org.uk>  Sun, 03 May 2009 01:43:57 +0100
 
 mlib (2.1.1) experimental; urgency=low