diff options
-rw-r--r-- | serd/serd.h | 49 | ||||
-rw-r--r-- | src/base64.c | 29 | ||||
-rw-r--r-- | src/node.c | 6 | ||||
-rw-r--r-- | tests/base64_test.c | 121 | ||||
-rw-r--r-- | tests/serd_test.c | 13 | ||||
-rw-r--r-- | wscript | 2 |
6 files changed, 194 insertions, 26 deletions
diff --git a/serd/serd.h b/serd/serd.h index 2b4f0410..4a562dbc 100644 --- a/serd/serd.h +++ b/serd/serd.h @@ -366,18 +366,59 @@ double serd_strtod(const char* str, size_t* end); /** + @} + @name Base64 + @{ +*/ + +/** + Return the number of bytes required to encode `size` bytes in base64. + + @param size The number of input (binary) bytes to encode. + @param wrap_lines Wrap lines at 76 characters to conform to RFC 2045. + @return The length of the base64 encoding, excluding null terminator. +*/ +SERD_API +size_t +serd_base64_encoded_length(size_t size, bool wrap_lines); + +/** + Return the maximum number of bytes required to decode `size` bytes of base64. + + @param len The number of input (text) bytes to decode. + @return The required buffer size to decode `size` bytes of base64. +*/ +SERD_API +size_t +serd_base64_decoded_size(size_t len); + +/** + Encode `size` bytes of `buf` into `str`, which must be large enough. + + @param str Output buffer of at least serd_base64_encoded_length(size) bytes. + @param buf Input binary data. + @param size Number of bytes to encode from `buf`. + @param wrap_lines Wrap lines at 76 characters to conform to RFC 2045. + @return True iff `str` contains newlines. +*/ +SERD_API +bool +serd_base64_encode(char* str, const void* buf, size_t size, bool wrap_lines); + +/** Decode a base64 string. + This function can be used to deserialise a blob node created with serd_new_blob(). + @param buf Output buffer of at least serd_base64_decoded_size(size) bytes. + @param size Set to the size of the decoded data in bytes. @param str Base64 string to decode. @param len The length of `str`. - @param size Set to the size of the returned blob in bytes. - @return A newly allocated blob which must be freed with serd_free(). */ SERD_API -void* -serd_base64_decode(const char* str, size_t len, size_t* size); +SerdStatus +serd_base64_decode(void* buf, size_t* size, const char* str, size_t len); /** @} diff --git a/src/base64.c b/src/base64.c index 884d0a48..c9e958c2 100644 --- a/src/base64.c +++ b/src/base64.c @@ -14,8 +14,6 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#include "base64.h" - #include "serd_internal.h" #include "string_utils.h" @@ -60,29 +58,36 @@ encode_chunk(uint8_t out[4], const uint8_t in[3], size_t n_in) } size_t -serd_base64_get_length(const size_t size, const bool wrap_lines) +serd_base64_encoded_length(const size_t size, const bool wrap_lines) { return (size + 2) / 3 * 4 + (wrap_lines * ((size - 1) / 57)); } +size_t +serd_base64_decoded_size(const size_t len) +{ + return (len * 3) / 4 + 2; +} + bool -serd_base64_encode(uint8_t* const str, +serd_base64_encode(char* const str, const void* const buf, const size_t size, const bool wrap_lines) { - bool has_newline = false; + uint8_t* const out = (uint8_t*)str; + bool has_newline = false; for (size_t i = 0, j = 0; i < size; i += 3, j += 4) { uint8_t in[4] = { 0, 0, 0, 0 }; size_t n_in = MIN(3, size - i); memcpy(in, (const uint8_t*)buf + i, n_in); if (wrap_lines && i > 0 && (i % 57) == 0) { - str[j++] = '\n'; + out[j++] = '\n'; has_newline = true; } - encode_chunk(str + j, in, n_in); + encode_chunk(out + j, in, n_in); } return has_newline; @@ -104,13 +109,12 @@ decode_chunk(const uint8_t in[4], uint8_t out[3]) return 1 + (in[2] != '=') + ((in[2] != '=') && (in[3] != '=')); } -void* -serd_base64_decode(const char* str, size_t len, size_t* size) +SerdStatus +serd_base64_decode(void* buf, size_t* size, const char* str, size_t len) { const uint8_t* const ustr = (const uint8_t*)str; - void* buf = malloc((len * 3) / 4 + 2); - *size = 0; + *size = 0; for (size_t i = 0, j = 0; i < len; j += 3) { uint8_t in[] = "===="; size_t n_in = 0; @@ -122,5 +126,6 @@ serd_base64_decode(const char* str, size_t len, size_t* size) *size += decode_chunk(in, (uint8_t*)buf + j); } } - return buf; + + return SERD_SUCCESS; } @@ -16,7 +16,6 @@ #include "node.h" -#include "base64.h" #include "serd_internal.h" #include "static_nodes.h" #include "string_utils.h" @@ -686,15 +685,14 @@ serd_new_blob(const void* buf, } const SerdNode* type = datatype ? datatype : &serd_xsd_base64Binary.node; - const size_t len = serd_base64_get_length(size, wrap_lines); + const size_t len = serd_base64_encoded_length(size, wrap_lines); const size_t type_len = serd_node_total_size(type); const size_t total_len = len + 1 + type_len; SerdNode* const node = serd_node_malloc(total_len, SERD_HAS_DATATYPE, SERD_LITERAL); - uint8_t* str = (uint8_t*)serd_node_buffer(node); - if (serd_base64_encode(str, buf, size, wrap_lines)) { + if (serd_base64_encode(serd_node_buffer(node), buf, size, wrap_lines)) { node->flags |= SERD_HAS_NEWLINE; } diff --git a/tests/base64_test.c b/tests/base64_test.c new file mode 100644 index 00000000..19b58745 --- /dev/null +++ b/tests/base64_test.c @@ -0,0 +1,121 @@ +/* + Copyright 2011-2020 David Robillard <http://drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#undef NDEBUG + +#include "serd/serd.h" + +#include <assert.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> + +static int +test_round_trip(void) +{ + for (size_t size = 1; size < 1024; ++size) { + const size_t len = serd_base64_encoded_length(size, true); + + char* buf = (char*)malloc(size); + for (size_t i = 0; i < size; ++i) { + buf[i] = (char)i; + } + + char* str = (char*)calloc(1, len + 1); + serd_base64_encode(str, buf, size, true); + + const size_t max_size = serd_base64_decoded_size(len); + size_t copy_size = 0; + char* copy = (char*)malloc(max_size); + serd_base64_decode(copy, ©_size, str, len); + assert(copy_size == size); + assert(!memcmp(buf, copy, size)); + + free(copy); + free(str); + free(buf); + } + + return 0; +} + +static void +test_encoding_equals(const char* const input, const char* const expected) +{ + const size_t size = strlen(input); + const size_t len = serd_base64_encoded_length(size, true); + + char* str = (char*)calloc(1, len + 1); + serd_base64_encode(str, input, size, true); + + assert(!strcmp(str, expected)); + + free(str); +} + +static int +test_rfc4648_vectors(void) +{ + test_encoding_equals("f", "Zg=="); + test_encoding_equals("fo", "Zm8="); + test_encoding_equals("foo", "Zm9v"); + test_encoding_equals("foob", "Zm9vYg=="); + test_encoding_equals("fooba", "Zm9vYmE="); + test_encoding_equals("foobar", "Zm9vYmFy"); + return 0; +} + +static void +test_decoding_equals(const char* const base64, const char* const expected) +{ + const size_t len = strlen(base64); + const size_t size = serd_base64_decoded_size(len); + + size_t buf_size = 0; + char* buf = (char*)malloc(size); + serd_base64_decode(buf, &buf_size, base64, len); + + assert(buf_size <= size); + assert(!memcmp(buf, expected, buf_size)); + + free(buf); +} + +static int +test_junk(void) +{ + test_decoding_equals("?Zm9vYmFy", "foobar"); + test_decoding_equals("Z?m9vYmFy", "foobar"); + test_decoding_equals("?Z?m9vYmFy", "foobar"); + test_decoding_equals("?Z??m9vYmFy", "foobar"); + test_decoding_equals("?Z???m9vYmFy", "foobar"); + test_decoding_equals("?Z????m9vYmFy", "foobar"); + + test_decoding_equals("Zm9vYmFy?", "foobar"); + test_decoding_equals("Zm9vYmF?y?", "foobar"); + test_decoding_equals("Zm9vYmF?y??", "foobar"); + test_decoding_equals("Zm9vYmF?y???", "foobar"); + test_decoding_equals("Zm9vYmF?y????", "foobar"); + + return 0; +} + +int +main(void) +{ + return test_round_trip() || test_rfc4648_vectors() || test_junk(); +} diff --git a/tests/serd_test.c b/tests/serd_test.c index 26515833..da3780f7 100644 --- a/tests/serd_test.c +++ b/tests/serd_test.c @@ -316,12 +316,13 @@ test_blob_to_node(void) data[i] = (uint8_t)((size + i) % 256); } - size_t out_size = 0; - SerdNode* blob = serd_new_blob(data, size, size % 5, NULL); - const char* blob_str = serd_node_string(blob); - uint8_t* out = (uint8_t*)serd_base64_decode( - blob_str, serd_node_length(blob), &out_size); + size_t out_size = 0; + SerdNode* blob = serd_new_blob(data, size, size % 5, NULL); + const char* blob_str = serd_node_string(blob); + const size_t len = serd_node_length(blob); + uint8_t* out = (uint8_t*)malloc(serd_base64_decoded_size(len)); + assert(!serd_base64_decode(out, &out_size, blob_str, len)); assert(serd_node_length(blob) == strlen(blob_str)); assert(out_size == size); @@ -333,7 +334,7 @@ test_blob_to_node(void) NS_XSD "base64Binary")); serd_node_free(blob); - serd_free(out); + free(out); free(data); } } @@ -222,6 +222,7 @@ def build(bld): # Test programs for prog in [('serdi_static', 'src/serdi.c'), + ('base64_test', 'tests/base64_test.c'), ('cursor_test', 'tests/cursor_test.c'), ('env_test', 'tests/env_test.c'), ('free_null_test', 'tests/free_null_test.c'), @@ -571,6 +572,7 @@ def test(tst): srcdir = tst.path.abspath() with tst.group('Unit') as check: + check(['./base64_test']) check(['./cursor_test']) check(['./env_test']) check(['./free_null_test']) |