Have base_16 codec return a transform__output
diff --git a/example/jsonfindptrs/jsonfindptrs.cc b/example/jsonfindptrs/jsonfindptrs.cc
index 7eb845c..298179b 100644
--- a/example/jsonfindptrs/jsonfindptrs.cc
+++ b/example/jsonfindptrs/jsonfindptrs.cc
@@ -652,18 +652,22 @@
           }
           while (encoded.len) {
             uint8_t decoded[64];
-            size_t len = wuffs_base__hexadecimal__decode4(
-                wuffs_base__make_slice_u8(&decoded[0], 64), encoded);
-            if ((len > 64) || ((len * 4) > encoded.len)) {
+            const bool src_closed = true;
+            wuffs_base__transform__output o = wuffs_base__base_16__decode4(
+                wuffs_base__make_slice_u8(&decoded[0], 64), encoded, src_closed,
+                WUFFS_BASE__BASE_16__DEFAULT_OPTIONS);
+            if (o.status.is_error()) {
+              return Result(o.status.message(), JsonThing());
+            } else if ((o.num_dst > 64) || (o.num_src > encoded.len)) {
               return Result(
                   "main: internal error: inconsistent hexadecimal decoding",
                   JsonThing());
             }
             const char* ptr =  // Convert from (uint8_t*).
                 static_cast<const char*>(static_cast<void*>(&decoded[0]));
-            jt.value.s.append(ptr, len);
-            encoded.ptr += len * 4;
-            encoded.len -= len * 4;
+            jt.value.s.append(ptr, o.num_dst);
+            encoded.ptr += o.num_src;
+            encoded.len -= o.num_src;
           }
 
         } else {
diff --git a/internal/cgen/base/i64conv-submodule.c b/internal/cgen/base/i64conv-submodule.c
index f6fcb2e..b071c11 100644
--- a/internal/cgen/base/i64conv-submodule.c
+++ b/internal/cgen/base/i64conv-submodule.c
@@ -396,13 +396,30 @@
                                                                false);
 }
 
-// ---------------- Hexadecimal
+// ---------------- Base-16
 
-WUFFS_BASE__MAYBE_STATIC size_t  //
-wuffs_base__hexadecimal__decode2(wuffs_base__slice_u8 dst,
-                                 wuffs_base__slice_u8 src) {
+WUFFS_BASE__MAYBE_STATIC wuffs_base__transform__output  //
+wuffs_base__base_16__decode2(wuffs_base__slice_u8 dst,
+                             wuffs_base__slice_u8 src,
+                             bool src_closed,
+                             uint32_t options) {
+  wuffs_base__transform__output o;
   size_t src_len2 = src.len / 2;
-  size_t len = dst.len < src_len2 ? dst.len : src_len2;
+  size_t len;
+  if (dst.len < src_len2) {
+    len = dst.len;
+    o.status.repr = wuffs_base__suspension__short_write;
+  } else {
+    len = src_len2;
+    if (!src_closed) {
+      o.status.repr = wuffs_base__suspension__short_read;
+    } else if (src.len & 1) {
+      o.status.repr = wuffs_base__error__bad_data;
+    } else {
+      o.status.repr = NULL;
+    }
+  }
+
   uint8_t* d = dst.ptr;
   uint8_t* s = src.ptr;
   size_t n = len;
@@ -414,14 +431,33 @@
     s += 2;
   }
 
-  return len;
+  o.num_dst = len;
+  o.num_src = len * 2;
+  return o;
 }
 
-WUFFS_BASE__MAYBE_STATIC size_t  //
-wuffs_base__hexadecimal__decode4(wuffs_base__slice_u8 dst,
-                                 wuffs_base__slice_u8 src) {
+WUFFS_BASE__MAYBE_STATIC wuffs_base__transform__output  //
+wuffs_base__base_16__decode4(wuffs_base__slice_u8 dst,
+                             wuffs_base__slice_u8 src,
+                             bool src_closed,
+                             uint32_t options) {
+  wuffs_base__transform__output o;
   size_t src_len4 = src.len / 4;
   size_t len = dst.len < src_len4 ? dst.len : src_len4;
+  if (dst.len < src_len4) {
+    len = dst.len;
+    o.status.repr = wuffs_base__suspension__short_write;
+  } else {
+    len = src_len4;
+    if (!src_closed) {
+      o.status.repr = wuffs_base__suspension__short_read;
+    } else if (src.len & 1) {
+      o.status.repr = wuffs_base__error__bad_data;
+    } else {
+      o.status.repr = NULL;
+    }
+  }
+
   uint8_t* d = dst.ptr;
   uint8_t* s = src.ptr;
   size_t n = len;
@@ -433,7 +469,9 @@
     s += 4;
   }
 
-  return len;
+  o.num_dst = len;
+  o.num_src = len * 4;
+  return o;
 }
 
 // ---------------- Base-64
