From ad4b597262fdc726b14a46844a2a95bc21047664 Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Mon, 19 Mar 2018 15:45:04 -0700 Subject: [PATCH] [settings] adding Settings class (#2606) --- etc/visual-studio/libopenthread.vcxproj | 1 + .../libopenthread.vcxproj.filters | 3 + etc/visual-studio/libopenthread_k.vcxproj | 1 + .../libopenthread_k.vcxproj.filters | 3 + src/core/Makefile.am | 1 + src/core/api/thread_api.cpp | 13 +- src/core/common/instance.cpp | 8 +- src/core/common/instance.hpp | 10 + src/core/common/settings.cpp | 324 +++++++++++++ src/core/common/settings.hpp | 429 +++++++++++++++--- src/core/meshcop/dataset.cpp | 17 - src/core/meshcop/dataset.hpp | 18 +- src/core/meshcop/dataset_local.cpp | 27 +- src/core/meshcop/dataset_local.hpp | 4 +- src/core/thread/mle.cpp | 64 ++- src/core/thread/mle.hpp | 6 + src/core/thread/mle_router.cpp | 41 +- 17 files changed, 801 insertions(+), 169 deletions(-) create mode 100644 src/core/common/settings.cpp diff --git a/etc/visual-studio/libopenthread.vcxproj b/etc/visual-studio/libopenthread.vcxproj index 6c705b71a..5258d1fcc 100644 --- a/etc/visual-studio/libopenthread.vcxproj +++ b/etc/visual-studio/libopenthread.vcxproj @@ -82,6 +82,7 @@ + diff --git a/etc/visual-studio/libopenthread.vcxproj.filters b/etc/visual-studio/libopenthread.vcxproj.filters index d11a2c08f..2e4558714 100644 --- a/etc/visual-studio/libopenthread.vcxproj.filters +++ b/etc/visual-studio/libopenthread.vcxproj.filters @@ -144,6 +144,9 @@ Source Files\common + + Source Files\common + Source Files\common diff --git a/etc/visual-studio/libopenthread_k.vcxproj b/etc/visual-studio/libopenthread_k.vcxproj index 633ab275a..68b2b8bbd 100644 --- a/etc/visual-studio/libopenthread_k.vcxproj +++ b/etc/visual-studio/libopenthread_k.vcxproj @@ -91,6 +91,7 @@ + diff --git a/etc/visual-studio/libopenthread_k.vcxproj.filters b/etc/visual-studio/libopenthread_k.vcxproj.filters index 9a8231810..36caed498 100644 --- a/etc/visual-studio/libopenthread_k.vcxproj.filters +++ b/etc/visual-studio/libopenthread_k.vcxproj.filters @@ -144,6 +144,9 @@ Source Files\common + + Source Files\common + Source Files\common diff --git a/src/core/Makefile.am b/src/core/Makefile.am index 9db5c4404..06c55aa6d 100644 --- a/src/core/Makefile.am +++ b/src/core/Makefile.am @@ -123,6 +123,7 @@ SOURCES_COMMON = \ common/locator.cpp \ common/notifier.cpp \ common/message.cpp \ + common/settings.cpp \ common/tasklet.cpp \ common/timer.cpp \ common/tlvs.cpp \ diff --git a/src/core/api/thread_api.cpp b/src/core/api/thread_api.cpp index 99760a62b..ec87a4268 100644 --- a/src/core/api/thread_api.cpp +++ b/src/core/api/thread_api.cpp @@ -36,7 +36,6 @@ #include "openthread-core-config.h" #include -#include #include "common/instance.hpp" #include "common/logging.hpp" @@ -497,10 +496,10 @@ exit: bool otThreadGetAutoStart(otInstance *aInstance) { #if OPENTHREAD_CONFIG_ENABLE_AUTO_START_SUPPORT - uint8_t autoStart = 0; - uint16_t autoStartLength = sizeof(autoStart); + uint8_t autoStart = 0; + Instance &instance = *static_cast(aInstance); - if (otPlatSettingsGet(aInstance, Settings::kKeyThreadAutoStart, 0, &autoStart, &autoStartLength) != OT_ERROR_NONE) + if (instance.GetSettings().ReadThreadAutoStart(autoStart) != OT_ERROR_NONE) { autoStart = 0; } @@ -515,8 +514,10 @@ bool otThreadGetAutoStart(otInstance *aInstance) otError otThreadSetAutoStart(otInstance *aInstance, bool aStartAutomatically) { #if OPENTHREAD_CONFIG_ENABLE_AUTO_START_SUPPORT - uint8_t autoStart = aStartAutomatically ? 1 : 0; - return otPlatSettingsSet(aInstance, Settings::kKeyThreadAutoStart, &autoStart, sizeof(autoStart)); + uint8_t autoStart = aStartAutomatically ? 1 : 0; + Instance &instance = *static_cast(aInstance); + + return instance.GetSettings().SaveThreadAutoStart(autoStart); #else OT_UNUSED_VARIABLE(aInstance); OT_UNUSED_VARIABLE(aStartAutomatically); diff --git a/src/core/common/instance.cpp b/src/core/common/instance.cpp index d3acacd07..af950d2fe 100644 --- a/src/core/common/instance.cpp +++ b/src/core/common/instance.cpp @@ -36,7 +36,6 @@ #include "instance.hpp" #include -#include #include "common/logging.hpp" #include "common/new.hpp" @@ -56,6 +55,7 @@ Instance::Instance(void) , mEnergyScanCallback(NULL) , mEnergyScanCallbackContext(NULL) , mNotifier(*this) + , mSettings(*this) , mTimerMilliScheduler(*this) #if OPENTHREAD_CONFIG_ENABLE_PLATFORM_USEC_TIMER , mTimerMicroScheduler(*this) @@ -134,7 +134,7 @@ void Instance::AfterInit(void) // Restore datasets and network information - otPlatSettingsInit(this); + GetSettings().Init(); mThreadNetif.GetMle().Restore(); #if OPENTHREAD_CONFIG_ENABLE_AUTO_START_SUPPORT @@ -175,7 +175,7 @@ void Instance::Reset(void) void Instance::FactoryReset(void) { - otPlatSettingsWipe(this); + GetSettings().Wipe(); otPlatReset(this); } @@ -184,7 +184,7 @@ otError Instance::ErasePersistentInfo(void) otError error = OT_ERROR_NONE; VerifyOrExit(mThreadNetif.GetMle().GetRole() == OT_DEVICE_ROLE_DISABLED, error = OT_ERROR_INVALID_STATE); - otPlatSettingsWipe(this); + GetSettings().Wipe(); exit: return error; diff --git a/src/core/common/instance.hpp b/src/core/common/instance.hpp index a38e0953a..c51b0b944 100644 --- a/src/core/common/instance.hpp +++ b/src/core/common/instance.hpp @@ -52,6 +52,7 @@ #include "crypto/mbedtls.hpp" #endif #include "common/notifier.hpp" +#include "common/settings.hpp" #include "net/ip6.hpp" #include "thread/link_quality.hpp" #include "thread/thread_netif.hpp" @@ -234,6 +235,14 @@ public: */ Notifier &GetNotifier(void) { return mNotifier; } + /** + * This method returns a reference to the `Settings` object. + * + * @returns A reference to the `Settings` object. + * + */ + Settings &GetSettings(void) { return mSettings; } + /** * This method returns a reference to the tasklet scheduler object. * @@ -361,6 +370,7 @@ private: void * mEnergyScanCallbackContext; Notifier mNotifier; + Settings mSettings; TaskletScheduler mTaskletScheduler; TimerMilliScheduler mTimerMilliScheduler; diff --git a/src/core/common/settings.cpp b/src/core/common/settings.cpp new file mode 100644 index 000000000..7cee7f085 --- /dev/null +++ b/src/core/common/settings.cpp @@ -0,0 +1,324 @@ +/* + * Copyright (c) 2018, 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 non-volatile storage of settings. + */ + +#define WPP_NAME "settings.tmh" + +#include "settings.hpp" + +#include + +#include "common/code_utils.hpp" +#include "common/instance.hpp" +#include "common/logging.hpp" +#include "meshcop/dataset.hpp" +#include "thread/mle.hpp" + +namespace ot { + +#if (OPENTHREAD_CONFIG_LOG_UTIL != 0) +#if (OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_INFO) + +void SettingsBase::LogNetworkInfo(const char *aAction, const NetworkInfo &aNetworkInfo) const +{ + char string[Mac::Address::kAddressStringSize]; + + otLogInfoCore(GetInstance(), + "Non-volatile: %s NetworkInfo {rloc:0x%04x, extaddr:%s, role:%s, mode:0x%02x, keyseq:0x%x, ...", + aAction, aNetworkInfo.mRloc16, aNetworkInfo.mExtAddress.ToString(string, sizeof(string)), + Mle::Mle::RoleToString(static_cast(aNetworkInfo.mRole)), aNetworkInfo.mDeviceMode, + aNetworkInfo.mKeySequence); + + otLogInfoCore(GetInstance(), + "Non-volatile: ... pid:0x%x, mlecntr:0x%x, maccntr:0x%x, mliid:%02x%02x%02x%02x%02x%02x%02x%02x}", + aNetworkInfo.mPreviousPartitionId, aNetworkInfo.mMleFrameCounter, aNetworkInfo.mMacFrameCounter, + aNetworkInfo.mMlIid[0], aNetworkInfo.mMlIid[1], aNetworkInfo.mMlIid[2], aNetworkInfo.mMlIid[3], + aNetworkInfo.mMlIid[4], aNetworkInfo.mMlIid[5], aNetworkInfo.mMlIid[6], aNetworkInfo.mMlIid[7]); +} + +void SettingsBase::LogParentInfo(const char *aAction, const ParentInfo &aParentInfo) const +{ + char string[Mac::Address::kAddressStringSize]; + + otLogInfoCore(GetInstance(), "Non-volatile: %s ParentInfo {extaddr:%s}", aAction, + aParentInfo.mExtAddress.ToString(string, sizeof(string))); +} + +void SettingsBase::LogChildInfo(const char *aAction, const ChildInfo &aChildInfo) const +{ + char string[Mac::Address::kAddressStringSize]; + + otLogInfoCore(GetInstance(), "Non-volatile: %s ChildInfo {rloc:0x%04x, extaddr:%s, timeout:%u, mode:0x%02x}", + aAction, aChildInfo.mRloc16, aChildInfo.mExtAddress.ToString(string, sizeof(string)), + aChildInfo.mTimeout, aChildInfo.mMode); +} + +#endif // #if (OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_INFO) + +#if (OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_WARN) + +void SettingsBase::LogFailure(otError error, const char *aText) const +{ + if (error != OT_ERROR_NONE) + { + otLogWarnCore(GetInstance(), "Non-volatile: Error %s %s", otThreadErrorToString(error), aText); + } +} + +#endif // #if (OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_WARN) +#endif // #if (OPENTHREAD_CONFIG_LOG_UTIL != 0) + +void Settings::Init(void) +{ + otPlatSettingsInit(&GetInstance()); +} + +void Settings::Wipe(void) +{ + otPlatSettingsWipe(&GetInstance()); + otLogInfoCore(GetInstance(), "Non-volatile: Wiped all info"); +} + +otError Settings::SaveOperationalDataset(bool aIsActive, const MeshCoP::Dataset &aDataset) +{ + otError error = Save(aIsActive ? kKeyActiveDataset : kKeyPendingDataset, aDataset.GetBytes(), aDataset.GetSize()); + + LogFailure(error, "saving OperationalDataset"); + return error; +} + +otError Settings::ReadOperationalDataset(bool aIsActive, MeshCoP::Dataset &aDataset) const +{ + otError error = OT_ERROR_NONE; + uint16_t length; + + SuccessOrExit(error = Read(aIsActive ? kKeyActiveDataset : kKeyPendingDataset, aDataset.GetBytes(), + MeshCoP::Dataset::kMaxSize, length)); + aDataset.SetSize(length); + +exit: + return error; +} + +otError Settings::DeleteOperationalDataset(bool aIsActive) +{ + otError error = Delete(aIsActive ? kKeyActiveDataset : kKeyPendingDataset); + + LogFailure(error, "deleting OperationalDataset"); + return error; +} + +otError Settings::ReadNetworkInfo(NetworkInfo &aNetworkInfo) const +{ + otError error; + + SuccessOrExit(error = ReadFixedSize(kKeyNetworkInfo, &aNetworkInfo, sizeof(NetworkInfo))); + LogNetworkInfo("Read", aNetworkInfo); + +exit: + return error; +} + +otError Settings::SaveNetworkInfo(const NetworkInfo &aNetworkInfo) +{ + otError error; + + SuccessOrExit(error = Save(kKeyNetworkInfo, &aNetworkInfo, sizeof(NetworkInfo))); + LogNetworkInfo("Saved", aNetworkInfo); + +exit: + LogFailure(error, "saving NetworkInfo"); + return error; +} + +otError Settings::DeleteNetworkInfo(void) +{ + otError error; + + SuccessOrExit(error = Delete(kKeyNetworkInfo)); + otLogInfoCore(GetInstance(), "Non-volatile: Deleted NetworkInfo"); + +exit: + LogFailure(error, "deleting NetworkInfo"); + return error; +} + +otError Settings::ReadParentInfo(ParentInfo &aParentInfo) const +{ + otError error; + + SuccessOrExit(error = ReadFixedSize(kKeyParentInfo, &aParentInfo, sizeof(ParentInfo))); + LogParentInfo("Read", aParentInfo); + +exit: + return error; +} + +otError Settings::SaveParentInfo(const ParentInfo &aParentInfo) +{ + otError error; + + SuccessOrExit(error = Save(kKeyParentInfo, &aParentInfo, sizeof(ParentInfo))); + LogParentInfo("Saved", aParentInfo); + +exit: + LogFailure(error, "saving ParentInfo"); + return error; +} + +otError Settings::DeleteParentInfo(void) +{ + otError error; + + SuccessOrExit(error = Delete(kKeyParentInfo)); + otLogInfoCore(GetInstance(), "Non-volatile: Deleted ParentInfo"); + +exit: + LogFailure(error, "deleting ParentInfo"); + return error; +} + +otError Settings::AddChildInfo(const ChildInfo &aChildInfo) +{ + otError error; + + SuccessOrExit(error = Add(kKeyChildInfo, &aChildInfo, sizeof(aChildInfo))); + LogChildInfo("Added", aChildInfo); + +exit: + LogFailure(error, "adding ChildInfo"); + return error; +} + +otError Settings::DeleteChildInfo(void) +{ + otError error; + + SuccessOrExit(error = Delete(kKeyChildInfo)); + otLogInfoCore(GetInstance(), "Non-volatile: Deleted all ChildInfo"); + +exit: + LogFailure(error, "deleting all ChildInfo"); + return error; +} + +Settings::ChildInfoIterator::ChildInfoIterator(Instance &aInstance) + : SettingsBase(aInstance) + , mIndex(0) + , mIsDone(false) +{ + Reset(); +} + +void Settings::ChildInfoIterator::Reset(void) +{ + mIndex = 0; + mIsDone = false; + Read(); +} + +void Settings::ChildInfoIterator::Advance(void) +{ + if (!mIsDone) + { + mIndex++; + Read(); + } +} + +otError Settings::ChildInfoIterator::Delete(void) +{ + otError error = OT_ERROR_NONE; + + VerifyOrExit(!mIsDone, error = OT_ERROR_INVALID_STATE); + SuccessOrExit(error = otPlatSettingsDelete(&GetInstance(), kKeyChildInfo, mIndex)); + LogChildInfo("Removed", mChildInfo); + +exit: + LogFailure(error, "removing ChildInfo entry"); + return error; +} + +void Settings::ChildInfoIterator::Read(void) +{ + uint16_t size = sizeof(ChildInfo); + otError error; + + SuccessOrExit(error = otPlatSettingsGet(&GetInstance(), kKeyChildInfo, mIndex, + reinterpret_cast(&mChildInfo), &size)); + VerifyOrExit(size >= sizeof(ChildInfo), error = OT_ERROR_NOT_FOUND); + LogChildInfo("Read", mChildInfo); + +exit: + mIsDone = (error != OT_ERROR_NONE); +} + +otError Settings::ReadFixedSize(Key aKey, void *aBuffer, uint16_t aExpectedSize) const +{ + uint16_t size = aExpectedSize; + otError error; + + SuccessOrExit(error = otPlatSettingsGet(&GetInstance(), aKey, 0, reinterpret_cast(aBuffer), &size)); + VerifyOrExit(size >= aExpectedSize, error = OT_ERROR_NOT_FOUND); + +exit: + return error; +} + +otError Settings::Read(Key aKey, void *aBuffer, uint16_t aMaxBufferSize, uint16_t &aReadSize) const +{ + uint16_t size = aMaxBufferSize; + otError error; + + SuccessOrExit(error = otPlatSettingsGet(&GetInstance(), aKey, 0, reinterpret_cast(aBuffer), &size)); + aReadSize = (size <= aMaxBufferSize) ? size : aMaxBufferSize; + +exit: + return error; +} + +otError Settings::Save(Key aKey, const void *aBuffer, uint16_t aSize) +{ + return otPlatSettingsSet(&GetInstance(), aKey, reinterpret_cast(aBuffer), aSize); +} + +otError Settings::Add(Key aKey, const void *aBuffer, uint16_t aSize) +{ + return otPlatSettingsAdd(&GetInstance(), aKey, reinterpret_cast(aBuffer), aSize); +} + +otError Settings::Delete(Key aKey) +{ + return otPlatSettingsDelete(&GetInstance(), aKey, -1); +} + +} // namespace ot diff --git a/src/core/common/settings.hpp b/src/core/common/settings.hpp index c0b809e4b..c7d5ae621 100644 --- a/src/core/common/settings.hpp +++ b/src/core/common/settings.hpp @@ -28,7 +28,7 @@ /** * @file - * This file includes functions for debugging. + * This file includes definitions for non-volatile storage of settings. */ #ifndef SETTINGS_HPP_ @@ -36,86 +36,387 @@ #include "openthread-core-config.h" +#include "common/locator.hpp" +#include "mac/mac_frame.hpp" #include "thread/mle.hpp" namespace ot { -namespace Settings { + +namespace MeshCoP { +class Dataset; +} /** - * Rules for updating existing value structures. + * This class defines the base class used by `Settings` and `Settings::ChildInfoIterator`. * - * 1. Modifying existing otPlatSettings* key value fields MUST only be - * done by appending new fields. Existing fields MUST NOT be - * deleted or modified in any way. - * - * 2. To support backward compatibility (rolling back to an older - * software version), code reading and processing key values MUST - * process key values that have longer length. Additionally, newer - * versions MUST update/maintain values in existing key value - * fields. - * - * 3. To support forward compatibility (rolling forward to a newer - * software version), code reading and processing key values MUST - * process key values that have shorter length. - * - * 4. New Key IDs may be defined in the future with the understanding - * that such key values are not backward compatible. + * This class provides structure definitions for different settings keys. * */ - -/** - * This enumeration defines the keys of settings - * - */ -enum +class SettingsBase : public InstanceLocator { - kKeyActiveDataset = 0x0001, ///< Active Operational Dataset - kKeyPendingDataset = 0x0002, ///< Pending Operational Dataset - kKeyNetworkInfo = 0x0003, ///< Thread network information - kKeyParentInfo = 0x0004, ///< Parent information - kKeyChildInfo = 0x0005, ///< Child information - kKeyThreadAutoStart = 0x0006, ///< Auto-start information +public: + /** + * Rules for updating existing value structures. + * + * 1. Modifying existing key value fields in settings MUST only be + * done by appending new fields. Existing fields MUST NOT be + * deleted or modified in any way. + * + * 2. To support backward compatibility (rolling back to an older + * software version), code reading and processing key values MUST + * process key values that have longer length. Additionally, newer + * versions MUST update/maintain values in existing key value + * fields. + * + * 3. To support forward compatibility (rolling forward to a newer + * software version), code reading and processing key values MUST + * process key values that have shorter length. + * + * 4. New Key IDs may be defined in the future with the understanding + * that such key values are not backward compatible. + * + */ + + /** + * This structure represents the device's own network information for settings storage. + * + */ + struct NetworkInfo + { + uint8_t mRole; ///< Current Thread role. + uint8_t mDeviceMode; ///< Device mode setting. + uint16_t mRloc16; ///< RLOC16 + uint32_t mKeySequence; ///< Key Sequence + uint32_t mMleFrameCounter; ///< MLE Frame Counter + uint32_t mMacFrameCounter; ///< MAC Frame Counter + uint32_t mPreviousPartitionId; ///< PartitionId + Mac::ExtAddress mExtAddress; ///< Extended Address + uint8_t mMlIid[OT_IP6_IID_SIZE]; ///< IID from ML-EID + }; + + /** + * This structure represents the parent information for settings storage. + * + */ + struct ParentInfo + { + Mac::ExtAddress mExtAddress; ///< Extended Address + }; + + /** + * This structure represents the child information for settings storage. + * + */ + struct ChildInfo + { + Mac::ExtAddress mExtAddress; ///< Extended Address + uint32_t mTimeout; ///< Timeout + uint16_t mRloc16; ///< RLOC16 + uint8_t mMode; ///< The MLE device mode + }; + +protected: + /** + * This enumeration defines the keys of settings. + * + */ + enum Key + { + kKeyActiveDataset = 0x0001, ///< Active Operational Dataset + kKeyPendingDataset = 0x0002, ///< Pending Operational Dataset + kKeyNetworkInfo = 0x0003, ///< Thread network information + kKeyParentInfo = 0x0004, ///< Parent information + kKeyChildInfo = 0x0005, ///< Child information + kKeyThreadAutoStart = 0x0006, ///< Auto-start information + }; + + explicit SettingsBase(Instance &aInstance) + : InstanceLocator(aInstance) + { + } + +#if (OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_INFO) && (OPENTHREAD_CONFIG_LOG_UTIL != 0) + void LogNetworkInfo(const char *aAction, const NetworkInfo &aNetworkInfo) const; + void LogParentInfo(const char *aAction, const ParentInfo &aParentInfo) const; + void LogChildInfo(const char *aAction, const ChildInfo &aChildInfo) const; +#else + void LogNetworkInfo(const char *, const NetworkInfo &) const {} + void LogParentInfo(const char *, const ParentInfo &) const {} + void LogChildInfo(const char *, const ChildInfo &) const {} +#endif + +#if (OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_WARN) && (OPENTHREAD_CONFIG_LOG_UTIL != 0) + void LogFailure(otError aError, const char *aAction) const; +#else + void LogFailure(otError, const char *) const {} +#endif }; /** - * This structure represents the device's own network information for settings storage. + * This class defines methods related to non-volatile storage of settings. * */ -struct NetworkInfo +class Settings : public SettingsBase { - uint8_t mRole; ///< Current Thread role. - uint8_t mDeviceMode; ///< Device mode setting. - uint16_t mRloc16; ///< RLOC16 - uint32_t mKeySequence; ///< Key Sequence - uint32_t mMleFrameCounter; ///< MLE Frame Counter - uint32_t mMacFrameCounter; ///< MAC Frame Counter - uint32_t mPreviousPartitionId; ///< PartitionId - Mac::ExtAddress mExtAddress; ///< Extended Address - uint8_t mMlIid[OT_IP6_IID_SIZE]; ///< IID from ML-EID +public: + /** + * This constructor initializes a `Settings` object. + * + * @param[in] aInstance A reference to the OpenThread instance. + * + */ + explicit Settings(Instance &aInstance) + : SettingsBase(aInstance) + { + } + + /** + * This method initializes the platform settings (non-volatile) module. + * + * This should be called before any other method from this class. + * + */ + void Init(void); + + /** + * This method removes all settings from the non-volatile store. + * + */ + void Wipe(void); + + /** + * This method saves the Operational Dataset (active or pending). + * + * @param[in] aIsActive Indicates whether Dataset is active or pending. + * @param[in] aDataset A reference to a `Dataset` object to be saved. + * + * @retval OT_ERROR_NONE Successfully saved the Dataset. + * @retval OT_ERROR_NOT_IMPLEMENTED The platform does not implement settings functionality. + * + */ + otError SaveOperationalDataset(bool aIsActive, const MeshCoP::Dataset &aDataset); + + /** + * This method reads the Operational Dataset (active or pending). + * + * @param[in] aIsActive Indicates whether Dataset is active or pending. + * @param[out] aDataset A reference to a `Dataset` object to output the read content. + * + * @retval OT_ERROR_NONE Successfully read the Dataset. + * @retval OT_ERROR_NOT_FOUND No corresponding value in the setting store. + * @retval OT_ERROR_NOT_IMPLEMENTED The platform does not implement settings functionality. + * + */ + otError ReadOperationalDataset(bool aIsActive, MeshCoP::Dataset &aDataset) const; + + /** + * This method deletes the Operational Dataset (active/pending) from settings. + * + * @param[in] aIsActive Indicates whether Dataset is active or pending. + * + * @retval OT_ERROR_NONE Successfully deleted the Dataset. + * @retval OT_ERROR_NOT_IMPLEMENTED The platform does not implement settings functionality. + * + */ + otError DeleteOperationalDataset(bool aIsActive); + + /** + * This method saves Network Info. + * + * @param[in] aNetworkInfo A reference to a `NetworkInfo` structure to be saved. + * + * @retval OT_ERROR_NONE Successfully saved Network Info in settings. + * @retval OT_ERROR_NOT_IMPLEMENTED The platform does not implement settings functionality. + * + */ + otError SaveNetworkInfo(const NetworkInfo &aNetworkInfo); + + /** + * This method reads Network Info. + * + * @param[out] aNetworkInfo A reference to a `NetworkInfo` structure to output the read content. + * + * @retval OT_ERROR_NONE Successfully read the Network Info. + * @retval OT_ERROR_NOT_FOUND No corresponding value in the setting store. + * @retval OT_ERROR_NOT_IMPLEMENTED The platform does not implement settings functionality. + * + */ + otError ReadNetworkInfo(NetworkInfo &aNetworkInfo) const; + + /** + * This method deletes Network Info from settings. + * + * @retval OT_ERROR_NONE Successfully deleted the value. + * @retval OT_ERROR_NOT_IMPLEMENTED The platform does not implement settings functionality. + * + */ + otError DeleteNetworkInfo(void); + + /** + * This method saves Parent Info. + * + * @param[in] aParentInfo A reference to a `ParentInfo` structure to be saved. + * + * @retval OT_ERROR_NONE Successfully saved Parent Info in settings. + * @retval OT_ERROR_NOT_IMPLEMENTED The platform does not implement settings functionality. + * + */ + otError SaveParentInfo(const ParentInfo &aParentInfo); + + /** + * This method reads Parent Info. + * + * @param[out] aParentInfo A reference to a `ParentInfo` structure to output the read content. + * + * @retval OT_ERROR_NONE Successfully read the Parent Info. + * @retval OT_ERROR_NOT_FOUND No corresponding value in the setting store. + * @retval OT_ERROR_NOT_IMPLEMENTED The platform does not implement settings functionality. + * + */ + otError ReadParentInfo(ParentInfo &aParentInfo) const; + + /** + * This method deletes Parent Info from settings. + * + * @retval OT_ERROR_NONE Successfully deleted the value. + * @retval OT_ERROR_NOT_IMPLEMENTED The platform does not implement settings functionality. + * + */ + otError DeleteParentInfo(void); + + /** + * This method saves ThreadAutoStart. + * + * @param[in] aAutoStart A value to be saved (0 or 1). + * + * @retval OT_ERROR_NONE Successfully saved the value. + * @retval OT_ERROR_NOT_IMPLEMENTED The platform does not implement settings functionality. + * + */ + otError SaveThreadAutoStart(uint8_t aAutoStart) { return Save(kKeyThreadAutoStart, &aAutoStart, sizeof(uint8_t)); } + + /** + * This method reads ThreadAutoStart . + * + * @param[out] aAutoStart A reference to a `uint8_t` to output the read value + * + * @retval OT_ERROR_NONE Successfully read the value. + * @retval OT_ERROR_NOT_FOUND No corresponding value in the setting store. + * @retval OT_ERROR_NOT_IMPLEMENTED The platform does not implement settings functionality. + * + */ + otError ReadThreadAutoStart(uint8_t &aAutoStart) const + { + return ReadFixedSize(kKeyThreadAutoStart, &aAutoStart, sizeof(uint8_t)); + } + + /** + * This method deletes ThreadAutoStart value from settings. + * + * @retval OT_ERROR_NONE Successfully deleted the value. + * @retval OT_ERROR_NOT_IMPLEMENTED The platform does not implement settings functionality. + * + */ + otError DeleteThreadAutoStart(void) { return Delete(kKeyThreadAutoStart); } + + /** + * This method adds a Child Info entry to settings. + * + * @note Child Info is a list-based settings property and can contain multiple entries. + * + * @param[in] aChildInfo A reference to a `ChildInfo` structure to be saved/added. + * + * @retval OT_ERROR_NONE Successfully saved the Child Info in settings. + * @retval OT_ERROR_NOT_IMPLEMENTED The platform does not implement settings functionality. + * + */ + otError AddChildInfo(const ChildInfo &aChildInfo); + + /** + * This method deletes all Child Info entries from the settings. + * + * @note Child Info is a list-based settings property and can contain multiple entries. + * + * @retval OT_ERROR_NONE Successfully deleted the value. + * @retval OT_ERROR_NOT_IMPLEMENTED The platform does not implement settings functionality. + * + */ + otError DeleteChildInfo(void); + + /** + * This class defines an iterator to access all Child Info entries in the settings. + * + */ + class ChildInfoIterator : public SettingsBase + { + public: + /** + * This constructor initializes a `ChildInfoInterator` object. + * + * @param[in] aInstance A reference to the OpenThread instance. + * + */ + ChildInfoIterator(Instance &aInstance); + + /** + * This method resets the iterator to start from the first Child Info entry in the list. + * + */ + void Reset(void); + + /** + * This method indicates whether there are no more Child Info entries in the list (iterator has reached end of + * the list), or the current entry is valid. + * + * @retval TRUE There are no more entries in the list (reached end of the list). + * @retval FALSE The current entry is valid. + * + */ + bool IsDone(void) { return mIsDone; } + + /** + * This method advances the iterator to move to the next Child Info entry in the list (if any). + * + */ + void Advance(void); + + /** + * This method gets the Child Info corresponding to the current iterator entry in the list. + * + * @note This method should be used only if `IsDone()` is returning FALSE indicating that the iterator is + * pointing to a valid entry. + * + * @returns A reference to `ChildInfo` structure corresponding to current iterator entry. + * + */ + const ChildInfo &GetChildInfo(void) const { return mChildInfo; } + + /** + * This method deletes the current Child Info entry. + * + * @retval OT_ERROR_NONE The entry was deleted successfully. + * @retval OT_ERROR_INVALID_STATE The entry is not valid (iterator has reached end of list). + * @retval OT_ERROR_NOT_IMPLEMENTED The platform does not implement settings functionality. + * + */ + otError Delete(void); + + private: + void Read(void); + + ChildInfo mChildInfo; + uint8_t mIndex; + bool mIsDone; + }; + +private: + otError ReadFixedSize(Key aKey, void *aBuffer, uint16_t aExpectedLength) const; + otError Read(Key aKey, void *aBuffer, uint16_t aMaxBufferSize, uint16_t &aReadSize) const; + otError Save(Key aKey, const void *aValue, uint16_t aSize); + otError Add(Key aKey, const void *aValue, uint16_t aSize); + otError Delete(Key aKey); }; -/** - * This structure represents the parent information for settings storage. - * - */ -struct ParentInfo -{ - Mac::ExtAddress mExtAddress; ///< Extended Address -}; - -/** - * This structure represents the child information for settings storage. - * - */ -struct ChildInfo -{ - Mac::ExtAddress mExtAddress; ///< Extended Address - uint32_t mTimeout; ///< Timeout - uint16_t mRloc16; ///< RLOC16 - uint8_t mMode; ///< The MLE device mode -}; - -} // namespace Settings } // namespace ot #endif // SETTINGS_HPP_ diff --git a/src/core/meshcop/dataset.cpp b/src/core/meshcop/dataset.cpp index e64062158..5c7888de0 100644 --- a/src/core/meshcop/dataset.cpp +++ b/src/core/meshcop/dataset.cpp @@ -41,7 +41,6 @@ #include "common/code_utils.hpp" #include "common/instance.hpp" #include "common/logging.hpp" -#include "common/settings.hpp" #include "meshcop/meshcop_tlvs.hpp" #include "thread/mle_tlvs.hpp" @@ -509,22 +508,6 @@ exit: return error; } -uint16_t Dataset::GetSettingsKey(void) -{ - uint16_t rval; - - if (mType == Tlv::kActiveTimestamp) - { - rval = static_cast(Settings::kKeyActiveDataset); - } - else - { - rval = static_cast(Settings::kKeyPendingDataset); - } - - return rval; -} - void Dataset::Remove(uint8_t *aStart, uint8_t aLength) { memmove(aStart, aStart + aLength, mLength - (static_cast(aStart - mTlvs) + aLength)); diff --git a/src/core/meshcop/dataset.hpp b/src/core/meshcop/dataset.hpp index 9c21620a2..7c937307c 100644 --- a/src/core/meshcop/dataset.hpp +++ b/src/core/meshcop/dataset.hpp @@ -85,6 +85,14 @@ public: */ const Tlv *Get(Tlv::Type aType) const; + /** + * This method returns a pointer to the byte representation of the Dataset. + * + * @returns A pointer to the byte representation of the Dataset. + * + */ + uint8_t *GetBytes(void) { return mTlvs; } + /** * This method returns a pointer to the byte representation of the Dataset. * @@ -107,6 +115,14 @@ public: */ uint16_t GetSize(void) const { return mLength; } + /** + * This method sets the Dataset size in bytes. + * + * @param[in] aSize The Dataset size in bytes. + * + */ + void SetSize(uint16_t aSize) { mLength = aSize; } + /** * This method returns the local time the dataset was last updated. * @@ -216,8 +232,6 @@ public: otError ConvertToActive(void); private: - uint16_t GetSettingsKey(void); - void Remove(uint8_t *aStart, uint8_t aLength); uint8_t mTlvs[kMaxSize]; ///< The Dataset buffer diff --git a/src/core/meshcop/dataset_local.cpp b/src/core/meshcop/dataset_local.cpp index 01107ab6b..d55a4b2b8 100644 --- a/src/core/meshcop/dataset_local.cpp +++ b/src/core/meshcop/dataset_local.cpp @@ -38,8 +38,6 @@ #include -#include - #include "common/code_utils.hpp" #include "common/instance.hpp" #include "common/logging.hpp" @@ -64,7 +62,7 @@ void DatasetLocal::Clear(void) { mTimestamp.Init(); mTimestampPresent = false; - otPlatSettingsDelete(&GetInstance(), GetSettingsKey(), -1); + GetInstance().GetSettings().DeleteOperationalDataset(IsActive()); } otError DatasetLocal::Restore(Dataset &aDataset) @@ -97,8 +95,7 @@ otError DatasetLocal::Get(Dataset &aDataset) const uint32_t elapsed; otError error; - aDataset.mLength = sizeof(aDataset.mTlvs); - error = otPlatSettingsGet(&GetInstance(), GetSettingsKey(), 0, aDataset.mTlvs, &aDataset.mLength); + error = GetInstance().GetSettings().ReadOperationalDataset(IsActive(), aDataset); VerifyOrExit(error == OT_ERROR_NONE, aDataset.mLength = 0); if (mType == Tlv::kActiveTimestamp) @@ -171,12 +168,12 @@ otError DatasetLocal::Set(const Dataset &aDataset) if (aDataset.GetSize() == 0) { - error = otPlatSettingsDelete(&GetInstance(), GetSettingsKey(), 0); + error = GetInstance().GetSettings().DeleteOperationalDataset(IsActive()); otLogInfoMeshCoP(GetInstance(), "%s dataset deleted", mType == Tlv::kActiveTimestamp ? "Active" : "Pending"); } else { - error = otPlatSettingsSet(&GetInstance(), GetSettingsKey(), aDataset.GetBytes(), aDataset.GetSize()); + error = GetInstance().GetSettings().SaveOperationalDataset(IsActive(), aDataset); otLogInfoMeshCoP(GetInstance(), "%s dataset set", mType == Tlv::kActiveTimestamp ? "Active" : "Pending"); } @@ -200,22 +197,6 @@ exit: return error; } -uint16_t DatasetLocal::GetSettingsKey(void) const -{ - uint16_t rval; - - if (mType == Tlv::kActiveTimestamp) - { - rval = static_cast(Settings::kKeyActiveDataset); - } - else - { - rval = static_cast(Settings::kKeyPendingDataset); - } - - return rval; -} - int DatasetLocal::Compare(const Timestamp *aCompareTimestamp) { int rval = 1; diff --git a/src/core/meshcop/dataset_local.hpp b/src/core/meshcop/dataset_local.hpp index 16d9e37a2..81fd4c027 100644 --- a/src/core/meshcop/dataset_local.hpp +++ b/src/core/meshcop/dataset_local.hpp @@ -145,8 +145,8 @@ public: int Compare(const Timestamp *aCompare); private: - uint16_t GetSettingsKey(void) const; - void SetTimestamp(const Dataset &aDataset); + bool IsActive(void) const { return (mType == Tlv::kActiveTimestamp); } + void SetTimestamp(const Dataset &aDataset); Timestamp mTimestamp; ///< Active or Pending Timestamp uint32_t mUpdateTime; ///< Local time last updated diff --git a/src/core/thread/mle.cpp b/src/core/thread/mle.cpp index d64a7c7d5..feb7cc500 100644 --- a/src/core/thread/mle.cpp +++ b/src/core/thread/mle.cpp @@ -36,7 +36,6 @@ #include "mle.hpp" #include -#include #include "common/code_utils.hpp" #include "common/debug.hpp" @@ -293,19 +292,16 @@ otError Mle::Stop(bool aClearNetworkDatasets) otError Mle::Restore(void) { - ThreadNetif & netif = GetNetif(); - otError error = OT_ERROR_NONE; + ThreadNetif & netif = GetNetif(); + Settings & settings = GetInstance().GetSettings(); + otError error = OT_ERROR_NONE; Settings::NetworkInfo networkInfo; Settings::ParentInfo parentInfo; - uint16_t length; netif.GetActiveDataset().Restore(); netif.GetPendingDataset().Restore(); - length = sizeof(networkInfo); - SuccessOrExit(error = otPlatSettingsGet(&netif.GetInstance(), Settings::kKeyNetworkInfo, 0, - reinterpret_cast(&networkInfo), &length)); - VerifyOrExit(length >= sizeof(networkInfo), error = OT_ERROR_NOT_FOUND); + SuccessOrExit(error = settings.ReadNetworkInfo(networkInfo)); netif.GetKeyManager().SetCurrentKeySequence(networkInfo.mKeySequence); netif.GetKeyManager().SetMleFrameCounter(networkInfo.mMleFrameCounter); @@ -328,10 +324,7 @@ otError Mle::Restore(void) if (!IsActiveRouter(networkInfo.mRloc16)) { - length = sizeof(parentInfo); - - error = otPlatSettingsGet(&netif.GetInstance(), Settings::kKeyParentInfo, 0, - reinterpret_cast(&parentInfo), &length); + error = settings.ReadParentInfo(parentInfo); if (error != OT_ERROR_NONE) { @@ -346,8 +339,6 @@ otError Mle::Restore(void) ExitNow(); } - VerifyOrExit(length >= sizeof(parentInfo), error = OT_ERROR_PARSE); - memset(&mParent, 0, sizeof(mParent)); mParent.SetExtAddress(*static_cast(&parentInfo.mExtAddress)); mParent.SetDeviceMode(ModeTlv::kModeFFD | ModeTlv::kModeRxOnWhenIdle | ModeTlv::kModeFullNetworkData | @@ -372,8 +363,9 @@ exit: otError Mle::Store(void) { - ThreadNetif & netif = GetNetif(); - otError error = OT_ERROR_NONE; + ThreadNetif & netif = GetNetif(); + Settings & settings = GetInstance().GetSettings(); + otError error = OT_ERROR_NONE; Settings::NetworkInfo networkInfo; memset(&networkInfo, 0, sizeof(networkInfo)); @@ -396,16 +388,13 @@ otError Mle::Store(void) memset(&parentInfo, 0, sizeof(parentInfo)); parentInfo.mExtAddress = mParent.GetExtAddress(); - SuccessOrExit(error = otPlatSettingsSet(&netif.GetInstance(), Settings::kKeyParentInfo, - reinterpret_cast(&parentInfo), sizeof(parentInfo))); + SuccessOrExit(error = settings.SaveParentInfo(parentInfo)); } } else { // when not attached, read out any existing values so that we do not change them - uint16_t length = sizeof(networkInfo); - IgnoreReturnValue(otPlatSettingsGet(&netif.GetInstance(), Settings::kKeyNetworkInfo, 0, - reinterpret_cast(&networkInfo), &length)); + IgnoreReturnValue(settings.ReadNetworkInfo(networkInfo)); } // update MAC and MLE Frame Counters even when we are not attached MLE messages are sent before a device attached @@ -415,8 +404,7 @@ otError Mle::Store(void) networkInfo.mMacFrameCounter = netif.GetKeyManager().GetMacFrameCounter() + OPENTHREAD_CONFIG_STORE_FRAME_COUNTER_AHEAD; - SuccessOrExit(error = otPlatSettingsSet(&netif.GetInstance(), Settings::kKeyNetworkInfo, - reinterpret_cast(&networkInfo), sizeof(networkInfo))); + SuccessOrExit(error = settings.SaveNetworkInfo(networkInfo)); netif.GetKeyManager().SetStoredMleFrameCounter(networkInfo.mMleFrameCounter); netif.GetKeyManager().SetStoredMacFrameCounter(networkInfo.mMacFrameCounter); @@ -3661,5 +3649,35 @@ void Mle::LogMleMessage(const char *aLogString, const Ip6::Address &aAddress, ui OT_UNUSED_VARIABLE(aRloc); } +const char *Mle::RoleToString(otDeviceRole aRole) +{ + const char *roleString = "Unknown"; + + switch (aRole) + { + case OT_DEVICE_ROLE_DISABLED: + roleString = "Disabled"; + break; + + case OT_DEVICE_ROLE_DETACHED: + roleString = "Detached"; + break; + + case OT_DEVICE_ROLE_CHILD: + roleString = "Child"; + break; + + case OT_DEVICE_ROLE_ROUTER: + roleString = "Router"; + break; + + case OT_DEVICE_ROLE_LEADER: + roleString = "Leader"; + break; + } + + return roleString; +} + } // namespace Mle } // namespace ot diff --git a/src/core/thread/mle.hpp b/src/core/thread/mle.hpp index 494ccd639..cc854d7b6 100644 --- a/src/core/thread/mle.hpp +++ b/src/core/thread/mle.hpp @@ -951,6 +951,12 @@ public: */ void RemoveDelayedDataResponseMessage(void); + /** + * This method converts a device role into a human-readable string. + * + */ + static const char *RoleToString(otDeviceRole aRole); + protected: enum { diff --git a/src/core/thread/mle_router.cpp b/src/core/thread/mle_router.cpp index 6945b8fa2..0e2a30f64 100644 --- a/src/core/thread/mle_router.cpp +++ b/src/core/thread/mle_router.cpp @@ -36,8 +36,6 @@ #include "mle_router.hpp" -#include - #include "common/code_utils.hpp" #include "common/debug.hpp" #include "common/encoding.hpp" @@ -3787,20 +3785,14 @@ void MleRouter::RestoreChildren(void) { otError error = OT_ERROR_NONE; bool foundDuplicate = false; - uint8_t index; + uint8_t numChildren = 0; - for (index = 0;; index++) + for (Settings::ChildInfoIterator iter(GetInstance()); !iter.IsDone(); iter.Advance()) { - Child * child; - Settings::ChildInfo childInfo; - uint16_t length; + Child * child; + const Settings::ChildInfo &childInfo = iter.GetChildInfo(); - length = sizeof(childInfo); - SuccessOrExit(otPlatSettingsGet(&GetInstance(), Settings::kKeyChildInfo, index, - reinterpret_cast(&childInfo), &length)); - VerifyOrExit(length >= sizeof(childInfo), error = OT_ERROR_PARSE); - - child = FindChild(*static_cast(&childInfo.mExtAddress)); + child = FindChild(*static_cast(&childInfo.mExtAddress)); if (child == NULL) { @@ -3813,18 +3805,19 @@ void MleRouter::RestoreChildren(void) memset(child, 0, sizeof(*child)); - child->SetExtAddress(*static_cast(&childInfo.mExtAddress)); + child->SetExtAddress(*static_cast(&childInfo.mExtAddress)); child->SetRloc16(childInfo.mRloc16); child->SetTimeout(childInfo.mTimeout); child->SetDeviceMode(childInfo.mMode); child->SetState(Neighbor::kStateRestored); child->SetLastHeard(TimerMilli::GetNow()); GetNetif().GetMeshForwarder().GetSourceMatchController().SetSrcMatchAsShort(*child, true); + numChildren++; } exit: - if (foundDuplicate || (index > kMaxChildren) || (error != OT_ERROR_NONE)) + if (foundDuplicate || (numChildren > kMaxChildren) || (error != OT_ERROR_NONE)) { // If there is any error, e.g., there are more saved children // in non-volatile settings than could be restored or there are @@ -3840,18 +3833,11 @@ otError MleRouter::RemoveStoredChild(uint16_t aChildRloc16) { otError error = OT_ERROR_NOT_FOUND; - for (uint8_t i = 0; i < kMaxChildren; i++) + for (Settings::ChildInfoIterator iter(GetInstance()); !iter.IsDone(); iter.Advance()) { - Settings::ChildInfo childInfo; - uint16_t length = sizeof(childInfo); - - SuccessOrExit(error = otPlatSettingsGet(&GetInstance(), Settings::kKeyChildInfo, i, - reinterpret_cast(&childInfo), &length)); - VerifyOrExit(length >= sizeof(childInfo), error = OT_ERROR_PARSE); - - if (childInfo.mRloc16 == aChildRloc16) + if (iter.GetChildInfo().mRloc16 == aChildRloc16) { - error = otPlatSettingsDelete(&GetInstance(), Settings::kKeyChildInfo, i); + error = iter.Delete(); ExitNow(); } } @@ -3876,8 +3862,7 @@ otError MleRouter::StoreChild(uint16_t aChildRloc16) childInfo.mRloc16 = child->GetRloc16(); childInfo.mMode = child->GetDeviceMode(); - error = otPlatSettingsAdd(&GetInstance(), Settings::kKeyChildInfo, reinterpret_cast(&childInfo), - sizeof(childInfo)); + error = GetInstance().GetSettings().AddChildInfo(childInfo); exit: return error; @@ -3887,7 +3872,7 @@ otError MleRouter::RefreshStoredChildren(void) { otError error = OT_ERROR_NONE; - SuccessOrExit(error = otPlatSettingsDelete(&GetInstance(), Settings::kKeyChildInfo, -1)); + SuccessOrExit(error = GetInstance().GetSettings().DeleteChildInfo()); for (uint8_t i = 0; i < kMaxChildren; i++) {