1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00
RIOT/sys/net/application_layer/nanocoap/cache.c
2022-05-13 12:14:55 +02:00

326 lines
9.9 KiB
C

/*
* 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) {
/* gCoAP forward proxy is ETag-aware, so skip ETag option,
* see https://datatracker.ietf.org/doc/html/rfc7252#section-5.4.2 */
if (IS_USED(MODULE_GCOAP_FORWARD_PROXY) && (opt.opt_num == COAP_OPT_ETAG)) {
continue;
}
/* 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;
}
nanocoap_cache_entry_t *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 ((ce = nanocoap_cache_add_by_key(cache_key, request_method,
resp, resp_len)) == NULL) {
/* no space left in the cache? */
return NULL;
}
}
return ce;
}
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;
}