Merge pull request #16702 from miri64/ut_process/feat/initial

ut_process: initial import of a URI template processor
This commit is contained in:
Martine Lenders 2021-09-17 09:43:20 +02:00 committed by GitHub
commit 913573a200
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 918 additions and 0 deletions

View File

@ -647,6 +647,10 @@ ifneq (,$(filter usbus_dfu,$(USEMODULE)))
USEMODULE += riotboot_slot
endif
ifneq (,$(filter ut_process,$(USEMODULE)))
USEMODULE += fmt
endif
ifneq (,$(filter uuid,$(USEMODULE)))
USEMODULE += hashes
USEMODULE += random

101
sys/include/ut_process.h Normal file
View File

@ -0,0 +1,101 @@
/*
* Copyright (C) 2021 Freie Universität Berlin
*
* 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_ut_process URI template processor
* @ingroup sys
* @brief URI template processor
*
* A parser and processor for URI templates up to level 3 expression types
* according to [RFC 6570](https://tools.ietf.org/html/rfc6570).
*
* @see [RFC 6570](https://tools.ietf.org/html/rfc6570)
*
* @{
*
* @file
* @brief URI template processor definitions
*
* @author Martine Lenders <m.lenders@fu-berlin.de>
*/
#ifndef UT_PROCESS_H
#define UT_PROCESS_H
#include <stddef.h>
#include <string.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Name-value-pair of a variable for URI template expansion
*/
typedef struct {
const char *name; /**< name of the variable */
const char *value; /**< value for the variable */
} ut_process_var_t;
/**
* @brief Expands a URI template by a given value set
*
* @pre `(vars_len == 0) || (vars != NULL)`
* @pre `(uri != NULL) && (uri_len > 0)`
*
* @param[in] ut A URI template.
* @param[in] ut_len Length of @p ut.
* @param[in] vars A set of variable-value pairs.
* @param[in] vars_len The length of @p vars.
* @param[out] uri The resulting URI.
* @param[in, out] uri_len The maximum length for @p uri on in, the actual
* length of @p uri on out.
*
* @return The length of @p uri on success
* @return -EINVAL, when @p ut is not parseable.
* @return -ENOBUFS, when @p uri_len is too small to fit the resulting URI.
* Potentially broken data will be written to @p uri.
*/
int ut_process_expand(const char *ut, size_t ut_len,
const ut_process_var_t *vars, size_t vars_len,
char *uri, size_t *uri_len);
/**
* @brief Expands a URI template by a given value set
*
* @pre `(vars_len == 0) || (vars != NULL)`
* @pre `(uri != NULL) && (uri_len > 0)`
*
* @param[in] ut A `\0`-terminated URI template.
* @param[in] vars A set of variable-value pairs.
* @param[in] vars_len The length of @p vars.
* @param[out] uri The resulting URI.
* @param[in, out] uri_len The maximum length for @p uri on in, the actual
* length of @p uri on out.
*
* @return The length of @p uri on success
* @return -EINVAL, when @p ut is not parseable.
* @return -ENOENT, when any ut_process_var_t::name in @p vars contains
* an invalid character.
* @return -ENOBUFS, when @p uri_len is too small to fit the resulting URI.
* A truncated version of the resulting URI will then be stored in
* @p uri.
*/
static inline int ut_process_str_expand(const char *ut,
const ut_process_var_t *vars,
size_t vars_len,
char *uri, size_t *uri_len)
{
return ut_process_expand(ut, strlen(ut), vars, vars_len, uri, uri_len);
}
#ifdef __cplusplus
}
#endif
#endif /* UT_PROCESS_H */
/** @} */

1
sys/ut_process/Makefile Normal file
View File

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

446
sys/ut_process/ut_process.c Normal file
View File

