/* * Copyright (c) 2016, 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 MeshCoP Datasets manager to process commands. */ #include "meshcop/dataset_manager.hpp" #if OPENTHREAD_FTD #include "instance/instance.hpp" namespace ot { namespace MeshCoP { RegisterLogModule("DatasetManager"); //---------------------------------------------------------------------------------------------------------------------- // DatasetManager Error DatasetManager::ProcessSetOrReplaceRequest(MgmtCommand aCommand, const Coap::Message &aMessage, RequestInfo &aInfo) const { Error error = kErrorParse; Dataset dataset; OffsetRange offsetRange; Timestamp activeTimestamp; ChannelTlvValue channelValue; uint16_t sessionId; Ip6::NetworkPrefix meshLocalPrefix; NetworkKey networkKey; uint16_t panId; uint32_t delayTimer; aInfo.Clear(); offsetRange.InitFromMessageOffsetToEnd(aMessage); SuccessOrExit(dataset.SetFrom(aMessage, offsetRange)); SuccessOrExit(dataset.ValidateTlvs()); // Verify that the request includes timestamps that are // ahead of the locally stored values. SuccessOrExit(dataset.Read(activeTimestamp)); if (IsPendingDataset()) { Timestamp pendingTimestamp; SuccessOrExit(dataset.Read(pendingTimestamp)); VerifyOrExit(pendingTimestamp > mLocalTimestamp); } else { VerifyOrExit(activeTimestamp > mLocalTimestamp); } // Determine whether the new Dataset affects connectivity // or network key. if ((dataset.Read(channelValue) == kErrorNone) && (channelValue.GetChannel() != Get().GetPanChannel())) { aInfo.mAffectsConnectivity = true; } if ((dataset.Read(panId) == kErrorNone) && (panId != Get().GetPanId())) { aInfo.mAffectsConnectivity = true; } if ((dataset.Read(meshLocalPrefix) == kErrorNone) && (meshLocalPrefix != Get().GetMeshLocalPrefix())) { aInfo.mAffectsConnectivity = true; } if (dataset.Read(networkKey) == kErrorNone) { NetworkKey localNetworkKey; Get().GetNetworkKey(localNetworkKey); if (networkKey != localNetworkKey) { aInfo.mAffectsConnectivity = true; aInfo.mAffectsNetworkKey = true; } } // Check active timestamp rollback. If there is no change to // network key, active timestamp must be ahead of local value. if (IsPendingDataset() && !aInfo.mAffectsNetworkKey) { VerifyOrExit(activeTimestamp > Get().GetTimestamp()); } // Determine whether the request is from commissioner. if (dataset.Read(sessionId) == kErrorNone) { uint16_t localSessionId; aInfo.mIsFromCommissioner = true; dataset.RemoveTlv(Tlv::kCommissionerSessionId); SuccessOrExit(Get().FindCommissioningSessionId(localSessionId)); VerifyOrExit(localSessionId == sessionId); // Verify an MGMT_ACTIVE_SET.req from a Commissioner does not // affect connectivity. if (IsActiveDataset()) { VerifyOrExit(!aInfo.mAffectsConnectivity); } // Thread specification allows partial dataset changes for // MGMT_ACTIVE_SET.req/MGMT_PENDING_SET.req from Commissioner // based on existing active dataset. if (aCommand == kMgmtSet) { IgnoreError(Get().Read(aInfo.mDataset)); } } if (aCommand == kMgmtReplace) { // MGMT_ACTIVE_REPLACE can only be used by commissioner. VerifyOrExit(aInfo.mIsFromCommissioner); VerifyOrExit(IsActiveDataset()); VerifyOrExit(dataset.ContainsAllRequiredTlvsFor(Dataset::kActive)); } SuccessOrExit(error = aInfo.mDataset.WriteTlvsFrom(dataset)); // Check and update the Delay Timer TLV value if present. if (aInfo.mDataset.Read(delayTimer) == kErrorNone) { delayTimer = Min(delayTimer, DelayTimerTlv::kMaxDelay); if (aInfo.mAffectsNetworkKey && (delayTimer < DelayTimerTlv::kDefaultDelay)) { delayTimer = DelayTimerTlv::kDefaultDelay; } else { delayTimer = Max(delayTimer, Get().GetDelayTimerMinimal()); } IgnoreError(aInfo.mDataset.Write(delayTimer)); } exit: return error; } Error DatasetManager::HandleSetOrReplace(MgmtCommand aCommand, const Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo) { StateTlv::State state = StateTlv::kReject; RequestInfo info; VerifyOrExit(Get().IsLeader()); SuccessOrExit(ProcessSetOrReplaceRequest(aCommand, aMessage, info)); if (IsActiveDataset() && info.mAffectsConnectivity) { // MGMT_ACTIVE_SET/REPLACE.req which affects // connectivity MUST be delayed using pending // dataset. Get().ApplyActiveDataset(info.mDataset); } else { SuccessOrExit(Save(info.mDataset)); Get().IncrementVersionAndStableVersion(); } state = StateTlv::kAccept; // Notify commissioner if update is from a Thread device. if (!info.mIsFromCommissioner) { uint16_t localSessionId; Ip6::Address destination; SuccessOrExit(Get().FindCommissioningSessionId(localSessionId)); Get().GetCommissionerAloc(localSessionId, destination); Get().SendDatasetChanged(destination); } exit: SendSetOrReplaceResponse(aMessage, aMessageInfo, state); return (state == StateTlv::kAccept) ? kErrorNone : kErrorDrop; } void DatasetManager::SendSetOrReplaceResponse(const Coap::Message &aRequest, const Ip6::MessageInfo &aMessageInfo, StateTlv::State aState) { Error error = kErrorNone; Coap::Message *message; message = Get().NewPriorityResponseMessage(aRequest); VerifyOrExit(message != nullptr, error = kErrorNoBufs); SuccessOrExit(error = Tlv::Append(*message, aState)); SuccessOrExit(error = Get().SendMessage(*message, aMessageInfo)); LogInfo("sent dataset set/replace response"); exit: FreeMessageOnError(message, error); } //---------------------------------------------------------------------------------------------------------------------- // ActiveDatasetManager #if OPENTHREAD_CONFIG_OPERATIONAL_DATASET_AUTO_INIT Error ActiveDatasetManager::GenerateLocal(void) { Error error = kErrorNone; Dataset dataset; VerifyOrExit(Get().IsAttached(), error = kErrorInvalidState); VerifyOrExit(!mLocalTimestamp.IsValid(), error = kErrorAlready); IgnoreError(Read(dataset)); if (!dataset.Contains()) { Timestamp timestamp; timestamp.Clear(); IgnoreError(dataset.Write(timestamp)); } if (!dataset.Contains()) { ChannelTlvValue channelValue; channelValue.SetChannelAndPage(Get().GetPanChannel()); IgnoreError(dataset.Write(channelValue)); } if (!dataset.Contains()) { ChannelTlvValue channelValue; channelValue.SetChannelAndPage(Get().GetWakeupChannel()); IgnoreError(dataset.Write(channelValue)); } if (!dataset.Contains()) { ChannelMaskTlv::Value value; ChannelMaskTlv::PrepareValue(value, Get().GetSupportedChannelMask().GetMask()); IgnoreError(dataset.WriteTlv(Tlv::kChannelMask, value.mData, value.mLength)); } if (!dataset.Contains()) { IgnoreError(dataset.Write(Get().GetExtPanId())); } if (!dataset.Contains()) { IgnoreError(dataset.Write(Get().GetMeshLocalPrefix())); } if (!dataset.Contains()) { NetworkKey networkKey; Get().GetNetworkKey(networkKey); IgnoreError(dataset.Write(networkKey)); } if (!dataset.Contains()) { NameData nameData = Get().GetNetworkName().GetAsData(); IgnoreError(dataset.WriteTlv(Tlv::kNetworkName, nameData.GetBuffer(), nameData.GetLength())); } if (!dataset.Contains()) { IgnoreError(dataset.Write(Get().GetPanId())); } if (!dataset.Contains()) { Pskc pskc; if (Get().IsPskcSet()) { Get().GetPskc(pskc); } else { SuccessOrExit(error = pskc.GenerateRandom()); } IgnoreError(dataset.Write(pskc)); } if (!dataset.Contains()) { SecurityPolicyTlv tlv; tlv.Init(); tlv.SetSecurityPolicy(Get().GetSecurityPolicy()); IgnoreError(dataset.WriteTlv(tlv)); } LocalSave(dataset); Restore(dataset); LogInfo("Generated local dataset"); exit: return error; } void ActiveDatasetManager::StartLeader(void) { IgnoreError(GenerateLocal()); } #else // OPENTHREAD_CONFIG_OPERATIONAL_DATASET_AUTO_INIT void ActiveDatasetManager::StartLeader(void) {} #endif // OPENTHREAD_CONFIG_OPERATIONAL_DATASET_AUTO_INIT template <> void ActiveDatasetManager::HandleTmf(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo) { SuccessOrExit(DatasetManager::HandleSetOrReplace(kMgmtSet, aMessage, aMessageInfo)); IgnoreError(ApplyConfiguration()); exit: return; } template <> void ActiveDatasetManager::HandleTmf(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo) { SuccessOrExit(DatasetManager::HandleSetOrReplace(kMgmtReplace, aMessage, aMessageInfo)); IgnoreError(ApplyConfiguration()); exit: return; } //---------------------------------------------------------------------------------------------------------------------- // PendingDatasetManager void PendingDatasetManager::StartLeader(void) { StartDelayTimer(); } template <> void PendingDatasetManager::HandleTmf(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo) { SuccessOrExit(DatasetManager::HandleSetOrReplace(kMgmtSet, aMessage, aMessageInfo)); StartDelayTimer(); exit: return; } void PendingDatasetManager::ApplyActiveDataset(Dataset &aDataset) { // Generates and applies Pending Dataset from an Active Dataset. Timestamp activeTimestamp; SuccessOrExit(aDataset.Read(activeTimestamp)); SuccessOrExit(aDataset.Write(activeTimestamp)); SuccessOrExit(aDataset.Write(Get().GetDelayTimerMinimal())); IgnoreError(DatasetManager::Save(aDataset)); StartDelayTimer(aDataset); exit: return; } } // namespace MeshCoP } // namespace ot #endif // OPENTHREAD_FTD