[PATCH 2/4] dev/io: Add base64 decoder
Gedare Bloom
gedare at rtems.org
Mon Jan 22 15:34:18 UTC 2024
This seems to be generically useful by itself. I'd probably be OK with
it, if it has a suitable (non device-related) home.
On Mon, Jan 15, 2024 at 2:46 AM Sebastian Huber
<sebastian.huber at embedded-brains.de> wrote:
>
> From: Matthew Joyce <matthew.joyce at embedded-brains.de>
>
> ---
> cpukit/dev/iobase64decode.c | 166 ++++++++++++++++
> cpukit/include/rtems/dev/io.h | 63 ++++++
> spec/build/cpukit/librtemscpu.yml | 1 +
> .../build/testsuites/unit/unit-no-clock-0.yml | 1 +
> testsuites/unit/tc-io-base64-decode.c | 180 ++++++++++++++++++
> 5 files changed, 411 insertions(+)
> create mode 100644 cpukit/dev/iobase64decode.c
> create mode 100644 testsuites/unit/tc-io-base64-decode.c
>
> diff --git a/cpukit/dev/iobase64decode.c b/cpukit/dev/iobase64decode.c
> new file mode 100644
> index 0000000000..5d3fe1c3cb
> --- /dev/null
> +++ b/cpukit/dev/iobase64decode.c
> @@ -0,0 +1,166 @@
> +/* SPDX-License-Identifier: BSD-2-Clause */
> +
> +/**
> + * @file
> + *
> + * @ingroup RTEMSDeviceIO
> + *
> + * @brief This source file contains the implementation of
> + * _IO_Base64_decode_initialize() and _IO_Base64_decode().
> + */
> +
> +/*
> + * Copyright (C) 2023 embedded brains GmbH & Co. KG
> + *
> + * Redistribution and use in source and binary forms, with or without
> + * modification, are permitted provided that the following conditions
> + * are met:
> + * 1. Redistributions of source code must retain the above copyright
> + * notice, this list of conditions and the following disclaimer.
> + * 2. Redistributions in binary form must reproduce the above copyright
> + * notice, this list of conditions and the following disclaimer in the
> + * documentation and/or other materials provided with the distribution.
> + *
> + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
> + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
> + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
> + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
> + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
> + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
> + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
> + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
> + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
> + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
> + * POSSIBILITY OF SUCH DAMAGE.
> + */
> +
> +#include <rtems/dev/io.h>
> +
> +#define SPACE 253
> +
> +#define PAD 254
> +
> +#define INVALID 255
> +
> +const uint8_t _IO_Base64_decode_table[128] = {
> + ['A'] = 0, ['B'] = 1, ['C'] = 2, ['D'] = 3,
> + ['E'] = 4, ['F'] = 5, ['G'] = 6, ['H'] = 7,
> + ['I'] = 8, ['J'] = 9, ['K'] = 10, ['L'] = 11,
> + ['M'] = 12, ['N'] = 13, ['O'] = 14, ['P'] = 15,
> + ['Q'] = 16, ['R'] = 17, ['S'] = 18, ['T'] = 19,
> + ['U'] = 20, ['V'] = 21, ['W'] = 22, ['X'] = 23,
> + ['Y'] = 24, ['Z'] = 25, ['a'] = 26, ['b'] = 27,
> + ['c'] = 28, ['d'] = 29, ['e'] = 30, ['f'] = 31,
> + ['g'] = 32, ['h'] = 33, ['i'] = 34, ['j'] = 35,
> + ['k'] = 36, ['l'] = 37, ['m'] = 38, ['n'] = 39,
> + ['o'] = 40, ['p'] = 41, ['q'] = 42, ['r'] = 43,
> + ['s'] = 44, ['t'] = 45, ['u'] = 46, ['v'] = 47,
> + ['w'] = 48, ['x'] = 49, ['y'] = 50, ['z'] = 51,
> + ['0'] = 52, ['1'] = 53, ['2'] = 54, ['3'] = 55,
> + ['4'] = 56, ['5'] = 57, ['6'] = 58, ['7'] = 59,
> + ['8'] = 60, ['9'] = 61, ['+'] = 62, ['-'] = 62,
> + ['/'] = 63, ['_'] = 63, ['='] = PAD, [' '] = SPACE,
> + ['\t'] = SPACE, ['\n'] = SPACE, ['\v'] = SPACE, ['\f'] = SPACE,
> + ['\r'] = SPACE, [0] = INVALID, [1] = INVALID, [2] = INVALID,
> + [3] = INVALID, [4] = INVALID, [5] = INVALID, [6] = INVALID,
> + [7] = INVALID, [8] = INVALID, [14] = INVALID, [15] = INVALID,
> + [16] = INVALID, [17] = INVALID, [18] = INVALID, [19] = INVALID,
> + [20] = INVALID, [21] = INVALID, [22] = INVALID, [23] = INVALID,
> + [24] = INVALID, [25] = INVALID, [26] = INVALID, [27] = INVALID,
> + [28] = INVALID, [29] = INVALID, [30] = INVALID, [31] = INVALID,
> + [33] = INVALID, [34] = INVALID, [35] = INVALID, [36] = INVALID,
> + [37] = INVALID, [38] = INVALID, [39] = INVALID, [40] = INVALID,
> + [41] = INVALID, [42] = INVALID, [44] = INVALID, [46] = INVALID,
> + [58] = INVALID, [59] = INVALID, [60] = INVALID, [62] = INVALID,
> + [63] = INVALID, [64] = INVALID, [91] = INVALID, [92] = INVALID,
> + [93] = INVALID, [94] = INVALID, [96] = INVALID, [123] = INVALID,
> + [124] = INVALID, [125] = INVALID, [126] = INVALID, [127] = INVALID};
> +
> +void _IO_Base64_decode_initialize(IO_Base64_decode_control* self,
> + uint8_t* target,
> + size_t target_size) {
> + self->state = IO_BASE64_DECODE_STATE_0;
> + self->target = target;
> + self->target_end = target + target_size;
> +}
> +
> +IO_Base64_decode_status _IO_Base64_decode(IO_Base64_decode_control* self,
> + char ch) {
> + uint8_t decoded_ch;
> + uint8_t next_ch;
> + uint8_t* target;
> + const uint8_t* target_end;
> + IO_Base64_decode_state next_state;
> +
> + if ((unsigned char)ch >= 128) {
> + return IO_BASE64_DECODE_INVALID_INPUT;
> + }
> +
> + decoded_ch = _IO_Base64_decode_table[(unsigned char)ch];
> +
> + if (decoded_ch == SPACE) {
> + return IO_BASE64_DECODE_SUCCESS;
> + }
> +
> + target = self->target;
> +
> + if (decoded_ch == PAD) {
> + self->target_end = target;
> + return IO_BASE64_DECODE_SUCCESS;
> + }
> +
> + if (decoded_ch == INVALID) {
> + return IO_BASE64_DECODE_INVALID_INPUT;
> + }
> +
> + target_end = self->target_end;
> +
> + if (target == target_end) {
> + return IO_BASE64_DECODE_OVERFLOW;
> + }
> +
> + switch (self->state) {
> + case IO_BASE64_DECODE_STATE_0:
> + *target = decoded_ch << 2;
> + next_state = IO_BASE64_DECODE_STATE_1;
> + break;
> +
> + case IO_BASE64_DECODE_STATE_1:
> + *target |= decoded_ch >> 4;
> + next_ch = (decoded_ch & 0x0fU) << 4;
> + ++target;
> +
> + if (target != target_end) {
> + *target = next_ch;
> + } else if (next_ch != 0) {
> + return IO_BASE64_DECODE_OVERFLOW;
> + }
> +
> + next_state = IO_BASE64_DECODE_STATE_2;
> + break;
> +
> + case IO_BASE64_DECODE_STATE_2:
> + *target |= decoded_ch >> 2;
> + next_ch = (decoded_ch & 0x03U) << 6;
> + ++target;
> +
> + if (target != target_end) {
> + *target = next_ch;
> + } else if (next_ch != 0) {
> + return IO_BASE64_DECODE_OVERFLOW;
> + }
> +
> + next_state = IO_BASE64_DECODE_STATE_3;
> + break;
> +
> + default: /* IO_BASE64_DECODE_STATE_3 */
> + *target |= decoded_ch;
> + ++target;
> + next_state = IO_BASE64_DECODE_STATE_0;
> + break;
> + }
> +
> + self->state = next_state;
> + self->target = target;
> + return IO_BASE64_DECODE_SUCCESS;
> +}
> diff --git a/cpukit/include/rtems/dev/io.h b/cpukit/include/rtems/dev/io.h
> index efe0a32d0a..9b724e5df8 100644
> --- a/cpukit/include/rtems/dev/io.h
> +++ b/cpukit/include/rtems/dev/io.h
> @@ -178,6 +178,69 @@ int _IO_Base64url(
> int wordlen
> );
>
> +/**
> + * @brief Represents the base64 decoder state.
> + */
> +typedef enum {
> + IO_BASE64_DECODE_STATE_0,
> + IO_BASE64_DECODE_STATE_1,
> + IO_BASE64_DECODE_STATE_2,
> + IO_BASE64_DECODE_STATE_3
> +} IO_Base64_decode_state;
> +
> +/**
> + * @brief Contains the base64 decoder control.
> + */
> +typedef struct {
> + IO_Base64_decode_state state;
> + uint8_t *target;
> + const uint8_t *target_end;
> +} IO_Base64_decode_control;
> +
> +/**
> + * @brief Maps a 7-bit character to the 6-bit integer as defined by the base64
> + * or base64url encoding or a special value.
> + */
> +extern const uint8_t _IO_Base64_decode_table[ 128 ];
> +
> +/**
> + * @brief Initializes the base64 decoder.
> + *
> + * @param[out] self is the base64 decoder control to initialize.
> + *
> + * @param[out] target is the base address of the target area for decoding.
> + *
> + * @param target_size is the size in bytes of the target area for decoding.
> + */
> +void _IO_Base64_decode_initialize(
> + IO_Base64_decode_control *self,
> + uint8_t *target,
> + size_t target_size
> +);
> +
> +/**
> + * @brief Represents the base64 decoder status.
> + */
> +typedef enum {
> + IO_BASE64_DECODE_SUCCESS,
> + IO_BASE64_DECODE_OVERFLOW,
> + IO_BASE64_DECODE_INVALID_INPUT
> +} IO_Base64_decode_status;
> +
> +/**
> + * @brief Decodes the character.
> + *
> + * The decoder accepts base64 and base64url encodings. White space is ignored.
> + *
> + * @param[in, out] self is the base64 decoder control.
> + *
> + * @param ch is the character to decode.
> + */
> +IO_Base64_decode_status _IO_Base64_decode(
> + IO_Base64_decode_control *self,
> + char ch
> +);
> +
> /**
> * @brief Issues a couple of no-operation instructions.
> *
> diff --git a/spec/build/cpukit/librtemscpu.yml b/spec/build/cpukit/librtemscpu.yml
> index 9202c31715..56e5243673 100644
> --- a/spec/build/cpukit/librtemscpu.yml
> +++ b/spec/build/cpukit/librtemscpu.yml
> @@ -540,6 +540,7 @@ source:
> - cpukit/dev/i2c/ti-tmp112.c
> - cpukit/dev/i2c/xilinx-axi-i2c.c
> - cpukit/dev/iobase64.c
> +- cpukit/dev/iobase64decode.c
> - cpukit/dev/ioprintf.c
> - cpukit/dev/iorelax.c
> - cpukit/dev/iovprintf.c
> diff --git a/spec/build/testsuites/unit/unit-no-clock-0.yml b/spec/build/testsuites/unit/unit-no-clock-0.yml
> index 5d15bf52b7..be5cafe990 100644
> --- a/spec/build/testsuites/unit/unit-no-clock-0.yml
> +++ b/spec/build/testsuites/unit/unit-no-clock-0.yml
> @@ -13,6 +13,7 @@ links: []
> source:
> - testsuites/unit/tc-compiler-builtins.c
> - testsuites/unit/tc-config.c
> +- testsuites/unit/tc-io-base64-decode.c
> - testsuites/unit/tc-misaligned-builtin-memcpy.c
> - testsuites/unit/tc-score-msgq.c
> - testsuites/unit/tc-score-rbtree.c
> diff --git a/testsuites/unit/tc-io-base64-decode.c b/testsuites/unit/tc-io-base64-decode.c
> new file mode 100644
> index 0000000000..b09390960f
> --- /dev/null
> +++ b/testsuites/unit/tc-io-base64-decode.c
> @@ -0,0 +1,180 @@
> +/* SPDX-License-Identifier: BSD-2-Clause */
> +
> +/*
> + * Copyright (C) 2023 embedded brains GmbH & Co. KG
> + *
> + * Redistribution and use in source and binary forms, with or without
> + * modification, are permitted provided that the following conditions
> + * are met:
> + * 1. Redistributions of source code must retain the above copyright
> + * notice, this list of conditions and the following disclaimer.
> + * 2. Redistributions in binary form must reproduce the above copyright
> + * notice, this list of conditions and the following disclaimer in the
> + * documentation and/or other materials provided with the distribution.
> + *
> + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
> + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
> + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
> + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
> + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
> + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
> + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
> + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
> + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
> + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
> + * POSSIBILITY OF SUCH DAMAGE.
> + */
> +
> +#include <rtems/dev/io.h>
> +
> +#include <rtems/test.h>
> +
> +typedef struct {
> + IO_Base64_decode_control base;
> + uint8_t buf[64];
> +} test_control;
> +
> +static int test_run(test_control* self, const char* payload) {
> + size_t len = strlen(payload);
> +
> + for (size_t i = 0; i < len; ++i) {
> + int rv = _IO_Base64_decode(&self->base, payload[i]);
> +
> + if (rv != 0) {
> + return rv;
> + }
> + }
> +
> + return 0;
> +}
> +
> +static int test_payload(test_control* self, const char* payload) {
> + memset(self->buf, 0xff, sizeof(self->buf));
> + _IO_Base64_decode_initialize(&self->base, &self->buf[0], sizeof(self->buf));
> + return test_run(self, payload);
> +}
> +
> +T_TEST_CASE(IOBase64Decode) {
> + int rv;
> + test_control instance;
> + test_control* self = &instance;
> +
> + rv = test_payload(self, "POOL");
> + T_eq_int(rv, IO_BASE64_DECODE_SUCCESS);
> + T_eq_int(self->base.state, IO_BASE64_DECODE_STATE_0);
> + T_eq_ptr(self->base.target, &self->buf[3]);
> + const uint8_t expected1[] = {0x3c, 0xe3, 0x8b, 0xff};
> + T_eq_mem(&self->buf[0], expected1, sizeof(expected1));
> +
> + rv = test_payload(self, "ABCDEFGH");
> + T_eq_int(rv, IO_BASE64_DECODE_SUCCESS);
> + T_eq_int(self->base.state, IO_BASE64_DECODE_STATE_0);
> + T_eq_ptr(self->base.target, &self->buf[6]);
> + const uint8_t expected2[] = {0x00, 0x10, 0x83, 0x10, 0x51, 0x87, 0xff};
> + T_eq_mem(&self->buf[0], expected2, sizeof(expected2));
> +
> + /* Non-base64 character results in an error */
> + rv = test_payload(self, "PO*OL");
> + T_eq_int(rv, IO_BASE64_DECODE_INVALID_INPUT);
> + T_eq_int(self->base.state, IO_BASE64_DECODE_STATE_2);
> + T_eq_ptr(self->base.target, &self->buf[1]);
> + const uint8_t expected3[] = {0x3c};
> + T_eq_mem(&self->buf[0], expected3, sizeof(expected3));
> +
> + /* Other non-base64 character results in an error */
> + rv = test_payload(self, "PO\x80OL");
> + T_eq_int(rv, IO_BASE64_DECODE_INVALID_INPUT);
> + T_eq_int(self->base.state, IO_BASE64_DECODE_STATE_2);
> + T_eq_ptr(self->base.target, &self->buf[1]);
> + T_eq_mem(&self->buf[0], expected3, sizeof(expected3));
> +
> + /* Space characters should be ignored */
> + rv = test_payload(self, "P OOL");
> + T_eq_int(rv, IO_BASE64_DECODE_SUCCESS);
> + T_eq_int(self->base.state, IO_BASE64_DECODE_STATE_0);
> + T_eq_ptr(self->base.target, &self->buf[3]);
> + const uint8_t expected4[] = {0x3c, 0xe3, 0x8b, 0xff};
> + T_eq_mem(&self->buf[0], expected4, sizeof(expected4));
> +
> + /* Handle pad characters */
> + rv = test_payload(self, "POOL==");
> + T_eq_int(rv, IO_BASE64_DECODE_SUCCESS);
> + T_eq_int(self->base.state, IO_BASE64_DECODE_STATE_0);
> + T_eq_ptr(self->base.target, &self->buf[3]);
> + T_eq_ptr(self->base.target, self->base.target_end);
> + const uint8_t expected5[] = {0x3c, 0xe3, 0x8b, 0xff};
> + T_eq_mem(&self->buf[0], expected5, sizeof(expected5));
> +
> + /* If characters come after pad characters, an error results */
> + rv = test_payload(self, "POOL==xy");
> + T_eq_int(rv, IO_BASE64_DECODE_OVERFLOW);
> + T_eq_int(self->base.state, IO_BASE64_DECODE_SUCCESS);
> + T_eq_ptr(self->base.target, &self->buf[3]);
> + T_eq_ptr(self->base.target, self->base.target_end);
> + const uint8_t expected6[] = {0x3c, 0xe3, 0x8b, 0xff};
> + T_eq_mem(&self->buf[0], expected6, sizeof(expected6));
> +
> + rv = test_payload(self, "POOLPOOL");
> + T_eq_int(rv, IO_BASE64_DECODE_SUCCESS);
> + T_eq_int(self->base.state, IO_BASE64_DECODE_STATE_0);
> + T_eq_ptr(self->base.target, &self->buf[6]);
> + const uint8_t expected7[] = {0x3c, 0xe3, 0x8b, 0x3c, 0xe3, 0x8b, 0xff};
> + T_eq_mem(&self->buf[0], expected7, sizeof(expected7));
> +
> + /*
> + * Test valid payload with series of target sizes. All target sizes
> + * less than three are invalid for the given payload and will result
> + * in an error.
> + */
> + const uint8_t expected9[] = {0x3c, 0xe3, 0x8b, 0xff};
> +
> + for (size_t i = 0; i < 4; ++i) {
> + memset(&self->buf[0], 0xff, sizeof(self->buf));
> + _IO_Base64_decode_initialize(&self->base, &self->buf[0], i);
> + rv = test_run(self, "POOL");
> +
> + if (i < 3) {
> + T_eq_int(rv, IO_BASE64_DECODE_OVERFLOW);
> + T_eq_int(self->base.state, i);
> + T_ne_ptr(self->base.target, &self->buf[3]);
> + T_ne_mem(&self->buf[0], expected9, sizeof(expected9));
> + } else {
> + T_eq_int(rv, IO_BASE64_DECODE_SUCCESS);
> + T_eq_int(self->base.state, IO_BASE64_DECODE_STATE_0);
> + T_eq_ptr(self->base.target, &self->buf[3]);
> + T_eq_mem(&self->buf[0], expected9, sizeof(expected9));
> + }
> + }
> +
> + /* No overflow in state 1 */
> + memset(&self->buf[0], 0xff, sizeof(self->buf));
> + _IO_Base64_decode_initialize(&self->base, &self->buf[0], 1);
> + rv = test_run(self, "AA");
> + T_eq_int(rv, IO_BASE64_DECODE_SUCCESS);
> + T_eq_int(self->base.state, IO_BASE64_DECODE_STATE_2);
> + T_eq_ptr(self->base.target, &self->buf[1]);
> + const uint8_t expected10[] = {0x00, 0xff};
> + T_eq_mem(&self->buf[0], expected10, sizeof(expected10));
> +
> + /* No overflow in state 2 */
> + memset(&self->buf[0], 0xff, sizeof(self->buf));
> + _IO_Base64_decode_initialize(&self->base, &self->buf[0], 2);
> + rv = test_run(self, "AAA");
> + T_eq_int(rv, IO_BASE64_DECODE_SUCCESS);
> + T_eq_int(self->base.state, IO_BASE64_DECODE_STATE_3);
> + T_eq_ptr(self->base.target, &self->buf[2]);
> + const uint8_t expected11[] = {0x00, 0x00, 0xff};
> + T_eq_mem(&self->buf[0], expected11, sizeof(expected11));
> +}
> +
> +T_TEST_CASE(IOBase64DecodeInitialize) {
> + IO_Base64_decode_control instance;
> + IO_Base64_decode_control* self = &instance;
> + uint8_t buf[1];
> +
> + memset(self, 0xff, sizeof(*self));
> + _IO_Base64_decode_initialize(self, buf, sizeof(buf));
> + T_eq_int(self->state, IO_BASE64_DECODE_STATE_0);
> + T_eq_ptr(self->target, &buf[0]);
> + T_eq_ptr(self->target_end, &buf[1]);
> +}
> --
> 2.35.3
>
> _______________________________________________
> devel mailing list
> devel at rtems.org
> http://lists.rtems.org/mailman/listinfo/devel
More information about the devel
mailing list