From 61aee192bcd814b3cb9073bc0280e4ef5cc45c7d Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Thu, 5 Apr 2018 11:53:11 +0930 Subject: [PATCH 1/1] str/base32: new module. Signed-off-by: Rusty Russell --- ccan/str/base32/LICENSE | 1 + ccan/str/base32/_info | 26 ++++++ ccan/str/base32/base32.c | 160 +++++++++++++++++++++++++++++++++++++ ccan/str/base32/base32.h | 68 ++++++++++++++++ ccan/str/base32/test/run.c | 36 +++++++++ 5 files changed, 291 insertions(+) create mode 120000 ccan/str/base32/LICENSE create mode 100644 ccan/str/base32/_info create mode 100644 ccan/str/base32/base32.c create mode 100644 ccan/str/base32/base32.h create mode 100644 ccan/str/base32/test/run.c diff --git a/ccan/str/base32/LICENSE b/ccan/str/base32/LICENSE new file mode 120000 index 00000000..08d5d486 --- /dev/null +++ b/ccan/str/base32/LICENSE @@ -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 index 00000000..d14f9e3d --- /dev/null +++ b/ccan/str/base32/_info @@ -0,0 +1,26 @@ +#include "config.h" +#include +#include + +/** + * 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 + */ +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 index 00000000..5cdd461b --- /dev/null +++ b/ccan/str/base32/base32.c @@ -0,0 +1,160 @@ +/* CC0 license (public domain) - see LICENSE file for details */ +#include "base32.h" +#include +#include +#include /* 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 index 00000000..6960fe82 --- /dev/null +++ b/ccan/str/base32/base32.h @@ -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 +#include + +/** + * 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 index 00000000..397658ba --- /dev/null +++ b/ccan/str/base32/test/run.c @@ -0,0 +1,36 @@ +#include +/* Include the C files directly. */ +#include +#include + +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(); +} -- 2.39.2