Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Thread Border Router Management cluster server implementation and Generic Thread BR delegate #33872

Merged
merged 52 commits into from
Jul 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
2b50fd7
Add Thread Border Router Management cluster server implementation and…
wqx6 Jun 12, 2024
38ef19a
Restyled by clang-format
restyled-commits Jun 12, 2024
a0349ca
Fix compile error for external platform and mark thread-br-management…
wqx6 Jun 13, 2024
3550ee4
remove the topology request and response command and fix the id of ac…
wqx6 Jun 13, 2024
6421343
review changes
wqx6 Jun 14, 2024
5ad4655
move the border router delegate to generic directory
wqx6 Jun 18, 2024
c8f1e9f
Restyled by clang-format
restyled-commits Jun 18, 2024
ea5aff6
remove the duplicate thread br management cluster in controller zap
wqx6 Jun 20, 2024
280a619
Ensure that the Get dataset response failure status matches the spec
wqx6 Jun 21, 2024
656fe2c
add the document for ActivateDatasetCallback
wqx6 Jun 24, 2024
3c2e3ad
Update GenericThreadBorderRouterDelegate and add the function descrip…
wqx6 Jun 25, 2024
9e4bc25
Restyled by clang-format
restyled-commits Jun 25, 2024
b0cca25
Add unit test
wqx6 Jun 26, 2024
2f8e888
Restyled by clang-format
restyled-commits Jun 26, 2024
8911398
Restyled by gn
restyled-commits Jun 26, 2024
349d3d9
Update src/app/clusters/thread-border-router-management-server/thread…
wqx6 Jun 27, 2024
1309bf6
Update src/app/clusters/thread-border-router-management-server/thread…
wqx6 Jun 27, 2024
d2a332b
Update src/app/clusters/thread-border-router-management-server/thread…
wqx6 Jun 27, 2024
f03cc4b
Update src/app/clusters/thread-border-router-management-server/thread…
wqx6 Jun 27, 2024
5becd0a
add logic of no callbacks for an activate-before-revert happening aft…
wqx6 Jun 27, 2024
f92d997
fix clang tidy error
wqx6 Jun 27, 2024
479f08e
rename the delegate implementation class
wqx6 Jul 1, 2024
1ddbedd
Restyled by clang-format
restyled-commits Jul 1, 2024
be5b828
Try to fix CI tests
wqx6 Jul 1, 2024
3419a00
some review changes
wqx6 Jul 3, 2024
4098d93
Restyled by clang-format
restyled-commits Jul 3, 2024
a024728
rename the source files
wqx6 Jul 3, 2024
00bd9d1
review changes
wqx6 Jul 4, 2024
4c5a0a2
some review changes
wqx6 Jun 12, 2024
f8b8f56
Restyled by clang-format
restyled-commits Jul 8, 2024
8ab955d
review changes from marius
wqx6 Jul 9, 2024
0995ccb
review changes
wqx6 Jul 12, 2024
1e0c3b9
add commissioning complete action
wqx6 Jul 12, 2024
6463cf3
Restyled by clang-format
restyled-commits Jul 12, 2024
2f1184f
Only build TestThreadBorderRouterManagementCluster for no-fake platform
wqx6 Jul 12, 2024
71e3d3c
fix committing active dataset configured
wqx6 Jul 15, 2024
c4e2d69
Merge branch 'master' into thread-br-mgmt/server
wqx6 Jul 16, 2024
387fb09
add attribute change report logic
wqx6 Jul 18, 2024
9fa5a6c
Restyled by clang-format
restyled-commits Jul 18, 2024
8cb5ef2
Merge branch 'master' into thread-br-mgmt/server
wqx6 Jul 18, 2024
872abd9
Don't build TestThreadBorderRouterManagementCluster with TestCommissi…
wqx6 Jul 18, 2024
3bcd93e
Merge branch 'thread-br-mgmt/server' of https://github.com/wqx6/conne…
wqx6 Jul 18, 2024
93b7744
Merge branch 'master' into thread-br-mgmt/server
wqx6 Jul 18, 2024
4dba805
review changes
wqx6 Jul 22, 2024
fc02deb
Merge branch 'master' into thread-br-mgmt/server
wqx6 Jul 22, 2024
8a08287
Restyled by clang-format
restyled-commits Jul 22, 2024
c177a9c
regenerate zap
wqx6 Jul 23, 2024
605b1ae
Merge branch 'master' into thread-br-mgmt/server
dhrishi Jul 29, 2024
b89eb4f
Fix the compilation error
dhrishi Jul 29, 2024
ac173d6
Update thread-border-router-management-server.cpp
dhrishi Jul 29, 2024
23bcb7d
Update thread-border-router-management-server.cpp
dhrishi Jul 29, 2024
e13880a
Change the sequence of the header file inclusion
dhrishi Jul 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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))
wqx6 marked this conversation as resolved.
Show resolved Hide resolved
{
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 };
tcarmelveilleux marked this conversation as resolved.
Show resolved Hide resolved
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();
wqx6 marked this conversation as resolved.
Show resolved Hide resolved
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)
wqx6 marked this conversation as resolved.
Show resolved Hide resolved
{
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();
tcarmelveilleux marked this conversation as resolved.
Show resolved Hide resolved
}
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
Loading