Browse Source

sys: add base64 encoding/decoding module

- suppressed cppcheck warning for `unassignedVariable` for
   the encode/decode arrays in stream encode/decode tests
   as these arrays are assigned by the according `base64_[en|de]code()` function.
dev/timer
Martin Landsmann 9 years ago committed by BytesGalore
parent
commit
a0898dbb9f
  1. 1
      sys/base64/Makefile
  2. 219
      sys/base64/base64.c
  3. 82
      sys/include/base64.h
  4. 3
      tests/unittests/tests-base64/Makefile
  5. 1
      tests/unittests/tests-base64/Makefile.include
  6. 324
      tests/unittests/tests-base64/tests-base64.c
  7. 43
      tests/unittests/tests-base64/tests-base64.h

1
sys/base64/Makefile

@ -0,0 +1 @@
include $(RIOTBASE)/Makefile.base

219
sys/base64/base64.c

@ -0,0 +1,219 @@
/*
* Copyright (C) 2014 Hochschule für Angewandte Wissenschaften Hamburg (HAW)
* Copyright (C) 2014 Martin Landsmann <Martin.Landsmann@HAW-Hamburg.de>
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup base64
* @{
* @file base64.c
* @brief Functions to encode and decode base64
*
* @author Martin Landsmann <Martin.Landsmann@HAW-Hamburg.de>
* @}
*
*/
#include "base64.h"
#define BASE64_CAPITAL_UPPER_BOUND (25) /**< base64 'Z' */
#define BASE64_SMALL_UPPER_BOUND (51) /**< base64 'z' */
#define BASE64_NUMBER_UPPER_BOUND (61) /**< base64 '9' */
#define BASE64_PLUS (62) /**< base64 '+' */
#define BASE64_SLASH (63) /**< base64 '/' */
#define BASE64_EQUALS (0xFE) /**< no base64 symbol '=' */
#define BASE64_NOT_DEFINED (0xFF) /**< no base64 symbol */
/*
* returns the corresponding ascii symbol value for the given base64 code
*/
static char getsymbol(unsigned char code)
{
if (code == BASE64_SLASH) {
return '/';
}
if (code == BASE64_PLUS) {
return '+';
}
if (code <= BASE64_CAPITAL_UPPER_BOUND) {
return (code + 'A');
}
if (code <= BASE64_SMALL_UPPER_BOUND) {
return (code + ('z' - BASE64_SMALL_UPPER_BOUND));
}
if (code <= BASE64_NUMBER_UPPER_BOUND) {
return (code + ('9' - BASE64_NUMBER_UPPER_BOUND));
}
return (char)BASE64_NOT_DEFINED;
}
int base64_encode(unsigned char *data_in, size_t data_in_size, \
unsigned char *base64_out, size_t *base64_out_size)
{
size_t padding_bytes = ((data_in_size % 3) ? (3 - (data_in_size % 3)) : 0);
size_t required_size = (4 * (data_in_size + 2 - ((data_in_size + 2) % 3)) / 3) + padding_bytes;
if (data_in == NULL) {
return BASE64_ERROR_DATA_IN;
}
if (data_in_size < 1) {
return BASE64_ERROR_DATA_IN_SIZE;
}
if (*base64_out_size < required_size) {
*base64_out_size = required_size;
return BASE64_ERROR_BUFFER_OUT_SIZE;
}
if (base64_out == NULL) {
return BASE64_ERROR_BUFFER_OUT;
}
int iterate_base64_buffer = 0;
unsigned char nNum = 0;
int nLst = 0;
int njump = 0;
unsigned char tmpval;
for (int i = 0; i < (int)(data_in_size); ++i) {
njump++;
tmpval = *(data_in + i);
nNum = (tmpval >> (2 * njump));
if (njump == 4) {
nNum = nLst << (8 - 2 * njump);
njump = 0;
nLst = 0;
--i;
}
else {
nNum += nLst << (8 - 2 * njump);
nLst = tmpval & ((1 << njump * 2) - 1);
}
base64_out[iterate_base64_buffer++] = getsymbol(nNum);
}
/* The last character is not finished yet */
njump++;
if (njump == 4) {
nNum = (tmpval >> (2 * njump));
}
nNum = nLst << (8 - 2 * njump);
base64_out[iterate_base64_buffer++] = getsymbol(nNum);
/* if required we append '=' for the required dividability */
while (iterate_base64_buffer % 4) {
base64_out[iterate_base64_buffer++] = '=';
}
*base64_out_size = iterate_base64_buffer;
return BASE64_SUCCESS;
}
/*
* returns the corresponding base64 code for the given ascii symbol
*/
static int getcode(char symbol)
{
if (symbol == '/') {
return BASE64_SLASH;
}
if (symbol == '+') {
return BASE64_PLUS;
}
if (symbol == '=') {
/* indicates a padded base64 end */
return BASE64_EQUALS;
}
if (symbol < '0') {
/* indicates that the given symbol is not base64 and should be ignored */
return BASE64_NOT_DEFINED;
}
if (symbol <= '9' && symbol >= '0') {
return (symbol + (BASE64_NUMBER_UPPER_BOUND - '9'));
}
if (symbol <= 'Z' && symbol >= 'A') {
return (symbol - 'A');
}
if (symbol <= 'z' && symbol >= 'a') {
return (symbol + (BASE64_SMALL_UPPER_BOUND - 'z'));
}
/* indicates that the given symbol is not base64 and should be ignored */
return BASE64_NOT_DEFINED;
}
int base64_decode(unsigned char *base64_in, size_t base64_in_size, \
unsigned char *data_out, size_t *data_out_size)
{
size_t required_size = ((base64_in_size / 4) * 3);
if (base64_in == NULL) {
return BASE64_ERROR_DATA_IN;
}
if (base64_in_size < 4) {
return BASE64_ERROR_DATA_IN_SIZE;
}
if (*data_out_size < required_size) {
*data_out_size = required_size;
return BASE64_ERROR_BUFFER_OUT_SIZE;
}
if (data_out == NULL) {
return BASE64_ERROR_BUFFER_OUT;
}
int iterate_data_buffer = 0;
unsigned char nNum = 0;
int nLst = getcode(base64_in[0]) << 2;
int code = 0;
int mask = 2;
for (int i = 1; i < (int)(base64_in_size); i++) {
code = getcode(base64_in[i]);
if (code == BASE64_NOT_DEFINED || code == BASE64_EQUALS) {
continue;
}
int nm = (0xFF << (2 * mask));
nNum = nLst + ((code & (0xFF & nm)) >> (2 * mask));
nLst = (code & (0xFF & ~nm)) << (8 - (2 * mask));
(mask != 3) ? data_out[iterate_data_buffer++] = nNum : nNum;
(mask == 0) ? mask = 3 : mask--;
}
if (code == BASE64_EQUALS) {
/* add the last character to the data_out buffer */
data_out[iterate_data_buffer] = nNum;
}
*data_out_size = iterate_data_buffer;
return BASE64_SUCCESS;
}

