|
| 1 | +/* |
| 2 | + * |
| 3 | + * Copyright (c) 2024 Project CHIP Authors |
| 4 | + * |
| 5 | + * Licensed under the Apache License, Version 2.0 (the "License"); |
| 6 | + * you may not use this file except in compliance with the License. |
| 7 | + * You may obtain a copy of the License at |
| 8 | + * |
| 9 | + * http://www.apache.org/licenses/LICENSE-2.0 |
| 10 | + * |
| 11 | + * Unless required by applicable law or agreed to in writing, software |
| 12 | + * distributed under the License is distributed on an "AS IS" BASIS, |
| 13 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14 | + * See the License for the specific language governing permissions and |
| 15 | + * limitations under the License. |
| 16 | + */ |
| 17 | + |
| 18 | +#include "thread-border-router-management-server.h" |
| 19 | + |
| 20 | +#include "app-common/zap-generated/cluster-objects.h" |
| 21 | +#include "app-common/zap-generated/ids/Attributes.h" |
| 22 | +#include "app-common/zap-generated/ids/Clusters.h" |
| 23 | +#include "app-common/zap-generated/ids/Commands.h" |
| 24 | +#include "app/AttributeAccessInterfaceRegistry.h" |
| 25 | +#include "app/AttributeValueEncoder.h" |
| 26 | +#include "app/CommandHandler.h" |
| 27 | +#include "app/CommandHandlerInterface.h" |
| 28 | +#include "app/CommandHandlerInterfaceRegistry.h" |
| 29 | +#include "app/InteractionModelEngine.h" |
| 30 | +#include "app/MessageDef/StatusIB.h" |
| 31 | +#include "app/clusters/general-commissioning-server/general-commissioning-server.h" |
| 32 | +#include "app/data-model/Nullable.h" |
| 33 | +#include "lib/core/CHIPError.h" |
| 34 | +#include "lib/core/Optional.h" |
| 35 | +#include "lib/support/CodeUtils.h" |
| 36 | +#include "lib/support/Span.h" |
| 37 | +#include "lib/support/ThreadOperationalDataset.h" |
| 38 | +#include "platform/CHIPDeviceEvent.h" |
| 39 | +#include "platform/PlatformManager.h" |
| 40 | +#include "protocols/interaction_model/StatusCode.h" |
| 41 | + |
| 42 | +namespace chip { |
| 43 | +namespace app { |
| 44 | +namespace Clusters { |
| 45 | +namespace ThreadBorderRouterManagement { |
| 46 | + |
| 47 | +using Protocols::InteractionModel::Status; |
| 48 | + |
| 49 | +static bool IsCommandOverCASESession(CommandHandlerInterface::HandlerContext & ctx) |
| 50 | +{ |
| 51 | + Messaging::ExchangeContext * exchangeCtx = ctx.mCommandHandler.GetExchangeContext(); |
| 52 | + return exchangeCtx && exchangeCtx->HasSessionHandle() && exchangeCtx->GetSessionHandle()->IsSecureSession() && |
| 53 | + exchangeCtx->GetSessionHandle()->AsSecureSession()->GetSecureSessionType() == Transport::SecureSession::Type::kCASE; |
| 54 | +} |
| 55 | + |
| 56 | +Status ServerInstance::HandleGetDatasetRequest(bool isOverCASESession, Delegate::DatasetType type, |
| 57 | + Thread::OperationalDataset & dataset) |
| 58 | +{ |
| 59 | + VerifyOrDie(mDelegate); |
| 60 | + if (!isOverCASESession) |
| 61 | + { |
| 62 | + return Status::UnsupportedAccess; |
| 63 | + } |
| 64 | + |
| 65 | + CHIP_ERROR err = mDelegate->GetDataset(dataset, type); |
| 66 | + if (err != CHIP_NO_ERROR) |
| 67 | + { |
| 68 | + return err == CHIP_IM_GLOBAL_STATUS(NotFound) ? StatusIB(err).mStatus : Status::Failure; |
| 69 | + } |
| 70 | + return Status::Success; |
| 71 | +} |
| 72 | + |
| 73 | +Status ServerInstance::HandleSetActiveDatasetRequest(CommandHandler * commandHandler, |
| 74 | + const Commands::SetActiveDatasetRequest::DecodableType & req) |
| 75 | +{ |
| 76 | + // The SetActiveDatasetRequest command SHALL be FailSafeArmed. Upon receiving this command, the Thread BR will set its |
| 77 | + // active dataset. If the dataset is set successfully, OnActivateDatasetComplete will be called with CHIP_NO_ERROR, prompting |
| 78 | + // the Thread BR to respond with a success status. If an error occurs while setting the active dataset, the Thread BR should |
| 79 | + // respond with a failure status. In this case, when the FailSafe timer expires, the active dataset set by this command will be |
| 80 | + // reverted. If the FailSafe timer expires before the Thread BR responds, the Thread BR will respond with a timeout status and |
| 81 | + // the active dataset should also be reverted. |
| 82 | + VerifyOrDie(mDelegate); |
| 83 | + VerifyOrReturnValue(mFailsafeContext.IsFailSafeArmed(commandHandler->GetAccessingFabricIndex()), Status::FailsafeRequired); |
| 84 | + |
| 85 | + Thread::OperationalDataset activeDataset; |
| 86 | + Thread::OperationalDataset currentActiveDataset; |
| 87 | + uint64_t currentActiveDatasetTimestamp = 0; |
| 88 | + // If any of the parameters in the ActiveDataset is invalid, the command SHALL fail with a status code |
| 89 | + // of INVALID_COMMAND. |
| 90 | + VerifyOrReturnValue(activeDataset.Init(req.activeDataset) == CHIP_NO_ERROR, Status::InvalidCommand); |
| 91 | + |
| 92 | + // If this command is invoked when the ActiveDatasetTimestamp attribute is not null, the command SHALL |
| 93 | + // fail with a status code of INVALID_IN_STATE. |
| 94 | + if ((mDelegate->GetDataset(currentActiveDataset, Delegate::DatasetType::kActive) == CHIP_NO_ERROR) && |
| 95 | + (currentActiveDataset.GetActiveTimestamp(currentActiveDatasetTimestamp) == CHIP_NO_ERROR)) |
| 96 | + { |
| 97 | + return Status::InvalidInState; |
| 98 | + } |
| 99 | + // If there is a back end command process, return status BUSY. |
| 100 | + if (mAsyncCommandHandle.Get()) |
| 101 | + { |
| 102 | + return Status::Busy; |
| 103 | + } |
| 104 | + commandHandler->FlushAcksRightAwayOnSlowCommand(); |
| 105 | + mAsyncCommandHandle = CommandHandler::Handle(commandHandler); |
| 106 | + mBreadcrumb = req.breadcrumb; |
| 107 | + mSetActiveDatasetSequenceNumber++; |
| 108 | + mDelegate->SetActiveDataset(activeDataset, mSetActiveDatasetSequenceNumber, this); |
| 109 | + return Status::Success; |
| 110 | +} |
| 111 | + |
| 112 | +Status ServerInstance::HandleSetPendingDatasetRequest(const Commands::SetPendingDatasetRequest::DecodableType & req) |
| 113 | +{ |
| 114 | + VerifyOrDie(mDelegate); |
| 115 | + if (!mDelegate->GetPanChangeSupported()) |
| 116 | + { |
| 117 | + return Status::UnsupportedCommand; |
| 118 | + } |
| 119 | + Thread::OperationalDataset pendingDataset; |
| 120 | + // If any of the parameters in the PendingDataset is invalid, the command SHALL fail with a status code |
| 121 | + // of INVALID_COMMAND. |
| 122 | + ReturnErrorCodeIf(pendingDataset.Init(req.pendingDataset) != CHIP_NO_ERROR, Status::InvalidCommand); |
| 123 | + CHIP_ERROR err = mDelegate->SetPendingDataset(pendingDataset); |
| 124 | + return StatusIB(err).mStatus; |
| 125 | +} |
| 126 | + |
| 127 | +void AddDatasetResponse(CommandHandlerInterface::HandlerContext & ctx, Status status, const Thread::OperationalDataset & dataset) |
| 128 | +{ |
| 129 | + if (status != Status::Success) |
| 130 | + { |
| 131 | + ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status); |
| 132 | + return; |
| 133 | + } |
| 134 | + Commands::DatasetResponse::Type response; |
| 135 | + response.dataset = dataset.AsByteSpan(); |
| 136 | + ctx.mCommandHandler.AddResponse(ctx.mRequestPath, response); |
| 137 | +} |
| 138 | + |
| 139 | +void ServerInstance::InvokeCommand(HandlerContext & ctxt) |
| 140 | +{ |
| 141 | + switch (ctxt.mRequestPath.mCommandId) |
| 142 | + { |
| 143 | + case Commands::GetActiveDatasetRequest::Id: |
| 144 | + HandleCommand<Commands::GetActiveDatasetRequest::DecodableType>(ctxt, [this](HandlerContext & ctx, const auto & req) { |
| 145 | + Thread::OperationalDataset dataset; |
| 146 | + Status status = HandleGetActiveDatasetRequest(IsCommandOverCASESession(ctx), dataset); |
| 147 | + AddDatasetResponse(ctx, status, dataset); |
| 148 | + }); |
| 149 | + break; |
| 150 | + case Commands::GetPendingDatasetRequest::Id: |
| 151 | + HandleCommand<Commands::GetPendingDatasetRequest::DecodableType>(ctxt, [this](HandlerContext & ctx, const auto & req) { |
| 152 | + Thread::OperationalDataset dataset; |
| 153 | + Status status = HandleGetPendingDatasetRequest(IsCommandOverCASESession(ctx), dataset); |
| 154 | + AddDatasetResponse(ctx, status, dataset); |
| 155 | + }); |
| 156 | + break; |
| 157 | + case Commands::SetActiveDatasetRequest::Id: |
| 158 | + HandleCommand<Commands::SetActiveDatasetRequest::DecodableType>(ctxt, [this](HandlerContext & ctx, const auto & req) { |
| 159 | + mPath = ctx.mRequestPath; |
| 160 | + Status status = HandleSetActiveDatasetRequest(&ctx.mCommandHandler, req); |
| 161 | + if (status != Status::Success) |
| 162 | + { |
| 163 | + // If status is not Success, we should immediately report the status. Otherwise the async work will report the |
| 164 | + // status to the client. |
| 165 | + ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status); |
| 166 | + } |
| 167 | + }); |
| 168 | + break; |
| 169 | + case Commands::SetPendingDatasetRequest::Id: |
| 170 | + HandleCommand<Commands::SetPendingDatasetRequest::DecodableType>(ctxt, [this](HandlerContext & ctx, const auto & req) { |
| 171 | + ctx.mCommandHandler.AddStatus(ctx.mRequestPath, HandleSetPendingDatasetRequest(req)); |
| 172 | + }); |
| 173 | + break; |
| 174 | + default: |
| 175 | + break; |
| 176 | + } |
| 177 | +} |
| 178 | + |
| 179 | +void ServerInstance::ReadFeatureMap(BitFlags<Feature> & outFeatureMap) |
| 180 | +{ |
| 181 | + if (mDelegate->GetPanChangeSupported()) |
| 182 | + { |
| 183 | + outFeatureMap.Set(Feature::kPANChange); |
| 184 | + } |
| 185 | +} |
| 186 | + |
| 187 | +CHIP_ERROR ServerInstance::ReadBorderRouterName(MutableCharSpan & outBorderRouterName) |
| 188 | +{ |
| 189 | + mDelegate->GetBorderRouterName(outBorderRouterName); |
| 190 | + VerifyOrReturnValue(outBorderRouterName.size() <= kBorderRouterNameMaxLength, CHIP_IM_GLOBAL_STATUS(Failure)); |
| 191 | + return CHIP_NO_ERROR; |
| 192 | +} |
| 193 | + |
| 194 | +CHIP_ERROR ServerInstance::ReadBorderAgentID(MutableByteSpan & outBorderAgentId) |
| 195 | +{ |
| 196 | + VerifyOrReturnValue((mDelegate->GetBorderAgentId(outBorderAgentId) == CHIP_NO_ERROR) && |
| 197 | + (outBorderAgentId.size() == kBorderAgentIdLength), |
| 198 | + CHIP_IM_GLOBAL_STATUS(Failure)); |
| 199 | + return CHIP_NO_ERROR; |
| 200 | +} |
| 201 | + |
| 202 | +Optional<uint64_t> ServerInstance::ReadActiveDatasetTimestamp() |
| 203 | +{ |
| 204 | + uint64_t activeDatasetTimestampValue = 0; |
| 205 | + Thread::OperationalDataset activeDataset; |
| 206 | + if ((mDelegate->GetDataset(activeDataset, Delegate::DatasetType::kActive) == CHIP_NO_ERROR) && |
| 207 | + (activeDataset.GetActiveTimestamp(activeDatasetTimestampValue) == CHIP_NO_ERROR)) |
| 208 | + { |
| 209 | + return MakeOptional(activeDatasetTimestampValue); |
| 210 | + } |
| 211 | + return NullOptional; |
| 212 | +} |
| 213 | + |
| 214 | +CHIP_ERROR ServerInstance::Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) |
| 215 | +{ |
| 216 | + if (aPath.mClusterId != ThreadBorderRouterManagement::Id) |
| 217 | + { |
| 218 | + return CHIP_ERROR_INVALID_ARGUMENT; |
| 219 | + } |
| 220 | + VerifyOrDie(mDelegate); |
| 221 | + CHIP_ERROR status = CHIP_NO_ERROR; |
| 222 | + switch (aPath.mAttributeId) |
| 223 | + { |
| 224 | + case Globals::Attributes::FeatureMap::Id: { |
| 225 | + BitFlags<Feature> featureMap; |
| 226 | + ReadFeatureMap(featureMap); |
| 227 | + status = aEncoder.Encode(featureMap); |
| 228 | + break; |
| 229 | + } |
| 230 | + case Attributes::BorderRouterName::Id: { |
| 231 | + char borderRouterNameBuf[kBorderRouterNameMaxLength] = { 0 }; |
| 232 | + MutableCharSpan borderRouterName(borderRouterNameBuf); |
| 233 | + status = ReadBorderRouterName(borderRouterName); |
| 234 | + // If there are any internal errors, the status will be returned and the client will get an error report. |
| 235 | + if (status == CHIP_NO_ERROR) |
| 236 | + { |
| 237 | + status = aEncoder.Encode(borderRouterName); |
| 238 | + } |
| 239 | + break; |
| 240 | + } |
| 241 | + case Attributes::BorderAgentID::Id: { |
| 242 | + uint8_t borderAgentIDBuf[kBorderAgentIdLength] = { 0 }; |
| 243 | + MutableByteSpan borderAgentID(borderAgentIDBuf); |
| 244 | + status = ReadBorderAgentID(borderAgentID); |
| 245 | + if (status == CHIP_NO_ERROR) |
| 246 | + { |
| 247 | + status = aEncoder.Encode(borderAgentID); |
| 248 | + } |
| 249 | + break; |
| 250 | + } |
| 251 | + case Attributes::ThreadVersion::Id: { |
| 252 | + uint16_t threadVersion = mDelegate->GetThreadVersion(); |
| 253 | + status = aEncoder.Encode(threadVersion); |
| 254 | + break; |
| 255 | + } |
| 256 | + case Attributes::InterfaceEnabled::Id: { |
| 257 | + bool interfaceEnabled = mDelegate->GetInterfaceEnabled(); |
| 258 | + status = aEncoder.Encode(interfaceEnabled); |
| 259 | + break; |
| 260 | + } |
| 261 | + case Attributes::ActiveDatasetTimestamp::Id: { |
| 262 | + Optional<uint64_t> activeDatasetTimestamp = ReadActiveDatasetTimestamp(); |
| 263 | + status = activeDatasetTimestamp.HasValue() ? aEncoder.Encode(DataModel::MakeNullable(activeDatasetTimestamp.Value())) |
| 264 | + : aEncoder.EncodeNull(); |
| 265 | + break; |
| 266 | + } |
| 267 | + default: |
| 268 | + break; |
| 269 | + } |
| 270 | + return status; |
| 271 | +} |
| 272 | + |
| 273 | +void ServerInstance::CommitSavedBreadcrumb() |
| 274 | +{ |
| 275 | + if (mBreadcrumb.HasValue()) |
| 276 | + { |
| 277 | + GeneralCommissioning::SetBreadcrumb(mBreadcrumb.Value()); |
| 278 | + } |
| 279 | + mBreadcrumb.ClearValue(); |
| 280 | +} |
| 281 | + |
| 282 | +void ServerInstance::OnActivateDatasetComplete(uint32_t sequenceNum, CHIP_ERROR error) |
| 283 | +{ |
| 284 | + auto commandHandleRef = std::move(mAsyncCommandHandle); |
| 285 | + auto commandHandle = commandHandleRef.Get(); |
| 286 | + if (commandHandle == nullptr) |
| 287 | + { |
| 288 | + return; |
| 289 | + } |
| 290 | + if (mSetActiveDatasetSequenceNumber != sequenceNum) |
| 291 | + { |
| 292 | + // Previous SetActiveDatasetRequest was handled. |
| 293 | + return; |
| 294 | + } |
| 295 | + if (error == CHIP_NO_ERROR) |
| 296 | + { |
| 297 | + // TODO: SPEC Issue #10022 |
| 298 | + CommitSavedBreadcrumb(); |
| 299 | + } |
| 300 | + else |
| 301 | + { |
| 302 | + ChipLogError(Zcl, "Failed on activating the active dataset for Thread BR: %" CHIP_ERROR_FORMAT, error.Format()); |
| 303 | + } |
| 304 | + commandHandle->AddStatus(mPath, StatusIB(error).mStatus); |
| 305 | +} |
| 306 | + |
| 307 | +void ServerInstance::ReportAttributeChanged(AttributeId attributeId) |
| 308 | +{ |
| 309 | + MatterReportingAttributeChangeCallback(mServerEndpointId, Id, attributeId); |
| 310 | +} |
| 311 | + |
| 312 | +void ServerInstance::OnFailSafeTimerExpired() |
| 313 | +{ |
| 314 | + if (mDelegate) |
| 315 | + { |
| 316 | + mDelegate->RevertActiveDataset(); |
| 317 | + } |
| 318 | + auto commandHandleRef = std::move(mAsyncCommandHandle); |
| 319 | + auto commandHandle = commandHandleRef.Get(); |
| 320 | + if (commandHandle == nullptr) |
| 321 | + { |
| 322 | + return; |
| 323 | + } |
| 324 | + commandHandle->AddStatus(mPath, Status::Timeout); |
| 325 | +} |
| 326 | + |
| 327 | +void ServerInstance::OnPlatformEventHandler(const DeviceLayer::ChipDeviceEvent * event, intptr_t arg) |
| 328 | +{ |
| 329 | + ServerInstance * _this = reinterpret_cast<ServerInstance *>(arg); |
| 330 | + if (event->Type == DeviceLayer::DeviceEventType::kFailSafeTimerExpired) |
| 331 | + { |
| 332 | + _this->OnFailSafeTimerExpired(); |
| 333 | + } |
| 334 | + else if (event->Type == DeviceLayer::DeviceEventType::kCommissioningComplete) |
| 335 | + { |
| 336 | + _this->mDelegate->CommitActiveDataset(); |
| 337 | + } |
| 338 | +} |
| 339 | + |
| 340 | +CHIP_ERROR ServerInstance::Init() |
| 341 | +{ |
| 342 | + ReturnErrorCodeIf(!mDelegate, CHIP_ERROR_INVALID_ARGUMENT); |
| 343 | + ReturnErrorOnFailure(CommandHandlerInterfaceRegistry::RegisterCommandHandler(this)); |
| 344 | + VerifyOrReturnError(registerAttributeAccessOverride(this), CHIP_ERROR_INCORRECT_STATE); |
| 345 | + ReturnErrorOnFailure(DeviceLayer::PlatformMgrImpl().AddEventHandler(OnPlatformEventHandler, reinterpret_cast<intptr_t>(this))); |
| 346 | + return mDelegate->Init(this); |
| 347 | +} |
| 348 | + |
| 349 | +} // namespace ThreadBorderRouterManagement |
| 350 | +} // namespace Clusters |
| 351 | +} // namespace app |
| 352 | +} // namespace chip |
| 353 | + |
| 354 | +void MatterThreadBorderRouterManagementPluginServerInitCallback() |
| 355 | +{ |
| 356 | + // Nothing to do, the server init routine will be done in Instance::Init() |
| 357 | +} |
0 commit comments