diff --git a/CHANGELOG.md b/CHANGELOG.md
index 83e8b130..58434034 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,33 @@
# Changelog
+## 3.8.0 (May 28th, 2024)
+
+### BREAKING CHANGES
+
+- Timeout in case of sending a Confirmable Notification does not cancel the
+ observation anymore by default.
+
+### Features
+
+- Added a ``connection_error_is_registration_failure`` configuration option that
+ allows handling connection errors as Register failures, including the
+ automatic retry mechanism
+- Added experimental server connection status API.
+
+### Improvements
+
+- (commercial version only) Changed MSISDN matching method in SMS binding
+ feature to allow handling messages with Short Codes as originating number
+
+### Bugfixes
+
+- Fixed a corner case in which a connection error during a non-first Register
+ attempt could cause uncontrolled infinite retries
+- Fixed a bug in demo of Advanced Firmware Update module that prevented
+ proper handling of security config for targets other than APP
+- Fixed a bug that caused anjay_next_planned_notify_trigger family APIs to
+ return an invalid value after canceling observations
+
## 3.7.0 (February 16th, 2024)
### Features
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9b66190f..5f8c17e4 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -8,7 +8,7 @@
cmake_minimum_required(VERSION 3.6.0)
project(anjay C)
-set(ANJAY_VERSION "3.7.0" CACHE STRING "Anjay library version")
+set(ANJAY_VERSION "3.8.0" CACHE STRING "Anjay library version")
set(ANJAY_BINARY_VERSION 1.0.0)
set(ANJAY_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
@@ -210,6 +210,7 @@ else()
endif()
option(WITH_AVS_COAP_TCP "Enable CoAP over TCP support" "${WITH_LWM2M11}")
+option(WITH_CONN_STATUS_API "Enable support for the experimental anjay_get_server_connection_status() API and related callback." ON)
@@ -557,6 +558,7 @@ set(ANJAY_WITH_SECURITY_STRUCTURED "${WITH_SECURITY_STRUCTURED}")
set(ANJAY_WITH_SEND "${WITH_SEND}")
set(ANJAY_WITH_SENML_JSON "${WITH_SENML_JSON}")
set(ANJAY_WITHOUT_QUEUE_MODE_AUTOCLOSE "${WITHOUT_QUEUE_MODE_AUTOCLOSE}")
+set(ANJAY_WITH_CONN_STATUS_API "${WITH_CONN_STATUS_API}")
configure_file(include_public/anjay/anjay_config.h.in
include_public/anjay/anjay_config.h)
diff --git a/demo/advanced_firmware_update.c b/demo/advanced_firmware_update.c
index 714754e8..f201862e 100644
--- a/demo/advanced_firmware_update.c
+++ b/demo/advanced_firmware_update.c
@@ -1206,7 +1206,7 @@ int advanced_firmware_update_install(
(int) state.result);
result = advanced_firmware_update_additional_image_install(
anjay, fw_logic_add_inst->iid, fw_table, &state,
- ADD_IMG_NAMES[i]);
+ security_info, ADD_IMG_NAMES[i]);
if (result) {
demo_log(ERROR, "AFU instance %u install failed",
@@ -1261,3 +1261,17 @@ void advanced_firmware_update_uninstall(advanced_fw_update_logic_t *fw_table) {
afu_logic_destroy(&fw_table[i]);
}
}
+
+int advanced_firmware_update_get_security_config(
+ anjay_iid_t iid,
+ void *fw_,
+ anjay_security_config_t *out_security_config,
+ const char *download_uri) {
+ (void) iid;
+ advanced_fw_update_logic_t *fw_table = (advanced_fw_update_logic_t *) fw_;
+ advanced_fw_update_logic_t *fw = &fw_table[FW_UPDATE_IID_APP];
+ (void) download_uri;
+ memset(out_security_config, 0, sizeof(*out_security_config));
+ out_security_config->security_info = fw->security_info;
+ return 0;
+}
diff --git a/demo/advanced_firmware_update.h b/demo/advanced_firmware_update.h
index 88bbf898..e66eb334 100644
--- a/demo/advanced_firmware_update.h
+++ b/demo/advanced_firmware_update.h
@@ -102,6 +102,7 @@ int advanced_firmware_update_additional_image_install(
anjay_iid_t iid,
advanced_fw_update_logic_t *fw_table,
anjay_advanced_fw_update_initial_state_t *init_state,
+ const avs_net_security_info_t *security_info,
const char *component_name);
const char *
@@ -143,6 +144,12 @@ int fw_update_common_perform_upgrade(
size_t requested_supplemental_iids_count);
int fw_update_common_maybe_create_firmware_file(advanced_fw_update_logic_t *fw);
+int advanced_firmware_update_get_security_config(
+ anjay_iid_t iid,
+ void *fw_,
+ anjay_security_config_t *out_security_config,
+ const char *download_uri);
+
typedef struct {
anjay_advanced_fw_update_state_t inst_states[FW_UPDATE_IID_IMAGE_SLOTS];
anjay_advanced_fw_update_result_t inst_results[FW_UPDATE_IID_IMAGE_SLOTS];
diff --git a/demo/advanced_firmware_update_addimg.c b/demo/advanced_firmware_update_addimg.c
index 1d742892..f856ecd0 100644
--- a/demo/advanced_firmware_update_addimg.c
+++ b/demo/advanced_firmware_update_addimg.c
@@ -92,7 +92,7 @@ static int update(advanced_fw_update_logic_t *fw) {
return 0;
}
-static const anjay_advanced_fw_update_handlers_t handlers = {
+static anjay_advanced_fw_update_handlers_t handlers = {
.stream_open = fw_stream_open,
.stream_write = fw_update_common_write,
.stream_finish = fw_update_common_finish,
@@ -107,10 +107,19 @@ int advanced_firmware_update_additional_image_install(
anjay_iid_t iid,
advanced_fw_update_logic_t *fw_table,
anjay_advanced_fw_update_initial_state_t *init_state,
+ const avs_net_security_info_t *security_info,
const char *component_name) {
advanced_fw_update_logic_t *fw_logic = &fw_table[iid];
memcpy(fw_logic->current_ver, VER_DEFAULT, sizeof(VER_DEFAULT));
fw_global = fw_logic;
+ if (security_info) {
+ memcpy(&fw_logic->security_info, security_info,
+ sizeof(fw_logic->security_info));
+ handlers.get_security_config =
+ advanced_firmware_update_get_security_config;
+ } else {
+ handlers.get_security_config = NULL;
+ }
int result =
anjay_advanced_fw_update_instance_add(anjay, fw_logic->iid,
component_name, &handlers,
diff --git a/demo/advanced_firmware_update_app.c b/demo/advanced_firmware_update_app.c
index 48bbe259..16a1566b 100644
--- a/demo/advanced_firmware_update_app.c
+++ b/demo/advanced_firmware_update_app.c
@@ -189,19 +189,6 @@ static anjay_advanced_fw_update_handlers_t handlers = {
.perform_upgrade = fw_update_common_perform_upgrade
};
-static int fw_get_security_config(anjay_iid_t iid,
- void *fw_,
- anjay_security_config_t *out_security_config,
- const char *download_uri) {
- (void) iid;
- advanced_fw_update_logic_t *fw_table = (advanced_fw_update_logic_t *) fw_;
- advanced_fw_update_logic_t *fw = &fw_table[FW_UPDATE_IID_APP];
- (void) download_uri;
- memset(out_security_config, 0, sizeof(*out_security_config));
- out_security_config->security_info = fw->security_info;
- return 0;
-}
-
int advanced_firmware_update_application_install(
anjay_t *anjay,
advanced_fw_update_logic_t *fw_table,
@@ -215,7 +202,8 @@ int advanced_firmware_update_application_install(
if (security_info) {
memcpy(&fw_logic->security_info, security_info,
sizeof(fw_logic->security_info));
- handlers.get_security_config = fw_get_security_config;
+ handlers.get_security_config =
+ advanced_firmware_update_get_security_config;
} else {
handlers.get_security_config = NULL;
}
diff --git a/demo/demo.c b/demo/demo.c
index 14b22c91..06fcb93a 100644
--- a/demo/demo.c
+++ b/demo/demo.c
@@ -498,6 +498,21 @@ static void reschedule_notify_time_dependent(anjay_demo_t *demo) {
}
}
+// !defined(ANJAY_WITH_CONN_STATUS_API)
+
+#ifdef ANJAY_WITH_CONN_STATUS_API
+static void
+server_connection_status_change_callback(void *demo_,
+ anjay_t *anjay,
+ anjay_ssid_t ssid,
+ anjay_server_conn_status_t status) {
+ (void) demo_;
+ (void) anjay;
+ demo_log(INFO, "Current status of the server with SSID %d is: %s", ssid,
+ translate_server_connection_status_enum_to_str(status));
+}
+#endif // ANJAY_WITH_CONN_STATUS_API
+
static int demo_init(anjay_demo_t *demo, cmdline_args_t *cmdline_args) {
demo->allocated_buffers = cmdline_args->allocated_buffers;
cmdline_args->allocated_buffers = NULL;
@@ -544,6 +559,8 @@ static int demo_init(anjay_demo_t *demo, cmdline_args_t *cmdline_args) {
.update_immediately_on_dm_change =
cmdline_args->update_immediately_on_dm_change,
.enable_self_notify = cmdline_args->enable_self_notify,
+ .connection_error_is_registration_failure =
+ cmdline_args->connection_error_is_registration_failure,
.default_tls_ciphersuites = {
.ids = cmdline_args->default_ciphersuites,
.num_ids = cmdline_args->default_ciphersuites_count
@@ -555,6 +572,10 @@ static int demo_init(anjay_demo_t *demo, cmdline_args_t *cmdline_args) {
#if defined(ANJAY_WITH_LWM2M11) && defined(WITH_AVS_COAP_TCP)
.coap_tcp_request_timeout = cmdline_args->tcp_request_timeout,
#endif // defined(ANJAY_WITH_LWM2M11) && defined(WITH_AVS_COAP_TCP)
+#ifdef ANJAY_WITH_CONN_STATUS_API
+ .server_connection_status_cb = server_connection_status_change_callback,
+ .server_connection_status_cb_arg = demo,
+#endif // ANJAY_WITH_CONN_STATUS_API
};
#ifdef ANJAY_WITH_LWM2M11
diff --git a/demo/demo_args.c b/demo/demo_args.c
index 81b1a8d1..88ac87bc 100644
--- a/demo/demo_args.c
+++ b/demo/demo_args.c
@@ -131,6 +131,7 @@ static const cmdline_args_t DEFAULT_CMDLINE_ARGS = {
.prefer_hierarchical_formats = false,
.update_immediately_on_dm_change = false,
.enable_self_notify = false,
+ .connection_error_is_registration_failure = false,
.prefer_same_socket_downloads = false,
};
@@ -594,6 +595,9 @@ static void print_help(const struct option *options) {
"over CoAP+TCP and HTTP" },
# endif // ANJAY_WITH_DOWNLOADER
#endif // ANJAY_WITH_MODULE_SW_MGMT
+ { 348, NULL, NULL,
+ "Treat failures of the \"connect\" socket operation (e.g. (D)TLS "
+ "handshake failures) as a failed LwM2M Register operation." },
};
const size_t screen_width = get_screen_width();
@@ -994,6 +998,7 @@ int demo_parse_argv(cmdline_args_t *parsed_args, int argc, char *argv[]) {
{ "sw-mgmt-tcp-request-timeout", required_argument, 0, 347 },
# endif // ANJAY_WITH_DOWNLOADER
#endif // ANJAY_WITH_MODULE_SW_MGMT
+ { "connection-error-is-registration-failure", no_argument, 0, 348 },
{ 0, 0, 0, 0 }
// clang-format on
};
@@ -2001,6 +2006,9 @@ int demo_parse_argv(cmdline_args_t *parsed_args, int argc, char *argv[]) {
}
# endif // ANJAY_WITH_DOWNLOADER
#endif // ANJAY_WITH_MODULE_SW_MGMT
+ case 348:
+ parsed_args->connection_error_is_registration_failure = true;
+ break;
case 0:
goto process;
}
diff --git a/demo/demo_args.h b/demo/demo_args.h
index ceedfa3e..a38a6207 100644
--- a/demo/demo_args.h
+++ b/demo/demo_args.h
@@ -168,6 +168,7 @@ typedef struct cmdline_args {
bool prefer_hierarchical_formats;
bool update_immediately_on_dm_change;
bool enable_self_notify;
+ bool connection_error_is_registration_failure;
bool use_connection_id;
bool start_offline;
diff --git a/demo/demo_cmds.c b/demo/demo_cmds.c
index 8ea2bb7e..38f41b5d 100644
--- a/demo/demo_cmds.c
+++ b/demo/demo_cmds.c
@@ -1430,6 +1430,24 @@ static void cmd_last_communication_time(anjay_demo_t *demo,
}
#endif // ANJAY_WITH_COMMUNICATION_TIMESTAMP_API
+#ifdef ANJAY_WITH_CONN_STATUS_API
+static void cmd_get_server_connection_status(anjay_demo_t *demo,
+ const char *args_string) {
+ anjay_ssid_t ssid = ANJAY_SSID_ANY;
+ anjay_server_conn_status_t result;
+
+ if (*args_string && parse_ssid(args_string, &ssid)) {
+ demo_log(ERROR, "invalid Short Server ID: %s", args_string);
+ return;
+ }
+
+ result = anjay_get_server_connection_status(demo->anjay, ssid);
+
+ demo_log(INFO, "Current server connection status: %s",
+ translate_server_connection_status_enum_to_str(result));
+}
+#endif // ANJAY_WITH_CONN_STATUS_API
+
static void cmd_help(anjay_demo_t *demo, const char *args_string);
struct cmd_handler_def {
@@ -1665,6 +1683,12 @@ static const struct cmd_handler_def COMMAND_HANDLERS[] = {
"no argument specified) or a given server (if numeric SSID "
"argument given)."),
#endif // ANJAY_WITH_COMMUNICATION_TIMESTAMP_API
+#ifdef ANJAY_WITH_CONN_STATUS_API
+ CMD_HANDLER("get-server-connection-status", "[SSID]",
+ cmd_get_server_connection_status,
+ "Displays current connection status for the server with SSID "
+ "specified by the argument."),
+#endif // ANJAY_WITH_CONN_STATUS_API
CMD_HANDLER("help", "", cmd_help, "Prints this message")
// clang-format on
};
diff --git a/demo/demo_utils.c b/demo/demo_utils.c
index 16c164f3..8985ff5b 100644
--- a/demo/demo_utils.c
+++ b/demo/demo_utils.c
@@ -29,6 +29,30 @@ static struct {
char **argv;
} g_saved_args;
+#ifdef ANJAY_WITH_CONN_STATUS_API
+const char *translate_server_connection_status_enum_to_str(
+ anjay_server_conn_status_t status) {
+ static const char *const demo_server_connection_states_str[] = {
+ [ANJAY_SERV_CONN_STATUS_INVALID] = "INVALID",
+ [ANJAY_SERV_CONN_STATUS_ERROR] = "ERROR",
+ [ANJAY_SERV_CONN_STATUS_INITIAL] = "INITIAL",
+ [ANJAY_SERV_CONN_STATUS_CONNECTING] = "CONNECTING",
+ [ANJAY_SERV_CONN_STATUS_BOOTSTRAPPING] = "BOOTSTRAPPING",
+ [ANJAY_SERV_CONN_STATUS_BOOTSTRAPPED] = "BOOTSTRAPPED",
+ [ANJAY_SERV_CONN_STATUS_REGISTERING] = "REGISTERING",
+ [ANJAY_SERV_CONN_STATUS_REGISTERED] = "REGISTERED",
+ [ANJAY_SERV_CONN_STATUS_REG_FAILURE] = "REG_FAILURE",
+ [ANJAY_SERV_CONN_STATUS_DEREGISTERING] = "DEREGISTERING",
+ [ANJAY_SERV_CONN_STATUS_DEREGISTERED] = "DEREGISTERED",
+ [ANJAY_SERV_CONN_STATUS_SUSPENDING] = "SUSPENDING",
+ [ANJAY_SERV_CONN_STATUS_SUSPENDED] = "SUSPENDED",
+ [ANJAY_SERV_CONN_STATUS_REREGISTERING] = "REREGISTERING",
+ [ANJAY_SERV_CONN_STATUS_UPDATING] = "UPDATING"
+ };
+ return demo_server_connection_states_str[status];
+}
+#endif // ANJAY_WITH_CONN_STATUS_API
+
char **argv_get(void) {
AVS_ASSERT(g_saved_args.argv, "argv_store not called before argv_get");
return g_saved_args.argv;
diff --git a/demo/demo_utils.h b/demo/demo_utils.h
index ce1d90ed..63a3a5b2 100644
--- a/demo/demo_utils.h
+++ b/demo/demo_utils.h
@@ -71,6 +71,11 @@ int copy_file_contents(FILE *dst, FILE *src);
int calc_file_crc32(const char *filename, uint32_t *out_crc);
+#ifdef ANJAY_WITH_CONN_STATUS_API
+const char *translate_server_connection_status_enum_to_str(
+ anjay_server_conn_status_t status);
+#endif // ANJAY_WITH_CONN_STATUS_API
+
#if defined(AVS_COMMONS_WITH_AVS_PERSISTENCE) \
&& defined(AVS_COMMONS_STREAM_WITH_FILE)
avs_error_t store_etag(avs_persistence_context_t *ctx,
diff --git a/deps/avs_coap/CMakeLists.txt b/deps/avs_coap/CMakeLists.txt
index 4d323b65..9cc79951 100644
--- a/deps/avs_coap/CMakeLists.txt
+++ b/deps/avs_coap/CMakeLists.txt
@@ -37,6 +37,7 @@ option(WITH_AVS_COAP_TCP "Enable CoAP over TCP support" ON)
option(WITH_AVS_COAP_STREAMING_API "Enable streaming API" ON)
option(WITH_AVS_COAP_OBSERVE "Enable support for observations" ON)
+option(WITH_AVS_COAP_OBSERVE_CANCEL_ON_TIMEOUT "Turn on cancelling observation on a timeout " OFF)
cmake_dependent_option(WITH_AVS_COAP_OBSERVE_PERSISTENCE "Enable observations persistence" ON "WITH_AVS_COAP_OBSERVE" OFF)
option(WITH_AVS_COAP_BLOCK "Enable support for BLOCK/BERT transfers" ON)
diff --git a/deps/avs_coap/doc/CMakeLists.txt b/deps/avs_coap/doc/CMakeLists.txt
index 5aaa2a67..2cfc2068 100644
--- a/deps/avs_coap/doc/CMakeLists.txt
+++ b/deps/avs_coap/doc/CMakeLists.txt
@@ -48,6 +48,7 @@ if(EXISTS "${AVS_COAP_SOURCE_DIR}/Doxyfile.in")
# TODO: List these flags automatically
foreach(FLAG WITH_AVS_COAP_BLOCK
WITH_AVS_COAP_OBSERVE
+ WITH_AVS_COAP_OBSERVE_CANCEL_ON_TIMEOUT
WITH_AVS_COAP_OBSERVE_PERSISTENCE
WITH_AVS_COAP_STREAMING_API
WITH_AVS_COAP_TCP
diff --git a/deps/avs_coap/include_public/avsystem/coap/avs_coap_config.h.in b/deps/avs_coap/include_public/avsystem/coap/avs_coap_config.h.in
index edba0df2..b846abad 100644
--- a/deps/avs_coap/include_public/avsystem/coap/avs_coap_config.h.in
+++ b/deps/avs_coap/include_public/avsystem/coap/avs_coap_config.h.in
@@ -68,11 +68,23 @@
*/
#cmakedefine WITH_AVS_COAP_OBSERVE
+/**
+ * Turn on cancelling observation on a timeout.
+ *
+ * Only meaningful if WITH_AVS_COAP_OBSERVE is enabled.
+ *
+ * NOTE: LwM2M specification requires LwM2M server to send Cancel Observation
+ * request. Meanwhile CoAP RFC 7641 states that timeout on notification should
+ * cancel it. This setting is to enable both of these behaviors with default
+ * focused on keeping observations in case of bad connectivity.
+ */
+#cmakedefine WITH_AVS_COAP_OBSERVE_CANCEL_ON_TIMEOUT
+
/**
* Enable support for observation persistence (avs_coap_observe_persist()
* and avs_coap_observe_restore() calls).
*
- * Only meaningful WITH_AVS_COAP_OBSERVE is enabled.
+ * Only meaningful if WITH_AVS_COAP_OBSERVE is enabled.
*/
#cmakedefine WITH_AVS_COAP_OBSERVE_PERSISTENCE
diff --git a/deps/avs_coap/src/async/avs_coap_async_server.c b/deps/avs_coap/src/async/avs_coap_async_server.c
index 68d692ff..90cebd78 100644
--- a/deps/avs_coap/src/async/avs_coap_async_server.c
+++ b/deps/avs_coap/src/async/avs_coap_async_server.c
@@ -314,17 +314,16 @@ send_result_handler(avs_coap_ctx_t *ctx,
},
code);
}
-#ifdef WITH_AVS_COAP_OBSERVE
+#if defined(WITH_AVS_COAP_OBSERVE) \
+ && defined(WITH_AVS_COAP_OBSERVE_CANCEL_ON_TIMEOUT)
else if (fail_err.category == AVS_COAP_ERR_CATEGORY
&& fail_err.code == AVS_COAP_ERR_TIMEOUT) {
- // According to RFC 7641, when trying to send a Confirmable notification
- // ends in a timeout, the client "is considered no longer interested in
- // the resource and is removed by the server from the list of observers"
avs_coap_observe_cancel(ctx, (avs_coap_observe_id_t) {
.token = token
});
}
-#endif // WITH_AVS_COAP_OBSERVE
+#endif /* defined(WITH_AVS_COAP_OBSERVE) && \
+ defined(WITH_AVS_COAP_OBSERVE_CANCEL_ON_TIMEOUT) */
// delivery status handler or observe cancel handler
// might have canceled the exchange
diff --git a/deps/avs_commons b/deps/avs_commons
index 683a8cb3..2885ea16 160000
--- a/deps/avs_commons
+++ b/deps/avs_commons
@@ -1 +1 @@
-Subproject commit 683a8cb3d92efd094ff7dafc8322c8fce4666dbe
+Subproject commit 2885ea16a2662c3b4ea2c245f3eac408c02e9d73
diff --git a/doc/sphinx/snippet_sources.md5 b/doc/sphinx/snippet_sources.md5
index c17470e4..268ce0b6 100644
--- a/doc/sphinx/snippet_sources.md5
+++ b/doc/sphinx/snippet_sources.md5
@@ -1,9 +1,9 @@
563b9fc06ed0c55bae149f839ab80f11 deps/avs_coap/include_public/avsystem/coap/tcp.h
8d609e7d1e46df8b37f8d694f6d78e7f deps/avs_coap/include_public/avsystem/coap/udp.h
6979acbbeda62d10befc6f7aa7e0865e deps/avs_commons/include_public/avsystem/commons/avs_addrinfo.h
-65c5f1a934166ea7ffe0b427f87e0d2d deps/avs_commons/include_public/avsystem/commons/avs_base64.h
+0e56e80e053a9afedc7a714f2acbcc7b deps/avs_commons/include_public/avsystem/commons/avs_base64.h
e9bc875ab314a3d88787889cfe282f8f deps/avs_commons/include_public/avsystem/commons/avs_crypto_common.h
-9f284929ca4c327e8955e3e01fe8f007 deps/avs_commons/include_public/avsystem/commons/avs_crypto_pki.h
+c351a8bd3738a441a32749df2f1489ca deps/avs_commons/include_public/avsystem/commons/avs_crypto_pki.h
e23cc205e18c2bc699b7805ff2f04fe3 deps/avs_commons/include_public/avsystem/commons/avs_crypto_psk.h
a9cad6508cf9c61d88f0dc809848bc9d deps/avs_commons/include_public/avsystem/commons/avs_socket.h
99c2b66c9d05b8af9138472a43014197 deps/avs_commons/include_public/avsystem/commons/avs_socket_v_table.h
@@ -84,7 +84,7 @@ f971df7872a8f052bb72ae64610a8031 examples/tutorial/firmware-update/basic-implem
077f6b89dad59ef3b9b58e2abe93aff6 examples/tutorial/firmware-update/secure-downloads/src/firmware_update.c
99ef6e1d741fbfefab9ddf0db97c61e7 include_public/anjay/advanced_fw_update.h
be8a4c22a41c2ab79bf3a68f1f74f301 include_public/anjay/attr_storage.h
-6434100b9f5206dd350885fdab853504 include_public/anjay/core.h
+8da010b13c862c6730a6e41624f16266 include_public/anjay/core.h
9771c6bc4940d778a12ab93cb400a11c include_public/anjay/dm.h
56b500ecbd5ffc8d468327c650883cbd include_public/anjay/fw_update.h
b49ca67d9f4f04bf20261c261ad41822 include_public/anjay/io.h
diff --git a/doc/sphinx/source/AdvancedTopics/AT-NetworkErrorHandling.rst b/doc/sphinx/source/AdvancedTopics/AT-NetworkErrorHandling.rst
index 4d7f13bf..6e23e6ab 100644
--- a/doc/sphinx/source/AdvancedTopics/AT-NetworkErrorHandling.rst
+++ b/doc/sphinx/source/AdvancedTopics/AT-NetworkErrorHandling.rst
@@ -23,12 +23,14 @@ conditions happen while performing each of the client-initiated operations.
| | Request | Register | Update | De-register | Notify |
| | Bootstrap | | | | (confirmable) |
+=================+==================+==================+==================+=============+===================+
-| **Timeout | Retry DTLS | Retry DTLS | Fall back | Ignored | Cancel |
-| (DTLS)** [#t]_ | handshake [#hs]_ | handshake [#hs]_ | to Register | | observation |
-+-----------------+------------------+------------------+ | | |
-| **Timeout | Abort all | :ref:`Abort | | | |
-| (NoSec)** [#t]_ | communication | registration | | | |
-+-----------------+ [#a]_ | ` +------------------+ +-------------------+
+| **Timeout | Retry DTLS | Retry DTLS | Fall back | Ignored | Ignored by |
+| (DTLS)** [#t]_ | handshake [#hs]_ | handshake [#hs]_ | to Register | | default; |
++-----------------+------------------+------------------+ | | configurable; |
+| **Timeout | Abort all | :ref:`Abort | | | will be retried |
+| (NoSec)** [#t]_ | communication | registration | | | whenever |
+| | [#a]_ | ` | | | next notification |
+| | | | | | is scheduled |
++-----------------+ | +------------------+ +-------------------+
| **Network | | | Fall back to | | Fall back to |
| (e.g. ICMP) | | | Client-Initiated | | Client-Initiated |
| error** | | | Bootstrap [#bs]_ | | Bootstrap [#bs]_ |
@@ -73,20 +75,50 @@ condition is equivalent with the "fall back to Client-Initiated Bootstrap"
Other error conditions
----------------------
-* **DTLS handshakes** are performed by the DTLS backend library used. This
- includes handling non-fatal errors and retransmissions. In case of no response
- from the server, DTLS handshake retransmissions are expected to follow
- `RFC 6347, Section 4.2.4. Timeout and Retransmission
- `_.
- The handshake timers can be customized during Anjay initialization, by setting
- `anjay_configuration_t::udp_dtls_hs_tx_params
- <../api/structanjay__configuration.html#ab8ca076537138e7d78bd1ee5d5e2031a>`_.
-
- In case of the ultimate timeout, network-layer error, or an internal error
- during the handshake attempt, Anjay will fall back to Client-Initiated
- Bootstrap [#bs]_ or, if the attempt was to connect to a Bootstrap Server,
- cease any attempts to communicate with it (note that unless regular Server
- accounts are available, this will mean abortion of all communication [#a]_).
+* **Connect operation errors** can occur for several reasons, the most common
+ being:
+
+ * **(D)TLS handshake errors.** Handshakes are performed by the TLS backend
+ library used. This includes handling non-fatal errors and retransmissions.
+ In case of no response from the server, DTLS handshake retransmissions are
+ expected to follow `RFC 6347, Section 4.2.4. Timeout and Retransmission
+ `_. The handshake timers
+ can be customized during Anjay initialization, by setting
+ `anjay_configuration_t::udp_dtls_hs_tx_params
+ <../api/structanjay__configuration.html#ab8ca076537138e7d78bd1ee5d5e2031a>`_.
+
+ Ultimate timeout, network-layer errors, and internal errors during the
+ handshake attempt will be treated as a failure of the "connect" operation.
+
+ * **Domain name resolution errors.** If the ``getaddrinfo()`` call (or
+ equivalent) fails to return any usable IP address, this is also treated as
+ a failure of the "connect" operation.
+
+ * **TCP handshake errors.** While the actual socket-level "connect" operation
+ does not involve any network communication for UDP and as such can almost
+ never fail, it performs actual handshake in case of TCP. Failure of this
+ handshake is also treated in the same way as the other cases mentioned here.
+
+ * In some cases, **inconsistent data model state** may be treated equivalently
+ to a connection error, e.g. when there is no Security object instance that
+ would match a given Server object instance.
+
+ Note that all of the operations mentioned above (domain name resolution and
+ both TCP and (D)TLS handshakes) are performed synchronously and will block all
+ other operations.
+
+ If any of the above conditions happen, Anjay will, by default, fall back to
+ Client-Initiated Bootstrap [#bs]_ or, if the attempt was to connect to
+ a Bootstrap Server, cease any attempts to communicate with it (note that
+ unless regular Server accounts are available, this will mean abortion of all
+ communication [#a]_).
+
+ This behavior can be changed by enabling the
+ `connection_error_is_registration_failure
+ <../api/structanjay__configuration.html#adcc95609ca645a5bd6a572f4c99a83fb>`_.
+ In that case, connection errors will trigger :ref:`err-abort-reg`, and thus
+ the automatic retry flow described in "Bootstrap and LwM2M Server Registration
+ Mechanisms" section mentioned above will be respected.
* Errors while receiving an incoming request, or any unrecognized incoming
packets, will be ignored
diff --git a/doc/sphinx/source/Migrating.rst b/doc/sphinx/source/Migrating.rst
index 545086e1..6aa7a560 100644
--- a/doc/sphinx/source/Migrating.rst
+++ b/doc/sphinx/source/Migrating.rst
@@ -35,3 +35,4 @@ Migrating from older versions
Migrating/MigratingFromAnjay32
Migrating/MigratingFromAnjay33
Migrating/MigratingFromAnjay34
+ Migrating/MigratingFromAnjay37
diff --git a/doc/sphinx/source/Migrating/MigratingFromAnjay214.rst b/doc/sphinx/source/Migrating/MigratingFromAnjay214.rst
index a5299ff7..85ad7eaa 100644
--- a/doc/sphinx/source/Migrating/MigratingFromAnjay214.rst
+++ b/doc/sphinx/source/Migrating/MigratingFromAnjay214.rst
@@ -132,8 +132,7 @@ Changed flow of cancelling observations in case of errors
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
CoAP observations are implicitly cancelled if a notification bearing a 4.xx or
-5.xx error code is delivered, or if an attempt to deliver a notification times
-out.
+5.xx error code is delivered.
In Anjay 3.4.x and earlier, this cancellation (which involves calling the
``avs_coap_observe_cancel_handler_t`` callback) was performed *before* calling
diff --git a/doc/sphinx/source/Migrating/MigratingFromAnjay215.rst b/doc/sphinx/source/Migrating/MigratingFromAnjay215.rst
index 342c9980..4f187a9d 100644
--- a/doc/sphinx/source/Migrating/MigratingFromAnjay215.rst
+++ b/doc/sphinx/source/Migrating/MigratingFromAnjay215.rst
@@ -127,8 +127,7 @@ Changed flow of cancelling observations in case of errors
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
CoAP observations are implicitly cancelled if a notification bearing a 4.xx or
-5.xx error code is delivered, or if an attempt to deliver a notification times
-out.
+5.xx error code is delivered.
In Anjay 3.4.x and earlier, this cancellation (which involves calling the
``avs_coap_observe_cancel_handler_t`` callback) was performed *before* calling
diff --git a/doc/sphinx/source/Migrating/MigratingFromAnjay225.rst b/doc/sphinx/source/Migrating/MigratingFromAnjay225.rst
index c31a0b12..4899bf71 100644
--- a/doc/sphinx/source/Migrating/MigratingFromAnjay225.rst
+++ b/doc/sphinx/source/Migrating/MigratingFromAnjay225.rst
@@ -272,8 +272,7 @@ Changed flow of cancelling observations in case of errors
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
CoAP observations are implicitly cancelled if a notification bearing a 4.xx or
-5.xx error code is delivered, or if an attempt to deliver a notification times
-out.
+5.xx error code is delivered.
In Anjay 3.4.x and earlier, this cancellation (which involves calling the
``avs_coap_observe_cancel_handler_t`` callback) was performed *before* calling
diff --git a/doc/sphinx/source/Migrating/MigratingFromAnjay24.rst b/doc/sphinx/source/Migrating/MigratingFromAnjay24.rst
index c19ec016..6922a5ad 100644
--- a/doc/sphinx/source/Migrating/MigratingFromAnjay24.rst
+++ b/doc/sphinx/source/Migrating/MigratingFromAnjay24.rst
@@ -162,8 +162,7 @@ Changed flow of cancelling observations in case of errors
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
CoAP observations are implicitly cancelled if a notification bearing a 4.xx or
-5.xx error code is delivered, or if an attempt to deliver a notification times
-out.
+5.xx error code is delivered.
In Anjay 3.4.x and earlier, this cancellation (which involves calling the
``avs_coap_observe_cancel_handler_t`` callback) was performed *before* calling
diff --git a/doc/sphinx/source/Migrating/MigratingFromAnjay26.rst b/doc/sphinx/source/Migrating/MigratingFromAnjay26.rst
index fb59c3e3..dca5f55d 100644
--- a/doc/sphinx/source/Migrating/MigratingFromAnjay26.rst
+++ b/doc/sphinx/source/Migrating/MigratingFromAnjay26.rst
@@ -162,8 +162,7 @@ Changed flow of cancelling observations in case of errors
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
CoAP observations are implicitly cancelled if a notification bearing a 4.xx or
-5.xx error code is delivered, or if an attempt to deliver a notification times
-out.
+5.xx error code is delivered.
In Anjay 3.4.x and earlier, this cancellation (which involves calling the
``avs_coap_observe_cancel_handler_t`` callback) was performed *before* calling
diff --git a/doc/sphinx/source/Migrating/MigratingFromAnjay27.rst b/doc/sphinx/source/Migrating/MigratingFromAnjay27.rst
index 5be880e8..450ccd9f 100644
--- a/doc/sphinx/source/Migrating/MigratingFromAnjay27.rst
+++ b/doc/sphinx/source/Migrating/MigratingFromAnjay27.rst
@@ -155,8 +155,7 @@ Changed flow of cancelling observations in case of errors
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
CoAP observations are implicitly cancelled if a notification bearing a 4.xx or
-5.xx error code is delivered, or if an attempt to deliver a notification times
-out.
+5.xx error code is delivered.
In Anjay 3.4.x and earlier, this cancellation (which involves calling the
``avs_coap_observe_cancel_handler_t`` callback) was performed *before* calling
diff --git a/doc/sphinx/source/Migrating/MigratingFromAnjay28.rst b/doc/sphinx/source/Migrating/MigratingFromAnjay28.rst
index c5b3c700..3ec79fc0 100644
--- a/doc/sphinx/source/Migrating/MigratingFromAnjay28.rst
+++ b/doc/sphinx/source/Migrating/MigratingFromAnjay28.rst
@@ -150,8 +150,7 @@ Changed flow of cancelling observations in case of errors
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
CoAP observations are implicitly cancelled if a notification bearing a 4.xx or
-5.xx error code is delivered, or if an attempt to deliver a notification times
-out.
+5.xx error code is delivered.
In Anjay 3.4.x and earlier, this cancellation (which involves calling the
``avs_coap_observe_cancel_handler_t`` callback) was performed *before* calling
diff --git a/doc/sphinx/source/Migrating/MigratingFromAnjay30.rst b/doc/sphinx/source/Migrating/MigratingFromAnjay30.rst
index 49ac697e..a0ebbdda 100644
--- a/doc/sphinx/source/Migrating/MigratingFromAnjay30.rst
+++ b/doc/sphinx/source/Migrating/MigratingFromAnjay30.rst
@@ -62,8 +62,7 @@ Changed flow of cancelling observations in case of errors
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
CoAP observations are implicitly cancelled if a notification bearing a 4.xx or
-5.xx error code is delivered, or if an attempt to deliver a notification times
-out.
+5.xx error code is delivered.
In Anjay 3.4.x and earlier, this cancellation (which involves calling the
``avs_coap_observe_cancel_handler_t`` callback) was performed *before* calling
diff --git a/doc/sphinx/source/Migrating/MigratingFromAnjay32.rst b/doc/sphinx/source/Migrating/MigratingFromAnjay32.rst
index 70c90823..95254626 100644
--- a/doc/sphinx/source/Migrating/MigratingFromAnjay32.rst
+++ b/doc/sphinx/source/Migrating/MigratingFromAnjay32.rst
@@ -62,8 +62,7 @@ Changed flow of cancelling observations in case of errors
---------------------------------------------------------
CoAP observations are implicitly cancelled if a notification bearing a 4.xx or
-5.xx error code is delivered, or if an attempt to deliver a notification times
-out.
+5.xx error code is delivered.
In Anjay 3.4.x and earlier, this cancellation (which involves calling the
``avs_coap_observe_cancel_handler_t`` callback) was performed *before* calling
diff --git a/doc/sphinx/source/Migrating/MigratingFromAnjay33.rst b/doc/sphinx/source/Migrating/MigratingFromAnjay33.rst
index b717daa0..fd267081 100644
--- a/doc/sphinx/source/Migrating/MigratingFromAnjay33.rst
+++ b/doc/sphinx/source/Migrating/MigratingFromAnjay33.rst
@@ -59,8 +59,7 @@ Changed flow of cancelling observations in case of errors
---------------------------------------------------------
CoAP observations are implicitly cancelled if a notification bearing a 4.xx or
-5.xx error code is delivered, or if an attempt to deliver a notification times
-out.
+5.xx error code is delivered.
In Anjay 3.4.x and earlier, this cancellation (which involves calling the
``avs_coap_observe_cancel_handler_t`` callback) was performed *before* calling
diff --git a/doc/sphinx/source/Migrating/MigratingFromAnjay34.rst b/doc/sphinx/source/Migrating/MigratingFromAnjay34.rst
index 820ae11a..fbf21f42 100644
--- a/doc/sphinx/source/Migrating/MigratingFromAnjay34.rst
+++ b/doc/sphinx/source/Migrating/MigratingFromAnjay34.rst
@@ -25,8 +25,9 @@ Changed flow of cancelling observations in case of errors
---------------------------------------------------------
CoAP observations are implicitly cancelled if a notification bearing a 4.xx or
-5.xx error code is delivered, or if an attempt to deliver a notification times
-out.
+5.xx error code is delivered. If an attempt to deliver a confirmable
+notification times out, CoAP observation is not cancelled by default anymore.
+It can be adjusted by ``WITH_AVS_COAP_OBSERVE_CANCEL_ON_TIMEOUT``.
In Anjay 3.4.x and earlier, this cancellation (which involves calling the
``avs_coap_observe_cancel_handler_t`` callback) was performed *before* calling
diff --git a/doc/sphinx/source/Migrating/MigratingFromAnjay37.rst b/doc/sphinx/source/Migrating/MigratingFromAnjay37.rst
new file mode 100644
index 00000000..f20aff7d
--- /dev/null
+++ b/doc/sphinx/source/Migrating/MigratingFromAnjay37.rst
@@ -0,0 +1,28 @@
+..
+ Copyright 2017-2024 AVSystem
+ AVSystem Anjay LwM2M SDK
+ All rights reserved.
+
+ Licensed under the AVSystem-5-clause License.
+ See the attached LICENSE file for details.
+
+Migrating from Anjay 3.7
+========================
+
+.. contents:: :local:
+
+.. highlight:: c
+
+Introduction
+------------
+
+Since Anjay 3.8.0, confirmable notifications are not cancelled anymore in case
+of a timeout.
+
+Changed flow of cancelling observations in case of timeout
+----------------------------------------------------------
+
+If an attempt to deliver a confirmable notification times out, CoAP observation
+is not cancelled by default anymore. It can be adjusted by
+``WITH_AVS_COAP_OBSERVE_CANCEL_ON_TIMEOUT``.
+The LwM2M Observe/Notify implementation in Anjay has been updated accordingly.
diff --git a/example_configs/embedded_lwm2m10/anjay/anjay_config.h b/example_configs/embedded_lwm2m10/anjay/anjay_config.h
index ab60d916..e169621c 100644
--- a/example_configs/embedded_lwm2m10/anjay/anjay_config.h
+++ b/example_configs/embedded_lwm2m10/anjay/anjay_config.h
@@ -656,6 +656,13 @@
* composite type request.
*/
/* #undef ANJAY_WITHOUT_COMPOSITE_OPERATIONS */
+
+/**
+ * Enable support for the experimental
+ * anjay_get_server_connection_status() API and related
+ * anjay_server_connection_status_cb_t callback.
+ */
+/* #undef ANJAY_WITH_CONN_STATUS_API */
/**@}*/
#endif // ANJAY_CONFIG_H
diff --git a/example_configs/embedded_lwm2m10/avsystem/coap/avs_coap_config.h b/example_configs/embedded_lwm2m10/avsystem/coap/avs_coap_config.h
index a7e62a80..94dcddf0 100644
--- a/example_configs/embedded_lwm2m10/avsystem/coap/avs_coap_config.h
+++ b/example_configs/embedded_lwm2m10/avsystem/coap/avs_coap_config.h
@@ -68,11 +68,23 @@
*/
#define WITH_AVS_COAP_OBSERVE
+/**
+ * Turn on cancelling observation on a timeout.
+ *
+ * Only meaningful if WITH_AVS_COAP_OBSERVE is enabled.
+ *
+ * NOTE: LwM2M specification requires LwM2M server to send Cancel Observation
+ * request. Meanwhile CoAP RFC 7641 states that timeout on notification should
+ * cancel it. This setting is to enable both of these behaviors with default
+ * focused on keeping observations in case of bad connectivity.
+ */
+/* #undef WITH_AVS_COAP_OBSERVE_CANCEL_ON_TIMEOUT */
+
/**
* Enable support for observation persistence (avs_coap_observe_persist()
* and avs_coap_observe_restore() calls).
*
- * Only meaningful WITH_AVS_COAP_OBSERVE is enabled.
+ * Only meaningful if WITH_AVS_COAP_OBSERVE is enabled.
*/
/* #undef WITH_AVS_COAP_OBSERVE_PERSISTENCE */
diff --git a/example_configs/embedded_lwm2m11/anjay/anjay_config.h b/example_configs/embedded_lwm2m11/anjay/anjay_config.h
index b9f915d1..d6c598d6 100644
--- a/example_configs/embedded_lwm2m11/anjay/anjay_config.h
+++ b/example_configs/embedded_lwm2m11/anjay/anjay_config.h
@@ -656,6 +656,13 @@
* composite type request.
*/
/* #undef ANJAY_WITHOUT_COMPOSITE_OPERATIONS */
+
+/**
+ * Enable support for the experimental
+ * anjay_get_server_connection_status() API and related
+ * anjay_server_connection_status_cb_t callback.
+ */
+/* #undef ANJAY_WITH_CONN_STATUS_API */
/**@}*/
#endif // ANJAY_CONFIG_H
diff --git a/example_configs/embedded_lwm2m11/avsystem/coap/avs_coap_config.h b/example_configs/embedded_lwm2m11/avsystem/coap/avs_coap_config.h
index 8982b7c3..c79812f8 100644
--- a/example_configs/embedded_lwm2m11/avsystem/coap/avs_coap_config.h
+++ b/example_configs/embedded_lwm2m11/avsystem/coap/avs_coap_config.h
@@ -68,11 +68,23 @@
*/
#define WITH_AVS_COAP_OBSERVE
+/**
+ * Turn on cancelling observation on a timeout.
+ *
+ * Only meaningful if WITH_AVS_COAP_OBSERVE is enabled.
+ *
+ * NOTE: LwM2M specification requires LwM2M server to send Cancel Observation
+ * request. Meanwhile CoAP RFC 7641 states that timeout on notification should
+ * cancel it. This setting is to enable both of these behaviors with default
+ * focused on keeping observations in case of bad connectivity.
+ */
+/* #undef WITH_AVS_COAP_OBSERVE_CANCEL_ON_TIMEOUT */
+
/**
* Enable support for observation persistence (avs_coap_observe_persist()
* and avs_coap_observe_restore() calls).
*
- * Only meaningful WITH_AVS_COAP_OBSERVE is enabled.
+ * Only meaningful if WITH_AVS_COAP_OBSERVE is enabled.
*/
#define WITH_AVS_COAP_OBSERVE_PERSISTENCE
diff --git a/example_configs/linux_lwm2m10/anjay/anjay_config.h b/example_configs/linux_lwm2m10/anjay/anjay_config.h
index 93a59c83..8fb98c0b 100644
--- a/example_configs/linux_lwm2m10/anjay/anjay_config.h
+++ b/example_configs/linux_lwm2m10/anjay/anjay_config.h
@@ -656,6 +656,13 @@
* composite type request.
*/
/* #undef ANJAY_WITHOUT_COMPOSITE_OPERATIONS */
+
+/**
+ * Enable support for the experimental
+ * anjay_get_server_connection_status() API and related
+ * anjay_server_connection_status_cb_t callback.
+ */
+#define ANJAY_WITH_CONN_STATUS_API
/**@}*/
#endif // ANJAY_CONFIG_H
diff --git a/example_configs/linux_lwm2m10/avsystem/coap/avs_coap_config.h b/example_configs/linux_lwm2m10/avsystem/coap/avs_coap_config.h
index cf107322..745f2de9 100644
--- a/example_configs/linux_lwm2m10/avsystem/coap/avs_coap_config.h
+++ b/example_configs/linux_lwm2m10/avsystem/coap/avs_coap_config.h
@@ -68,11 +68,23 @@
*/
#define WITH_AVS_COAP_OBSERVE
+/**
+ * Turn on cancelling observation on a timeout.
+ *
+ * Only meaningful if WITH_AVS_COAP_OBSERVE is enabled.
+ *
+ * NOTE: LwM2M specification requires LwM2M server to send Cancel Observation
+ * request. Meanwhile CoAP RFC 7641 states that timeout on notification should
+ * cancel it. This setting is to enable both of these behaviors with default
+ * focused on keeping observations in case of bad connectivity.
+ */
+/* #undef WITH_AVS_COAP_OBSERVE_CANCEL_ON_TIMEOUT */
+
/**
* Enable support for observation persistence (avs_coap_observe_persist()
* and avs_coap_observe_restore() calls).
*
- * Only meaningful WITH_AVS_COAP_OBSERVE is enabled.
+ * Only meaningful if WITH_AVS_COAP_OBSERVE is enabled.
*/
/* #undef WITH_AVS_COAP_OBSERVE_PERSISTENCE */
diff --git a/example_configs/linux_lwm2m11/anjay/anjay_config.h b/example_configs/linux_lwm2m11/anjay/anjay_config.h
index e120d5e0..ee4e9afd 100644
--- a/example_configs/linux_lwm2m11/anjay/anjay_config.h
+++ b/example_configs/linux_lwm2m11/anjay/anjay_config.h
@@ -656,6 +656,13 @@
* composite type request.
*/
/* #undef ANJAY_WITHOUT_COMPOSITE_OPERATIONS */
+
+/**
+ * Enable support for the experimental
+ * anjay_get_server_connection_status() API and related
+ * anjay_server_connection_status_cb_t callback.
+ */
+#define ANJAY_WITH_CONN_STATUS_API
/**@}*/
#endif // ANJAY_CONFIG_H
diff --git a/example_configs/linux_lwm2m11/avsystem/coap/avs_coap_config.h b/example_configs/linux_lwm2m11/avsystem/coap/avs_coap_config.h
index bcff8a8d..83107b82 100644
--- a/example_configs/linux_lwm2m11/avsystem/coap/avs_coap_config.h
+++ b/example_configs/linux_lwm2m11/avsystem/coap/avs_coap_config.h
@@ -68,11 +68,23 @@
*/
#define WITH_AVS_COAP_OBSERVE
+/**
+ * Turn on cancelling observation on a timeout.
+ *
+ * Only meaningful if WITH_AVS_COAP_OBSERVE is enabled.
+ *
+ * NOTE: LwM2M specification requires LwM2M server to send Cancel Observation
+ * request. Meanwhile CoAP RFC 7641 states that timeout on notification should
+ * cancel it. This setting is to enable both of these behaviors with default
+ * focused on keeping observations in case of bad connectivity.
+ */
+/* #undef WITH_AVS_COAP_OBSERVE_CANCEL_ON_TIMEOUT */
+
/**
* Enable support for observation persistence (avs_coap_observe_persist()
* and avs_coap_observe_restore() calls).
*
- * Only meaningful WITH_AVS_COAP_OBSERVE is enabled.
+ * Only meaningful if WITH_AVS_COAP_OBSERVE is enabled.
*/
#define WITH_AVS_COAP_OBSERVE_PERSISTENCE
diff --git a/include_public/anjay/anjay_config.h.in b/include_public/anjay/anjay_config.h.in
index 517ed789..753bbaf0 100644
--- a/include_public/anjay/anjay_config.h.in
+++ b/include_public/anjay/anjay_config.h.in
@@ -656,6 +656,13 @@
* composite type request.
*/
#cmakedefine ANJAY_WITHOUT_COMPOSITE_OPERATIONS
+
+/**
+ * Enable support for the experimental
+ * anjay_get_server_connection_status() API and related
+ * anjay_server_connection_status_cb_t callback.
+ */
+#cmakedefine ANJAY_WITH_CONN_STATUS_API
/**@}*/
#endif // ANJAY_CONFIG_H
diff --git a/include_public/anjay/core.h b/include_public/anjay/core.h
index 19aa3048..8f440c7e 100644
--- a/include_public/anjay/core.h
+++ b/include_public/anjay/core.h
@@ -100,6 +100,144 @@ typedef struct {
} anjay_lwm2m_version_config_t;
#endif // ANJAY_WITH_LWM2M11
+#ifdef ANJAY_WITH_CONN_STATUS_API
+/**
+ * @experimental This is experimental server connection status API. This API can
+ * change in future versions without any notice.
+ *
+ * This enum represents the possible states of a server connection.
+ */
+typedef enum anjay_server_conn_status {
+ /**
+ * Invalid state. It is only returned if a given server connection object
+ * does not exist, or there was an error in determining its state.
+ */
+ ANJAY_SERV_CONN_STATUS_INVALID,
+ /**
+ * Generic state for any errors for which more specific states are not
+ * defined. For a regular LwM2M Server, this means that the primary
+ * connection is invalid or de-register/update operation has failed. For a
+ * LwM2M Bootstrap Server, it means that the bootstrap process failed.
+ */
+ ANJAY_SERV_CONN_STATUS_ERROR,
+ /**
+ * This status means that the server connection object has been just created
+ * as a result of the Security and Server object instances having been
+ * previously added to the data model. A connection attempt is scheduled to
+ * be made during subsequent calls to @ref anjay_sched_run.
+ */
+ ANJAY_SERV_CONN_STATUS_INITIAL,
+ /**
+ * This status is reported just before attempting the connection procedure,
+ * which may include binding the socket to a local port, performing DNS
+ * resolution, connecting the socket, and performing a (D)TLS handshake.
+ */
+ ANJAY_SERV_CONN_STATUS_CONNECTING,
+ /**
+ * This status means that the bootstrap process is in progress. It only
+ * applies to LwM2M Bootstrap Servers.
+ */
+ ANJAY_SERV_CONN_STATUS_BOOTSTRAPPING,
+ /**
+ * This status means that the bootstrap process has completed successfully.
+ * It only applies to LwM2M Bootstrap Servers.
+ */
+ ANJAY_SERV_CONN_STATUS_BOOTSTRAPPED,
+ /**
+ * This status means that the register operation is in progress. It only
+ * applies to regular LwM2M Servers.
+ */
+ ANJAY_SERV_CONN_STATUS_REGISTERING,
+ /**
+ * This status means that the Anjay is registered to the LwM2M Server. It
+ * only applies to regular LwM2M Servers.
+ */
+ ANJAY_SERV_CONN_STATUS_REGISTERED,
+ /**
+ * This status means that the last register operation has failed due to no
+ * response, a non-success CoAP response was received, or network problems.
+ * It only applies to regular LwM2M Servers.
+ */
+ ANJAY_SERV_CONN_STATUS_REG_FAILURE,
+ /**
+ * This status means that the de-register operation is in progress. It
+ * only applies to regular LwM2M Servers.
+ */
+ ANJAY_SERV_CONN_STATUS_DEREGISTERING,
+ /**
+ * This status means that the the library has de-registered with the LwM2M
+ * Server, either explicitly by successfully performing the De-register
+ * operation, or implicitly by discarding the registration state (which
+ * happens when the server is disabled while offline). It only applies to
+ * regular LwM2M Servers.
+ */
+ ANJAY_SERV_CONN_STATUS_DEREGISTERED,
+ /**
+ * This status means that the /1/x/4 resource has been executed and Anjay
+ * will try to suspend the LwM2M Server. It only applies to regular LwM2M
+ * Servers.
+ */
+ ANJAY_SERV_CONN_STATUS_SUSPENDING,
+ /**
+ * This status means that the suspend process has completed successfully. It
+ * only applies to regular LwM2M Servers.
+ */
+ ANJAY_SERV_CONN_STATUS_SUSPENDED,
+ /**
+ * This status means that the last update operation has failed due to either
+ * no response or a non-success CoAP response was received. It only
+ * applies to regular LwM2M Servers.
+ */
+ ANJAY_SERV_CONN_STATUS_REREGISTERING,
+ /**
+ * This status means that the update operation is in progress. It only
+ * applies to regular LwM2M Servers.
+ */
+ ANJAY_SERV_CONN_STATUS_UPDATING
+} anjay_server_conn_status_t;
+
+/**
+ * @experimental This is experimental server connection status API. This API can
+ * change in future versions without any notice.
+ *
+ * Callback called each time there is a transition of a server connection status
+ * (as listed in @ref anjay_server_conn_status_t ).
+ *
+ * @param arg Opaque argument as set through the @c
+ * server_connection_status_cb_arg field in @ref
+ * anjay_configuration_t
+ *
+ * @param anjay Anjay object that calls this callback
+ *
+ * @param ssid Short Server ID of the server for which the status is reported
+ *
+ * @param status New status of the server connection
+ */
+typedef void
+anjay_server_connection_status_cb_t(void *arg,
+ anjay_t *anjay,
+ anjay_ssid_t ssid,
+ anjay_server_conn_status_t status);
+
+/**
+ * @experimental This is experimental server connection status API. This API can
+ * change in future versions without any notice.
+ *
+ * This function returns the server connection status. Possible statuses are
+ * given in the @ref anjay_server_conn_status_t. Statuses for a LwM2M Bootstrap
+ * Server are different from the statuses for a regular LwM2M Server.
+ *
+ * @param anjay Anjay object to operate on.
+ * @param ssid Short Server ID of the server which status will be returned. @ref
+ * ANJAY_SSID_ANY is not a valid argument.
+ *
+ * @returns Server status on success, in case of error
+ * ANJAY_SERV_CONN_STATUS_UNKNOWN.
+ */
+anjay_server_conn_status_t
+anjay_get_server_connection_status(anjay_t *anjay, anjay_ssid_t ssid);
+#endif // ANJAY_WITH_CONN_STATUS_API
+
typedef struct anjay_configuration {
/**
* Endpoint name as presented to the LwM2M server. Must be non-NULL, or
@@ -285,6 +423,18 @@ typedef struct anjay_configuration {
*/
bool enable_self_notify;
+ /**
+ * Treat failures of the "connect" socket operation (e.g. (D)TLS handshake
+ * failures) as a failed LwM2M Register operation. This enables automatic
+ * retrying of them as described in the "Bootstrap and LwM2M Server
+ * Registration Mechanisms" of LwM2M Core TS 1.1.
+ *
+ * When disabled, such failures are treated as fatal errors and cause the
+ * entire registration sequence for that server to be aborted (which will
+ * trigger a fallback to Bootstrap if applicable).
+ */
+ bool connection_error_is_registration_failure;
+
/**
* (D)TLS ciphersuites to use if the "DTLS/TLS Ciphersuite" Resource
* (/0/x/16) is not available or empty.
@@ -424,6 +574,27 @@ typedef struct anjay_configuration {
bool rebuild_client_cert_chain;
#endif // ANJAY_WITH_LWM2M11
+#ifdef ANJAY_WITH_CONN_STATUS_API
+ /**
+ * @experimental This is experimental server connection status API. This API
+ * can change in future versions without any notice.
+ *
+ * Function called each time there is a transition of a server connection
+ * status (as listed in @ref anjay_server_conn_status_t ).
+ */
+ anjay_server_connection_status_cb_t *server_connection_status_cb;
+
+ /**
+ * @experimental This is experimental server connection status API. This API
+ * can change in future versions without any notice.
+ *
+ * Opaque argument that will be passed to the function configured in the
+ * server_connection_status_cb field.
+ *
+ * If server_connection_status_cb is NULL, this field is ignored.
+ */
+ void *server_connection_status_cb_arg;
+#endif // ANJAY_WITH_CONN_STATUS_API
} anjay_configuration_t;
/**
diff --git a/src/anjay_config_log.h b/src/anjay_config_log.h
index 9294e51a..6f1f609f 100644
--- a/src/anjay_config_log.h
+++ b/src/anjay_config_log.h
@@ -104,6 +104,11 @@ static inline void _anjay_log_feature_list(void) {
#else // ANJAY_WITH_COMMUNICATION_TIMESTAMP_API
_anjay_log(anjay, TRACE, "ANJAY_WITH_COMMUNICATION_TIMESTAMP_API = OFF");
#endif // ANJAY_WITH_COMMUNICATION_TIMESTAMP_API
+#ifdef ANJAY_WITH_CONN_STATUS_API
+ _anjay_log(anjay, TRACE, "ANJAY_WITH_CONN_STATUS_API = ON");
+#else // ANJAY_WITH_CONN_STATUS_API
+ _anjay_log(anjay, TRACE, "ANJAY_WITH_CONN_STATUS_API = OFF");
+#endif // ANJAY_WITH_CONN_STATUS_API
#ifdef ANJAY_WITH_CON_ATTR
_anjay_log(anjay, TRACE, "ANJAY_WITH_CON_ATTR = ON");
#else // ANJAY_WITH_CON_ATTR
@@ -700,6 +705,11 @@ static inline void _anjay_log_feature_list(void) {
#else // WITH_AVS_COAP_OBSERVE
_anjay_log(anjay, TRACE, "WITH_AVS_COAP_OBSERVE = OFF");
#endif // WITH_AVS_COAP_OBSERVE
+#ifdef WITH_AVS_COAP_OBSERVE_CANCEL_ON_TIMEOUT
+ _anjay_log(anjay, TRACE, "WITH_AVS_COAP_OBSERVE_CANCEL_ON_TIMEOUT = ON");
+#else // WITH_AVS_COAP_OBSERVE_CANCEL_ON_TIMEOUT
+ _anjay_log(anjay, TRACE, "WITH_AVS_COAP_OBSERVE_CANCEL_ON_TIMEOUT = OFF");
+#endif // WITH_AVS_COAP_OBSERVE_CANCEL_ON_TIMEOUT
#ifdef WITH_AVS_COAP_OBSERVE_PERSISTENCE
_anjay_log(anjay, TRACE, "WITH_AVS_COAP_OBSERVE_PERSISTENCE = ON");
#else // WITH_AVS_COAP_OBSERVE_PERSISTENCE
diff --git a/src/anjay_modules/anjay_servers.h b/src/anjay_modules/anjay_servers.h
index 78a714ab..a247e0d3 100644
--- a/src/anjay_modules/anjay_servers.h
+++ b/src/anjay_modules/anjay_servers.h
@@ -113,6 +113,15 @@ int _anjay_schedule_disable_server_with_explicit_timeout_unlocked(
*/
int _anjay_enable_server_unlocked(anjay_unlocked_t *anjay, anjay_ssid_t ssid);
+#ifdef ANJAY_WITH_CONN_STATUS_API
+/**
+ * Set suspending flag for the server specified by the ssid argument.
+ */
+void _anjay_set_server_suspending_flag(anjay_unlocked_t *anjay,
+ anjay_ssid_t ssid,
+ bool val);
+#endif // ANJAY_WITH_CONN_STATUS_API
+
VISIBILITY_PRIVATE_HEADER_END
#endif /* ANJAY_INCLUDE_ANJAY_MODULES_SERVERS_H */
diff --git a/src/core/anjay_bootstrap_core.c b/src/core/anjay_bootstrap_core.c
index 0bd10d5f..2982ed2c 100644
--- a/src/core/anjay_bootstrap_core.c
+++ b/src/core/anjay_bootstrap_core.c
@@ -97,6 +97,13 @@ static avs_error_t start_bootstrap_if_not_already_started(
avs_sched_del(&anjay->bootstrap.purge_bootstrap_handle);
}
anjay->bootstrap.in_progress = true;
+# ifdef ANJAY_WITH_CONN_STATUS_API
+ if (bootstrap_connection.server) {
+ _anjay_set_server_connection_status(
+ bootstrap_connection.server,
+ ANJAY_SERV_CONN_STATUS_BOOTSTRAPPING);
+ }
+# endif // ANJAY_WITH_CONN_STATUS_API
return AVS_OK;
}
@@ -705,6 +712,12 @@ static int bootstrap_finish_impl(anjay_unlocked_t *anjay,
anjay_log(
WARNING,
_("Bootstrap configuration could not be committed, rejecting"));
+# ifdef ANJAY_WITH_CONN_STATUS_API
+ if (bootstrap_connection.server) {
+ _anjay_set_server_connection_status(bootstrap_connection.server,
+ ANJAY_SERV_CONN_STATUS_ERROR);
+ }
+# endif // ANJAY_WITH_CONN_STATUS_API
return retval;
}
if ((retval = _anjay_notify_perform_without_servers(
@@ -736,7 +749,20 @@ static int bootstrap_finish_impl(anjay_unlocked_t *anjay,
_anjay_server_on_server_communication_error(
bootstrap_connection.server, err);
}
+# ifdef ANJAY_WITH_CONN_STATUS_API
+ if (avs_is_err(err) && bootstrap_connection.server) {
+ _anjay_set_server_connection_status(bootstrap_connection.server,
+ ANJAY_SERV_CONN_STATUS_ERROR);
+ }
+# endif // ANJAY_WITH_CONN_STATUS_API
} else {
+# ifdef ANJAY_WITH_CONN_STATUS_API
+ if (bootstrap_connection.server) {
+ _anjay_set_server_connection_status(
+ bootstrap_connection.server,
+ ANJAY_SERV_CONN_STATUS_BOOTSTRAPPED);
+ }
+# endif // ANJAY_WITH_CONN_STATUS_API
_anjay_schedule_reload_servers(anjay);
}
return retval;
@@ -783,6 +809,13 @@ int _anjay_bootstrap_notify_regular_connection_available(
BOOTSTRAP_FINISH_DISABLE_SERVER)));
} else {
cancel_client_initiated_bootstrap(anjay);
+# ifdef ANJAY_WITH_CONN_STATUS_API
+ if (bootstrap_connection.server) {
+ _anjay_set_server_connection_status(
+ bootstrap_connection.server,
+ ANJAY_SERV_CONN_STATUS_BOOTSTRAPPED);
+ }
+# endif // ANJAY_WITH_CONN_STATUS_API
}
if (!result) {
reset_client_initiated_bootstrap_backoff(&anjay->bootstrap);
diff --git a/src/core/anjay_core.c b/src/core/anjay_core.c
index 9a69fd25..0be02ee6 100644
--- a/src/core/anjay_core.c
+++ b/src/core/anjay_core.c
@@ -47,7 +47,7 @@
VISIBILITY_SOURCE_BEGIN
#ifndef ANJAY_VERSION
-# define ANJAY_VERSION "3.7.0"
+# define ANJAY_VERSION "3.8.0"
#endif // ANJAY_VERSION
#ifdef ANJAY_WITH_LWM2M11
@@ -127,6 +127,15 @@ static int init_anjay(anjay_unlocked_t *anjay,
return -1;
}
+#ifdef ANJAY_WITH_CONN_STATUS_API
+ anjay->server_connection_status_cb = config->server_connection_status_cb;
+
+ if (anjay->server_connection_status_cb) {
+ anjay->server_connection_status_cb_arg =
+ config->server_connection_status_cb_arg;
+ }
+#endif // ANJAY_WITH_CONN_STATUS_API
+
anjay->socket_config = config->socket_config;
anjay->udp_listen_port = config->udp_listen_port;
@@ -232,6 +241,8 @@ static int init_anjay(anjay_unlocked_t *anjay,
anjay->prefer_hierarchical_formats = config->prefer_hierarchical_formats;
anjay->update_immediately_on_dm_change =
config->update_immediately_on_dm_change;
+ anjay->connection_error_is_registration_failure =
+ config->connection_error_is_registration_failure;
anjay->enable_self_notify = config->enable_self_notify;
anjay->use_connection_id = config->use_connection_id;
anjay->additional_tls_config_clb = config->additional_tls_config_clb;
diff --git a/src/core/anjay_core.h b/src/core/anjay_core.h
index 0c2a3ef1..6129406d 100644
--- a/src/core/anjay_core.h
+++ b/src/core/anjay_core.h
@@ -130,6 +130,11 @@ struct
char *endpoint_name;
anjay_transaction_state_t transaction_state;
+#ifdef ANJAY_WITH_CONN_STATUS_API
+ anjay_server_connection_status_cb_t *server_connection_status_cb;
+ void *server_connection_status_cb_arg;
+#endif // ANJAY_WITH_CONN_STATUS_API
+
#ifdef ANJAY_WITH_SEND
anjay_sender_t sender;
#endif // ANJAY_WITH_SEND
@@ -143,6 +148,7 @@ struct
bool prefer_hierarchical_formats;
bool update_immediately_on_dm_change;
bool enable_self_notify;
+ bool connection_error_is_registration_failure;
#ifdef ANJAY_WITH_NET_STATS
closed_connections_stats_t closed_connections_stats;
#endif // ANJAY_WITH_NET_STATS
diff --git a/src/core/anjay_dm_core.c b/src/core/anjay_dm_core.c
index 96de0e3c..8c93b712 100644
--- a/src/core/anjay_dm_core.c
+++ b/src/core/anjay_dm_core.c
@@ -567,6 +567,14 @@ static int dm_execute(anjay_unlocked_t *anjay,
request->uri.ids[ANJAY_ID_IID],
request->uri.ids[ANJAY_ID_RID],
execute_ctx);
+#ifdef ANJAY_WITH_CONN_STATUS_API
+ /* RID == 4 -> Disable resource */
+ if (!retval && request->uri.ids[ANJAY_ID_OID] == ANJAY_DM_OID_SERVER
+ && request->uri.ids[ANJAY_ID_RID]
+ == ANJAY_DM_RID_SERVER_DISABLE) {
+ _anjay_set_server_suspending_flag(anjay, ssid, true);
+ }
+#endif // ANJAY_WITH_CONN_STATUS_API
_anjay_execute_ctx_destroy(&execute_ctx);
}
return retval;
diff --git a/src/core/anjay_servers_private.h b/src/core/anjay_servers_private.h
index 29987bb6..3beebe77 100644
--- a/src/core/anjay_servers_private.h
+++ b/src/core/anjay_servers_private.h
@@ -536,6 +536,24 @@ _anjay_connection_transport(anjay_connection_ref_t conn_ref);
AVS_LIST(const anjay_socket_entry_t)
_anjay_collect_socket_entries(anjay_unlocked_t *anjay, bool include_offline);
+#ifdef ANJAY_WITH_CONN_STATUS_API
+/**
+ * Set connection status for the server specified by the server argument. This
+ * function supports LwM2M bootstrap and regular LwM2M servers.
+ */
+void _anjay_set_server_connection_status(anjay_server_info_t *server,
+ anjay_server_conn_status_t new_status);
+
+/**
+ * Check the server parameters and set one of the following server states:
+ * ANJAY_SERV_CONN_STATUS_UNKNOWN, ANJAY_SERV_CONN_STATUS_REGISTERING,
+ * ANJAY_SERV_CONN_STATUS_REREGISTERING, ANJAY_SERV_CONN_STATUS_REGISTERED or
+ * ANJAY_SERV_CONN_STATUS_UPDATING. This function supports only regular LwM2M
+ * servers.
+ */
+void _anjay_check_server_connection_status(anjay_server_info_t *server);
+#endif // ANJAY_WITH_CONN_STATUS_API
+
VISIBILITY_PRIVATE_HEADER_END
#endif // ANJAY_SERVERS_PRIVATE_H
diff --git a/src/core/io/anjay_base64_out.c b/src/core/io/anjay_base64_out.c
index 660a5007..da39b082 100644
--- a/src/core/io/anjay_base64_out.c
+++ b/src/core/io/anjay_base64_out.c
@@ -38,12 +38,8 @@ static int base64_ret_encode_and_write(base64_ret_bytes_ctx_t *ctx,
return 0;
}
char encoded[4 * (TEXT_CHUNK_SIZE / 3) + 1];
- size_t encoded_size;
- if (ctx->config.padding_char) {
- encoded_size = avs_base64_encoded_size(buffer_size);
- } else {
- encoded_size = avs_base64_encoded_size_without_padding(buffer_size);
- }
+ size_t encoded_size =
+ avs_base64_encoded_size_custom(buffer_size, ctx->config);
assert(encoded_size <= sizeof(encoded));
int retval = avs_base64_encode_custom(encoded, encoded_size, buffer,
buffer_size, ctx->config);
diff --git a/src/core/io/anjay_json_encoder.c b/src/core/io/anjay_json_encoder.c
index c234dbe0..4c9095e0 100644
--- a/src/core/io/anjay_json_encoder.c
+++ b/src/core/io/anjay_json_encoder.c
@@ -504,7 +504,8 @@ _anjay_senml_json_encoder_new(avs_stream_t *stream) {
.alphabet = AVS_BASE64_URL_SAFE_CHARS,
.padding_char = '\0',
.allow_whitespace = false,
- .require_padding = false
+ .require_padding = false,
+ .without_null_termination = false,
};
json_encoder_t *ctx = json_encoder_new(stream, &SENML_JSON_ENCODER_VTABLE,
senml_encode_key, BASE64_CONFIG);
diff --git a/src/core/observe/anjay_observe_core.c b/src/core/observe/anjay_observe_core.c
index e068106d..5f97c7fa 100644
--- a/src/core/observe/anjay_observe_core.c
+++ b/src/core/observe/anjay_observe_core.c
@@ -860,6 +860,31 @@ delete_observation(AVS_LIST(anjay_observe_connection_entry_t) *conn_ptr,
}
}
+static void
+recalculate_conn_trigger_times(anjay_observe_connection_entry_t *conn) {
+ avs_time_monotonic_t monotonic_now = avs_time_monotonic_now();
+ avs_time_real_t real_now = avs_time_real_now();
+ conn->next_trigger = AVS_TIME_REAL_INVALID;
+ conn->next_pmax_trigger = AVS_TIME_REAL_INVALID;
+ AVS_SORTED_SET_ELEM(anjay_observation_t) observation;
+ AVS_SORTED_SET_FOREACH(observation, conn->observations) {
+ if (avs_time_real_valid(observation->next_pmax_trigger)
+ && !avs_time_real_before(conn->next_pmax_trigger,
+ observation->next_pmax_trigger)) {
+ conn->next_pmax_trigger = observation->next_pmax_trigger;
+ }
+ avs_time_real_t next_trigger = avs_time_real_add(
+ real_now,
+ avs_time_monotonic_diff(avs_sched_time(
+ &observation->notify_task),
+ monotonic_now));
+ if (avs_time_real_valid(next_trigger)
+ && !avs_time_real_before(conn->next_trigger, next_trigger)) {
+ conn->next_trigger = next_trigger;
+ }
+ }
+}
+
static void observe_remove_entry(anjay_connection_ref_t connection,
const avs_coap_token_t *token) {
AVS_LIST(anjay_observe_connection_entry_t) *conn_ptr =
@@ -874,6 +899,9 @@ static void observe_remove_entry(anjay_connection_ref_t connection,
if (observation) {
delete_observation(conn_ptr, &observation);
}
+ if (*conn_ptr) {
+ recalculate_conn_trigger_times(*conn_ptr);
+ }
}
void _anjay_observe_cancel_handler(avs_coap_observe_id_t id, void *ref_ptr) {
@@ -1458,31 +1486,6 @@ static void remove_all_unsent_values(anjay_observe_connection_entry_t *conn) {
}
}
-static void
-recalculate_conn_trigger_times(anjay_observe_connection_entry_t *conn) {
- avs_time_monotonic_t monotonic_now = avs_time_monotonic_now();
- avs_time_real_t real_now = avs_time_real_now();
- conn->next_trigger = AVS_TIME_REAL_INVALID;
- conn->next_pmax_trigger = AVS_TIME_REAL_INVALID;
- AVS_SORTED_SET_ELEM(anjay_observation_t) observation;
- AVS_SORTED_SET_FOREACH(observation, conn->observations) {
- if (avs_time_real_valid(observation->next_pmax_trigger)
- && !avs_time_real_before(conn->next_pmax_trigger,
- observation->next_pmax_trigger)) {
- conn->next_pmax_trigger = observation->next_pmax_trigger;
- }
- avs_time_real_t next_trigger = avs_time_real_add(
- real_now,
- avs_time_monotonic_diff(avs_sched_time(
- &observation->notify_task),
- monotonic_now));
- if (avs_time_real_valid(next_trigger)
- && !avs_time_real_before(conn->next_trigger, next_trigger)) {
- conn->next_trigger = next_trigger;
- }
- }
-}
-
static bool connection_exists(anjay_unlocked_t *anjay,
anjay_observe_connection_entry_t *conn) {
AVS_LIST(anjay_observe_connection_entry_t) *conn_ptr =
diff --git a/src/core/servers/anjay_activate.c b/src/core/servers/anjay_activate.c
index f6803141..b9dd35a0 100644
--- a/src/core/servers/anjay_activate.c
+++ b/src/core/servers/anjay_activate.c
@@ -37,6 +37,12 @@ VISIBILITY_SOURCE_BEGIN
*/
static int deactivate_server(anjay_server_info_t *server) {
assert(server);
+#ifdef ANJAY_WITH_CONN_STATUS_API
+ if (server->suspending) {
+ _anjay_set_server_connection_status(server,
+ ANJAY_SERV_CONN_STATUS_SUSPENDING);
+ }
+#endif // ANJAY_WITH_CONN_STATUS_API
#ifndef ANJAY_WITHOUT_DEREGISTER
if (server->ssid != ANJAY_SSID_BOOTSTRAP
&& !_anjay_bootstrap_in_progress(server->anjay)) {
@@ -47,6 +53,19 @@ static int deactivate_server(anjay_server_info_t *server) {
// optional anyway. _anjay_serve_deregister logs the error cause.
_anjay_server_deregister(server);
}
+# ifdef ANJAY_WITH_CONN_STATUS_API
+ else if (server->connection_status != ANJAY_SERV_CONN_STATUS_ERROR
+ && server->connection_status != ANJAY_SERV_CONN_STATUS_INITIAL
+ && server->connection_status
+ != ANJAY_SERV_CONN_STATUS_REG_FAILURE
+ && server->connection_status
+ != ANJAY_SERV_CONN_STATUS_SUSPENDED
+ && !avs_time_real_before(server->reactivate_time,
+ avs_time_real_now())) {
+ _anjay_set_server_connection_status(
+ server, ANJAY_SERV_CONN_STATUS_DEREGISTERED);
+ }
+# endif // ANJAY_WITH_CONN_STATUS_API
}
#endif // ANJAY_WITHOUT_DEREGISTER
_anjay_server_clean_active_data(server);
@@ -60,6 +79,10 @@ static int deactivate_server(anjay_server_info_t *server) {
&& _anjay_server_sched_activate(server)) {
// not much we can do other than removing the server altogether
anjay_log(ERROR, _("could not reschedule server reactivation"));
+#ifdef ANJAY_WITH_CONN_STATUS_API
+ _anjay_check_server_connection_status(server);
+ _anjay_set_server_suspending_flag(server->anjay, server->ssid, false);
+#endif // ANJAY_WITH_CONN_STATUS_API
AVS_LIST(anjay_server_info_t) *server_ptr =
_anjay_servers_find_ptr(&server->anjay->servers, server->ssid);
assert(server_ptr);
@@ -67,6 +90,13 @@ static int deactivate_server(anjay_server_info_t *server) {
AVS_LIST_DELETE(server_ptr);
return -1;
}
+#ifdef ANJAY_WITH_CONN_STATUS_API
+ if (server->suspending) {
+ _anjay_set_server_connection_status(server,
+ ANJAY_SERV_CONN_STATUS_SUSPENDED);
+ _anjay_set_server_suspending_flag(server->anjay, server->ssid, false);
+ }
+#endif // ANJAY_WITH_CONN_STATUS_API
return 0;
}
@@ -138,6 +168,10 @@ void _anjay_server_on_failure(anjay_server_info_t *server,
debug_msg);
// Abort any further bootstrap retries.
_anjay_bootstrap_cleanup(server->anjay);
+#ifdef ANJAY_WITH_CONN_STATUS_API
+ _anjay_set_server_connection_status(server,
+ ANJAY_SERV_CONN_STATUS_ERROR);
+#endif // ANJAY_WITH_CONN_STATUS_API
} else {
#ifdef ANJAY_WITH_LWM2M11
if (server->registration_attempts > 0) {
@@ -196,6 +230,12 @@ void _anjay_server_on_failure(anjay_server_info_t *server,
anjay_log(DEBUG,
_("Non-Bootstrap Server ") "%" PRIu16 _(": ") "%s" _("."),
server->ssid, debug_msg);
+#ifdef ANJAY_WITH_CONN_STATUS_API
+ if (server->connection_status != ANJAY_SERV_CONN_STATUS_REG_FAILURE) {
+ _anjay_set_server_connection_status(server,
+ ANJAY_SERV_CONN_STATUS_ERROR);
+ }
+#endif // ANJAY_WITH_CONN_STATUS_API
(void) _anjay_perform_bootstrap_action_if_appropriate(
server->anjay,
_anjay_servers_find_active(server->anjay, ANJAY_SSID_BOOTSTRAP),
@@ -239,6 +279,12 @@ void _anjay_server_on_server_communication_timeout(
server->anjay, server->ssid, AVS_TIME_DURATION_ZERO)) {
server->refresh_failed = true;
} else {
+#ifdef ANJAY_WITH_CONN_STATUS_API
+ if (server->ssid != ANJAY_SSID_BOOTSTRAP) {
+ _anjay_set_server_connection_status(
+ server, ANJAY_SERV_CONN_STATUS_REG_FAILURE);
+ }
+#endif // ANJAY_WITH_CONN_STATUS_API
_anjay_server_on_server_communication_error(server,
avs_errno(AVS_EBADF));
}
@@ -276,6 +322,30 @@ void _anjay_server_on_refreshed(anjay_server_info_t *server,
anjay_log(TRACE,
_("could not initialize sockets for SSID ") "%" PRIu16,
server->ssid);
+ if (server->ssid != ANJAY_SSID_BOOTSTRAP) {
+ if (server->anjay->connection_error_is_registration_failure) {
+ anjay_connection_type_t conn_type;
+ ANJAY_CONNECTION_TYPE_FOREACH(conn_type) {
+ _anjay_observe_invalidate((anjay_connection_ref_t) {
+ .server = server,
+ .conn_type = conn_type
+ });
+ }
+ ++server->registration_attempts;
+ server->registration_info.expire_time =
+ AVS_TIME_REAL_INVALID;
+ server->registration_info.update_forced = false;
+ // defined(ANJAY_WITH_CORE_PERSISTENCE)
+#ifdef ANJAY_WITH_CONN_STATUS_API
+ _anjay_set_server_connection_status(
+ server, ANJAY_SERV_CONN_STATUS_REG_FAILURE);
+#endif // ANJAY_WITH_CONN_STATUS_API
+ } else {
+ // Interrupt any registration flow that may be in progress
+ server->registration_attempts = 0;
+ server->registration_sequences_performed = 0;
+ }
+ }
_anjay_server_on_server_communication_error(server, err);
} else if (_anjay_socket_transport_supported(server->anjay,
primary_conn->transport)
@@ -310,6 +380,20 @@ void _anjay_server_on_refreshed(anjay_server_info_t *server,
}
// _anjay_bootstrap_request_if_appropriate() may fail only due to
// failure to schedule a job. Not much that we can do about it then.
+#ifdef ANJAY_WITH_CONN_STATUS_API
+ if (server->connection_status == ANJAY_SERV_CONN_STATUS_CONNECTING) {
+ if (server->refresh_failed) {
+ _anjay_set_server_connection_status(
+ server, ANJAY_SERV_CONN_STATUS_ERROR);
+ } else if (action == ANJAY_BOOTSTRAP_ACTION_NONE
+ && !avs_coap_exchange_id_valid(
+ server->anjay->bootstrap
+ .outgoing_request_exchange_id)) {
+ _anjay_set_server_connection_status(
+ server, ANJAY_SERV_CONN_STATUS_BOOTSTRAPPED);
+ }
+ }
+#endif // ANJAY_WITH_CONN_STATUS_API
} else {
assert(avs_is_ok(err));
_anjay_server_ensure_valid_registration(server);
@@ -319,6 +403,17 @@ void _anjay_server_on_refreshed(anjay_server_info_t *server,
void _anjay_server_on_updated_registration(anjay_server_info_t *server,
anjay_registration_result_t result,
avs_error_t err) {
+#ifdef ANJAY_WITH_CONN_STATUS_API
+ if ((result == ANJAY_REGISTRATION_ERROR_NETWORK
+ || result == ANJAY_REGISTRATION_ERROR_OTHER)
+ && server->connection_status
+ != ANJAY_SERV_CONN_STATUS_REG_FAILURE) {
+ _anjay_set_server_connection_status(server,
+ ANJAY_SERV_CONN_STATUS_ERROR);
+ } else {
+ _anjay_check_server_connection_status(server);
+ }
+#endif // ANJAY_WITH_CONN_STATUS_API
if (result == ANJAY_REGISTRATION_SUCCESS) {
if (_anjay_server_reschedule_update_job(server)) {
// Updates are retryable, we only need to reschedule after success
@@ -626,6 +721,12 @@ static int disable_server_impl(anjay_unlocked_t *anjay,
int anjay_disable_server(anjay_t *anjay_locked, anjay_ssid_t ssid) {
int result = -1;
ANJAY_MUTEX_LOCK(anjay, anjay_locked);
+#ifdef ANJAY_WITH_CONN_STATUS_API
+ anjay_server_info_t *server = _anjay_servers_find(anjay, ssid);
+ if (server) {
+ _anjay_set_server_suspending_flag(server->anjay, server->ssid, true);
+ }
+#endif // ANJAY_WITH_CONN_STATUS_API
result = disable_server_impl(
anjay, ssid, ANJAY_SERVER_NEXT_ACTION_DISABLE_WITH_TIMEOUT_FROM_DM,
"ANJAY_SERVER_NEXT_ACTION_DISABLE_WITH_TIMEOUT_FROM_DM",
@@ -667,6 +768,12 @@ int anjay_disable_server_with_timeout(anjay_t *anjay_locked,
avs_time_duration_t timeout) {
int result = -1;
ANJAY_MUTEX_LOCK(anjay, anjay_locked);
+#ifdef ANJAY_WITH_CONN_STATUS_API
+ anjay_server_info_t *server = _anjay_servers_find(anjay, ssid);
+ if (server) {
+ _anjay_set_server_suspending_flag(server->anjay, server->ssid, true);
+ }
+#endif // ANJAY_WITH_CONN_STATUS_API
result = _anjay_schedule_disable_server_with_explicit_timeout_unlocked(
anjay, ssid, timeout);
ANJAY_MUTEX_UNLOCK(anjay_locked);
diff --git a/src/core/servers/anjay_connections.c b/src/core/servers/anjay_connections.c
index b6709780..6e14b1a7 100644
--- a/src/core/servers/anjay_connections.c
+++ b/src/core/servers/anjay_connections.c
@@ -43,6 +43,10 @@
#include "anjay_security.h"
#include "anjay_server_connections.h"
+#ifdef ANJAY_WITH_CONN_STATUS_API
+# include "../anjay_servers_inactive.h"
+#endif // ANJAY_WITH_CONN_STATUS_API
+
VISIBILITY_SOURCE_BEGIN
avs_net_socket_t *_anjay_connection_internal_get_socket(
@@ -371,6 +375,14 @@ avs_error_t _anjay_server_connection_internal_bring_online(
return AVS_OK;
}
+#ifdef ANJAY_WITH_CONN_STATUS_API
+ if (conn_type == ANJAY_CONNECTION_PRIMARY) {
+ _anjay_set_server_connection_status(server,
+ ANJAY_SERV_CONN_STATUS_CONNECTING);
+ }
+#endif // ANJAY_WITH_CONN_STATUS_API
+ // defined(ANJAY_WITH_CORE_PERSISTENCE)
+
bool session_resumed;
avs_error_t err = AVS_OK;
if (avs_is_err((err = def->connect_socket(server->anjay, connection)))) {
@@ -735,6 +747,92 @@ bool _anjay_socket_transport_supported(anjay_unlocked_t *anjay,
return true;
}
+#ifdef ANJAY_WITH_CONN_STATUS_API
+void _anjay_set_server_suspending_flag(anjay_unlocked_t *anjay,
+ anjay_ssid_t ssid,
+ bool val) {
+ assert(ssid != ANJAY_SSID_ANY);
+ anjay_server_info_t *server = _anjay_servers_find(anjay, ssid);
+ if (!server) {
+ return;
+ }
+ server->suspending = val;
+}
+
+void _anjay_set_server_connection_status(
+ anjay_server_info_t *server, anjay_server_conn_status_t new_status) {
+ if (new_status != server->connection_status) {
+ server->connection_status = new_status;
+ if (server->anjay->server_connection_status_cb) {
+ ANJAY_MUTEX_UNLOCK_FOR_CALLBACK(anjay_locked, server->anjay);
+ server->anjay->server_connection_status_cb(
+ server->anjay->server_connection_status_cb_arg,
+ anjay_locked, server->ssid, new_status);
+ ANJAY_MUTEX_LOCK_AFTER_CALLBACK(anjay_locked);
+ }
+ }
+
+ if (new_status == ANJAY_SERV_CONN_STATUS_REGISTERED
+ || new_status == ANJAY_SERV_CONN_STATUS_INVALID
+ || new_status == ANJAY_SERV_CONN_STATUS_INITIAL
+ || new_status == ANJAY_SERV_CONN_STATUS_ERROR
+ || new_status == ANJAY_SERV_CONN_STATUS_DEREGISTERING
+ || new_status == ANJAY_SERV_CONN_STATUS_SUSPENDING
+ || new_status == ANJAY_SERV_CONN_STATUS_UPDATING) {
+ server->reregistration = false;
+ }
+}
+
+void _anjay_check_server_connection_status(anjay_server_info_t *server) {
+ if (!server || server->ssid == ANJAY_SSID_BOOTSTRAP) {
+ return;
+ }
+ assert(server->ssid != ANJAY_SSID_ANY);
+
+ anjay_server_conn_status_t current_status = server->connection_status;
+
+ if (_anjay_server_connection_active((anjay_connection_ref_t) {
+ .server = server,
+ .conn_type = ANJAY_CONNECTION_PRIMARY
+ })) {
+ /* expired mean that we aren't registered or lifetime is exceeded */
+ if (!_anjay_server_registration_expired(server)) {
+ if (avs_coap_exchange_id_valid(
+ server->registration_exchange_state.exchange_id)) {
+ current_status = ANJAY_SERV_CONN_STATUS_UPDATING;
+ } else {
+ current_status = ANJAY_SERV_CONN_STATUS_REGISTERED;
+ }
+ } else if (avs_coap_exchange_id_valid(
+ server->registration_exchange_state.exchange_id)) {
+ current_status = server->reregistration
+ ? ANJAY_SERV_CONN_STATUS_REREGISTERING
+ : ANJAY_SERV_CONN_STATUS_REGISTERING;
+ }
+ } else {
+ // In the contexts in which this function is called,
+ // this could only be an error
+ current_status = ANJAY_SERV_CONN_STATUS_ERROR;
+ }
+ _anjay_set_server_connection_status(server, current_status);
+}
+
+anjay_server_conn_status_t
+anjay_get_server_connection_status(anjay_t *anjay, anjay_ssid_t ssid) {
+ if (ssid == ANJAY_SSID_ANY) {
+ return ANJAY_SERV_CONN_STATUS_INVALID;
+ }
+ anjay_server_conn_status_t ret = ANJAY_SERV_CONN_STATUS_INVALID;
+ ANJAY_MUTEX_LOCK(anjay_unlocked, anjay);
+ anjay_server_info_t *server = _anjay_servers_find(anjay_unlocked, ssid);
+ if (server) {
+ ret = server->connection_status;
+ }
+ ANJAY_MUTEX_UNLOCK(anjay);
+ return ret;
+}
+#endif // ANJAY_WITH_CONN_STATUS_API
+
#ifdef ANJAY_WITH_LWM2M11
avs_error_t _anjay_server_read_sni(anjay_unlocked_t *anjay,
anjay_iid_t security_iid,
diff --git a/src/core/servers/anjay_register.c b/src/core/servers/anjay_register.c
index 0eee039c..31af1abb 100644
--- a/src/core/servers/anjay_register.c
+++ b/src/core/servers/anjay_register.c
@@ -33,6 +33,8 @@
#include "anjay_server_connections.h"
#include "anjay_servers_internal.h"
+// !defined(ANJAY_WITH_CONN_STATUS_API)
+
VISIBILITY_SOURCE_BEGIN
/** Update messages are sent to the server every
@@ -641,7 +643,13 @@ handle_register_response(anjay_server_info_t *server,
if (result != ANJAY_REGISTRATION_SUCCESS) {
anjay_log(WARNING, _("could not register to server ") "%u",
_anjay_server_ssid(server));
- AVS_LIST_CLEAR(move_endpoint_path) {}
+ AVS_LIST_CLEAR(move_endpoint_path);
+#ifdef ANJAY_WITH_CONN_STATUS_API
+ if (result != ANJAY_REGISTRATION_ERROR_TIMEOUT) {
+ _anjay_set_server_connection_status(
+ server, ANJAY_SERV_CONN_STATUS_REG_FAILURE);
+ }
+#endif // ANJAY_WITH_CONN_STATUS_API
} else {
_anjay_server_update_registration_info(
server, move_endpoint_path, attempted_version,
@@ -814,6 +822,9 @@ static void send_register(anjay_server_info_t *server,
_anjay_server_set_last_communication_time(server);
#endif // ANJAY_WITH_COMMUNICATION_TIMESTAMP_API
}
+#ifdef ANJAY_WITH_CONN_STATUS_API
+ _anjay_check_server_connection_status(server);
+#endif // ANJAY_WITH_CONN_STATUS_API
cleanup:
avs_coap_options_cleanup(&request.options);
}
@@ -1033,6 +1044,12 @@ receive_update_response(avs_coap_ctx_t *coap,
server->registration_info.update_forced = true;
return;
}
+#ifdef ANJAY_WITH_CONN_STATUS_API
+ if (result == ANJAY_REGISTRATION_ERROR_TIMEOUT
+ || result == ANJAY_REGISTRATION_ERROR_REJECTED) {
+ server->reregistration = true;
+ }
+#endif // ANJAY_WITH_CONN_STATUS_API
on_registration_update_result(server, &state->new_params, result, err);
}
@@ -1097,6 +1114,9 @@ static void send_update(anjay_server_info_t *server,
_anjay_server_set_last_communication_time(server);
#endif // ANJAY_WITH_COMMUNICATION_TIMESTAMP_API
}
+#ifdef ANJAY_WITH_CONN_STATUS_API
+ _anjay_check_server_connection_status(server);
+#endif // ANJAY_WITH_CONN_STATUS_API
end:
avs_coap_options_cleanup(&request.options);
}
@@ -1232,6 +1252,11 @@ static avs_error_t deregister(anjay_server_info_t *server) {
AVS_ASSERT(coap, "Register is not supposed to be called on a connection "
"that has no CoAP context");
+# ifdef ANJAY_WITH_CONN_STATUS_API
+ _anjay_set_server_connection_status(server,
+ ANJAY_SERV_CONN_STATUS_DEREGISTERING);
+# endif // ANJAY_WITH_CONN_STATUS_API
+
avs_coap_request_header_t request = { 0 };
avs_coap_response_header_t response = { 0 };
avs_error_t err =
@@ -1255,6 +1280,10 @@ static avs_error_t deregister(anjay_server_info_t *server) {
err = avs_errno(AVS_EPROTO);
goto end;
}
+# ifdef ANJAY_WITH_CONN_STATUS_API
+ _anjay_set_server_connection_status(server,
+ ANJAY_SERV_CONN_STATUS_DEREGISTERED);
+# endif // ANJAY_WITH_CONN_STATUS_API
anjay_log(INFO, _("De-register sent"));
err = AVS_OK;
@@ -1280,11 +1309,19 @@ avs_error_t _anjay_server_deregister(anjay_server_info_t *server) {
};
if (!_anjay_connection_get_online_socket(connection)) {
anjay_log(ERROR, _("server connection is not online, skipping"));
+# ifdef ANJAY_WITH_CONN_STATUS_API
+ _anjay_set_server_connection_status(
+ server, ANJAY_SERV_CONN_STATUS_DEREGISTERED);
+# endif // ANJAY_WITH_CONN_STATUS_API
return AVS_OK;
}
avs_error_t err = deregister(server);
if (avs_is_err(err)) {
+# ifdef ANJAY_WITH_CONN_STATUS_API
+ _anjay_set_server_connection_status(server,
+ ANJAY_SERV_CONN_STATUS_ERROR);
+# endif // ANJAY_WITH_CONN_STATUS_API
anjay_log(ERROR, _("could not send De-Register request: ") "%s",
AVS_COAP_STRERROR(err));
}
diff --git a/src/core/servers/anjay_reload.c b/src/core/servers/anjay_reload.c
index 6c5bedf0..377d42a1 100644
--- a/src/core/servers/anjay_reload.c
+++ b/src/core/servers/anjay_reload.c
@@ -62,6 +62,10 @@ static int reload_server_by_ssid(anjay_unlocked_t *anjay,
new_server->reactivate_time = avs_time_real_now();
result = _anjay_server_sched_activate(new_server);
}
+#ifdef ANJAY_WITH_CONN_STATUS_API
+ _anjay_set_server_connection_status(new_server,
+ ANJAY_SERV_CONN_STATUS_INITIAL);
+#endif // ANJAY_WITH_CONN_STATUS_API
return result;
}
diff --git a/src/core/servers/anjay_servers_internal.h b/src/core/servers/anjay_servers_internal.h
index d3ed3de1..346c84b9 100644
--- a/src/core/servers/anjay_servers_internal.h
+++ b/src/core/servers/anjay_servers_internal.h
@@ -189,9 +189,12 @@ struct anjay_server_info_struct {
/**
* Number of attempted (potentially) failed registrations. It is incremented
- * in send_register(), then compared (if non-zero) against "Communication
- * Retry Count" resource in _anjay_server_on_failure(). When the
- * registration succeeds, it is reset to 0.
+ * in send_register() (and also _anjay_server_on_refreshed() in case of
+ * connection error if connection_error_is_registration_failure is enabled),
+ * then compared (if non-zero) against "Communication Retry Count" resource
+ * in _anjay_server_on_failure(). When the registration succeeds or in case
+ * of a connection error (when connection_error_is_registration_failure is
+ * disabled), it is reset to 0.
*/
uint32_t registration_attempts;
@@ -208,6 +211,31 @@ struct anjay_server_info_struct {
*/
avs_time_real_t last_communication_time;
#endif // ANJAY_WITH_COMMUNICATION_TIMESTAMP_API
+
+#ifdef ANJAY_WITH_CONN_STATUS_API
+ /**
+ * Stores current server connection status.
+ */
+ anjay_server_conn_status_t connection_status;
+
+ /**
+ * Indicates that the server is in the process of suspending. It is checked
+ * by the ANJAY_SERVER_NEXT_ACTION_DISABLE_WITH_EXPLICIT_TIMEOUT and the
+ * ANJAY_SERVER_NEXT_ACTION_DISABLE_WITH_TIMEOUT_FROM_DM actions to
+ * see if it was called due to /1/x/4 resource execution, function
+ * anjay_disable_server call or function anjay_disable_server_with_timeout
+ * call.
+ */
+ bool suspending;
+
+ /**
+ * Indicates that re-registration will be carried out due to an error
+ * (ANJAY_REGISTRATION_ERROR_TIMEOUT or ANJAY_REGISTRATION_ERROR_REJECTED)
+ * during update operation. If the reregistration process fails, this flag
+ * keeps its value.
+ */
+ bool reregistration;
+#endif // ANJAY_WITH_CONN_STATUS_API
};
#ifndef ANJAY_WITHOUT_DEREGISTER
diff --git a/tests/integration/suites/default/notifications.py b/tests/integration/suites/default/notifications.py
index 4a48c0b7..ae29da2d 100644
--- a/tests/integration/suites/default/notifications.py
+++ b/tests/integration/suites/default/notifications.py
@@ -224,3 +224,57 @@ def runTest(self):
notif = self.serv.recv(timeout_s=5)
self.assertEqual(notif.code, coap.Code.RES_NOT_FOUND)
self.assertEqual(notif.token, orig_notif.token)
+
+
+class NextPlannedNotify(test_suite.Lwm2mSingleServerTest, test_suite.Lwm2mDmOperations):
+ def setUp(self):
+ super().setUp()
+
+ def runTest(self):
+ # set up observe without attributes
+ self.observe(self.serv, oid=OID.Device, iid=0, rid=RID.Device.ErrorCode)
+
+ # ensure there is no planned notify
+ self.communicate('next-planned-pmax-notify',
+ match_regex='NEXT_PLANNED_PMAX_NOTIFY=TIME_INVALID\n')
+ self.communicate('next-planned-notify',
+ match_regex='NEXT_PLANNED_NOTIFY=TIME_INVALID\n')
+
+ # add an observation with pmin&pmax
+ write_atts = self.write_attributes(self.serv, oid=OID.Device, iid=0, rid=RID.Device.Timezone,
+ query=['pmin=5','pmax=5'])
+ self.assertEqual(write_atts.code, coap.Code.RES_CHANGED)
+
+ second_notif = self.observe(self.serv, oid=OID.Device, iid=0, rid=RID.Device.Timezone)
+ self.assertEqual(second_notif.code, coap.Code.RES_CONTENT)
+
+ expected_notify_time = time.time() + 5.0
+
+ time.sleep(0.1)
+
+ # verify that Anjay schedules the notifications
+ planned_notify_time = float(
+ self.communicate('next-planned-notify',
+ match_regex='NEXT_PLANNED_NOTIFY=(.*)\n').group(1))
+ self.assertAlmostEqual(expected_notify_time, planned_notify_time, delta=0.1)
+
+ # wait for the notification and check if next-planned-notify was rescheduled
+ notif = self.serv.recv(timeout_s=6)
+ self.assertEqual(notif.code, coap.Code.RES_CONTENT)
+ self.assertEqual(notif.token, second_notif.token)
+
+ expected_notify_time = expected_notify_time + 5
+ planned_notify_time = float(
+ self.communicate('next-planned-notify',
+ match_regex='NEXT_PLANNED_NOTIFY=(.*)\n').group(1))
+ self.assertAlmostEqual(expected_notify_time, planned_notify_time, delta=0.1)
+
+ # cancel the observation
+ self.observe(self.serv, oid=OID.Device, iid=0, rid=RID.Device.Timezone, observe=1, token=notif.token)
+ time.sleep(0.2)
+
+ # ensure there is no planned notify
+ self.communicate('next-planned-pmax-notify',
+ match_regex='NEXT_PLANNED_PMAX_NOTIFY=TIME_INVALID\n')
+ self.communicate('next-planned-notify',
+ match_regex='NEXT_PLANNED_NOTIFY=TIME_INVALID\n')
diff --git a/tests/integration/suites/default/retransmissions.py b/tests/integration/suites/default/retransmissions.py
index a327587e..71e8e3f3 100644
--- a/tests/integration/suites/default/retransmissions.py
+++ b/tests/integration/suites/default/retransmissions.py
@@ -22,10 +22,10 @@ class TestMixin:
# Note that these values differ from default ones in Anjay. This is done to
# speed up the test execution a bit by limiting the number of retransmissions
# as well as wait intervals between them.
- ACK_RANDOM_FACTOR=1.0
- ACK_TIMEOUT=2.0
- MAX_RETRANSMIT=2
- CONFIRMABLE_NOTIFICATIONS=False
+ ACK_RANDOM_FACTOR = 1.0
+ ACK_TIMEOUT = 2.0
+ MAX_RETRANSMIT = 2
+ CONFIRMABLE_NOTIFICATIONS = False
def tx_params(self):
return TxParams(ack_random_factor=self.ACK_RANDOM_FACTOR,
@@ -33,17 +33,20 @@ def tx_params(self):
max_retransmit=self.MAX_RETRANSMIT)
def setUp(self, *args, **kwargs):
- extra_cmdline_args=[
+ extra_cmdline_args = [
'--ack-random-factor', str(self.tx_params().ack_random_factor),
'--ack-timeout', str(self.tx_params().ack_timeout),
'--max-retransmit', str(self.tx_params().max_retransmit),
- '--dtls-hs-retry-wait-min', str(self.tx_params().first_retransmission_timeout()),
- '--dtls-hs-retry-wait-max', str(self.tx_params().last_retransmission_timeout()),
+ '--dtls-hs-retry-wait-min', str(
+ self.tx_params().first_retransmission_timeout()),
+ '--dtls-hs-retry-wait-max', str(
+ self.tx_params().last_retransmission_timeout()),
]
if self.CONFIRMABLE_NOTIFICATIONS:
extra_cmdline_args += ['--confirmable-notifications']
if 'extra_cmdline_args' in kwargs:
- kwargs['extra_cmdline_args'] = extra_cmdline_args + kwargs['extra_cmdline_args']
+ kwargs['extra_cmdline_args'] = extra_cmdline_args + \
+ kwargs['extra_cmdline_args']
super().setUp(*args, **kwargs)
else:
super().setUp(*args, **kwargs, extra_cmdline_args=extra_cmdline_args)
@@ -98,7 +101,8 @@ def tearDown(self):
def runTest(self):
for i in range(self.MAX_RETRANSMIT + 1):
- self.assertPktIsDtlsClientHello(self.serv._raw_udp_socket.recv(4096), seq_number=i)
+ self.assertPktIsDtlsClientHello(
+ self.serv._raw_udp_socket.recv(4096), seq_number=i)
self.wait_for_retransmission_response_timeout()
# Ensure that the control is given back to the user.
@@ -108,8 +112,8 @@ def runTest(self):
class DtlsRegisterTimeoutFallbacksToHsTest(RetransmissionTest.TestMixin,
test_suite.Lwm2mDtlsSingleServerTest):
# These settings speed up tests considerably.
- MAX_RETRANSMIT=1
- ACK_TIMEOUT=1
+ MAX_RETRANSMIT = 1
+ ACK_TIMEOUT = 1
def tearDown(self):
super().teardown_demo_with_servers(auto_deregister=False)
@@ -119,16 +123,19 @@ def runTest(self):
# Register only falls back to handshake if it's not performed immediately after one
self.communicate('send-update')
pkt = self.assertDemoUpdatesRegistration(respond=False)
- self.serv.send(Lwm2mErrorResponse.matching(pkt)(code=coap.Code.RES_NOT_FOUND))
+ self.serv.send(Lwm2mErrorResponse.matching(pkt)
+ (code=coap.Code.RES_NOT_FOUND))
# Ignore register requests.
for _ in range(self.MAX_RETRANSMIT + 1):
- self.assertDemoRegisters(respond=False, timeout_s=self.last_retransmission_timeout())
+ self.assertDemoRegisters(
+ respond=False, timeout_s=self.last_retransmission_timeout())
self.wait_for_retransmission_response_timeout()
# Demo should fall back to DTLS handshake.
- self.assertPktIsDtlsClientHello(self.serv._raw_udp_socket.recv(4096), seq_number=0)
+ self.assertPktIsDtlsClientHello(
+ self.serv._raw_udp_socket.recv(4096), seq_number=0)
self.wait_for_retransmission_response_timeout()
# Ensure that the control is given back to the user.
@@ -188,13 +195,15 @@ def runTest(self):
self.serv.close()
# Wait for ICMP port unreachable.
- self.wait_until_icmp_unreachable_count(1, timeout_s=self.last_retransmission_timeout())
+ self.wait_until_icmp_unreachable_count(
+ 1, timeout_s=self.last_retransmission_timeout())
self.wait_for_retransmission_response_timeout()
# Ensure that no more retransmissions occurred.
self.assertEqual(1, self.count_icmp_unreachable_packets())
# Ensure that no more dtls handshake messages occurred.
- self.assertEqual(num_initial_dtls_hs_packets, self.count_dtls_client_hello_packets())
+ self.assertEqual(num_initial_dtls_hs_packets,
+ self.count_dtls_client_hello_packets())
# Ensure that the control is given back to the user.
self.assertTrue(self.get_all_connections_failed())
@@ -203,8 +212,8 @@ def runTest(self):
class RegisterIcmpTest(test_suite.PcapEnabledTest,
RetransmissionTest.TestMixin,
test_suite.Lwm2mSingleServerTest):
- MAX_RETRANSMIT=1
- ACK_TIMEOUT=4
+ MAX_RETRANSMIT = 1
+ ACK_TIMEOUT = 4
def setUp(self):
super().setUp(auto_register=False)
@@ -219,7 +228,8 @@ def runTest(self):
# Force Register
self.communicate('reconnect')
# Wait for ICMP port unreachable.
- self.wait_until_icmp_unreachable_count(1, timeout_s=self.last_retransmission_timeout())
+ self.wait_until_icmp_unreachable_count(
+ 1, timeout_s=self.last_retransmission_timeout())
# Ensure that the control is given back to the user.
with self.assertRaises(socket.timeout, msg="unexpected packets from the client"):
@@ -264,7 +274,8 @@ def runTest(self):
# Close socket to induce ICMP port unreachables.
self.serv.close()
self.communicate('trim-servers 0')
- self.wait_until_icmp_unreachable_count(1, timeout_s=2 * self.last_retransmission_timeout())
+ self.wait_until_icmp_unreachable_count(
+ 1, timeout_s=2 * self.last_retransmission_timeout())
# Give demo time to realize deregister failed.
time.sleep(self.ACK_TIMEOUT * self.ACK_RANDOM_FACTOR)
# Ensure that no more retransmissions occurred.
@@ -291,22 +302,27 @@ def tearDown(self):
def runTest(self):
new_lifetime = int(2 * self.max_transmit_wait())
# Change lifetime to 2*MAX_TRANSMIT_WAIT
- self.write_resource(self.serv, oid=OID.Server, iid=1, rid=RID.Server.Lifetime, content=str(new_lifetime))
+ self.write_resource(self.serv, oid=OID.Server, iid=1,
+ rid=RID.Server.Lifetime, content=str(new_lifetime))
self.assertDemoUpdatesRegistration(lifetime=new_lifetime)
# Demo should attempt to update registration.
for _ in range(self.MAX_RETRANSMIT + 1):
- self.assertDemoUpdatesRegistration(respond=False, timeout_s=new_lifetime)
+ self.assertDemoUpdatesRegistration(
+ respond=False, timeout_s=new_lifetime)
self.wait_for_retransmission_response_timeout()
# Demo should re-register
for _ in range(self.MAX_RETRANSMIT + 1):
- self.assertDemoRegisters(respond=False, lifetime=new_lifetime, timeout_s=self.max_transmit_wait())
+ self.assertDemoRegisters(
+ respond=False, lifetime=new_lifetime, timeout_s=self.max_transmit_wait())
self.wait_for_retransmission_response_timeout()
- self.serv._raw_udp_socket.settimeout(self.last_retransmission_timeout() + 1)
+ self.serv._raw_udp_socket.settimeout(
+ self.last_retransmission_timeout() + 1)
# Demo should attempt handshake
for i in range(self.MAX_RETRANSMIT + 1):
- self.assertPktIsDtlsClientHello(self.serv._raw_udp_socket.recv(4096))
+ self.assertPktIsDtlsClientHello(
+ self.serv._raw_udp_socket.recv(4096))
self.wait_for_retransmission_response_timeout()
with self.assertRaises(socket.timeout, msg="unexpected packets from the client"):
@@ -321,11 +337,13 @@ class UpdateTimeoutFallbacksToRegisterTest(RetransmissionTest.TestMixin,
def runTest(self):
new_lifetime = int(2 * self.max_transmit_wait())
# Change lifetime to 2*MAX_TRANSMIT_WAIT
- self.write_resource(self.serv, oid=OID.Server, iid=1, rid=RID.Server.Lifetime, content=str(new_lifetime))
+ self.write_resource(self.serv, oid=OID.Server, iid=1,
+ rid=RID.Server.Lifetime, content=str(new_lifetime))
self.assertDemoUpdatesRegistration(lifetime=new_lifetime)
# Demo should attempt to update registration.
for _ in range(self.MAX_RETRANSMIT + 1):
- self.assertDemoUpdatesRegistration(respond=False, timeout_s=new_lifetime)
+ self.assertDemoUpdatesRegistration(
+ respond=False, timeout_s=new_lifetime)
self.wait_for_retransmission_response_timeout()
# Demo should re-register
@@ -341,7 +359,8 @@ def setUp(self):
def runTest(self):
self.communicate('send-update')
for _ in range(self.MAX_RETRANSMIT + 1):
- self.assertDemoUpdatesRegistration(respond=False, timeout_s=2 * self.max_transmit_wait())
+ self.assertDemoUpdatesRegistration(
+ respond=False, timeout_s=2 * self.max_transmit_wait())
self.wait_for_retransmission_response_timeout()
# Demo should re-register
@@ -374,7 +393,8 @@ def runTest(self):
# Ensure that no more retransmissions occurred.
self.assertEqual(1, self.count_icmp_unreachable_packets())
# Ensure that no more dtls handshake messages occurred.
- self.assertEqual(num_initial_dtls_hs_packets, self.count_dtls_client_hello_packets())
+ self.assertEqual(num_initial_dtls_hs_packets,
+ self.count_dtls_client_hello_packets())
# Ensure that the control is given back to the user.
self.assertTrue(self.get_all_connections_failed())
@@ -393,8 +413,8 @@ class UpdateIcmpTest(UpdateFailsOnIcmpTest.TestMixin,
class DtlsRequestBootstrapTimeoutFallbacksToHsTest(RetransmissionTest.TestMixin,
test_suite.Lwm2mDtlsSingleServerTest):
# These settings speed up tests considerably.
- MAX_RETRANSMIT=1
- ACK_TIMEOUT=1
+ MAX_RETRANSMIT = 1
+ ACK_TIMEOUT = 1
def setUp(self):
super().setUp(bootstrap_server=Lwm2mServer(coap.DtlsServer(psk_identity=self.PSK_IDENTITY, psk_key=self.PSK_KEY)),
@@ -411,19 +431,23 @@ def runTest(self):
# Request Bootstrap only falls back to handshake if it's not performed immediately after one
self.communicate('send-update')
pkt = self.assertDemoUpdatesRegistration(respond=False)
- self.serv.send(Lwm2mErrorResponse.matching(pkt)(code=coap.Code.RES_FORBIDDEN))
+ self.serv.send(Lwm2mErrorResponse.matching(pkt)
+ (code=coap.Code.RES_FORBIDDEN))
pkt = self.assertDemoRegisters(respond=False)
- self.serv.send(Lwm2mErrorResponse.matching(pkt)(code=coap.Code.RES_FORBIDDEN))
+ self.serv.send(Lwm2mErrorResponse.matching(pkt)
+ (code=coap.Code.RES_FORBIDDEN))
# Ignore Request Bootstrap requests.
for _ in range(self.MAX_RETRANSMIT + 1):
- pkt = self.bootstrap_server.recv(timeout_s=self.last_retransmission_timeout())
+ pkt = self.bootstrap_server.recv(
+ timeout_s=self.last_retransmission_timeout())
self.assertIsInstance(pkt, Lwm2mRequestBootstrap)
self.wait_for_retransmission_response_timeout()
# Demo should fall back to DTLS handshake.
- self.assertPktIsDtlsClientHello(self.bootstrap_server._raw_udp_socket.recv(4096), seq_number=0)
+ self.assertPktIsDtlsClientHello(
+ self.bootstrap_server._raw_udp_socket.recv(4096), seq_number=0)
self.wait_for_retransmission_response_timeout()
# Ensure that the control is given back to the user.
@@ -442,7 +466,8 @@ def setUp(self, bootstrap_server=True, *args, **kwargs):
def runTest(self):
# Ignore Request Bootstrap requests.
for _ in range(self.MAX_RETRANSMIT + 1):
- pkt = self.bootstrap_server.recv(timeout_s=self.last_retransmission_timeout() + 5)
+ pkt = self.bootstrap_server.recv(
+ timeout_s=self.last_retransmission_timeout() + 5)
self.assertIsInstance(pkt, Lwm2mRequestBootstrap)
self.wait_for_retransmission_response_timeout()
@@ -486,16 +511,19 @@ def runTest(self):
self.bootstrap_server.close()
# respond with Forbidden to Register so that client falls back to Bootstrap
- self.serv.send(Lwm2mErrorResponse.matching(pkt)(code=coap.Code.RES_FORBIDDEN))
+ self.serv.send(Lwm2mErrorResponse.matching(pkt)
+ (code=coap.Code.RES_FORBIDDEN))
# Wait for ICMP port unreachable.
- self.wait_until_icmp_unreachable_count(1, timeout_s=self.last_retransmission_timeout())
+ self.wait_until_icmp_unreachable_count(
+ 1, timeout_s=self.last_retransmission_timeout())
self.wait_for_retransmission_response_timeout()
# Ensure that no more retransmissions occurred.
self.assertEqual(1, self.count_icmp_unreachable_packets())
# Ensure that no more dtls handshake messages occurred.
- self.assertEqual(num_initial_dtls_hs_packets, self.count_dtls_client_hello_packets())
+ self.assertEqual(num_initial_dtls_hs_packets,
+ self.count_dtls_client_hello_packets())
# Ensure that the control is given back to the user.
self.assertTrue(self.get_all_connections_failed())
@@ -504,8 +532,8 @@ def runTest(self):
class RequestBootstrapIcmpTest(test_suite.PcapEnabledTest,
RetransmissionTest.TestMixin,
test_suite.Lwm2mTest):
- MAX_RETRANSMIT=1
- ACK_TIMEOUT=4
+ MAX_RETRANSMIT = 1
+ ACK_TIMEOUT = 4
def setUp(self):
super().setUp(servers=0, bootstrap_server=True)
@@ -522,7 +550,8 @@ def runTest(self):
# Force Register
self.communicate('reconnect')
# Wait for ICMP port unreachable.
- self.wait_until_icmp_unreachable_count(1, timeout_s=self.last_retransmission_timeout())
+ self.wait_until_icmp_unreachable_count(
+ 1, timeout_s=self.last_retransmission_timeout())
# Ensure that the control is given back to the user.
with self.assertRaises(socket.timeout, msg="unexpected packets from the client"):
@@ -541,7 +570,7 @@ class NotificationIcmpTest(test_suite.PcapEnabledTest,
RetransmissionTest.TestMixin,
test_suite.Lwm2mSingleServerTest,
test_suite.Lwm2mDmOperations):
- CONFIRMABLE_NOTIFICATIONS=True
+ CONFIRMABLE_NOTIFICATIONS = True
def tearDown(self):
super().teardown_demo_with_servers(auto_deregister=False)
@@ -567,7 +596,7 @@ class NotificationDtlsFailsOnIcmpTest(test_suite.PcapEnabledTest,
RetransmissionTest.TestMixin,
test_suite.Lwm2mDtlsSingleServerTest,
test_suite.Lwm2mDmOperations):
- CONFIRMABLE_NOTIFICATIONS=True
+ CONFIRMABLE_NOTIFICATIONS = True
def tearDown(self):
super().teardown_demo_with_servers(auto_deregister=False)
@@ -591,18 +620,88 @@ def runTest(self):
# Ensure that no more retransmissions occurred.
self.assertEqual(1, self.count_icmp_unreachable_packets())
# Ensure that no more dtls handshake messages occurred.
- self.assertEqual(num_initial_dtls_hs_packets, self.count_dtls_client_hello_packets())
+ self.assertEqual(num_initial_dtls_hs_packets,
+ self.count_dtls_client_hello_packets())
# Ensure that the control is given back to the user.
self.assertTrue(self.get_all_connections_failed())
+class NotificationTimeoutIsIgnored:
+ class TestMixin(RetransmissionTest.TestMixin,
+ test_suite.Lwm2mDmOperations):
+ CONFIRMABLE_NOTIFICATIONS = True
+
+ def runTest(self):
+ import subprocess
+ import unittest
+
+ output = subprocess.run([self._get_demo_executable(), '-e', 'dummy', '-u', 'invalid'],
+ stdout=subprocess.PIPE, stderr=subprocess.STDOUT).stdout.decode(
+ 'utf-8')
+
+ if 'WITH_AVS_COAP_OBSERVE_CANCEL_ON_TIMEOUT = ON' in output:
+ raise unittest.SkipTest(
+ 'Timeout cancels observation in that configuration')
+
+ # Trigger a CON notification
+ self.create_instance(self.serv, oid=OID.Test, iid=1)
+
+ # force an Update so that change to the data model does not get notified later
+ self.communicate('send-update')
+ self.assertDemoUpdatesRegistration(content=ANY)
+
+ self.observe(self.serv, oid=OID.Test, iid=1, rid=RID.Test.Counter)
+ self.execute_resource(self.serv, oid=OID.Test,
+ iid=1, rid=RID.Test.IncrementCounter)
+
+ first_pkt = self.serv.recv(timeout_s=2)
+ first_attempt = time.time()
+ self.assertIsInstance(first_pkt, Lwm2mNotify)
+
+ for attempt in range(self.MAX_RETRANSMIT):
+ self.assertIsInstance(
+ self.serv.recv(timeout_s=30), Lwm2mNotify)
+ last_attempt = time.time()
+
+ transmit_span_lower_bound = self.ACK_TIMEOUT * \
+ ((2 ** self.MAX_RETRANSMIT) - 1)
+ transmit_span_upper_bound = transmit_span_lower_bound * self.ACK_RANDOM_FACTOR
+
+ self.assertGreater(last_attempt - first_attempt,
+ transmit_span_lower_bound - 1)
+ self.assertLess(last_attempt - first_attempt,
+ transmit_span_upper_bound + 1)
+
+ time.sleep(self.last_retransmission_timeout() + 1)
+
+ # check that following notifications still trigger attempts to send the value
+ self.execute_resource(self.serv, oid=OID.Test,
+ iid=1, rid=RID.Test.IncrementCounter)
+ pkt = self.serv.recv(
+ timeout_s=self.last_retransmission_timeout() + 2)
+ self.assertIsInstance(pkt, Lwm2mNotify)
+ self.assertEqual(pkt.content, first_pkt.content)
+ self.serv.send(Lwm2mReset.matching(pkt)())
+
+
class NotificationTimeoutCancelsObservation:
class TestMixin(RetransmissionTest.TestMixin,
test_suite.Lwm2mDmOperations):
CONFIRMABLE_NOTIFICATIONS = True
def runTest(self):
+ import subprocess
+ import unittest
+
+ output = subprocess.run([self._get_demo_executable(), '-e', 'dummy', '-u', 'invalid'],
+ stdout=subprocess.PIPE, stderr=subprocess.STDOUT).stdout.decode(
+ 'utf-8')
+
+ if 'WITH_AVS_COAP_OBSERVE_CANCEL_ON_TIMEOUT = OFF' in output:
+ raise unittest.SkipTest(
+ 'Timeout does not cancel observation in that configuration')
+
# Trigger a CON notification
self.create_instance(self.serv, oid=OID.Test, iid=1)
@@ -611,28 +710,45 @@ def runTest(self):
self.assertDemoUpdatesRegistration(content=ANY)
self.observe(self.serv, oid=OID.Test, iid=1, rid=RID.Test.Counter)
- self.execute_resource(self.serv, oid=OID.Test, iid=1, rid=RID.Test.IncrementCounter)
+ self.execute_resource(self.serv, oid=OID.Test,
+ iid=1, rid=RID.Test.IncrementCounter)
first_pkt = self.serv.recv(timeout_s=2)
first_attempt = time.time()
self.assertIsInstance(first_pkt, Lwm2mNotify)
for attempt in range(self.MAX_RETRANSMIT):
- self.assertIsInstance(self.serv.recv(timeout_s=30), Lwm2mNotify)
+ self.assertIsInstance(
+ self.serv.recv(timeout_s=30), Lwm2mNotify)
last_attempt = time.time()
- transmit_span_lower_bound = self.ACK_TIMEOUT * ((2 ** self.MAX_RETRANSMIT) - 1)
+ transmit_span_lower_bound = self.ACK_TIMEOUT * \
+ ((2 ** self.MAX_RETRANSMIT) - 1)
transmit_span_upper_bound = transmit_span_lower_bound * self.ACK_RANDOM_FACTOR
- self.assertGreater(last_attempt - first_attempt, transmit_span_lower_bound - 1)
- self.assertLess(last_attempt - first_attempt, transmit_span_upper_bound + 1)
+ self.assertGreater(last_attempt - first_attempt,
+ transmit_span_lower_bound - 1)
+ self.assertLess(last_attempt - first_attempt,
+ transmit_span_upper_bound + 1)
time.sleep(self.last_retransmission_timeout() + 1)
# check that the observation is really cancelled
- self.execute_resource(self.serv, oid=OID.Test, iid=1, rid=RID.Test.IncrementCounter)
+ self.execute_resource(self.serv, oid=OID.Test,
+ iid=1, rid=RID.Test.IncrementCounter)
with self.assertRaises(socket.timeout):
- print(self.serv.recv(timeout_s=self.last_retransmission_timeout() + 5))
+ print(self.serv.recv(
+ timeout_s=self.last_retransmission_timeout() + 5))
+
+
+class NotificationDtlsTimeoutIsIgnoredTest(NotificationTimeoutIsIgnored.TestMixin,
+ test_suite.Lwm2mDtlsSingleServerTest):
+ pass
+
+
+class NotificationTimeoutIsIgnoredTest(NotificationTimeoutIsIgnored.TestMixin,
+ test_suite.Lwm2mSingleServerTest):
+ pass
class NotificationDtlsTimeoutCancelsObservationTest(NotificationTimeoutCancelsObservation.TestMixin,
@@ -648,8 +764,8 @@ class NotificationTimeoutCancelsObservationTest(NotificationTimeoutCancelsObserv
class ReplacedBootstrapServerReconnectTest(RetransmissionTest.TestMixin,
test_suite.Lwm2mDtlsSingleServerTest,
test_suite.Lwm2mDmOperations):
- MAX_RETRANSMIT=1
- ACK_TIMEOUT=1
+ MAX_RETRANSMIT = 1
+ ACK_TIMEOUT = 1
def setUp(self):
super().setUp(bootstrap_server=Lwm2mServer(coap.DtlsServer(psk_identity=self.PSK_IDENTITY, psk_key=self.PSK_KEY)),
@@ -672,33 +788,38 @@ def runTest(self):
# replace the existing instance
self.write_instance(self.bootstrap_server, oid=OID.Security, iid=1,
- content=TLV.make_resource(RID.Security.ServerURI, 'coaps://127.0.0.1:%d' % self.bootstrap_server.get_listen_port()).serialize()
- + TLV.make_resource(RID.Security.Bootstrap, 1).serialize()
- + TLV.make_resource(RID.Security.Mode, coap.server.SecurityMode.PreSharedKey.value).serialize()
- + TLV.make_resource(RID.Security.PKOrIdentity, self.PSK_IDENTITY).serialize()
- + TLV.make_resource(RID.Security.SecretKey, self.PSK_KEY).serialize())
+ content=TLV.make_resource(
+ RID.Security.ServerURI, 'coaps://127.0.0.1:%d' % self.bootstrap_server.get_listen_port()).serialize()
+ + TLV.make_resource(RID.Security.Bootstrap, 1).serialize()
+ + TLV.make_resource(RID.Security.Mode, coap.server.SecurityMode.PreSharedKey.value).serialize()
+ + TLV.make_resource(RID.Security.PKOrIdentity, self.PSK_IDENTITY).serialize()
+ + TLV.make_resource(RID.Security.SecretKey, self.PSK_KEY).serialize())
# provision the regular Server instance
self.write_instance(self.bootstrap_server, oid=OID.Security, iid=2,
- content=TLV.make_resource(RID.Security.ServerURI, 'coaps://127.0.0.1:%d' % self.serv.get_listen_port()).serialize()
- + TLV.make_resource(RID.Security.Bootstrap, 0).serialize()
- + TLV.make_resource(RID.Security.Mode, coap.server.SecurityMode.PreSharedKey.value).serialize()
- + TLV.make_resource(RID.Security.ShortServerID, 2).serialize()
- + TLV.make_resource(RID.Security.PKOrIdentity, self.PSK_IDENTITY).serialize()
- + TLV.make_resource(RID.Security.SecretKey, self.PSK_KEY).serialize())
+ content=TLV.make_resource(
+ RID.Security.ServerURI, 'coaps://127.0.0.1:%d' % self.serv.get_listen_port()).serialize()
+ + TLV.make_resource(RID.Security.Bootstrap, 0).serialize()
+ + TLV.make_resource(RID.Security.Mode, coap.server.SecurityMode.PreSharedKey.value).serialize()
+ + TLV.make_resource(RID.Security.ShortServerID, 2).serialize()
+ + TLV.make_resource(RID.Security.PKOrIdentity, self.PSK_IDENTITY).serialize()
+ + TLV.make_resource(RID.Security.SecretKey, self.PSK_KEY).serialize())
self.write_instance(self.bootstrap_server, oid=OID.Server, iid=2,
- content=TLV.make_resource(RID.Server.Lifetime, int(lifetime)).serialize()
- + TLV.make_resource(RID.Server.ShortServerID, 2).serialize()
- + TLV.make_resource(RID.Server.NotificationStoring, True).serialize()
- + TLV.make_resource(RID.Server.Binding, "U").serialize()
- + TLV.make_resource(RID.Server.ServerCommunicationRetryCount, 1).serialize()
- + TLV.make_resource(RID.Server.ServerCommunicationSequenceRetryCount, 1).serialize()
- )
+ content=TLV.make_resource(
+ RID.Server.Lifetime, int(lifetime)).serialize()
+ + TLV.make_resource(RID.Server.ShortServerID, 2).serialize()
+ + TLV.make_resource(RID.Server.NotificationStoring, True).serialize()
+ + TLV.make_resource(RID.Server.Binding,
+ "U").serialize()
+ + TLV.make_resource(RID.Server.ServerCommunicationRetryCount, 1).serialize()
+ + TLV.make_resource(RID.Server.ServerCommunicationSequenceRetryCount, 1).serialize()
+ )
# Bootstrap Finish
pkt = Lwm2mBootstrapFinish()
self.bootstrap_server.send(pkt)
- self.assertMsgEqual(Lwm2mChanged.matching(pkt)(), self.bootstrap_server.recv())
+ self.assertMsgEqual(Lwm2mChanged.matching(pkt)(),
+ self.bootstrap_server.recv())
# demo will refresh Bootstrap connection...
self.assertDtlsReconnect(self.bootstrap_server)
@@ -708,20 +829,26 @@ def runTest(self):
# let the Update fail
self.assertIsInstance(self.serv.recv(timeout_s=lifetime), Lwm2mUpdate)
- self.assertIsInstance(self.serv.recv(timeout_s=self.ACK_TIMEOUT + 1), Lwm2mUpdate)
+ self.assertIsInstance(self.serv.recv(
+ timeout_s=self.ACK_TIMEOUT + 1), Lwm2mUpdate)
# client falls back to Register
pkt = self.serv.recv(timeout_s=lifetime)
self.assertIsInstance(pkt, Lwm2mRegister)
- self.serv.send(Lwm2mErrorResponse.matching(pkt)(coap.Code.RES_FORBIDDEN))
+ self.serv.send(Lwm2mErrorResponse.matching(pkt)
+ (coap.Code.RES_FORBIDDEN))
# now the client falls back to Bootstrap, and doesn't get response
- self.assertIsInstance(self.bootstrap_server.recv(), Lwm2mRequestBootstrap)
- self.assertIsInstance(self.bootstrap_server.recv(timeout_s=self.ACK_TIMEOUT + 1), Lwm2mRequestBootstrap)
+ self.assertIsInstance(
+ self.bootstrap_server.recv(), Lwm2mRequestBootstrap)
+ self.assertIsInstance(self.bootstrap_server.recv(
+ timeout_s=self.ACK_TIMEOUT + 1), Lwm2mRequestBootstrap)
# rehandshake should appear here
- self.assertDtlsReconnect(self.bootstrap_server, timeout_s=2*self.ACK_TIMEOUT + 1)
- self.assertIsInstance(self.bootstrap_server.recv(), Lwm2mRequestBootstrap)
+ self.assertDtlsReconnect(
+ self.bootstrap_server, timeout_s=2*self.ACK_TIMEOUT + 1)
+ self.assertIsInstance(
+ self.bootstrap_server.recv(), Lwm2mRequestBootstrap)
class ModifyingTxParams(RetransmissionTest.TestMixin, bootstrap_client.BootstrapTest.Test):
@@ -745,8 +872,10 @@ def runTest(self):
self.assertMsgEqual(pkt2, pkt3)
# check that it used the initial transmission params
- self.assertAlmostEqual(pkt2_time - pkt1_time, self.ACK_TIMEOUT, delta=0.5)
- self.assertAlmostEqual(pkt3_time - pkt2_time, 2.0 * self.ACK_TIMEOUT, delta=0.5)
+ self.assertAlmostEqual(pkt2_time - pkt1_time,
+ self.ACK_TIMEOUT, delta=0.5)
+ self.assertAlmostEqual(pkt3_time - pkt2_time,
+ 2.0 * self.ACK_TIMEOUT, delta=0.5)
# Now let's change the transmission params
# ACK_TIMEOUT=5, ACK_RANDOM_FACTOR=1, MAX_RETRANSMIT=1, NSTART=1
@@ -760,13 +889,15 @@ def runTest(self):
# so check that the new ACK_TIMEOUT is in effect for the 1.0 attempt
pkt1 = self.bootstrap_server.recv(timeout_s=self.max_transmit_wait())
pkt1_time = time.time()
- self.assertMsgEqual(Lwm2mRequestBootstrap(endpoint_name=DEMO_ENDPOINT_NAME), pkt1)
+ self.assertMsgEqual(Lwm2mRequestBootstrap(
+ endpoint_name=DEMO_ENDPOINT_NAME), pkt1)
pkt2 = self.bootstrap_server.recv(timeout_s=self.max_transmit_wait())
pkt2_time = time.time()
self.assertMsgEqual(pkt1, pkt2)
- self.assertAlmostEqual(pkt2_time - pkt1_time, self.ACK_TIMEOUT, delta=0.5)
+ self.assertAlmostEqual(pkt2_time - pkt1_time,
+ self.ACK_TIMEOUT, delta=0.5)
# Respond to Request Bootstrap
self.bootstrap_server.send(Lwm2mChanged.matching(pkt2)())
@@ -784,9 +915,11 @@ def runTest(self):
pkt2_time = time.time()
self.assertMsgEqual(pkt1, pkt2)
- self.assertAlmostEqual(pkt2_time - pkt1_time, self.ACK_TIMEOUT, delta=0.5)
+ self.assertAlmostEqual(pkt2_time - pkt1_time,
+ self.ACK_TIMEOUT, delta=0.5)
- self.serv.send(Lwm2mCreated.matching(pkt2)(location=self.DEFAULT_REGISTER_ENDPOINT))
+ self.serv.send(Lwm2mCreated.matching(pkt2)(
+ location=self.DEFAULT_REGISTER_ENDPOINT))
# check that downloads also use the new transmission params
dl_server = coap.Server()
@@ -802,7 +935,8 @@ def runTest(self):
pkt2_time = time.time()
self.assertMsgEqual(pkt1, pkt2)
- self.assertAlmostEqual(pkt2_time - pkt1_time, self.ACK_TIMEOUT, delta=0.5)
+ self.assertAlmostEqual(
+ pkt2_time - pkt1_time, self.ACK_TIMEOUT, delta=0.5)
dl_server.send(Lwm2mErrorResponse.matching(pkt2)(
code=coap.Code.RES_NOT_FOUND).fill_placeholders())
@@ -817,14 +951,18 @@ def runTest(self):
self.assertDemoRequestsBootstrap()
# change exchange lifetime to 2 seconds
- self.communicate('set-coap-exchange-timeout udp %s' % (self.EXCHANGE_LIFETIME,))
+ self.communicate('set-coap-exchange-timeout udp %s' %
+ (self.EXCHANGE_LIFETIME,))
# Create typical Server Object instance
server_entries_no_ssid = [TLV.make_resource(RID.Server.Lifetime, 86400),
- TLV.make_resource(RID.Server.NotificationStoring, True),
+ TLV.make_resource(
+ RID.Server.NotificationStoring, True),
TLV.make_resource(RID.Server.Binding, "U"),
- TLV.make_resource(RID.Server.ServerCommunicationRetryCount, 1),
- TLV.make_resource(RID.Server.ServerCommunicationRetryTimer, 0),
+ TLV.make_resource(
+ RID.Server.ServerCommunicationRetryCount, 1),
+ TLV.make_resource(
+ RID.Server.ServerCommunicationRetryTimer, 0),
TLV.make_resource(
RID.Server.ServerCommunicationSequenceRetryCount, 1),
TLV.make_resource(
@@ -849,7 +987,8 @@ def runTest(self):
req = packets[0]
self.bootstrap_server.send(req)
- self.assertMsgEqual(Lwm2mContinue.matching(req)(), self.bootstrap_server.recv())
+ self.assertMsgEqual(Lwm2mContinue.matching(req)(),
+ self.bootstrap_server.recv())
# Wait for the exchange to time out
time.sleep(self.EXCHANGE_LIFETIME + 0.5)
@@ -858,12 +997,15 @@ def runTest(self):
req = packets[1]
self.bootstrap_server.send(req)
self.assertMsgEqual(
- Lwm2mErrorResponse.matching(req)(code=coap.Code.RES_REQUEST_ENTITY_INCOMPLETE),
+ Lwm2mErrorResponse.matching(req)(
+ code=coap.Code.RES_REQUEST_ENTITY_INCOMPLETE),
self.bootstrap_server.recv())
# That's tested, now bootstrap normally
- self.write_instance(self.bootstrap_server, oid=OID.Server, iid=1, content=server_tlv)
- self.write_instance(self.bootstrap_server, oid=OID.Security, iid=1, content=security_tlv)
+ self.write_instance(self.bootstrap_server,
+ oid=OID.Server, iid=1, content=server_tlv)
+ self.write_instance(self.bootstrap_server,
+ oid=OID.Security, iid=1, content=security_tlv)
self.perform_bootstrap_finish()
self.assertDemoRegisters()
@@ -886,7 +1028,8 @@ def runTest(self):
req = packets[1]
self.serv.send(req)
self.assertMsgEqual(
- Lwm2mErrorResponse.matching(req)(code=coap.Code.RES_REQUEST_ENTITY_INCOMPLETE),
+ Lwm2mErrorResponse.matching(req)(
+ code=coap.Code.RES_REQUEST_ENTITY_INCOMPLETE),
self.serv.recv())
@@ -917,7 +1060,8 @@ def runTest(self):
self.assertPktIsDtlsClientHello(self.serv._raw_udp_socket.recv(65536))
mgmt_hello_time = time.time()
- self.assertPktIsDtlsClientHello(self.bootstrap_server._raw_udp_socket.recv(65536))
+ self.assertPktIsDtlsClientHello(
+ self.bootstrap_server._raw_udp_socket.recv(65536))
bootstrap_hello_time = time.time()
self.wait_until_socket_count(0, timeout_s=self.HANDSHAKE_TIMEOUT + 1)