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:
parent
5aa063cafd
commit
b9cd9198d7
@ -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_ */
|
||||
|
128
src/base64-lib.c
128
src/base64-lib.c
@ -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;
|
||||
}
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user