update #2

Merged
vilor merged 6 commits from dev into master 2 years ago
  1. 5
      README.md
  2. 34
      c/keyval.c
  3. 75
      c/response.c
  4. 61
      c/utils.c
  5. 17
      c/utils.h
  6. 12
      config.mk
  7. 12
      cxx/Request.cxx
  8. 32
      cxx/Response.cxx
  9. 8
      examples/c/example.c
  10. 12
      examples/c/minimal.c
  11. 2
      examples/cxx/example.cxx
  12. 3
      examples/cxx/minimal.cxx
  13. 18
      include/KeyVal.hxx
  14. 21
      include/Request.hxx
  15. 52
      include/Response.hxx
  16. 18
      include/keyval.h
  17. 3
      include/request.h
  18. 4
      include/response.h
  19. 57
      servers/fcgi.c
  20. 17
      servers/tcp.c
  21. 2
      tests/app.cxx
  22. 18
      tests/keyval.cxx

5
README.md

@ -25,11 +25,6 @@ make install clean @@ -25,11 +25,6 @@ make install clean
```
Run `make help` to see targets, flags and arguments.
Extensions
----------
* [inja](https://github.com/pantor/inja) (template rendering,
requires [nlohmann/json](https://github.com/nlohmann/json))
Examples
--------
See [examples](examples).

34
c/keyval.c

@ -3,7 +3,6 @@ @@ -3,7 +3,6 @@
#include "keyval.h"
#include "utils.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@ -20,6 +19,7 @@ static int rpd_keyval_realloc(rpd_keyval *keyval, int capacity); @@ -20,6 +19,7 @@ static int rpd_keyval_realloc(rpd_keyval *keyval, int capacity);
int rpd_keyval_init(rpd_keyval *keyval, int capacity)
{
keyval->size = keyval->capacity = 0;
keyval->unique = 1;
if (capacity == 0) {
keyval->items = NULL;
@ -46,7 +46,7 @@ int rpd_keyval_insert(rpd_keyval *keyval, const char *key, const char *value) @@ -46,7 +46,7 @@ int rpd_keyval_insert(rpd_keyval *keyval, const char *key, const char *value)
}
rpd_keyval_item *item = rpd_keyval_find(keyval, key);
if (item) {
if (item && keyval->unique) {
free(item->key);
item->key = NULL;
if (item->val) {
@ -76,6 +76,36 @@ rpd_keyval_item *rpd_keyval_find(const rpd_keyval *keyval, const char *key) @@ -76,6 +76,36 @@ rpd_keyval_item *rpd_keyval_find(const rpd_keyval *keyval, const char *key)
return NULL;
}
rpd_keyval_item **rpd_keyval_findall(const rpd_keyval *keyval, const char *key)
{
int i = 0, n = 0;
rpd_keyval_item **items = NULL;
while (i < keyval->size) {
if (!strcmp(keyval->items[i].key, key)) {
n++;
}
i++;
}
if (!n)
return NULL;
items = malloc(sizeof(rpd_keyval_item *) * (n + 1));
if (!items)
return NULL;
items[n] = NULL;
i = 0, n = 0;
while (i < keyval->size) {
if (!strcmp(keyval->items[i].key, key)) {
items[n] = keyval->items + i;
n++;
}
i++;
}
return items;
}
void rpd_keyval_cleanup(rpd_keyval *keyval)
{
if (keyval->items) {

75
c/response.c

@ -6,22 +6,25 @@ @@ -6,22 +6,25 @@
#include <stdlib.h>
#include <string.h>
static size_t calc_res_headers_sz(const rpd_res *res);
static size_t calc_res_status_sz(const rpd_res *res);
static size_t calc_res_headers_sz(const rpd_keyval *res);
void rpd_res_init(rpd_res *dest)
{
dest->status = rpd_res_st_ok;
dest->location = dest->content_type = NULL;
dest->body = NULL;
rpd_keyval_init(&dest->cookie, 0);
rpd_keyval_init(&dest->headers, 5);
dest->headers.unique = 0;
}
int rpd_res_headers_str(char **dest, const rpd_res *src)
{
size_t size = calc_res_headers_sz(src);
size_t size, i = 0;
char *ptr;
size = calc_res_headers_sz(&src->headers);
*dest = (char *) malloc(sizeof(char) * size);
if (!*dest) {
perror("malloc");
@ -29,12 +32,13 @@ int rpd_res_headers_str(char **dest, const rpd_res *src) @@ -29,12 +32,13 @@ int rpd_res_headers_str(char **dest, const rpd_res *src)
}
ptr = *dest;
if (src->content_type) {
ptr += sprintf(ptr, "Content-Type: %s\r\n", src->content_type);
}
if (src->location) {
ptr += sprintf(ptr, "Location: %s\r\n", src->location);
while (i < src->headers.size) {
ptr += sprintf(
ptr,
"%s: %s\r\n",
src->headers.items[i].key,
src->headers.items[i].val);
i++;
}
return 0;
@ -44,7 +48,9 @@ int rpd_res_str(char **dest, const rpd_res *res) @@ -44,7 +48,9 @@ int rpd_res_str(char **dest, const rpd_res *res)
{
size_t headers_size, size;
char *ptr, *headers;
size = headers_size = calc_res_headers_sz(res);
size = headers_size = calc_res_headers_sz(&res->headers);
size += calc_res_status_sz(res);
if (res->body)
size += 2 + strlen(res->body);
@ -78,25 +84,24 @@ int rpd_res_str(char **dest, const rpd_res *res) @@ -78,25 +84,24 @@ int rpd_res_str(char **dest, const rpd_res *res)
return 0;
}
static size_t calc_res_headers_sz(const rpd_res *res)
static size_t calc_res_status_sz(const rpd_res *res)
{
size_t size = 0;
size += strlen("Status: \r\n");
size += 3; /* plus status code */
return size;
}
size += strlen("Status: \r\n") + 3;
if (res->location) {
size += strlen("Location: \r\n") + strlen(res->location);
}
if (res->content_type) {
size += strlen("Content-Type: \r\n") + strlen(res->content_type);
}
if (res->cookie.size) {
size += strlen("Set-Cookie: \r\n") * res->cookie.size;
for (int i = 0; i < res->cookie.size; i++) {
rpd_keyval_item *item = res->cookie.items + i;
size += strlen(item->key) + strlen(item->val);
}
static size_t calc_res_headers_sz(const rpd_keyval *headers)
{
size_t size = 0, i = 0;
while (i < headers->size) {
size += strlen(headers->items[i].key);
size += 2; /* plus ": " */
size += strlen(headers->items[i].val);
size += 2; /* plus CRLF */
i++;
}
return size;
@ -106,22 +111,12 @@ void rpd_res_cleanup(rpd_res *res) @@ -106,22 +111,12 @@ void rpd_res_cleanup(rpd_res *res)
{
res->status = rpd_res_st_ok;
if (res->location) {
free(res->location);
res->location = NULL;
}
if (res->content_type) {
free(res->content_type);
res->content_type = NULL;
}
if (res->body) {
free(res->body);
res->body = NULL;
}
if (res->cookie.capacity) {
rpd_keyval_cleanup(&res->cookie);
if (res->headers.capacity) {
rpd_keyval_cleanup(&res->headers);
}
}

61
c/utils.c

@ -2,6 +2,7 @@ @@ -2,6 +2,7 @@
/* Copyright 2022 Ivan Polyakov */
#include "utils.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@ -43,3 +44,63 @@ char *rpd_strsep(char **str, const char *sep) @@ -43,3 +44,63 @@ char *rpd_strsep(char **str, const char *sep)
*str = end;
return s;
}
const char *rpd_splitbyc(char **dest1, char **dest2, const char *src, const char sep)
{
const char *start = src, *end = src;
if (!src)
return 0;
while (*end) {
if (*end == sep) {
size_t len = end - start;
*dest1 = malloc(sizeof(char) * (len + 1));
if (!*dest1) {
perror("malloc");
return NULL;
}
memcpy(*dest1, start, len);
(*dest1)[len] = '\0';
end++;
len = strlen(start) - (end - start);
if (!len) {
*dest2 = NULL;
return 0;
}
*dest2 = malloc(sizeof(char) * (len + 1));
if ((!*dest2)) {
perror("malloc");
return NULL;
}
memcpy(*dest2, end, len);
(*dest2)[len + 1] = '\0';
return 0;
}
end++;
}
return 0;
}
void rpd_strerase(char *src, int nchars)
{
char *ptr = NULL;
size_t len = strlen(src);
if (!src)
return;
ptr = src + nchars;
while (*ptr != '\0') {
*(ptr - nchars) = *ptr;
ptr++;
}
ptr = src + len;
while (ptr >= (src + len) - nchars) {
*ptr = '\0';
ptr--;
}
}

17
c/utils.h

@ -10,4 +10,21 @@ char *rpd_strdup(const char *src); @@ -10,4 +10,21 @@ char *rpd_strdup(const char *src);
char *rpd_strsep(char **str, const char *sep);
const char *rpd_splitbyc(char **dest1, char **dest2, const char *src, const char sep);
/*!
* \brief Erases part of the string.
*
* This function moves characters to the beginning of the string
* and inserts '\0' at the original position without reallocation.
*
* To erase characters not from beginning of the string,
* you can pass a pointer to the beginning of the desired
* part of the string.
*
* \param src String to erase.
* \param nchars Number of charecters to erase.
*/
void rpd_strerase(char *src, int nchars);
#endif /* RAPIDA_UTILS_H_ENTRY */

12
config.mk

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
VERSION=0.3.1
VERSION=0.4
#arg Installation prefix
PREFIX=/usr/local
@ -31,16 +31,6 @@ ifeq ($(FCGI_SERVER), 1) @@ -31,16 +31,6 @@ ifeq ($(FCGI_SERVER), 1)
LDFLAGS += -lfcgi
endif
#flag Enable inja extension
EXTENSIONS_INJA ?= 0
#arg Dist path. Needed only if inja is enabled.
DIST_PATH = /var/www/html
ifneq ($(EXTENSIONS_INJA), 0)
CXXFLAGS+=-DEXTENSIONS_INJA -DDIST_PATH=\"$(DIST_PATH)\"
CXXSTD=-std=c++17
endif
#flag Multithread support
MT_ENABLED ?= 0
#arg Number of threads. 8 by default.

12
cxx/Request.cxx

@ -0,0 +1,12 @@ @@ -0,0 +1,12 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
/* Copyright 2022 Ivan Polyakov */
#include "Request.hxx"
using namespace rpd;
const char *Request::header(const char *key) const
{
rpd_keyval_item *hi = rpd_keyval_find(&req->headers, key);
return hi ? hi->val : NULL;
}

32
cxx/Response.cxx

@ -3,34 +3,12 @@ @@ -3,34 +3,12 @@
#include "Response.hxx"
#ifdef EXTENSIONS_INJA
#include <inja/inja.hpp>
#endif
using namespace rpd;
#ifdef EXTENSIONS_INJA
void Response::render(const char *path, nlohmann::json data)
int Response::header(const char *key, const char *value)
{
inja::Environment env;
inja::Template tpl;
try {
std::string tplpath = DIST_PATH;
tplpath += path;
tpl = env.parse_template(tplpath);
} catch (inja::FileError &e) {
std::cerr << e.what() << std::endl;
return;
}
try {
std::string result = env.render(tpl, data);
body(result.c_str());
} catch (inja::RenderError &e) {
std::cerr << e.what() << std::endl;
status(rpd_res_st_internal_server_error);
return;
}
return rpd_keyval_insert(
&this->res->headers,
key,
value);
}
#endif

8
examples/c/example.c

@ -28,6 +28,7 @@ static void products_handler(rpd_req *req, rpd_res *res, void *userdata) @@ -28,6 +28,7 @@ static void products_handler(rpd_req *req, rpd_res *res, void *userdata)
int bufflen = strlen(body) + strlen(cat->val) + strlen(id->val);
res->body = (char *) malloc(sizeof(char) * (bufflen + 1));
if (!res->body) {
perror("malloc");
res->status = rpd_res_st_internal_server_error;
return;
}
@ -37,12 +38,7 @@ static void products_handler(rpd_req *req, rpd_res *res, void *userdata) @@ -37,12 +38,7 @@ static void products_handler(rpd_req *req, rpd_res *res, void *userdata)
res->status = rpd_res_st_ok;
/*
* place all values in heap, please,
* because after call handler
* `req` and `res` will be freed
*/
res->content_type = strdup("text/html");
rpd_keyval_insert(&res->headers, "Content-Type", "text/html");
}
int main()

12
examples/c/minimal.c

@ -15,15 +15,17 @@ static void home_page_handler(rpd_req *req, rpd_res *res, void *userdata) @@ -15,15 +15,17 @@ static void home_page_handler(rpd_req *req, rpd_res *res, void *userdata)
/* Check request method */
switch (req->method) {
case HEAD:
/* Process GET request */
res->status = rpd_res_st_ok;
rpd_keyval_insert(&res->headers, "Content-Type", "text/plain");
break;
case GET:
res->status = rpd_res_st_ok;
rpd_keyval_insert(&res->headers, "Content-Type", "text/plain");
/* Please allocate data on the heap,
/* Please allocate body on the heap,
* because after calling this handler
* Rapida will free it all.
* Rapida will free it.
*/
res->content_type = strdup("text/plain");
case GET:
res->body = strdup("Hello World!");
break;
default:

2
examples/cxx/example.cxx

@ -35,7 +35,7 @@ protected: @@ -35,7 +35,7 @@ protected:
virtual void handle_head(const rpd::Request &req, rpd::Response &res) override
{
res.status(rpd_res_st_ok);
res.content_type("text/html");
res.header("Content-Type", "text/html");
}
};

3
examples/cxx/minimal.cxx

@ -1,7 +1,6 @@ @@ -1,7 +1,6 @@
#include "../../include/rapida.hxx"
#include "../../include/servers/tcp.h"
/*
* \brief Home page route handler.
*/
@ -29,7 +28,7 @@ protected: @@ -29,7 +28,7 @@ protected:
virtual void handle_head(const rpd::Request &req, rpd::Response &res) override
{
res.status(rpd_res_st_ok);
res.content_type("text/plain");
res.header("Content-Type", "text/plain");
}
};

18
include/KeyVal.hxx

@ -36,6 +36,24 @@ public: @@ -36,6 +36,24 @@ public:
_keyval = keyval;
}
/*!
* \brief Is the storage unique?
*/
bool unique() const
{
return _keyval->unique;
}
/*!
* \brief Sets the uniqueness flag.
*
* \param is_unique Uniqueness flag.
*/
void unique(bool is_unique)
{
_keyval->unique = is_unique;
}
/*!
* \brief Returns real key-value storage.
*

21
include/Request.hxx

@ -69,24 +69,23 @@ public: @@ -69,24 +69,23 @@ public:
}
/*!
* \brief Gets authorization string.
* \brief Gets the request header.
*
* \return Authorization string.
* \params key Header key.
*
* \return Found header or NULL.
*/
const char *authorization() const
{
return req->auth;
}
const char *header(const char *key) const;
/*!
* \brief Gets cookie string.
* \brief Gets all request headers.
*
* \return Cookie string.
* \return Key-value pairs.
*/
const char *cookie() const
KeyVal headers() const
{
return req->cookie;
};
return &req->headers;
}
/*!
* \brief Gets request body content.

52
include/Response.hxx

@ -8,10 +8,6 @@ @@ -8,10 +8,6 @@
#include <stdlib.h>
#include <string.h>
#ifdef EXTENSIONS_INJA
#include <nlohmann/json.hpp>
#endif
namespace rpd {
/*!
* \brief C++ response wrapper.
@ -59,43 +55,14 @@ public: @@ -59,43 +55,14 @@ public:
}
/*!
* \brief Sets _location_ response field.
*
* \param location Location URL.
*/
void location(const char *location)
{
if (res->location)
free(res->location);
res->location = strdup(location);
}
/*!
* \brief Sets _Content-Type_ response field.
*
* \param content_type Response content type.
*/
void content_type(const char *content_type)
{
if (res->content_type)
free(res->content_type);
res->content_type = strdup(content_type);
}
/*!
* \brief Sets cookie field.
* \brief Sets response header.
*
* Adds new cookie field to key-value storage.
* If you need to set cookie parameters such as lifetime,
* place it to val.
* \param key Header key.
* \param value Header value
*
* \param key Cookie field key.
* \param val Cookie field value.
* \return Status code. 0 is success.
*/
void cookie(const char *key, const char *val)
{
rpd_keyval_insert(&res->cookie, key, val);
}
int header(const char *key, const char *value);
/*!
* \brief Sets response body.
@ -109,15 +76,6 @@ public: @@ -109,15 +76,6 @@ public:
res->body = strdup(body);
}
#ifdef EXTENSIONS_INJA
/*!
* \brief Render data to HTML template.
* \param path Path to HTML template relative to dist location.
* \param data Template data to interpolate.
*/
void render(const char *path, nlohmann::json data);
#endif
/*!
* \brief Destructor.
*/

18
include/keyval.h

@ -9,7 +9,7 @@ @@ -9,7 +9,7 @@
#ifndef RAPIDA_KEYVAL_H_ENTRY
#define RAPIDA_KEYVAL_H_ENTRY
#include "unistd.h"
#include <unistd.h>
#ifdef __cplusplus
extern "C" {
@ -30,6 +30,7 @@ typedef struct { @@ -30,6 +30,7 @@ typedef struct {
rpd_keyval_item *items; /**< Key-value pairs array. */
int size; /**< Number of elements in rpd_keyval::items. */
int capacity; /**< Current capacity (allocated memory). */
int unique; /**< Unique flag */
} rpd_keyval;
/*!
@ -70,6 +71,21 @@ int rpd_keyval_insert(rpd_keyval *keyval, const char *key, const char *value); @@ -70,6 +71,21 @@ int rpd_keyval_insert(rpd_keyval *keyval, const char *key, const char *value);
*/
rpd_keyval_item *rpd_keyval_find(const rpd_keyval *keyval, const char *key);
/*!
* \brief Finds all key-value pair by key.
*
* Useful when storage is not unique.
* In request header fields, for example.
*
* Last item will be NULL.
*
* \param keyval Key-value storage instance.
* \param key Key to search.
*
* \return Found items or NULL.
*/
rpd_keyval_item **rpd_keyval_findall(const rpd_keyval *keyval, const char *key);
/*!
* \brief Free key-val pairs and reset.
*

3
include/request.h

@ -36,10 +36,9 @@ enum rpd_req_methods { @@ -36,10 +36,9 @@ enum rpd_req_methods {
*/
typedef struct {
enum rpd_req_methods method; /**< Request method. */
char *auth; /**< Authorization field. */
char *cookie; /**< Cookie field. */
char *body; /**< Body field. */
rpd_url path; /**< Requested URL. */
rpd_keyval headers; /**< Request headers. */
rpd_keyval query; /**< Query. */
rpd_keyval params; /**< Dynamic parameters. */
} rpd_req;

4
include/response.h

@ -86,10 +86,8 @@ enum rpd_res_statuses { @@ -86,10 +86,8 @@ enum rpd_res_statuses {
*/
typedef struct {
enum rpd_res_statuses status; /**< Response status code. */
char *location; /**< Location field. */
char *content_type; /**< Content type. */
char *body; /**< Response body. */
rpd_keyval cookie; /**< Set-Cookie fields. */
rpd_keyval headers; /**< Response headers. */
} rpd_res;
/*!

57
servers/fcgi.c

@ -2,7 +2,11 @@ @@ -2,7 +2,11 @@
/* Copyright 2022 Ivan Polyakov */
#include "../include/servers/fcgi.h"
#include "../c/utils.h"
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef MT_ENABLED
#include <pthread.h>
@ -56,6 +60,8 @@ static int read_fcgx_req_body(char **dest, FCGX_Request *req); @@ -56,6 +60,8 @@ static int read_fcgx_req_body(char **dest, FCGX_Request *req);
*/
static void send_response(rpd_res *res, FCGX_Stream *out);
static int env_to_req_header(char **key, char **val, const char *envp);
int rpd_fcgi_server_start(rpd_app *app, const char *sock_path)
{
FCGX_Init();
@ -155,9 +161,40 @@ static int fcgx_to_rpd_req(rpd_req *dest, FCGX_Request *req) @@ -155,9 +161,40 @@ static int fcgx_to_rpd_req(rpd_req *dest, FCGX_Request *req)
dest->method = rpd_req_smethod(FCGX_GetParam("REQUEST_METHOD", req->envp));
rpd_url_parse(&dest->path, FCGX_GetParam("DOCUMENT_URI", req->envp));
dest->auth = FCGX_GetParam("HTTP_AUTHORIZATION", req->envp);
dest->cookie = FCGX_GetParam("HTTP_COOKIE", req->envp);
// dest->auth = FCGX_GetParam("HTTP_AUTHORIZATION", req->envp);
// dest->cookie = FCGX_GetParam("HTTP_COOKIE", req->envp);
rpd_keyval_init(&dest->params, 0);
rpd_keyval_init(&dest->headers, 0);
dest->headers.unique = 0;
char **env = req->envp;
char *key = NULL, *val = NULL;
while (*(++env)) {
char *ptr = NULL;
/* keep only http request fields */
ptr = strstr(*env, "HTTP");
if (ptr == NULL || ptr - *env != 0)
continue;
env_to_req_header(&key, &val, *env);
if (!val || !key)
continue;
rpd_strerase(key, 5);
ptr = key;
while (*ptr) {
*ptr = tolower(*ptr);
if (ptr == key || *(ptr - 1) == '-')
*ptr = toupper(*ptr);
if (*ptr == '_')
*ptr = '-';
ptr++;
}
rpd_keyval_insert(&dest->headers, key, val);
free(key);
free(val);
}
if (dest->method != GET) {
if (read_fcgx_req_body(&dest->body, req)) {
@ -186,3 +223,19 @@ static int read_fcgx_req_body(char **dest, FCGX_Request *req) @@ -186,3 +223,19 @@ static int read_fcgx_req_body(char **dest, FCGX_Request *req)
return 0;
}
static int env_to_req_header(char **key, char **val, const char *env)
{
const char *ptr = NULL;
rpd_splitbyc(key, val, env, '=');
if (!*key || !*val) {
if (*key)
free(*key);
if (*val)
free(*val);
return 1;
}
return 0;
}

17
servers/tcp.c

@ -45,6 +45,23 @@ static int mg_to_rpd_req(rpd_req *req, struct mg_http_message *msg) @@ -45,6 +45,23 @@ static int mg_to_rpd_req(rpd_req *req, struct mg_http_message *msg)
rpd_query_parse(&req->query, tmp);
}
size_t i, max = sizeof(msg->headers) / sizeof(msg->headers[0]);
rpd_keyval_init(&req->headers, max);
req->headers.unique = 0;
// Iterate over request headers
char *key = NULL, *val = NULL;
for (i = 0; i < max && msg->headers[i].name.len > 0; i++) {
struct mg_str *k = &msg->headers[i].name, *v = &msg->headers[i].value;
mg_str_alloc(&key, *k);
mg_str_alloc(&val, *v);
rpd_keyval_insert(&req->headers, key, val);
}
free(key);
free(val);
free(tmp);
return 0;
}

2
tests/app.cxx

@ -9,7 +9,7 @@ using namespace rpd; @@ -9,7 +9,7 @@ using namespace rpd;
TEST_CASE("Application")
{
rpd_app app;
int res = rpd_app_create(&app, "/tmp/rapida.test.socket");
int res = rpd_app_create(&app);
SECTION("App creation")
{

18
tests/keyval.cxx

@ -36,6 +36,24 @@ TEST_CASE("Key-value storage") @@ -36,6 +36,24 @@ TEST_CASE("Key-value storage")
REQUIRE(std::string(item->val) == "val");
}
SECTION("Passing duplicates to non-unique storage and finding them")
{
int sz = keyval.size;
keyval.unique = 0;
rpd_keyval_insert(&keyval, "Set-Cookie", "param=val");
rpd_keyval_insert(&keyval, "Set-Cookie", "param1=val1");
REQUIRE(keyval.size == sz + 2);
rpd_keyval_item **cookies = rpd_keyval_findall(&keyval, "Set-Cookie");
REQUIRE(cookies != NULL);
REQUIRE(std::string(cookies[0]->key) == "Set-Cookie");
REQUIRE(std::string(cookies[0]->val) == "param=val");
REQUIRE(std::string(cookies[1]->key) == "Set-Cookie");
REQUIRE(std::string(cookies[1]->val) == "param1=val1");
REQUIRE(cookies[2] == NULL);
}
SECTION("Cleanup")
{
rpd_keyval_cleanup(&keyval);

Loading…
Cancel
Save