diff --git a/base32.c b/base32.c index 4ae018ce57..1da7e63327 100644 --- a/base32.c +++ b/base32.c @@ -22,10 +22,16 @@ #include "base32.h" +#include + const char *BASE32_ALPHABET_RFC4648 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ23456789"; static inline void base32_5to8(const uint8_t *in, uint8_t length, uint8_t *out); +static inline bool base32_8to5(const uint8_t *in, uint8_t length, uint8_t *out, const char *alphabet); +static inline void base32_8to5_raw(const uint8_t *in, uint8_t length, uint8_t *out); + static inline int base32_encode_character(uint8_t decoded, const char *alphabet); +static inline int base32_decode_character(char encoded, const char *alphabet); bool base32_encode(const uint8_t *in, size_t inlen, char *out, size_t outlen, const char *alphabet) { size_t length = base32_encoded_length(inlen); @@ -45,6 +51,19 @@ bool base32_encode(const uint8_t *in, size_t inlen, char *out, size_t outlen, co return true; } +uint8_t *base32_decode(const char *in, size_t inlen, uint8_t *out, size_t outlen, const char *alphabet) { + size_t length = base32_decoded_length(inlen); + if (outlen < length) { + return NULL; + } + + if (!base32_decode_unsafe((uint8_t *) in, inlen, (uint8_t *) out, alphabet)) { + return NULL; + } + + return &out[length]; +} + void base32_encode_unsafe(const uint8_t *in, size_t inlen, uint8_t *out) { uint8_t remainder = inlen % 5; size_t limit = inlen - remainder; @@ -57,6 +76,24 @@ void base32_encode_unsafe(const uint8_t *in, size_t inlen, uint8_t *out) { if (remainder) base32_5to8(&in[i], remainder, &out[j]); } +bool base32_decode_unsafe(const uint8_t *in, size_t inlen, uint8_t *out, const char *alphabet) { + uint8_t remainder = inlen % 8; + size_t limit = inlen - remainder; + + size_t i, j; + for (i = 0, j = 0; i < limit; i += 8, j += 5) { + if (!base32_8to5(&in[i], 8, &out[j], alphabet)) { + return false; + } + } + + if (remainder && !base32_8to5(&in[i], remainder, &out[j], alphabet)) { + return false; + } + + return true; +} + size_t base32_encoded_length(size_t inlen) { uint8_t remainder = inlen % 5; @@ -98,6 +135,61 @@ void base32_5to8(const uint8_t *in, uint8_t length, uint8_t *out) { } } +bool base32_8to5(const uint8_t *in, uint8_t length, uint8_t *out, const char *alphabet) { + if (length == 1 || length == 3 || length == 6 || length > 8) { + return false; + } + + if (alphabet) { + uint8_t decoded[length]; + + for (size_t i = 0; i < length; i++) { + int ret = base32_decode_character(in[i], alphabet); + + if (ret == -1) { + return false; + } else { + decoded[i] = ret; + } + } + + base32_8to5_raw(decoded, length, out); + } else { + base32_8to5_raw(in, length, out); + } + + return true; +} + +void base32_8to5_raw(const uint8_t *in, uint8_t length, uint8_t *out) { + if (length >= 2) { + out[0] = (in[0] << 3); + out[0] |= (in[1] >> 2); + } + + if (length >= 4) { + out[1] = (in[1] & 3) << 6; + out[1] |= (in[2] << 1); + out[1] |= (in[3] >> 4); + } + + if (length >= 5) { + out[2] = (in[3] & 15) << 4; + out[2] |= (in[4] >> 1); + } + + if (length >= 7) { + out[3] = (in[4] & 1) << 7; + out[3] |= (in[5] << 2); + out[3] |= (in[6] >> 3); + } + + if (length >= 8) { + out[4] = (in[6] & 7) << 5; + out[4] |= (in[7] & 31); + } +} + int base32_encode_character(uint8_t decoded, const char *alphabet) { if (decoded >> 5) { return -1; @@ -113,3 +205,25 @@ int base32_encode_character(uint8_t decoded, const char *alphabet) { return alphabet[decoded]; } + +int base32_decode_character(char encoded, const char *alphabet) { + if (alphabet == BASE32_ALPHABET_RFC4648) { + if (encoded >= 'A' && encoded <= 'Z') { + return encoded - 'A'; + } else if (encoded >= 'a' && encoded <= 'z') { + return encoded - 'a'; + } else if (encoded >= '2' && encoded <= '7') { + return encoded - '2' + 26; + } else { + return -1; + } + } + + const char *occurrence = strchr(alphabet, encoded); + + if (occurrence) { + return occurrence - alphabet; + } else { + return -1; + } +} diff --git a/base32.h b/base32.h index edef9806b8..a4dcbc8da5 100644 --- a/base32.h +++ b/base32.h @@ -32,6 +32,9 @@ extern const char *BASE32_ALPHABET_RFC4648; bool base32_encode(const uint8_t *in, size_t inlen, char *out, size_t outlen, const char *alphabet); void base32_encode_unsafe(const uint8_t *in, size_t inlen, uint8_t *out); +uint8_t *base32_decode(const char *in, size_t inlen, uint8_t *out, size_t outlen, const char *alphabet); +bool base32_decode_unsafe(const uint8_t *in, size_t inlen, uint8_t *out, const char *alphabet); + size_t base32_encoded_length(size_t inlen); size_t base32_decoded_length(size_t inlen); diff --git a/test_check.c b/test_check.c index 54037b6156..4659460fae 100644 --- a/test_check.c +++ b/test_check.c @@ -527,8 +527,14 @@ START_TEST(test_base32_rfc4648) ck_assert_int_eq(outlen, base32_encoded_length(inlen)); ck_assert_int_eq(inlen, base32_decoded_length(outlen)); - base32_encode((uint8_t *) in, inlen, buffer, sizeof(buffer), BASE32_ALPHABET_RFC4648); + ck_assert(base32_encode((uint8_t *) in, inlen, buffer, sizeof(buffer), BASE32_ALPHABET_RFC4648)); ck_assert_str_eq(buffer, out); + + char *ret = (char *) base32_decode(out, outlen, (uint8_t *) buffer, sizeof(buffer), BASE32_ALPHABET_RFC4648); + ck_assert(ret); + + *ret = '\0'; + ck_assert_str_eq(buffer, in); } } END_TEST