Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge pull request #14400 from maribu/base64_tests
sys/base64: Fix, unit test cleanup, and benchmark
  • Loading branch information
miri64 committed Jul 2, 2020
2 parents 045874a + 4e6ec34 commit 339e3fa
Show file tree
Hide file tree
Showing 6 changed files with 288 additions and 142 deletions.
54 changes: 29 additions & 25 deletions sys/base64/base64.c
Expand Up @@ -19,6 +19,8 @@
*/

#include <stdbool.h>
#include <stdint.h>

#include "base64.h"
#include "kernel_defines.h"

Expand All @@ -35,7 +37,7 @@
/*
* returns the corresponding ascii symbol value for the given base64 code
*/
static char getsymbol(unsigned char code, bool urlsafe)
static char getsymbol(uint8_t code, bool urlsafe)
{
if (!IS_ACTIVE(MODULE_BASE64URL)) {
urlsafe = false;
Expand Down Expand Up @@ -73,10 +75,11 @@ static char getsymbol(unsigned char code, bool urlsafe)
}

static int base64_encode_base(const void *data_in, size_t data_in_size,
unsigned char *base64_out, size_t *base64_out_size,
void *base64_out, size_t *base64_out_size,
bool urlsafe)
{
const unsigned char *in = data_in;
const uint8_t *in = data_in;
uint8_t *out = base64_out;
size_t required_size = base64_estimate_encode_size(data_in_size);

if (data_in == NULL) {
Expand All @@ -93,45 +96,45 @@ static int base64_encode_base(const void *data_in, size_t data_in_size,
return BASE64_ERROR_BUFFER_OUT_SIZE;
}

if (base64_out == NULL) {
if (out == NULL) {
return BASE64_ERROR_BUFFER_OUT;
}

int iterate_base64_buffer = 0;
unsigned char nNum = 0;
uint8_t n_num = 0;
int nLst = 0;
int njump = 0;

for (int i = 0; i < (int)(data_in_size); ++i) {
unsigned char tmpval;
uint8_t tmpval;
njump++;
tmpval = *(in + i);

nNum = (tmpval >> (2 * njump));
n_num = (tmpval >> (2 * njump));

if (njump == 4) {
nNum = nLst << (8 - 2 * njump);
n_num = nLst << (8 - 2 * njump);
njump = 0;
nLst = 0;
--i;
}
else {
nNum += nLst << (8 - 2 * njump);
n_num += nLst << (8 - 2 * njump);
nLst = tmpval & ((1 << njump * 2) - 1);
}

base64_out[iterate_base64_buffer++] = getsymbol(nNum, urlsafe);
out[iterate_base64_buffer++] = getsymbol(n_num, urlsafe);
}

/* The last character is not finished yet */
njump++;

nNum = nLst << (8 - 2 * njump);
base64_out[iterate_base64_buffer++] = getsymbol(nNum, urlsafe);
n_num = nLst << (8 - 2 * njump);
out[iterate_base64_buffer++] = getsymbol(n_num, urlsafe);

/* if required we append '=' for the required dividability */
while (iterate_base64_buffer % 4) {
base64_out[iterate_base64_buffer++] = '=';
out[iterate_base64_buffer++] = '=';
}

*base64_out_size = iterate_base64_buffer;
Expand All @@ -140,14 +143,14 @@ static int base64_encode_base(const void *data_in, size_t data_in_size,
}

int base64_encode(const void *data_in, size_t data_in_size,
unsigned char *base64_out, size_t *base64_out_size)
void *base64_out, size_t *base64_out_size)
{
return base64_encode_base(data_in, data_in_size, base64_out, base64_out_size, false);
}

#if IS_ACTIVE(MODULE_BASE64URL)
int base64url_encode(const void *data_in, size_t data_in_size,
unsigned char *base64_out, size_t *base64_out_size)
void *base64_out, size_t *base64_out_size)
{
return base64_encode_base(data_in, data_in_size, base64_out, base64_out_size, true);
}
Expand All @@ -156,7 +159,7 @@ int base64url_encode(const void *data_in, size_t data_in_size,
/*
* returns the corresponding base64 code for the given ascii symbol
*/
static int getcode(char symbol)
static uint8_t getcode(char symbol)
{
if (symbol == '/') {
return BASE64_SLASH;
Expand Down Expand Up @@ -200,13 +203,14 @@ static int getcode(char symbol)
return BASE64_NOT_DEFINED;
}

int base64_decode(const unsigned char *base64_in, size_t base64_in_size,
int base64_decode(const void *base64_in, size_t base64_in_size,
void *data_out, size_t *data_out_size)
{
unsigned char *out = data_out;
uint8_t *out = data_out;
const uint8_t *in = base64_in;
size_t required_size = base64_estimate_decode_size(base64_in_size);

if (base64_in == NULL) {
if (in == NULL) {
return BASE64_ERROR_DATA_IN;
}

Expand All @@ -229,31 +233,31 @@ int base64_decode(const unsigned char *base64_in, size_t base64_in_size,
}

int iterate_data_buffer = 0;
unsigned char nNum = 0;
int nLst = getcode(base64_in[0]) << 2;
uint8_t n_num = 0;
int nLst = getcode(in[0]) << 2;
int code = 0;

int mask = 2;

for (int i = 1; i < (int)(base64_in_size); i++) {
code = getcode(base64_in[i]);
code = getcode(in[i]);

if (code == BASE64_NOT_DEFINED || code == BASE64_EQUALS) {
continue;
}

int nm = (0xFF << (2 * mask));

nNum = nLst + ((code & (0xFF & nm)) >> (2 * mask));
n_num = nLst + ((code & (0xFF & nm)) >> (2 * mask));
nLst = (code & (0xFF & ~nm)) << (8 - (2 * mask));

(mask != 3) ? out[iterate_data_buffer++] = nNum : nNum;
(mask != 3) ? out[iterate_data_buffer++] = n_num : n_num;
(mask == 0) ? mask = 3 : mask--;
}

if (code == BASE64_EQUALS) {
/* add the last character to the data_out buffer */
out[iterate_data_buffer] = nNum;
out[iterate_data_buffer] = n_num;
}

*data_out_size = iterate_data_buffer;
Expand Down
8 changes: 4 additions & 4 deletions sys/include/base64.h
Expand Up @@ -41,7 +41,7 @@ extern "C" {
*/
static inline size_t base64_estimate_decode_size(size_t base64_in_size)
{
return ((base64_in_size / 4) * 3);
return (((base64_in_size + 3) / 4) * 3);
}

/**
Expand Down Expand Up @@ -74,7 +74,7 @@ static inline size_t base64_estimate_encode_size(size_t data_in_size)
BASE64_ERROR_DATA_IN if `data_in` equals NULL.
*/
int base64_encode(const void *data_in, size_t data_in_size,
unsigned char *base64_out, size_t *base64_out_size);
void *base64_out, size_t *base64_out_size);

/**
* @brief Encodes a given datum to base64 with URL and Filename Safe Alphabet
Expand All @@ -100,7 +100,7 @@ int base64_encode(const void *data_in, size_t data_in_size,
BASE64_ERROR_DATA_IN if `data_in` equals NULL.
*/
int base64url_encode(const void *data_in, size_t data_in_size,
unsigned char *base64_out, size_t *base64_out_size);
void *base64_out, size_t *base64_out_size);

/**
* @brief Decodes a given base64 string and save the result to the given destination.
Expand All @@ -120,7 +120,7 @@ int base64url_encode(const void *data_in, size_t data_in_size,
BASE64_ERROR_DATA_IN if `base64_in` equals NULL,
BASE64_ERROR_DATA_IN_SIZE if `base64_in_size` is between 1 and 4.
*/
int base64_decode(const unsigned char *base64_in, size_t base64_in_size,
int base64_decode(const void *base64_in, size_t base64_in_size,
void *data_out, size_t *data_out_size);

#ifdef __cplusplus
Expand Down
7 changes: 7 additions & 0 deletions tests/bench_sys_base64/Makefile
@@ -0,0 +1,7 @@
include ../Makefile.tests_common

USEMODULE += base64
USEMODULE += fmt
USEMODULE += xtimer

include $(RIOTBASE)/Makefile.include
99 changes: 99 additions & 0 deletions tests/bench_sys_base64/main.c
@@ -0,0 +1,99 @@
/*
* Copyright (C) 2020 Otto-von-Guericke-Universität Magdeburg
*
* 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 tests
* @{
*
* @file
* @brief Benchmark for the base64 lib
*
* @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
*
* @}
*/

#include <stdint.h>
#include <string.h>

#include "base64.h"
#include "fmt.h"
#include "xtimer.h"

#define MIN(a, b) (a < b) ? a : b

static char buf[128];

static const char input[96] = "This is an extremely, enormously, greatly, "
"immensely, tremendously, remarkably lengthy "
"sentence!";
static const char base64[128] =
"VGhpcyBpcyBhbiBleHRyZW1lbHksIGVub3Jtb3VzbHksIGdyZWF0bHksIGltbWVuc2VseSwgdHJl"
"bWVuZG91c2x5LCByZW1hcmthYmx5IGxlbmd0aHkgc2VudGVuY2Uh";

int main(void) {
uint32_t start, stop;
size_t size;

/* We don't want check return value in the benchmark loop, so we just do
* a simple self test now. */
print_str("Verifying that base64 encoding works for benchmark input: ");
size = sizeof(buf);
if ((BASE64_SUCCESS != base64_encode(input, sizeof(input), buf, &size)) ||
(size != sizeof(buf)) ||
(0 != memcmp(base64, buf, sizeof(base64))))
{
print_str("FAIL\nGot: \"");
print(buf, MIN(size, sizeof(base64)));
print_str("\"\nExpected: \"");
print(base64, sizeof(base64));
print_str("\"\n");
}
else {
print_str("OK\n");
}

print_str("Verifying that base64 decoding works for benchmark input: ");
size = sizeof(buf);
if ((BASE64_SUCCESS != base64_decode(base64, sizeof(base64), buf, &size)) ||
(size != sizeof(input)) ||
(0 != memcmp(input, buf, sizeof(input))))
{
print_str("FAIL\nGot: \"");
print(buf, MIN(size, sizeof(input)));
print_str("\"\nExpected: \"");
print(input, sizeof(input));
print_str("\"\n");
}
else {
print_str("OK\n");
}

start = xtimer_now_usec();
for (unsigned i = 0; i < 10000; i++) {
size = sizeof(buf);
base64_encode(input, sizeof(input), buf, &size);
}
stop = xtimer_now_usec();

print_str("Encoding 10.000 x 96 bytes (128 bytes in base64): ");
print_u32_dec(stop - start);
print_str(" µs\n");

start = xtimer_now_usec();
for (unsigned i = 0; i < 10000; i++) {
size = sizeof(buf);
base64_decode(base64, sizeof(base64), buf, &size);
}
stop = xtimer_now_usec();

print_str("Decoding 10.000 x 96 bytes (128 bytes in base64): ");
print_u32_dec(stop - start);
print_str(" µs\n");
return 0;
}
21 changes: 21 additions & 0 deletions tests/bench_sys_base64/tests/01-run.py
@@ -0,0 +1,21 @@
#!/usr/bin/env python3

# Copyright (C) 2020 Otto-von-Guericke-Universität Magdeburg
#
# 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.

import sys
from testrunner import run


def testfunc(child):
child.expect_exact("Verifying that base64 encoding works for benchmark input: OK\r\n")
child.expect_exact("Verifying that base64 decoding works for benchmark input: OK\r\n")
child.expect(r"Encoding 10\.000 x 96 bytes \(128 bytes in base64\): [0-9]+ µs\r\n")
child.expect(r"Decoding 10\.000 x 96 bytes \(128 bytes in base64\): [0-9]+ µs\r\n")


if __name__ == "__main__":
sys.exit(run(testfunc))

0 comments on commit 339e3fa

Please sign in to comment.