diff --git a/internal/cgen/base/strconv-public.h b/internal/cgen/base/strconv-public.h
index 071476d..b92c747 100644
--- a/internal/cgen/base/strconv-public.h
+++ b/internal/cgen/base/strconv-public.h
@@ -259,13 +259,14 @@
                               uint64_t x,
                               uint32_t options);
 
-// ---------------- Hexadecimal
+// ---------------- Base-16
 
-// wuffs_base__hexadecimal__decode2 converts "6A6b" to "jk", where e.g. 'j' is
-// U+006A. There are 2 source bytes for every destination byte.
-//
-// It returns the number of dst bytes written: the minimum of dst.len and
-// (src.len / 2). Excess source bytes are ignored.
+// Options (bitwise or'ed together) for wuffs_base__base_16__xxx functions.
+
+#define WUFFS_BASE__BASE_16__DEFAULT_OPTIONS ((uint32_t)0x00000000)
+
+// wuffs_base__base_16__decode2 converts "6A6b" to "jk", where e.g. 'j' is
+// U+006A. There are 2 src bytes for every dst byte.
 //
 // It assumes that the src bytes are two hexadecimal digits (0-9, A-F, a-f),
 // repeated. It may write nonsense bytes if not, although it will not read or
@@ -274,15 +275,14 @@
 // For modular builds that divide the base module into sub-modules, using this
 // function requires the WUFFS_CONFIG__MODULE__BASE__I64CONV sub-module, not
 // just WUFFS_CONFIG__MODULE__BASE__CORE.
-WUFFS_BASE__MAYBE_STATIC size_t  //
-wuffs_base__hexadecimal__decode2(wuffs_base__slice_u8 dst,
-                                 wuffs_base__slice_u8 src);
+WUFFS_BASE__MAYBE_STATIC wuffs_base__transform__output  //
+wuffs_base__base_16__decode2(wuffs_base__slice_u8 dst,
+                                 wuffs_base__slice_u8 src,
+                                 bool src_closed,
+                                 uint32_t options);
 
-// wuffs_base__hexadecimal__decode4 converts "\\x6A\\x6b" to "jk", where e.g.
-// 'j' is U+006A. There are 4 source bytes for every destination byte.
-//
-// It returns the number of dst bytes written: the minimum of dst.len and
-// (src.len / 4). Excess source bytes are ignored.
+// wuffs_base__base_16__decode4 converts both "\\x6A\\x6b" and "??6a??6B" to
+// "jk", where e.g. 'j' is U+006A. There are 4 src bytes for every dst byte.
 //
 // It assumes that the src bytes are two ignored bytes and then two hexadecimal
 // digits (0-9, A-F, a-f), repeated. It may write nonsense bytes if not,
@@ -291,9 +291,11 @@
 // For modular builds that divide the base module into sub-modules, using this
 // function requires the WUFFS_CONFIG__MODULE__BASE__I64CONV sub-module, not
 // just WUFFS_CONFIG__MODULE__BASE__CORE.
-WUFFS_BASE__MAYBE_STATIC size_t  //
-wuffs_base__hexadecimal__decode4(wuffs_base__slice_u8 dst,
-                                 wuffs_base__slice_u8 src);
+WUFFS_BASE__MAYBE_STATIC wuffs_base__transform__output  //
+wuffs_base__base_16__decode4(wuffs_base__slice_u8 dst,
+                                 wuffs_base__slice_u8 src,
+                                 bool src_closed,
+                                 uint32_t options);
 
 // ---------------- Base-64
 
