]> git.ozlabs.org Git - ccan/commitdiff
str/base32: new module.
authorRusty Russell <rusty@rustcorp.com.au>
Thu, 5 Apr 2018 02:23:11 +0000 (11:53 +0930)
committerRusty Russell <rusty@rustcorp.com.au>
Thu, 5 Apr 2018 02:24:16 +0000 (11:54 +0930)
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
ccan/str/base32/LICENSE [new symlink]
ccan/str/base32/_info [new file with mode: 0644]
ccan/str/base32/base32.c [new file with mode: 0644]
ccan/str/base32/base32.h [new file with mode: 0644]
ccan/str/base32/test/run.c [new file with mode: 0644]

diff --git a/ccan/str/base32/LICENSE b/ccan/str/base32/LICENSE
new file mode 120000 (symlink)
index 0000000..08d5d48
--- /dev/null
@@ -0,0 +1 @@
+../../../licenses/CC0
\ No newline at end of file
diff --git a/ccan/str/base32/_info b/ccan/str/base32/_info
new file mode 100644 (file)
index 0000000..d14f9e3
--- /dev/null
@@ -0,0 +1,26 @@
+#include "config.h"
+#include <stdio.h>
+#include <string.h>
+
+/**
+ * str/base32 - RFC4648 base32 encoder/decoder.
+ *
+ * This code implements RFC4638 encoding, but you should use bech32 for most
+ * things anyway.
+ *
+ * License: CC0 (Public domain)
+ * Author: Rusty Russell <rusty@rustcorp.com.au>
+ */
+int main(int argc, char *argv[])
+{
+       /* Expect exactly one argument */
+       if (argc != 2)
+               return 1;
+
+       if (strcmp(argv[1], "depends") == 0) {
+               printf("ccan/endian\n");
+               return 0;
+       }
+
+       return 1;
+}
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;
+}
diff --git a/ccan/str/base32/base32.h b/ccan/str/base32/base32.h
new file mode 100644 (file)
index 0000000..6960fe8
--- /dev/null
@@ -0,0 +1,68 @@
+/* CC0 (Public domain) - see LICENSE file for details */
+#ifndef CCAN_STR_BASE32_H
+#define CCAN_STR_BASE32_H
+#include "config.h"
+#include <stdbool.h>
+#include <stdlib.h>
+
+/**
+ * base32_decode - Unpack a base32 string.
+ * @str: the base32 string
+ * @slen: the length of @str
+ * @buf: the buffer to write the data into
+ * @bufsize: the length of @buf
+ *
+ * Returns false if there are any characters which aren't valid encodings
+ * or the string wasn't the right length for @bufsize.
+ *
+ * Example:
+ *     unsigned char data[20];
+ *
+ *     if (!base32_decode(argv[1], strlen(argv[1]), data, 20))
+ *             printf("String is malformed!\n");
+ */
+bool base32_decode(const char *str, size_t slen, void *buf, size_t bufsize);
+
+/**
+ * base32_encode - Create a nul-terminated base32 string
+ * @buf: the buffer to read the data from
+ * @bufsize: the length of @buf
+ * @dest: the string to fill
+ * @destsize: the max size of the string
+ *
+ * Returns true if the string, including terminator, fits in @destsize;
+ *
+ * Example:
+ *     unsigned char buf[] = { 'f', 'o' };
+ *     char str[9];
+ *
+ *     if (!base32_encode(buf, sizeof(buf), str, sizeof(str)))
+ *             abort();
+ */
+bool base32_encode(const void *buf, size_t bufsize, char *dest, size_t destsize);
+
+/**
+ * base32_str_size - Calculate how big a nul-terminated base32 string is
+ * @bytes: bytes of data to represent
+ *
+ * Example:
+ *     unsigned char buf[] = { 'f', 'o' };
+ *     char str[base32_str_size(sizeof(buf))];
+ *
+ *     base32_encode(buf, sizeof(buf), str, sizeof(str));
+ */
+size_t base32_str_size(size_t bytes);
+
+/**
+ * base32_data_size - Calculate how many bytes of data in a base32 string
+ * @str: the string
+ * @strlen: the length of str to examine.
+ *
+ * Example:
+ *     const char str[] = "MZXQ====";
+ *     unsigned char buf[base32_data_size(str, strlen(str))];
+ *
+ *     base32_decode(str, strlen(str), buf, sizeof(buf));
+ */
+size_t base32_data_size(const char *str, size_t strlen);
+#endif /* CCAN_STR_BASE32_H */
diff --git a/ccan/str/base32/test/run.c b/ccan/str/base32/test/run.c
new file mode 100644 (file)
index 0000000..397658b
--- /dev/null
@@ -0,0 +1,36 @@
+#include <ccan/str/base32/base32.h>
+/* Include the C files directly. */
+#include <ccan/str/base32/base32.c>
+#include <ccan/tap/tap.h>
+
+static void test(const char *data, const char *b32)
+{
+       char test[1000];
+
+       ok1(base32_str_size(strlen(data)) == strlen(b32) + 1);
+       ok1(base32_data_size(b32, strlen(b32)) == strlen(data));
+       ok1(base32_encode(data, strlen(data), test, strlen(b32)+1));
+       ok1(strcmp(test, b32) == 0);
+       test[strlen(data)] = '\0';
+       ok1(base32_decode(b32, strlen(b32), test, strlen(data)));
+       ok1(strcmp(test, data) == 0);
+}
+
+int main(void)
+{
+       /* This is how many tests you plan to run */
+       plan_tests(8 * 6);
+
+       /* Test vectors from RFC */
+       test("", "");
+       test("f", "MY======");
+       test("fo", "MZXQ====");
+       test("foo", "MZXW6===");
+       test("foob", "MZXW6YQ=");
+       test("fooba", "MZXW6YTB");
+       test("r", "OI======");
+       test("foobar", "MZXW6YTBOI======");
+
+       /* This exits depending on whether all tests passed */
+       return exit_status();
+}