Skip to content

Commit 697b842

Browse files
wqx6restyled-commitsbzbarsky-appledhrishi
authored andcommittedJul 31, 2024
Add Thread Border Router Management cluster server implementation and 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 <bzbarsky@apple.com> * Update src/app/clusters/thread-border-router-management-server/thread-br-delegate.h Co-authored-by: Boris Zbarsky <bzbarsky@apple.com> * Update src/app/clusters/thread-border-router-management-server/thread-br-delegate.h Co-authored-by: Boris Zbarsky <bzbarsky@apple.com> * Update src/app/clusters/thread-border-router-management-server/thread-br-delegate.h Co-authored-by: Boris Zbarsky <bzbarsky@apple.com> * 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 <commits@restyled.io> Co-authored-by: Boris Zbarsky <bzbarsky@apple.com> Co-authored-by: Hrishikesh Dhayagude <dhrishi@users.noreply.github.com>
1 parent bec8edc commit 697b842

File tree

18 files changed

+1295
-393
lines changed

18 files changed

+1295
-393
lines changed
 

‎config/esp32/components/chip/CMakeLists.txt

+6
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,12 @@ else()
237237
chip_gn_arg_append("chip_openthread_ftd" "false")
238238
endif()
239239

240+
if (CONFIG_OPENTHREAD_BORDER_ROUTER)
241+
chip_gn_arg_append("chip_openthread_border_router" "true")
242+
else()
243+
chip_gn_arg_append("chip_openthread_border_router" "false")
244+
endif()
245+
240246
if (CONFIG_ENABLE_OTA_REQUESTOR)
241247
chip_gn_arg_append("chip_enable_ota_requestor" "true")
242248
endif()

‎examples/platform/esp32/external_platform/ESP32_custom/BUILD.gn

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ declare_args() {
2929
chip_bt_bluedroid_enabled = true
3030
chip_max_discovered_ip_addresses = 5
3131
chip_enable_route_hook = false
32+
chip_enable_thread_border_router = false
3233
}
3334

3435
buildconfig_header("custom_buildconfig") {

‎src/app/chip_data_model.gni

+6
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,12 @@ template("chip_data_model") {
398398
"${_app_root}/clusters/${cluster}/thread-network-diagnostics-provider.cpp",
399399
"${_app_root}/clusters/${cluster}/thread-network-diagnostics-provider.h",
400400
]
401+
} else if (cluster == "thread-border-router-management-server") {
402+
sources += [
403+
"${_app_root}/clusters/${cluster}/thread-border-router-management-server.cpp",
404+
"${_app_root}/clusters/${cluster}/thread-border-router-management-server.h",
405+
"${_app_root}/clusters/${cluster}/thread-br-delegate.h",
406+
]
401407
} else if (cluster == "water-heater-management-server") {
402408
sources += [
403409
"${_app_root}/clusters/${cluster}/${cluster}.cpp",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,357 @@
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

Comments
 (0)