Skip to content

Commit

Permalink
Add Thread Border Router Management cluster server implementation and…
Browse files Browse the repository at this point in the history
… Generic Thread BR delegate (project-chip#33872)

* Add Thread Border Router Management cluster server implementation and ESP32 delegate

* Restyled by clang-format

* Fix compile error for external platform and mark thread-br-management cluster provisional

* remove the topology request and response command and fix the id of activedatasettimestamp

* review changes

* move the border router delegate to generic directory

* Restyled by clang-format

* remove the duplicate thread br management cluster in controller zap

* Ensure that the Get dataset response failure status matches the spec

* add the document for ActivateDatasetCallback

* Update GenericThreadBorderRouterDelegate and add the function description for thread-br-delegate

* Restyled by clang-format

* Add unit test

* Restyled by clang-format

* Restyled by gn

* Update src/app/clusters/thread-border-router-management-server/thread-br-delegate.h

Co-authored-by: Boris Zbarsky <[email protected]>

* Update src/app/clusters/thread-border-router-management-server/thread-br-delegate.h

Co-authored-by: Boris Zbarsky <[email protected]>

* Update src/app/clusters/thread-border-router-management-server/thread-br-delegate.h

Co-authored-by: Boris Zbarsky <[email protected]>

* Update src/app/clusters/thread-border-router-management-server/thread-br-delegate.h

Co-authored-by: Boris Zbarsky <[email protected]>

* add logic of no callbacks for an activate-before-revert happening after the revert

* fix clang tidy error

* rename the delegate implementation class

* Restyled by clang-format

* Try to fix CI tests

* some review changes

* Restyled by clang-format

* rename the source files

* review changes

* some review changes

* Restyled by clang-format

* review changes from marius

* review changes

* add commissioning complete action

* Restyled by clang-format

* Only build TestThreadBorderRouterManagementCluster for no-fake platform

* fix committing active dataset configured

* add attribute change report logic

* Restyled by clang-format

* Don't build TestThreadBorderRouterManagementCluster with TestCommissioningWindowManager

* review changes

* Restyled by clang-format

* regenerate zap

* Fix the compilation error

Update the code as 
`chip::app::InteractionModelEngine::RegisterCommandHandler` is now replaced by  `chip::app::CommandHandlerInterfaceRegistry::RegisterCommandHandler`

* Update thread-border-router-management-server.cpp

* Update thread-border-router-management-server.cpp

* Change the sequence of the header file inclusion

---------

Co-authored-by: Restyled.io <[email protected]>
Co-authored-by: Boris Zbarsky <[email protected]>
Co-authored-by: Hrishikesh Dhayagude <[email protected]>
  • Loading branch information
4 people authored and rochaferraz committed Jul 31, 2024
1 parent bec8edc commit 697b842
Show file tree
Hide file tree
Showing 18 changed files with 1,295 additions and 393 deletions.
6 changes: 6 additions & 0 deletions config/esp32/components/chip/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,12 @@ else()
chip_gn_arg_append("chip_openthread_ftd" "false")
endif()

if (CONFIG_OPENTHREAD_BORDER_ROUTER)
chip_gn_arg_append("chip_openthread_border_router" "true")
else()
chip_gn_arg_append("chip_openthread_border_router" "false")
endif()

if (CONFIG_ENABLE_OTA_REQUESTOR)
chip_gn_arg_append("chip_enable_ota_requestor" "true")
endif()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ declare_args() {
chip_bt_bluedroid_enabled = true
chip_max_discovered_ip_addresses = 5
chip_enable_route_hook = false
chip_enable_thread_border_router = false
}

buildconfig_header("custom_buildconfig") {
Expand Down
6 changes: 6 additions & 0 deletions src/app/chip_data_model.gni
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,12 @@ template("chip_data_model") {
"${_app_root}/clusters/${cluster}/thread-network-diagnostics-provider.cpp",
"${_app_root}/clusters/${cluster}/thread-network-diagnostics-provider.h",
]
} else if (cluster == "thread-border-router-management-server") {
sources += [
"${_app_root}/clusters/${cluster}/thread-border-router-management-server.cpp",
"${_app_root}/clusters/${cluster}/thread-border-router-management-server.h",
"${_app_root}/clusters/${cluster}/thread-br-delegate.h",
]
} else if (cluster == "water-heater-management-server") {
sources += [
"${_app_root}/clusters/${cluster}/${cluster}.cpp",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,357 @@
/*
*
* Copyright (c) 2024 Project CHIP Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include "thread-border-router-management-server.h"

#include "app-common/zap-generated/cluster-objects.h"
#include "app-common/zap-generated/ids/Attributes.h"
#include "app-common/zap-generated/ids/Clusters.h"
#include "app-common/zap-generated/ids/Commands.h"
#include "app/AttributeAccessInterfaceRegistry.h"
#include "app/AttributeValueEncoder.h"
#include "app/CommandHandler.h"
#include "app/CommandHandlerInterface.h"
#include "app/CommandHandlerInterfaceRegistry.h"
#include "app/InteractionModelEngine.h"
#include "app/MessageDef/StatusIB.h"
#include "app/clusters/general-commissioning-server/general-commissioning-server.h"
#include "app/data-model/Nullable.h"
#include "lib/core/CHIPError.h"
#include "lib/core/Optional.h"
#include "lib/support/CodeUtils.h"
#include "lib/support/Span.h"
#include "lib/support/ThreadOperationalDataset.h"
#include "platform/CHIPDeviceEvent.h"
#include "platform/PlatformManager.h"
#include "protocols/interaction_model/StatusCode.h"

namespace chip {
namespace app {
namespace Clusters {
namespace ThreadBorderRouterManagement {

using Protocols::InteractionModel::Status;

static bool IsCommandOverCASESession(CommandHandlerInterface::HandlerContext & ctx)
{
Messaging::ExchangeContext * exchangeCtx = ctx.mCommandHandler.GetExchangeContext();
return exchangeCtx && exchangeCtx->HasSessionHandle() && exchangeCtx->GetSessionHandle()->IsSecureSession() &&
exchangeCtx->GetSessionHandle()->AsSecureSession()->GetSecureSessionType() == Transport::SecureSession::Type::kCASE;
}

Status ServerInstance::HandleGetDatasetRequest(bool isOverCASESession, Delegate::DatasetType type,
Thread::OperationalDataset & dataset)
{
VerifyOrDie(mDelegate);
if (!isOverCASESession)
{
return Status::UnsupportedAccess;
}

CHIP_ERROR err = mDelegate->GetDataset(dataset, type);
if (err != CHIP_NO_ERROR)
{
return err == CHIP_IM_GLOBAL_STATUS(NotFound) ? StatusIB(err).mStatus : Status::Failure;
}
return Status::Success;
}

Status ServerInstance::HandleSetActiveDatasetRequest(CommandHandler * commandHandler,
const Commands::SetActiveDatasetRequest::DecodableType & req)
{
// The SetActiveDatasetRequest command SHALL be FailSafeArmed. Upon receiving this command, the Thread BR will set its
// active dataset. If the dataset is set successfully, OnActivateDatasetComplete will be called with CHIP_NO_ERROR, prompting
// the Thread BR to respond with a success status. If an error occurs while setting the active dataset, the Thread BR should
// respond with a failure status. In this case, when the FailSafe timer expires, the active dataset set by this command will be
// reverted. If the FailSafe timer expires before the Thread BR responds, the Thread BR will respond with a timeout status and
// the active dataset should also be reverted.
VerifyOrDie(mDelegate);
VerifyOrReturnValue(mFailsafeContext.IsFailSafeArmed(commandHandler->GetAccessingFabricIndex()), Status::FailsafeRequired);

Thread::OperationalDataset activeDataset;
Thread::OperationalDataset currentActiveDataset;
uint64_t currentActiveDatasetTimestamp = 0;
// If any of the parameters in the ActiveDataset is invalid, the command SHALL fail with a status code
// of INVALID_COMMAND.
VerifyOrReturnValue(activeDataset.Init(req.activeDataset) == CHIP_NO_ERROR, Status::InvalidCommand);

// If this command is invoked when the ActiveDatasetTimestamp attribute is not null, the command SHALL
// fail with a status code of INVALID_IN_STATE.
if ((mDelegate->GetDataset(currentActiveDataset, Delegate::DatasetType::kActive) == CHIP_NO_ERROR) &&
(currentActiveDataset.GetActiveTimestamp(currentActiveDatasetTimestamp) == CHIP_NO_ERROR))
{
return Status::InvalidInState;
}
// If there is a back end command process, return status BUSY.
if (mAsyncCommandHandle.Get())
{
return Status::Busy;
}
commandHandler->FlushAcksRightAwayOnSlowCommand();
mAsyncCommandHandle = CommandHandler::Handle(commandHandler);
mBreadcrumb = req.breadcrumb;
mSetActiveDatasetSequenceNumber++;
mDelegate->SetActiveDataset(activeDataset, mSetActiveDatasetSequenceNumber, this);
return Status::Success;
}

Status ServerInstance::HandleSetPendingDatasetRequest(const Commands::SetPendingDatasetRequest::DecodableType & req)
{
VerifyOrDie(mDelegate);
if (!mDelegate->GetPanChangeSupported())
{
return Status::UnsupportedCommand;
}
Thread::OperationalDataset pendingDataset;
// If any of the parameters in the PendingDataset is invalid, the command SHALL fail with a status code
// of INVALID_COMMAND.
ReturnErrorCodeIf(pendingDataset.Init(req.pendingDataset) != CHIP_NO_ERROR, Status::InvalidCommand);
CHIP_ERROR err = mDelegate->SetPendingDataset(pendingDataset);
return StatusIB(err).mStatus;
}

void AddDatasetResponse(CommandHandlerInterface::HandlerContext & ctx, Status status, const Thread::OperationalDataset & dataset)
{
if (status != Status::Success)
{
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status);
return;
}
Commands::DatasetResponse::Type response;
response.dataset = dataset.AsByteSpan();
ctx.mCommandHandler.AddResponse(ctx.mRequestPath, response);
}

void ServerInstance::InvokeCommand(HandlerContext & ctxt)
{
switch (ctxt.mRequestPath.mCommandId)
{
case Commands::GetActiveDatasetRequest::Id:
HandleCommand<Commands::GetActiveDatasetRequest::DecodableType>(ctxt, [this](HandlerContext & ctx, const auto & req) {
Thread::OperationalDataset dataset;
Status status = HandleGetActiveDatasetRequest(IsCommandOverCASESession(ctx), dataset);
AddDatasetResponse(ctx, status, dataset);
});
break;
case Commands::GetPendingDatasetRequest::Id:
HandleCommand<Commands::GetPendingDatasetRequest::DecodableType>(ctxt, [this](HandlerContext & ctx, const auto & req) {
Thread::OperationalDataset dataset;
Status status = HandleGetPendingDatasetRequest(IsCommandOverCASESession(ctx), dataset);
AddDatasetResponse(ctx, status, dataset);
});
break;
case Commands::SetActiveDatasetRequest::Id:
HandleCommand<Commands::SetActiveDatasetRequest::DecodableType>(ctxt, [this](HandlerContext & ctx, const auto & req) {
mPath = ctx.mRequestPath;
Status status = HandleSetActiveDatasetRequest(&ctx.mCommandHandler, req);
if (status != Status::Success)
{
// If status is not Success, we should immediately report the status. Otherwise the async work will report the
// status to the client.
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status);
}
});
break;
case Commands::SetPendingDatasetRequest::Id:
HandleCommand<Commands::SetPendingDatasetRequest::DecodableType>(ctxt, [this](HandlerContext & ctx, const auto & req) {
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, HandleSetPendingDatasetRequest(req));
});
break;
default:
break;
}
}

void ServerInstance::ReadFeatureMap(BitFlags<Feature> & outFeatureMap)
{
if (mDelegate->GetPanChangeSupported())
{
outFeatureMap.Set(Feature::kPANChange);
}
}

CHIP_ERROR ServerInstance::ReadBorderRouterName(MutableCharSpan & outBorderRouterName)
{
mDelegate->GetBorderRouterName(outBorderRouterName);
VerifyOrReturnValue(outBorderRouterName.size() <= kBorderRouterNameMaxLength, CHIP_IM_GLOBAL_STATUS(Failure));
return CHIP_NO_ERROR;
}

CHIP_ERROR ServerInstance::ReadBorderAgentID(MutableByteSpan & outBorderAgentId)
{
VerifyOrReturnValue((mDelegate->GetBorderAgentId(outBorderAgentId) == CHIP_NO_ERROR) &&
(outBorderAgentId.size() == kBorderAgentIdLength),
CHIP_IM_GLOBAL_STATUS(Failure));
return CHIP_NO_ERROR;
}

Optional<uint64_t> ServerInstance::ReadActiveDatasetTimestamp()
{
uint64_t activeDatasetTimestampValue = 0;
Thread::OperationalDataset activeDataset;
if ((mDelegate->GetDataset(activeDataset, Delegate::DatasetType::kActive) == CHIP_NO_ERROR) &&
(activeDataset.GetActiveTimestamp(activeDatasetTimestampValue) == CHIP_NO_ERROR))
{
return MakeOptional(activeDatasetTimestampValue);
}
return NullOptional;
}

CHIP_ERROR ServerInstance::Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder)
{
if (aPath.mClusterId != ThreadBorderRouterManagement::Id)
{
return CHIP_ERROR_INVALID_ARGUMENT;
}
VerifyOrDie(mDelegate);
CHIP_ERROR status = CHIP_NO_ERROR;
switch (aPath.mAttributeId)
{
case Globals::Attributes::FeatureMap::Id: {
BitFlags<Feature> featureMap;
ReadFeatureMap(featureMap);
status = aEncoder.Encode(featureMap);
break;
}
case Attributes::BorderRouterName::Id: {
char borderRouterNameBuf[kBorderRouterNameMaxLength] = { 0 };
MutableCharSpan borderRouterName(borderRouterNameBuf);
status = ReadBorderRouterName(borderRouterName);
// If there are any internal errors, the status will be returned and the client will get an error report.
if (status == CHIP_NO_ERROR)
{
status = aEncoder.Encode(borderRouterName);
}
break;
}
case Attributes::BorderAgentID::Id: {
uint8_t borderAgentIDBuf[kBorderAgentIdLength] = { 0 };
MutableByteSpan borderAgentID(borderAgentIDBuf);
status = ReadBorderAgentID(borderAgentID);
if (status == CHIP_NO_ERROR)
{
status = aEncoder.Encode(borderAgentID);
}
break;
}
case Attributes::ThreadVersion::Id: {
uint16_t threadVersion = mDelegate->GetThreadVersion();
status = aEncoder.Encode(threadVersion);
break;
}
case Attributes::InterfaceEnabled::Id: {
bool interfaceEnabled = mDelegate->GetInterfaceEnabled();
status = aEncoder.Encode(interfaceEnabled);
break;
}
case Attributes::ActiveDatasetTimestamp::Id: {
Optional<uint64_t> activeDatasetTimestamp = ReadActiveDatasetTimestamp();
status = activeDatasetTimestamp.HasValue() ? aEncoder.Encode(DataModel::MakeNullable(activeDatasetTimestamp.Value()))
: aEncoder.EncodeNull();
break;
}
default:
break;
}
return status;
}

void ServerInstance::CommitSavedBreadcrumb()
{
if (mBreadcrumb.HasValue())
{
GeneralCommissioning::SetBreadcrumb(mBreadcrumb.Value());
}
mBreadcrumb.ClearValue();
}

void ServerInstance::OnActivateDatasetComplete(uint32_t sequenceNum, CHIP_ERROR error)
{
auto commandHandleRef = std::move(mAsyncCommandHandle);
auto commandHandle = commandHandleRef.Get();
if (commandHandle == nullptr)
{
return;
}
if (mSetActiveDatasetSequenceNumber != sequenceNum)
{
// Previous SetActiveDatasetRequest was handled.
return;
}
if (error == CHIP_NO_ERROR)
{
// TODO: SPEC Issue #10022
CommitSavedBreadcrumb();
}
else
{
ChipLogError(Zcl, "Failed on activating the active dataset for Thread BR: %" CHIP_ERROR_FORMAT, error.Format());
}
commandHandle->AddStatus(mPath, StatusIB(error).mStatus);
}

void ServerInstance::ReportAttributeChanged(AttributeId attributeId)
{
MatterReportingAttributeChangeCallback(mServerEndpointId, Id, attributeId);
}

void ServerInstance::OnFailSafeTimerExpired()
{
if (mDelegate)
{
mDelegate->RevertActiveDataset();
}
auto commandHandleRef = std::move(mAsyncCommandHandle);
auto commandHandle = commandHandleRef.Get();
if (commandHandle == nullptr)
{
return;
}
commandHandle->AddStatus(mPath, Status::Timeout);
}

void ServerInstance::OnPlatformEventHandler(const DeviceLayer::ChipDeviceEvent * event, intptr_t arg)
{
ServerInstance * _this = reinterpret_cast<ServerInstance *>(arg);
if (event->Type == DeviceLayer::DeviceEventType::kFailSafeTimerExpired)
{
_this->OnFailSafeTimerExpired();
}
else if (event->Type == DeviceLayer::DeviceEventType::kCommissioningComplete)
{
_this->mDelegate->CommitActiveDataset();
}
}

CHIP_ERROR ServerInstance::Init()
{
ReturnErrorCodeIf(!mDelegate, CHIP_ERROR_INVALID_ARGUMENT);
ReturnErrorOnFailure(CommandHandlerInterfaceRegistry::RegisterCommandHandler(this));
VerifyOrReturnError(registerAttributeAccessOverride(this), CHIP_ERROR_INCORRECT_STATE);
ReturnErrorOnFailure(DeviceLayer::PlatformMgrImpl().AddEventHandler(OnPlatformEventHandler, reinterpret_cast<intptr_t>(this)));
return mDelegate->Init(this);
}

} // namespace ThreadBorderRouterManagement
} // namespace Clusters
} // namespace app
} // namespace chip

void MatterThreadBorderRouterManagementPluginServerInitCallback()
{
// Nothing to do, the server init routine will be done in Instance::Init()
}
Loading

0 comments on commit 697b842

Please sign in to comment.