diff --git a/internal/cgen/data/data.go b/internal/cgen/data/data.go
index 44b14b4..b6e7d85 100644
--- a/internal/cgen/data/data.go
+++ b/internal/cgen/data/data.go
@@ -175,8 +175,9 @@
 	"   ptr -= 1;\n    ptr[0] = (uint8_t)('0' + x);\n  } else {\n    size_t index = x * 2;\n    uint8_t s0 = wuffs_base__render_number__first_hundred[index + 0];\n    uint8_t s1 = wuffs_base__render_number__first_hundred[index + 1];\n    ptr -= 2;\n    ptr[0] = s0;\n    ptr[1] = s1;\n  }\n\n  if (neg) {\n    ptr -= 1;\n    ptr[0] = '-';\n  } else if (options & WUFFS_BASE__RENDER_NUMBER_XXX__LEADING_PLUS_SIGN) {\n    ptr -= 1;\n    ptr[0] = '+';\n  }\n\n  size_t n = sizeof(buf) - ((size_t)(ptr - &buf[0]));\n  if (n > dst.len) {\n    return 0;\n  }\n  memcpy(dst.ptr + ((options & WUFFS_BASE__RENDER_NUMBER_XXX__ALIGN_RIGHT)\n                        ? (dst.len - n)\n                        : 0),\n         ptr, n);\n  return n;\n}\n\nWUFFS_BASE__MAYBE_STATIC size_t  //\nwuffs_base__render_number_i64(wuffs_base__slice_u8 dst,\n                              int64_t x,\n                              uint32_t options) {\n  uint64_t u = (uint64_t)x;\n  bool neg = x < 0;\n  if (neg) {\n    u = 1 + ~u;\n  }\n  return wuffs_base__private_implementation__render_numb" +
 	"er_u64(dst, u, options,\n                                                               neg);\n}\n\nWUFFS_BASE__MAYBE_STATIC size_t  //\nwuffs_base__render_number_u64(wuffs_base__slice_u8 dst,\n                              uint64_t x,\n                              uint32_t options) {\n  return wuffs_base__private_implementation__render_number_u64(dst, x, options,\n                                                               false);\n}\n\n" +
 	"" +
