diff --git a/Android.mk b/Android.mk index 3c71d731e..b431c024a 100644 --- a/Android.mk +++ b/Android.mk @@ -156,6 +156,7 @@ LOCAL_SRC_FILES := \ src/core/api/crypto_api.cpp \ src/core/api/dataset_api.cpp \ src/core/api/dataset_ftd_api.cpp \ + src/core/api/dataset_updater_api.cpp \ src/core/api/diags_api.cpp \ src/core/api/dns_api.cpp \ src/core/api/icmp6_api.cpp \ @@ -283,6 +284,7 @@ LOCAL_SRC_FILES := \ src/core/utils/channel_manager.cpp \ src/core/utils/channel_monitor.cpp \ src/core/utils/child_supervision.cpp \ + src/core/utils/dataset_updater.cpp \ src/core/utils/heap.cpp \ src/core/utils/jam_detector.cpp \ src/core/utils/lookup_table.cpp \ diff --git a/etc/cmake/options.cmake b/etc/cmake/options.cmake index ea3479006..741aaa980 100644 --- a/etc/cmake/options.cmake +++ b/etc/cmake/options.cmake @@ -116,6 +116,11 @@ if(OT_CSL_DEBUG) target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_MAC_CSL_DEBUG_ENABLE=1") endif() +option(OT_DATASET_UPDATER "enable dataset updater support") +if(OT_DATASET_UPDATER) + target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_DATASET_UPDATER_ENABLE=1") +endif() + option(OT_DHCP6_CLIENT "enable DHCP6 client support") if(OT_DHCP6_CLIENT) target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_DHCP6_CLIENT_ENABLE=1") diff --git a/examples/Makefile-simulation b/examples/Makefile-simulation index 28f4d1ea4..5eca36cb0 100644 --- a/examples/Makefile-simulation +++ b/examples/Makefile-simulation @@ -44,6 +44,7 @@ COMMISSIONER ?= 1 CHANNEL_MANAGER ?= 1 CHANNEL_MONITOR ?= 1 CHILD_SUPERVISION ?= 1 +DATASET_UPDATER ?= 1 DHCP6_CLIENT ?= 1 DHCP6_SERVER ?= 1 DIAGNOSTIC ?= 1 diff --git a/examples/common-switches.mk b/examples/common-switches.mk index a9630c18d..d09b05604 100644 --- a/examples/common-switches.mk +++ b/examples/common-switches.mk @@ -41,6 +41,7 @@ CHANNEL_MANAGER ?= 0 CHANNEL_MONITOR ?= 0 CHILD_SUPERVISION ?= 0 CLI_TRANSPORT ?= UART +DATASET_UPDATER ?= 0 DEBUG ?= 0 DHCP6_CLIENT ?= 0 DHCP6_SERVER ?= 0 @@ -139,6 +140,10 @@ ifeq ($(CSL_DEBUG),1) COMMONCFLAGS += -DOPENTHREAD_CONFIG_MAC_CSL_DEBUG_ENABLE=1 endif +ifeq ($(DATASET_UPDATER),1) +COMMONCFLAGS += -DOPENTHREAD_CONFIG_DATASET_UPDATER_ENABLE=1 +endif + ifeq ($(DEBUG),1) configure_OPTIONS += --enable-debug --disable-optimization endif diff --git a/include/Makefile.am b/include/Makefile.am index 3347fee22..bd61c319e 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -49,6 +49,7 @@ openthread_headers = \ openthread/crypto.h \ openthread/dataset.h \ openthread/dataset_ftd.h \ + openthread/dataset_updater.h \ openthread/diag.h \ openthread/dns.h \ openthread/entropy.h \ diff --git a/include/openthread/BUILD.gn b/include/openthread/BUILD.gn index 014dac654..731efe7d9 100644 --- a/include/openthread/BUILD.gn +++ b/include/openthread/BUILD.gn @@ -70,6 +70,7 @@ source_set("openthread") { "crypto.h", "dataset.h", "dataset_ftd.h", + "dataset_updater.h", "diag.h", "dns.h", "entropy.h", diff --git a/include/openthread/dataset_updater.h b/include/openthread/dataset_updater.h new file mode 100644 index 000000000..5706863c6 --- /dev/null +++ b/include/openthread/dataset_updater.h @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2020, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file + * @brief + * This file includes the OpenThread API for Dataset Updater module. + */ + +#ifndef OPENTHREAD_DATASET_UPDATER_H_ +#define OPENTHREAD_DATASET_UPDATER_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @addtogroup api-dataset-updater + * + * @brief + * This module includes functions for Dataset Updater. + * + * The functions in this module are available when Dataset Updater feature is enabled (i.e. + * `OPENTHREAD_CONFIG_DATASET_UPDATER_ENABLE` is set to 1). Further this feature is available only on an FTD build. + * + * @{ + * + */ + +/** + * This callback function pointer is called when a Dataset update request finishes, reporting success or failure status + * of the Dataset update request. + * + * @param[in] aError The error status. + * OT_ERROR_NONE indicates successful Dataset update. + * OT_ERROR_INVALID_STATE indicates failure due invalid state (MLE being disabled). + * OT_ERROR_ALREADY indicates failure due to another device within network requesting + * a conflicting Dataset update. + * + * @param[in] aContext A pointer to the arbitrary context (provided by user in `otDatasetUpdaterRequestUpdate()`). + * + */ +typedef void (*otDatasetUpdaterCallback)(otError aError, void *aContext); + +/** + * This function requests an update to Operational Dataset. + * + * @p aDataset should contain the fields to be updated and their new value. It must not contain Active or Pending + * Timestamp fields. The Delay field is optional, if not provided a default value (1000 ms) would be used. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aDataset A pointer to the Dataset containing the fields to change. + * @param[in] aCallback A callback to indicate when Dataset update request finishes. + * @param[in] aContext An arbitrary context passed to callback. + * @param[in] aRetryWaitInterval The wait time after sending Pending dataset before retrying (interval in ms). + * + * @retval OT_ERROR_NONE Dataset update started successfully (@p aCallback will be invoked on completion). + * @retval OT_ERROR_INVALID_STATE Device is disabled (MLE is disabled). + * @retval OT_ERROR_INVALID_ARGS The @p aDataset is not valid (contains Active or Pending Timestamp). + * @retval OT_ERROR_BUSY Cannot start update, a previous one is ongoing. + * @retval OT_ERROR_NO_BUFS Could not allocated buffer to save Dataset. + * + */ +otError otDatasetUpdaterRequestUpdate(otInstance * aInstance, + const otOperationalDataset *aDataset, + otDatasetUpdaterCallback aCallback, + void * aContext, + uint32_t aReryWaitInterval); + +/** + * This function cancels an ongoing (if any) Operational Dataset update request. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * + */ +void otDatasetUpdaterCancelUpdate(otInstance *aInstance); + +/** + * This function indicates whether there is an ongoing Operation Dataset update request. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * + * @retval TRUE There is an ongoing update. + * @retval FALSE There is no ongoing update. + * + */ +bool otDatasetUpdaterIsUpdateOngoing(otInstance *aInstance); + +/** + * @} + * + */ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // OPENTHREAD_DATASET_UPDATER_H_ diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 873edd32d..84ccdb794 100644 --- a/include/openthread/instance.h +++ b/include/openthread/instance.h @@ -53,7 +53,7 @@ extern "C" { * @note This number versions both OpenThread platform and user APIs. * */ -#define OPENTHREAD_API_VERSION (46) +#define OPENTHREAD_API_VERSION (47) /** * @addtogroup api-instance diff --git a/script/check-scan-build b/script/check-scan-build index 38323717e..3c38f1ea0 100755 --- a/script/check-scan-build +++ b/script/check-scan-build @@ -43,6 +43,7 @@ do_scan_build() "-DOPENTHREAD_CONFIG_COAP_API_ENABLE=1" "-DOPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE=1" "-DOPENTHREAD_CONFIG_COMMISSIONER_ENABLE=1" + "-DOPENTHREAD_CONFIG_DATASET_UPDATER_ENABLE=1" "-DOPENTHREAD_CONFIG_DHCP6_CLIENT_ENABLE=1" "-DOPENTHREAD_CONFIG_DHCP6_SERVER_ENABLE=1" "-DOPENTHREAD_CONFIG_DIAG_ENABLE=1" diff --git a/script/check-simulation-build-autotools b/script/check-simulation-build-autotools index bb55d6b89..eadb3ce29 100755 --- a/script/check-simulation-build-autotools +++ b/script/check-simulation-build-autotools @@ -48,6 +48,7 @@ build_all_features() "-DOPENTHREAD_CONFIG_COAP_API_ENABLE=1" "-DOPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE=1" "-DOPENTHREAD_CONFIG_COMMISSIONER_ENABLE=1" + "-DOPENTHREAD_CONFIG_DATASET_UPDATER_ENABLE=1" "-DOPENTHREAD_CONFIG_DHCP6_CLIENT_ENABLE=1" "-DOPENTHREAD_CONFIG_DHCP6_SERVER_ENABLE=1" "-DOPENTHREAD_CONFIG_DIAG_ENABLE=1" @@ -126,6 +127,7 @@ build_nest_common() "-DOPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE=1" "-DOPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE=1" "-DOPENTHREAD_CONFIG_CHILD_SUPERVISION_ENABLE=1" + "-DOPENTHREAD_CONFIG_DATASET_UPDATER_ENABLE=1" "-DOPENTHREAD_CONFIG_DIAG_ENABLE=1" "-DOPENTHREAD_CONFIG_JAM_DETECTION_ENABLE=1" "-DOPENTHREAD_CONFIG_LEGACY_ENABLE=1" diff --git a/script/check-size b/script/check-size index bd1a554a9..b4c589797 100755 --- a/script/check-size +++ b/script/check-size @@ -109,6 +109,7 @@ size_nrf52840_version() "COAP=1" "COAPS=1" "COMMISSIONER=1" + "DATASET_UPDATER=1" "DHCP6_CLIENT=1" "DHCP6_SERVER=1" "DIAGNOSTIC=1" diff --git a/script/cmake-build b/script/cmake-build index 2a500897e..6906282cb 100755 --- a/script/cmake-build +++ b/script/cmake-build @@ -75,6 +75,7 @@ readonly OT_POSIX_SIM_COMMON_OPTIONS=( "-DOT_CHANNEL_MANAGER=ON" "-DOT_CHANNEL_MONITOR=ON" "-DOT_CHILD_SUPERVISION=ON" + "-DOT_DATASET_UPDATER=ON" "-DOT_DHCP6_CLIENT=ON" "-DOT_DHCP6_SERVER=ON" "-DOT_DIAGNOSTIC=ON" diff --git a/script/make-pretty b/script/make-pretty index 380725aa0..63f131d3d 100755 --- a/script/make-pretty +++ b/script/make-pretty @@ -89,6 +89,7 @@ readonly OT_CLANG_TIDY_BUILD_OPTS=( '-DOT_COAPS=ON' '-DOT_COMMISSIONER=ON' '-DOT_CSL_RECEIVER=ON' + '-DOT_DATASET_UPDATER=ON' '-DOT_DHCP6_CLIENT=ON' '-DOT_DHCP6_SERVER=ON' '-DOT_DIAGNOSTIC=ON' diff --git a/src/core/BUILD.gn b/src/core/BUILD.gn index 859a19935..85174b390 100644 --- a/src/core/BUILD.gn +++ b/src/core/BUILD.gn @@ -303,6 +303,7 @@ openthread_core_files = [ "api/crypto_api.cpp", "api/dataset_api.cpp", "api/dataset_ftd_api.cpp", + "api/dataset_updater_api.cpp", "api/diags_api.cpp", "api/dns_api.cpp", "api/entropy_api.cpp", @@ -572,6 +573,8 @@ openthread_core_files = [ "utils/channel_monitor.hpp", "utils/child_supervision.cpp", "utils/child_supervision.hpp", + "utils/dataset_updater.cpp", + "utils/dataset_updater.hpp", "utils/flash.cpp", "utils/flash.hpp", "utils/heap.cpp", @@ -635,6 +638,7 @@ source_set("libopenthread_core_config") { "config/child_supervision.h", "config/coap.h", "config/commissioner.h", + "config/dataset_updater.h", "config/dhcp6_client.h", "config/dhcp6_server.h", "config/diag.h", diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 3f8d74f09..369f7871c 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -43,6 +43,7 @@ set(COMMON_SOURCES api/crypto_api.cpp api/dataset_api.cpp api/dataset_ftd_api.cpp + api/dataset_updater_api.cpp api/diags_api.cpp api/dns_api.cpp api/entropy_api.cpp @@ -178,6 +179,7 @@ set(COMMON_SOURCES utils/channel_manager.cpp utils/channel_monitor.cpp utils/child_supervision.cpp + utils/dataset_updater.cpp utils/flash.cpp utils/heap.cpp utils/jam_detector.cpp diff --git a/src/core/Makefile.am b/src/core/Makefile.am index ffa37f6b4..4b7420ea9 100644 --- a/src/core/Makefile.am +++ b/src/core/Makefile.am @@ -120,6 +120,7 @@ SOURCES_COMMON = \ api/crypto_api.cpp \ api/dataset_api.cpp \ api/dataset_ftd_api.cpp \ + api/dataset_updater_api.cpp \ api/diags_api.cpp \ api/dns_api.cpp \ api/entropy_api.cpp \ @@ -255,6 +256,7 @@ SOURCES_COMMON = \ utils/channel_manager.cpp \ utils/channel_monitor.cpp \ utils/child_supervision.cpp \ + utils/dataset_updater.cpp \ utils/flash.cpp \ utils/heap.cpp \ utils/jam_detector.cpp \ @@ -378,6 +380,7 @@ HEADERS_COMMON = \ config/child_supervision.h \ config/coap.h \ config/commissioner.h \ + config/dataset_updater.h \ config/dhcp6_client.h \ config/dhcp6_server.h \ config/diag.h \ @@ -490,6 +493,7 @@ HEADERS_COMMON = \ utils/channel_manager.hpp \ utils/channel_monitor.hpp \ utils/child_supervision.hpp \ + utils/dataset_updater.hpp \ utils/flash.hpp \ utils/heap.hpp \ utils/jam_detector.hpp \ diff --git a/src/core/api/dataset_updater_api.cpp b/src/core/api/dataset_updater_api.cpp new file mode 100644 index 000000000..63a61d018 --- /dev/null +++ b/src/core/api/dataset_updater_api.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2020, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file + * This file implements the OpenThread Dataset Updater APIs. + */ + +#include "openthread-core-config.h" + +#include + +#include "common/instance.hpp" +#include "common/locator-getters.hpp" +#include "utils/dataset_updater.hpp" + +using namespace ot; + +#if OPENTHREAD_CONFIG_DATASET_UPDATER_ENABLE && OPENTHREAD_FTD + +otError otDatasetUpdaterRequestUpdate(otInstance * aInstance, + const otOperationalDataset *aDataset, + otDatasetUpdaterCallback aCallback, + void * aContext, + uint32_t aReryWaitInterval) +{ + Instance &instance = *static_cast(aInstance); + + return instance.Get().RequestUpdate(*static_cast(aDataset), + aCallback, aContext, aReryWaitInterval); +} + +void otDatasetUpdaterCancelUpdate(otInstance *aInstance) +{ + Instance &instance = *static_cast(aInstance); + + instance.Get().CancelUpdate(); +} + +bool otDatasetUpdaterIsUpdateOngoing(otInstance *aInstance) +{ + Instance &instance = *static_cast(aInstance); + + return instance.Get().IsUpdateOngoing(); +} + +#endif // OPENTHREAD_CONFIG_DATASET_UPDATER_ENABLE && OPENTHREAD_FTD diff --git a/src/core/common/instance.cpp b/src/core/common/instance.cpp index d1bb08bd2..440dcdbec 100644 --- a/src/core/common/instance.cpp +++ b/src/core/common/instance.cpp @@ -81,9 +81,12 @@ Instance::Instance(void) #if OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE , mChannelMonitor(*this) #endif -#if OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE +#if OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE && OPENTHREAD_FTD , mChannelManager(*this) #endif +#if (OPENTHREAD_CONFIG_DATASET_UPDATER_ENABLE || OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE) && OPENTHREAD_FTD + , mDatasetUpdater(*this) +#endif #if OPENTHREAD_CONFIG_ANNOUNCE_SENDER_ENABLE , mAnnounceSender(*this) #endif diff --git a/src/core/common/instance.hpp b/src/core/common/instance.hpp index 38def2502..8a57c61ee 100644 --- a/src/core/common/instance.hpp +++ b/src/core/common/instance.hpp @@ -72,12 +72,15 @@ #include "thread/announce_sender.hpp" #include "thread/link_quality.hpp" #include "thread/thread_netif.hpp" -#if OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE +#if OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE && OPENTHREAD_FTD #include "utils/channel_manager.hpp" #endif #if OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE #include "utils/channel_monitor.hpp" #endif +#if (OPENTHREAD_CONFIG_DATASET_UPDATER_ENABLE || OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE) && OPENTHREAD_FTD +#include "utils/dataset_updater.hpp" +#endif #if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2) #include "backbone_router/bbr_leader.hpp" @@ -372,10 +375,14 @@ private: Utils::ChannelMonitor mChannelMonitor; #endif -#if OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE +#if OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE && OPENTHREAD_FTD Utils::ChannelManager mChannelManager; #endif +#if (OPENTHREAD_CONFIG_DATASET_UPDATER_ENABLE || OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE) && OPENTHREAD_FTD + Utils::DatasetUpdater mDatasetUpdater; +#endif + #if OPENTHREAD_CONFIG_ANNOUNCE_SENDER_ENABLE AnnounceSender mAnnounceSender; #endif @@ -721,13 +728,20 @@ template <> inline Utils::ChannelMonitor &Instance::Get(void) } #endif -#if OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE +#if OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE && OPENTHREAD_FTD template <> inline Utils::ChannelManager &Instance::Get(void) { return mChannelManager; } #endif +#if (OPENTHREAD_CONFIG_DATASET_UPDATER_ENABLE || OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE) && OPENTHREAD_FTD +template <> inline Utils::DatasetUpdater &Instance::Get(void) +{ + return mDatasetUpdater; +} +#endif + #if OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE template <> inline MeshCoP::BorderAgent &Instance::Get(void) { diff --git a/src/core/common/notifier.cpp b/src/core/common/notifier.cpp index 4a96300cd..79282ba90 100644 --- a/src/core/common/notifier.cpp +++ b/src/core/common/notifier.cpp @@ -145,8 +145,8 @@ void Notifier::EmitEvents(void) #if OPENTHREAD_CONFIG_CHILD_SUPERVISION_ENABLE Get().HandleNotifierEvents(events); #endif -#if OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE - Get().HandleNotifierEvents(events); +#if OPENTHREAD_CONFIG_DATASET_UPDATER_ENABLE || OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE + Get().HandleNotifierEvents(events); #endif #endif // OPENTHREAD_FTD #if OPENTHREAD_FTD || OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE || OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE diff --git a/src/core/config/dataset_updater.h b/src/core/config/dataset_updater.h new file mode 100644 index 000000000..1cff726a5 --- /dev/null +++ b/src/core/config/dataset_updater.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2020, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file + * This file includes compile-time configurations for Dataset Updater. + * + */ + +#ifndef CONFIG_DATASET_UPDATER_H_ +#define CONFIG_DATASET_UPDATER_H_ + +/** + * @def OPENTHREAD_CONFIG_DATASET_UPDATER_ENABLE + * + * Define as 1 to enable Dataset Updater support. + * + */ +#ifndef OPENTHREAD_CONFIG_DATASET_UPDATER_ENABLE +#define OPENTHREAD_CONFIG_DATASET_UPDATER_ENABLE 0 +#endif + +/** + * @def OPENTHREAD_CONFIG_DATASET_UPDATER_DEFAULT_DELAY + * + * Specifies the default delay (in ms) used by Dataset Updater when not included in Dataset already. + * + */ +#ifndef OPENTHREAD_CONFIG_DATASET_UPDATER_DEFAULT_DELAY +#define OPENTHREAD_CONFIG_DATASET_UPDATER_DEFAULT_DELAY 1000 +#endif + +/** + * @def OPENTHREAD_CONFIG_DATASET_UPDATER_DEFAULT_RETRY_WAIT_INTERVAL + * + * Specifies the default retry wait interval (in ms) for Dataset Updater to wait (in addition to Dataset delay) after + * sending MGMT Set Pending Dataset command waiting for Active Dataset to be updated before retrying again before + * retrying the MGMT Set Pending Dataset command. + * + */ +#ifndef OPENTHREAD_CONFIG_DATASET_UPDATER_DEFAULT_RETRY_WAIT_INTERVAL +#define OPENTHREAD_CONFIG_DATASET_UPDATER_DEFAULT_RETRY_WAIT_INTERVAL 1500 +#endif + +#endif // CONFIG_DATASET_UPDATER_H_ diff --git a/src/core/mac/mac_types.cpp b/src/core/mac/mac_types.cpp index f7873221f..df41a7f1d 100644 --- a/src/core/mac/mac_types.cpp +++ b/src/core/mac/mac_types.cpp @@ -145,6 +145,15 @@ exit: return error; } +bool NetworkName::operator==(const NetworkName &aOther) const +{ + NameData data = GetAsData(); + NameData otherData = aOther.GetAsData(); + + return (data.GetLength() == otherData.GetLength()) && + (memcmp(data.GetBuffer(), otherData.GetBuffer(), data.GetLength()) == 0); +} + #if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2) NameData DomainName::GetAsData(void) const { diff --git a/src/core/mac/mac_types.hpp b/src/core/mac/mac_types.hpp index a332c308c..cdb9718b8 100644 --- a/src/core/mac/mac_types.hpp +++ b/src/core/mac/mac_types.hpp @@ -570,6 +570,17 @@ public: * */ otError Set(const NameData &aNameData); + + /** + * This method overloads operator `==` to evaluate whether or not two given `NetworkName` objects are equal. + * + * @param[in] aOther The other `NetworkName` to compare with. + * + * @retval TRUE If the two are equal. + * @retval FALSE If the two are not equal. + * + */ + bool operator==(const NetworkName &aOther) const; }; #if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2) diff --git a/src/core/meshcop/dataset.cpp b/src/core/meshcop/dataset.cpp index 051980b70..9b561c6f2 100644 --- a/src/core/meshcop/dataset.cpp +++ b/src/core/meshcop/dataset.cpp @@ -95,6 +95,63 @@ exit: return error; } +bool Dataset::Info::IsSubsetOf(const Info &aOther) const +{ + bool isSubset = false; + + if (IsMasterKeyPresent()) + { + VerifyOrExit(aOther.IsMasterKeyPresent() && GetMasterKey() == aOther.GetMasterKey()); + } + + if (IsNetworkNamePresent()) + { + VerifyOrExit(aOther.IsNetworkNamePresent() && GetNetworkName() == aOther.GetNetworkName()); + } + + if (IsExtendedPanIdPresent()) + { + VerifyOrExit(aOther.IsExtendedPanIdPresent() && GetExtendedPanId() == aOther.GetExtendedPanId()); + } + + if (IsMeshLocalPrefixPresent()) + { + VerifyOrExit(aOther.IsMeshLocalPrefixPresent() && GetMeshLocalPrefix() == aOther.GetMeshLocalPrefix()); + } + + if (IsPanIdPresent()) + { + VerifyOrExit(aOther.IsPanIdPresent() && GetPanId() == aOther.GetPanId()); + } + + if (IsChannelPresent()) + { + VerifyOrExit(aOther.IsChannelPresent() && GetChannel() == aOther.GetChannel()); + } + + if (IsPskcPresent()) + { + VerifyOrExit(aOther.IsPskcPresent() && GetPskc() == aOther.GetPskc()); + } + + if (IsSecurityPolicyPresent()) + { + VerifyOrExit(aOther.IsSecurityPolicyPresent() && + GetSecurityPolicy().mRotationTime == aOther.GetSecurityPolicy().mRotationTime && + GetSecurityPolicy().mFlags == aOther.GetSecurityPolicy().mFlags); + } + + if (IsChannelMaskPresent()) + { + VerifyOrExit(aOther.IsChannelMaskPresent() && GetChannelMask() == aOther.GetChannelMask()); + } + + isSubset = true; + +exit: + return isSubset; +} + Dataset::Dataset(Type aType) : mUpdateTime(0) , mLength(0) diff --git a/src/core/meshcop/dataset.hpp b/src/core/meshcop/dataset.hpp index ca06ed3cc..3aa3ec9fb 100644 --- a/src/core/meshcop/dataset.hpp +++ b/src/core/meshcop/dataset.hpp @@ -550,6 +550,18 @@ public: mComponents.mIsSecurityPolicyPresent = true; } + /** + * This method sets the Security Policy in the Dataset. + * + * @param[in] aSecurityPolicy A Security Policy to set in Dataset. + * + */ + void SetSecurityPolicy(const otSecurityPolicy &aSecurityPolicy) + { + mSecurityPolicy = aSecurityPolicy; + mComponents.mIsSecurityPolicyPresent = true; + } + /** * This method indicates whether or not the Channel Mask is present in the Dataset. * @@ -595,6 +607,20 @@ public: * */ otError GenerateRandom(Instance &aInstance); + + /** + * This method checks whether the Dataset is a subset of another one, i.e., all the components in the current + * Dataset are also present in the @p aOther and the component values fully match. + * + * The matching of components in the two Datasets excludes Active/Pending Timestamp and Delay components. + * + * @param[in] aOther The other Dataset to check against. + * + * @retval TRUE The current dataset is a subset of @p aOther. + * @retval FALSE The current Dataset is not a subset of @p aOther. + * + */ + bool IsSubsetOf(const Info &aOther) const; }; /** diff --git a/src/core/openthread-core-config.h b/src/core/openthread-core-config.h index 5f79261bb..735030cc1 100644 --- a/src/core/openthread-core-config.h +++ b/src/core/openthread-core-config.h @@ -60,6 +60,7 @@ #include "config/child_supervision.h" #include "config/coap.h" #include "config/commissioner.h" +#include "config/dataset_updater.h" #include "config/dhcp6_client.h" #include "config/dhcp6_server.h" #include "config/diag.h" diff --git a/src/core/utils/channel_manager.cpp b/src/core/utils/channel_manager.cpp index f297d2f4a..888668271 100644 --- a/src/core/utils/channel_manager.cpp +++ b/src/core/utils/channel_manager.cpp @@ -40,6 +40,7 @@ #include "common/logging.hpp" #include "common/random.hpp" #include "radio/radio.hpp" +#include "utils/dataset_updater.hpp" #if OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE && OPENTHREAD_FTD @@ -50,7 +51,6 @@ ChannelManager::ChannelManager(Instance &aInstance) : InstanceLocator(aInstance) , mSupportedChannelMask(0) , mFavoredChannelMask(0) - , mActiveTimestamp(0) , mDelay(kMinimumDelay) , mChannel(0) , mState(kStateIdle) @@ -70,9 +70,14 @@ void ChannelManager::RequestChannelChange(uint8_t aChannel) ExitNow(); } - mState = kStateChangeRequested; - mChannel = aChannel; - mActiveTimestamp = 0; + if (mState == kStateChangeInProgress) + { + VerifyOrExit(mChannel != aChannel); + Get().CancelUpdate(); + } + + mState = kStateChangeRequested; + mChannel = aChannel; mTimer.Start(1 + Random::NonCrypto::GetUint32InRange(0, kRequestStartJitterInterval)); @@ -93,131 +98,57 @@ exit: return error; } -void ChannelManager::PreparePendingDataset(void) +void ChannelManager::StartDatasetUpdate(void) { - uint64_t pendingTimestamp = 0; - uint64_t pendingActiveTimestamp = 0; - uint32_t delayInMs = Time::SecToMsec(static_cast(mDelay)); MeshCoP::Dataset::Info dataset; - otError error; - VerifyOrExit(mState == kStateChangeRequested); - - VerifyOrExit(mChannel != Get().GetPanChannel()); - - if (Get().Read(dataset) == OT_ERROR_NONE) - { - if (dataset.IsPendingTimestampPresent()) - { - pendingTimestamp = dataset.GetPendingTimestamp(); - } - - // We check whether the Pending Dataset is changing the - // channel to same one as the current request (i.e., channel - // should match and delay should be less than the requested - // delay). - - if (dataset.IsChannelPresent() && (mChannel == dataset.GetChannel()) && dataset.IsDelayPresent() && - (dataset.GetDelay() <= delayInMs) && dataset.IsActiveTimestampPresent()) - { - // We save the active timestamp to later check and ensure it - // is ahead of current ActiveDataset timestamp. - - pendingActiveTimestamp = dataset.GetActiveTimestamp(); - } - } - - pendingTimestamp += 1 + Random::NonCrypto::GetUint32InRange(0, kMaxTimestampIncrease); - - error = Get().Read(dataset); - - if (error != OT_ERROR_NONE) - { - // If there is no valid Active Dataset but we are not disabled, set - // the timer to try again after the retry interval. This handles the - // situation where a channel change request comes right after the - // network is formed but before the active dataset is created. - - if (!Get().IsDisabled()) - { - mTimer.Start(kPendingDatasetTxRetryInterval); - } - else - { - otLogInfoUtil("ChannelManager: Request to change to channel %d failed. Device is disabled", mChannel); - - mState = kStateIdle; - StartAutoSelectTimer(); - } - - ExitNow(); - } - - // `pendingActiveTimestamp` will be non-zero if the Pending - // Dataset is valid and is performing the same channel change. - // We check to ensure its timestamp is indeed ahead of current - // Active Dataset's timestamp, and if so, we skip updating - // the Pending Dataset. - - if (pendingActiveTimestamp != 0) - { - if (dataset.GetActiveTimestamp() < pendingActiveTimestamp) - { - otLogInfoUtil("ChannelManager: Pending Dataset is valid for change channel to %d", mChannel); - mState = kStateSentMgmtPendingDataset; - mTimer.Start(delayInMs + kChangeCheckWaitInterval); - ExitNow(); - } - } - - // A non-zero `mActiveTimestamp` indicates that this is not the first - // attempt to update the Dataset for the ongoing requested channel - // change. In that case, if the Timestamp in current Active Dataset is - // more recent compared to `mActiveTimestamp`, the channel change - // process is canceled. This helps address situations where two - // different devices may be performing channel change around the same - // time. - - if (mActiveTimestamp != 0) - { - if (dataset.GetActiveTimestamp() >= mActiveTimestamp) - { - otLogInfoUtil("ChannelManager: Canceling channel change to %d since current ActiveDataset is more recent", - mChannel); - - ExitNow(); - } - } - else - { - mActiveTimestamp = - dataset.GetActiveTimestamp() + 1 + Random::NonCrypto::GetUint32InRange(0, kMaxTimestampIncrease); - } - - dataset.SetActiveTimestamp(mActiveTimestamp); + dataset.Clear(); dataset.SetChannel(mChannel); - dataset.SetPendingTimestamp(pendingTimestamp); - dataset.SetDelay(delayInMs); + dataset.SetDelay(Time::SecToMsec(mDelay)); - error = Get().SendSetRequest(dataset, nullptr, 0); - - if (error == OT_ERROR_NONE) + switch (Get().RequestUpdate(dataset, HandleDatasetUpdateDone, this, kChangeCheckWaitInterval)) { - otLogInfoUtil("ChannelManager: Sent PendingDatasetSet to change channel to %d", mChannel); + case OT_ERROR_NONE: + mState = kStateChangeInProgress; + // Wait for the `HandleDatasetUpdateDone()` callback. + break; - mState = kStateSentMgmtPendingDataset; - mTimer.Start(delayInMs + kChangeCheckWaitInterval); + case OT_ERROR_BUSY: + case OT_ERROR_NO_BUFS: + mTimer.Start(kPendingDatasetTxRetryInterval); + break; + + case OT_ERROR_INVALID_STATE: + otLogInfoUtil("ChannelManager: Request to change to channel %d failed. Device is disabled", mChannel); + + // Fall through + + default: + mState = kStateIdle; + StartAutoSelectTimer(); + break; + } +} + +void ChannelManager::HandleDatasetUpdateDone(otError aError, void *aContext) +{ + static_cast(aContext)->HandleDatasetUpdateDone(aError); +} + +void ChannelManager::HandleDatasetUpdateDone(otError aError) +{ + if (aError == OT_ERROR_NONE) + { + otLogInfoUtil("ChannelManager: Channel changed to %d", mChannel); } else { - otLogInfoUtil("ChannelManager: %s error in dataset update (channel change %d), retry in %d sec", - otThreadErrorToString(error), mChannel, Time::MsecToSec(kPendingDatasetTxRetryInterval)); - - mTimer.Start(kPendingDatasetTxRetryInterval); + otLogInfoUtil("ChannelManager: Canceling channel change to %d%s", mChannel, + (aError == OT_ERROR_ALREADY) ? " since current ActiveDataset is more recent" : ""); } -exit: - return; + mState = kStateIdle; + StartAutoSelectTimer(); } void ChannelManager::HandleTimer(Timer &aTimer) @@ -235,32 +166,15 @@ void ChannelManager::HandleTimer(void) StartAutoSelectTimer(); break; - case kStateSentMgmtPendingDataset: - otLogInfoUtil("ChannelManager: Timed out waiting for change to %d, trying again.", mChannel); - mState = kStateChangeRequested; - - // fall through - case kStateChangeRequested: - PreparePendingDataset(); + StartDatasetUpdate(); + break; + + case kStateChangeInProgress: break; } } -void ChannelManager::HandleNotifierEvents(Events aEvents) -{ - VerifyOrExit(aEvents.Contains(kEventThreadChannelChanged)); - VerifyOrExit(mChannel == Get().GetPanChannel()); - - mState = kStateIdle; - StartAutoSelectTimer(); - - otLogInfoUtil("ChannelManager: Channel successfully changed to %d", mChannel); - -exit: - return; -} - #if OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE otError ChannelManager::FindBetterChannel(uint8_t &aNewChannel, uint16_t &aOccupancy) diff --git a/src/core/utils/channel_manager.hpp b/src/core/utils/channel_manager.hpp index 941c848ef..a74b0eb25 100644 --- a/src/core/utils/channel_manager.hpp +++ b/src/core/utils/channel_manager.hpp @@ -40,7 +40,6 @@ #include "common/locator.hpp" #include "common/non_copyable.hpp" -#include "common/notifier.hpp" #include "common/timer.hpp" #include "mac/mac.hpp" @@ -56,9 +55,7 @@ namespace Utils { * @{ */ -#if OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE - -#if OPENTHREAD_FTD +#if OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE && OPENTHREAD_FTD /** * This class implements the Channel Manager. @@ -66,8 +63,6 @@ namespace Utils { */ class ChannelManager : public InstanceLocator, private NonCopyable { - friend class ot::Notifier; - public: enum { @@ -235,9 +230,6 @@ public: private: enum { - // Maximum increase of Pending/Active Dataset Timestamp per channel change request. - kMaxTimestampIncrease = 128, - // Retry interval to resend Pending Dataset in case of tx failure (in ms). kPendingDatasetTxRetryInterval = 20000, @@ -265,17 +257,18 @@ private: kCcaFailureRateThreshold = OPENTHREAD_CONFIG_CHANNEL_MANAGER_CCA_FAILURE_THRESHOLD, }; - enum State + enum State : uint8_t { kStateIdle, kStateChangeRequested, - kStateSentMgmtPendingDataset, + kStateChangeInProgress, }; + void StartDatasetUpdate(void); + static void HandleDatasetUpdateDone(otError aError, void *aContext); + void HandleDatasetUpdateDone(otError aError); static void HandleTimer(Timer &aTimer); void HandleTimer(void); - void HandleNotifierEvents(Events aEvents); - void PreparePendingDataset(void); void StartAutoSelectTimer(void); #if OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE @@ -285,7 +278,6 @@ private: Mac::ChannelMask mSupportedChannelMask; Mac::ChannelMask mFavoredChannelMask; - uint64_t mActiveTimestamp; uint16_t mDelay; uint8_t mChannel; State mState; @@ -294,17 +286,8 @@ private: bool mAutoSelectEnabled; }; -#else // OPENTHREAD_FTD +#endif // OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE && OPENTHREAD_FTD -class ChannelManager : private NonCopyable -{ -public: - explicit ChannelManager(Instance &) {} -}; - -#endif // OPENTHREAD_FTD - -#endif // OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE /** * @} * diff --git a/src/core/utils/dataset_updater.cpp b/src/core/utils/dataset_updater.cpp new file mode 100644 index 000000000..10aea32d6 --- /dev/null +++ b/src/core/utils/dataset_updater.cpp @@ -0,0 +1,249 @@ +/* + * Copyright (c) 2020, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file + * This file implements Dataset Updater. + * + */ + +#include "dataset_updater.hpp" + +#include "common/code_utils.hpp" +#include "common/instance.hpp" +#include "common/locator-getters.hpp" +#include "common/logging.hpp" +#include "common/random.hpp" + +#if (OPENTHREAD_CONFIG_DATASET_UPDATER_ENABLE || OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE) && OPENTHREAD_FTD + +namespace ot { +namespace Utils { + +DatasetUpdater::DatasetUpdater(Instance &aInstance) + : InstanceLocator(aInstance) + , mState(kStateIdle) + , mWaitInterval(kWaitInterval) + , mCallback(nullptr) + , mCallbackContext(nullptr) + , mTimer(aInstance, DatasetUpdater::HandleTimer, this) + , mDataset(nullptr) +{ +} + +otError DatasetUpdater::RequestUpdate(const MeshCoP::Dataset::Info &aDataset, + Callback aCallback, + void * aContext, + uint32_t aReryWaitInterval) +{ + otError error = OT_ERROR_NONE; + Message *message = nullptr; + + VerifyOrExit(!Get().IsDisabled(), error = OT_ERROR_INVALID_STATE); + VerifyOrExit(mState == kStateIdle, error = OT_ERROR_BUSY); + + VerifyOrExit(!aDataset.IsActiveTimestampPresent() && !aDataset.IsPendingTimestampPresent(), + error = OT_ERROR_INVALID_ARGS); + + message = Get().New(Message::kTypeOther, 0); + VerifyOrExit(message != nullptr, error = OT_ERROR_NO_BUFS); + + SuccessOrExit(error = message->Append(aDataset)); + + mCallback = aCallback; + mCallbackContext = aContext; + mWaitInterval = aReryWaitInterval; + mDataset = message; + mState = kStateUpdateRequested; + + PreparePendingDataset(); + +exit: + FreeMessageOnError(message, error); + return error; +} + +void DatasetUpdater::CancelUpdate(void) +{ + if (mState != kStateIdle) + { + FreeMessage(mDataset); + mState = kStateIdle; + mTimer.Stop(); + } +} + +void DatasetUpdater::HandleTimer(Timer &aTimer) +{ + aTimer.GetOwner().HandleTimer(); +} + +void DatasetUpdater::HandleTimer(void) +{ + switch (mState) + { + case kStateIdle: + break; + case kStateUpdateRequested: + case kStateSentMgmtPendingDataset: + PreparePendingDataset(); + break; + } +} + +void DatasetUpdater::PreparePendingDataset(void) +{ + otError error; + MeshCoP::Dataset::Info newDataset; + MeshCoP::Dataset::Info curDataset; + + VerifyOrExit(mState != kStateIdle); + + VerifyOrExit(!Get().IsDisabled(), Finish(OT_ERROR_INVALID_STATE)); + + error = Get().Read(curDataset); + + if (error != OT_ERROR_NONE) + { + // If there is no valid Active Dataset but MLE is not disabled, + // set the timer to try again after the retry interval. This + // handles the situation where a dataset update request comes + // right after the network is formed but before the active + // dataset is created. + + mState = kStateUpdateRequested; + mTimer.Start(kRetryInterval); + ExitNow(); + } + + IgnoreError(mDataset->Read(0, newDataset)); + + if (newDataset.IsSubsetOf(curDataset)) + { + // If new requested Dataset is already contained in the current + // Active Dataset, no change is required, and we can report the + // update to be successful. + + Finish(OT_ERROR_NONE); + ExitNow(); + } + + if (newDataset.IsActiveTimestampPresent()) + { + // Presence of the active timestamp in the new Dataset + // indicates that it is a retry. In this case, we ensure + // that the timestamp is ahead of current active dataset. + // This covers the case where another device in network + // requested a Dataset update after this device. + + VerifyOrExit(newDataset.GetActiveTimestamp() > curDataset.GetActiveTimestamp(), Finish(OT_ERROR_ALREADY)); + } + else + { + newDataset.SetActiveTimestamp(curDataset.GetActiveTimestamp() + + Random::NonCrypto::GetUint32InRange(1, kMaxTimestampIncrease)); + } + + if (!newDataset.IsDelayPresent()) + { + newDataset.SetDelay(kDefaultDelay); + } + + if (!newDataset.IsPendingTimestampPresent()) + { + uint32_t timestampIncrease = Random::NonCrypto::GetUint32InRange(1, kMaxTimestampIncrease); + + if (Get().Read(curDataset) == OT_ERROR_NONE) + { + newDataset.SetPendingTimestamp(curDataset.GetPendingTimestamp() + timestampIncrease); + } + else + { + newDataset.SetPendingTimestamp(timestampIncrease); + } + + mDataset->Write(0, newDataset); + } + + error = Get().SendSetRequest(newDataset, nullptr, 0); + + if (error == OT_ERROR_NONE) + { + mState = kStateSentMgmtPendingDataset; + mTimer.Start(newDataset.GetDelay() + mWaitInterval); + } + else + { + mTimer.Start(kRetryInterval); + } + +exit: + return; +} + +void DatasetUpdater::Finish(otError aError) +{ + FreeMessage(mDataset); + mState = kStateIdle; + + if (mCallback != nullptr) + { + mCallback(aError, mCallbackContext); + } +} + +void DatasetUpdater::HandleNotifierEvents(Events aEvents) +{ + VerifyOrExit(mState == kStateSentMgmtPendingDataset); + + if (aEvents.Contains(kEventActiveDatasetChanged)) + { + MeshCoP::Dataset::Info requestedDataset; + MeshCoP::Dataset::Info activeDataset; + + SuccessOrExit(Get().Read(activeDataset)); + IgnoreError(mDataset->Read(0, requestedDataset)); + + if (requestedDataset.IsSubsetOf(activeDataset)) + { + Finish(OT_ERROR_NONE); + } + else if (requestedDataset.GetActiveTimestamp() <= activeDataset.GetActiveTimestamp()) + { + Finish(OT_ERROR_ALREADY); + } + } + +exit: + return; +} + +} // namespace Utils +} // namespace ot + +#endif // #if (OPENTHREAD_CONFIG_DATASET_UPDATER_ENABLE || OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE) && OPENTHREAD_FTD diff --git a/src/core/utils/dataset_updater.hpp b/src/core/utils/dataset_updater.hpp new file mode 100644 index 000000000..63f13d3e5 --- /dev/null +++ b/src/core/utils/dataset_updater.hpp @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2020, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file + * This file includes definitions for Dataset Updater. + */ + +#ifndef DATASET_UPDATER_HPP_ +#define DATASET_UPDATER_HPP_ + +#include "openthread-core-config.h" + +#include + +#include "common/locator.hpp" +#include "common/message.hpp" +#include "common/non_copyable.hpp" +#include "common/notifier.hpp" +#include "common/timer.hpp" +#include "meshcop/dataset.hpp" +#include "meshcop/meshcop_tlvs.hpp" + +namespace ot { +namespace Utils { + +#if (OPENTHREAD_CONFIG_DATASET_UPDATER_ENABLE || OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE) && OPENTHREAD_FTD + +/** + * This class implements the Dataset Updater. + * + */ +class DatasetUpdater : public InstanceLocator, private NonCopyable +{ + friend class ot::Notifier; + +public: + /** + * This constructor initializes a `DatasetUpdater` object. + * + * @param[in] aInstance A reference to the OpenThread instance. + * + */ + explicit DatasetUpdater(Instance &aInstance); + + /** + * This type represents the callback function pointer which is called when a Dataset update request finishes, + * reporting success or failure status of the request. + * + * The function pointer has the syntax `void (*Callback)(otError aError, void *aContext)`. + * + * @param[in] aError The error status. + * OT_ERROR_NONE indicates Dataset update successfully finished. + * OT_ERROR_INVALID_STATE indicates failure due invalid state (MLE being disabled). + * OT_ERROR_ALREADY indicates failure due to another device within network requesting + * a conflicting Dataset update. + * @param[in] aContext A pointer to the arbitrary context provided by the user. + * + */ + typedef otDatasetUpdaterCallback Callback; + + /** + * This method requests an update to Operational Dataset. + * + * @p aDataset should contain the fields to be updated and their new value. It must not contain Active or Pending + * Timestamp fields. The Delay field is optional, if not provided a default value (`kDefaultDelay`) would be used. + * + * @param[in] aDataset Dataset info containing fields to change. + * @param[in] aCallback A callback to indicate when Dataset update request finishes. + * @param[in] aContext An arbitrary context passed to callback. + * @param[in] aRetryWaitInterval The wait time after sending Pending dataset before retrying (interval in ms). + * + * @retval OT_ERROR_NONE Dataset update started successfully (@p aCallback will be invoked on completion). + * @retval OT_ERROR_INVALID_STATE Device is disabled (MLE is disabled). + * @retval OT_ERROR_INVALID_ARGS The @p aDataset is not valid (contains Active or Pending Timestamp). + * @retval OT_ERROR_BUSY Cannot start update, a previous one is ongoing. + * @retval OT_ERROR_NO_BUFS Could not allocated buffer to save Dataset. + * + */ + otError RequestUpdate(const MeshCoP::Dataset::Info &aDataset, + Callback aCallback, + void * aContext, + uint32_t aReryWaitInterval = kWaitInterval); + + /** + * This method cancels an ongoing (if any) Operational Dataset update request. + * + */ + void CancelUpdate(void); + + /** + * This method indicates whether there is an ongoing Operation Dataset update request. + * + * @retval TRUE There is an ongoing update. + * @retval FALSE There is no ongoing update. + * + */ + bool IsUpdateOngoing(void) const { return (mState != kStateIdle); } + +private: + enum State : uint8_t + { + kStateIdle, + kStateUpdateRequested, + kStateSentMgmtPendingDataset, + }; + + enum : uint32_t + { + // Default delay (in ms) in Pending Dataset. + kDefaultDelay = OPENTHREAD_CONFIG_DATASET_UPDATER_DEFAULT_DELAY, + + // Default wait interval (in ms) after sending Pending Dataset to retry (in addition Dataset Delay) + kWaitInterval = OPENTHREAD_CONFIG_DATASET_UPDATER_DEFAULT_RETRY_WAIT_INTERVAL, + + kRetryInterval = 1000, // In ms. Retry interval when preparing and/or sending Pending Dataset fails. + kMaxTimestampIncrease = 128, // Maximum increase of Pending/Active Timestamp during Dataset Update. + }; + + static void HandleTimer(Timer &aTimer); + void HandleTimer(void); + void PreparePendingDataset(void); + void Finish(otError aError); + void HandleNotifierEvents(Events aEvents); + + State mState; + uint32_t mWaitInterval; + Callback mCallback; + void * mCallbackContext; + TimerMilli mTimer; + Message * mDataset; +}; + +#endif // (OPENTHREAD_CONFIG_DATASET_UPDATER_ENABLE || OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE) && OPENTHREAD_FTD + +} // namespace Utils +} // namespace ot + +#endif // DATASET_UPDATER_HPP_