From 4e45b45796642c88bfcd162856511083a40f7835 Mon Sep 17 00:00:00 2001 From: Jonathan Hui Date: Tue, 23 Apr 2019 22:10:40 -0700 Subject: [PATCH] [dataset] allow setting partially complete active dataset (#3773) When a partial dataset is stored, the device will attempt to attach to a Thread network using the limited parameters available. If the device successfully attaches, the device will then obtain the complete Active Dataset from its Parent. If the device is router-capable, it will not become a Router/Leader until it has successfully retrieved a complete Active Dataset. Saving a partial dataset supports out-of-band commissioning scenarios. --- include/openthread/dataset.h | 13 ++++++++ src/core/meshcop/dataset.cpp | 47 +++++++++++++--------------- src/core/meshcop/dataset_local.cpp | 21 +++++++------ src/core/meshcop/dataset_local.hpp | 12 ++++++- src/core/meshcop/dataset_manager.cpp | 15 ++++++--- src/core/meshcop/dataset_manager.hpp | 12 +++++++ src/core/thread/mle.cpp | 14 +++++++-- src/core/thread/mle_router.cpp | 1 + 8 files changed, 92 insertions(+), 43 deletions(-) diff --git a/include/openthread/dataset.h b/include/openthread/dataset.h index f857d5ff8..a4618c5e1 100644 --- a/include/openthread/dataset.h +++ b/include/openthread/dataset.h @@ -306,6 +306,19 @@ OTAPI otError OTCALL otDatasetGetActive(otInstance *aInstance, otOperationalData /** * This function sets the Active Operational Dataset. * + * If the dataset does not include an Active Timestamp, the dataset is only partially complete. + * + * If Thread is enabled on a device that has a partially complete Active Dataset, the device will attempt to attach to + * an existing Thread network using any existing information in the dataset. The minimum set of information needed to + * attach is the PAN ID and Thread Master Key. + * + * If channel is not included in the dataset, the device will send MLE Announce messages across different channels to + * find neighbors on other channels. + * + * If the device successfully attaches to a Thread network, the device will then retrieve the full Active Dataset from + * its Parent. Note that a router-capable device will not transition to the Router or Leader roles until it has a + * complete Active Dataset. + * * @param[in] aInstance A pointer to an OpenThread instance. * @param[in] aDataset A pointer to the Active Operational Dataset. * diff --git a/src/core/meshcop/dataset.cpp b/src/core/meshcop/dataset.cpp index ac073476e..8fc29953c 100644 --- a/src/core/meshcop/dataset.cpp +++ b/src/core/meshcop/dataset.cpp @@ -257,34 +257,32 @@ void Dataset::Set(const Dataset &aDataset) otError Dataset::Set(const otOperationalDataset &aDataset) { - otError error = OT_ERROR_NONE; - MeshCoP::ActiveTimestampTlv activeTimestampTlv; + otError error = OT_ERROR_NONE; - VerifyOrExit(aDataset.mComponents.mIsActiveTimestampPresent, error = OT_ERROR_INVALID_ARGS); - - activeTimestampTlv.Init(); - activeTimestampTlv.SetSeconds(aDataset.mActiveTimestamp); - activeTimestampTlv.SetTicks(0); - Set(activeTimestampTlv); - - if (mType == Tlv::kPendingTimestamp) + if (aDataset.mComponents.mIsActiveTimestampPresent) { - MeshCoP::PendingTimestampTlv pendingTimestampTlv; + MeshCoP::ActiveTimestampTlv tlv; + tlv.Init(); + tlv.SetSeconds(aDataset.mActiveTimestamp); + tlv.SetTicks(0); + Set(tlv); + } - VerifyOrExit(aDataset.mComponents.mIsPendingTimestampPresent, error = OT_ERROR_INVALID_ARGS); + if (aDataset.mComponents.mIsPendingTimestampPresent) + { + MeshCoP::PendingTimestampTlv tlv; + tlv.Init(); + tlv.SetSeconds(aDataset.mPendingTimestamp); + tlv.SetTicks(0); + Set(tlv); + } - pendingTimestampTlv.Init(); - pendingTimestampTlv.SetSeconds(aDataset.mPendingTimestamp); - pendingTimestampTlv.SetTicks(0); - Set(pendingTimestampTlv); - - if (aDataset.mComponents.mIsDelayPresent) - { - MeshCoP::DelayTimerTlv tlv; - tlv.Init(); - tlv.SetDelayTimer(aDataset.mDelay); - Set(tlv); - } + if (aDataset.mComponents.mIsDelayPresent) + { + MeshCoP::DelayTimerTlv tlv; + tlv.Init(); + tlv.SetDelayTimer(aDataset.mDelay); + Set(tlv); } if (aDataset.mComponents.mIsChannelPresent) @@ -362,7 +360,6 @@ otError Dataset::Set(const otOperationalDataset &aDataset) mUpdateTime = TimerMilli::GetNow(); -exit: return error; } diff --git a/src/core/meshcop/dataset_local.cpp b/src/core/meshcop/dataset_local.cpp index 9af9b5a67..4d818c447 100644 --- a/src/core/meshcop/dataset_local.cpp +++ b/src/core/meshcop/dataset_local.cpp @@ -61,9 +61,10 @@ DatasetLocal::DatasetLocal(Instance &aInstance, const Tlv::Type aType) void DatasetLocal::Clear(void) { + Get().DeleteOperationalDataset(IsActive()); mTimestamp.Init(); mTimestampPresent = false; - Get().DeleteOperationalDataset(IsActive()); + mSaved = false; } otError DatasetLocal::Restore(Dataset &aDataset) @@ -71,9 +72,12 @@ otError DatasetLocal::Restore(Dataset &aDataset) const Timestamp *timestamp; otError error; + mTimestampPresent = false; + error = Read(aDataset); SuccessOrExit(error); + mSaved = true; timestamp = aDataset.GetTimestamp(); if (timestamp != NULL) @@ -81,10 +85,6 @@ otError DatasetLocal::Restore(Dataset &aDataset) mTimestamp = *timestamp; mTimestampPresent = true; } - else - { - mTimestampPresent = false; - } exit: return error; @@ -161,21 +161,22 @@ exit: otError DatasetLocal::Save(const Dataset &aDataset) { const Timestamp *timestamp; - otError error; + otError error = OT_ERROR_NONE; if (aDataset.GetSize() == 0) { - error = Get().DeleteOperationalDataset(IsActive()); + // do not propagate error back + Get().DeleteOperationalDataset(IsActive()); + mSaved = false; otLogInfoMeshCoP("%s dataset deleted", mType == Tlv::kActiveTimestamp ? "Active" : "Pending"); } else { - error = Get().SaveOperationalDataset(IsActive(), aDataset); + SuccessOrExit(error = Get().SaveOperationalDataset(IsActive(), aDataset)); + mSaved = true; otLogInfoMeshCoP("%s dataset set", mType == Tlv::kActiveTimestamp ? "Active" : "Pending"); } - SuccessOrExit(error); - timestamp = aDataset.GetTimestamp(); if (timestamp != NULL) diff --git a/src/core/meshcop/dataset_local.hpp b/src/core/meshcop/dataset_local.hpp index c4a169553..9e77c63ab 100644 --- a/src/core/meshcop/dataset_local.hpp +++ b/src/core/meshcop/dataset_local.hpp @@ -71,6 +71,15 @@ public: */ void Clear(void); + /** + * This method indicates whether an Active or Pending Dataset is saved in non-volatile memory. + * + * @retval TRUE if an Active or Pending Dataset is saved in non-volatile memory. + * @retval FALSE if an Active or Pending Dataset is not saved in non-volatile memory. + * + */ + bool IsSaved(void) const { return mSaved; } + /** * This method restores and retrieves the dataset from non-volatile memory. * @@ -149,7 +158,8 @@ private: Timestamp mTimestamp; ///< Active or Pending Timestamp uint32_t mUpdateTime; ///< Local time last updated Tlv::Type mType; ///< Active or Pending - bool mTimestampPresent : 1; ///< Whether or not timestamp is present + bool mTimestampPresent : 1; ///< Whether a timestamp is present + bool mSaved : 1; ///< Whether a dataset is saved in non-volatile }; } // namespace MeshCoP diff --git a/src/core/meshcop/dataset_manager.cpp b/src/core/meshcop/dataset_manager.cpp index 1ea9a0527..c445b8ca4 100644 --- a/src/core/meshcop/dataset_manager.cpp +++ b/src/core/meshcop/dataset_manager.cpp @@ -92,6 +92,8 @@ otError DatasetManager::Restore(void) mTimer.Stop(); + mTimestampValid = false; + SuccessOrExit(error = mLocal.Restore(dataset)); timestamp = dataset.GetTimestamp(); @@ -100,11 +102,11 @@ otError DatasetManager::Restore(void) { mTimestamp = *timestamp; mTimestampValid = true; + } - if (mLocal.GetType() == Tlv::kActiveTimestamp) - { - dataset.ApplyConfiguration(GetInstance()); - } + if (mLocal.GetType() == Tlv::kActiveTimestamp) + { + dataset.ApplyConfiguration(GetInstance()); } exit: @@ -703,6 +705,11 @@ ActiveDataset::ActiveDataset(Instance &aInstance) Get().AddResource(mResourceGet); } +bool ActiveDataset::IsPartiallyComplete(void) const +{ + return mLocal.IsSaved() && !mTimestampValid; +} + otError ActiveDataset::Save(const Timestamp &aTimestamp, const Message &aMessage, uint16_t aOffset, uint8_t aLength) { otError error = OT_ERROR_NONE; diff --git a/src/core/meshcop/dataset_manager.hpp b/src/core/meshcop/dataset_manager.hpp index 27a8ad8ba..67bd7c663 100644 --- a/src/core/meshcop/dataset_manager.hpp +++ b/src/core/meshcop/dataset_manager.hpp @@ -313,6 +313,18 @@ public: */ explicit ActiveDataset(Instance &aInstance); + /** + * This method indicates whether the Active Dataset is partially complete. + * + * This method is primarily used to determine whether a user has supplied a partial Active Dataset for use + * with joining a network. + * + * @retval TRUE if an Active Dataset is saved but does not include an Active Timestamp. + * @retval FALSE if an Active Dataset is not saved or does include an Active Timestamp. + * + */ + bool IsPartiallyComplete(void) const; + /** * This method clears the Active Operational Dataset. * diff --git a/src/core/thread/mle.cpp b/src/core/thread/mle.cpp index 6ea4517f5..a65caad10 100644 --- a/src/core/thread/mle.cpp +++ b/src/core/thread/mle.cpp @@ -1713,9 +1713,13 @@ bool Mle::PrepareAnnounceState(void) bool shouldAnnounce = false; Mac::ChannelMask channelMask; - VerifyOrExit((mRole != OT_DEVICE_ROLE_CHILD) && !IsFullThreadDevice() && (mReattachState == kReattachStop)); + VerifyOrExit((mRole != OT_DEVICE_ROLE_CHILD) && (mReattachState == kReattachStop) && + (Get().IsPartiallyComplete() || !IsFullThreadDevice())); - SuccessOrExit(Get().GetChannelMask(channelMask)); + if (Get().GetChannelMask(channelMask) != OT_ERROR_NONE) + { + channelMask = Get().GetSupportedChannelMask(); + } mAnnounceDelay = kAnnounceTimeout / (channelMask.GetNumberOfChannels() + 1); @@ -2374,7 +2378,11 @@ otError Mle::SendOrphanAnnounce(void) otError error; Mac::ChannelMask channelMask; - SuccessOrExit(error = Get().GetChannelMask(channelMask)); + if (Get().GetChannelMask(channelMask) != OT_ERROR_NONE) + { + channelMask = Get().GetSupportedChannelMask(); + } + SuccessOrExit(error = channelMask.GetNextChannel(mAnnounceChannel)); SendAnnounce(mAnnounceChannel, true); diff --git a/src/core/thread/mle_router.cpp b/src/core/thread/mle_router.cpp index 0eff1749e..7f4914e25 100644 --- a/src/core/thread/mle_router.cpp +++ b/src/core/thread/mle_router.cpp @@ -169,6 +169,7 @@ otError MleRouter::BecomeLeader(void) uint32_t partitionId; uint8_t leaderId; + VerifyOrExit(!Get().IsPartiallyComplete(), error = OT_ERROR_INVALID_STATE); VerifyOrExit(mRole != OT_DEVICE_ROLE_DISABLED, error = OT_ERROR_INVALID_STATE); VerifyOrExit(mRole != OT_DEVICE_ROLE_LEADER, error = OT_ERROR_NONE); VerifyOrExit(IsRouterRoleEnabled(), error = OT_ERROR_NOT_CAPABLE);