-	"// ---------------- Hexadecimal\n\nWUFFS_BASE__MAYBE_STATIC size_t  //\nwuffs_base__hexadecimal__decode2(wuffs_base__slice_u8 dst,\n                                 wuffs_base__slice_u8 src) {\n  size_t src_len2 = src.len / 2;\n  size_t len = dst.len < src_len2 ? dst.len : src_len2;\n  uint8_t* d = dst.ptr;\n  uint8_t* s = src.ptr;\n  size_t n = len;\n\n  while (n--) {\n    *d = (uint8_t)((wuffs_base__parse_number__hexadecimal_digits[s[0]] << 4) |\n                   (wuffs_base__parse_number__hexadecimal_digits[s[1]] & 0x0F));\n    d += 1;\n    s += 2;\n  }\n\n  return len;\n}\n\nWUFFS_BASE__MAYBE_STATIC size_t  //\nwuffs_base__hexadecimal__decode4(wuffs_base__slice_u8 dst,\n                                 wuffs_base__slice_u8 src) {\n  size_t src_len4 = src.len / 4;\n  size_t len = dst.len < src_len4 ? dst.len : src_len4;\n  uint8_t* d = dst.ptr;\n  uint8_t* s = src.ptr;\n  size_t n = len;\n\n  while (n--) {\n    *d = (uint8_t)((wuffs_base__parse_number__hexadecimal_digits[s[2]] << 4) |\n                   (wuffs_base__parse_number__hexa" +
-	"decimal_digits[s[3]] & 0x0F));\n    d += 1;\n    s += 4;\n  }\n\n  return len;\n}\n\n" +
+	"// ---------------- Base-16\n\nWUFFS_BASE__MAYBE_STATIC wuffs_base__transform__output  //\nwuffs_base__base_16__decode2(wuffs_base__slice_u8 dst,\n                             wuffs_base__slice_u8 src,\n                             bool src_closed,\n                             uint32_t options) {\n  wuffs_base__transform__output o;\n  size_t src_len2 = src.len / 2;\n  size_t len;\n  if (dst.len < src_len2) {\n    len = dst.len;\n    o.status.repr = wuffs_base__suspension__short_write;\n  } else {\n    len = src_len2;\n    if (!src_closed) {\n      o.status.repr = wuffs_base__suspension__short_read;\n    } else if (src.len & 1) {\n      o.status.repr = wuffs_base__error__bad_data;\n    } else {\n      o.status.repr = NULL;\n    }\n  }\n\n  uint8_t* d = dst.ptr;\n  uint8_t* s = src.ptr;\n  size_t n = len;\n\n  while (n--) {\n    *d = (uint8_t)((wuffs_base__parse_number__hexadecimal_digits[s[0]] << 4) |\n                   (wuffs_base__parse_number__hexadecimal_digits[s[1]] & 0x0F));\n    d += 1;\n    s += 2;\n  }\n\n  o.num_dst = len;\n  o.num_s" +
+	"rc = len * 2;\n  return o;\n}\n\nWUFFS_BASE__MAYBE_STATIC wuffs_base__transform__output  //\nwuffs_base__base_16__decode4(wuffs_base__slice_u8 dst,\n                             wuffs_base__slice_u8 src,\n                             bool src_closed,\n                             uint32_t options) {\n  wuffs_base__transform__output o;\n  size_t src_len4 = src.len / 4;\n  size_t len = dst.len < src_len4 ? dst.len : src_len4;\n  if (dst.len < src_len4) {\n    len = dst.len;\n    o.status.repr = wuffs_base__suspension__short_write;\n  } else {\n    len = src_len4;\n    if (!src_closed) {\n      o.status.repr = wuffs_base__suspension__short_read;\n    } else if (src.len & 1) {\n      o.status.repr = wuffs_base__error__bad_data;\n    } else {\n      o.status.repr = NULL;\n    }\n  }\n\n  uint8_t* d = dst.ptr;\n  uint8_t* s = src.ptr;\n  size_t n = len;\n\n  while (n--) {\n    *d = (uint8_t)((wuffs_base__parse_number__hexadecimal_digits[s[2]] << 4) |\n                   (wuffs_base__parse_number__hexadecimal_digits[s[3]] & 0x0F));\n    d += 1;\n   " +
+	" s += 4;\n  }\n\n  o.num_dst = len;\n  o.num_src = len * 4;\n  return o;\n}\n\n" +
 	"" +
 	"// ---------------- Base-64\n\n// The two base-64 alphabets, std and url, differ only in the last two codes.\n//  - std: \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\"\n//  - url: \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_\"\n\nstatic const uint8_t wuffs_base__base_64__decode_std[256] = {\n    // 0     1     2     3     4     5     6     7\n    // 8     9     A     B     C     D     E     F\n    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x00 ..= 0x07.\n    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x08 ..= 0x0F.\n    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x10 ..= 0x17.\n    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x18 ..= 0x1F.\n    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x20 ..= 0x27.\n    0x80, 0x80, 0x80, 0x3E, 0x80, 0x80, 0x80, 0x3F,  // 0x28 ..= 0x2F.\n    0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B,  // 0x30 ..= 0x37.\n    0x3C, 0x3D, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x38 ..= 0x3F.\n\n    0x80, 0x00, 0x01, 0x02," +
 	" 0x03, 0x04, 0x05, 0x06,  // 0x40 ..= 0x47.\n    0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,  // 0x48 ..= 0x4F.\n    0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,  // 0x50 ..= 0x57.\n    0x17, 0x18, 0x19, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x58 ..= 0x5F.\n    0x80, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20,  // 0x60 ..= 0x67.\n    0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,  // 0x68 ..= 0x6F.\n    0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30,  // 0x70 ..= 0x77.\n    0x31, 0x32, 0x33, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x78 ..= 0x7F.\n\n    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x80 ..= 0x87.\n    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x88 ..= 0x8F.\n    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x90 ..= 0x97.\n    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0x98 ..= 0x9F.\n    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xA0 ..= 0xA7.\n    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // 0xA8 ..= 0xAF.\n    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,  // " +
@@ -526,8 +527,8 @@
 	" (and no bytes are written).\n//\n// dst will never be too short if its length is at least 20, also known as\n// WUFFS_BASE__I64__BYTE_LENGTH__MAX_INCL.\n//\n// For modular builds that divide the base module into sub-modules, using this\n// function requires the WUFFS_CONFIG__MODULE__BASE__I64CONV sub-module, not\n// just WUFFS_CONFIG__MODULE__BASE__CORE.\nWUFFS_BASE__MAYBE_STATIC size_t  //\nwuffs_base__render_number_i64(wuffs_base__slice_u8 dst,\n                              int64_t x,\n                              uint32_t options);\n\n// wuffs_base__render_number_u64 writes the decimal encoding of x to dst and\n// returns the number of bytes written. If dst is shorter than the entire\n// encoding, it returns 0 (and no bytes are written).\n//\n// dst will never be too short if its length is at least 21, also known as\n// WUFFS_BASE__U64__BYTE_LENGTH__MAX_INCL.\n//\n// For modular builds that divide the base module into sub-modules, using this\n// function requires the WUFFS_CONFIG__MODULE__BASE__I64CONV sub-module, not\n// ju" +
 	"st WUFFS_CONFIG__MODULE__BASE__CORE.\nWUFFS_BASE__MAYBE_STATIC size_t  //\nwuffs_base__render_number_u64(wuffs_base__slice_u8 dst,\n                              uint64_t x,\n                              uint32_t options);\n\n" +
 	"" +
