1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00

Merge pull request #13889 from cgundogan/pr/coca

nanocoap: cache for CoAP responses
This commit is contained in:
Martine Lenders 2022-05-10 23:01:47 +02:00 committed by GitHub
commit 02fb040d9c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 985 additions and 6 deletions

View File

@ -14273,6 +14273,7 @@ sys/include/net/coap\.h:[0-9]+: warning: Member COAP_OPT_ACCEPT \(macro definiti
sys/include/net/coap\.h:[0-9]+: warning: Member COAP_OPT_BLOCK1 \(macro definition\) of group net_coap is not documented\.
sys/include/net/coap\.h:[0-9]+: warning: Member COAP_OPT_BLOCK2 \(macro definition\) of group net_coap is not documented\.
sys/include/net/coap\.h:[0-9]+: warning: Member COAP_OPT_CONTENT_FORMAT \(macro definition\) of group net_coap is not documented\.
sys/include/net/coap\.h:[0-9]+: warning: Member COAP_OPT_MAX_AGE \(macro definition\) of group net_coap is not documented\.
sys/include/net/coap\.h:[0-9]+: warning: Member COAP_OPT_LOCATION_PATH \(macro definition\) of group net_coap is not documented\.
sys/include/net/coap\.h:[0-9]+: warning: Member COAP_OPT_LOCATION_QUERY \(macro definition\) of group net_coap is not documented\.
sys/include/net/coap\.h:[0-9]+: warning: Member COAP_OPT_OBSERVE \(macro definition\) of group net_coap is not documented\.

View File

@ -662,6 +662,11 @@ ifneq (,$(filter nanocoap_sock,$(USEMODULE)))
USEMODULE += ztimer_msec
endif
ifneq (,$(filter nanocoap_cache,$(USEMODULE)))
USEMODULE += ztimer_sec
USEMODULE += hashes
endif
ifneq (,$(filter nanocoap_%,$(USEMODULE)))
USEMODULE += nanocoap
endif

View File

@ -40,6 +40,7 @@ extern "C" {
#define COAP_OPT_LOCATION_PATH (8)
#define COAP_OPT_URI_PATH (11)
#define COAP_OPT_CONTENT_FORMAT (12)
#define COAP_OPT_MAX_AGE (14)
#define COAP_OPT_URI_QUERY (15)
#define COAP_OPT_ACCEPT (17)
#define COAP_OPT_LOCATION_QUERY (20)

View File

@ -0,0 +1,234 @@
/*
* Copyright (C) 2020 HAW Hamburg
*
* 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 net_nanocoap_cache Nanocoap-Cache implementation
* @ingroup net_nanocoap
* @brief A cache implementation for nanocoap response messages
*
* @{
*
* @file
* @brief nanocoap-cache API
*
* @author Cenk Gündoğan <cenk.guendogan@haw-hamburg.de>
*/
#ifndef NET_NANOCOAP_CACHE_H
#define NET_NANOCOAP_CACHE_H
#include <assert.h>
#include <stdint.h>
#include "clist.h"
#include "net/nanocoap.h"
#include "hashes/sha256.h"
#include "ztimer.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief The number of maximum cache entries.
*/
#ifndef CONFIG_NANOCOAP_CACHE_ENTRIES
#define CONFIG_NANOCOAP_CACHE_ENTRIES (8)
#endif
/**
* @brief The length of the cache key in bytes.
*/
#ifndef CONFIG_NANOCOAP_CACHE_KEY_LENGTH
#define CONFIG_NANOCOAP_CACHE_KEY_LENGTH (8)
#endif
/**
* @brief Size of the buffer to store responses in the cache.
*/
#ifndef CONFIG_NANOCOAP_CACHE_RESPONSE_SIZE
#define CONFIG_NANOCOAP_CACHE_RESPONSE_SIZE (128)
#endif
/**
* @brief Cache container that holds a @p coap_pkt_t struct.
*/
typedef struct {
/**
* @brief needed for clist_t, must be the first struct member!
*/
clist_node_t node;
/**
* @brief the calculated cache key, see nanocoap_cache_key_generate().
*/
uint8_t cache_key[CONFIG_NANOCOAP_CACHE_KEY_LENGTH];
/**
* @brief packet representation of the response
*/
coap_pkt_t response_pkt;
/**
* @brief buffer to hold the response message.
*/
uint8_t response_buf[CONFIG_NANOCOAP_CACHE_RESPONSE_SIZE];
size_t response_len; /**< length of the message in @p response */
unsigned request_method; /**< the method of the initial request */
/**
* @brief absolute system time in seconds until which this cache entry
* is considered valid.
*/
ztimer_now_t max_age;
} nanocoap_cache_entry_t;
/**
* @brief Typedef for the cache replacement strategy on full cache list.
*
* @return 0 on successfully replacing a cache element
* @return -1 on error
*/
typedef int (*nanocoap_cache_replacement_strategy_t)(void);
/**
* @brief Typedef for the cache update strategy on element access.
*
* @param[in] node The accessed node.
*
* @return 0 on successfully updating the element
* @return -1 on error
*/
typedef int (*nanocoap_cache_update_strategy_t)(clist_node_t *node);
/**
* @brief Initializes the internal state of the nanocoap cache.
*/
#if IS_USED(MODULE_NANOCOAP_CACHE)
void nanocoap_cache_init(void);
#else
static inline void nanocoap_cache_init(void)
{
return;
}
#endif
/**
* @brief Returns the number of cached entries.
*
* @return Number of cached entries
*/
size_t nanocoap_cache_used_count(void);
/**
* @brief Returns the number of unused cache entries.
*
* @return Number of unused cache entries
*/
size_t nanocoap_cache_free_count(void);
/**
* @brief Determines if a response is cacheable and modifies the cache
* as reflected in RFC7252, Section 5.9.
* @param[in] cache_key The cache key of the request
* @param[in] request_method The method of the initial request
* @param[in] resp The response to operate on
* @param[in] resp_len The actual length of the response in @p resp
*
* @return 0 on successfully handling the response
* @return -1 on error
*/
int nanocoap_cache_process(const uint8_t *cache_key, unsigned request_method,
const coap_pkt_t *resp, size_t resp_len);
/**
* @brief Creates a new or gets an existing cache entry using the
* request packet.
*
* @param[in] req The request to calculate the cache-key
* @param[in] resp The response to add to the cache
* @param[in] resp_len The actual length of the response message in @p resp
*
* @return The previously existing or newly added cache entry on success
* @return NULL, if there is no space left
*/
nanocoap_cache_entry_t *nanocoap_cache_add_by_req(const coap_pkt_t *req,
const coap_pkt_t *resp,
size_t resp_len);
/**
* @brief Creates a new or gets an existing cache entry using the cache key.
*
* @param[in] cache_key The cache key of the request
* @param[in] request_method The method of the initial request
* @param[in] resp The response to add to the cache
* @param[in] resp_len The actual length of the response in @p resp
*
* @return The previously existing or newly added cache entry on success
* @return NULL, if there is no space left
*/
nanocoap_cache_entry_t *nanocoap_cache_add_by_key(const uint8_t *cache_key,
unsigned request_method,
const coap_pkt_t *resp,
size_t resp_len);
/**
* @brief Performs a cache lookup based on the @p req.
*
* @param[in] req The request to calculate the cache-key
*
* @return An existing cache entry on cache hit
* @return NULL on cache miss
*/
nanocoap_cache_entry_t *nanocoap_cache_request_lookup(const coap_pkt_t *req);
/**
* @brief Performs a cache lookup based on the cache key of a request.
*
* @param[in] cache_key The cache key of a request
*
* @return An existing cache entry on cache hit
* @return NULL on cache miss
*/
nanocoap_cache_entry_t *nanocoap_cache_key_lookup(const uint8_t *cache_key);
/**
* @brief Deletes the provided cache entry @p ce.
*
* @param[in] ce The cache entry to delete
*
* @return 0 on success
* @return -1 if entry is not available in the cache
*/
int nanocoap_cache_del(const nanocoap_cache_entry_t *ce);
/**
* @brief Generates a cache key based on the request @p req.
*
* @param[in] req The request to generate the cache key from
* @param[out] cache_key The generated cache key
*/
void nanocoap_cache_key_generate(const coap_pkt_t *req, uint8_t *cache_key);
/**
* @brief Compares two cache keys.
*
* @param[in] cache_key1 The first cache key in the comparison
* @param[in] cache_key2 The second cache key in the comparison
*
* @return 0 if cache keys are equal
* @return <0 or 0> (see memcmp()) for unequal cache keys
*/
ssize_t nanocoap_cache_key_compare(uint8_t *cache_key1, uint8_t *cache_key2);
#ifdef __cplusplus
}
#endif
#endif /* NET_NANOCOAP_CACHE_H */
/** @} */

View File

@ -16,6 +16,7 @@
#include "net/gcoap.h"
#include "net/gcoap/forward_proxy.h"
#include "uri_parser.h"
#include "net/nanocoap/cache.h"
#define ENABLE_DEBUG 0
#include "debug.h"
@ -23,6 +24,9 @@
typedef struct {
int in_use;
sock_udp_ep_t ep;
#if IS_USED(MODULE_NANOCOAP_CACHE)
uint8_t cache_key[CONFIG_NANOCOAP_CACHE_KEY_LENGTH];
#endif
} client_ep_t;
static uint8_t proxy_req_buf[CONFIG_GCOAP_PDU_BUF_SIZE];
@ -50,6 +54,58 @@ gcoap_listener_t forward_proxy_listener = {
void gcoap_forward_proxy_init(void)
{
gcoap_register_listener(&forward_proxy_listener);
/* initialize the nanocoap cache operation, if compiled */
if (IS_USED(MODULE_NANOCOAP_CACHE)) {
nanocoap_cache_init();
}
}
static int _cache_build_response(nanocoap_cache_entry_t *ce,
coap_pkt_t *pdu,
uint8_t *buf,
size_t len)
{
/* Use the same code from the cached content. Use other header
* fields from the incoming request */
gcoap_resp_init(pdu, buf, len, ce->response_pkt.hdr->code);
/* copy all options and possible payload from the cached response
* to the new response */
unsigned header_len_req = coap_get_total_hdr_len(pdu);
unsigned header_len_cached = coap_get_total_hdr_len(&ce->response_pkt);
unsigned opt_payload_len = ce->response_len - header_len_cached;
memcpy((buf + header_len_req),
(ce->response_buf + header_len_cached),
opt_payload_len);
return header_len_req + opt_payload_len;
}
static int _cache_lookup_and_process(coap_pkt_t *pdu,
uint8_t *buf,
size_t len,
client_ep_t *cep)
{
(void) cep;
uint8_t cache_key[SHA256_DIGEST_LENGTH];
ztimer_now_t now = ztimer_now(ZTIMER_SEC);
nanocoap_cache_key_generate(pdu, cache_key);
nanocoap_cache_entry_t *ce = nanocoap_cache_key_lookup(cache_key);
/* cache hit, methods are equal, and cache entry is not stale */
if (ce &&
(ce->request_method == coap_get_code(pdu)) &&
(ce->max_age > now)) {
/* use response from cache */
return _cache_build_response(ce, pdu, buf, len);
}
#if IS_USED(MODULE_NANOCOAP_CACHE)
memcpy(cep->cache_key, cache_key, CONFIG_NANOCOAP_CACHE_KEY_LENGTH);
#endif
return 0;
}
static client_ep_t *_allocate_client_ep(sock_udp_ep_t *ep)
@ -91,24 +147,25 @@ static int _request_matcher_forward_proxy(gcoap_listener_t *listener,
static ssize_t _forward_proxy_handler(coap_pkt_t *pdu, uint8_t *buf,
size_t len, void *ctx)
{
int pdu_len = 0;
sock_udp_ep_t *remote = (sock_udp_ep_t *)ctx;
int proxy_res = gcoap_forward_proxy_request_process(pdu, remote);
pdu_len = gcoap_forward_proxy_request_process(pdu, remote);
/* Out of memory, reply with 5.00 */
if (proxy_res == -ENOMEM) {
if (pdu_len == -ENOMEM) {
return gcoap_response(pdu, buf, len, COAP_CODE_INTERNAL_SERVER_ERROR);
}
/* Proxy-Uri malformed, reply with 4.02 */
else if (proxy_res == -EINVAL) {
else if (pdu_len == -EINVAL) {
return gcoap_response(pdu, buf, len, COAP_CODE_BAD_OPTION);
}
/* scheme not supported */
else if (proxy_res == -EPERM) {
else if (pdu_len == -EPERM) {
return gcoap_response(pdu, buf, len, COAP_CODE_PROXYING_NOT_SUPPORTED);
}
return 0;
return pdu_len;
}
static bool _parse_endpoint(sock_udp_ep_t *remote,
@ -194,6 +251,19 @@ static void _forward_resp_handler(const gcoap_request_memo_t *memo,
(pdu->payload -
(uint8_t *)pdu->hdr + pdu->payload_len),
&cep->ep);
#if IS_USED(MODULE_NANOCOAP_CACHE)
coap_pkt_t req;
if (memo->send_limit == GCOAP_SEND_LIMIT_NON) {
req.hdr = (coap_hdr_t *) &memo->msg.hdr_buf[0];
}
else {
req.hdr = (coap_hdr_t *) memo->msg.data.pdu_buf;
}
size_t pdu_len = pdu->payload_len +
(pdu->payload - (uint8_t *)pdu->hdr);
nanocoap_cache_process(cep->cache_key, coap_get_code(&req), pdu, pdu_len);
#endif
}
_free_client_ep(cep);
}
@ -312,7 +382,6 @@ int gcoap_forward_proxy_request_process(coap_pkt_t *pkt,
sock_udp_ep_t *client) {
char *uri;
uri_parser_result_t urip;
ssize_t optlen = 0;
client_ep_t *cep = _allocate_client_ep(client);
@ -321,6 +390,20 @@ int gcoap_forward_proxy_request_process(coap_pkt_t *pkt,
return -ENOMEM;
}
if (IS_USED(MODULE_NANOCOAP_CACHE)) {
int pdu_len = _cache_lookup_and_process(pkt,
(uint8_t *)pkt->hdr,
CONFIG_GCOAP_PDU_BUF_SIZE,
cep);
/* if a valid cache entry was found, then pdu_len contains the
* length of that response message */
if (pdu_len > 0) {
_free_client_ep(cep);
return pdu_len;
}
/* if there was no cache hit, then we continue forwarding */
}
optlen = coap_get_proxy_uri(pkt, &uri);
if (optlen < 0) {

View File

@ -35,4 +35,24 @@ config NANOCOAP_QS_MAX
int "Maximum length of a query string written to a message"
default 64
menuconfig KCONFIG_USEMODULE_NANOCOAP_CACHE
bool "Configure Nanocoap Cache module"
depends on USEMODULE_NANOCOAP_CACHE
if KCONFIG_USEMODULE_NANOCOAP_CACHE
config NANOCOAP_CACHE_ENTRIES
int "Number of maximum cache entries"
default 8
config NANOCOAP_CACHE_KEY_LENGTH
int "The length of the cache key in bytes"
default 8
config NANOCOAP_CACHE_RESPONSE_SIZE
int "Size of the buffer to store responses in the cache"
default 128
endif # KCONFIG_USEMODULE_NANOCOAP_CACHE
endif # KCONFIG_USEMODULE_NANOCOAP

View File

@ -0,0 +1,321 @@
/*
* Copyright (C) 2020 HAW Hamburg
*
* 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 net_nanocoap_cache
* @{
*
* @file
* @brief Implementation of common functions for the nanocoap-cache
*
* @author Cenk Gündoğan <cenk.guendogan@haw-hamburg.de>
*
* @}
*/
#include <string.h>
#include "kernel_defines.h"
#include "net/nanocoap/cache.h"
#include "hashes/sha256.h"
#define ENABLE_DEBUG 0
#include "debug.h"
static int _cache_replacement_lru(void);
static int _cache_update_lru(clist_node_t *node);
static clist_node_t _cache_list_head = { NULL };
static clist_node_t _empty_list_head = { NULL };
static nanocoap_cache_entry_t _cache_entries[CONFIG_NANOCOAP_CACHE_ENTRIES];
static const nanocoap_cache_replacement_strategy_t _replacement_strategy = _cache_replacement_lru;
static const nanocoap_cache_update_strategy_t _update_strategy = _cache_update_lru;
static int _cache_replacement_lru(void)
{
clist_node_t *lru_node = clist_lpeek(&_cache_list_head);
/* no element in the list */
if (!lru_node) {
return -1;
}
nanocoap_cache_entry_t *lru_ce = container_of(lru_node, nanocoap_cache_entry_t, node);
return nanocoap_cache_del(lru_ce);
}
static int _cache_update_lru(clist_node_t *node)
{
if (clist_remove(&_cache_list_head, node)) {
/* Move an accessed node to the end of the list. Least
* recently used nodes are at the beginning of this list */
clist_rpush(&_cache_list_head, node);
return 0;
}
return -1;
}
void nanocoap_cache_init(void)
{
_cache_list_head.next = NULL;
_empty_list_head.next = NULL;
memset(_cache_entries, 0, sizeof(_cache_entries));
/* construct list of empty entries */
for (unsigned i = 0; i < CONFIG_NANOCOAP_CACHE_ENTRIES; i++) {
clist_rpush(&_empty_list_head, &_cache_entries[i].node);
}
}
size_t nanocoap_cache_used_count(void)
{
return clist_count(&_cache_list_head);
}
size_t nanocoap_cache_free_count(void)
{
return clist_count(&_empty_list_head);
}
void nanocoap_cache_key_generate(const coap_pkt_t *req, uint8_t *cache_key)
{
sha256_context_t ctx;
sha256_init(&ctx);
coap_optpos_t opt = {0, 0};
uint8_t *value;
for (unsigned i = 0; i < req->options_len; i++) {
ssize_t optlen = coap_opt_get_next(req, &opt, &value, !i);
if (optlen >= 0) {
/* skip NoCacheKey,
see https://tools.ietf.org/html/rfc7252#section-5.4.6 */
if ((opt.opt_num & 0x1E) == 0x1C) {
continue;
}
sha256_update(&ctx, value, optlen);
}
}
switch (req->hdr->code) {
case COAP_METHOD_FETCH:
sha256_update(&ctx, req->payload, req->payload_len);
break;
default:
break;
}
sha256_final(&ctx, cache_key);
}
ssize_t nanocoap_cache_key_compare(uint8_t *cache_key1, uint8_t *cache_key2)
{
return memcmp(cache_key1, cache_key2, CONFIG_NANOCOAP_CACHE_KEY_LENGTH);
}
static int _compare_cache_keys(clist_node_t *ce, void *arg)
{
nanocoap_cache_entry_t *iterator = container_of(ce, nanocoap_cache_entry_t, node);
uint8_t *cache_key = (uint8_t *) arg;
if (!memcmp(iterator->cache_key, cache_key, CONFIG_NANOCOAP_CACHE_KEY_LENGTH)) {
return 1;
}
return 0;
}
static clist_node_t *_nanocoap_cache_foreach(const uint8_t *key)
{
return clist_foreach(&_cache_list_head, _compare_cache_keys, (uint8_t *)key);
}
nanocoap_cache_entry_t *nanocoap_cache_key_lookup(const uint8_t *key)
{
clist_node_t *node = _nanocoap_cache_foreach(key);
if (node) {
_update_strategy(node);
}
return container_of(node, nanocoap_cache_entry_t, node);
}
nanocoap_cache_entry_t *nanocoap_cache_request_lookup(const coap_pkt_t *req)
{
uint8_t cache_key[SHA256_DIGEST_LENGTH];
nanocoap_cache_entry_t *ce;
/* generate cache key */
nanocoap_cache_key_generate(req, cache_key);
/* check if cache key already exists */
ce = nanocoap_cache_key_lookup(cache_key);
return ce;
}
int nanocoap_cache_process(const uint8_t *cache_key, unsigned request_method,
const coap_pkt_t *resp, size_t resp_len)
{
nanocoap_cache_entry_t *ce;
ce = nanocoap_cache_key_lookup(cache_key);
/* This response is not cacheable. */
if (resp->hdr->code == COAP_CODE_CREATED) {
/* NO OP */
}
/* This response is not cacheable. However, a cache MUST mark any
stored response for the deleted resource as not fresh.
*/
else if (resp->hdr->code == COAP_CODE_DELETED) {
if (ce) {
/* set max_age to now(), so that the cache is considered
* stale immdiately */
ce->max_age = ztimer_now(ZTIMER_SEC);
}
}
/* When a cache that recognizes and processes the ETag response
option receives a 2.03 (Valid) response, it MUST update the
stored response with the value of the Max-Age Option included
in the response (explicitly, or implicitly as a default value;
see also Section 5.6.2). For each type of Safe-to-Forward
option present in the response, the (possibly empty) set of
options of this type that are present in the stored response
MUST be replaced with the set of options of this type in the
response received. (Unsafe options may trigger similar
option-specific processing as defined by the option.)
*/
else if (resp->hdr->code == COAP_CODE_VALID) {
if (ce) {
/* refresh max_age() */
uint32_t max_age = 60;
coap_opt_get_uint((coap_pkt_t *)resp, COAP_OPT_MAX_AGE, &max_age);
ce->max_age = ztimer_now(ZTIMER_SEC) + max_age;
}
/* TODO: handle the copying of the new options (if changed) */
}
/* This response is not cacheable. However, a cache MUST mark any
stored response for the changed resource as not fresh.
*/
else if (resp->hdr->code == COAP_CODE_CHANGED) {
if (ce) {
/* set max_age to now(), so that the cache is considered
* stale immdiately */
ce->max_age = ztimer_now(ZTIMER_SEC);
}
}
/* This response is cacheable: Caches can use the Max-Age Option
to determine freshness and (if present) the
ETag Option for validation.
*/
else if (resp->hdr->code == COAP_CODE_CONTENT) {
if (NULL == nanocoap_cache_add_by_key(cache_key, request_method,
resp, resp_len)) {
/* no space left in the cache? */
return -1;
}
/* TODO: ETAG handling */
}
return 0;
}
static nanocoap_cache_entry_t *_nanocoap_cache_pop(void)
{
clist_node_t *node;
node = clist_lpop(&_empty_list_head);
if (node != NULL) {
return container_of(node, nanocoap_cache_entry_t, node);
}
else {
return NULL;
}
}
nanocoap_cache_entry_t *nanocoap_cache_add_by_key(const uint8_t *cache_key,
unsigned request_method,
const coap_pkt_t *resp,
size_t resp_len)
{
nanocoap_cache_entry_t *ce = nanocoap_cache_key_lookup(cache_key);
bool add_to_cache = false;
if (resp_len > CONFIG_NANOCOAP_CACHE_RESPONSE_SIZE) {
DEBUG("nanocoap_cache: response too large to cache (%lu > %d)\n",
(long unsigned)resp_len, CONFIG_NANOCOAP_CACHE_RESPONSE_SIZE);
return NULL;
}
if (!ce) {
/* did not find .. get an empty cache container */
ce = _nanocoap_cache_pop();
add_to_cache = true;
}
/* no space left */
if (!ce) {
/* could not remove any entry */
if (_replacement_strategy()) {
return NULL;
}
/* could remove an entry */
ce = _nanocoap_cache_pop();
add_to_cache = true;
if (!ce) {
/* still no free space ? stop trying now */
return NULL;
}
}
memcpy(ce->cache_key, cache_key, CONFIG_NANOCOAP_CACHE_KEY_LENGTH);
memcpy(&ce->response_pkt, resp, sizeof(coap_pkt_t));
memcpy(&ce->response_buf, resp->hdr, resp_len);
ce->response_pkt.hdr = (coap_hdr_t *) ce->response_buf;
ce->response_pkt.payload = ce->response_buf + (resp->payload - ((uint8_t *)resp->hdr));
ce->response_len = resp_len;
ce->request_method = request_method;
/* default value is 60 seconds, if MAX_AGE not present */
uint32_t max_age = 60;
coap_opt_get_uint((coap_pkt_t *)resp, COAP_OPT_MAX_AGE, &max_age);
ce->max_age = ztimer_now(ZTIMER_SEC) + max_age;
if (add_to_cache) {
clist_rpush(&_cache_list_head, &ce->node);
}
return ce;
}
nanocoap_cache_entry_t *nanocoap_cache_add_by_req(const coap_pkt_t *req,
const coap_pkt_t *resp,
size_t resp_len)
{
uint8_t cache_key[SHA256_DIGEST_LENGTH];
/* generate cache key */
nanocoap_cache_key_generate(req, cache_key);
return nanocoap_cache_add_by_key(cache_key,
coap_get_code((coap_pkt_t *)req),
resp,
resp_len);
}
int nanocoap_cache_del(const nanocoap_cache_entry_t *ce)
{
clist_node_t *entry = clist_find(&_cache_list_head, &ce->node);
if (entry) {
clist_remove(&_cache_list_head, entry);
memset(entry, 0, sizeof(nanocoap_cache_entry_t));
clist_rpush(&_empty_list_head, entry);
return 0;
}
return -1;
}

View File

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

View File

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

View File

@ -0,0 +1,275 @@
/*
* Copyright (c) 2020 Cenk Gündoğan
*
* 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
*/
#include <errno.h>
#include <stdint.h>
#include <string.h>
#include <stdio.h>
#include "embUnit.h"
#include "net/nanocoap/cache.h"
#include "ztimer.h"
#include "hashes/sha256.h"
#include "unittests-constants.h"
#include "tests-nanocoap_cache.h"
#define _BUF_SIZE (128U)
static void test_nanocoap_cache__cachekey(void)
{
uint8_t digest1[SHA256_DIGEST_LENGTH];
uint8_t digest2[SHA256_DIGEST_LENGTH];
uint8_t buf1[_BUF_SIZE];
uint8_t buf2[_BUF_SIZE];
coap_pkt_t pkt1;
coap_pkt_t pkt2;
uint16_t msgid = 0xABCD;
uint8_t token[2] = {0xDA, 0xEC};
char path[] = "/time";
char path2[] = "/time2";
size_t len;
/* 1. packet */
len = coap_build_hdr((coap_hdr_t *)&buf1[0], COAP_TYPE_NON,
&token[0], 2, COAP_METHOD_GET, msgid);
coap_pkt_init(&pkt1, &buf1[0], sizeof(buf1), len);
coap_opt_add_string(&pkt1, COAP_OPT_URI_PATH, &path[0], '/');
coap_opt_finish(&pkt1, COAP_OPT_FINISH_NONE);
/* 2. packet */
len = coap_build_hdr((coap_hdr_t *)&buf2[0], COAP_TYPE_NON,
&token[0], 2, COAP_METHOD_GET, msgid);
coap_pkt_init(&pkt2, &buf2[0], sizeof(buf2), len);
coap_opt_add_string(&pkt2, COAP_OPT_URI_PATH, &path[0], '/');
coap_opt_finish(&pkt2, COAP_OPT_FINISH_NONE);
nanocoap_cache_key_generate((const coap_pkt_t *) &pkt1, digest1);
nanocoap_cache_key_generate((const coap_pkt_t *) &pkt2, digest2);
/* compare 1. and 2. packet */
TEST_ASSERT_EQUAL_INT(0, nanocoap_cache_key_compare(digest1, digest2));
/* 3. packet */
len = coap_build_hdr((coap_hdr_t *)&buf2[0], COAP_TYPE_NON,
&token[0], 2, COAP_METHOD_GET, msgid);
coap_pkt_init(&pkt2, &buf2[0], sizeof(buf2), len);
coap_opt_add_string(&pkt2, COAP_OPT_URI_PATH, &path2[0], '/');
coap_opt_finish(&pkt2, COAP_OPT_FINISH_NONE);
nanocoap_cache_key_generate((const coap_pkt_t *) &pkt2, digest2);
/* compare 1. and 3. packet */
TEST_ASSERT_EQUAL_INT(-1, nanocoap_cache_key_compare(digest1, digest2));
/* compare 3. and 1. packet */
TEST_ASSERT_EQUAL_INT(1, nanocoap_cache_key_compare(digest2, digest1));
}
static void test_nanocoap_cache__add(void)
{
uint8_t buf[_BUF_SIZE];
uint8_t rbuf[_BUF_SIZE];
coap_pkt_t req, resp;
uint16_t msgid = 0xABCD;
uint8_t token[2] = {0xDA, 0xEC};
char path[16];
nanocoap_cache_entry_t *c = NULL;
size_t len;
uint8_t temp_cache_key[CONFIG_NANOCOAP_CACHE_KEY_LENGTH];
/* initialize the nanocoap cache */
nanocoap_cache_init();
/* add more entries to test LRU replacement */
for (unsigned i = 0; i < CONFIG_NANOCOAP_CACHE_ENTRIES + 4; i++) {
if (i < CONFIG_NANOCOAP_CACHE_ENTRIES) {
TEST_ASSERT_EQUAL_INT(CONFIG_NANOCOAP_CACHE_ENTRIES - i,
nanocoap_cache_free_count());
TEST_ASSERT_EQUAL_INT(i, nanocoap_cache_used_count());
}
else {
TEST_ASSERT_EQUAL_INT(0, nanocoap_cache_free_count());
TEST_ASSERT_EQUAL_INT(CONFIG_NANOCOAP_CACHE_ENTRIES,
nanocoap_cache_used_count());
}
snprintf(path, sizeof(path), "/path_%u", i);
/* request */
len = coap_build_hdr((coap_hdr_t *)&buf[0], COAP_TYPE_NON,
&token[0], 2, COAP_METHOD_GET, msgid);
coap_pkt_init(&req, &buf[0], sizeof(buf), len);
coap_opt_add_string(&req, COAP_OPT_URI_PATH, &path[0], '/');
coap_opt_finish(&req, COAP_OPT_FINISH_NONE);
/* response */
len = coap_build_hdr((coap_hdr_t *)&rbuf[0], COAP_TYPE_NON,
&token[0], 2, COAP_CODE_205, msgid);
coap_pkt_init(&resp, &rbuf[0], sizeof(rbuf), len);
coap_opt_finish(&resp, COAP_OPT_FINISH_NONE);
/* add a fake response buffer with fake response length */
c = nanocoap_cache_add_by_req((const coap_pkt_t *)&req,
(const coap_pkt_t *)&req,
CONFIG_NANOCOAP_CACHE_RESPONSE_SIZE);
TEST_ASSERT_NOT_NULL(c);
/* keep a pointer to the first cache entry for testing the LRU
replacement */
if (i == 0) {
memcpy(temp_cache_key, c->cache_key,
CONFIG_NANOCOAP_CACHE_KEY_LENGTH);
}
if (i == CONFIG_NANOCOAP_CACHE_ENTRIES-1) {
/* lookup the first entry to update its list position */
nanocoap_cache_key_lookup(temp_cache_key);
}
}
/* the first entry should NOT be cached out, because we updated
* its list position */
c = nanocoap_cache_key_lookup(temp_cache_key);
TEST_ASSERT_NOT_NULL(c);
}
static void test_nanocoap_cache__del(void)
{
uint8_t buf[_BUF_SIZE];
uint8_t rbuf[_BUF_SIZE];
coap_pkt_t req, resp;
uint16_t msgid = 0xABCD;
uint8_t token[2] = {0xDA, 0xEC};
char *path = "/path";
nanocoap_cache_entry_t *c = NULL;
size_t len;
int res;
/* initialize the nanocoap cache */
nanocoap_cache_init();
TEST_ASSERT_EQUAL_INT(CONFIG_NANOCOAP_CACHE_ENTRIES,
nanocoap_cache_free_count());
TEST_ASSERT_EQUAL_INT(0, nanocoap_cache_used_count());
/* request */
len = coap_build_hdr((coap_hdr_t *)&buf[0], COAP_TYPE_NON,
&token[0], 2, COAP_METHOD_GET, msgid);
coap_pkt_init(&req, &buf[0], sizeof(buf), len);
coap_opt_add_string(&req, COAP_OPT_URI_PATH, &path[0], '/');
coap_opt_finish(&req, COAP_OPT_FINISH_NONE);
/* response */
len = coap_build_hdr((coap_hdr_t *)&rbuf[0], COAP_TYPE_NON,
&token[0], 2, COAP_CODE_205, msgid);
coap_pkt_init(&resp, &rbuf[0], sizeof(rbuf), len);
coap_opt_finish(&resp, COAP_OPT_FINISH_NONE);
c = nanocoap_cache_add_by_req((const coap_pkt_t *)&req,
(const coap_pkt_t *)&resp,
CONFIG_NANOCOAP_CACHE_RESPONSE_SIZE);
TEST_ASSERT_NOT_NULL(c);
TEST_ASSERT_EQUAL_INT(CONFIG_NANOCOAP_CACHE_ENTRIES - 1,
nanocoap_cache_free_count());
TEST_ASSERT_EQUAL_INT(1, nanocoap_cache_used_count());
/* delete previously added cache entry */
res = nanocoap_cache_del(c);
TEST_ASSERT_EQUAL_INT(0, res);
TEST_ASSERT_EQUAL_INT(CONFIG_NANOCOAP_CACHE_ENTRIES,
nanocoap_cache_free_count());
TEST_ASSERT_EQUAL_INT(0, nanocoap_cache_used_count());
}
static void test_nanocoap_cache__max_age(void)
{
uint8_t buf[_BUF_SIZE];
uint8_t rbuf[_BUF_SIZE];
coap_pkt_t req, resp;
uint16_t msgid = 0xABCD;
uint8_t token[2] = {0xDA, 0xEC};
char *path = "/path";
nanocoap_cache_entry_t *c = NULL;
size_t len;
uint32_t now;
/* initialize the nanocoap cache */
nanocoap_cache_init();
/* request */
len = coap_build_hdr((coap_hdr_t *)&buf[0], COAP_TYPE_NON,
&token[0], 2, COAP_METHOD_GET, msgid);
coap_pkt_init(&req, &buf[0], sizeof(buf), len);
coap_opt_add_string(&req, COAP_OPT_URI_PATH, &path[0], '/');
coap_opt_finish(&req, COAP_OPT_FINISH_NONE);
/* response with max-age 30 sec */
len = coap_build_hdr((coap_hdr_t *)&rbuf[0], COAP_TYPE_NON,
&token[0], 2, COAP_CODE_205, msgid);
coap_pkt_init(&resp, &rbuf[0], sizeof(rbuf), len);
coap_opt_add_uint(&resp, COAP_OPT_MAX_AGE, 30);
coap_opt_finish(&resp, COAP_OPT_FINISH_NONE);
c = nanocoap_cache_add_by_req((const coap_pkt_t *)&req,
(const coap_pkt_t *)&resp,
CONFIG_NANOCOAP_CACHE_RESPONSE_SIZE);
/* the absolute time of max-age should be at approx. now + 30 sec
(1 sec buffer) */
now = ztimer_now(ZTIMER_SEC);
TEST_ASSERT(c->max_age < (now + 31));
/* delete previously added cache entry */
nanocoap_cache_del(c);
/* response with max-age 60 sec (default, if option is missing) */
len = coap_build_hdr((coap_hdr_t *)&rbuf[0], COAP_TYPE_NON,
&token[0], 2, COAP_CODE_205, msgid);
coap_pkt_init(&resp, &rbuf[0], sizeof(rbuf), len);
coap_opt_finish(&resp, COAP_OPT_FINISH_NONE);
now = ztimer_now(ZTIMER_SEC);
c = nanocoap_cache_add_by_req((const coap_pkt_t *)&req,
(const coap_pkt_t *)&resp,
CONFIG_NANOCOAP_CACHE_RESPONSE_SIZE);
/* the absolute time of max-age should be at approx. now + 60 sec
(1 sec buffer) */
now = ztimer_now(ZTIMER_SEC);
TEST_ASSERT(c->max_age < (now + 61));
}
Test *tests_nanocoap_cache_tests(void)
{
EMB_UNIT_TESTFIXTURES(fixtures) {
new_TestFixture(test_nanocoap_cache__add),
new_TestFixture(test_nanocoap_cache__del),
new_TestFixture(test_nanocoap_cache__cachekey),
new_TestFixture(test_nanocoap_cache__max_age),
};
EMB_UNIT_TESTCALLER(nanocoap_cache_entry_tests, NULL, NULL, fixtures);
return (Test *)&nanocoap_cache_entry_tests;
}
void tests_nanocoap_cache(void)
{
TESTS_RUN(tests_nanocoap_cache_tests());
}
/** @} */

View File

@ -0,0 +1,37 @@
/*
* Copyright (c) 2020 HAW Hamburg
*
* 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 nanocoap-cache module
*
* @author Cenk Gündoğan <cenk.guendogan@haw-hamburg.de>
*/
#ifndef TESTS_NANOCOAP_CACHE_H
#define TESTS_NANOCOAP_CACHE_H
#include "embUnit.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief The entry point of this test suite.
*/
void tests_nanocoap_cache(void);
#ifdef __cplusplus
}
#endif
#endif /* TESTS_NANOCOAP_CACHE_H */
/** @} */