Finish first draft of this library

* Implement decode function with tests
* Write doxygen headers
* Fix ifdef guards in include file
* Fix test output formatting
This commit is contained in:
Mario Hüttel 2020-11-02 23:42:55 +01:00
parent 5aa063cafd
commit b9cd9198d7
4 changed files with 231 additions and 1 deletions

View File

@ -1,7 +1,56 @@
#ifndef _BASE64_LIB_H_
#define _BASE64_LIB_H_
#include <stddef.h>
/**
* @defgroup base64-lib Base64 Encoding / Decoding Library
* @addtogroup base64-lib
* @{
*/
/**
* @brief Encode a byte sequence to base 64
* @param src The source sequence
* @param dest The destination to write the base64 encoded data to.
* @param src_size Source size
* @param dest_size Available destination space
* @param output_length Actual count of written data
* @note The output is not null-terminated
* @return 0 if successful, -1000 if parameter error, -1 if destination too small,
*/
int base64_encode(const char *src, char *dest, size_t src_size, size_t dest_size, size_t *output_length);
/**
* @brief Calcualte encoded size of \p unencoded_size amount of unencoded bytes
* @param unencoded_size Unencoded size
* @return Encoded size
*/
size_t base64_calculate_encoded_size(size_t unencoded_size);
/**
* @brief Calculate the maximum decoded size.
*
* The true size may be up to 2 bytes smaller, due to possible padding.
*
* @param encoded_size Encoded size
* @return Decoded size
*/
size_t base64_calculate_maximum_decoded_size(size_t encoded_size);
/**
* @brief Decode a base64 encoded string
* @param base64_src Source data
* @param dest Destination buffer
* @param src_size Source size
* @param dest_size Destination size
* @param output_written Actual number of written bytes
* @return 0 if successful, -1 if input length does not match a base64 string,
* -2 if destination size is too small, -3 and -4 if a malformed character
* is found.
*/
int base64_decode(const char *base64_src, char *dest, size_t src_size, size_t dest_size, size_t *output_written);
/** @} */
#endif /* _BASE64_LIB_H_ */

View File

@ -11,6 +11,7 @@
static bool base64_lib_initialized = false;
static char LOOKUP_SECTION_ATTR base64_encode_table[64];
static char LOOKUP_SECTION_ATTR base64_decode_table[256];
static void initialize_encode_lookup_table(void)
{
@ -27,12 +28,23 @@ static void initialize_encode_lookup_table(void)
base64_encode_table[63] = '/';
}
static void initialize_decode_lookup_table(void)
{
uint8_t i;
for (i = 0; i < 64; i++) {
base64_decode_table[(unsigned int)base64_encode_table[i]] = (char)i;
}
base64_decode_table[(unsigned int)'='] = '\0';
}
static void base64_init(void)
{
if (base64_lib_initialized)
return;
initialize_encode_lookup_table();
initialize_decode_lookup_table();
base64_lib_initialized = true;
}
@ -103,3 +115,119 @@ size_t base64_calculate_maximum_decoded_size(size_t encoded_size)
return 0;
return encoded_size / 4 * 3;
}
/**
* @brief This function checks, if a char is a valid character for base64 encoded data
* @note This function treats the padding '=' as an invalid character
*/
static bool is_valid_base64_character(char c)
{
bool ret;
switch (c) {
case 'A' ... 'Z': /* FALLTHRU */
case 'a' ... 'z': /* FALLTHRU */
case '0' ... '9': /* FALLTHRU */
case '+': /* FALLTHRU */
case '/':
ret = true;
break;
default:
ret = false;
break;
}
return ret;
}
static inline uint32_t base64_chars_to_three_bytes(char a, char b, char c, char d)
{
return ((((uint32_t)base64_decode_table[(unsigned int)a] & 0x3F) << 18) ) |
((((uint32_t)base64_decode_table[(unsigned int)b] & 0x3F) << 12)) |
((((uint32_t)base64_decode_table[(unsigned int)c] & 0x3F) << 6)) |
((((uint32_t)base64_decode_table[(unsigned int)d] & 0x3F)));
}
int base64_decode(const char *base64_src, char *dest, size_t src_size, size_t dest_size, size_t *output_written)
{
size_t output_len;
size_t idx;
char first, second, third, fourth;
uint32_t three_bytes;
bool padded = false;
size_t written = 0;
int ret = 0;
if (!base64_src || !dest || !src_size || !dest_size)
return -1000;
output_len = base64_calculate_maximum_decoded_size(src_size);
if (output_len == 0) {
ret = -1;
goto exit;
}
base64_init();
/* Check paddings and correct output length */
if (base64_src[src_size-1] == '=') {
padded = true;
output_len--;
}
if (base64_src[src_size-2] == '=') {
output_len--;
padded = true;
}
if (dest_size < output_len) {
ret = -2;
goto exit;
}
/* Convert all full quartetts */
for (idx = 0; idx < src_size - (padded ? 4 : 0);) {
/* Due to the call to base64_calculate_maximum_decoded_size(), it is guaranteed, that src_size is
* a multiple of four characters */
first = base64_src[idx++];
second = base64_src[idx++];
third = base64_src[idx++];
fourth = base64_src[idx++];
if (is_valid_base64_character(first) &&
is_valid_base64_character(second) &&
is_valid_base64_character(third) &&
is_valid_base64_character(fourth)) {
three_bytes = base64_chars_to_three_bytes(first, second, third, fourth);
dest[written++] = (three_bytes >> 16) & 0xFF;
dest[written++] = (three_bytes >> 8) & 0xFF;
dest[written++] = (three_bytes) & 0xFF;
} else {
ret = -3;
goto exit;
}
}
if (padded) {
first = base64_src[idx++];
second = base64_src[idx++];
third = base64_src[idx++];
fourth = base64_src[idx++];
if (!is_valid_base64_character(first) || !is_valid_base64_character(second)) {
ret = -4;
goto exit;
}
three_bytes = base64_chars_to_three_bytes(first, second, third, fourth);
dest[written++] = (three_bytes >> 16) & 0xFF;
if (third != '=')
dest[written++] = (three_bytes >> 8) & 0xFF;
if (fourth != '=')
dest[written++] = (three_bytes) & 0xFF;
}
exit:
if (output_written)
*output_written = written;
return ret;
}