@ -0,0 +1,446 @@
/*
* Copyright (C) 2021 Freie Universität Berlin
*
* 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.
*/
/**
* @{
*
* @file
* @author Martine Lenders <m.lenders@fu-berlin.de>
*/
#include <assert.h>
#include <errno.h>
#include <stdbool.h>
#include "fmt.h"
#include "ut_process.h"
#define ENABLE_DEBUG 0
#include "debug.h"
typedef enum {
INVAL = -1, /* reserved operators '=', ',', '!', '@', '|', '$', '(', ')' */
SIMPLE = 0, /* no operator */
RESERVED, /* '+' */
FRAGMENT, /* '#' */
LABEL, /* '.' */
PATH, /* '/' */
PATH_PARAM, /* ';' */
QUERY, /* '?' */
QUERY_CONT, /* '&' */
} _op_t;
static int _set_var_list(const char *var_list, size_t var_list_len,
const ut_process_var_t *vars, size_t vars_len,
char *uri, size_t uri_len, unsigned uri_idx);
static int _set_var(const char *var, size_t var_len,
const ut_process_var_t *vars, size_t vars_len,
char *uri, size_t uri_len, unsigned uri_idx, _op_t op,
bool first);
static int _fill_var(const ut_process_var_t *var, bool has_reserved,
bool has_name, bool empty_equal,
char *uri, size_t uri_len, unsigned uri_idx);
static int _copy_char(char c, char *out, size_t out_len);
static int _copy_str(const char *in, size_t in_len, char *out, size_t out_len);
int ut_process_expand(const char *ut, size_t ut_len,
const ut_process_var_t *vars, size_t vars_len,
char *uri, size_t *uri_len_ptr)
{
const char *exp_start = NULL;
int res;
unsigned i, uri_idx = 0;
size_t uri_len = *uri_len_ptr;
assert(ut != NULL);
assert((vars_len == 0) || (vars != NULL));
assert((uri != NULL) && (uri_len > 0));
for (i = 0; i < ut_len; i++) {
switch (ut[i]) {
case '{':
if (exp_start) {
/* nested variable expressions are not allowed so we are
* not in a variable expression. Write-back all collected
* chars so far */
res = _copy_str(exp_start, (&ut[i] - exp_start),
&uri[uri_idx], uri_len - uri_idx);
if (res < 0) {
DEBUG("ut_process: %p(%u) does not fit write-back %.*s "
"of template %s\n",
(void *)uri, (unsigned)uri_len,
(int)(&ut[i] - exp_start), exp_start, ut);
return res;
}
uri_idx += res;
}
exp_start = &ut[i];
break;
case '}':
if (exp_start) {
res = _set_var_list(
exp_start + 1,
i - (exp_start - ut) - 1,
vars, vars_len,
uri, uri_len, uri_idx
);
if (res < 0) {
return res;
}
uri_idx = res;
exp_start = NULL;
break;
}
/* else treat as literal */
/* Intentionally falls through */
default:
if (!exp_start) {
res = _copy_char(ut[i], &uri[uri_idx], uri_len - uri_idx);
if (res < 0) {
DEBUG("ut_process: %p(%u) does not fit literal %c of "
"template %s\n", (void *)uri, (unsigned)uri_len,
ut[i], ut);
return res;
}
uri_idx += res;
}
break;
}
}
if (exp_start) {
/* a { was opened but not closed until the end of template, copy it
* into the URI */
res = _copy_str(exp_start, (&ut[i] - exp_start),
&uri[uri_idx], uri_len - uri_idx);
if (res < 0) {
DEBUG("ut_process: %p(%u) does not fit terminal write-back %.*s of "
"template %s\n",
(void *)uri, (unsigned)uri_len,
(int)(&ut[i] - exp_start), exp_start, ut);
return res;
}
uri_idx += res;
}
res = _copy_char('\0', &uri[uri_idx], uri_len - uri_idx);
if (res < 0) {
DEBUG("ut_process: %p(%u) does not fit terminating '\\0' char\n",
(void *)uri, (unsigned)uri_len);
return res;
}
/* do not increment uri_idx. We want the string length so \0 does not count
* ;-) */
*uri_len_ptr = uri_idx;
return uri_idx;
}
static inline bool _is_lower(char c)
{
return (c >= 'a') && (c <= 'z');
}
static inline bool _is_alpha(char c)
{
return (fmt_is_upper(c)) || (_is_lower(c));
}
static inline bool _is_valid_name_char(char c)
{
return fmt_is_digit(c) || _is_alpha(c) || (c == '_') ||
(c == '%'); /* pct-encoded, hex is within fmt_is_digit and _is_alpha */
}
static bool _is_unreserved(char c)
{
return fmt_is_digit(c) || _is_alpha(c) || (c == '-') || (c == '.') ||
(c == '_') || (c == '~');
}
static bool _is_reserved(char c)
{
return _is_lower(c) || (c == '!') || (c == '#') || (c == '$') ||
/* ascii range & to ; includes digits and a few unreserved but
* safes us a few checks */
((c >= '&') && (c <= ';')) ||
/* ascii range ? to [ includes upper alphas and a few unreserved but
* safes us a few checks */
(c == '=') || ((c >= '?') && (c <= '[')) ||
(c == ']') || _is_unreserved(c);
}
static _op_t _get_op(char ch)
{
switch (ch) {
case '#':
return FRAGMENT;
case '&':
return QUERY_CONT;
case '+':
return RESERVED;
case '.':
return LABEL;
case '/':
return PATH;
case ';':
return PATH_PARAM;
case '?':
return QUERY;
break;
case '!': case '$': case '(': case ')':
case ',': case '=': case '|': case '@':
/* reserved operators;
* see https://datatracker.ietf.org/doc/html/rfc6570#section-2.2 */
return INVAL;
default:
return SIMPLE;
}
}
static const ut_process_var_t *_find_var(const char *var, size_t var_len,
const ut_process_var_t *vars,
size_t vars_len)
{
for (unsigned i = 0; i < vars_len; i++) {
const char *name = vars[i].name;
if ((strlen(name) == var_len) && strncmp(name, var, var_len) == 0) {
return &vars[i];
}
}
return NULL;
}
static size_t _enc_reserved(char c, char *enc)
{
if (_is_reserved(c)) {
*enc = c;
return sizeof(c);
}
else {
enc[0] = '%';
return 1 + fmt_byte_hex(&enc[1], (uint8_t)c);
}
}
static size_t _enc_unreserved(char c, char *enc)
{
if (_is_unreserved(c)) {
*enc = c;
return sizeof(c);
}
else {
enc[0] = '%';
return 1 + fmt_byte_hex(&enc[1], (uint8_t)c);
}
}
static int _set_var_list(const char *var_list, size_t var_list_len,
const ut_process_var_t *vars, size_t vars_len,
char *uri, size_t uri_len, unsigned uri_idx)
{
int res;
bool first = true;
const char *cur_var;
_op_t op = _get_op(*var_list);
if (op == INVAL) {
DEBUG("ut_process: reserved operator %c used\n", *var_list);
return -EINVAL;
}
else if (op != SIMPLE) {
var_list++;
var_list_len--;
}
cur_var = var_list;
for (unsigned i = 0; i < var_list_len; i++) {
switch (var_list[i]) {
case ',':
res = _set_var(
cur_var, &var_list[i] - cur_var,
vars, vars_len, uri, uri_len, uri_idx, op, first
);
if (res < 0) {
return res;
}
if (res > 0) {
uri_idx = res;
first = false;
}
cur_var = &var_list[i + 1];
break;
default:
if (!_is_valid_name_char(var_list[i])) {
DEBUG("ut_process: invalid variable name character %c\n",
var_list[i]);
return -EINVAL;
}
break;
}
}
res = _set_var(cur_var, var_list_len - (cur_var - var_list),
vars, vars_len, uri, uri_len, uri_idx, op, first);
if (res == 0) { /* the variable was not expanded */
return uri_idx;
}
return res;
}
static int _set_var(const char *var, size_t var_len,
const ut_process_var_t *vars, size_t vars_len,
char *uri, size_t uri_len, unsigned uri_idx, _op_t op,
bool first)
{
int res;
const ut_process_var_t *value;
bool has_name = false;
bool has_reserved = false;
bool empty_equal = true;
char prefix = '\0', sep = '\0';
if ((var == NULL) || (var_len == 0)) {
DEBUG("ut_process: zero-length variable found\n")
return -EINVAL;
}
value = _find_var(var, var_len, vars, vars_len);
if ((value == NULL) || (value->value == NULL)) {
return 0;
}
switch (op) {
case SIMPLE:
sep = ',';
break;
case RESERVED:
sep = ',';
has_reserved = true; /* reserved chars are allowed in expansion */
break;
case FRAGMENT:
prefix = '#';
sep = ',';
has_reserved = true; /* reserved chars are allowed in expansion */
break;
case LABEL:
prefix = sep = '.';
break;
case PATH:
prefix = sep = '/';
break;
case PATH_PARAM:
prefix = sep = ';';
has_name = true; /* name-value pair is used completely */
empty_equal = false; /* append equal only if value is non-empty */
break;
case QUERY:
prefix = '?';
sep = '&';
has_name = true; /* name-value pair is used completely */
break;
case QUERY_CONT:
prefix = sep = '&';
has_name = true; /* name-value pair is used completely */
break;
default:
break;
}
if (first) {
if (prefix) {
res = _copy_char(prefix, &uri[uri_idx], uri_len - uri_idx);
if (res < 0) {
DEBUG("ut_process: %p(%u) does not fit prefix %c\n",
(void *)uri, (unsigned)uri_len, prefix);
return res;
}
uri_idx += res;
}
}
else {
assert(sep); /* all operators have a separator defined */
res = _copy_char(sep, &uri[uri_idx], uri_len - uri_idx);
if (res < 0) {
DEBUG("ut_process: %p(%u) does not fit separator '%c'\n",
(void *)uri, (unsigned)uri_len, sep);
return -ENOBUFS;
}
uri_idx += res;
}
return _fill_var(value, has_reserved, has_name, empty_equal, uri, uri_len,
uri_idx);
}
static int _fill_var(const ut_process_var_t *var, bool has_reserved,
bool has_name, bool empty_equal,
char *uri, size_t uri_len, unsigned uri_idx)
{
int res;
if (has_name) {
/* copy one by one so we do not iterate twice for strlen(var->name)
* and strcpy() (also code size becomes smaller due to the omitted
* strlen()) */
for (const char *c = var->name; *c != '\0'; c++) {
res = _copy_char(*c, &uri[uri_idx], uri_len - uri_idx);
if (res < 0) {
DEBUG("ut_process: %p(%u) does not var name %s\n", (void *)uri,
(unsigned)uri_len, var->name);
return res;
}
uri_idx += res;
}
/* value is not an empty string */
if ((var->value[0] != '\0') || empty_equal) {
res = _copy_char('=', &uri[uri_idx], uri_len - uri_idx);
if (res < 0) {
DEBUG("ut_process: %p(%u) does not fit =\n", (void *)uri,
(unsigned)uri_len);
return res;
}
uri_idx += res;
}
}
for (const char *c = var->value; *c != '\0'; c++) {
char enc[sizeof("%00")];
size_t enc_len;
if (has_reserved) {
enc_len = _enc_reserved(*c, enc);
}
else {
enc_len = _enc_unreserved(*c, enc);
}
res = _copy_str(enc, enc_len, &uri[uri_idx], uri_len - uri_idx);
if (res < 0) {
DEBUG("ut_process: %p(%u) does not fit value encoding %.*s\n",
(void *)uri, (unsigned)uri_len, (unsigned)enc_len, enc);
return res;
}
uri_idx += res;
}
return uri_idx;
}
static int _copy_char(char c, char *out, size_t out_len)
{
if (out_len == 0) {
return -ENOBUFS;
}
out[0] = c;
return sizeof(c);
}
static int _copy_str(const char *in, size_t in_len, char *out, size_t out_len)
{
if (in_len >= out_len) {
return -ENOBUFS;
}
strncpy(out, in, in_len);
return in_len;
}
/** @} */

