From 4b3b40709d1203c872e5152c6dbd8a2f15f7c184 Mon Sep 17 00:00:00 2001 From: Ahson Khan Date: Wed, 7 Jul 2021 20:41:10 -0700 Subject: [PATCH 1/3] Add base64 encoding and decoding APIs that accept az_span. --- sdk/inc/azure/az_core.h | 1 + sdk/inc/azure/core/az_base64.h | 89 +++++ sdk/src/azure/core/CMakeLists.txt | 1 + sdk/src/azure/core/az_base64.c | 521 +++++++++++++++++++++++++++ sdk/tests/core/CMakeLists.txt | 1 + sdk/tests/core/az_test_definitions.h | 1 + sdk/tests/core/main.c | 1 + sdk/tests/core/test_az_base64.c | 361 +++++++++++++++++++ 8 files changed, 976 insertions(+) create mode 100644 sdk/inc/azure/core/az_base64.h create mode 100644 sdk/src/azure/core/az_base64.c create mode 100644 sdk/tests/core/test_az_base64.c diff --git a/sdk/inc/azure/az_core.h b/sdk/inc/azure/az_core.h index d9e404bac5..c3c65c3ca6 100644 --- a/sdk/inc/azure/az_core.h +++ b/sdk/inc/azure/az_core.h @@ -15,6 +15,7 @@ #ifndef _az_CORE_H #define _az_CORE_H +#include #include #include #include diff --git a/sdk/inc/azure/core/az_base64.h b/sdk/inc/azure/core/az_base64.h new file mode 100644 index 0000000000..2710a0a10f --- /dev/null +++ b/sdk/inc/azure/core/az_base64.h @@ -0,0 +1,89 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +/** + * @file + * + * @brief Defines APIs to convert between binary data and UTF-8 encoded text that is represented in + * base 64. + * + * @note You MUST NOT use any symbols (macros, functions, structures, enums, etc.) + * prefixed with an underscore ('_') directly in your application code. These symbols + * are part of Azure SDK's internal implementation; we do not document these symbols + * and they are subject to change in future versions of the SDK which would break your code. + */ + +#ifndef _az_BASE64_H +#define _az_BASE64_H + +#include +#include + +#include + +#include + +/** + * @brief Encodes the span of binary data into UTF-8 encoded text represented as base 64. + * + * @param destination_base64_text The output #az_span where the encoded base 64 text should be + * copied to as a result of the operation. + * @param[in] source_bytes The input #az_span that contains binary data to be encoded. + * @param[out] out_written A pointer to an `int32_t` that receives the number of bytes written into + * the destination #az_span. This can be used to slice the output for subsequent calls, if + * necessary. + * + * @return An #az_result value indicating the result of the operation. + * @retval #AZ_OK Success. + * @retval #AZ_ERROR_NOT_ENOUGH_SPACE The \p destination_base64_text is not large enough to contain + * the encoded bytes. + */ +AZ_NODISCARD az_result +az_base64_encode(az_span destination_base64_text, az_span source_bytes, int32_t* out_written); + +/** + * @brief Returns the maximum length of the result if you were to encode an #az_span of the + * specified length which contained binary data. + * + * @param source_bytes_size The size of the span containing binary data. + * + * @return The maximum length of the result. + */ +AZ_NODISCARD int32_t az_base64_get_max_encoded_size(int32_t source_bytes_size); + +/** + * @brief Decodes the span of UTF-8 encoded text represented as base 64 into binary data. + * + * @param destination_bytes The output #az_span where the decoded binary data should be copied to as + * a result of the operation. + * @param[in] source_base64_text The input #az_span that contains the base 64 text to be decoded. + * @param[out] out_written A pointer to an `int32_t` that receives the number of bytes written into + * the destination #az_span. This can be used to slice the output for subsequent calls, if + * necessary. + * + * @return An #az_result value indicating the result of the operation. + * @retval #AZ_OK Success. + * @retval #AZ_ERROR_NOT_ENOUGH_SPACE The \p destination_bytes is not large enough to contain + * the decoded text. + * @retval #AZ_ERROR_UNEXPECTED_CHAR The input \p source_base64_text contains characters outside of + * the expected base 64 range, has invalid or more than two padding characters, or is incomplete + * (that is, not a multiple of 4). + * @retval #AZ_ERROR_UNEXPECTED_END The input \p source_base64_text is incomplete (that is, it is + * not of a size which is a multiple of 4). + */ +AZ_NODISCARD az_result +az_base64_decode(az_span destination_bytes, az_span source_base64_text, int32_t* out_written); + +/** + * @brief Returns the maximum length of the result if you were to decode an #az_span of the + * specified length which contained base 64 encoded text. + * + * @param source_base64_text_size The size of the span containing base 64 encoded text. + * + * @return The maximum length of the result. + */ +AZ_NODISCARD int32_t az_base64_get_max_decoded_size(int32_t source_base64_text_size); + +#include + +#endif // _az_BASE64_H diff --git a/sdk/src/azure/core/CMakeLists.txt b/sdk/src/azure/core/CMakeLists.txt index f70cc2e610..fd76e8e9e6 100644 --- a/sdk/src/azure/core/CMakeLists.txt +++ b/sdk/src/azure/core/CMakeLists.txt @@ -44,6 +44,7 @@ include(CheckAndIncludeCodeCov) add_library ( az_core + ${CMAKE_CURRENT_LIST_DIR}/az_base64.c ${CMAKE_CURRENT_LIST_DIR}/az_context.c ${CMAKE_CURRENT_LIST_DIR}/az_http_pipeline.c ${CMAKE_CURRENT_LIST_DIR}/az_http_policy.c diff --git a/sdk/src/azure/core/az_base64.c b/sdk/src/azure/core/az_base64.c new file mode 100644 index 0000000000..5c34013dbd --- /dev/null +++ b/sdk/src/azure/core/az_base64.c @@ -0,0 +1,521 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +#include +#include + +#include + +// The maximum integer length of binary data that can be encoded into base 64 text and still fit +// into an az_span which has an int32_t length, i.e. (INT32_MAX / 4) * 3; +#define _az_MAX_SAFE_ENCODED_LENGTH 1610612733 + +#define _az_ENCODING_PAD '=' + +static const char _az_base64_encode_array[65] + = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +static const int8_t _az_base64_decode_array[256] = { + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + 62, + -1, + -1, + -1, + 63, // 62 is placed at index 43 (for +), 63 at index 47 (for /) + 52, + 53, + 54, + 55, + 56, + 57, + 58, + 59, + 60, + 61, + -1, + -1, + -1, + -1, + -1, + -1, // 52-61 are placed at index 48-57 (for 0-9), 64 at index 61 (for =) + -1, + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + -1, + -1, + -1, + -1, + -1, // 0-25 are placed at index 65-90 (for A-Z) + -1, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + -1, + -1, + -1, + -1, + -1, // 26-51 are placed at index 97-122 (for a-z) + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, // Bytes over 122 ('z') are invalid and cannot be decoded. Hence, padding the map with 255, + // which indicates invalid input + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, +}; + +static AZ_NODISCARD int32_t _az_base64_encode(uint8_t* three_bytes) +{ + int32_t i = (*three_bytes << 16) | (*(three_bytes + 1) << 8) | *(three_bytes + 2); + + int32_t i0 = _az_base64_encode_array[i >> 18]; + int32_t i1 = _az_base64_encode_array[(i >> 12) & 0x3F]; + int32_t i2 = _az_base64_encode_array[(i >> 6) & 0x3F]; + int32_t i3 = _az_base64_encode_array[i & 0x3F]; + + return i0 | (i1 << 8) | (i2 << 16) | (i3 << 24); +} + +static AZ_NODISCARD int32_t _az_base64_encode_and_pad_one(uint8_t* two_bytes) +{ + int32_t i = (*two_bytes << 16) | (*(two_bytes + 1) << 8); + + int32_t i0 = _az_base64_encode_array[i >> 18]; + int32_t i1 = _az_base64_encode_array[(i >> 12) & 0x3F]; + int32_t i2 = _az_base64_encode_array[(i >> 6) & 0x3F]; + + return i0 | (i1 << 8) | (i2 << 16) | (_az_ENCODING_PAD << 24); +} + +static AZ_NODISCARD int32_t _az_base64_encode_and_pad_two(uint8_t* one_byte) +{ + int32_t i = (*one_byte << 8); + + int32_t i0 = _az_base64_encode_array[i >> 10]; + int32_t i1 = _az_base64_encode_array[(i >> 4) & 0x3F]; + + return i0 | (i1 << 8) | (_az_ENCODING_PAD << 16) | (_az_ENCODING_PAD << 24); +} + +static void _az_base64_write_int_as_four_bytes(uint8_t* destination, int32_t value) +{ + *(destination + 3) = (uint8_t)((value >> 24) & 0xFF); + *(destination + 2) = (uint8_t)((value >> 16) & 0xFF); + *(destination + 1) = (uint8_t)((value >> 8) & 0xFF); + *(destination + 0) = (uint8_t)(value & 0xFF); +} + +AZ_NODISCARD az_result +az_base64_encode(az_span destination_base64_text, az_span source_bytes, int32_t* out_written) +{ + _az_PRECONDITION_VALID_SPAN(destination_base64_text, 4, false); + _az_PRECONDITION_VALID_SPAN(source_bytes, 1, false); + _az_PRECONDITION_NOT_NULL(out_written); + + int32_t source_length = az_span_size(source_bytes); + uint8_t* source_ptr = az_span_ptr(source_bytes); + + int32_t destination_length = az_span_size(destination_base64_text); + uint8_t* destination_ptr = az_span_ptr(destination_base64_text); + + if (destination_length < az_base64_get_max_encoded_size(source_length)) + { + return AZ_ERROR_NOT_ENOUGH_SPACE; + } + + int32_t source_index = 0; + int32_t result = 0; + + while (source_index < source_length - 2) + { + result = _az_base64_encode(source_ptr + source_index); + _az_base64_write_int_as_four_bytes(destination_ptr, result); + destination_ptr += 4; + source_index += 3; + } + + if (source_index == source_length - 1) + { + result = _az_base64_encode_and_pad_two(source_ptr + source_index); + _az_base64_write_int_as_four_bytes(destination_ptr, result); + destination_ptr += 4; + source_index += 1; + } + else if (source_index == source_length - 2) + { + result = _az_base64_encode_and_pad_one(source_ptr + source_index); + _az_base64_write_int_as_four_bytes(destination_ptr, result); + destination_ptr += 4; + source_index += 2; + } + + *out_written = (int32_t)(destination_ptr - az_span_ptr(destination_base64_text)); + return AZ_OK; +} + +AZ_NODISCARD int32_t az_base64_get_max_encoded_size(int32_t source_bytes_size) +{ + _az_PRECONDITION_RANGE(0, source_bytes_size, _az_MAX_SAFE_ENCODED_LENGTH); + return (((source_bytes_size + 2) / 3) * 4); +} + +static AZ_NODISCARD int32_t _az_base64_decode(uint8_t* encoded_bytes) +{ + int32_t i0 = *encoded_bytes; + int32_t i1 = *(encoded_bytes + 1); + int32_t i2 = *(encoded_bytes + 2); + int32_t i3 = *(encoded_bytes + 3); + + i0 = _az_base64_decode_array[i0]; + i1 = _az_base64_decode_array[i1]; + i2 = _az_base64_decode_array[i2]; + i3 = _az_base64_decode_array[i3]; + + i0 <<= 18; + i1 <<= 12; + i2 <<= 6; + + i0 |= i3; + i1 |= i2; + + i0 |= i1; + return i0; +} + +static void _az_base64_write_three_low_order_bytes(uint8_t* destination, int32_t value) +{ + *destination = (uint8_t)(value >> 16); + *(destination + 1) = (uint8_t)(value >> 8); + *(destination + 2) = (uint8_t)(value); +} + +AZ_NODISCARD az_result +az_base64_decode(az_span destination_bytes, az_span source_base64_text, int32_t* out_written) +{ + _az_PRECONDITION_VALID_SPAN(destination_bytes, 1, false); + _az_PRECONDITION_VALID_SPAN(source_base64_text, 4, false); + _az_PRECONDITION_NOT_NULL(out_written); + + int32_t source_length = az_span_size(source_base64_text); + uint8_t* source_ptr = az_span_ptr(source_base64_text); + + int32_t destination_length = az_span_size(destination_bytes); + uint8_t* destination_ptr = az_span_ptr(destination_bytes); + + // The input must be non-empty and a multiple of 4 to be valid. + if (source_length == 0 || source_length % 4 != 0) + { + return AZ_ERROR_UNEXPECTED_END; + } + + if (destination_length < az_base64_get_max_decoded_size(source_length) - 2) + { + return AZ_ERROR_NOT_ENOUGH_SPACE; + } + + int32_t source_index = 0; + int32_t destination_index = 0; + + while (source_index < source_length - 4) + { + int32_t result = _az_base64_decode(source_ptr + source_index); + if (result < 0) + { + return AZ_ERROR_UNEXPECTED_CHAR; + } + _az_base64_write_three_low_order_bytes(destination_ptr, result); + destination_ptr += 3; + destination_index += 3; + source_index += 4; + } + + // We are guaranteed to have an input with at least 4 bytes at this point, with a size that is a + // multiple of 4. + int32_t i0 = *(source_ptr + source_length - 4); + int32_t i1 = *(source_ptr + source_length - 3); + int32_t i2 = *(source_ptr + source_length - 2); + int32_t i3 = *(source_ptr + source_length - 1); + + i0 = _az_base64_decode_array[i0]; + i1 = _az_base64_decode_array[i1]; + + i0 <<= 18; + i1 <<= 12; + + i0 |= i1; + + if (i3 != _az_ENCODING_PAD) + { + i2 = _az_base64_decode_array[i2]; + i3 = _az_base64_decode_array[i3]; + + i2 <<= 6; + + i0 |= i3; + i0 |= i2; + + if (i0 < 0) + { + return AZ_ERROR_UNEXPECTED_CHAR; + } + if (destination_index > destination_length - 3) + { + return AZ_ERROR_NOT_ENOUGH_SPACE; + } + _az_base64_write_three_low_order_bytes(destination_ptr, i0); + destination_ptr += 3; + } + else if (i2 != _az_ENCODING_PAD) + { + i2 = _az_base64_decode_array[i2]; + + i2 <<= 6; + + i0 |= i2; + + if (i0 < 0) + { + return AZ_ERROR_UNEXPECTED_CHAR; + } + if (destination_index > destination_length - 2) + { + return AZ_ERROR_NOT_ENOUGH_SPACE; + } + *(destination_ptr + 1) = (uint8_t)(i0 >> 8); + *destination_ptr = (uint8_t)(i0 >> 16); + destination_ptr += 2; + } + else + { + if (i0 < 0) + { + return AZ_ERROR_UNEXPECTED_CHAR; + } + if (destination_index > destination_length - 1) + { + return AZ_ERROR_NOT_ENOUGH_SPACE; + } + *destination_ptr = (uint8_t)(i0 >> 16); + destination_ptr += 1; + } + + *out_written = (int32_t)(destination_ptr - az_span_ptr(destination_bytes)); + return AZ_OK; +} + +AZ_NODISCARD int32_t az_base64_get_max_decoded_size(int32_t source_base64_text_size) +{ + _az_PRECONDITION(source_base64_text_size >= 0); + return (source_base64_text_size / 4) * 3; +} diff --git a/sdk/tests/core/CMakeLists.txt b/sdk/tests/core/CMakeLists.txt index 75f181771d..9ff332f129 100644 --- a/sdk/tests/core/CMakeLists.txt +++ b/sdk/tests/core/CMakeLists.txt @@ -23,6 +23,7 @@ endif() add_cmocka_test(az_core_test SOURCES main.c + test_az_base64.c test_az_context.c test_az_http.c test_az_json.c diff --git a/sdk/tests/core/az_test_definitions.h b/sdk/tests/core/az_test_definitions.h index 27bb9d6d11..82b25ac347 100644 --- a/sdk/tests/core/az_test_definitions.h +++ b/sdk/tests/core/az_test_definitions.h @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // SPDX-License-Identifier: MIT +int test_az_base64(); int test_az_context(); int test_az_http(); int test_az_json(); diff --git a/sdk/tests/core/main.c b/sdk/tests/core/main.c index 128e45ee4a..b588ab635b 100644 --- a/sdk/tests/core/main.c +++ b/sdk/tests/core/main.c @@ -19,6 +19,7 @@ int main() // every test function returns the number of tests failed, 0 means success (there shouldn't be // negative numbers + result += test_az_base64(); result += test_az_context(); result += test_az_http(); result += test_az_json(); diff --git a/sdk/tests/core/test_az_base64.c b/sdk/tests/core/test_az_base64.c new file mode 100644 index 0000000000..d761eb2a54 --- /dev/null +++ b/sdk/tests/core/test_az_base64.c @@ -0,0 +1,361 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +#include "az_test_definitions.h" +#include + +#include +#include +#include + +#include + +#include + +static void az_base64_max_encode_test(void** state) +{ + (void)state; + assert_int_equal(az_base64_get_max_encoded_size(1), 4); + assert_int_equal(az_base64_get_max_encoded_size(2), 4); + assert_int_equal(az_base64_get_max_encoded_size(3), 4); + assert_int_equal(az_base64_get_max_encoded_size(4), 8); + assert_int_equal(az_base64_get_max_encoded_size(5), 8); + assert_int_equal(az_base64_get_max_encoded_size(30), 40); + assert_int_equal(az_base64_get_max_encoded_size(1610612729), 2147483640); + assert_int_equal(az_base64_get_max_encoded_size(1610612730), 2147483640); + assert_int_equal(az_base64_get_max_encoded_size(1610612731), 2147483644); + assert_int_equal(az_base64_get_max_encoded_size(1610612732), 2147483644); + assert_int_equal(az_base64_get_max_encoded_size(1610612733), 2147483644); +} + +static void az_base64_max_decode_test(void** state) +{ + (void)state; + assert_int_equal(az_base64_get_max_decoded_size(1), 0); + assert_int_equal(az_base64_get_max_decoded_size(2), 0); + assert_int_equal(az_base64_get_max_decoded_size(3), 0); + assert_int_equal(az_base64_get_max_decoded_size(4), 3); + assert_int_equal(az_base64_get_max_decoded_size(5), 3); + assert_int_equal(az_base64_get_max_decoded_size(30), 21); + assert_int_equal(az_base64_get_max_decoded_size(40), 30); + assert_int_equal(az_base64_get_max_decoded_size(1610612733), 1207959549); + assert_int_equal(az_base64_get_max_decoded_size(INT32_MAX - 4), 1610612730); + assert_int_equal(az_base64_get_max_decoded_size(INT32_MAX - 3), 1610612733); + assert_int_equal(az_base64_get_max_decoded_size(INT32_MAX - 2), 1610612733); + assert_int_equal(az_base64_get_max_decoded_size(INT32_MAX - 1), 1610612733); + assert_int_equal(az_base64_get_max_decoded_size(INT32_MAX), 1610612733); +} + +static void _az_base64_encode_test_helper( + int32_t input_length, + const char* expected, + int32_t expected_length) +{ + uint8_t input_buffer[7]; + for (int i = 0; i < input_length; i++) + { + input_buffer[i] = (uint8_t)(i + 1); + } + + az_span source = AZ_SPAN_FROM_BUFFER(input_buffer); + + uint8_t destination_buffer[12]; + az_span destination = AZ_SPAN_FROM_BUFFER(destination_buffer); + + int32_t bytes_written = 0; + assert_true(az_result_succeeded( + az_base64_encode(destination, az_span_slice(source, 0, input_length), &bytes_written))); + assert_int_equal(bytes_written, expected_length); + + char actual[13]; + az_span_to_str(actual, 13, az_span_slice(destination, 0, bytes_written)); + assert_string_equal(actual, expected); +} + +static void az_base64_encode_test(void** state) +{ + (void)state; + + uint8_t input_buffer[1]; + input_buffer[0] = 0; + az_span source = AZ_SPAN_FROM_BUFFER(input_buffer); + + uint8_t destination_buffer[4]; + az_span destination = AZ_SPAN_FROM_BUFFER(destination_buffer); + + int32_t bytes_written = 0; + assert_true(az_result_succeeded(az_base64_encode(destination, source, &bytes_written))); + assert_int_equal(bytes_written, 4); + + char actual[5]; + az_span_to_str(actual, 5, destination); + assert_string_equal(actual, "AA=="); + + _az_base64_encode_test_helper(1, "AQ==", 4); + _az_base64_encode_test_helper(2, "AQI=", 4); + _az_base64_encode_test_helper(3, "AQID", 4); + _az_base64_encode_test_helper(4, "AQIDBA==", 8); + _az_base64_encode_test_helper(5, "AQIDBAU=", 8); + _az_base64_encode_test_helper(6, "AQIDBAUG", 8); + _az_base64_encode_test_helper(7, "AQIDBAUGBw==", 12); +} + +static void az_base64_encode_destination_small_test(void** state) +{ + (void)state; + + uint8_t input_buffer[10] = { 23, 51, 61, 250, 131, 184, 127, 228, 250, 66 }; + az_span source = AZ_SPAN_FROM_BUFFER(input_buffer); + + uint8_t destination_buffer[16]; + az_span destination = AZ_SPAN_FROM_BUFFER(destination_buffer); + + int32_t bytes_written = 0; + assert_int_equal( + az_base64_encode(az_span_slice(destination, 0, 4), source, &bytes_written), + AZ_ERROR_NOT_ENOUGH_SPACE); + assert_int_equal(bytes_written, 0); + + assert_int_equal( + az_base64_encode(az_span_slice(destination, 0, 14), source, &bytes_written), + AZ_ERROR_NOT_ENOUGH_SPACE); + assert_int_equal(bytes_written, 0); + + assert_int_equal( + az_base64_encode(az_span_slice(destination, 0, 15), source, &bytes_written), + AZ_ERROR_NOT_ENOUGH_SPACE); + assert_int_equal(bytes_written, 0); + + assert_true(az_result_succeeded(az_base64_encode(destination, source, &bytes_written))); + assert_int_equal(bytes_written, 16); + + char actual[17]; + az_span_to_str(actual, 17, destination); + assert_string_equal(actual, "FzM9+oO4f+T6Qg=="); +} + +static void _az_base64_decode_test_helper(az_span source, az_span expected) +{ + uint8_t destination_buffer[7]; + az_span destination = AZ_SPAN_FROM_BUFFER(destination_buffer); + + int32_t bytes_written = 0; + assert_true(az_result_succeeded(az_base64_decode(destination, source, &bytes_written))); + assert_int_equal(bytes_written, az_span_size(expected)); + + assert_true(az_span_is_content_equal(az_span_slice(destination, 0, bytes_written), expected)); +} + +static void az_base64_decode_test(void** state) +{ + (void)state; + + az_span source = AZ_SPAN_FROM_STR("AA=="); + + uint8_t destination_buffer[1]; + az_span destination = AZ_SPAN_FROM_BUFFER(destination_buffer); + + int32_t bytes_written = 0; + assert_true(az_result_succeeded(az_base64_decode(destination, source, &bytes_written))); + assert_int_equal(bytes_written, 1); + + uint8_t input_buffer[1] = { 0 }; + az_span expected = AZ_SPAN_FROM_BUFFER(input_buffer); + assert_true(az_span_is_content_equal(destination, expected)); + + uint8_t expected_buffer1[1] = { 1 }; + _az_base64_decode_test_helper(AZ_SPAN_FROM_STR("AQ=="), AZ_SPAN_FROM_BUFFER(expected_buffer1)); + uint8_t expected_buffer2[2] = { 1, 2 }; + _az_base64_decode_test_helper(AZ_SPAN_FROM_STR("AQI="), AZ_SPAN_FROM_BUFFER(expected_buffer2)); + uint8_t expected_buffer3[3] = { 1, 2, 3 }; + _az_base64_decode_test_helper(AZ_SPAN_FROM_STR("AQID"), AZ_SPAN_FROM_BUFFER(expected_buffer3)); + uint8_t expected_buffer4[4] = { 1, 2, 3, 4 }; + _az_base64_decode_test_helper( + AZ_SPAN_FROM_STR("AQIDBA=="), AZ_SPAN_FROM_BUFFER(expected_buffer4)); + uint8_t expected_buffer5[5] = { 1, 2, 3, 4, 5 }; + _az_base64_decode_test_helper( + AZ_SPAN_FROM_STR("AQIDBAU="), AZ_SPAN_FROM_BUFFER(expected_buffer5)); + uint8_t expected_buffer6[6] = { 1, 2, 3, 4, 5, 6 }; + _az_base64_decode_test_helper( + AZ_SPAN_FROM_STR("AQIDBAUG"), AZ_SPAN_FROM_BUFFER(expected_buffer6)); + uint8_t expected_buffer7[7] = { 1, 2, 3, 4, 5, 6, 7 }; + _az_base64_decode_test_helper( + AZ_SPAN_FROM_STR("AQIDBAUGBw=="), AZ_SPAN_FROM_BUFFER(expected_buffer7)); +} + +static void az_base64_decode_destination_small_test(void** state) +{ + (void)state; + + uint8_t expected_buffer[10] = { 23, 51, 61, 250, 131, 184, 127, 228, 250, 66 }; + az_span source = AZ_SPAN_FROM_STR("FzM9+oO4f+T6Qg=="); + + uint8_t destination_buffer[10]; + az_span destination = AZ_SPAN_FROM_BUFFER(destination_buffer); + + int32_t bytes_written = 0; + assert_int_equal( + az_base64_decode(az_span_slice(destination, 0, 4), source, &bytes_written), + AZ_ERROR_NOT_ENOUGH_SPACE); + assert_int_equal(bytes_written, 0); + + assert_int_equal( + az_base64_decode(az_span_slice(destination, 0, 8), source, &bytes_written), + AZ_ERROR_NOT_ENOUGH_SPACE); + assert_int_equal(bytes_written, 0); + + assert_int_equal( + az_base64_decode(az_span_slice(destination, 0, 9), source, &bytes_written), + AZ_ERROR_NOT_ENOUGH_SPACE); + assert_int_equal(bytes_written, 0); + + assert_true(az_result_succeeded(az_base64_decode(destination, source, &bytes_written))); + assert_int_equal(bytes_written, 10); + + assert_true(az_span_is_content_equal(destination, AZ_SPAN_FROM_BUFFER(expected_buffer))); + + source = AZ_SPAN_FROM_STR("AQI="); + assert_int_equal( + az_base64_decode(az_span_slice(destination, 0, 1), source, &bytes_written), + AZ_ERROR_NOT_ENOUGH_SPACE); + assert_int_equal(bytes_written, 10); + + source = AZ_SPAN_FROM_STR("AQID"); + assert_int_equal( + az_base64_decode(az_span_slice(destination, 0, 2), source, &bytes_written), + AZ_ERROR_NOT_ENOUGH_SPACE); + assert_int_equal(bytes_written, 10); + + source = AZ_SPAN_FROM_STR("AQIDBA=="); + assert_int_equal( + az_base64_decode(az_span_slice(destination, 0, 3), source, &bytes_written), + AZ_ERROR_NOT_ENOUGH_SPACE); + assert_int_equal(bytes_written, 10); +} + +static void az_base64_decode_source_small_test(void** state) +{ + (void)state; + + uint8_t destination_buffer[10]; + az_span destination = AZ_SPAN_FROM_BUFFER(destination_buffer); + + int32_t bytes_written = 0; + + assert_int_equal( + az_base64_decode(destination, AZ_SPAN_FROM_STR("AQIDB"), &bytes_written), + AZ_ERROR_UNEXPECTED_END); + assert_int_equal(bytes_written, 0); + + assert_int_equal( + az_base64_decode(destination, AZ_SPAN_FROM_STR("AQIDBA"), &bytes_written), + AZ_ERROR_UNEXPECTED_END); + assert_int_equal(bytes_written, 0); + + assert_int_equal( + az_base64_decode(destination, AZ_SPAN_FROM_STR("AQIDBA="), &bytes_written), + AZ_ERROR_UNEXPECTED_END); + assert_int_equal(bytes_written, 0); + + assert_int_equal( + az_base64_decode(destination, AZ_SPAN_FROM_STR("AQIDBAU"), &bytes_written), + AZ_ERROR_UNEXPECTED_END); + assert_int_equal(bytes_written, 0); +} + +static void az_base64_decode_invalid_test(void** state) +{ + (void)state; + + // Invalid Bytes: + // 0-42 + // 44-46 + // 58-64 + // 91-96 + // 123-255 + + uint8_t destination_buffer[20]; + az_span destination = AZ_SPAN_FROM_BUFFER(destination_buffer); + + int32_t bytes_written = 0; + + assert_int_equal( + az_base64_decode(destination, AZ_SPAN_FROM_STR("A---"), &bytes_written), + AZ_ERROR_UNEXPECTED_CHAR); + assert_int_equal(bytes_written, 0); + + assert_int_equal( + az_base64_decode(destination, AZ_SPAN_FROM_STR("A-=="), &bytes_written), + AZ_ERROR_UNEXPECTED_CHAR); + assert_int_equal(bytes_written, 0); + + assert_int_equal( + az_base64_decode(destination, AZ_SPAN_FROM_STR("A!B="), &bytes_written), + AZ_ERROR_UNEXPECTED_CHAR); + assert_int_equal(bytes_written, 0); + + assert_int_equal( + az_base64_decode(destination, AZ_SPAN_FROM_STR("A:BC"), &bytes_written), + AZ_ERROR_UNEXPECTED_CHAR); + assert_int_equal(bytes_written, 0); + + assert_int_equal( + az_base64_decode(destination, AZ_SPAN_FROM_STR("AQ-_"), &bytes_written), + AZ_ERROR_UNEXPECTED_CHAR); + assert_int_equal(bytes_written, 0); + + assert_int_equal( + az_base64_decode(destination, AZ_SPAN_FROM_STR("AQ=_"), &bytes_written), + AZ_ERROR_UNEXPECTED_CHAR); + assert_int_equal(bytes_written, 0); + + assert_int_equal( + az_base64_decode(destination, AZ_SPAN_FROM_STR("AQI_"), &bytes_written), + AZ_ERROR_UNEXPECTED_CHAR); + assert_int_equal(bytes_written, 0); + + assert_int_equal( + az_base64_decode(destination, AZ_SPAN_FROM_STR("AQIDB..."), &bytes_written), + AZ_ERROR_UNEXPECTED_CHAR); + assert_int_equal(bytes_written, 0); + + assert_int_equal( + az_base64_decode(destination, AZ_SPAN_FROM_STR("AQIDBA.."), &bytes_written), + AZ_ERROR_UNEXPECTED_CHAR); + assert_int_equal(bytes_written, 0); + + assert_int_equal( + az_base64_decode(destination, AZ_SPAN_FROM_STR("AQIDBA=|"), &bytes_written), + AZ_ERROR_UNEXPECTED_CHAR); + assert_int_equal(bytes_written, 0); + + assert_int_equal( + az_base64_decode(destination, AZ_SPAN_FROM_STR("AQIDBAU?"), &bytes_written), + AZ_ERROR_UNEXPECTED_CHAR); + assert_int_equal(bytes_written, 0); + + assert_int_equal( + az_base64_decode(destination, AZ_SPAN_FROM_STR("FzM9+oO4f+T6Qg==}}}}"), &bytes_written), + AZ_ERROR_UNEXPECTED_CHAR); + assert_int_equal(bytes_written, 0); + + assert_int_equal( + az_base64_decode(destination, AZ_SPAN_FROM_STR("\\zM9+oO4f+T6Qg=="), &bytes_written), + AZ_ERROR_UNEXPECTED_CHAR); + assert_int_equal(bytes_written, 0); +} + +int test_az_base64() +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(az_base64_max_encode_test), + cmocka_unit_test(az_base64_max_decode_test), + cmocka_unit_test(az_base64_encode_test), + cmocka_unit_test(az_base64_encode_destination_small_test), + cmocka_unit_test(az_base64_decode_test), + cmocka_unit_test(az_base64_decode_destination_small_test), + cmocka_unit_test(az_base64_decode_source_small_test), + cmocka_unit_test(az_base64_decode_invalid_test), + }; + return cmocka_run_group_tests_name("az_core_base64", tests, NULL, NULL); +} From 03c760855052fb70ef8ed6f49e2da95d73868bf4 Mon Sep 17 00:00:00 2001 From: Ahson Khan Date: Wed, 7 Jul 2021 20:49:35 -0700 Subject: [PATCH 2/3] Add entry to the changelog. --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c22a7d7b6..54d2861bab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,8 @@ ### New Features -- Add a current depth field to the JSON reader. +- Added a current depth field to the JSON reader. +- Added base64 encoding and decoding APIs that accept `az_span`, available from the `azure/core/az_base64.h` header. ### Bug Fixes From 07e6ce9d2d22561a645299f9eb103fa78f1ef299 Mon Sep 17 00:00:00 2001 From: Ahson Khan Date: Thu, 8 Jul 2021 14:18:47 -0700 Subject: [PATCH 3/3] Address PR feedback - move const to r.h.s. --- sdk/src/azure/core/az_base64.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/src/azure/core/az_base64.c b/sdk/src/azure/core/az_base64.c index 5c34013dbd..4c7587774c 100644 --- a/sdk/src/azure/core/az_base64.c +++ b/sdk/src/azure/core/az_base64.c @@ -12,10 +12,10 @@ #define _az_ENCODING_PAD '=' -static const char _az_base64_encode_array[65] +static char const _az_base64_encode_array[65] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; -static const int8_t _az_base64_decode_array[256] = { +static int8_t const _az_base64_decode_array[256] = { -1, -1, -1,