View File

@ -1,6 +1,6 @@
project(base64-test)
add_custom_target("execute-${PROJECT_NAME}" "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}" "-r compact" "-s" DEPENDS ${PROJECT_NAME})
add_custom_target("execute-${PROJECT_NAME}" "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}" DEPENDS ${PROJECT_NAME})
include_directories("${CMAKE_CURRENT_SOURCE_DIR}/catch-framework")
aux_source_directory("src" TEST_SOURCES)

View File

@ -73,3 +73,56 @@ TEST_CASE("base64/base64_calculate_maximum_decoded_size", "[DECODE]")
REQUIRE(base64_calculate_maximum_decoded_size(4) == 3);
REQUIRE(base64_calculate_maximum_decoded_size(160) == 120);
}
TEST_CASE("base64/base64_decode", "[DECODE]")
{
char decoded[2048];
size_t written;
int ret;
ret = base64_decode(base64_reference.c_str(), decoded, base64_reference.length(), lorem_ipsum.length(), &written);
REQUIRE(ret == 0);
REQUIRE(written == lorem_ipsum.length());
if (!ret) {
decoded[written] = 0;
REQUIRE(std::string(decoded) == lorem_ipsum);
}
}
TEST_CASE("base64/base64_decode/error_src_to_short", "[DECODE]")
{
char decoded[2048];
size_t written;
int ret;
ret = base64_decode(base64_reference.c_str(), decoded, base64_reference.length() -1, sizeof(decoded), &written);
REQUIRE(ret == -1);
REQUIRE(written == 0);
}
TEST_CASE("base64/base64_decode/error_dest_to_short", "[DECODE]")
{
char decoded[2048];
size_t written;
int ret;
ret = base64_decode(base64_reference.c_str(), decoded, base64_reference.length(), 12, &written);
REQUIRE(ret == -2);
REQUIRE(written == 0);
}
TEST_CASE("base64/base64_decode/invalid_char", "[DECODE]")
{
char decoded[2048];
size_t written;
int ret;
std::string ref = base64_reference;
ref[12] = '!';
ret = base64_decode(ref.c_str(), decoded, ref.length(), 2048, &written);
REQUIRE(ret == -3);
REQUIRE(written == 9);
}