diff --git a/Makefile.am b/Makefile.am index c1bde83b..ed239967 100644 --- a/Makefile.am +++ b/Makefile.am @@ -75,6 +75,7 @@ libcoap_@LIBCOAP_NAME_SUFFIX@_la_SOURCES = \ src/address.c \ src/async.c \ src/block.c \ + src/coap_cache.c \ src/coap_debug.c \ src/coap_event.c \ src/coap_hashkey.c \ @@ -90,6 +91,7 @@ libcoap_@LIBCOAP_NAME_SUFFIX@_la_SOURCES = \ src/coap_tinydtls.c \ src/encode.c \ src/mem.c \ + src/murmur3.c \ src/net.c \ src/option.c \ src/pdu.c \ @@ -115,6 +117,7 @@ libcoap_include_HEADERS = \ $(top_srcdir)/include/coap$(LIBCOAP_API_VERSION)/bits.h \ $(top_srcdir)/include/coap$(LIBCOAP_API_VERSION)/block.h \ $(top_builddir)/include/coap$(LIBCOAP_API_VERSION)/coap.h \ + $(top_srcdir)/include/coap$(LIBCOAP_API_VERSION)/coap_cache.h \ $(top_srcdir)/include/coap$(LIBCOAP_API_VERSION)/coap_debug.h \ $(top_srcdir)/include/coap$(LIBCOAP_API_VERSION)/coap_dtls.h \ $(top_srcdir)/include/coap$(LIBCOAP_API_VERSION)/coap_event.h \ diff --git a/include/coap2/coap.h.in b/include/coap2/coap.h.in index 37f91544..d8c2e72f 100644 --- a/include/coap2/coap.h.in +++ b/include/coap2/coap.h.in @@ -40,6 +40,7 @@ extern "C" { #include "async.h" #include "bits.h" #include "block.h" +#include "coap_cache.h" #include "coap_dtls.h" #include "coap_event.h" #include "coap_io.h" diff --git a/include/coap2/coap_cache.h b/include/coap2/coap_cache.h new file mode 100644 index 00000000..eb3b2ee4 --- /dev/null +++ b/include/coap2/coap_cache.h @@ -0,0 +1,89 @@ +/* coap_cache.h -- Caching of CoAP requests +* +* Copyright (C) 2020 Olaf Bergmann +* +* This file is part of the CoAP library libcoap. Please see +* README for terms of use. +*/ + +/** + * @file coap_cache.h + * @brief Provides a simple request storage for CoAP requests + */ + +#ifndef COAP_CACHE_H_ +#define COAP_CACHE_H_ + +#include + +#include "coap_forward_decls.h" + +typedef enum { + COAP_CACHE_INVALID=-1, + COAP_CACHE_MAXKEY=INT_MAX +} coap_cache_key_t; + +/** + * Calculates a cache key for the given CoAP PDU. See + * https://tools.ietf.org/html/rfc7252#section-5.6 + * for an explanation of CoAP cache keys. + * + * @param pdu The CoAP PDU for which a cache key is to be + * calculated. + * + * @return The calculcated cache key. + */ +coap_cache_key_t coap_cache_key(const coap_pdu_t *pdu); + +/** + * Marks a request for permanent storage. The request may be retrieved + * through its cache-key. + * + * @param ctx The context to use. + * @param request The request to be stored. + * + * @return The cache key that corresponds to the newly stored + * request or @c COAP_CACHE_INVALID if the request could + * not be stored. + */ +coap_cache_key_t coap_cache_mark_request(coap_context_t *ctx, + const coap_pdu_t *request); + +/** + * Clears a mark from a request. + * + * @param ctx The context to use. + * @param cache_key The cache key for the request which can be + * unmarked. + */ +void coap_cache_unmark_request(coap_context_t *ctx, + coap_cache_key_t cache_key); + +/** + * Searches for a cache entry identified by @p cache_key. This + * function returns the corresponding cache entry or @c NULL + * if not found. + * + * @param ctx The context to use. + * @param cache_key The cache key to look up. + * + * @return The cache entry for @p cache_key or @c NULL if not found. + */ +struct coap_cache_entry_t *coap_cache_lookup_key(coap_context_t *ctx, + coap_cache_key_t cache_key); + +/** + * Searches for a cache entry corresponding to @p request. This + * function returns the corresponding cache entry or @c NULL if not + * found. + * + * @param ctx The context to use. + * @param request The CoAP request to search for. + * + * @return The cache entry for @p request or @c NULL if not found. + */ +struct coap_cache_entry_t *coap_cache_lookup_request(coap_context_t *ctx, + const coap_pdu_t *request); + + +#endif /* COAP_CACHE_H */ diff --git a/include/coap2/coap_forward_decls.h b/include/coap2/coap_forward_decls.h index 2325f08e..e99c6b42 100644 --- a/include/coap2/coap_forward_decls.h +++ b/include/coap2/coap_forward_decls.h @@ -28,6 +28,7 @@ struct coap_queue_t; struct coap_session_t; struct coap_string_t; struct coap_subscription_t; +struct coap_cache_entry_t; /* * typedef all the structures that are defined in coap_*_internal.h diff --git a/include/coap2/net.h b/include/coap2/net.h index 0090d096..7a32475f 100644 --- a/include/coap2/net.h +++ b/include/coap2/net.h @@ -209,6 +209,7 @@ typedef struct coap_context_t { unsigned int csm_timeout; /**< Timeout for waiting for a CSM from the remote side. 0 means disabled. */ int observe_pending; /**< Observe response pending */ + struct coap_cache_entry_t *cache; /**< CoAP message cache */ void *app; /**< application-specific data */ #ifdef COAP_EPOLL_SUPPORT int epfd; /**< External FD for epoll */ diff --git a/src/coap_cache.c b/src/coap_cache.c new file mode 100644 index 00000000..6f1cc120 --- /dev/null +++ b/src/coap_cache.c @@ -0,0 +1,110 @@ +/* coap_cache.c -- Caching of CoAP requests +* +* Copyright (C) 2020 Olaf Bergmann +* +* This file is part of the CoAP library libcoap. Please see +* README for terms of use. +*/ + +#include "coap_internal.h" + +#include "murmur3.h" + +/* Determines if the given option_type denotes an option type that can + * be used as CacheKey. Options that can be cache keys are not Unsafe + * and not marked explicitly as NoCacheKey. */ +static int +is_cache_key(uint16_t option_type) { + const uint16_t unsafe = 0x02; + const uint16_t no_cache_key = 0x04; + + return (option_type & (unsafe | no_cache_key)) == 0; +} + +coap_cache_key_t +coap_cache_key(const coap_pdu_t *pdu) { + coap_opt_t *option; + coap_opt_iterator_t opt_iter; + murmur3_context_t mctx; + + if (!coap_option_iterator_init(pdu, &opt_iter, COAP_OPT_ALL)) { + return COAP_CACHE_INVALID; + } + + murmur3_32_init(&mctx); + + while ((option = coap_option_next(&opt_iter))) { + if (is_cache_key(opt_iter.type)) { + murmur3_32_update(&mctx, option, coap_opt_size(option)); + } + } + + /* The body of a FETCH payload is part of the cache key, + * see https://tools.ietf.org/html/rfc8132#section-2 */ + if (pdu->code == COAP_REQUEST_FETCH) { + size_t len; + uint8_t *data; + if (coap_get_data(pdu, &len, &data)) { + murmur3_32_update(&mctx, option, coap_opt_size(option)); + } + } + + return murmur3_32_finalize(&mctx); +} + +typedef struct coap_cache_entry_t { + UT_hash_handle hh; + const coap_pdu_t *pdu; + coap_cache_key_t cache_key; +} coap_cache_entry_t; + +/** + * Marks a request for permanent storage. The request may be retrieved + * through its cache-key. + * + * @param ctx The context to use. + * @param request The request to be stored. + * + * @return The cache key that corresponds to the newly stored + * request or @c COAP_CACHE_INVALID if the request could + * not be stored. + */ +coap_cache_key_t +coap_cache_mark_request(coap_context_t *ctx, const coap_pdu_t *request) { + coap_cache_entry_t *entry = coap_malloc(sizeof(coap_cache_entry_t)); + if (!entry) { + return COAP_CACHE_INVALID; + } + + entry->pdu = request; + entry->cache_key = coap_cache_key(request); + HASH_ADD(hh, ctx->cache, cache_key, sizeof(coap_cache_key_t), entry); + return entry->cache_key; +} + +coap_cache_entry_t * +coap_cache_lookup_key(coap_context_t *ctx, coap_cache_key_t key) { + coap_cache_entry_t *entry = NULL; + if (key != COAP_CACHE_INVALID) { + HASH_FIND(hh, ctx->cache, &key, sizeof(coap_cache_key_t), entry); + } + return entry; +} + +coap_cache_entry_t * +coap_cache_lookup_request(coap_context_t *ctx, const coap_pdu_t *request) { + return coap_cache_lookup_key(ctx, coap_cache_key(request)); +} + +/** + * Clears a mark from a request. + * + * @param ctx The context to use. + * @param cache_key The cache key for the request which can be + * unmarked. + */ +void +coap_cache_unmark_request(coap_context_t *ctx, coap_cache_key_t cache_key) { + (void)ctx; + (void)cache_key; +}