82
sys/include/base64.h

@ -0,0 +1,82 @@
/*
* Copyright (C) 2014 Hochschule für Angewandte Wissenschaften Hamburg (HAW)
* Copyright (C) 2014 Martin Landsmann <Martin.Landsmann@HAW-Hamburg.de>
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @defgroup sys_base64 base64 encoder decoder
* @ingroup sys
* @brief base64 encoder and decoder
* @{
*
* @brief encoding and decoding functions for base64
* @author Martin Landsmann <Martin.Landsmann@HAW-Hamburg.de>
*/
#ifndef _BASE64_ENCODER_DECODER_H
#define _BASE64_ENCODER_DECODER_H
#include <stdlib.h>
#ifdef __cplusplus
extern "C" {
#endif
#define BASE64_SUCCESS (0) /**< return value for success */
#define BASE64_ERROR_BUFFER_OUT (-1) /**< error value for invalid output buffer pointer */
#define BASE64_ERROR_BUFFER_OUT_SIZE (-2) /**< error value for invalid output buffer size */
#define BASE64_ERROR_DATA_IN (-3) /**< error value for invalid input buffer */
#define BASE64_ERROR_DATA_IN_SIZE (-4) /**< error value for invalid output buffer size */
/**
* @brief Encodes a given datum to base64 and save the result to the given destination.
* @param[in] data_in pointer to the datum to encode
* @param[in] data_in_size the size of `data_in`
* @param[out] base64_out pointer to store the encoded base64 string
* @param[in,out] base64_out_size pointer to the variable containing the size of `base64_out.`
This value is overwritten with the estimated size used for
the encoded base64 string on BASE64_ERROR_BUFFER_OUT_SIZE.
This value is overwritten with the actual used size for the
encoded base64 string on BASE64_SUCCESS.
* @returns BASE64_SUCCESS on success,
BASE64_ERROR_BUFFER_OUT_SIZE on insufficient size for encoding to `base64_out`,
BASE64_ERROR_BUFFER_OUT if `base64_out` equals NULL
but the `base64_out_size` is sufficient,
BASE64_ERROR_DATA_IN if `data_in` equals NULL,
BASE64_ERROR_DATA_IN_SIZE if `data_in_size` is less then 1.
*/
int base64_encode(unsigned char *data_in, size_t data_in_size, \
unsigned char *base64_out, size_t *base64_out_size);
/**
* @brief Decodes a given base64 string and save the result to the given destination.
* @param[out] base64_in pointer to store the encoded base64 string
* @param[in] base64_in_size pointer to the variable containing the size of `base64_out.`
* @param[in] data_out pointer to the datum to encode
* @param[in, out] data_out_size the size of `data_out`.
This value is overwritten with the estimated size used for the
decoded string on BASE64_ERROR_BUFFER_OUT_SIZE.
This value is overwritten with the actual used size for the
decoded string on BASE64_SUCCESS.
* @returns BASE64_SUCCESS on success,
BASE64_ERROR_BUFFER_OUT_SIZE on insufficient size for decoding to `data_out`,
BASE64_ERROR_BUFFER_OUT if `data_out` equals NULL
but the size for `data_out_size` is sufficient,
BASE64_ERROR_DATA_IN if `base64_in` equals NULL,
BASE64_ERROR_DATA_IN_SIZE if `base64_in_size` is less then 4.
*/
int base64_decode(unsigned char *base64_in, size_t base64_in_size, \
unsigned char *data_out, size_t *data_out_size);
#ifdef __cplusplus
}
#endif
/** @} */
#endif /* _BASE64_ENCODER_DECODER_H */

