]> git.ozlabs.org Git - ccan/blobdiff - ccan/str/base32/base32.c
str/base32: new module.
[ccan] / ccan / str / base32 / base32.c
diff --git a/ccan/str/base32/base32.c b/ccan/str/base32/base32.c
new file mode 100644 (file)
index 0000000..5cdd461
--- /dev/null
@@ -0,0 +1,160 @@
+/* CC0 license (public domain) - see LICENSE file for details */
+#include "base32.h"
+#include <assert.h>
+#include <ccan/endian/endian.h>
+#include <string.h> /* for memcpy, memset */
+
+/* RFC 4648:
+ *
+ * (1) The final quantum of encoding input is an integral multiple of 40
+ *     bits; here, the final unit of encoded output will be an integral
+ *     multiple of 8 characters with no "=" padding.
+ *
+ * (2) The final quantum of encoding input is exactly 8 bits; here, the
+ *     final unit of encoded output will be two characters followed by
+ *     six "=" padding characters.
+ *
+ * (3) The final quantum of encoding input is exactly 16 bits; here, the
+ *     final unit of encoded output will be four characters followed by
+ *     four "=" padding characters.
+ *
+ * (4) The final quantum of encoding input is exactly 24 bits; here, the
+ *     final unit of encoded output will be five characters followed by
+ *     three "=" padding characters.
+ *
+ * (5) The final quantum of encoding input is exactly 32 bits; here, the
+ *     final unit of encoded output will be seven characters followed by
+ *     one "=" padding character.
+ */
+static size_t padlen(size_t remainder)
+{
+       switch (remainder) {
+       case 0:
+               return 0;
+       case 1:
+               return 6;
+       case 2:
+               return 4;
+       case 3:
+               return 3;
+       case 4:
+               return 1;
+       default:
+               abort();
+       }
+}
+
+size_t base32_str_size(size_t bytes)
+{
+       return (bytes + 4) / 5 * 8 + 1;
+}
+
+size_t base32_data_size(const char *str, size_t strlen)
+{
+       /* 8 chars == 5 bytes, round up to avoid overflow even though
+        * not required for well-formed strings. */
+       size_t max = (strlen + 7) / 8 * 5, padding = 0;
+
+       /* Count trailing padding bytes. */
+       while (strlen && str[strlen-1] == '=' && padding < 6) {
+               strlen--;
+               padding++;
+       }
+
+       return max - (padding * 5 + 7)  / 8;
+}
+
+static bool decode_8_chars(const char c[8], beint64_t *res, int *bytes)
+{
+       uint64_t acc = 0;
+       size_t num_pad = 0;
+       for (int i = 0; i < 8; i++) {
+               uint8_t val;
+               acc <<= 5;
+               if (c[i] >= 'a' && c[i] <= 'z')
+                       val = c[i] - 'a';
+               else if (c[i] >= 'A' && c[i] <= 'Z')
+                       val = c[i] - 'A';
+               else if (c[i] >= '2' && c[i] <= '7')
+                       val = c[i] - '2' + 26;
+               else if (c[i] == '=') {
+                       num_pad++;
+                       continue;
+               } else
+                       return false;
+               /* Can't have padding then non-pad */
+               if (num_pad)
+                       return false;
+               acc |= val;
+       }
+       *res = cpu_to_be64(acc);
+
+       /* Can't have 2 or 5 padding bytes */
+       if (num_pad == 5 || num_pad == 2)
+               return false;
+       *bytes = (40 - num_pad * 5) / 8;
+       return true;
+}
+
+bool base32_decode(const char *str, size_t slen, void *buf, size_t bufsize)
+{
+       while (slen >= 8) {
+               beint64_t val;
+               int bytes;
+               if (!decode_8_chars(str, &val, &bytes))
+                       return false;
+               str += 8;
+               slen -= 8;
+               /* Copy bytes into dst. */
+               if (bufsize < bytes)
+                       return false;
+               memcpy(buf, (char *)&val + 3, bytes);
+               buf = (char *)buf + bytes;
+               bufsize -= bytes;
+       }
+       return slen == 0 && bufsize == 0;
+}
+
+static void encode_8_chars(char *dest, const uint8_t *buf, int bytes)
+{
+       beint64_t val = 0;
+       uint64_t res;
+       int bits = bytes * 8;
+       static const char enc[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
+
+       assert(bytes > 0 && bytes <= 5);
+       memcpy((char *)&val + 3, buf, bytes);
+       res = be64_to_cpu(val);
+
+       while (bits > 0) {
+               *dest = enc[(res >> 35) & 0x1F];
+               dest++;
+               res <<= 5;
+               bits -= 5;
+       }
+
+       if (bytes != 5)
+               memset(dest, '=', padlen(bytes));
+}
+
+bool base32_encode(const void *buf, size_t bufsize, char *dest, size_t destsize)
+{
+       while (bufsize) {
+               int bytes = 5;
+
+               if (bytes > bufsize)
+                       bytes = bufsize;
+
+               if (destsize < 8)
+                       return false;
+               encode_8_chars(dest, buf, bytes);
+               buf = (const char *)buf + bytes;
+               bufsize -= bytes;
+               destsize -= 8;
+               dest += 8;
+       }
+       if (destsize != 1)
+               return false;
+       *dest = '\0';
+       return true;
+}