xref: /third_party/libcoap/src/coap_subscribe.c (revision c87c5fba)
1/* coap_subscribe.c -- subscription handling for CoAP
2 *                see RFC7641
3 *
4 * Copyright (C) 2010-2019,2022-2023 Olaf Bergmann <bergmann@tzi.org>
5 *
6 * SPDX-License-Identifier: BSD-2-Clause
7 *
8 * This file is part of the CoAP library libcoap. Please see
9 * README for terms of use.
10 */
11
12/**
13 * @file coap_subscribe.c
14 * @brief Subscription handling functions
15 */
16
17#include "coap3/coap_internal.h"
18
19#ifndef min
20#define min(a,b) ((a) < (b) ? (a) : (b))
21#endif
22
23#if COAP_SERVER_SUPPORT
24void
25coap_subscription_init(coap_subscription_t *s) {
26  assert(s);
27  memset(s, 0, sizeof(coap_subscription_t));
28}
29
30void
31coap_persist_track_funcs(coap_context_t *context,
32                         coap_observe_added_t observe_added,
33                         coap_observe_deleted_t observe_deleted,
34                         coap_track_observe_value_t track_observe_value,
35                         coap_dyn_resource_added_t dyn_resource_added,
36                         coap_resource_deleted_t resource_deleted,
37                         uint32_t save_freq,
38                         void *user_data) {
39  context->observe_added = observe_added;
40  context->observe_deleted = observe_deleted;
41  context->observe_user_data = user_data;
42  context->observe_save_freq = save_freq ? save_freq : 1;
43  context->track_observe_value = track_observe_value;
44  context->dyn_resource_added = dyn_resource_added;
45  context->resource_deleted = resource_deleted;
46}
47
48coap_subscription_t *
49coap_persist_observe_add(coap_context_t *context,
50                         coap_proto_t e_proto,
51                         const coap_address_t *e_listen_addr,
52                         const coap_addr_tuple_t *s_addr_info,
53                         const coap_bin_const_t *raw_packet,
54                         const coap_bin_const_t *oscore_info) {
55  coap_session_t *session = NULL;
56  const uint8_t *data;
57  size_t data_len;
58  coap_pdu_t *pdu = NULL;
59#if COAP_CONSTRAINED_STACK
60  /* e_packet protected by mutex m_persist_add */
61  static coap_packet_t e_packet;
62#else /* ! COAP_CONSTRAINED_STACK */
63  coap_packet_t e_packet;
64#endif /* ! COAP_CONSTRAINED_STACK */
65  coap_packet_t *packet = &e_packet;
66  coap_tick_t now;
67  coap_string_t *uri_path = NULL;
68  coap_opt_iterator_t opt_iter;
69  coap_opt_t *observe;
70  int observe_action;
71  coap_resource_t *r;
72  coap_subscription_t *s;
73  coap_endpoint_t *ep;
74
75  if (e_listen_addr == NULL || s_addr_info == NULL || raw_packet == NULL)
76    return NULL;
77
78  /* Will be creating a local 'open' session */
79  if (e_proto != COAP_PROTO_UDP)
80    return NULL;
81
82  ep = context->endpoint;
83  while (ep) {
84    if (ep->proto == e_proto &&
85        memcmp(e_listen_addr, &ep->bind_addr, sizeof(ep->bind_addr)) == 0)
86      break;
87    ep = ep->next;
88  }
89  if (!ep)
90    return NULL;
91
92#if COAP_CONSTRAINED_STACK
93  coap_mutex_lock(&m_persist_add);
94#endif /* COAP_CONSTRAINED_STACK */
95
96  /* Build up packet */
97  memcpy(&packet->addr_info, s_addr_info, sizeof(packet->addr_info));
98  packet->ifindex = 0;
99  memcpy(&packet->payload, &raw_packet->s, sizeof(packet->payload));
100  packet->length = raw_packet->length;
101
102  data = raw_packet->s;
103  data_len = raw_packet->length;
104  if (data_len < 4)
105    goto malformed;
106
107  /* Get the session */
108
109  coap_ticks(&now);
110  session = coap_endpoint_get_session(ep, packet, now);
111  if (session == NULL)
112    goto fail;
113  /* Need max space incase PDU is updated with updated token, huge size etc. */
114  pdu = coap_pdu_init(0, 0, 0, 0);
115  if (!pdu)
116    goto fail;
117
118  if (!coap_pdu_parse(session->proto, data, data_len, pdu)) {
119    goto malformed;
120  }
121  pdu->max_size = pdu->used_size;
122
123  if (pdu->code != COAP_REQUEST_CODE_GET &&
124      pdu->code != COAP_REQUEST_CODE_FETCH)
125    goto malformed;
126
127  observe = coap_check_option(pdu, COAP_OPTION_OBSERVE, &opt_iter);
128  if (observe == NULL)
129    goto malformed;
130  observe_action = coap_decode_var_bytes(coap_opt_value(observe),
131                                         coap_opt_length(observe));
132  if (observe_action != COAP_OBSERVE_ESTABLISH)
133    goto malformed;
134
135  /* Get the resource */
136
137  uri_path = coap_get_uri_path(pdu);
138  if (!uri_path)
139    goto malformed;
140
141  r = coap_get_resource_from_uri_path(session->context,
142                                      (coap_str_const_t *)uri_path);
143  if (r == NULL) {
144    coap_log_warn("coap_persist_observe_add: resource '%s' not defined\n",
145                  uri_path->s);
146    goto fail;
147  }
148  if (!r->observable) {
149    coap_log_warn("coap_persist_observe_add: resource '%s' not observable\n",
150                  uri_path->s);
151    goto fail;
152  }
153  coap_delete_string(uri_path);
154  uri_path = NULL;
155
156  /* Create / update subscription for observing */
157  /* Now set up the subscription */
158  s = coap_add_observer(r, session, &pdu->actual_token, pdu);
159  if (s == NULL)
160    goto fail;
161
162#if COAP_OSCORE_SUPPORT
163  if (oscore_info) {
164    coap_log_debug("persist: OSCORE association being updated\n");
165    /*
166     * Need to track the association used for tracking this observe, done as
167     * a CBOR array. Written in coap_add_observer().
168     *
169     * If an entry is null, then use nil, else a set of bytes
170     *
171     * Currently tracking 5 items
172     *  recipient_id
173     *  id_context
174     *  aad        (from oscore_association_t)
175     *  partial_iv (from oscore_association_t)
176     *  nonce      (from oscore_association_t)
177     */
178    oscore_ctx_t *osc_ctx;
179    const uint8_t *info_buf = oscore_info->s;
180    size_t info_buf_len = oscore_info->length;
181    size_t ret = 0;
182    coap_bin_const_t oscore_key_id;
183    coap_bin_const_t partial_iv;
184    coap_bin_const_t aad;
185    coap_bin_const_t id_context;
186    coap_bin_const_t nonce;
187    int have_aad = 0;
188    int have_partial_iv = 0;
189    int have_id_context = 0;
190    int have_nonce = 0;
191
192    ret = oscore_cbor_get_next_element(&info_buf, &info_buf_len);
193    if (ret != CBOR_ARRAY)
194      goto oscore_fail;
195    if (oscore_cbor_get_element_size(&info_buf, &info_buf_len) != 5)
196      goto oscore_fail;
197
198    /* recipient_id */
199    ret = oscore_cbor_get_next_element(&info_buf, &info_buf_len);
200    if (ret != CBOR_BYTE_STRING)
201      goto oscore_fail;
202    oscore_key_id.length = oscore_cbor_get_element_size(&info_buf,
203                                                        &info_buf_len);
204    oscore_key_id.s = info_buf;
205    info_buf += oscore_key_id.length;
206
207    /* id_context */
208    ret = oscore_cbor_get_next_element(&info_buf, &info_buf_len);
209    if (ret == CBOR_BYTE_STRING) {
210      id_context.length = oscore_cbor_get_element_size(&info_buf,
211                                                       &info_buf_len);
212      id_context.s = info_buf;
213      info_buf += id_context.length;
214      have_id_context = 1;
215    } else if (ret == CBOR_SIMPLE_VALUE &&
216               oscore_cbor_get_element_size(&info_buf,
217                                            &info_buf_len) == CBOR_NULL) {
218    } else
219      goto oscore_fail;
220
221    /* aad */
222    ret = oscore_cbor_get_next_element(&info_buf, &info_buf_len);
223    if (ret == CBOR_BYTE_STRING) {
224      aad.length = oscore_cbor_get_element_size(&info_buf, &info_buf_len);
225      aad.s = info_buf;
226      info_buf += aad.length;
227      have_aad = 1;
228    } else if (ret == CBOR_SIMPLE_VALUE &&
229               oscore_cbor_get_element_size(&info_buf,
230                                            &info_buf_len) == CBOR_NULL) {
231    } else
232      goto oscore_fail;
233
234    /* partial_iv */
235    ret = oscore_cbor_get_next_element(&info_buf, &info_buf_len);
236    if (ret == CBOR_BYTE_STRING) {
237      partial_iv.length = oscore_cbor_get_element_size(&info_buf,
238                                                       &info_buf_len);
239      partial_iv.s = info_buf;
240      info_buf += partial_iv.length;
241      have_partial_iv = 1;
242    } else if (ret == CBOR_SIMPLE_VALUE &&
243               oscore_cbor_get_element_size(&info_buf,
244                                            &info_buf_len) == CBOR_NULL) {
245    } else
246      goto oscore_fail;
247
248    /* nonce */
249    ret = oscore_cbor_get_next_element(&info_buf, &info_buf_len);
250    if (ret == CBOR_BYTE_STRING) {
251      nonce.length = oscore_cbor_get_element_size(&info_buf,
252                                                  &info_buf_len);
253      nonce.s = info_buf;
254      info_buf += nonce.length;
255      have_nonce = 1;
256    } else if (ret == CBOR_SIMPLE_VALUE &&
257               oscore_cbor_get_element_size(&info_buf,
258                                            &info_buf_len) == CBOR_NULL) {
259    } else
260      goto oscore_fail;
261
262    osc_ctx = oscore_find_context(session->context, oscore_key_id,
263                                  have_id_context ? &id_context : NULL, NULL,
264                                  &session->recipient_ctx);
265    if (osc_ctx) {
266      session->oscore_encryption = 1;
267      oscore_new_association(session, pdu, &pdu->actual_token,
268                             session->recipient_ctx,
269                             have_aad ? &aad : NULL,
270                             have_nonce ? &nonce : NULL,
271                             have_partial_iv ? &partial_iv : NULL,
272                             1);
273      coap_log_debug("persist: OSCORE association added\n");
274      oscore_log_hex_value(COAP_LOG_OSCORE, "partial_iv",
275                           have_partial_iv ? &partial_iv : NULL);
276    }
277  }
278oscore_fail:
279#else /* ! COAP_OSCORE_SUPPORT */
280  (void)oscore_info;
281#endif /* ! COAP_OSCORE_SUPPORT */
282  coap_delete_pdu(pdu);
283#if COAP_CONSTRAINED_STACK
284  coap_mutex_unlock(&m_persist_add);
285#endif /* COAP_CONSTRAINED_STACK */
286  return s;
287
288malformed:
289  coap_log_warn("coap_persist_observe_add: discard malformed PDU\n");
290fail:
291#if COAP_CONSTRAINED_STACK
292  coap_mutex_unlock(&m_persist_add);
293#endif /* COAP_CONSTRAINED_STACK */
294  coap_delete_string(uri_path);
295  coap_delete_pdu(pdu);
296  return NULL;
297}
298
299#if COAP_WITH_OBSERVE_PERSIST
300#include <stdio.h>
301
302/*
303 * read in active observe entry.
304 */
305static int
306coap_op_observe_read(FILE *fp, coap_subscription_t **observe_key,
307                     coap_proto_t *e_proto, coap_address_t *e_listen_addr,
308                     coap_addr_tuple_t *s_addr_info,
309                     coap_bin_const_t **raw_packet, coap_bin_const_t **oscore_info) {
310  ssize_t size;
311  coap_binary_t *scratch = NULL;
312
313  assert(fp && observe_key && e_proto && e_listen_addr && s_addr_info &&
314         raw_packet && oscore_info);
315
316  *raw_packet = NULL;
317  *oscore_info = NULL;
318
319  if (fread(observe_key, sizeof(*observe_key), 1, fp) == 1) {
320    /* New record 'key proto listen addr_info len raw_packet len oscore' */
321    if (fread(e_proto, sizeof(*e_proto), 1, fp) != 1)
322      goto fail;
323    if (fread(e_listen_addr, sizeof(*e_listen_addr), 1, fp) != 1)
324      goto fail;
325    if (fread(s_addr_info, sizeof(*s_addr_info), 1, fp) != 1)
326      goto fail;
327    if (fread(&size, sizeof(size), 1, fp) != 1)
328      goto fail;
329    if (size < 0 || size > 0x10000)
330      goto fail;
331    scratch = coap_new_binary(size);
332    if ((scratch) == NULL)
333      goto fail;
334    if (fread(scratch->s, scratch->length, 1, fp) != 1)
335      goto fail;
336    *raw_packet = (coap_bin_const_t *)scratch;
337    scratch = NULL;
338    if (fread(&size, sizeof(size), 1, fp) != 1)
339      goto fail;
340    /* If size == -1, then no oscore information */
341    if (size == -1)
342      return 1;
343    else if (size < 0 || size > 0x10000)
344      goto fail;
345    else {
346      scratch = coap_new_binary(size);
347      if (scratch == NULL)
348        goto fail;
349      if (fread(scratch->s, scratch->length, 1, fp) != 1)
350        goto fail;
351      *oscore_info = (coap_bin_const_t *)scratch;
352    }
353    return 1;
354  }
355fail:
356  coap_delete_bin_const(*raw_packet);
357  coap_delete_binary(scratch);
358
359  *observe_key = NULL;
360  memset(e_proto, 0, sizeof(*e_proto));
361  memset(e_listen_addr, 0, sizeof(*e_listen_addr));
362  memset(s_addr_info, 0, sizeof(*s_addr_info));
363  *raw_packet = NULL;
364  return 0;
365}
366
367/*
368 * write out active observe entry.
369 */
370static int
371coap_op_observe_write(FILE *fp, coap_subscription_t *observe_key,
372                      coap_proto_t e_proto, coap_address_t e_listen_addr,
373                      coap_addr_tuple_t s_addr_info,
374                      coap_bin_const_t *raw_packet, coap_bin_const_t *oscore_info) {
375  if (fwrite(&observe_key, sizeof(observe_key), 1, fp) != 1)
376    goto fail;
377  if (fwrite(&e_proto, sizeof(e_proto), 1, fp) != 1)
378    goto fail;
379  if (fwrite(&e_listen_addr, sizeof(e_listen_addr),
380             1, fp) != 1)
381    goto fail;
382  if (fwrite(&s_addr_info, sizeof(s_addr_info), 1, fp) != 1)
383    goto fail;
384  if (fwrite(&raw_packet->length, sizeof(raw_packet->length), 1, fp) != 1)
385    goto fail;
386  if (fwrite(raw_packet->s, raw_packet->length, 1, fp) != 1)
387    goto fail;
388  if (oscore_info) {
389    if (fwrite(&oscore_info->length, sizeof(oscore_info->length), 1, fp) != 1)
390      goto fail;
391    if (fwrite(oscore_info->s, oscore_info->length, 1, fp) != 1)
392      goto fail;
393  } else {
394    ssize_t not_defined = -1;
395
396    if (fwrite(&not_defined, sizeof(not_defined), 1, fp) != 1)
397      goto fail;
398  }
399  return 1;
400fail:
401  return 0;
402}
403
404/*
405 * This should be called before coap_persist_track_funcs() to prevent
406 * coap_op_observe_added() getting unnecessarily called.
407 * It should be called after init_resources() and coap_op_resource_load_disk()
408 * so that all the resources are in place.
409 */
410static void
411coap_op_observe_load_disk(coap_context_t *ctx) {
412  FILE *fp_orig = fopen((const char *)ctx->observe_save_file->s, "r");
413  FILE *fp_new = NULL;
414  coap_subscription_t *observe_key = NULL;
415  coap_proto_t e_proto;
416  coap_address_t e_listen_addr;
417  coap_addr_tuple_t s_addr_info;
418  coap_bin_const_t *raw_packet = NULL;
419  coap_bin_const_t *oscore_info = NULL;
420  char *new = NULL;
421
422  if (fp_orig == NULL)
423    goto fail;
424
425  new = coap_malloc_type(COAP_STRING, ctx->observe_save_file->length + 5);
426  if (!new)
427    goto fail;
428
429  strcpy(new, (const char *)ctx->observe_save_file->s);
430  strcat(new, ".tmp");
431  fp_new = fopen(new, "w+");
432  if (fp_new == NULL)
433    goto fail;
434
435  /* Go through and load oscore entry, updating key on the way */
436  while (1) {
437    if (!coap_op_observe_read(fp_orig, &observe_key, &e_proto, &e_listen_addr,
438                              &s_addr_info, &raw_packet, &oscore_info))
439      break;
440    coap_log_debug("persist: New session/observe being created\n");
441    observe_key = coap_persist_observe_add(ctx, e_proto,
442                                           &e_listen_addr,
443                                           &s_addr_info,
444                                           raw_packet,
445                                           oscore_info);
446    if (observe_key) {
447      if (!coap_op_observe_write(fp_new, observe_key, e_proto, e_listen_addr,
448                                 s_addr_info, raw_packet, oscore_info))
449        goto fail;
450      coap_delete_bin_const(raw_packet);
451      raw_packet = NULL;
452      coap_delete_bin_const(oscore_info);
453      oscore_info = NULL;
454    }
455  }
456  coap_delete_bin_const(raw_packet);
457  raw_packet = NULL;
458  coap_delete_bin_const(oscore_info);
459  oscore_info = NULL;
460
461  if (fflush(fp_new) == EOF)
462    goto fail;
463  fclose(fp_new);
464  fclose(fp_orig);
465  /* Either old or new is in place */
466  (void)rename(new, (const char *)ctx->observe_save_file->s);
467  coap_free_type(COAP_STRING, new);
468  return;
469
470fail:
471  coap_delete_bin_const(raw_packet);
472  coap_delete_bin_const(oscore_info);
473  if (fp_new)
474    fclose(fp_new);
475  if (fp_orig)
476    fclose(fp_orig);
477  if (new) {
478    (void)remove(new);
479  }
480  coap_free_type(COAP_STRING, new);
481  return;
482}
483
484/*
485 * client has registered a new observe subscription request.
486 */
487static int
488coap_op_observe_added(coap_session_t *session,
489                      coap_subscription_t *a_observe_key,
490                      coap_proto_t a_e_proto, coap_address_t *a_e_listen_addr,
491                      coap_addr_tuple_t *a_s_addr_info,
492                      coap_bin_const_t *a_raw_packet,
493                      coap_bin_const_t *a_oscore_info, void *user_data) {
494  FILE *fp_orig = fopen((const char *)session->context->observe_save_file->s,
495                        "r");
496  FILE *fp_new = NULL;
497  coap_subscription_t *observe_key = NULL;
498  coap_proto_t e_proto;
499  coap_address_t e_listen_addr;
500  coap_addr_tuple_t s_addr_info;
501  coap_bin_const_t *raw_packet = NULL;
502  coap_bin_const_t *oscore_info = NULL;
503  char *new = NULL;
504
505  (void)user_data;
506
507  new = coap_malloc_type(COAP_STRING,
508                         session->context->observe_save_file->length + 5);
509  if (!new)
510    goto fail;
511
512  strcpy(new, (const char *)session->context->observe_save_file->s);
513  strcat(new, ".tmp");
514  fp_new = fopen(new, "w+");
515  if (fp_new == NULL)
516    goto fail;
517
518  /* Go through and delete observe entry if it exists */
519  while (fp_orig) {
520    if (!coap_op_observe_read(fp_orig, &observe_key, &e_proto, &e_listen_addr,
521                              &s_addr_info, &raw_packet, &oscore_info))
522      break;
523    if (observe_key != a_observe_key) {
524      if (!coap_op_observe_write(fp_new, observe_key, e_proto, e_listen_addr,
525                                 s_addr_info, raw_packet, oscore_info))
526        goto fail;
527    }
528    coap_delete_bin_const(raw_packet);
529    raw_packet = NULL;
530    coap_delete_bin_const(oscore_info);
531    oscore_info = NULL;
532  }
533  coap_delete_bin_const(raw_packet);
534  raw_packet = NULL;
535  coap_delete_bin_const(oscore_info);
536  oscore_info = NULL;
537
538  /* Add in new entry to the end */
539  if (!coap_op_observe_write(fp_new, a_observe_key, a_e_proto, *a_e_listen_addr,
540                             *a_s_addr_info, a_raw_packet, a_oscore_info))
541    goto fail;
542
543  if (fflush(fp_new) == EOF)
544    goto fail;
545  fclose(fp_new);
546  if (fp_orig)
547    fclose(fp_orig);
548  /* Either old or new is in place */
549  (void)rename(new, (const char *)session->context->observe_save_file->s);
550  coap_free_type(COAP_STRING, new);
551  return 1;
552
553fail:
554  coap_delete_bin_const(raw_packet);
555  coap_delete_bin_const(oscore_info);
556  if (fp_new)
557    fclose(fp_new);
558  if (fp_orig)
559    fclose(fp_orig);
560  if (new) {
561    (void)remove(new);
562  }
563  coap_free_type(COAP_STRING, new);
564  return 0;
565}
566
567/*
568 * client has de-registered a observe subscription request.
569 */
570static int
571coap_op_observe_deleted(coap_session_t *session,
572                        coap_subscription_t *d_observe_key,
573                        void *user_data) {
574  FILE *fp_orig = fopen((const char *)session->context->observe_save_file->s,
575                        "r");
576  FILE *fp_new = NULL;
577  coap_subscription_t *observe_key = NULL;
578  coap_proto_t e_proto;
579  coap_address_t e_listen_addr;
580  coap_addr_tuple_t s_addr_info;
581  coap_bin_const_t *raw_packet = NULL;
582  coap_bin_const_t *oscore_info = NULL;
583  char *new = NULL;
584
585  (void)user_data;
586
587  if (fp_orig == NULL)
588    goto fail;
589  new = coap_malloc_type(COAP_STRING,
590                         session->context->observe_save_file->length + 5);
591  if (!new)
592    goto fail;
593
594  strcpy(new, (const char *)session->context->observe_save_file->s);
595  strcat(new, ".tmp");
596  fp_new = fopen(new, "w+");
597  if (fp_new == NULL)
598    goto fail;
599
600  /* Go through and locate observe entry to delete and not copy it across */
601  while (1) {
602    if (!coap_op_observe_read(fp_orig, &observe_key, &e_proto, &e_listen_addr,
603                              &s_addr_info, &raw_packet, &oscore_info))
604      break;
605    if (observe_key != d_observe_key) {
606      if (!coap_op_observe_write(fp_new, observe_key, e_proto, e_listen_addr,
607                                 s_addr_info, (coap_bin_const_t *)raw_packet,
608                                 (coap_bin_const_t *)oscore_info))
609        goto fail;
610    }
611    coap_delete_bin_const(raw_packet);
612    raw_packet = NULL;
613    coap_delete_bin_const(oscore_info);
614    oscore_info = NULL;
615  }
616  coap_delete_bin_const(raw_packet);
617  raw_packet = NULL;
618  coap_delete_bin_const(oscore_info);
619  oscore_info = NULL;
620
621  if (fflush(fp_new) == EOF)
622    goto fail;
623  fclose(fp_new);
624  fclose(fp_orig);
625  /* Either old or new is in place */
626  (void)rename(new, (const char *)session->context->observe_save_file->s);
627  coap_free_type(COAP_STRING, new);
628  return 1;
629
630fail:
631  coap_delete_bin_const(raw_packet);
632  coap_delete_bin_const(oscore_info);
633  if (fp_new)
634    fclose(fp_new);
635  if (fp_orig)
636    fclose(fp_orig);
637  if (new) {
638    (void)remove(new);
639  }
640  coap_free_type(COAP_STRING, new);
641  return 0;
642}
643
644/*
645 * This should be called before coap_persist_track_funcs() to prevent
646 * coap_op_obs_cnt_track_observe() getting unnecessarily called.
647 * Should be called after coap_op_dyn_resource_load_disk() to make sure that
648 * all the resources are in the right place.
649 */
650static void
651coap_op_obs_cnt_load_disk(coap_context_t *context) {
652  FILE *fp = fopen((const char *)context->obs_cnt_save_file->s, "r");
653  char buf[1500];
654
655  if (fp == NULL)
656    return;
657
658  while (fgets(buf, sizeof(buf), fp) != NULL) {
659    char *cp = strchr(buf, ' ');
660    coap_str_const_t resource_key;
661    uint32_t observe_num;
662    coap_resource_t *r;
663
664    if (!cp)
665      break;
666
667    *cp = '\000';
668    cp++;
669    observe_num = atoi(cp);
670    /*
671     * Need to assume 0 .. (context->observe_save_freq-1) have in addition
672     * been sent so need to round up to latest possible send value
673     */
674    observe_num = ((observe_num + context->observe_save_freq) /
675                   context->observe_save_freq) *
676                  context->observe_save_freq - 1;
677    resource_key.s = (uint8_t *)buf;
678    resource_key.length = strlen(buf);
679    r = coap_get_resource_from_uri_path(context, &resource_key);
680    if (r) {
681      coap_log_debug("persist: Initial observe number being updated\n");
682      coap_persist_set_observe_num(r, observe_num);
683    }
684  }
685  fclose(fp);
686}
687
688/*
689 * Called when the observe value of a resource has been changed, but limited
690 * to be called every context->context->observe_save_freq to reduce update
691 * overheads.
692 */
693static int
694coap_op_obs_cnt_track_observe(coap_context_t *context,
695                              coap_str_const_t *resource_name,
696                              uint32_t n_observe_num,
697                              void *user_data) {
698  FILE *fp_orig = fopen((const char *)context->obs_cnt_save_file->s, "r");
699  FILE *fp_new = NULL;
700  char buf[1500];
701  char *new = NULL;
702
703  (void)user_data;
704
705  new = coap_malloc_type(COAP_STRING, context->obs_cnt_save_file->length + 5);
706  if (!new)
707    goto fail;
708
709  strcpy(new, (const char *)context->obs_cnt_save_file->s);
710  strcat(new, ".tmp");
711  fp_new = fopen(new, "w+");
712  if (fp_new == NULL)
713    goto fail;
714
715  /* Go through and locate resource entry to update */
716  while (fp_orig && fgets(buf, sizeof(buf), fp_orig) != NULL) {
717    char *cp = strchr(buf, ' ');
718    uint32_t observe_num;
719    coap_bin_const_t resource_key;
720
721    if (!cp)
722      break;
723
724    *cp = '\000';
725    cp++;
726    observe_num = atoi(cp);
727    resource_key.s = (uint8_t *)buf;
728    resource_key.length = strlen(buf);
729    if (!coap_binary_equal(resource_name, &resource_key)) {
730      if (fprintf(fp_new, "%s %u\n", resource_key.s, observe_num) < 0)
731        goto fail;
732    }
733  }
734  if (fprintf(fp_new, "%s %u\n", resource_name->s, n_observe_num) < 0)
735    goto fail;
736  if (fflush(fp_new) == EOF)
737    goto fail;
738  fclose(fp_new);
739  if (fp_orig)
740    fclose(fp_orig);
741  /* Either old or new is in place */
742  (void)rename(new, (const char *)context->obs_cnt_save_file->s);
743  coap_free_type(COAP_STRING, new);
744  return 1;
745
746fail:
747  if (fp_new)
748    fclose(fp_new);
749  if (fp_orig)
750    fclose(fp_orig);
751  if (new) {
752    (void)remove(new);
753  }
754  coap_free_type(COAP_STRING, new);
755  return 0;
756}
757
758/*
759 * Called when a resource has been deleted.
760 */
761static int
762coap_op_obs_cnt_deleted(coap_context_t *context,
763                        coap_str_const_t *resource_name) {
764  FILE *fp_orig = fopen((const char *)context->obs_cnt_save_file->s, "r");
765  FILE *fp_new = NULL;
766  char buf[1500];
767  char *new = NULL;
768
769  if (fp_orig == NULL)
770    goto fail;
771  new = coap_malloc_type(COAP_STRING, context->obs_cnt_save_file->length + 5);
772  if (!new)
773    goto fail;
774
775  strcpy(new, (const char *)context->obs_cnt_save_file->s);
776  strcat(new, ".tmp");
777  fp_new = fopen(new, "w+");
778  if (fp_new == NULL)
779    goto fail;
780
781  /* Go through and locate resource entry to delete */
782  while (fgets(buf, sizeof(buf), fp_orig) != NULL) {
783    char *cp = strchr(buf, ' ');
784    uint32_t observe_num;
785    coap_bin_const_t resource_key;
786
787    if (!cp)
788      break;
789
790    *cp = '\000';
791    cp++;
792    observe_num = atoi(cp);
793    resource_key.s = (uint8_t *)buf;
794    resource_key.length = strlen(buf);
795    if (!coap_binary_equal(resource_name, &resource_key)) {
796      if (fprintf(fp_new, "%s %u\n", resource_key.s, observe_num) < 0)
797        goto fail;
798    }
799  }
800  if (fflush(fp_new) == EOF)
801    goto fail;
802  fclose(fp_new);
803  fclose(fp_orig);
804  /* Either old or new is in place */
805  (void)rename(new, (const char *)context->obs_cnt_save_file->s);
806  coap_free_type(COAP_STRING, new);
807  return 1;
808
809fail:
810  if (fp_new)
811    fclose(fp_new);
812  if (fp_orig)
813    fclose(fp_orig);
814  if (new) {
815    (void)remove(new);
816  }
817  coap_free_type(COAP_STRING, new);
818  return 0;
819}
820
821/*
822 * read in dynamic resource entry, allocating name & raw_packet
823 * which need to be freed off by caller.
824 */
825static int
826coap_op_dyn_resource_read(FILE *fp, coap_proto_t *e_proto,
827                          coap_string_t **name,
828                          coap_binary_t **raw_packet) {
829  ssize_t size;
830
831  *name = NULL;
832  *raw_packet = NULL;
833
834  if (fread(e_proto, sizeof(*e_proto), 1, fp) == 1) {
835    /* New record 'proto len resource_name len raw_packet' */
836    if (fread(&size, sizeof(size), 1, fp) != 1)
837      goto fail;
838    if (size < 0 || size > 0x10000)
839      goto fail;
840    *name = coap_new_string(size);
841    if (!(*name))
842      goto fail;
843    if (fread((*name)->s, size, 1, fp) != 1)
844      goto fail;
845    if (fread(&size, sizeof(size), 1, fp) != 1)
846      goto fail;
847    if (size < 0 || size > 0x10000)
848      goto fail;
849    *raw_packet = coap_new_binary(size);
850    if (!(*raw_packet))
851      goto fail;
852    if (fread((*raw_packet)->s, size, 1, fp) != 1)
853      goto fail;
854    return 1;
855  }
856fail:
857  return 0;
858}
859
860/*
861 * write out dynamic resource entry.
862 */
863static int
864coap_op_dyn_resource_write(FILE *fp, coap_proto_t e_proto,
865                           coap_str_const_t *name,
866                           coap_bin_const_t *raw_packet) {
867  if (fwrite(&e_proto, sizeof(e_proto), 1, fp) != 1)
868    goto fail;
869  if (fwrite(&name->length, sizeof(name->length), 1, fp) != 1)
870    goto fail;
871  if (fwrite(name->s, name->length, 1, fp) != 1)
872    goto fail;
873  if (fwrite(&raw_packet->length, sizeof(raw_packet->length), 1, fp) != 1)
874    goto fail;
875  if (fwrite(raw_packet->s, raw_packet->length, 1, fp) != 1)
876    goto fail;
877  return 1;
878fail:
879  return 0;
880}
881
882/*
883 * This should be called before coap_persist_track_funcs() to prevent
884 * coap_op_dyn_resource_added() getting unnecessarily called.
885 *
886 * Each record 'proto len resource_name len raw_packet'
887 */
888static void
889coap_op_dyn_resource_load_disk(coap_context_t *ctx) {
890  FILE *fp_orig = NULL;
891  coap_proto_t e_proto;
892  coap_string_t *name = NULL;
893  coap_binary_t *raw_packet = NULL;
894  coap_resource_t *r;
895  coap_session_t *session = NULL;
896  coap_pdu_t *request = NULL;
897  coap_pdu_t *response = NULL;
898  coap_string_t *query = NULL;
899
900  if (!ctx->unknown_resource)
901    return;
902
903  fp_orig = fopen((const char *)ctx->dyn_resource_save_file->s, "r");
904  if (fp_orig == NULL)
905    return;
906  session = (coap_session_t *)coap_malloc_type(COAP_SESSION,
907                                               sizeof(coap_session_t));
908  if (!session)
909    goto fail;
910  memset(session, 0, sizeof(coap_session_t));
911  session->context = ctx;
912
913  /* Go through and create each dynamic resource if it does not exist*/
914  while (1) {
915    if (!coap_op_dyn_resource_read(fp_orig, &e_proto, &name, &raw_packet))
916      break;
917    r = coap_get_resource_from_uri_path(ctx, (coap_str_const_t *)name);
918    if (!r) {
919      /* Create the new resource using the application logic */
920
921      coap_log_debug("persist: dynamic resource being re-created\n");
922      /*
923       * Need max space incase PDU is updated with updated token,
924       * huge size etc.
925       * */
926      request = coap_pdu_init(0, 0, 0, 0);
927      if (!request)
928        goto fail;
929
930      session->proto = e_proto;
931      if (!coap_pdu_parse(session->proto, raw_packet->s,
932                          raw_packet->length, request)) {
933        goto fail;
934      }
935      if (!ctx->unknown_resource->handler[request->code-1])
936        goto fail;
937      response = coap_pdu_init(0, 0, 0, 0);
938      if (!response)
939        goto fail;
940      query = coap_get_query(request);
941      /* Call the application handler to set up this dynamic resource */
942      ctx->unknown_resource->handler[request->code-1](ctx->unknown_resource,
943                                                      session, request,
944                                                      query, response);
945      coap_delete_string(query);
946      query = NULL;
947      coap_delete_pdu(request);
948      request = NULL;
949      coap_delete_pdu(response);
950      response = NULL;
951    }
952    coap_delete_string(name);
953    coap_delete_binary(raw_packet);
954  }
955fail:
956  coap_delete_string(name);
957  coap_delete_binary(raw_packet);
958  coap_delete_string(query);
959  coap_delete_pdu(request);
960  coap_delete_pdu(response);
961  fclose(fp_orig);
962  coap_free_type(COAP_SESSION, session);
963}
964
965/*
966 * Server has set up a new dynamic resource agains a request for an unknown
967 * resource.
968 */
969static int
970coap_op_dyn_resource_added(coap_session_t *session,
971                           coap_str_const_t *resource_name,
972                           coap_bin_const_t *packet,
973                           void *user_data) {
974  FILE *fp_orig;
975  FILE *fp_new = NULL;
976  char *new = NULL;
977  coap_context_t *context = session->context;
978  coap_string_t *name = NULL;
979  coap_binary_t *raw_packet = NULL;
980  coap_proto_t e_proto;
981
982  (void)user_data;
983
984  fp_orig = fopen((const char *)context->dyn_resource_save_file->s, "a");
985  if (fp_orig == NULL)
986    return 0;
987
988  new = coap_malloc_type(COAP_STRING,
989                         context->dyn_resource_save_file->length + 5);
990  if (!new)
991    goto fail;
992
993  strcpy(new, (const char *)context->dyn_resource_save_file->s);
994  strcat(new, ".tmp");
995  fp_new = fopen(new, "w+");
996  if (fp_new == NULL)
997    goto fail;
998
999  /* Go through and locate duplicate resource to delete */
1000  while (1) {
1001    if (!coap_op_dyn_resource_read(fp_orig, &e_proto, &name, &raw_packet))
1002      break;
1003    if (!coap_string_equal(resource_name, name)) {
1004      /* Copy across non-matching entry */
1005      if (!coap_op_dyn_resource_write(fp_new, e_proto, (coap_str_const_t *)name,
1006                                      (coap_bin_const_t *)raw_packet))
1007        break;
1008    }
1009    coap_delete_string(name);
1010    name = NULL;
1011    coap_delete_binary(raw_packet);
1012    raw_packet = NULL;
1013  }
1014  coap_delete_string(name);
1015  coap_delete_binary(raw_packet);
1016  /* Add new entry to the end */
1017  if (!coap_op_dyn_resource_write(fp_new, session->proto,
1018                                  resource_name, packet))
1019    goto fail;
1020
1021  if (fflush(fp_new) == EOF)
1022    goto fail;
1023  fclose(fp_new);
1024  fclose(fp_orig);
1025  /* Either old or new is in place */
1026  (void)rename(new, (const char *)context->dyn_resource_save_file->s);
1027  coap_free_type(COAP_STRING, new);
1028  return 1;
1029
1030fail:
1031  if (fp_new)
1032    fclose(fp_new);
1033  if (fp_orig)
1034    fclose(fp_orig);
1035  if (new) {
1036    (void)remove(new);
1037  }
1038  coap_free_type(COAP_STRING, new);
1039  return 0;
1040}
1041
1042/*
1043 * Server has deleted a resource
1044 */
1045static int
1046coap_op_resource_deleted(coap_context_t *context,
1047                         coap_str_const_t *resource_name,
1048                         void *user_data) {
1049  FILE *fp_orig = NULL;
1050  FILE *fp_new = NULL;
1051  char *new = NULL;
1052  coap_proto_t e_proto;
1053  coap_string_t *name = NULL;
1054  coap_binary_t *raw_packet = NULL;
1055  (void)user_data;
1056
1057  coap_op_obs_cnt_deleted(context, resource_name);
1058
1059  fp_orig = fopen((const char *)context->dyn_resource_save_file->s, "r");
1060  if (fp_orig == NULL)
1061    return 1;
1062
1063  new = coap_malloc_type(COAP_STRING,
1064                         context->dyn_resource_save_file->length + 5);
1065  if (!new)
1066    goto fail;
1067
1068  strcpy(new, (const char *)context->dyn_resource_save_file->s);
1069  strcat(new, ".tmp");
1070  fp_new = fopen(new, "w+");
1071  if (fp_new == NULL)
1072    goto fail;
1073
1074  /* Go through and locate resource to delete and not copy it across */
1075  while (1) {
1076    if (!coap_op_dyn_resource_read(fp_orig, &e_proto, &name, &raw_packet))
1077      break;
1078    if (!coap_string_equal(resource_name, name)) {
1079      /* Copy across non-matching entry */
1080      if (!coap_op_dyn_resource_write(fp_new, e_proto, (coap_str_const_t *)name,
1081                                      (coap_bin_const_t *)raw_packet))
1082        break;
1083    }
1084    coap_delete_string(name);
1085    name = NULL;
1086    coap_delete_binary(raw_packet);
1087    raw_packet = NULL;
1088  }
1089  coap_delete_string(name);
1090  coap_delete_binary(raw_packet);
1091
1092  if (fflush(fp_new) == EOF)
1093    goto fail;
1094  fclose(fp_new);
1095  fclose(fp_orig);
1096  /* Either old or new is in place */
1097  (void)rename(new, (const char *)context->dyn_resource_save_file->s);
1098  coap_free_type(COAP_STRING, new);
1099  return 1;
1100
1101fail:
1102  if (fp_new)
1103    fclose(fp_new);
1104  if (fp_orig)
1105    fclose(fp_orig);
1106  if (new) {
1107    (void)remove(new);
1108  }
1109  coap_free_type(COAP_STRING, new);
1110  return 0;
1111}
1112
1113int
1114coap_persist_startup(coap_context_t *context,
1115                     const char *dyn_resource_save_file,
1116                     const char *observe_save_file,
1117                     const char *obs_cnt_save_file,
1118                     uint32_t save_freq) {
1119  if (dyn_resource_save_file) {
1120    context->dyn_resource_save_file =
1121        coap_new_bin_const((const uint8_t *)dyn_resource_save_file,
1122                           strlen(dyn_resource_save_file));
1123    if (!context->dyn_resource_save_file)
1124      return 0;
1125    coap_op_dyn_resource_load_disk(context);
1126    context->dyn_resource_added = coap_op_dyn_resource_added;
1127    context->resource_deleted = coap_op_resource_deleted;
1128  }
1129  if (obs_cnt_save_file) {
1130    context->obs_cnt_save_file =
1131        coap_new_bin_const((const uint8_t *)obs_cnt_save_file,
1132                           strlen(obs_cnt_save_file));
1133    if (!context->obs_cnt_save_file)
1134      return 0;
1135    context->observe_save_freq = save_freq ? save_freq : 1;
1136    coap_op_obs_cnt_load_disk(context);
1137    context->track_observe_value = coap_op_obs_cnt_track_observe;
1138    context->resource_deleted = coap_op_resource_deleted;
1139  }
1140  if (observe_save_file) {
1141    context->observe_save_file =
1142        coap_new_bin_const((const uint8_t *)observe_save_file,
1143                           strlen(observe_save_file));
1144    if (!context->observe_save_file)
1145      return 0;
1146    coap_op_observe_load_disk(context);
1147    context->observe_added = coap_op_observe_added;
1148    context->observe_deleted = coap_op_observe_deleted;
1149  }
1150  return 1;
1151}
1152
1153void
1154coap_persist_cleanup(coap_context_t *context) {
1155  coap_delete_bin_const(context->dyn_resource_save_file);
1156  coap_delete_bin_const(context->obs_cnt_save_file);
1157  coap_delete_bin_const(context->observe_save_file);
1158  context->dyn_resource_save_file = NULL;
1159  context->obs_cnt_save_file = NULL;
1160  context->observe_save_file = NULL;
1161
1162  /* Close down any tracking */
1163  coap_persist_track_funcs(context, NULL, NULL, NULL, NULL,
1164                           NULL, 0, NULL);
1165}
1166
1167void
1168coap_persist_stop(coap_context_t *context) {
1169  if (context == NULL)
1170    return;
1171  context->observe_no_clear = 1;
1172  coap_persist_cleanup(context);
1173}
1174#else /* ! COAP_WITH_OBSERVE_PERSIST */
1175int
1176coap_persist_startup(coap_context_t *context,
1177                     const char *dyn_resource_save_file,
1178                     const char *observe_save_file,
1179                     const char *obs_cnt_save_file,
1180                     uint32_t save_freq) {
1181  (void)context;
1182  (void)dyn_resource_save_file;
1183  (void)observe_save_file;
1184  (void)obs_cnt_save_file;
1185  (void)save_freq;
1186  return 0;
1187}
1188
1189void
1190coap_persist_stop(coap_context_t *context) {
1191  context->observe_no_clear = 1;
1192  /* Close down any tracking */
1193  coap_persist_track_funcs(context, NULL, NULL, NULL, NULL,
1194                           NULL, 0, NULL);
1195}
1196
1197#endif /* ! COAP_WITH_OBSERVE_PERSIST */
1198
1199#endif /* COAP_SERVER_SUPPORT */
1200