View File

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

View File

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

View File

@ -0,0 +1,329 @@
/*
* Copyright (C) 2021 Freie Universität Berlin
*
* 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.
*/
/**
* @{
*
* @file
* @author Martine Lenders <m.lenders@fu-berlin.de>
*/
#include <errno.h>
#include "embUnit.h"
#include "kernel_defines.h"
#include "ut_process.h"
#include "tests-ut_process.h"
static char _res_buf[128U];
static const ut_process_var_t _test_vec[] = {
/* Taken from https://datatracker.ietf.org/doc/html/rfc6570#section-3.2
* minus the level 4 variables */
{ .name = "dub", .value = "me/too", },
{ .name = "hello", .value = "Hello World!", },
{ .name = "half", .value = "50%", },
{ .name = "var", .value = "value", },
{ .name = "who", .value = "fred", },
{ .name = "base", .value = "http://example.com/home/", },
{ .name = "path", .value = "/foo/bar", },
{ .name = "v", .value = "6", },
{ .name = "x", .value = "1024", },
{ .name = "y", .value = "768", },
{ .name = "empty", .value = "", },
{ .name = "undef", .value = NULL },
};
static void _setup(void)
{
memset(_res_buf, 0, sizeof(_res_buf));
}
static void test_expand_str__overview_example(void)
{
/* Tests example from
* https://datatracker.ietf.org/doc/html/rfc6570#section-1.1 */
static const char *ut = "http://www.example.com/foo{?query,number}";
static const ut_process_var_t vars[] = {
{ .name = "number", .value = "100", },
{ .name = "query", .value = "mycelium", },
};
size_t res_buf_len = sizeof(_res_buf);
TEST_ASSERT_EQUAL_INT(
sizeof("http://www.example.com/foo?query=mycelium&number=100") - 1,
ut_process_str_expand(ut, vars, ARRAY_SIZE(vars), _res_buf, &res_buf_len)
);
TEST_ASSERT_EQUAL_STRING(
"http://www.example.com/foo?query=mycelium&number=100", _res_buf
);
TEST_ASSERT_EQUAL_INT(
sizeof("http://www.example.com/foo?query=mycelium&number=100") - 1,
res_buf_len
);
TEST_ASSERT_EQUAL_INT(
sizeof("http://www.example.com/foo?number=100") - 1,
ut_process_str_expand(ut, vars, 1U, _res_buf, &res_buf_len)
);
TEST_ASSERT_EQUAL_STRING(
"http://www.example.com/foo?number=100", _res_buf
);
TEST_ASSERT_EQUAL_INT(
sizeof("http://www.example.com/foo?number=100") - 1,
res_buf_len
);
TEST_ASSERT_EQUAL_INT(
sizeof("http://www.example.com/foo") - 1,
ut_process_str_expand(ut, NULL, 0U, _res_buf, &res_buf_len)
);
TEST_ASSERT_EQUAL_STRING(
"http://www.example.com/foo", _res_buf
);
TEST_ASSERT_EQUAL_INT(
sizeof("http://www.example.com/foo") - 1,
res_buf_len
);
}
#define ASSERT_EXPANSION(template, expansion) \
res_buf_len = sizeof(_res_buf); \
TEST_ASSERT_EQUAL_INT(sizeof(expansion) - 1, ut_process_str_expand( \
template, _test_vec, ARRAY_SIZE(_test_vec), _res_buf, &res_buf_len \
)); \
TEST_ASSERT_EQUAL_STRING(expansion, _res_buf)
static void test_expand_str__simple(void)
{
size_t res_buf_len;
/* see https://datatracker.ietf.org/doc/html/rfc6570#section-3.2.2
* except level 4 expansions */
ASSERT_EXPANSION("{var}", "value");
ASSERT_EXPANSION("{hello}", "Hello%20World%21");
ASSERT_EXPANSION("{half}", "50%25");
ASSERT_EXPANSION("O{empty}X", "OX");
ASSERT_EXPANSION("O{undef}X", "OX");
ASSERT_EXPANSION("{x,y}", "1024,768");
ASSERT_EXPANSION("{x,hello,y}", "1024,Hello%20World%21,768");
ASSERT_EXPANSION("?{x,empty}", "?1024,");
ASSERT_EXPANSION("?{x,undef}", "?1024");
ASSERT_EXPANSION("?{undef,y}", "?768");
}
static void test_expand_str__reserved(void)
{
size_t res_buf_len;
/* see https://datatracker.ietf.org/doc/html/rfc6570#section-3.2.3
* except level 4 expansions */
ASSERT_EXPANSION("{+var}", "value");
ASSERT_EXPANSION("{+hello}", "Hello%20World!");
ASSERT_EXPANSION("{+half}", "50%25");
ASSERT_EXPANSION("{base}index", "http%3A%2F%2Fexample.com%2Fhome%2Findex");
ASSERT_EXPANSION("{+base}index", "http://example.com/home/index");
ASSERT_EXPANSION("O{+empty}X", "OX");
ASSERT_EXPANSION("O{+undef}X", "OX");
ASSERT_EXPANSION("{+path}/here", "/foo/bar/here");
ASSERT_EXPANSION("here?ref={+path}", "here?ref=/foo/bar");
ASSERT_EXPANSION("up{+path}{var}/here", "up/foo/barvalue/here");
ASSERT_EXPANSION("{+x,hello,y}", "1024,Hello%20World!,768");
ASSERT_EXPANSION("{+path,x}/here", "/foo/bar,1024/here");
}
static void test_expand_str__fragment(void)
{
size_t res_buf_len;
/* see https://datatracker.ietf.org/doc/html/rfc6570#section-3.2.4
* except level 4 expansions */
ASSERT_EXPANSION("{#var}", "#value");
ASSERT_EXPANSION("{#hello}", "#Hello%20World!");
ASSERT_EXPANSION("{#half}", "#50%25");
ASSERT_EXPANSION("foo{#empty}", "foo#");
ASSERT_EXPANSION("foo{#undef}", "foo");
ASSERT_EXPANSION("{#x,hello,y}", "#1024,Hello%20World!,768");
ASSERT_EXPANSION("{#path,x}/here", "#/foo/bar,1024/here");
}
static void test_expand_str__label(void)
{
size_t res_buf_len;
/* see https://datatracker.ietf.org/doc/html/rfc6570#section-3.2.5
* except level 4 expansions */
ASSERT_EXPANSION("{.who}", ".fred");
ASSERT_EXPANSION("{.who,who}", ".fred.fred");
ASSERT_EXPANSION("{.half,who}", ".50%25.fred");
ASSERT_EXPANSION("X{.var}", "X.value");
ASSERT_EXPANSION("X{.empty}", "X.");
ASSERT_EXPANSION("X{.undef}", "X");
/* unreserved set is not really tested with provided set, so add a test for
* that */
ASSERT_EXPANSION("{.hello}", ".Hello%20World%21");
}
static void test_expand_str__path(void)
{
size_t res_buf_len;
/* see https://datatracker.ietf.org/doc/html/rfc6570#section-3.2.6
* except level 4 expansions */
ASSERT_EXPANSION("{/who}", "/fred");
ASSERT_EXPANSION("{/who,who}", "/fred/fred");
ASSERT_EXPANSION("{/half,who}", "/50%25/fred");
ASSERT_EXPANSION("{/who,dub}", "/fred/me%2Ftoo");
ASSERT_EXPANSION("{/var}", "/value");
ASSERT_EXPANSION("{/var,empty}", "/value/");
ASSERT_EXPANSION("{/var,undef}", "/value");
ASSERT_EXPANSION("{/var,x}/here", "/value/1024/here");
/* unreserved set is not really tested with provided set, so add a test for
* that */
ASSERT_EXPANSION("{/hello}", "/Hello%20World%21");
}
static void test_expand_str__path_param(void)
{
size_t res_buf_len;
/* see https://datatracker.ietf.org/doc/html/rfc6570#section-3.2.7
* except level 4 expansions */
ASSERT_EXPANSION("{;who}", ";who=fred");
ASSERT_EXPANSION("{;half}", ";half=50%25");
ASSERT_EXPANSION("{;empty}", ";empty");
ASSERT_EXPANSION("{;v,empty,who}", ";v=6;empty;who=fred");
ASSERT_EXPANSION("{;v,bar,who}", ";v=6;who=fred");
ASSERT_EXPANSION("{;x,y}", ";x=1024;y=768");
ASSERT_EXPANSION("{;x,y,empty}", ";x=1024;y=768;empty");
ASSERT_EXPANSION("{;x,y,undef}", ";x=1024;y=768");
/* unreserved set is not really tested with provided set, so add a test for
* that */
ASSERT_EXPANSION("{;hello}", ";hello=Hello%20World%21");
}
static void test_expand_str__query(void)
{
size_t res_buf_len;
/* see https://datatracker.ietf.org/doc/html/rfc6570#section-3.2.8
* except level 4 expansions */
ASSERT_EXPANSION("{?who}", "?who=fred");
ASSERT_EXPANSION("{?half}", "?half=50%25");
ASSERT_EXPANSION("{?x,y}", "?x=1024&y=768");
ASSERT_EXPANSION("{?x,y,empty}", "?x=1024&y=768&empty=");
ASSERT_EXPANSION("{?x,y,undef}", "?x=1024&y=768");
/* unreserved set is not really tested with provided set, so add a test for
* that */
ASSERT_EXPANSION("{?hello}", "?hello=Hello%20World%21");
}
static void test_expand_str__query_cont(void)
{
size_t res_buf_len;
/* see https://datatracker.ietf.org/doc/html/rfc6570#section-3.2.8
* except level 4 expansions */
ASSERT_EXPANSION("{&who}", "&who=fred");
ASSERT_EXPANSION("{&half}", "&half=50%25");
ASSERT_EXPANSION("?fixed=yes{&x}", "?fixed=yes&x=1024");
ASSERT_EXPANSION("{&x,y,empty}", "&x=1024&y=768&empty=");
ASSERT_EXPANSION("{&x,y,undef}", "&x=1024&y=768");
/* unreserved set is not really tested with provided set, so add a test for
* that */
ASSERT_EXPANSION("{&hello}", "&hello=Hello%20World%21");
}
static void test_expand_str__dup_open(void)
{
size_t res_buf_len;
ASSERT_EXPANSION("{half{who}", "{halffred");
ASSERT_EXPANSION("{{half{who}", "{{halffred");
ASSERT_EXPANSION("half{who}{{", "halffred{{");
}
static void test_expand_str__dup_close(void)
{
size_t res_buf_len;
ASSERT_EXPANSION("{half{who}}", "{halffred}");
ASSERT_EXPANSION("half{who}}}", "halffred}}");
}
#define ASSERT_ENOBUFS_EXPANSION(template, res_len) \
res_buf_len = (res_len); \
TEST_ASSERT_EQUAL_INT(-ENOBUFS, ut_process_str_expand( \
template, _test_vec, ARRAY_SIZE(_test_vec), _res_buf, &res_buf_len \
))
static void test_expand_str__enobufs(void)
{
size_t res_buf_len;
ASSERT_ENOBUFS_EXPANSION("{half{who}}", sizeof("{half") - 1);
ASSERT_ENOBUFS_EXPANSION("foo{#empty}", sizeof("fo") - 1);
ASSERT_ENOBUFS_EXPANSION("{thisisonlyatest", sizeof("{thisi") - 1);
ASSERT_ENOBUFS_EXPANSION("foo", sizeof("foo") - 1);
ASSERT_ENOBUFS_EXPANSION("{?x,y,empty}", sizeof("?x=1024&") - 1);
ASSERT_ENOBUFS_EXPANSION("{?half}", sizeof("?hal") - 1);
ASSERT_ENOBUFS_EXPANSION("{?half}", sizeof("?half") - 1);
ASSERT_ENOBUFS_EXPANSION("{?half}", sizeof("?half=50") - 1);
ASSERT_ENOBUFS_EXPANSION("foo{#empty}", sizeof("foo") - 1);
}
#define ASSERT_EINVAL_EXPANSION(template) \
res_buf_len = sizeof(_res_buf); \
TEST_ASSERT_EQUAL_INT(-EINVAL, ut_process_str_expand( \
template, _test_vec, ARRAY_SIZE(_test_vec), _res_buf, &res_buf_len \
))
static void test_expand_str__einval(void)
{
size_t res_buf_len;
ASSERT_EINVAL_EXPANSION("{?x,,empty}");
ASSERT_EINVAL_EXPANSION("{=x,y,empty}");
ASSERT_EINVAL_EXPANSION("{?x,$,empty}");
}
static Test *_ut_process_tests(void)
{
EMB_UNIT_TESTFIXTURES(fixtures) {
new_TestFixture(test_expand_str__overview_example),
new_TestFixture(test_expand_str__simple),
new_TestFixture(test_expand_str__reserved),
new_TestFixture(test_expand_str__fragment),
new_TestFixture(test_expand_str__label),
new_TestFixture(test_expand_str__path),
new_TestFixture(test_expand_str__path_param),
new_TestFixture(test_expand_str__query),
new_TestFixture(test_expand_str__query_cont),
new_TestFixture(test_expand_str__dup_open),
new_TestFixture(test_expand_str__dup_close),
new_TestFixture(test_expand_str__enobufs),
new_TestFixture(test_expand_str__einval),
};
EMB_UNIT_TESTCALLER(ut_process_tests, _setup, NULL, fixtures);
return (Test *)&ut_process_tests;
}
void tests_ut_process(void)
{
TESTS_RUN(_ut_process_tests());
}
/** @} */

View File

@ -0,0 +1,35 @@
/*
* Copyright (C) 2021 Freie Universität Berlin
*
* 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
* @brief Unit tests for the ut_process module
*
* @author Martine Lenders <m.lenders@fu-berlin.de>
*/
#ifndef TESTS_UT_PROCESS_H
#define TESTS_UT_PROCESS_H
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief The entry point of this test suite.
*/
void test_uri_parser(void);
#ifdef __cplusplus
}
#endif
#endif /* TESTS_UT_PROCESS_H */
/** @} */