-	"// ---------------- Hexadecimal\n\n// wuffs_base__hexadecimal__decode2 converts \"6A6b\" to \"jk\", where e.g. 'j' is\n// U+006A. There are 2 source bytes for every destination byte.\n//\n// It returns the number of dst bytes written: the minimum of dst.len and\n// (src.len / 2). Excess source bytes are ignored.\n//\n// It assumes that the src bytes are two hexadecimal digits (0-9, A-F, a-f),\n// repeated. It may write nonsense bytes if not, although it will not read or\n// write out of bounds.\n//\n// For modular builds that divide the base module into sub-modules, using this\n// function requires the WUFFS_CONFIG__MODULE__BASE__I64CONV sub-module, not\n// just WUFFS_CONFIG__MODULE__BASE__CORE.\nWUFFS_BASE__MAYBE_STATIC size_t  //\nwuffs_base__hexadecimal__decode2(wuffs_base__slice_u8 dst,\n                                 wuffs_base__slice_u8 src);\n\n// wuffs_base__hexadecimal__decode4 converts \"\\\\x6A\\\\x6b\" to \"jk\", where e.g.\n// 'j' is U+006A. There are 4 source bytes for every destination byte.\n//\n// It returns the number of d" +
-	"st bytes written: the minimum of dst.len and\n// (src.len / 4). Excess source bytes are ignored.\n//\n// It assumes that the src bytes are two ignored bytes and then two hexadecimal\n// digits (0-9, A-F, a-f), repeated. It may write nonsense bytes if not,\n// although it will not read or write out of bounds.\n//\n// For modular builds that divide the base module into sub-modules, using this\n// function requires the WUFFS_CONFIG__MODULE__BASE__I64CONV sub-module, not\n// just WUFFS_CONFIG__MODULE__BASE__CORE.\nWUFFS_BASE__MAYBE_STATIC size_t  //\nwuffs_base__hexadecimal__decode4(wuffs_base__slice_u8 dst,\n                                 wuffs_base__slice_u8 src);\n\n" +
+	"// ---------------- Base-16\n\n// Options (bitwise or'ed together) for wuffs_base__base_16__xxx functions.\n\n#define WUFFS_BASE__BASE_16__DEFAULT_OPTIONS ((uint32_t)0x00000000)\n\n// wuffs_base__base_16__decode2 converts \"6A6b\" to \"jk\", where e.g. 'j' is\n// U+006A. There are 2 src bytes for every dst byte.\n//\n// It assumes that the src bytes are two hexadecimal digits (0-9, A-F, a-f),\n// repeated. It may write nonsense bytes if not, although it will not read or\n// write out of bounds.\n//\n// For modular builds that divide the base module into sub-modules, using this\n// function requires the WUFFS_CONFIG__MODULE__BASE__I64CONV sub-module, not\n// just WUFFS_CONFIG__MODULE__BASE__CORE.\nWUFFS_BASE__MAYBE_STATIC wuffs_base__transform__output  //\nwuffs_base__base_16__decode2(wuffs_base__slice_u8 dst,\n                                 wuffs_base__slice_u8 src,\n                                 bool src_closed,\n                                 uint32_t options);\n\n// wuffs_base__base_16__decode4 converts both \"\\\\x6A\\\\x6b\" and" +
+	" \"??6a??6B\" to\n// \"jk\", where e.g. 'j' is U+006A. There are 4 src bytes for every dst byte.\n//\n// It assumes that the src bytes are two ignored bytes and then two hexadecimal\n// digits (0-9, A-F, a-f), repeated. It may write nonsense bytes if not,\n// although it will not read or write out of bounds.\n//\n// For modular builds that divide the base module into sub-modules, using this\n// function requires the WUFFS_CONFIG__MODULE__BASE__I64CONV sub-module, not\n// just WUFFS_CONFIG__MODULE__BASE__CORE.\nWUFFS_BASE__MAYBE_STATIC wuffs_base__transform__output  //\nwuffs_base__base_16__decode4(wuffs_base__slice_u8 dst,\n                                 wuffs_base__slice_u8 src,\n                                 bool src_closed,\n                                 uint32_t options);\n\n" +
 	"" +
 	"// ---------------- Base-64\n\n// Options (bitwise or'ed together) for wuffs_base__base_64__xxx functions.\n\n#define WUFFS_BASE__BASE_64__DEFAULT_OPTIONS ((uint32_t)0x00000000)\n\n// WUFFS_BASE__BASE_64__DECODE_ALLOW_PADDING means that, when decoding base-64,\n// the input may (but does not need to) be padded with '=' bytes so that the\n// overall encoded length in bytes is a multiple of 4. A successful decoding\n// will return a num_src that includes those padding bytes.\n//\n// Excess padding (e.g. three final '='s) will be rejected as bad data.\n#define WUFFS_BASE__BASE_64__DECODE_ALLOW_PADDING ((uint32_t)0x00000001)\n\n// WUFFS_BASE__BASE_64__ENCODE_EMIT_PADDING means that, when encoding base-64,\n// the output will be padded with '=' bytes so that the overall encoded length\n// in bytes is a multiple of 4.\n#define WUFFS_BASE__BASE_64__ENCODE_EMIT_PADDING ((uint32_t)0x00000002)\n\n// WUFFS_BASE__BASE_64__URL_ALPHABET means that, for base-64, the URL-friendly\n// and file-name-friendly alphabet be used, as per RFC 4648 sect" +
 	"ion 5. When\n// this option bit is off, the standard alphabet from section 4 is used.\n#define WUFFS_BASE__BASE_64__URL_ALPHABET ((uint32_t)0x00000100)\n\n// wuffs_base__base_64__decode transforms base-64 encoded bytes from src to\n// arbitrary bytes in dst.\n//\n// It will not permit line breaks or other whitespace in src. Filtering those\n// out is the responsibility of the caller.\n//\n// For modular builds that divide the base module into sub-modules, using this\n// function requires the WUFFS_CONFIG__MODULE__BASE__I64CONV sub-module, not\n// just WUFFS_CONFIG__MODULE__BASE__CORE.\nWUFFS_BASE__MAYBE_STATIC wuffs_base__transform__output  //\nwuffs_base__base_64__decode(wuffs_base__slice_u8 dst,\n                            wuffs_base__slice_u8 src,\n                            bool src_closed,\n                            uint32_t options);\n\n// wuffs_base__base_64__encode transforms arbitrary bytes from src to base-64\n// encoded bytes in dst.\n//\n// For modular builds that divide the base module into sub-modules, using this" +
