diff --git a/CMakeLists.txt b/CMakeLists.txt index adf637cc22..93b24eb89e 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" @@ -238,6 +242,13 @@ else() message(STATUS "compiling without WebSockets 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 5c1d4ce68e..c7fbbaef1a 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 88f68f3bb0..76017f6c6c 100644 --- a/configure.ac +++ b/configure.ac @@ -787,6 +787,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], @@ -1043,6 +1054,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 @@ -1120,6 +1132,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 969233d9f5..eb1f4774d8 100644 --- a/examples/coap-client.c +++ b/examples/coap-client.c @@ -235,12 +235,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) { @@ -445,7 +445,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; } @@ -462,11 +462,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 166b0452ba..8f4955a6c1 100644 --- a/examples/coap-server.c +++ b/examples/coap-server.c @@ -77,6 +77,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; @@ -84,6 +87,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 @@ -184,6 +188,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. @@ -1446,6 +1463,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); @@ -1538,6 +1556,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); @@ -2159,6 +2178,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-w [port][,secure_port]\n" @@ -2728,7 +2750,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:w: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:w:A:C:E:L:M:NP:R:S:T:U:V:X:")) != -1) { switch (opt) { case 'A' : strncpy(addr_str, optarg, NI_MAXHOST-1); @@ -2846,6 +2868,9 @@ main(int argc, char **argv) { goto failed; } break; + case 't': + track_observes = 1; + break; case 'u': #if SERVER_CAN_PROXY user_length = cmdline_read_user(optarg, &user, MAX_USER); @@ -2891,6 +2916,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); @@ -2924,6 +2951,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 */ @@ -3014,6 +3055,9 @@ main(int argc, char **argv) { finish: /* Clean up local usage */ + if (keep_persist) + coap_persist_stop(ctx); + coap_free(ca_mem); coap_free(cert_mem); coap_free(key_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..32583a7f7c 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 that 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 that 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 2200344ef5..86d07e648d 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 c5f513eef9..4342eff933 100644 --- a/libcoap-3.map +++ b/libcoap-3.map @@ -172,6 +172,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 e3da0c4296..50666bb6a0 100644 --- a/libcoap-3.sym +++ b/libcoap-3.sym @@ -170,6 +170,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 570a04f740..56dd3abc63 100644 --- a/man/coap-server.txt.in +++ b/man/coap-server.txt.in @@ -19,10 +19,11 @@ coap-server-notls SYNOPSIS -------- -*coap-server* [*-d* max] [*-e*] [*-g* group] [*-l* loss] [*-p* port] [-r] - [*-v* num] [*-w* [port][,secure_port]] [*-A* address] - [*-E* oscore_conf_file[,seq_file]] [*-G* group_if] [*-L* value] - [*-N*] [*-P* scheme://addr[:port],[name1[,name2..]]] +*coap-server* [*-d* max] [*-e*] [*-g* group] [*-l* loss] [*-p* port] [*-r*] + [*-t*] [*-v* num] [*-w* [port][,secure_port]] + [*-A* address] [*-E* oscore_conf_file[,seq_file]] + [*-G* group_if] [*-L* value] [*-N*] + [*-P* scheme://addr[:port],[name1[,name2..]]] [*-T* max_token_size] [*-U* type] [*-V* num] [*-X* size] [[*-h* hint] [*-i* match_identity_file] [*-k* key] [*-s* match_psk_sni_file] [*-u* user]] @@ -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 6efe643518..5828e86f39 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 83efd37b7f..e64d4763b5 100644 --- a/src/coap_oscore.c +++ b/src/coap_oscore.c @@ -2025,6 +2025,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_subscribe.c b/src/coap_subscribe.c index 2795a80d67..ceb6ac159b 100644 --- a/src/coap_subscribe.c +++ b/src/coap_subscribe.c @@ -16,10 +16,1123 @@ #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 info_buf_len = oscore_info->length; + 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, &info_buf_len); + if (ret != CBOR_ARRAY) + goto oscore_fail; + if (oscore_cbor_get_element_size(&info_buf, &info_buf_len) != 4) + goto oscore_fail; + + /* recipient_id */ + ret = oscore_cbor_get_next_element(&info_buf, &info_buf_len); + if (ret != CBOR_BYTE_STRING) + goto oscore_fail; + oscore_key_id.length = oscore_cbor_get_element_size(&info_buf, + &info_buf_len); + oscore_key_id.s = info_buf; + info_buf += oscore_key_id.length; + + /* id_context */ + ret = oscore_cbor_get_next_element(&info_buf, &info_buf_len); + if (ret == CBOR_BYTE_STRING) { + id_context.length = oscore_cbor_get_element_size(&info_buf, + &info_buf_len); + 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, + &info_buf_len) == CBOR_NULL) { + } else + goto oscore_fail; + + /* aad */ + ret = oscore_cbor_get_next_element(&info_buf, &info_buf_len); + if (ret == CBOR_BYTE_STRING) { + aad.length = oscore_cbor_get_element_size(&info_buf, &info_buf_len); + aad.s = info_buf; + info_buf += aad.length; + have_aad = 1; + } else if (ret == CBOR_SIMPLE_VALUE && + oscore_cbor_get_element_size(&info_buf, + &info_buf_len) == CBOR_NULL) { + } else + goto oscore_fail; + + /* partial_iv */ + ret = oscore_cbor_get_next_element(&info_buf, &info_buf_len); + if (ret == CBOR_BYTE_STRING) { + partial_iv.length = oscore_cbor_get_element_size(&info_buf, + &info_buf_len); + 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, + &info_buf_len) == 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 9f6b03bcde..94a477dcac 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 @@ -2981,6 +2986,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 03c28bb5ef..7f2d8e70a5 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 b43e7a7ef4..69f42d0c10 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