1 /* coap_cache.c -- Caching of CoAP requests
2 *
3 * Copyright (C) 2020 Olaf Bergmann <bergmann@tzi.org>
4 *
5  * SPDX-License-Identifier: BSD-2-Clause
6  *
7 * This file is part of the CoAP library libcoap. Please see
8 * README for terms of use.
9 */
10 
11 #include "coap3/coap_internal.h"
12 
13 /* Determines if the given option_type denotes an option type that can
14  * be used as CacheKey. Options that can be cache keys are not Unsafe
15  * and not marked explicitly as NoCacheKey. */
16 static int
is_cache_key(uint16_t option_type,size_t cache_ignore_count,const uint16_t * cache_ignore_options)17 is_cache_key(uint16_t option_type, size_t cache_ignore_count,
18              const uint16_t *cache_ignore_options) {
19   size_t i;
20 
21   /* https://tools.ietf.org/html/rfc7252#section-5.4.6 Nocachekey definition */
22   if ((option_type & 0x1e) == 0x1c)
23     return 0;
24   /*
25    * https://tools.ietf.org/html/rfc7641#section-2 Observe is not a
26    * part of the cache-key.
27    */
28   if (option_type == COAP_OPTION_OBSERVE)
29     return 0;
30 
31   /* Check for option user has defined as not part of cache-key */
32   for (i = 0; i < cache_ignore_count; i++) {
33     if (cache_ignore_options[i] == option_type) {
34       return 0;
35     }
36   }
37 
38   return 1;
39 }
40 
41 int
coap_cache_ignore_options(coap_context_t * ctx,const uint16_t * options,size_t count)42 coap_cache_ignore_options(coap_context_t *ctx,
43                           const uint16_t *options,
44                           size_t count) {
45   if (ctx->cache_ignore_options) {
46     coap_free(ctx->cache_ignore_options);
47   }
48   if (count) {
49     assert(options);
50     ctx->cache_ignore_options = coap_malloc(count * sizeof(options[0]));
51     if (ctx->cache_ignore_options) {
52       memcpy(ctx->cache_ignore_options, options, count * sizeof(options[0]));
53       ctx->cache_ignore_count = count;
54     }
55     else {
56       coap_log(LOG_WARNING, "Unable to create cache_ignore_options\n");
57       return 0;
58     }
59   }
60   else {
61     ctx->cache_ignore_options = NULL;
62     ctx->cache_ignore_count = count;
63   }
64   return 1;
65 }
66 
67 coap_cache_key_t *
coap_cache_derive_key_w_ignore(const coap_session_t * session,const coap_pdu_t * pdu,coap_cache_session_based_t session_based,const uint16_t * cache_ignore_options,size_t cache_ignore_count)68 coap_cache_derive_key_w_ignore(const coap_session_t *session,
69                                const coap_pdu_t *pdu,
70                                coap_cache_session_based_t session_based,
71                                const uint16_t *cache_ignore_options,
72                                size_t cache_ignore_count) {
73   coap_opt_t *option;
74   coap_opt_iterator_t opt_iter;
75   coap_digest_ctx_t *dctx;
76   coap_digest_t digest;
77   coap_cache_key_t *cache_key;
78 
79   if (!coap_option_iterator_init(pdu, &opt_iter, COAP_OPT_ALL)) {
80     return NULL;
81   }
82 
83   dctx = coap_digest_setup();
84   if (!dctx)
85     return NULL;
86 
87   if (session_based == COAP_CACHE_IS_SESSION_BASED) {
88     /* Include the session ptr */
89     if (!coap_digest_update(dctx, (const uint8_t*)session, sizeof(session))) {
90       coap_digest_free(dctx);
91       return NULL;
92     }
93   }
94   while ((option = coap_option_next(&opt_iter))) {
95     if (is_cache_key(opt_iter.number, cache_ignore_count,
96                      cache_ignore_options)) {
97       if (!coap_digest_update(dctx, option, coap_opt_size(option))) {
98         coap_digest_free(dctx);
99         return NULL;
100       }
101     }
102   }
103 
104   /* The body of a FETCH payload is part of the cache key,
105    * see https://tools.ietf.org/html/rfc8132#section-2 */
106   if (pdu->code == COAP_REQUEST_CODE_FETCH) {
107     size_t len;
108     const uint8_t *data;
109     if (coap_get_data(pdu, &len, &data)) {
110       if (!coap_digest_update(dctx, data, len)) {
111         coap_digest_free(dctx);
112         return NULL;
113       }
114     }
115   }
116 
117   if (!coap_digest_final(dctx, &digest)) {
118     /* coap_digest_final() is guaranteed to free off dctx no matter what */
119     return NULL;
120   }
121   cache_key = coap_malloc_type(COAP_CACHE_KEY, sizeof(coap_cache_key_t));
122   if (cache_key) {
123     memcpy(cache_key->key, digest.key, sizeof(cache_key->key));
124   }
125   return cache_key;
126 }
127 
128 coap_cache_key_t *
coap_cache_derive_key(const coap_session_t * session,const coap_pdu_t * pdu,coap_cache_session_based_t session_based)129 coap_cache_derive_key(const coap_session_t *session,
130                       const coap_pdu_t *pdu,
131                       coap_cache_session_based_t session_based) {
132   return coap_cache_derive_key_w_ignore(session, pdu, session_based,
133                                         session->context->cache_ignore_options,
134                                         session->context->cache_ignore_count);
135 }
136 
137 void
coap_delete_cache_key(coap_cache_key_t * cache_key)138 coap_delete_cache_key(coap_cache_key_t *cache_key) {
139   coap_free_type(COAP_CACHE_KEY, cache_key);
140 }
141 
142 coap_cache_entry_t *
coap_new_cache_entry(coap_session_t * session,const coap_pdu_t * pdu,coap_cache_record_pdu_t record_pdu,coap_cache_session_based_t session_based,unsigned int idle_timeout)143 coap_new_cache_entry(coap_session_t *session, const coap_pdu_t *pdu,
144                coap_cache_record_pdu_t record_pdu,
145                coap_cache_session_based_t session_based,
146                unsigned int idle_timeout) {
147   coap_cache_entry_t *entry = coap_malloc_type(COAP_CACHE_ENTRY,
148                                                sizeof(coap_cache_entry_t));
149   if (!entry) {
150     return NULL;
151   }
152 
153   memset(entry, 0, sizeof(coap_cache_entry_t));
154   entry->session = session;
155   if (record_pdu == COAP_CACHE_RECORD_PDU) {
156     entry->pdu = coap_pdu_init(pdu->type, pdu->code, pdu->mid, pdu->alloc_size);
157     if (entry->pdu) {
158       if (!coap_pdu_resize(entry->pdu, pdu->alloc_size)) {
159         coap_delete_pdu(entry->pdu);
160         coap_free_type(COAP_CACHE_ENTRY, entry);
161         return NULL;
162       }
163       /* Need to get the appropriate data across */
164       memcpy(entry->pdu, pdu, offsetof(coap_pdu_t, token));
165       memcpy(entry->pdu->token, pdu->token, pdu->used_size);
166       /* And adjust all the pointers etc. */
167       entry->pdu->data = entry->pdu->token + (pdu->data - pdu->token);
168     }
169   }
170   entry->cache_key = coap_cache_derive_key(session, pdu, session_based);
171   if (!entry->cache_key) {
172     coap_free_type(COAP_CACHE_ENTRY, entry);
173     return NULL;
174   }
175   entry->idle_timeout = idle_timeout;
176   if (idle_timeout > 0) {
177     coap_ticks(&entry->expire_ticks);
178     entry->expire_ticks += idle_timeout * COAP_TICKS_PER_SECOND;
179   }
180 
181   HASH_ADD(hh, session->context->cache, cache_key[0], sizeof(coap_cache_key_t), entry);
182   return entry;
183 }
184 
185 coap_cache_entry_t *
coap_cache_get_by_key(coap_context_t * ctx,const coap_cache_key_t * cache_key)186 coap_cache_get_by_key(coap_context_t *ctx, const coap_cache_key_t *cache_key) {
187   coap_cache_entry_t *cache_entry = NULL;
188 
189   assert(cache_key);
190   if (cache_key) {
191     HASH_FIND(hh, ctx->cache, cache_key, sizeof(coap_cache_key_t), cache_entry);
192   }
193   if (cache_entry && cache_entry->idle_timeout > 0) {
194     coap_ticks(&cache_entry->expire_ticks);
195     cache_entry->expire_ticks += cache_entry->idle_timeout * COAP_TICKS_PER_SECOND;
196   }
197   return cache_entry;
198 }
199 
200 coap_cache_entry_t *
coap_cache_get_by_pdu(coap_session_t * session,const coap_pdu_t * request,coap_cache_session_based_t session_based)201 coap_cache_get_by_pdu(coap_session_t *session,
202                       const coap_pdu_t *request,
203                       coap_cache_session_based_t session_based) {
204   coap_cache_key_t *cache_key = coap_cache_derive_key(session, request, session_based);
205   coap_cache_entry_t *cache_entry;
206 
207   if (!cache_key)
208     return NULL;
209 
210   cache_entry = coap_cache_get_by_key(session->context, cache_key);
211   coap_delete_cache_key(cache_key);
212   if (cache_entry && cache_entry->idle_timeout > 0) {
213     coap_ticks(&cache_entry->expire_ticks);
214     cache_entry->expire_ticks += cache_entry->idle_timeout * COAP_TICKS_PER_SECOND;
215   }
216   return cache_entry;
217 }
218 
219 void
coap_delete_cache_entry(coap_context_t * ctx,coap_cache_entry_t * cache_entry)220 coap_delete_cache_entry(coap_context_t *ctx, coap_cache_entry_t *cache_entry) {
221 
222   assert(cache_entry);
223 
224   if (cache_entry) {
225     HASH_DELETE(hh, ctx->cache, cache_entry);
226   }
227   if (cache_entry->pdu) {
228     coap_delete_pdu(cache_entry->pdu);
229   }
230   coap_delete_cache_key(cache_entry->cache_key);
231   if (cache_entry->callback && cache_entry->app_data) {
232     cache_entry->callback(cache_entry->app_data);
233   }
234   coap_free_type(COAP_CACHE_ENTRY, cache_entry);
235 }
236 
237 const coap_pdu_t *
coap_cache_get_pdu(const coap_cache_entry_t * cache_entry)238 coap_cache_get_pdu(const coap_cache_entry_t *cache_entry) {
239         return cache_entry->pdu;
240 }
241 
242 void
coap_cache_set_app_data(coap_cache_entry_t * cache_entry,void * data,coap_cache_app_data_free_callback_t callback)243 coap_cache_set_app_data(coap_cache_entry_t *cache_entry,
244                         void *data,
245                         coap_cache_app_data_free_callback_t callback) {
246   cache_entry->app_data = data;
247   cache_entry->callback = callback;
248 }
249 
250 void *
coap_cache_get_app_data(const coap_cache_entry_t * cache_entry)251 coap_cache_get_app_data(const coap_cache_entry_t *cache_entry) {
252   return cache_entry->app_data;
253 }
254 
255 void
coap_expire_cache_entries(coap_context_t * ctx)256 coap_expire_cache_entries(coap_context_t *ctx) {
257   coap_tick_t now;
258   coap_cache_entry_t *cp, *ctmp;
259 
260   coap_ticks(&now);
261   HASH_ITER(hh, ctx->cache, cp, ctmp) {
262     if (cp->idle_timeout > 0) {
263       if (cp->expire_ticks <= now) {
264         coap_delete_cache_entry(ctx, cp);
265       }
266     }
267   }
268 }
269 
270