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>
|
#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);
|
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);
|
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);
|
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 bool base64_lib_initialized = false;
|
||||||
|
|
||||||
static char LOOKUP_SECTION_ATTR base64_encode_table[64];
|
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)
|
static void initialize_encode_lookup_table(void)
|
||||||
{
|
{
|
||||||
@ -27,12 +28,23 @@ static void initialize_encode_lookup_table(void)
|
|||||||
base64_encode_table[63] = '/';
|
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)
|
static void base64_init(void)
|
||||||
{
|
{
|
||||||
if (base64_lib_initialized)
|
if (base64_lib_initialized)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
initialize_encode_lookup_table();
|
initialize_encode_lookup_table();
|
||||||
|
initialize_decode_lookup_table();
|
||||||
|
|
||||||
base64_lib_initialized = true;
|
base64_lib_initialized = true;
|
||||||
}
|
}
|
||||||
@ -103,3 +115,119 @@ size_t base64_calculate_maximum_decoded_size(size_t encoded_size)
|
|||||||
return 0;
|
return 0;
|
||||||
return encoded_size / 4 * 3;
|
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)
|
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")
|
include_directories("${CMAKE_CURRENT_SOURCE_DIR}/catch-framework")
|
||||||
aux_source_directory("src" TEST_SOURCES)
|
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(4) == 3);
|
||||||
REQUIRE(base64_calculate_maximum_decoded_size(160) == 120);
|
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