diff --git a/release/c/wuffs-unsupported-snapshot.c b/release/c/wuffs-unsupported-snapshot.c
index 9f188e1..a7a6592 100644
--- a/release/c/wuffs-unsupported-snapshot.c
+++ b/release/c/wuffs-unsupported-snapshot.c
@@ -4176,13 +4176,14 @@
                               uint64_t x,
                               uint32_t options);
 
-// ---------------- Hexadecimal
+// ---------------- Base-16
 
-// wuffs_base__hexadecimal__decode2 converts "6A6b" to "jk", where e.g. 'j' is
-// U+006A. There are 2 source bytes for every destination byte.
-//
-// It returns the number of dst bytes written: the minimum of dst.len and
-// (src.len / 2). Excess source bytes are ignored.
+// Options (bitwise or'ed together) for wuffs_base__base_16__xxx functions.
+
+#define WUFFS_BASE__BASE_16__DEFAULT_OPTIONS ((uint32_t)0x00000000)
+
+// wuffs_base__base_16__decode2 converts "6A6b" to "jk", where e.g. 'j' is
+// U+006A. There are 2 src bytes for every dst byte.
 //
 // It assumes that the src bytes are two hexadecimal digits (0-9, A-F, a-f),
 // repeated. It may write nonsense bytes if not, although it will not read or
@@ -4191,15 +4192,14 @@
 // For modular builds that divide the base module into sub-modules, using this
 // function requires the WUFFS_CONFIG__MODULE__BASE__I64CONV sub-module, not
 // just WUFFS_CONFIG__MODULE__BASE__CORE.
-WUFFS_BASE__MAYBE_STATIC size_t  //
-wuffs_base__hexadecimal__decode2(wuffs_base__slice_u8 dst,
-                                 wuffs_base__slice_u8 src);
+WUFFS_BASE__MAYBE_STATIC wuffs_base__transform__output  //
+wuffs_base__base_16__decode2(wuffs_base__slice_u8 dst,
+                                 wuffs_base__slice_u8 src,
+                                 bool src_closed,
+                                 uint32_t options);
 
