diff --git a/Makefile b/Makefile index 52d6de420..9dfac1994 100644 --- a/Makefile +++ b/Makefile @@ -79,6 +79,7 @@ CCAN_OBJS := \ ccan-ptr_valid.o \ ccan-rbuf.o \ ccan-read_write_all.o \ + ccan-str-base32.o \ ccan-str-hex.o \ ccan-str.o \ ccan-take.o \ @@ -138,6 +139,7 @@ CCAN_HEADERS := \ $(CCANDIR)/ccan/rbuf/rbuf.h \ $(CCANDIR)/ccan/read_write_all/read_write_all.h \ $(CCANDIR)/ccan/short_types/short_types.h \ + $(CCANDIR)/ccan/str/base32/base32.h \ $(CCANDIR)/ccan/str/hex/hex.h \ $(CCANDIR)/ccan/str/str.h \ $(CCANDIR)/ccan/str/str_debug.h \ @@ -549,3 +551,5 @@ ccan-bitops.o: $(CCANDIR)/ccan/bitops/bitops.c $(CC) $(CFLAGS) -c -o $@ $< ccan-rbuf.o: $(CCANDIR)/ccan/rbuf/rbuf.c $(CC) $(CFLAGS) -c -o $@ $< +ccan-str-base32.o: $(CCANDIR)/ccan/str/base32/base32.c + $(CC) $(CFLAGS) -c -o $@ $< diff --git a/ccan/ccan/str/base32/LICENSE b/ccan/ccan/str/base32/LICENSE new file mode 120000 index 000000000..08d5d486f --- /dev/null +++ b/ccan/ccan/str/base32/LICENSE @@ -0,0 +1 @@ +../../../licenses/CC0 \ No newline at end of file diff --git a/ccan/ccan/str/base32/_info b/ccan/ccan/str/base32/_info new file mode 100644 index 000000000..d14f9e3d8 --- /dev/null +++ b/ccan/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/ccan/str/base32/base32.c b/ccan/ccan/str/base32/base32.c new file mode 100644 index 000000000..71ca87d58 --- /dev/null +++ b/ccan/ccan/str/base32/base32.c @@ -0,0 +1,159 @@ +/* CC0 license (public domain) - see LICENSE file for details */ +#include "base32.h" +#include +#include +#include /* for memcpy, memset */ + +const char *base32_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567="; + +/* 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] == base32_chars[32] && 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++) { + const char *p; + + acc <<= 5; + p = memchr(base32_chars, c[i], 32); + if (!p) { + if (c[i] == base32_chars[32]) { + num_pad++; + continue; + } + return false; + } + /* Can't have padding then non-pad */ + if (num_pad) + return false; + acc |= (p - base32_chars); + } + *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; + + assert(bytes > 0 && bytes <= 5); + memcpy((char *)&val + 3, buf, bytes); + res = be64_to_cpu(val); + + while (bits > 0) { + *dest = base32_chars[(res >> 35) & 0x1F]; + dest++; + res <<= 5; + bits -= 5; + } + + if (bytes != 5) + memset(dest, base32_chars[32], 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/ccan/str/base32/base32.h b/ccan/ccan/str/base32/base32.h new file mode 100644 index 000000000..177aad7e0 --- /dev/null +++ b/ccan/ccan/str/base32/base32.h @@ -0,0 +1,77 @@ +/* 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); + +/** + * base32_chars - the encoding/decoding array to use. + * + * It must be at least 33 characters long, representing 32 values and + * the pad value. The default array is "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567=". + */ +extern const char *base32_chars; + +#endif /* CCAN_STR_BASE32_H */ diff --git a/ccan/ccan/str/base32/test/run-lower.c b/ccan/ccan/str/base32/test/run-lower.c new file mode 100644 index 000000000..5f64d64e7 --- /dev/null +++ b/ccan/ccan/str/base32/test/run-lower.c @@ -0,0 +1,38 @@ +#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); + + base32_chars = "abcdefghijklmnopqrstuvwxyz234567="; + + /* Test vectors from RFC, but lower-case */ + 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(); +} diff --git a/ccan/ccan/str/base32/test/run.c b/ccan/ccan/str/base32/test/run.c new file mode 100644 index 000000000..397658ba2 --- /dev/null +++ b/ccan/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(); +}