From 281600459a472f4235ed0ef92f3209e50dd49bcd Mon Sep 17 00:00:00 2001 From: Jon Shallow Date: Wed, 1 Feb 2023 12:24:20 +0000 Subject: [PATCH] Persist: Support tracking observe information over server restarts Add in optional callbacks for when dynamic resources are setup and deleted. Add in optional callbacks for when observe subscriptions start and stop. Add in optional callbacks for when the observe counter is updated (can be ratelimited) for a resource as well as track when a resource is deleted. Add is support for new functions Main functions with libcoap tracking support are coap_persist_startup() coap_persist_stop() coap-server has -t option to track observe information. UDP sessions supported, as well as OSCORE over UDP. Documentation updated libcoap tracking support (as opposed to application tracking) is configurable. --- CMakeLists.txt | 11 + cmake_coap_config.h.in | 3 + configure.ac | 17 + examples/coap-client.c | 9 +- examples/coap-server.c | 48 +- include/coap3/coap_net_internal.h | 40 +- include/coap3/coap_subscribe.h | 200 +++- include/coap3/coap_subscribe_internal.h | 7 + include/coap3/resource.h | 7 - include/oscore/oscore_cbor.h | 1 + include/oscore/oscore_context.h | 6 +- libcoap-3.map | 5 + libcoap-3.sym | 5 + man/Makefile.am | 1 + man/coap-client.txt.in | 2 +- man/coap-server.txt.in | 8 +- man/coap_persist.txt.in | 418 +++++++++ src/block.c | 4 +- src/coap_oscore.c | 1 + src/coap_session.c | 2 - src/coap_subscribe.c | 1106 +++++++++++++++++++++++ src/net.c | 15 +- src/oscore/oscore_context.c | 6 +- src/pdu.c | 2 +- src/resource.c | 138 ++- 25 files changed, 2025 insertions(+), 37 deletions(-) create mode 100644 man/coap_persist.txt.in diff --git a/CMakeLists.txt b/CMakeLists.txt index 0fb37e2793..fff97d0846 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -71,6 +71,10 @@ option( ENABLE_OSCORE "compile with support for OSCORE" ON) +option( + WITH_OBSERVE_PERSIST + "compile with observe persist support for server restarts" + ON) option( WITH_EPOLL "compile with epoll support" @@ -205,6 +209,13 @@ else() message(STATUS "compiling without OSCORE support") endif() +if(${WITH_OBSERVE_PERSIST}) + set(COAP_WITH_OBSERVE_PERSIST "1") + message(STATUS "compiling with observe persistence support") +else() + message(STATUS "compiling without observe persistence support") +endif() + if(${WITH_EPOLL} AND ${HAVE_EPOLL_H} AND ${HAVE_TIMERFD_H}) diff --git a/cmake_coap_config.h.in b/cmake_coap_config.h.in index c4fa50bfaf..c445dd6c41 100644 --- a/cmake_coap_config.h.in +++ b/cmake_coap_config.h.in @@ -32,6 +32,9 @@ /* Define if the library has server support */ #cmakedefine COAP_SERVER_SUPPORT @COAP_SERVER_SUPPORT@ +/* Define if the library is to have observe persistence */ +#cmakedefine COAP_WITH_OBSERVE_PERSIST @COAP_WITH_OBSERVE_PERSIST@ + /* Define if the system has epoll support */ #cmakedefine COAP_EPOLL_SUPPORT @COAP_EPOLL_SUPPORT@ diff --git a/configure.ac b/configure.ac index 1a83150acf..30d278107e 100644 --- a/configure.ac +++ b/configure.ac @@ -775,6 +775,17 @@ AC_ARG_ENABLE([async], AS_IF([test "x$build_async" != "xyes"], [AC_DEFINE(WITHOUT_ASYNC, [1], [Define to build without support for separate responses.])]) +# configure options +# __observe_persist__ +AC_ARG_ENABLE([async], + [AS_HELP_STRING([--enable-observe-persist], + [Enable building with support for persisting observes over a server restart [default=yes]])], + [build_observe_persist="$enableval"], + [build_observe_persist="yes"]) + +AS_IF([test "x$build_observe_persist" = "xyes"], + [AC_DEFINE(COAP_WITH_OBSERVE_PERSIST, [1], [Define to build support for persisting observes.])]) + # configure options # __add_default_names__ AC_ARG_ENABLE([add-default-names], @@ -1002,6 +1013,7 @@ man/coap_observe.txt man/coap_oscore.txt man/coap_pdu_access.txt man/coap_pdu_setup.txt +man/coap_persist.txt man/coap_recovery.txt man/coap_resource.txt man/coap_session.txt @@ -1079,6 +1091,11 @@ if test "x$build_add_default_names" = "xyes"; then else AC_MSG_RESULT([ add default names : "no"]) fi +if test "x$build_observe_persist" = "xyes"; then + AC_MSG_RESULT([ build Observe Persist : "yes"]) +else + AC_MSG_RESULT([ build Observe Persist : "no"]) +fi if test "x$have_epoll" = "xyes"; then AC_MSG_RESULT([ build using epoll : "$with_epoll"]) fi diff --git a/examples/coap-client.c b/examples/coap-client.c index 008de79e8f..48f5843ead 100644 --- a/examples/coap-client.c +++ b/examples/coap-client.c @@ -233,12 +233,12 @@ track_check_token(coap_bin_const_t *token) { } static void -track_flush_token(coap_bin_const_t *token) { +track_flush_token(coap_bin_const_t *token, int force) { size_t i; for (i = 0; i < tracked_tokens_count; i++) { if (coap_binary_equal(token, tracked_tokens[i].token)) { - if (!tracked_tokens[i].observe || !obs_started) { + if (force || !tracked_tokens[i].observe || !obs_started) { /* Only remove if not Observing */ coap_delete_binary(tracked_tokens[i].token); if (tracked_tokens_count-i > 1) { @@ -438,7 +438,7 @@ message_handler(coap_session_t *session COAP_UNUSED, } else { doing_getting_block = 0; if (!is_mcast) - track_flush_token(&token); + track_flush_token(&token, 0); } return COAP_RESPONSE_OK; } @@ -455,11 +455,12 @@ message_handler(coap_session_t *session COAP_UNUSED, } } fprintf(stderr, "\n"); + track_flush_token(&token, 1); } } if (!is_mcast) - track_flush_token(&token); + track_flush_token(&token, 0); /* our job is done, we can exit at any time */ ready = doing_observe ? coap_check_option(received, diff --git a/examples/coap-server.c b/examples/coap-server.c index 353c1b0ae0..2977d3c88b 100644 --- a/examples/coap-server.c +++ b/examples/coap-server.c @@ -75,6 +75,9 @@ static int doing_oscore = 0; /* set to 1 to request clean server shutdown */ static int quit = 0; +/* set to 1 if persist information is to be kept on server shutdown */ +static int keep_persist = 0; + /* changeable clock base (see handle_put_time()) */ static time_t clock_offset; static time_t my_clock_base = 0; @@ -82,6 +85,7 @@ static time_t my_clock_base = 0; coap_resource_t *time_resource = NULL; static int resource_flags = COAP_RESOURCE_FLAGS_NOTIFY_CON; +static int track_observes = 0; /* * For PKI, if one or more of cert_file, key_file and ca_file is in PKCS11 URI @@ -179,6 +183,19 @@ handle_sigint(int signum COAP_UNUSED) { quit = 1; } +#ifndef _WIN32 +/* + * SIGUSR2 handler: set quit to 1 for graceful termination + * Disable sending out 4.04 for any active observations. + * Note: coap_*() functions should not be called at sig interrupt. + */ +static void +handle_sigusr2(int signum COAP_UNUSED) { + quit = 1; + keep_persist = 1; +} +#endif /* ! _WIN32 */ + /* * This will return a correctly formed transient_value_t *, or NULL. * If an error, the passed in coap_binary_t * will get deleted. @@ -1445,6 +1462,7 @@ hnd_put_post(coap_resource_t *resource, } /* Need to de-reference as value may be in use elsewhere */ release_resource_data(session, resource_entry->value); + resource_entry->value = NULL; transient_value = alloc_resource_data(data_so_far); if (!transient_value) { coap_pdu_set_code(response, COAP_RESPONSE_CODE_INTERNAL_ERROR); @@ -1537,6 +1555,7 @@ hnd_put_post_unknown(coap_resource_t *resource COAP_UNUSED, coap_register_request_handler(r, COAP_REQUEST_PUT, hnd_put_post); coap_register_request_handler(r, COAP_REQUEST_POST, hnd_put_post); coap_register_request_handler(r, COAP_REQUEST_DELETE, hnd_delete); + coap_register_request_handler(r, COAP_REQUEST_FETCH, hnd_get); /* We possibly want to Observe the GETs */ coap_resource_set_get_observable(r, 1); coap_register_request_handler(r, COAP_REQUEST_GET, hnd_get); @@ -2123,7 +2142,8 @@ usage( const char *program, const char *version) { coap_string_tls_version(buffer, sizeof(buffer))); fprintf(stderr, "%s\n", coap_string_tls_support(buffer, sizeof(buffer))); fprintf(stderr, "\n" - "Usage: %s [-d max] [-e] [-g group] [-l loss] [-p port] [-r] [-v num]\n" + "Usage: %s [-d max] [-e] [-g group] [-l loss] [-p port] [-r] [-t]\n" + "\t\t[-v num]\n" "\t\t[-A address] [-E oscore_conf_file[,seq_file]] [-G group_if]\n" "\t\t[-L value] [-N] [-P scheme://address[:port],[name1[,name2..]]]\n" "\t\t[-T max_token_size] [-U type] [-V num] [-X size]\n" @@ -2152,6 +2172,9 @@ usage( const char *program, const char *version) { "\t \t\tonly '/', '/async' and '/.well-known/core' are enabled\n" "\t \t\tfor multicast requests support, otherwise all\n" "\t \t\tresources are enabled\n" + "\t-t \t\tTrack resource's observe values so observe\n" + "\t \t\tsubscriptions can be maintained over a server restart.\n" + "\t \t\tNote: Use 'kill SIGUSR2 ' for controlled shutdown\n" "\t-v num \t\tVerbosity level (default 4, maximum is 8) for general\n" "\t \t\tCoAP logging\n" "\t-A address\tInterface address to bind to\n" @@ -2758,7 +2781,7 @@ main(int argc, char **argv) { clock_offset = time(NULL); - while ((opt = getopt(argc, argv, "c:d:eg:G:h:i:j:J:k:l:mnp:rs:u:v:A:C:E:L:M:NP:R:S:T:U:V:X:")) != -1) { + while ((opt = getopt(argc, argv, "c:d:eg:G:h:i:j:J:k:l:mnp:rs:tu:v:A:C:E:L:M:NP:R:S:T:U:V:X:")) != -1) { switch (opt) { case 'A' : strncpy(addr_str, optarg, NI_MAXHOST-1); @@ -2876,6 +2899,9 @@ main(int argc, char **argv) { exit(1); } break; + case 't': + track_observes = 1; + break; case 'u': #if SERVER_CAN_PROXY user_length = cmdline_read_user(optarg, &user, MAX_USER); @@ -2914,6 +2940,8 @@ main(int argc, char **argv) { sa.sa_flags = 0; sigaction (SIGINT, &sa, NULL); sigaction (SIGTERM, &sa, NULL); + sa.sa_handler = handle_sigusr2; + sigaction (SIGUSR2, &sa, NULL); /* So we do not exit on a SIGPIPE */ sa.sa_handler = SIG_IGN; sigaction (SIGPIPE, &sa, NULL); @@ -2947,6 +2975,20 @@ main(int argc, char **argv) { if (group) coap_join_mcast_group_intf(ctx, group, group_if); + if (track_observes) { + /* + * Read in and set up appropriate persist information. + * Note that this should be done after ctx is properly set up. + */ + if (!coap_persist_startup(ctx, + "/tmp/coap_dyn_resource_save_file", + "/tmp/coap_observe_save_file", + "/tmp/coap_obs_cnt_save_file", 10)) { + fprintf(stderr, "Unable to set up persist logic\n"); + goto finish; + } + } + coap_fd = coap_context_get_coap_fd(ctx); if (coap_fd != -1) { /* if coap_fd is -1, then epoll is not supported within libcoap */ @@ -3035,6 +3077,8 @@ main(int argc, char **argv) { } finish: + if (keep_persist) + coap_persist_stop(ctx); coap_free(ca_mem); coap_free(cert_mem); diff --git a/include/coap3/coap_net_internal.h b/include/coap3/coap_net_internal.h index 8c3bbc6101..a3faa822f6 100644 --- a/include/coap3/coap_net_internal.h +++ b/include/coap3/coap_net_internal.h @@ -19,6 +19,7 @@ #define COAP_NET_INTERNAL_H_ #include "coap_internal.h" +#include "coap_subscribe.h" /** * @ingroup internal_api @@ -95,11 +96,40 @@ struct coap_context_t { #endif /* HAVE_OSCORE */ #if COAP_CLIENT_SUPPORT - coap_response_handler_t response_handler; + coap_response_handler_t response_handler; /**< Called when a response is + received */ #endif /* COAP_CLIENT_SUPPORT */ - coap_nack_handler_t nack_handler; - coap_ping_handler_t ping_handler; - coap_pong_handler_t pong_handler; + coap_nack_handler_t nack_handler; /**< Called when a response issue has + occurred */ + coap_ping_handler_t ping_handler; /**< Called when a CoAP ping is received */ + coap_pong_handler_t pong_handler; /**< Called when a ping response + is received */ + +#if COAP_SERVER_SUPPORT + coap_observe_added_t observe_added; /**< Called when there is a new observe + subscription request */ + coap_observe_deleted_t observe_deleted; /**< Called when there is a observe + subscription de-register request */ + void *observe_user_data; /**< App provided data for use in observe_added or + observe_deleted */ + uint32_t observe_save_freq; /**< How frequently to update observe value */ + coap_track_observe_value_t track_observe_value; /**< Callback to save observe + value when updated */ + coap_dyn_resource_added_t dyn_resource_added; /**< Callback to save dynamic + resource when created */ + coap_resource_deleted_t resource_deleted; /**< Invoked when resource + is deleted */ +#if COAP_WITH_OBSERVE_PERSIST + coap_bin_const_t *dyn_resource_save_file; /** Where dynamic resource requests + that create resources are + tracked */ + coap_bin_const_t *obs_cnt_save_file; /** Where resource observe counters are + tracked */ + coap_bin_const_t *observe_save_file; /** Where observes are tracked */ + coap_pdu_t *unknown_pdu; /** PDU used for unknown resource request */ + coap_session_t *unknown_session; /** Session used for unknown resource request */ +#endif /* COAP_WITH_OBSERVE_PERSIST */ +#endif /* COAP_SERVER_SUPPORT */ /** * Callback function that is used to signal events to the @@ -153,6 +183,8 @@ struct coap_context_t { #endif /* ! COAP_EPOLL_SUPPORT */ #if COAP_SERVER_SUPPORT uint8_t observe_pending; /**< Observe response pending */ + uint8_t observe_no_clear; /**< Observe 4.04 not to be sent on deleting + resource */ uint8_t mcast_per_resource; /**< Mcast controlled on a per resource basis */ #endif /* COAP_SERVER_SUPPORT */ diff --git a/include/coap3/coap_subscribe.h b/include/coap3/coap_subscribe.h index 063e0eaa66..cf7c7bcf77 100644 --- a/include/coap3/coap_subscribe.h +++ b/include/coap3/coap_subscribe.h @@ -60,9 +60,203 @@ void coap_resource_set_get_observable(coap_resource_t *resource, int mode); * * @return @c 1 if the Observe has been triggered, @c 0 otherwise. */ -int -coap_resource_notify_observers(coap_resource_t *resource, - const coap_string_t *query); +int coap_resource_notify_observers(coap_resource_t *resource, + const coap_string_t *query); + +/** + * Callback handler definition called when a new observe has been set up, + * as defined in coap_persist_track_funcs(). + * + * @param session The current session. + * @param observe_key The pointer to the subscription. + * @param e_proto The CoAP protocol in use for the session / endpoint. + * @param e_listen_addr The IP/port tthat the endpoint is listening on. + * @param s_addr_info Local / Remote IP addresses. ports etc. of session. + * @param raw_packet L7 packet as seen on the wire (could be concatenated if + * Block1 FETCH is being used. + * @param oscore_info Has OSCORE information if OSCORE is protecting the + * session or NULL if OSCORE is not in use. + * @param user_data Application provided information from + * coap_persist_track_funcs(). + * + * @return @c 1 if success else @c 0. + */ +typedef int (*coap_observe_added_t)(coap_session_t *session, + coap_subscription_t *observe_key, + coap_proto_t e_proto, + coap_address_t *e_listen_addr, + coap_addr_tuple_t *s_addr_info, + coap_bin_const_t *raw_packet, + coap_bin_const_t *oscore_info, + void *user_data); + +/** + * Callback handler definition called when an observe is being removed, + * as defined in coap_persist_track_funcs(). + * + * @param session The current session. + * @param observe_key The pointer to the subscription. + * @param user_data Application provided information from + * coap_persist_track_funcs(). + * + * @return @c 1 if success else @c 0. + */ +typedef int (*coap_observe_deleted_t)(coap_session_t *session, + coap_subscription_t *observe_key, + void *user_data); + +/** + * Callback handler definition called when an observe unsolicited response is + * being sent, as defined in coap_persist_track_funcs(). + * + * Note: This will only get called every save_freq as defined by + * coap_persist_track_funcs(). + * + * @param context The current CoAP context. + * @param resource_name The uri path name of the resource. + * @param observe_num The current observe value just sent. + * @param user_data Application provided information from + * coap_persist_track_funcs(). + * + * @return @c 1 if success else @c 0. + */ +typedef int (*coap_track_observe_value_t)(coap_context_t *context, + coap_str_const_t *resource_name, + uint32_t observe_num, + void *user_data); + +/** + * Callback handler definition called when a dynamic resource is getting + * created, as defined in coap_persist_track_funcs(). + * + * @param session The current CoAP session. + * @param resource_name The uri path name of the resource. + * @param raw_packet L7 packet as seen on the wire (could be concatenated if + * Block1 PUT/POST/FETCH used to create resource. + * @param user_data Application provided information from + * coap_persist_track_funcs(). + * + * @return @c 1 if success else @c 0. + */ +typedef int (*coap_dyn_resource_added_t)(coap_session_t *session, + coap_str_const_t *resource_name, + coap_bin_const_t *raw_packet, + void *user_data); + +/** + * Callback handler definition called when resource is removed, + * as defined in coap_persist_track_funcs(). + * + * This will remove any dynamic resources that are being tracked as well + * as any observe value tracking. + * + * @param context The current CoAP context. + * @param resource_name The uri path name of the resource. + * @param user_data Application provided information from + * coap_persist_track_funcs(). + * + * @return @c 1 if success else @c 0. + */ +typedef int (*coap_resource_deleted_t)(coap_context_t *context, + coap_str_const_t *resource_name, + void *user_data); + +/** + * Set up callbacks to handle persist tracking so on a coap-server inadvertent + * restart, existing observe subscriptions can continue. + * + * @param context The current CoAP context. + * @param observe_added Called when a new observe subscription is set up. + * @param observe_deleted Called when a observe subscription is de-registered. + * @param track_observe_value Called every @p save_freq so current observe + * value can be tracked. + * @param dyn_resource_added Called whan a dynamic resource is created from the + * 'unknown' resource for tracking. + * @param resource_deleted Called when a resource is removed. + * @param save_freq Frequency of change of observe value for calling + * @p save_observe_value + * @param user_data App defined data (can be NULL) passed into various + * callbacks. + */ +void coap_persist_track_funcs(coap_context_t *context, + coap_observe_added_t observe_added, + coap_observe_deleted_t observe_deleted, + coap_track_observe_value_t track_observe_value, + coap_dyn_resource_added_t dyn_resource_added, + coap_resource_deleted_t resource_deleted, + uint32_t save_freq, + void *user_data); + +/** + * Set up an active subscription for an observe that was previously active + * over a coap-server inadvertant restart. + * + * Only UDP sessions currently supported. + * + * @param context The context that the session is to be associated with. + * @param e_proto The CoAP protocol in use for the session / endpoint. + * @param e_listen_addr The IP/port tthat the endpoint is listening on. + * @param s_addr_info Local / Remote IP addresses. ports etc. of previous + * session. + * @param raw_packet L7 packet as seen on the wire (could be concatenated if + * Block1 FETCH is being used. + * @param oscore_info Has OSCORE information if OSCORE is protecting the + * session or NULL if OSCORE is not in use. + * + * @return ptr to subscription if success else @c NULL. + */ +coap_subscription_t *coap_persist_observe_add(coap_context_t *context, + coap_proto_t e_proto, + const coap_address_t *e_listen_addr, + const coap_addr_tuple_t *s_addr_info, + const coap_bin_const_t *raw_packet, + const coap_bin_const_t *oscore_info); + +/** + * Start up persist tracking using the libcoap module. If the files already + * exist with saved data, then this information is used in building back + * up the persist information. + * + * @param context The current CoAP context. + * @param dyn_resource_save_file File where dynamically created resource + * information is stored or NULL if not required. + * @param observe_save_file File where observe information is stored or NULL + * if not required. + * @param obs_cnt_save_file File where resource observe counter information + * is stored or NULL if not required. + * @param save_freq Frequency of change of observe value for calling + * the save observe counter logic. + * + * @return @c 1 if success else @c 0. + */ +int coap_persist_startup(coap_context_t *context, + const char *dyn_resource_save_file, + const char *observe_save_file, + const char *obs_cnt_save_file, + uint32_t save_freq); + +/** + * Stop tracking persist information, leaving the current persist information + * in the files defined in coap_persist_startup(). It is then safe to call + * coap_free_context() to close the application down cleanly. + * + * Alternatively, if coap_persist_track_funcs() was called, then this will + * disable all the callbacks, as well as making sure that no 4.04 is sent out + * for any active observe subscriptions when the resource is deleted after + * subsequently calling coap_free_context(). + * + * @param context The context that tracking information is to be stopped on. + */ +void coap_persist_stop(coap_context_t *context); + +/** + * Sets the current observe number value. + + * @param resource The resource to update. + * @param observe_num The value to set the observe number to. + */ +void coap_persist_set_observe_num(coap_resource_t *resource, + uint32_t observe_num); /** @} */ diff --git a/include/coap3/coap_subscribe_internal.h b/include/coap3/coap_subscribe_internal.h index 082720922d..fcd1fc933f 100644 --- a/include/coap3/coap_subscribe_internal.h +++ b/include/coap3/coap_subscribe_internal.h @@ -156,6 +156,13 @@ int coap_delete_observer(coap_resource_t *resource, */ void coap_delete_observers(coap_context_t *context, coap_session_t *session); +/** + * Close down persist tracking, releasing any memory used. + * + * @param context The current CoAP context. + */ +void coap_persist_cleanup(coap_context_t *context); + /** @} */ #endif /* COAP_SERVER_SUPPORT */ diff --git a/include/coap3/resource.h b/include/coap3/resource.h index 5081a1e731..e8073f1eb0 100644 --- a/include/coap3/resource.h +++ b/include/coap3/resource.h @@ -22,13 +22,6 @@ #define COAP_RESOURCE_CHECK_TIME 2 #endif /* COAP_RESOURCE_CHECK_TIME */ -#include "coap_async.h" -#include "block.h" -#include "coap_str.h" -#include "pdu.h" -#include "coap_net.h" -#include "coap_subscribe.h" - /** * @ingroup application_api * @defgroup coap_resource Resource Configuraton diff --git a/include/oscore/oscore_cbor.h b/include/oscore/oscore_cbor.h index a2baf6de73..a3ed4a1b3e 100644 --- a/include/oscore/oscore_cbor.h +++ b/include/oscore/oscore_cbor.h @@ -69,6 +69,7 @@ #define CBOR_FALSE 20 #define CBOR_TRUE 21 +#define CBOR_NULL 22 size_t oscore_cbor_put_nil(uint8_t **buffer, size_t *buf_size); diff --git a/include/oscore/oscore_context.h b/include/oscore/oscore_context.h index 642be774ad..1806cccb5f 100644 --- a/include/oscore/oscore_context.h +++ b/include/oscore/oscore_context.h @@ -240,9 +240,9 @@ void oscore_log_char_value(coap_log_t level, const char *name, * * return The OSCORE context and @p recipient_ctx updated, or NULL is error. */ -oscore_ctx_t *oscore_find_context(coap_context_t *c_context, - coap_bin_const_t rcpkey_id, - coap_bin_const_t *ctxkey_id, +oscore_ctx_t *oscore_find_context(const coap_context_t *c_context, + const coap_bin_const_t rcpkey_id, + const coap_bin_const_t *ctxkey_id, uint8_t *oscore_r2, oscore_recipient_ctx_t **recipient_ctx); diff --git a/libcoap-3.map b/libcoap-3.map index 30a7438a1c..2bc08960fb 100644 --- a/libcoap-3.map +++ b/libcoap-3.map @@ -171,6 +171,11 @@ global: coap_pdu_set_code; coap_pdu_set_mid; coap_pdu_set_type; + coap_persist_observe_add; + coap_persist_set_observe_num; + coap_persist_startup; + coap_persist_stop; + coap_persist_track_funcs; coap_print_addr; coap_print_link; coap_prng; diff --git a/libcoap-3.sym b/libcoap-3.sym index ffb785ae8a..9aed323dba 100644 --- a/libcoap-3.sym +++ b/libcoap-3.sym @@ -169,6 +169,11 @@ coap_pdu_parse coap_pdu_set_code coap_pdu_set_mid coap_pdu_set_type +coap_persist_observe_add +coap_persist_set_observe_num +coap_persist_startup +coap_persist_stop +coap_persist_track_funcs coap_print_addr coap_print_link coap_prng diff --git a/man/Makefile.am b/man/Makefile.am index bdde8e23a3..693db4f7df 100644 --- a/man/Makefile.am +++ b/man/Makefile.am @@ -36,6 +36,7 @@ TXT3 = coap_address.txt \ coap_oscore.txt \ coap_pdu_access.txt \ coap_pdu_setup.txt \ + coap_persist.txt \ coap_recovery.txt \ coap_resource.txt \ coap_session.txt \ diff --git a/man/coap-client.txt.in b/man/coap-client.txt.in index 0944eab5c3..768c596c1d 100644 --- a/man/coap-client.txt.in +++ b/man/coap-client.txt.in @@ -27,7 +27,7 @@ SYNOPSIS [*-P* scheme://addr[:port]] [*-T* token] [*-U*] [*-V* num] [*-X* size] [[*-h* match_hint_file] [*-k* key] [*-u* user]] - [[*-c* certfile] [*-j* keyfile] [-n] [*-C* cafile] + [[*-c* certfile] [*-j* keyfile] [*-n*] [*-C* cafile] [*-J* pkcs11_pin] [*-M* rpk_file] [*-R* trust_casfile]] URI For *coap-client* versions that use libcoap compiled for different diff --git a/man/coap-server.txt.in b/man/coap-server.txt.in index d9f329e56b..e71001ba29 100644 --- a/man/coap-server.txt.in +++ b/man/coap-server.txt.in @@ -19,7 +19,8 @@ coap-server-notls SYNOPSIS -------- -*coap-server* [*-d* max] [*-e*] [*-g* group] [*-l* loss] [*-p* port] [-r] +*coap-server* [*-d* max] [*-e*] [*-g* group] [*-l* loss] [*-p* port] [*-r*] + [*-t*] [*-v* num] [*-A* address] [*-E* oscore_conf_file[,seq_file]] [*-G* group_if] [*-L* value] [*-N*] [*-P* scheme://addr[:port],[name1[,name2..]]] @@ -73,6 +74,11 @@ OPTIONS - General and '/.well-known/core' are enabled for multicast requests support, otherwise all resources are enabled. +*-t* :: + Track resource's observe values so observe subscriptions can be + maintained over a server restart. + Note: Use 'kill SIGUSR2 ' for controlled shutdown. + *-v* num:: The verbosity level to use (default 4, maximum is 8) for general CoAP logging. diff --git a/man/coap_persist.txt.in b/man/coap_persist.txt.in new file mode 100644 index 0000000000..3fa8e0014f --- /dev/null +++ b/man/coap_persist.txt.in @@ -0,0 +1,418 @@ +// -*- mode:doc; -*- +// vim: set syntax=asciidoc tw=0 + +coap_persist(3) +=============== +:doctype: manpage +:man source: coap_persist +:man version: @PACKAGE_VERSION@ +:man manual: libcoap Manual + +NAME +---- +coap_persist, +coap_persist_startup, +coap_persist_stop, +coap_persist_track_funcs, +coap_persist_observe_add, +coap_persist_set_observe_num +- Work with CoAP persist support + +SYNOPSIS +-------- +*#include * + +*int coap_persist_startup(coap_context_t *_context_, +const char *_dyn_resource_save_file_, const char *_observe_save_file_, +const char *_obs_cnt_save_file_, uint32_t _save_freq_);* + +*void coap_persist_stop(coap_context_t *_context_);* + +*void coap_persist_track_funcs(coap_context_t *_context_, +coap_observe_added_t _observe_added_, coap_observe_deleted_t _observe_deleted_, +coap_track_observe_value_t _track_observe_value_, +coap_dyn_resource_added_t _dyn_resource_added_, +coap_resource_deleted_t _resource_deleted_, +uint32_t _save_freq_, void *_user_data_);* + +*coap_subscription_t *coap_persist_observe_add(coap_context_t *_context_, +coap_proto_t _e_proto_, const coap_address_t *_e_listen_addr_, +const coap_addr_tuple_t *_s_addr_info_, const coap_bin_const_t *_raw_packet_, +const coap_bin_const_t *_oscore_info_);* + +*void coap_persist_set_observe_num(coap_resource_t *_resource_, +uint32_t _start_observe_no_);* + +For specific (D)TLS library support, link with +*-lcoap-@LIBCOAP_API_VERSION@-notls*, *-lcoap-@LIBCOAP_API_VERSION@-gnutls*, +*-lcoap-@LIBCOAP_API_VERSION@-openssl*, *-lcoap-@LIBCOAP_API_VERSION@-mbedtls* +or *-lcoap-@LIBCOAP_API_VERSION@-tinydtls*. Otherwise, link with +*-lcoap-@LIBCOAP_API_VERSION@* to get the default (D)TLS library support. + +DESCRIPTION +----------- +When a coap-server is restarted, state information does not usually persist +over the restart. libcoap has optional compiled in support for maintaining +resources that were dynamically created, tracking ongoing observe subscriptions +and maintaining OSCORE protection. + +There are callbacks provided to support doing this as an alternative persist +storage in the coap-server application. + +*NOTE:* The observe persist support is only available for UDP sessions. + +When using the libcoap compiled in support, only two functions need to be +called by the application. *coap_persist_startup*() defines the file names to +use for maintaining the persist information over an application restart, and +*coap_persist_stop*() is called to preserve any persist information over the +server restart. + +FUNCTIONS +--------- + +*Function: coap_persist_startup()* + +The *coap_persist_startup*() function is used to enable persist tracking for +_context_ so when a coap-server is restarted, the persist tracked information +can be added back in for the server logic. +_dyn_resource_save_file_ is used to save the current list of resources created +from a request to the unknown resource. +_observe_save_file_ is used to save the current list of active observe +subscriptions. +_obs_cnt_save_file_ is used to save the current observe counter used when +sending an observe unsolicited response. _obs_cnt_save_file_ only gets +updated every _save_freq_ updates. + +If any of the files exist and are not empty, when *coap_persist_startup*() is +called, the information is loaded back into the server logic, and for the +active observe subscriptions a new server session is created for sending out +the ongoing observe updates (UDP only supported). + +If a file is defined as NULL, then that particular persist information is not +tracked by the libcoap module. This allows a combination of +*coap_persist_track_funcs*() for customized persist tracking followed by a +call to *coap_persist_startup*(). + +*Function: coap_persist_stop()* + +The *coap_persist_stop*() function is used to disable any current persist +tracking as set up by *coap_persist_startup*() for _context_ and preserve the +tracking for when the coap-server application restarts. + +If using *coap_persist_track_funcs*(), then calling *coap_persist_stop*() +will stop any 4.04 unsolicited response messages being sent when a +resource that has an active observe subscription is deleted (as happens +when *coap_free_context*() is subsequentially called). + +*Function: coap_persist_track_funcs()* + +The *coap_persist_track_funcs*() function is used to setup callback functions +associated with _context_ that track information so that the current tracked +information state can be rebuilt following a server application restart. It is +the responsibility of the server application to track the appropriate +information. + +The _observe_added_ callback function prototype, called when a client +subscribes to a resource for observation, is defined as: +[source, c] +---- +typedef int (*coap_observe_added_t)(coap_session_t *session, + coap_subscription_t *observe_key, + coap_proto_t e_proto, + coap_address_t *e_listen_addr, + coap_addr_tuple_t *s_addr_info, + coap_bin_const_t *raw_packet, + coap_bin_const_t *oscore_info, + void *user_data); +---- + +The _observe_deleted_ callback function prototype, called when a client +removes the subscription to a resource for observation, is defined as: +[source, c] +---- +typedef int (*coap_observe_deleted_t)(coap_session_t *session, + coap_subscription_t *observe_key, + void *user_data); +---- + +The _track_observe_value_ callback function prototype, called when a new +unsolicited observe response is went (every _save_freq_), is defined as: +[source, c] +---- +typedef int (*coap_track_observe_value_t)(coap_context_t *context, + coap_str_const_t *resource_name, + uint32_t observe_num, + void *user_data); +---- + +The _dyn_resource_added_ callback function prototype, called whenever a +resource is created from a request that is calling the resource unknown +handler, is defined as: +[source, c] +---- +typedef int (*coap_dyn_resource_added_t)(coap_session_t *session, + coap_str_const_t *resource_name, + coap_bin_const_t *raw_packet, + void *user_data); +---- + +The _resource_deleted_ callback function prototype, called whenever a +resource is deleted, is defined as: +[source, c] +---- +typedef int (*coap_resource_deleted_t)(coap_context_t *context, + coap_str_const_t *resource_name, + void *user_data); +---- + +_save_freq_ defines the frequency of the update to the observe value when +libcoap calls _track_observe_value_. _user_data_ is application defined and +is passed into all of the callback handlers. + +*Function: coap_persist_observe_add()* + +The *coap_persist_observe_add*() function is used to set up a session and a +observe subscription request (typically following a server reboot) so that a +client can continue to receive unsolicited observe responses without having +to establish a new session and issue a new observe subscription request. The +new session is associated with the endpoint defined by _e_proto_ and +_e_listen_address_. The session has the IP addresses as defined by +_s_addr_info_. _raw_packet_ contains the layer 7 of the IP packet that was +originally used to request the observe subscription. Optional _oscore_info_ +defines the OSCORE information if packets are protected by OSCORE. + _e_proto_, _e_listen_addr_, _s_addr_info_, _raw_packet_ and _oscore_info_ +are the same as passed into the _coap_observe_added_t_ callback. + +*Function: coap_persist_set_observe_num()* + +The *coap_persist_set_observe_num*() function is used to update the +_resource_'s current observe counter to start from _start_observe_no_ +instead of 0, + +RETURN VALUES +------------- +The *coap_persist_startup*() returns 1 on success else 0. + +The *coap_persist_observe_add*() function returns a newly created observe +subscription or NULL on failure. + +EXAMPLES +-------- +*Simple Time Server* + +[source, c] +---- +#include + +#include + +coap_resource_t *time_resource = NULL; + +/* specific GET "time" handler, called from hnd_get_generic() */ + +static void +hnd_get_time(coap_resource_t *resource, coap_session_t *session, +const coap_pdu_t *request, const coap_string_t *query, coap_pdu_t *response) { + + unsigned char buf[40]; + size_t len; + time_t now; + (void)resource; + (void)session; + + /* ... Additional analysis code for resource, request pdu etc. ... */ + + /* After analysis, generate a suitable response */ + + /* Note that token, if set, is already in the response pdu */ + + now = time(NULL); + + if (query != NULL && coap_string_equal(query, coap_make_str_const("secs"))) { + /* Output secs since Jan 1 1970 */ + len = snprintf((char *)buf, sizeof(buf), "%lu", now); + } + else { + /* Output human-readable time */ + struct tm *tmp; + tmp = gmtime(&now); + if (!tmp) { + /* If 'now' is not valid */ + coap_pdu_set_code(response, COAP_RESPONSE_CODE_NOT_FOUND); + return; + } + len = strftime((char *)buf, sizeof(buf), "%b %d %H:%M:%S", tmp); + } + coap_pdu_set_code(response, COAP_RESPONSE_CODE_CONTENT); + /* + * Invoke coap_add_data_large_response() to do all the hard work. + * + * Define the format - COAP_MEDIATYPE_TEXT_PLAIN - to add in + * Define how long this response is valid for (secs) - 1 - to add in. + * ETAG Option added internally with unique value as param set to 0 + * + * OBSERVE Option added internally if needed within the function + * BLOCK2 Option added internally if output too large + * SIZE2 Option added internally + */ + coap_add_data_large_response(resource, session, request, response, + query, COAP_MEDIATYPE_TEXT_PLAIN, 1, 0, + len, + buf, NULL, NULL); +} + +/* Generic GET handler */ + +static void +hnd_get_generic(coap_resource_t *resource, coap_session_t *session, +const coap_pdu_t *request, const coap_string_t *query, coap_pdu_t *response) { + + coap_str_const_t *uri_path = coap_resource_get_uri_path(resource); + + if (!uri_path) { + /* Unexpected Failure */ + coap_pdu_set_code(response, COAP_RESPONSE_CODE_BAD_REQUEST); + return; + } + + /* Is this the "time" resource" ? */ + if (coap_string_equal(uri_path, coap_make_str_const("time"))) { + hnd_get_time(resource, session, request, query, response); + return; + } + + /* Other resources code */ + + /* Failure response */ + coap_pdu_set_code(response, COAP_RESPONSE_CODE_BAD_REQUEST); +} + +/* Initialize generic GET handler */ + +static void +init_resources(coap_context_t *ctx) +{ + + coap_resource_t *r; + + /* Create a resource to return return or update time */ + r = coap_resource_init(coap_make_str_const("time"), + COAP_RESOURCE_FLAGS_NOTIFY_CON); + + /* We are using a generic GET handler here */ + coap_register_request_handler(r, COAP_REQUEST_GET, hnd_get_generic); + + coap_resource_set_get_observable(r, 1); + + coap_add_resource(ctx, r); + time_resource = r; + +} + +int main(int argc, char *argv[]){ + + coap_context_t *ctx = NULL; + coap_endpoint_t *ep = NULL; + coap_address_t addr; + unsigned wait_ms; + struct timeval tv_last = {0, 0}; + /* Remove (void) definition if variable is used */ + (void)argc; + (void)argv; + + memset (&tv_last, 0, sizeof(tv_last)); + + /* Create the libcoap context */ + ctx = coap_new_context(NULL); + if (!ctx) { + exit(1); + } + /* See coap_block(3) */ + coap_context_set_block_mode(ctx, + COAP_BLOCK_USE_LIBCOAP | COAP_BLOCK_SINGLE_BODY); + + coap_address_init(&addr); + addr.addr.sa.sa_family = AF_INET; + addr.addr.sin.sin_port = ntohs(COAP_DEFAULT_PORT); + ep = coap_new_endpoint(ctx, &addr, COAP_PROTO_UDP); + + /* Other Set up Code */ + + init_resources(ctx); + + if (!coap_persist_startup(ctx, + "/tmp/coap_dyn_resource_save_file", + "/tmp/coap_observe_save_file", + "/tmp/coap_obs_cnt_save_file", 10)) { + fprintf(stderr, "Unable to set up persist logic\n"); + exit(1); + } + + wait_ms = COAP_RESOURCE_CHECK_TIME * 1000; + + while (1) { + int result = coap_io_process( ctx, wait_ms ); + if ( result < 0 ) { + break; + } else if ( result && (unsigned)result < wait_ms ) { + /* decrement if there is a result wait time returned */ + wait_ms -= result; + } else { + /* + * result == 0, or result >= wait_ms + * (wait_ms could have decremented to a small value, below + * the granularity of the timer in coap_io_process() and hence + * result == 0) + */ + wait_ms = COAP_RESOURCE_CHECK_TIME * 1000; + } + if (time_resource) { + struct timeval tv_now; + if (gettimeofday (&tv_now, NULL) == 0) { + if (tv_last.tv_sec != tv_now.tv_sec) { + /* Happens once per second */ + tv_last = tv_now; + coap_resource_notify_observers(time_resource, NULL); + } + /* need to wait until next second starts if wait_ms is too large */ + unsigned next_sec_ms = 1000 - (tv_now.tv_usec / 1000); + + if (next_sec_ms && next_sec_ms < wait_ms) + wait_ms = next_sec_ms; + } + } + } + coap_persist_stop(ctx); + coap_free_context(ctx); + exit(0); + +} +---- + +SEE ALSO +-------- +*coap_block*(3), *coap_context*(3), *coap_handler*(3), *coap_observe*(3), +*coap_pdu_setup*(3), *coap_resource*(3) and *coap_session*(3) + +FURTHER INFORMATION +------------------- +See + +"https://rfc-editor.org/rfc/rfc7252[RFC7252: The Constrained Application Protocol (CoAP)]" + +"https://rfc-editor.org/rfc/rfc7641[RFC7641: Observing Resources in the Constrained Application Protocol (CoAP)]" + +"https://rfc-editor.org/rfc/rfc8613[RFC8613: Object Security for Constrained RESTful Environments (OSCORE)]" + +for further information. + + +BUGS +---- +Please report bugs on the mailing list for libcoap: +libcoap-developers@lists.sourceforge.net or raise an issue on GitHub at +https://github.com/obgm/libcoap/issues + +AUTHORS +------- +The libcoap project diff --git a/src/block.c b/src/block.c index ed16daffbb..4d2de6c709 100644 --- a/src/block.c +++ b/src/block.c @@ -428,7 +428,7 @@ coap_cancel_observe(coap_session_t *session, coap_binary_t *token, /* * Need to fix lg_xmit stateless token as using tokens from - observe setup + * observe setup */ if (pdu->lg_xmit) pdu->lg_xmit->b.b1.state_token = lg_crcv->state_token; @@ -1177,7 +1177,7 @@ track_fetch_observe(coap_pdu_t *pdu, coap_lg_crcv_t *lg_crcv, coap_opt_t *opt = coap_check_option(pdu, COAP_OPTION_OBSERVE, &opt_iter); - if (opt) { + if (opt && lg_crcv) { int observe_action = -1; coap_bin_const_t **tmp; diff --git a/src/coap_oscore.c b/src/coap_oscore.c index 2c916a2315..e8bcff55c6 100644 --- a/src/coap_oscore.c +++ b/src/coap_oscore.c @@ -2017,6 +2017,7 @@ coap_new_oscore_conf(coap_str_const_t conf_mem, oscore_conf->save_seq_num_func = save_seq_num_func; oscore_conf->save_seq_num_func_param = save_seq_num_func_param; oscore_conf->start_seq_num = start_seq_num; + coap_log_oscore("Start Seq no %" PRIu64 "\n", start_seq_num); return oscore_conf; } diff --git a/src/coap_session.c b/src/coap_session.c index 6e7b48b03b..a4ef0d239a 100644 --- a/src/coap_session.c +++ b/src/coap_session.c @@ -246,8 +246,6 @@ coap_session_mfree(coap_session_t *session) { } queue = queue->next; } - /* lg_crcv will be deleted when coap_cancel_observe() completes */ - continue; } } LL_DELETE(session->lg_crcv, lg_crcv); diff --git a/src/coap_subscribe.c b/src/coap_subscribe.c index 2795a80d67..c9056c8768 100644 --- a/src/coap_subscribe.c +++ b/src/coap_subscribe.c @@ -16,10 +16,1116 @@ #include "coap3/coap_internal.h" +#ifndef min +#define min(a,b) ((a) < (b) ? (a) : (b)) +#endif + #if COAP_SERVER_SUPPORT void coap_subscription_init(coap_subscription_t *s) { assert(s); memset(s, 0, sizeof(coap_subscription_t)); } + +void +coap_persist_track_funcs(coap_context_t *context, + coap_observe_added_t observe_added, + coap_observe_deleted_t observe_deleted, + coap_track_observe_value_t track_observe_value, + coap_dyn_resource_added_t dyn_resource_added, + coap_resource_deleted_t resource_deleted, + uint32_t save_freq, + void *user_data) { + context->observe_added = observe_added; + context->observe_deleted = observe_deleted; + context->observe_user_data = user_data; + context->observe_save_freq = save_freq ? save_freq : 1; + context->track_observe_value = track_observe_value; + context->dyn_resource_added = dyn_resource_added; + context->resource_deleted = resource_deleted; +} + +coap_subscription_t * +coap_persist_observe_add(coap_context_t *context, + coap_proto_t e_proto, + const coap_address_t *e_listen_addr, + const coap_addr_tuple_t *s_addr_info, + const coap_bin_const_t *raw_packet, + const coap_bin_const_t *oscore_info) { + coap_session_t *session = NULL; + const uint8_t *data; + size_t data_len; + coap_pdu_t *pdu = NULL; +#if COAP_CONSTRAINED_STACK + COAP_MUTEX_DEFINE(e_static_mutex); + static coap_packet_t e_packet; +#else /* ! COAP_CONSTRAINED_STACK */ + coap_packet_t e_packet; +#endif /* ! COAP_CONSTRAINED_STACK */ + coap_packet_t *packet = &e_packet; + coap_tick_t now; + coap_string_t *uri_path = NULL; + coap_opt_iterator_t opt_iter; + coap_opt_t *observe; + int observe_action; + coap_resource_t *r; + coap_subscription_t *s; + coap_endpoint_t *ep; + + if (e_listen_addr == NULL || s_addr_info == NULL || packet == NULL) + return NULL; + + /* Will be creating a local 'open' session */ + if (e_proto != COAP_PROTO_UDP) + return NULL; + + ep = context->endpoint; + while (ep) { + if (ep->proto == e_proto && + memcmp(e_listen_addr, &ep->bind_addr, sizeof(ep->bind_addr)) == 0) + break; + ep = ep->next; + } + +#if COAP_CONSTRAINED_STACK + coap_mutex_lock(&e_static_mutex); +#endif /* COAP_CONSTRAINED_STACK */ + + /* Build up packet */ + memcpy(&packet->addr_info, s_addr_info, sizeof(packet->addr_info)); + packet->ifindex = 0; + + data = raw_packet->s; + data_len = raw_packet->length; + if (data_len < 4) + goto malformed; + + /* Get the session */ + + coap_ticks(&now); + session = coap_endpoint_get_session(ep, packet, now); + if (session == NULL) + goto fail; + /* Need max space incase PDU is updated with updated token, huge size etc. */ + pdu = coap_pdu_init(0, 0, 0, 0); + if (!pdu) + goto fail; + + if (!coap_pdu_parse(session->proto, data, data_len, pdu)) { + goto malformed; + } + + if (pdu->code != COAP_REQUEST_CODE_GET && + pdu->code != COAP_REQUEST_CODE_FETCH) + goto malformed; + + observe = coap_check_option(pdu, COAP_OPTION_OBSERVE, &opt_iter); + if (observe == NULL) + goto malformed; + observe_action = coap_decode_var_bytes(coap_opt_value(observe), + coap_opt_length(observe)); + if (observe_action != COAP_OBSERVE_ESTABLISH) + goto malformed; + + /* Get the resource */ + + uri_path = coap_get_uri_path(pdu); + if (!uri_path) + goto malformed; + + r = coap_get_resource_from_uri_path(session->context, + (coap_str_const_t*)uri_path); + if (r == NULL) { + coap_log_warn("coap_persist_observe_add: resource '%s' not defined\n", + uri_path->s); + goto fail; + } + if (!r->observable) { + coap_log_warn("coap_persist_observe_add: resource '%s' not observable\n", + uri_path->s); + goto fail; + } + coap_delete_string(uri_path); + uri_path = NULL; + + /* Create / update subscription for observing */ + /* Now set up the subscription */ + s = coap_add_observer(r, session, &pdu->actual_token, pdu); + if (s == NULL) + goto fail; + +#if HAVE_OSCORE + if (oscore_info) { + coap_log_debug("persist: OSCORE association being updated\n"); + /* + * Need to track the association used for tracking this observe, done as + * a CBOR array. Written in coap_add_observer(). + * + * If an entry is null, then use nil, else a set of bytes + * + * Currently tracking 4 items + * recipient_id + * id_context + * aad (from oscore_association_t) + * partial_iv (from oscore_association_t) + */ + oscore_ctx_t *osc_ctx; + const uint8_t *info_buf = oscore_info->s; + size_t ret = 0; + coap_bin_const_t oscore_key_id; + coap_bin_const_t partial_iv; + coap_bin_const_t aad; + coap_bin_const_t id_context; + int have_aad = 0; + int have_partial_iv = 0; + int have_id_context = 0; + + ret = oscore_cbor_get_next_element(&info_buf); + if (ret != CBOR_ARRAY) + goto oscore_fail; + if (oscore_cbor_get_element_size(&info_buf) != 4) + goto oscore_fail; + + /* recipient_id */ + ret = oscore_cbor_get_next_element(&info_buf); + if (ret != CBOR_BYTE_STRING) + goto oscore_fail; + oscore_key_id.length = oscore_cbor_get_element_size(&info_buf); + oscore_key_id.s = info_buf; + info_buf += oscore_key_id.length; + + /* id_context */ + ret = oscore_cbor_get_next_element(&info_buf); + if (ret == CBOR_BYTE_STRING) { + id_context.length = oscore_cbor_get_element_size(&info_buf); + id_context.s = info_buf; + info_buf += id_context.length; + have_id_context = 1; + } else if (ret == CBOR_SIMPLE_VALUE && + oscore_cbor_get_element_size(&info_buf) == CBOR_NULL) { + } else + goto oscore_fail; + + /* aad */ + ret = oscore_cbor_get_next_element(&info_buf); + if (ret == CBOR_BYTE_STRING) { + aad.length = oscore_cbor_get_element_size(&info_buf); + aad.s = info_buf; + info_buf += aad.length; + have_aad = 1; + } else if (ret == CBOR_SIMPLE_VALUE && + oscore_cbor_get_element_size(&info_buf) == CBOR_NULL) { + } else + goto oscore_fail; + + /* partial_iv */ + ret = oscore_cbor_get_next_element(&info_buf); + if (ret == CBOR_BYTE_STRING) { + partial_iv.length = oscore_cbor_get_element_size(&info_buf); + partial_iv.s = info_buf; + info_buf += partial_iv.length; + have_partial_iv = 1; + } else if (ret == CBOR_SIMPLE_VALUE && + oscore_cbor_get_element_size(&info_buf) == CBOR_NULL) { + } else + goto oscore_fail; + + osc_ctx = oscore_find_context(session->context, oscore_key_id, + have_id_context ? &id_context : NULL, NULL, + &session->recipient_ctx); + if (osc_ctx) { + session->oscore_encryption = 1; + oscore_new_association(session, pdu, &pdu->actual_token, + session->recipient_ctx, + have_aad ? &aad : NULL, NULL, + have_partial_iv ? &partial_iv : NULL, + 1); + coap_log_debug("persist: OSCORE association added\n"); + oscore_log_hex_value(COAP_LOG_OSCORE, "partial_iv", + have_partial_iv ? &partial_iv : NULL); + } + } +oscore_fail: +#else /* ! HAVE_OSCORE */ + (void)oscore_info; +#endif /* ! HAVE_OSCORE */ + coap_delete_pdu(pdu); +#if COAP_CONSTRAINED_STACK + coap_mutex_unlock(&e_static_mutex); +#endif /* COAP_CONSTRAINED_STACK */ + return s; + +malformed: + coap_log_warn("coap_persist_observe_add: discard malformed PDU\n"); +fail: +#if COAP_CONSTRAINED_STACK + coap_mutex_unlock(&e_static_mutex); +#endif /* COAP_CONSTRAINED_STACK */ + coap_delete_string(uri_path); + coap_delete_pdu(pdu); + return NULL; +} + +#if COAP_WITH_OBSERVE_PERSIST +#include + +/* + * read in active observe entry. + */ +static int +coap_op_observe_read(FILE *fp, coap_subscription_t **observe_key, + coap_proto_t *e_proto, coap_address_t *e_listen_addr, + coap_addr_tuple_t *s_addr_info, + coap_bin_const_t **raw_packet, coap_bin_const_t **oscore_info) { + size_t size; + coap_binary_t *scratch; + + *raw_packet = NULL; + *oscore_info = NULL; + + if (fread(observe_key, sizeof(*observe_key), 1, fp) == 1) { + /* New record 'key proto listen addr_info len raw_packet len oscore' */ + if (fread(e_proto, sizeof(*e_proto), 1, fp) != 1) + goto fail; + if (fread(e_listen_addr, sizeof(*e_listen_addr), 1, fp) != 1) + goto fail; + if (fread(s_addr_info, sizeof(*s_addr_info), 1, fp) != 1) + goto fail; + if (fread(&size, sizeof(size), 1, fp) != 1) + goto fail; + scratch = coap_new_binary(size); + if ((scratch) == NULL) + goto fail; + if (fread(scratch->s, scratch->length, 1, fp) != 1) + goto fail; + *raw_packet = (coap_bin_const_t*)scratch; + if (fread(&size, sizeof(size), 1, fp) != 1) + goto fail; + if ((ssize_t)size == -1) + return 1; + else { + scratch = coap_new_binary(size); + if (scratch == NULL) + goto fail; + if (fread(scratch->s, scratch->length, 1, fp) != 1) + goto fail; + *oscore_info = (coap_bin_const_t*)scratch; + } + return 1; + } +fail: + return 0; +} + +/* + * write out active observe entry. + */ +static int +coap_op_observe_write(FILE *fp, coap_subscription_t *observe_key, + coap_proto_t e_proto, coap_address_t e_listen_addr, + coap_addr_tuple_t s_addr_info, + coap_bin_const_t *raw_packet, coap_bin_const_t *oscore_info) { + if (fwrite(&observe_key, sizeof(observe_key), 1, fp) != 1) + goto fail; + if (fwrite(&e_proto, sizeof(e_proto), 1, fp) != 1) + goto fail; + if (fwrite(&e_listen_addr, sizeof(e_listen_addr), + 1, fp) != 1) + goto fail; + if (fwrite(&s_addr_info, sizeof(s_addr_info), 1, fp) != 1) + goto fail; + if (fwrite(&raw_packet->length, sizeof(raw_packet->length), 1, fp) != 1) + goto fail; + if (fwrite(raw_packet->s, raw_packet->length, 1, fp) != 1) + goto fail; + if (oscore_info) { + if (fwrite(&oscore_info->length, sizeof(oscore_info->length), 1, fp) != 1) + goto fail; + if (fwrite(oscore_info->s, oscore_info->length, 1, fp) != 1) + goto fail; + } else { + ssize_t not_defined = -1; + + if (fwrite(¬_defined, sizeof(not_defined), 1, fp) != 1) + goto fail; + } + return 1; +fail: + return 0; +} + +/* + * This should be called before coap_persist_track_funcs() to prevent + * coap_op_observe_added() getting unnecessarily called. + * It should be called after init_resources() and coap_op_resource_load_disk() + * so that all the resources are in place. + */ +static void +coap_op_observe_load_disk(coap_context_t *ctx) { + FILE* fp_orig = fopen((const char*)ctx->observe_save_file->s, "r"); + FILE* fp_new = NULL; + coap_subscription_t *observe_key = NULL; + coap_proto_t e_proto; + coap_address_t e_listen_addr; + coap_addr_tuple_t s_addr_info; + coap_bin_const_t *raw_packet = NULL; + coap_bin_const_t *oscore_info = NULL; + char *new = NULL; + + if (fp_orig == NULL) + goto fail; + new = coap_malloc_type(COAP_STRING, ctx->observe_save_file->length + 5); + if (!new) + goto fail; + + strcpy(new, (const char *)ctx->observe_save_file->s); + strcat(new, ".tmp"); + fp_new = fopen(new, "w+"); + if (fp_new == NULL) + goto fail; + + /* Go through and load oscore entry, updating key on the way */ + while (1) { + if (!coap_op_observe_read(fp_orig, &observe_key, &e_proto, &e_listen_addr, + &s_addr_info, &raw_packet, &oscore_info)) + break; + coap_log_debug("persist: New session/observe being created\n"); + observe_key = coap_persist_observe_add(ctx, e_proto, + &e_listen_addr, + &s_addr_info, + raw_packet, + oscore_info); + if (observe_key) { + if (!coap_op_observe_write(fp_new, observe_key, e_proto, e_listen_addr, + s_addr_info, raw_packet, oscore_info)) + goto fail; + coap_delete_bin_const(raw_packet); + raw_packet = NULL; + coap_delete_bin_const(oscore_info); + oscore_info = NULL; + } + } + coap_delete_bin_const(raw_packet); + raw_packet = NULL; + coap_delete_bin_const(oscore_info); + oscore_info = NULL; + + if (fflush(fp_new) == EOF) + goto fail; + fclose(fp_new); + fclose(fp_orig); + /* Either old or new is in place */ + rename(new, (const char *)ctx->observe_save_file->s); + coap_free_type(COAP_STRING, new); + return; + +fail: + coap_delete_bin_const(raw_packet); + coap_delete_bin_const(oscore_info); + if (fp_new) + fclose(fp_new); + if (fp_orig) + fclose(fp_orig); + remove(new); + coap_free_type(COAP_STRING, new); + return; +} + +/* + * client has registered a new observe subscription request. + */ +static int +coap_op_observe_added(coap_session_t *session, + coap_subscription_t *a_observe_key, + coap_proto_t a_e_proto, coap_address_t *a_e_listen_addr, + coap_addr_tuple_t *a_s_addr_info, + coap_bin_const_t *a_raw_packet, + coap_bin_const_t *a_oscore_info, void *user_data) { + FILE* fp_orig = fopen((const char*)session->context->observe_save_file->s, + "r"); + FILE* fp_new = NULL; + coap_subscription_t *observe_key = NULL; + coap_proto_t e_proto; + coap_address_t e_listen_addr; + coap_addr_tuple_t s_addr_info; + coap_bin_const_t *raw_packet = NULL; + coap_bin_const_t *oscore_info = NULL; + char *new = NULL; + + (void)user_data; + + new = coap_malloc_type(COAP_STRING, + session->context->observe_save_file->length + 5); + if (!new) + goto fail; + + strcpy(new, (const char *)session->context->observe_save_file->s); + strcat(new, ".tmp"); + fp_new = fopen(new, "w+"); + if (fp_new == NULL) + goto fail; + + /* Go through and delete observe entry if it exists */ + while (fp_orig) { + if (!coap_op_observe_read(fp_orig, &observe_key, &e_proto, &e_listen_addr, + &s_addr_info, &raw_packet, &oscore_info)) + break; + if (observe_key != a_observe_key) { + if (!coap_op_observe_write(fp_new, observe_key, e_proto, e_listen_addr, + s_addr_info, raw_packet, oscore_info)) + goto fail; + } + coap_delete_bin_const(raw_packet); + raw_packet = NULL; + coap_delete_bin_const(oscore_info); + oscore_info = NULL; + } + coap_delete_bin_const(raw_packet); + raw_packet = NULL; + coap_delete_bin_const(oscore_info); + oscore_info = NULL; + + /* Add in new entry to the end */ + if (!coap_op_observe_write(fp_new, a_observe_key, a_e_proto, *a_e_listen_addr, + *a_s_addr_info, a_raw_packet, a_oscore_info)) + goto fail; + + if (fflush(fp_new) == EOF) + goto fail; + fclose(fp_new); + if (fp_orig) + fclose(fp_orig); + /* Either old or new is in place */ + rename(new, (const char *)session->context->observe_save_file->s); + coap_free_type(COAP_STRING, new); + return 1; + +fail: + coap_delete_bin_const(raw_packet); + coap_delete_bin_const(oscore_info); + if (fp_new) + fclose(fp_new); + if (fp_orig) + fclose(fp_orig); + remove(new); + coap_free_type(COAP_STRING, new); + return 0; +} + +/* + * client has de-registered a observe subscription request. + */ +static int +coap_op_observe_deleted(coap_session_t *session, + coap_subscription_t *d_observe_key, + void* user_data) { + FILE* fp_orig = fopen((const char*)session->context->observe_save_file->s, + "r"); + FILE* fp_new = NULL; + coap_subscription_t *observe_key = NULL; + coap_proto_t e_proto; + coap_address_t e_listen_addr; + coap_addr_tuple_t s_addr_info; + coap_bin_const_t *raw_packet = NULL; + coap_bin_const_t *oscore_info = NULL; + char *new = NULL; + + (void)user_data; + + if (fp_orig == NULL) + goto fail; + new = coap_malloc_type(COAP_STRING, + session->context->observe_save_file->length + 5); + if (!new) + goto fail; + + strcpy(new, (const char *)session->context->observe_save_file->s); + strcat(new, ".tmp"); + fp_new = fopen(new, "w+"); + if (fp_new == NULL) + goto fail; + + /* Go through and locate observe entry to delete and not copy it across */ + while (1) { + if (!coap_op_observe_read(fp_orig, &observe_key, &e_proto, &e_listen_addr, + &s_addr_info, &raw_packet, &oscore_info)) + break; + if (observe_key != d_observe_key) { + if (!coap_op_observe_write(fp_new, observe_key, e_proto, e_listen_addr, + s_addr_info, (coap_bin_const_t*)raw_packet, + (coap_bin_const_t*)oscore_info)) + goto fail; + } + coap_delete_bin_const(raw_packet); + raw_packet = NULL; + coap_delete_bin_const(oscore_info); + oscore_info = NULL; + } + coap_delete_bin_const(raw_packet); + raw_packet = NULL; + coap_delete_bin_const(oscore_info); + oscore_info = NULL; + + if (fflush(fp_new) == EOF) + goto fail; + fclose(fp_new); + fclose(fp_orig); + /* Either old or new is in place */ + rename(new, (const char *)session->context->observe_save_file->s); + coap_free_type(COAP_STRING, new); + return 1; + +fail: + coap_delete_bin_const(raw_packet); + coap_delete_bin_const(oscore_info); + if (fp_new) + fclose(fp_new); + if (fp_orig) + fclose(fp_orig); + remove(new); + coap_free_type(COAP_STRING, new); + return 0; +} + +/* + * This should be called before coap_persist_track_funcs() to prevent + * coap_op_obs_cnt_track_observe() getting unnecessarily called. + * Should be called after coap_op_dyn_resource_load_disk() to make sure that + * all the resources are in the right place. + */ +static void +coap_op_obs_cnt_load_disk(coap_context_t *context) { + FILE* fp = fopen((const char *)context->obs_cnt_save_file->s, "r"); + char buf[1500]; + + if (fp == NULL) + return; + + while (fgets(buf, sizeof(buf), fp) != NULL) { + char *cp = strchr(buf, ' '); + coap_str_const_t resource_key; + uint32_t observe_num; + coap_resource_t *r; + + if (!cp) + break; + + *cp = '\000'; + cp++; + observe_num = atoi(cp); + /* + * Need to assume 0 .. (context->observe_save_freq-1) have in addition + * been sent so need to round up to latest possible send value + */ + observe_num = ((observe_num + context->observe_save_freq) / + context->observe_save_freq) * + context->observe_save_freq - 1; + resource_key.s = (uint8_t *)buf; + resource_key.length = strlen(buf); + r = coap_get_resource_from_uri_path(context, &resource_key); + if (r) { + coap_log_debug("persist: Initial observe number being updated\n"); + coap_persist_set_observe_num(r, observe_num); + } + } + fclose(fp); +} + +/* + * Called when the observe value of a resource has been changed, but limited + * to be called every context->context->observe_save_freq to reduce update + * overheads. + */ +static int +coap_op_obs_cnt_track_observe(coap_context_t *context, + coap_str_const_t *resource_name, + uint32_t n_observe_num, + void *user_data) { + FILE* fp_orig = fopen((const char*)context->obs_cnt_save_file->s, "r"); + FILE* fp_new = NULL; + char buf[1500]; + char *new = NULL; + + (void)user_data; + + new = coap_malloc_type(COAP_STRING, context->obs_cnt_save_file->length + 5); + if (!new) + goto fail; + + strcpy(new, (const char *)context->obs_cnt_save_file->s); + strcat(new, ".tmp"); + fp_new = fopen(new, "w+"); + if (fp_new == NULL) + goto fail; + + /* Go through and locate resource entry to update */ + while (fp_orig && fgets(buf, sizeof(buf), fp_orig) != NULL) { + char *cp = strchr(buf, ' '); + uint32_t observe_num; + coap_bin_const_t resource_key; + + if (!cp) + break; + + *cp = '\000'; + cp++; + observe_num = atoi(cp); + resource_key.s = (uint8_t *)buf; + resource_key.length = strlen(buf); + if (!coap_binary_equal(resource_name, &resource_key)) { + if (fprintf(fp_new, "%s %u\n", resource_key.s, observe_num) < 0) + goto fail; + } + } + if (fprintf(fp_new, "%s %u\n", resource_name->s, n_observe_num) < 0) + goto fail; + if (fflush(fp_new) == EOF) + goto fail; + fclose(fp_new); + if (fp_orig) + fclose(fp_orig); + /* Either old or new is in place */ + rename(new, (const char *)context->obs_cnt_save_file->s); + coap_free_type(COAP_STRING, new); + return 1; + +fail: + if (fp_new) + fclose(fp_new); + if (fp_orig) + fclose(fp_orig); + remove(new); + coap_free_type(COAP_STRING, new); + return 0; +} + +/* + * Called when a resource has been deleted. + */ +static int +coap_op_obs_cnt_deleted(coap_context_t *context, + coap_str_const_t *resource_name) { + FILE* fp_orig = fopen((const char*)context->obs_cnt_save_file->s, "r"); + FILE* fp_new = NULL; + char buf[1500]; + char *new = NULL; + + if (fp_orig == NULL) + goto fail; + new = coap_malloc_type(COAP_STRING, context->obs_cnt_save_file->length + 5); + if (!new) + goto fail; + + strcpy(new, (const char *)context->obs_cnt_save_file->s); + strcat(new, ".tmp"); + fp_new = fopen(new, "w+"); + if (fp_new == NULL) + goto fail; + + /* Go through and locate resource entry to delete */ + while (fgets(buf, sizeof(buf), fp_orig) != NULL) { + char *cp = strchr(buf, ' '); + uint32_t observe_num; + coap_bin_const_t resource_key; + + if (!cp) + break; + + *cp = '\000'; + cp++; + observe_num = atoi(cp); + resource_key.s = (uint8_t *)buf; + resource_key.length = strlen(buf); + if (!coap_binary_equal(resource_name, &resource_key)) { + if (fprintf(fp_new, "%s %u\n", resource_key.s, observe_num) < 0) + goto fail; + } + } + if (fflush(fp_new) == EOF) + goto fail; + fclose(fp_new); + fclose(fp_orig); + /* Either old or new is in place */ + rename(new, (const char *)context->obs_cnt_save_file->s); + coap_free_type(COAP_STRING, new); + return 1; + +fail: + if (fp_new) + fclose(fp_new); + if (fp_orig) + fclose(fp_orig); + remove(new); + coap_free_type(COAP_STRING, new); + return 0; +} + +/* + * read in dynamic resource entry, allocating name & raw_packet + * which need to be freed off by caller. + */ +static int +coap_op_dyn_resource_read(FILE *fp, coap_proto_t *e_proto, + coap_string_t **name, + coap_binary_t **raw_packet) { + size_t size; + + *name = NULL; + *raw_packet = NULL; + + if (fread(e_proto, sizeof(*e_proto), 1, fp) == 1) { + /* New record 'proto len resource_name len raw_packet' */ + if (fread(&size, sizeof(size), 1, fp) != 1) + goto fail; + *name = coap_new_string(size); + if (!(*name)) + goto fail; + if (fread((*name)->s, size, 1, fp) != 1) + goto fail; + if (fread(&size, sizeof(size), 1, fp) != 1) + goto fail; + *raw_packet = coap_new_binary(size); + if (!(*raw_packet)) + goto fail; + if (fread((*raw_packet)->s, size, 1, fp) != 1) + goto fail; + return 1; + } +fail: + return 0; +} + +/* + * write out dynamic resource entry. + */ +static int +coap_op_dyn_resource_write(FILE *fp, coap_proto_t e_proto, + coap_str_const_t *name, + coap_bin_const_t *raw_packet) { + if (fwrite(&e_proto, sizeof(e_proto), 1, fp) != 1) + goto fail; + if (fwrite(&name->length, sizeof(name->length), 1, fp) != 1) + goto fail; + if (fwrite(name->s, name->length, 1, fp) != 1) + goto fail; + if (fwrite(&raw_packet->length, sizeof(raw_packet->length), 1, fp) != 1) + goto fail; + if (fwrite(raw_packet->s, raw_packet->length, 1, fp) != 1) + goto fail; + return 1; +fail: + return 0; +} + +/* + * This should be called before coap_persist_track_funcs() to prevent + * coap_op_dyn_resource_added() getting unnecessarily called. + * + * Each record 'proto len resource_name len raw_packet' + */ +static void +coap_op_dyn_resource_load_disk(coap_context_t *ctx) { + FILE* fp_orig = NULL; + coap_proto_t e_proto; + coap_string_t *name = NULL; + coap_binary_t *raw_packet = NULL; + coap_resource_t *r; + coap_session_t *session = NULL; + coap_pdu_t *request = NULL; + coap_pdu_t *response = NULL; + coap_string_t *query = NULL; + + if (!ctx->unknown_resource) + return; + + fp_orig = fopen((const char*)ctx->dyn_resource_save_file->s, "r"); + if (fp_orig == NULL) + return; + session = (coap_session_t*)coap_malloc_type(COAP_SESSION, + sizeof(coap_session_t)); + if (!session) + goto fail; + memset (session, 0, sizeof(coap_session_t)); + session->context = ctx; + + /* Go through and create each dynamic resource if it does not exist*/ + while (1) { + if (!coap_op_dyn_resource_read(fp_orig, &e_proto, &name, &raw_packet)) + break; + r = coap_get_resource_from_uri_path(ctx, (coap_str_const_t*)name); + if (!r) { + /* Create the new resource using the application logic */ + + coap_log_debug("persist: dynamic resource being re-created\n"); + /* + * Need max space incase PDU is updated with updated token, + * huge size etc. + * */ + request = coap_pdu_init(0, 0, 0, 0); + if (!request) + goto fail; + + session->proto = e_proto; + if (!coap_pdu_parse(session->proto, raw_packet->s, + raw_packet->length, request)) { + goto fail; + } + if (!ctx->unknown_resource->handler[request->code-1]) + goto fail; + response = coap_pdu_init(0, 0, 0, 0); + if (!response) + goto fail; + query = coap_get_query(request); + /* Call the application handler to set up this dynamic resource */ + ctx->unknown_resource->handler[request->code-1](ctx->unknown_resource, + session, request, + query, response); + coap_delete_string(query); + query = NULL; + coap_delete_pdu(request); + request = NULL; + coap_delete_pdu(response); + response = NULL; + } + coap_delete_string(name); + coap_delete_binary(raw_packet); + } +fail: + coap_delete_string(name); + coap_delete_binary(raw_packet); + coap_delete_string(query); + coap_delete_pdu(request); + coap_delete_pdu(response); + fclose(fp_orig); + coap_free_type(COAP_SESSION, session); +} + +/* + * Server has set up a new dynamic resource agains a request for an unknown + * resource. + */ +static int +coap_op_dyn_resource_added(coap_session_t *session, + coap_str_const_t *resource_name, + coap_bin_const_t *packet, + void *user_data) { + FILE *fp_orig; + FILE* fp_new = NULL; + char *new = NULL; + coap_context_t *context = session->context; + coap_string_t *name = NULL; + coap_binary_t *raw_packet = NULL; + coap_proto_t e_proto; + + (void)user_data; + + fp_orig = fopen((const char *)context->dyn_resource_save_file->s, "a"); + if (fp_orig == NULL) + return 0; + + new = coap_malloc_type(COAP_STRING, + context->dyn_resource_save_file->length + 5); + if (!new) + goto fail; + + strcpy(new, (const char *)context->dyn_resource_save_file->s); + strcat(new, ".tmp"); + fp_new = fopen(new, "w+"); + if (fp_new == NULL) + goto fail; + + /* Go through and locate duplicate resource to delete */ + while (1) { + if (!coap_op_dyn_resource_read(fp_orig, &e_proto, &name, &raw_packet)) + break; + if (!coap_string_equal(resource_name, name)) { + /* Copy across non-matching entry */ + if (!coap_op_dyn_resource_write(fp_new, e_proto, (coap_str_const_t*)name, + (coap_bin_const_t*)raw_packet)) + break; + } + coap_delete_string(name); + name = NULL; + coap_delete_binary(raw_packet); + raw_packet = NULL; + } + coap_delete_string(name); + coap_delete_binary(raw_packet); + /* Add new entry to the end */ + if (!coap_op_dyn_resource_write(fp_new, session->proto, + resource_name, packet)) + goto fail; + + if (fflush(fp_new) == EOF) + goto fail; + fclose(fp_new); + fclose(fp_orig); + /* Either old or new is in place */ + rename(new, (const char *)context->dyn_resource_save_file->s); + coap_free_type(COAP_STRING, new); + return 1; + +fail: + if (fp_new) + fclose(fp_new); + if (fp_orig) + fclose(fp_orig); + remove(new); + coap_free_type(COAP_STRING, new); + return 0; +} + +/* + * Server has deleted a resource + */ +static int +coap_op_resource_deleted(coap_context_t *context, + coap_str_const_t *resource_name, + void* user_data) { + FILE* fp_orig = NULL; + FILE* fp_new = NULL; + char *new = NULL; + coap_proto_t e_proto; + coap_string_t *name = NULL; + coap_binary_t *raw_packet = NULL; + (void)user_data; + + coap_op_obs_cnt_deleted(context, resource_name); + + fp_orig = fopen((const char *)context->dyn_resource_save_file->s, "r"); + if (fp_orig == NULL) + return 1; + + new = coap_malloc_type(COAP_STRING, + context->dyn_resource_save_file->length + 5); + if (!new) + goto fail; + + strcpy(new, (const char *)context->dyn_resource_save_file->s); + strcat(new, ".tmp"); + fp_new = fopen(new, "w+"); + if (fp_new == NULL) + goto fail; + + /* Go through and locate resource to delete and not copy it across */ + while (1) { + if (!coap_op_dyn_resource_read(fp_orig, &e_proto, &name, &raw_packet)) + break; + if (!coap_string_equal(resource_name, name)) { + /* Copy across non-matching entry */ + if (!coap_op_dyn_resource_write(fp_new, e_proto, (coap_str_const_t*)name, + (coap_bin_const_t*)raw_packet)) + break; + } + coap_delete_string(name); + name = NULL; + coap_delete_binary(raw_packet); + raw_packet = NULL; + } + coap_delete_string(name); + coap_delete_binary(raw_packet); + + if (fflush(fp_new) == EOF) + goto fail; + fclose(fp_new); + fclose(fp_orig); + /* Either old or new is in place */ + rename(new, (const char *)context->dyn_resource_save_file->s); + coap_free_type(COAP_STRING, new); + return 1; + +fail: + if (fp_new) + fclose(fp_new); + if (fp_orig) + fclose(fp_orig); + remove(new); + coap_free_type(COAP_STRING, new); + return 0; +} + +int +coap_persist_startup(coap_context_t *context, + const char *dyn_resource_save_file, + const char *observe_save_file, + const char *obs_cnt_save_file, + uint32_t save_freq) { + if (dyn_resource_save_file) { + context->dyn_resource_save_file = + coap_new_bin_const((const uint8_t *)dyn_resource_save_file, + strlen(dyn_resource_save_file)); + if (!context->dyn_resource_save_file) + return 0; + coap_op_dyn_resource_load_disk(context); + context->dyn_resource_added = coap_op_dyn_resource_added; + context->resource_deleted = coap_op_resource_deleted; + } + if (obs_cnt_save_file) { + context->obs_cnt_save_file = + coap_new_bin_const((const uint8_t *)obs_cnt_save_file, + strlen(obs_cnt_save_file)); + if (!context->obs_cnt_save_file) + return 0; + context->observe_save_freq = save_freq ? save_freq : 1; + coap_op_obs_cnt_load_disk(context); + context->track_observe_value = coap_op_obs_cnt_track_observe; + context->resource_deleted = coap_op_resource_deleted; + } + if (observe_save_file) { + context->observe_save_file = + coap_new_bin_const((const uint8_t *)observe_save_file, + strlen(observe_save_file)); + if (!context->observe_save_file) + return 0; + coap_op_observe_load_disk(context); + context->observe_added = coap_op_observe_added; + context->observe_deleted = coap_op_observe_deleted; + } + return 1; +} + +void +coap_persist_cleanup(coap_context_t *context) { + coap_delete_bin_const(context->dyn_resource_save_file); + coap_delete_bin_const(context->obs_cnt_save_file); + coap_delete_bin_const(context->observe_save_file); + context->dyn_resource_save_file = NULL; + context->obs_cnt_save_file = NULL; + context->observe_save_file = NULL; + + /* Close down any tracking */ + coap_persist_track_funcs(context, NULL, NULL, NULL, NULL, + NULL, 0, NULL); +} + +void +coap_persist_stop(coap_context_t *context) { + context->observe_no_clear = 1; + coap_persist_cleanup(context); +} +#else /* ! COAP_WITH_OBSERVE_PERSIST */ +int +coap_persist_startup(coap_context_t *context, + const char *dyn_resource_save_file, + const char *observe_save_file, + const char *obs_cnt_save_file, + uint32_t save_freq) { + (void)context; + (void)dyn_resource_save_file; + (void)observe_save_file; + (void)obs_cnt_save_file; + (void)save_freq; + return 0; +} + +void +coap_persist_stop(coap_context_t *context) { + context->observe_no_clear = 1; + /* Close down any tracking */ + coap_persist_track_funcs(context, NULL, NULL, NULL, NULL, + NULL, 0, NULL); +} + +#endif /* ! COAP_WITH_OBSERVE_PERSIST */ + #endif /* COAP_SERVER_SUPPORT */ diff --git a/src/net.c b/src/net.c index 96e0d035c1..adb18d5c45 100644 --- a/src/net.c +++ b/src/net.c @@ -560,7 +560,7 @@ coap_free_context(coap_context_t *context) { return; #if COAP_SERVER_SUPPORT - /* Removing a resource may cause a CON observe to be sent */ + /* Removing a resource may cause a NON unsolicited observe to be sent */ coap_delete_all_resources(context); #endif /* COAP_SERVER_SUPPORT */ @@ -630,6 +630,11 @@ coap_free_context(coap_context_t *context) { context->epfd = -1; } #endif /* COAP_EPOLL_SUPPORT */ +#if COAP_SERVER_SUPPORT +#if COAP_WITH_OBSERVE_PERSIST + coap_persist_cleanup(context); +#endif /* COAP_WITH_OBSERVE_PERSIST */ +#endif /* COAP_SERVER_SUPPORT */ coap_free_type(COAP_CONTEXT, context); #ifdef WITH_LWIP @@ -2940,6 +2945,14 @@ handle_request(coap_context_t *context, coap_session_t *session, coap_pdu_t *pdu } session->last_con_mid = pdu->mid; } +#if COAP_WITH_OBSERVE_PERSIST + /* If we are maintaining Observe persist */ + if (resource == context->unknown_resource) { + context->unknown_pdu = pdu; + context->unknown_session = session; + } else + context->unknown_pdu = NULL; +#endif /* COAP_WITH_OBSERVE_PERSIST */ /* * Call the request handler with everything set up diff --git a/src/oscore/oscore_context.c b/src/oscore/oscore_context.c index d02b633120..8b7460eec2 100644 --- a/src/oscore/oscore_context.c +++ b/src/oscore/oscore_context.c @@ -181,9 +181,9 @@ oscore_remove_context(coap_context_t *c_context, oscore_ctx_t *osc_ctx) { * Updates recipient_ctx. */ oscore_ctx_t * -oscore_find_context(coap_context_t *c_context, - coap_bin_const_t rcpkey_id, - coap_bin_const_t *ctxkey_id, +oscore_find_context(const coap_context_t *c_context, + const coap_bin_const_t rcpkey_id, + const coap_bin_const_t *ctxkey_id, uint8_t *oscore_r2, oscore_recipient_ctx_t **recipient_ctx) { oscore_ctx_t *pt = c_context->p_osc_ctx; diff --git a/src/pdu.c b/src/pdu.c index 6741ce8336..f6d81202ca 100644 --- a/src/pdu.c +++ b/src/pdu.c @@ -189,7 +189,7 @@ coap_pdu_duplicate(const coap_pdu_t *old_pdu, session->doing_first = 0; pdu = coap_pdu_init(old_pdu->type, old_pdu->code, coap_new_message_id(session), - coap_session_max_pdu_size(session)); + 0); /* Restore any pending waits */ session->doing_first = doing_first; if (pdu == NULL) diff --git a/src/resource.c b/src/resource.c index bae5e2566f..9de2dcd7e2 100644 --- a/src/resource.c +++ b/src/resource.c @@ -469,8 +469,14 @@ coap_free_resource(coap_resource_t *resource) { assert(resource); - coap_resource_notify_observers(resource, NULL); - coap_notify_observers(resource->context, resource, COAP_DELETING_RESOURCE); + if (!resource->context->observe_no_clear) { + coap_resource_notify_observers(resource, NULL); + coap_notify_observers(resource->context, resource, COAP_DELETING_RESOURCE); + } + + if (resource->context->resource_deleted) + resource->context->resource_deleted(resource->context, resource->uri_path, + resource->context->observe_user_data); if (resource->context->release_userdata && resource->user_data) resource->context->release_userdata(resource->user_data); @@ -483,7 +489,10 @@ coap_free_resource(coap_resource_t *resource) { /* free all elements from resource->subscribers */ LL_FOREACH_SAFE( resource->subscribers, obs, otmp ) { - coap_session_release( obs->session ); + if (resource->context->observe_deleted) + resource->context->observe_deleted(obs->session, obs, + resource->context->observe_user_data); + coap_session_release(obs->session); coap_delete_pdu(obs->pdu); coap_delete_cache_key(obs->cache_key); coap_free_type(COAP_SUBSCRIPTION, obs); @@ -524,6 +533,19 @@ coap_add_resource(coap_context_t *context, coap_resource_t *resource) { coap_delete_resource(context, r); } RESOURCES_ADD(context->resources, resource); +#if COAP_WITH_OBSERVE_PERSIST + if (context->unknown_pdu && context->dyn_resource_save_file && + context->dyn_resource_added && resource->observable) { + coap_bin_const_t raw_packet; + + raw_packet.s = context->unknown_pdu->token - + context->unknown_pdu->hdr_size; + raw_packet.length = context->unknown_pdu->used_size + + context->unknown_pdu->hdr_size; + context->dyn_resource_added(context->unknown_session, resource->uri_path, + &raw_packet, context->observe_user_data); + } +#endif /* COAP_WITH_OBSERVE_PERSIST */ } assert(resource->context == NULL); resource->context = context; @@ -796,6 +818,93 @@ static const uint16_t cache_ignore_options[] = { COAP_OPTION_ETAG, (void*)s, s->cache_key->key[0], s->cache_key->key[1], s->cache_key->key[2], s->cache_key->key[3]); + if (session->context->observe_added && session->proto == COAP_PROTO_UDP) { + coap_bin_const_t raw_packet; + coap_bin_const_t *oscore_info = NULL; +#if HAVE_OSCORE + oscore_association_t *association; + + if (session->recipient_ctx && session->recipient_ctx->recipient_id) { + /* + * Need to track the association used for tracking this observe, done as + * a CBOR array. Read in coap_persist_observe_add(). + * + * If an entry is null, then use nil, else a set of bytes + * + * Currently tracking 4 items + * recipient_id + * id_context + * aad (from oscore_association_t) + * partial_iv (from oscore_association_t) + */ + uint8_t info_buffer[60]; + uint8_t *info_buf = info_buffer; + size_t info_len = sizeof(info_buffer); + size_t ret = 0; + coap_bin_const_t ctoken = { token->length, token->s }; + + ret += oscore_cbor_put_array(&info_buf, &info_len, 4); + ret += oscore_cbor_put_bytes(&info_buf, + &info_len, + session->recipient_ctx->recipient_id->s, + session->recipient_ctx->recipient_id->length); + if (session->recipient_ctx->osc_ctx && + session->recipient_ctx->osc_ctx->id_context) { + ret += oscore_cbor_put_bytes(&info_buf, + &info_len, + session->recipient_ctx->osc_ctx->id_context->s, + session->recipient_ctx->osc_ctx->id_context->length); + } else { + ret += oscore_cbor_put_nil(&info_buf, &info_len); + } + association = oscore_find_association(session, &ctoken); + if (association) { + if (association->aad) { + ret += oscore_cbor_put_bytes(&info_buf, + &info_len, + association->aad->s, + association->aad->length); + } else { + ret += oscore_cbor_put_nil(&info_buf, &info_len); + } + if (association->partial_iv) { + ret += oscore_cbor_put_bytes(&info_buf, + &info_len, + association->partial_iv->s, + association->partial_iv->length); + } else { + ret += oscore_cbor_put_nil(&info_buf, &info_len); + } + } else { + ret += oscore_cbor_put_nil(&info_buf, &info_len); + ret += oscore_cbor_put_nil(&info_buf, &info_len); + } + oscore_info = coap_new_bin_const(info_buffer, ret); + } +#endif /* HAVE_OSCORE */ + + /* s->pdu header is not currently encoded */ + memcpy(s->pdu->token - request->hdr_size, + request->token - request->hdr_size, request->hdr_size); + raw_packet.s = s->pdu->token - request->hdr_size; + raw_packet.length = s->pdu->used_size + request->hdr_size; + session->context->observe_added(session, s, session->proto, + &session->endpoint->bind_addr, + &session->addr_info, + &raw_packet, + oscore_info, + session->context->observe_user_data); +#if HAVE_OSCORE + coap_delete_bin_const(oscore_info); +#endif /* HAVE_OSCORE */ + } + if (resource->context->track_observe_value) { + /* Track last used observe value (as app handler is called) */ + resource->context->track_observe_value(resource->context,resource->uri_path, + resource->observe, + resource->context->observe_user_data); + } + return s; } @@ -834,6 +943,9 @@ coap_delete_observer(coap_resource_t *resource, coap_session_t *session, (void*)s, outbuf, s->cache_key->key[0], s->cache_key->key[1], s->cache_key->key[2], s-> cache_key->key[3]); } + if (s && session->context->observe_deleted) + session->context->observe_deleted(session, s, + session->context->observe_user_data); if (resource->subscribers && s) { LL_DELETE(resource->subscribers, s); @@ -852,6 +964,8 @@ coap_delete_observers(coap_context_t *context, coap_session_t *session) { coap_subscription_t *s, *tmp; LL_FOREACH_SAFE(resource->subscribers, s, tmp) { if (s->session == session) { + if (context->observe_deleted) + context->observe_deleted(session, s, context->observe_user_data); LL_DELETE(resource->subscribers, s); coap_session_release(session); coap_delete_pdu(s->pdu); @@ -1039,6 +1153,15 @@ coap_resource_notify_observers(coap_resource_t *r, r->observe = (r->observe + 1) & 0xFFFFFF; assert(r->context); + + if (r->context->track_observe_value) { + /* Track last used observe value */ + if ((r->observe % r->context->observe_save_freq) == 0) + r->context->track_observe_value(r->context, r->uri_path, + r->observe, + r->context->observe_user_data); + } + r->context->observe_pending = 1; #ifdef COAP_EPOLL_SUPPORT coap_update_epoll_timer(r->context, 0); @@ -1092,6 +1215,15 @@ coap_check_notify(coap_context_t *context) { } } +void +coap_persist_set_observe_num(coap_resource_t *resource, + uint32_t start_observe_no) { + if (!resource) + return; + + resource->observe = start_observe_no & 0xffffff; +} + /** * Checks the failure counter for (peer, token) and removes peer from * the list of observers for the given resource when COAP_OBS_MAX_FAIL