-// wuffs_base__hexadecimal__decode4 converts "\\x6A\\x6b" to "jk", where e.g.
-// 'j' is U+006A. There are 4 source bytes for every destination byte.
-//
-// It returns the number of dst bytes written: the minimum of dst.len and
-// (src.len / 4). Excess source bytes are ignored.
+// wuffs_base__base_16__decode4 converts both "\\x6A\\x6b" and "??6a??6B" to
+// "jk", where e.g. 'j' is U+006A. There are 4 src bytes for every dst byte.
 //
 // It assumes that the src bytes are two ignored bytes and then two hexadecimal
 // digits (0-9, A-F, a-f), repeated. It may write nonsense bytes if not,
@@ -4208,9 +4208,11 @@
 // For modular builds that divide the base module into sub-modules, using this
 // function requires the WUFFS_CONFIG__MODULE__BASE__I64CONV sub-module, not
 // just WUFFS_CONFIG__MODULE__BASE__CORE.
-WUFFS_BASE__MAYBE_STATIC size_t  //
-wuffs_base__hexadecimal__decode4(wuffs_base__slice_u8 dst,
-                                 wuffs_base__slice_u8 src);
+WUFFS_BASE__MAYBE_STATIC wuffs_base__transform__output  //
+wuffs_base__base_16__decode4(wuffs_base__slice_u8 dst,
+                                 wuffs_base__slice_u8 src,
+                                 bool src_closed,
+                                 uint32_t options);
 
 // ---------------- Base-64
 
@@ -11963,13 +11965,30 @@
                                                                false);
 }
 
-// ---------------- Hexadecimal
+// ---------------- Base-16
 
-WUFFS_BASE__MAYBE_STATIC size_t  //
-wuffs_base__hexadecimal__decode2(wuffs_base__slice_u8 dst,
-                                 wuffs_base__slice_u8 src) {
+WUFFS_BASE__MAYBE_STATIC wuffs_base__transform__output  //
+wuffs_base__base_16__decode2(wuffs_base__slice_u8 dst,
+                             wuffs_base__slice_u8 src,
+                             bool src_closed,
+                             uint32_t options) {
+  wuffs_base__transform__output o;
   size_t src_len2 = src.len / 2;
-  size_t len = dst.len < src_len2 ? dst.len : src_len2;
+  size_t len;
+  if (dst.len < src_len2) {
+    len = dst.len;
+    o.status.repr = wuffs_base__suspension__short_write;
+  } else {
+    len = src_len2;
+    if (!src_closed) {
+      o.status.repr = wuffs_base__suspension__short_read;
+    } else if (src.len & 1) {
+      o.status.repr = wuffs_base__error__bad_data;
+    } else {
+      o.status.repr = NULL;
+    }
+  }
+
   uint8_t* d = dst.ptr;
   uint8_t* s = src.ptr;
   size_t n = len;
@@ -11981,14 +12000,33 @@
     s += 2;
   }
 
-  return len;
+  o.num_dst = len;
+  o.num_src = len * 2;
+  return o;
 }
 
-WUFFS_BASE__MAYBE_STATIC size_t  //
-wuffs_base__hexadecimal__decode4(wuffs_base__slice_u8 dst,
-                                 wuffs_base__slice_u8 src) {
+WUFFS_BASE__MAYBE_STATIC wuffs_base__transform__output  //
+wuffs_base__base_16__decode4(wuffs_base__slice_u8 dst,
+                             wuffs_base__slice_u8 src,
+                             bool src_closed,
+                             uint32_t options) {
+  wuffs_base__transform__output o;
   size_t src_len4 = src.len / 4;
   size_t len = dst.len < src_len4 ? dst.len : src_len4;
+  if (dst.len < src_len4) {
+    len = dst.len;
+    o.status.repr = wuffs_base__suspension__short_write;
+  } else {
+    len = src_len4;
+    if (!src_closed) {
+      o.status.repr = wuffs_base__suspension__short_read;
+    } else if (src.len & 1) {
+      o.status.repr = wuffs_base__error__bad_data;
+    } else {
+      o.status.repr = NULL;
+    }
+  }
+
   uint8_t* d = dst.ptr;
   uint8_t* s = src.ptr;
   size_t n = len;
@@ -12000,7 +12038,9 @@
     s += 4;
   }
 
-  return len;
+  o.num_dst = len;
+  o.num_src = len * 4;
+  return o;
 }
 
 // ---------------- Base-64