3
tests/unittests/tests-base64/Makefile

@ -0,0 +1,3 @@
MODULE = tests-base64
include $(RIOTBASE)/Makefile.base

1
tests/unittests/tests-base64/Makefile.include

@ -0,0 +1 @@
USEMODULE += base64

324
tests/unittests/tests-base64/tests-base64.c

@ -0,0 +1,324 @@
/*
* Copyright (C) 2015 Martin Landsmann
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
#define TEST_BASE64_SHOW_OUTPUT (0) /**< set if encoded/decoded string is displayed */
#if (TEST_BASE64_SHOW_OUTPUT == 1)
#include <stdio.h>
#endif
#include <string.h>
#include "embUnit.h"
#include "tests-base64.h"
#include "base64.h"
static void test_base64_01_encode_string(void)
{
unsigned char data_in[] = "Hello RIOT this is a base64 test!\n"
"This should work as intended.";
unsigned char expected_encoding[] = "SGVsbG8gUklPVCB0aGlzIGlzIGEgYmFzZTY0IHR"
"lc3QhClRoaXMgc2hvdWxkIHdvcmsgYXMgaW50ZW5kZWQu";
size_t data_in_size = strlen((char *)data_in);
size_t base64_out_size = 0;
unsigned char base64_out[ strlen((char *)expected_encoding) ];
/*
* @Note:
* The first encoding attempt fails, but reveals the required out size.
*
* This size is a lower bound estimation,
* thus it can require few more bytes then the actual used size for the output.
*/
int ret = base64_encode(data_in, data_in_size, NULL, &base64_out_size);
TEST_ASSERT_EQUAL_INT(BASE64_ERROR_BUFFER_OUT_SIZE, ret);
ret = base64_encode(data_in, data_in_size, base64_out, &base64_out_size);
TEST_ASSERT_EQUAL_INT(BASE64_SUCCESS, ret);
for (int i = 0; i < (int)base64_out_size; ++i) {
TEST_ASSERT_MESSAGE(base64_out[i] == expected_encoding[i], \
"encoding failed!(produced unexpected output)");
}
#if (TEST_BASE64_SHOW_OUTPUT == 1)
puts("Test 01 Encoded:");
for (int i = 0; i < (int)base64_out_size; ++i) {
printf("%c", base64_out[i]);
}
printf("\nFrom:\n%s\n", (char *)data_in);
#endif
}
static void test_base64_02_decode_base64(void)
{
unsigned char encoded_base64[] = "SGVsbG8gUklPVCB0aGlzIGlzIGEgYmFzZTY0IHRlc3Q"
"hClRoaXMgc2hvdWxkIHdvcmsgYXMgaW50ZW5kZWQu";
unsigned char expected_string[] = "Hello RIOT this is a base64 test!\n"
"This should work as intended.";
size_t base64_size = strlen((char *)encoded_base64);
size_t data_out_size = 0;
unsigned char data_out[ strlen((char *)expected_string) ];
int ret = base64_decode(encoded_base64, base64_size, NULL, &data_out_size);
TEST_ASSERT_EQUAL_INT(BASE64_ERROR_BUFFER_OUT_SIZE, ret);
ret = base64_decode(encoded_base64, base64_size, NULL, &data_out_size);
TEST_ASSERT_EQUAL_INT(BASE64_ERROR_BUFFER_OUT, ret);
ret = base64_decode(encoded_base64, base64_size, data_out, &data_out_size);
TEST_ASSERT_EQUAL_INT(BASE64_SUCCESS, ret);
for (int i = 0; i < (int)data_out_size; ++i) {
TEST_ASSERT_MESSAGE(data_out[i] == expected_string[i], \
"decoding failed!(produced unexpected output)");
}
#if (TEST_BASE64_SHOW_OUTPUT == 1)
puts("Test 02 Decoded:");
for (int i = 0; i < (int)data_out_size; ++i) {
printf("%c", data_out[i]);
}
printf("\nFrom:\n%s\n", (char *)encoded_base64);
#endif
}
static void test_base64_03_single_character(void)
{
size_t element_size = 1;
unsigned char element[] = "1";
size_t elementDecodeSize = 3;
unsigned char elementDecode[3];
size_t element_base64_out_size = 10;
unsigned char element_base64_out[10];
int ret = base64_encode(element, element_size, \
element_base64_out, &element_base64_out_size);
TEST_ASSERT_EQUAL_INT(BASE64_SUCCESS, ret);
ret = base64_decode(element_base64_out, element_base64_out_size, \
elementDecode, &elementDecodeSize);
TEST_ASSERT_EQUAL_INT(BASE64_SUCCESS, ret);
for (int i = 0; i < (int)elementDecodeSize; ++i) {
TEST_ASSERT_MESSAGE(element[i] == elementDecode[i], \
"decoding failed!(produced unexpected output)");
}
}
static void test_base64_04_free_conversion(void)
{
size_t elements = 255;
unsigned char elm[elements];
size_t elem_base64_out_size = 0;
unsigned char elm_base64_out[((elements / 3) * 4) + (elements / 10)];
size_t elem_base64_out_decoded_size = 0;
unsigned char elem_base64_out_decoded[ elements + 10 ];
/* fill some values */
for (int i = 0; i < (int)elements; ++i) {
elm[i] = i;
}
int ret = base64_encode(elm, elements, NULL, &elem_base64_out_size);
TEST_ASSERT_EQUAL_INT(BASE64_ERROR_BUFFER_OUT_SIZE, ret);
ret = base64_encode(elm, elements, elm_base64_out, &elem_base64_out_size);
TEST_ASSERT_EQUAL_INT(BASE64_SUCCESS, ret);
ret = base64_decode(elm_base64_out, elem_base64_out_size, \
NULL, &elem_base64_out_decoded_size);
TEST_ASSERT_EQUAL_INT(BASE64_ERROR_BUFFER_OUT_SIZE, ret);
ret = base64_decode(elm_base64_out, elem_base64_out_size, \
elem_base64_out_decoded, &elem_base64_out_decoded_size);
TEST_ASSERT_EQUAL_INT(BASE64_SUCCESS, ret);
for (int i = 0; i < (int)elements; ++i) {
TEST_ASSERT_MESSAGE(elem_base64_out_decoded[i] == elm[i], \
"decoding failed!(produced unexpected output)");
}
}
static void test_base64_05_decode_larger(void)
{
unsigned char decodeit[] = "SG93IG11Y2ggd29vZCB3b3VsZCBhIHdvb2RjaHVjayBjaHVj"
"awppZiBhIHdvb2RjaHVjayBjb3VsZCBjaHVjayB3b29kPwpI"
"ZSB3b3VsZCBjaHVjaywgaGUgd291bGQsIGFzIG11Y2ggYXMg"
"aGUgY291bGQsCmFuZCBjaHVjayBhcyBtdWNoIHdvb2QgYXMg"
"YSB3b29kY2h1Y2sgd291bGQKaWYgYSB3b29kY2h1Y2sgY291"
"bGQgY2h1Y2sgd29vZC4==";
size_t data_size = 199;
unsigned char data[data_size];
size_t decodeit_size = strlen((char *)decodeit);
int ret = base64_decode(decodeit, decodeit_size, data, &data_size);
TEST_ASSERT_EQUAL_INT(BASE64_SUCCESS, ret);
#if (TEST_BASE64_SHOW_OUTPUT == 1)
puts("Test 05 Decoded:");
for (int i = 0; i < (int)data_size; ++i) {
printf("%c", data[i]);
}
printf("\nFrom:\n%s\n", (char *)decodeit);
#endif
}
static void test_base64_06_stream_encode(void)
{
/*
* @Note:
* In this test we divide the `stream_encode[]` input for the encoding
* into several portions (chunks).
*
* Every chunk is encoded and appended to the base64 `encode_result[]`.
* To enable appending further, the chunks MUST be dividable by 3.
* Only the final chunk MAY be prime to 3. (it is in this test)
*
*/
unsigned char stream_encode[] = "Peter Piper picked a peck of pickled peppers."
"\nA peck of pickled peppers Peter Piper picked."
"\nIf Peter Piper picked a peck of pickled peppers,"
"\nWhere's the peck of pickled peppers Peter Piper picked?";
/* required output size +2 extra bytes */
size_t encoded_size = 264 + 2;
/* cppcheck-suppress unassignedVariable */
unsigned char encode_result[encoded_size];
/* the above array is used/assigned in base64_encode() using its pointer. */
int remain = strlen((char *)stream_encode);
int out_iter = 0;
int ret = BASE64_SUCCESS;
for (int i = 3; i < remain; (i += 3)) {
size_t size_used = encoded_size - out_iter;
ret = base64_encode(stream_encode + (strlen((char *)stream_encode) - remain), \
i, encode_result + out_iter, &size_used);
TEST_ASSERT_EQUAL_INT(BASE64_SUCCESS, ret);
out_iter += size_used;
remain -= i;
}
/* this final chunk we want to encode and append is prime to 3 */
size_t finish = encoded_size - out_iter;
ret = base64_encode(stream_encode + (strlen((char *)stream_encode) - remain), \
remain, encode_result + out_iter, &finish);
TEST_ASSERT_EQUAL_INT(BASE64_SUCCESS, ret);
#if (TEST_BASE64_SHOW_OUTPUT == 1)
out_iter += finish;
puts("Test 06 Encoded:");
for (int i = 0; i < (int)out_iter; ++i) {
printf("%c", encode_result[i]);
}
printf("\nFrom:\n%s\n", (char *)stream_encode);
#endif
}
static void test_base64_07_stream_decode(void)
{
/* @Note:
* Here we decode the base64 string `encoded[]`
*
* Every base64 chunk is decoded and appended to `stream_decoded[]`.
* The chunks passed to decode MUST be dividable by 4.
*/
unsigned char encoded[] = "UGV0ZXIgUGlwZXIgcGlja2VkIGEgcGVjayBvZiBwaWNrbGVkIH"
"BlcHBlcnMuCkEgcGVjayBvZiBwaWNrbGVkIHBlcHBlcnMgUGV0"
"ZXIgUGlwZXIgcGlja2VkLgpJZiBQZXRlciBQaXBlciBwaWNrZW"
"QgYSBwZWNrIG9mIHBpY2tsZWQgcGVwcGVycywKV2hlcmUncyB0"
"aGUgcGVjayBvZiBwaWNrbGVkIHBlcHBlcnMgUGV0ZXIgUGlwZX"
"IgcGlja2VkPw==";
/* required output size +2 extra bytes */
size_t decoded_size = 196 + 2;
/* cppcheck-suppress unassignedVariable */
unsigned char stream_decoded[decoded_size];
/* the above array is used/assigned in base64_decode() using its pointer. */
size_t encoded_size = strlen((char *)encoded);
int remain = encoded_size;
int out_iter = 0;
int ret = BASE64_SUCCESS;
for (int i = 4; i < remain; (i += 4)) {
size_t size_used = decoded_size - out_iter;
ret = base64_decode(encoded + (encoded_size - remain), \
i, stream_decoded + out_iter, &size_used);
TEST_ASSERT_EQUAL_INT(BASE64_SUCCESS, ret);
out_iter += size_used;
remain -= i;
}
size_t finish = decoded_size - out_iter;
ret = base64_decode(encoded + (encoded_size - remain), \
remain, stream_decoded + out_iter, &finish);
TEST_ASSERT_EQUAL_INT(BASE64_SUCCESS, ret);
#if (TEST_BASE64_SHOW_OUTPUT == 1)
out_iter += finish;
puts("Test 07 Decoded:");
for (int i = 0; i < (int)out_iter; ++i) {
printf("%c", stream_decoded[i]);
}
printf("\nFrom:\n%s\n", (char *)encoded);
#endif
}
Test *tests_base64_tests(void)
{
EMB_UNIT_TESTFIXTURES(fixtures) {
new_TestFixture(test_base64_01_encode_string),
new_TestFixture(test_base64_02_decode_base64),
new_TestFixture(test_base64_03_single_character),
new_TestFixture(test_base64_04_free_conversion),
new_TestFixture(test_base64_05_decode_larger),
new_TestFixture(test_base64_06_stream_encode),
new_TestFixture(test_base64_07_stream_decode),
};
EMB_UNIT_TESTCALLER(base64_tests, NULL, NULL, fixtures);
return (Test *)&base64_tests;
}
void tests_base64(void)
{
TESTS_RUN(tests_base64_tests());
}

43
tests/unittests/tests-base64/tests-base64.h

@ -0,0 +1,43 @@
/*
* Copyright (C) 2015 Martin Landsmann
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @addtogroup unittests
* @{
*
* @file tests-base64.h
* @brief Unittests for the ``base64`` module
*
* @author Martin Landsmann <Martin.Landsmann@HAW-Hamburg.de>
*/
#ifndef __TESTS_BASE64_H_
#define __TESTS_BASE64_H_
#include "embUnit/embUnit.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief The entry point of this test suite.
*/
void tests_base64(void);
/**
* @brief Generates tests for base64
*
* @return embUnit tests if successful, NULL if not.
*/
Test *tests_base64_tests(void);
#ifdef __cplusplus
}
#endif
#endif /* __TESTS_BASE64_H_ */
/** @} */
Loading…
Cancel
Save