diff --git a/test/c/std/json.c b/test/c/std/json.c
index 7c8f527..e6840b5 100644
--- a/test/c/std/json.c
+++ b/test/c/std/json.c
@@ -422,17 +422,26 @@
 // ----------------
 
 const char*  //
-test_wuffs_strconv_hexadecimal() {
+test_wuffs_strconv_base_16() {
   CHECK_FOCUS(__func__);
+  const bool src_closed = true;
 
   {
-    const char* str = "6A6b7";  // The "7" should be ignored.
+    const char* str = "6A6b7";  // The "7" should cause "#base: bad data".
     wuffs_base__slice_u8 dst = g_have_slice_u8;
     wuffs_base__slice_u8 src =
         wuffs_base__make_slice_u8((void*)str, strlen(str));
-    size_t have = wuffs_base__hexadecimal__decode2(dst, src);
-    if (have != 2) {
-      RETURN_FAIL("decode2: have %zu, want 2", have);
+    wuffs_base__transform__output have = wuffs_base__base_16__decode2(
+        dst, src, src_closed, WUFFS_BASE__BASE_16__DEFAULT_OPTIONS);
+    if (have.status.repr != wuffs_base__error__bad_data) {
+      RETURN_FAIL("decode2: have \"%s\", want \"%s\"", have.status.repr,
+                  wuffs_base__error__bad_data);
+    }
+    if (have.num_dst != 2) {
+      RETURN_FAIL("decode2: num_dst: have %zu, want 2", have.num_dst);
+    }
+    if (have.num_src != 4) {
+      RETURN_FAIL("decode2: num_src: have %zu, want 3", have.num_src);
     }
     if (g_have_array_u8[0] != 0x6A) {
       RETURN_FAIL("decode2: dst[0]: have 0x%02X, want 0x6A",
@@ -449,9 +458,16 @@
     wuffs_base__slice_u8 dst = g_have_slice_u8;
     wuffs_base__slice_u8 src =
         wuffs_base__make_slice_u8((void*)str, strlen(str));
-    size_t have = wuffs_base__hexadecimal__decode4(dst, src);
-    if (have != 3) {
-      RETURN_FAIL("decode4: have %zu, want 3", have);
+    wuffs_base__transform__output have = wuffs_base__base_16__decode4(
+        dst, src, src_closed, WUFFS_BASE__BASE_16__DEFAULT_OPTIONS);
+    if (have.status.repr) {
+      RETURN_FAIL("decode2: %s", have.status.repr);
+    }
+    if (have.num_dst != 3) {
+      RETURN_FAIL("decode4: num_dst: have %zu, want 3", have.num_dst);
+    }
+    if (have.num_src != 12) {
+      RETURN_FAIL("decode4: num_src: have %zu, want 3", have.num_src);
     }
     if (g_have_array_u8[0] != 0xA9) {
       RETURN_FAIL("decode4: dst[0]: have 0x%02X, want 0xA9",
@@ -2468,11 +2484,13 @@
           (vbd &
            WUFFS_BASE__TOKEN__VBD__STRING__CONVERT_1_DST_4_SRC_BACKSLASH_X)) {
         uint8_t b[8] = {0};
-        size_t n = wuffs_base__hexadecimal__decode4(
+        const bool src_closed = true;
+        wuffs_base__transform__output o = wuffs_base__base_16__decode4(
             wuffs_base__make_slice_u8(&b[0], 8),
-            wuffs_base__make_slice_u8(src_slice.ptr + src_index, token_length));
+            wuffs_base__make_slice_u8(src_slice.ptr + src_index, token_length),
+            src_closed, 0);
         size_t i = 0;
-        for (; i < n; i++) {
+        for (; i < o.num_dst; i++) {
           have_bytes <<= 8;
           have_bytes |= b[i];
         }
@@ -3437,7 +3455,7 @@
     // good as any other place.
     test_wuffs_core_count_leading_zeroes_u64,
     test_wuffs_core_multiply_u64,
-    test_wuffs_strconv_hexadecimal,
+    test_wuffs_strconv_base_16,
     test_wuffs_strconv_base_64,
     test_wuffs_strconv_hpd_rounded_integer,
     test_wuffs_strconv_hpd_shift,