diff --git a/examples/network-manager-app/network-manager-common/network-manager-app.matter b/examples/network-manager-app/network-manager-common/network-manager-app.matter index bbd20d46b43d94..3f24e4be5744c6 100644 --- a/examples/network-manager-app/network-manager-common/network-manager-app.matter +++ b/examples/network-manager-app/network-manager-common/network-manager-app.matter @@ -1518,6 +1518,7 @@ provisional cluster ThreadBorderRouterManagement = 1106 { provisional readonly attribute int16u threadVersion = 2; provisional readonly attribute boolean interfaceEnabled = 3; provisional readonly attribute nullable int64u activeDatasetTimestamp = 4; + provisional readonly attribute nullable int64u pendingDatasetTimestamp = 5; readonly attribute command_id generatedCommandList[] = 65528; readonly attribute command_id acceptedCommandList[] = 65529; readonly attribute event_id eventList[] = 65530; @@ -1879,6 +1880,7 @@ endpoint 1 { callback attribute threadVersion; callback attribute interfaceEnabled; callback attribute activeDatasetTimestamp; + callback attribute pendingDatasetTimestamp; callback attribute generatedCommandList; callback attribute acceptedCommandList; callback attribute eventList; diff --git a/examples/network-manager-app/network-manager-common/network-manager-app.zap b/examples/network-manager-app/network-manager-common/network-manager-app.zap index 0a14bcb26d7b03..7c1445ac46c190 100644 --- a/examples/network-manager-app/network-manager-common/network-manager-app.zap +++ b/examples/network-manager-app/network-manager-common/network-manager-app.zap @@ -3420,7 +3420,7 @@ "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": "", + "defaultValue": null, "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -3436,7 +3436,7 @@ "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": "", + "defaultValue": null, "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -3452,7 +3452,7 @@ "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": "", + "defaultValue": null, "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -3468,7 +3468,7 @@ "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": "", + "defaultValue": null, "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -3484,6 +3484,22 @@ "storageOption": "External", "singleton": 0, "bounded": 0, + "defaultValue": null, + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "PendingDatasetTimestamp", + "code": 5, + "mfgCode": null, + "side": "server", + "type": "int64u", + "included": 1, + "storageOption": "External", + "singleton": 0, + "bounded": 0, "defaultValue": "", "reportable": 1, "minInterval": 1, @@ -3500,7 +3516,7 @@ "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": "", + "defaultValue": null, "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -3516,7 +3532,7 @@ "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": "", + "defaultValue": null, "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -3532,7 +3548,7 @@ "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": "", + "defaultValue": null, "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -3548,7 +3564,7 @@ "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": "", + "defaultValue": null, "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -3564,7 +3580,7 @@ "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": "", + "defaultValue": null, "reportable": 1, "minInterval": 1, "maxInterval": 65534, diff --git a/src/app/clusters/thread-border-router-management-server/thread-border-router-management-server.cpp b/src/app/clusters/thread-border-router-management-server/thread-border-router-management-server.cpp index 5592da410100c2..4956f3f1fcc0be 100644 --- a/src/app/clusters/thread-border-router-management-server/thread-border-router-management-server.cpp +++ b/src/app/clusters/thread-border-router-management-server/thread-border-router-management-server.cpp @@ -38,6 +38,7 @@ #include "platform/CHIPDeviceEvent.h" #include "platform/PlatformManager.h" #include "protocols/interaction_model/StatusCode.h" +#include namespace chip { namespace app { @@ -46,21 +47,24 @@ namespace ThreadBorderRouterManagement { using Protocols::InteractionModel::Status; -static bool IsCommandOverCASESession(CommandHandlerInterface::HandlerContext & ctx) +bool ServerInstance::IsCommandOverCASESession(CommandHandlerInterface::HandlerContext & ctx) { +#if CONFIG_BUILD_FOR_HOST_UNIT_TEST + if (mSkipCASESessionCheck) + { + return true; + } +#endif // CONFIG_BUILD_FOR_HOST_UNIT_TEST 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, +Status ServerInstance::HandleGetDatasetRequest(CommandHandlerInterface::HandlerContext & ctx, Delegate::DatasetType type, Thread::OperationalDataset & dataset) { VerifyOrDie(mDelegate); - if (!isOverCASESession) - { - return Status::UnsupportedAccess; - } + VerifyOrReturnValue(IsCommandOverCASESession(ctx), Status::UnsupportedAccess); CHIP_ERROR err = mDelegate->GetDataset(dataset, type); if (err != CHIP_NO_ERROR) @@ -70,7 +74,7 @@ Status ServerInstance::HandleGetDatasetRequest(bool isOverCASESession, Delegate: return Status::Success; } -Status ServerInstance::HandleSetActiveDatasetRequest(CommandHandler * commandHandler, +Status ServerInstance::HandleSetActiveDatasetRequest(CommandHandlerInterface::HandlerContext & ctx, const Commands::SetActiveDatasetRequest::DecodableType & req) { // The SetActiveDatasetRequest command SHALL be FailSafeArmed. Upon receiving this command, the Thread BR will set its @@ -80,7 +84,8 @@ Status ServerInstance::HandleSetActiveDatasetRequest(CommandHandler * commandHan // 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); + VerifyOrReturnValue(IsCommandOverCASESession(ctx), Status::UnsupportedAccess); + VerifyOrReturnValue(mFailsafeContext.IsFailSafeArmed(ctx.mCommandHandler.GetAccessingFabricIndex()), Status::FailsafeRequired); Thread::OperationalDataset activeDataset; Thread::OperationalDataset currentActiveDataset; @@ -101,17 +106,19 @@ Status ServerInstance::HandleSetActiveDatasetRequest(CommandHandler * commandHan { return Status::Busy; } - commandHandler->FlushAcksRightAwayOnSlowCommand(); - mAsyncCommandHandle = CommandHandler::Handle(commandHandler); + ctx.mCommandHandler.FlushAcksRightAwayOnSlowCommand(); + mAsyncCommandHandle = CommandHandler::Handle(&ctx.mCommandHandler); mBreadcrumb = req.breadcrumb; mSetActiveDatasetSequenceNumber++; mDelegate->SetActiveDataset(activeDataset, mSetActiveDatasetSequenceNumber, this); return Status::Success; } -Status ServerInstance::HandleSetPendingDatasetRequest(const Commands::SetPendingDatasetRequest::DecodableType & req) +Status ServerInstance::HandleSetPendingDatasetRequest(CommandHandlerInterface::HandlerContext & ctx, + const Commands::SetPendingDatasetRequest::DecodableType & req) { VerifyOrDie(mDelegate); + VerifyOrReturnValue(IsCommandOverCASESession(ctx), Status::UnsupportedAccess); if (!mDelegate->GetPanChangeSupported()) { return Status::UnsupportedCommand; @@ -143,21 +150,21 @@ void ServerInstance::InvokeCommand(HandlerContext & ctxt) case Commands::GetActiveDatasetRequest::Id: HandleCommand(ctxt, [this](HandlerContext & ctx, const auto & req) { Thread::OperationalDataset dataset; - Status status = HandleGetActiveDatasetRequest(IsCommandOverCASESession(ctx), dataset); + Status status = HandleGetActiveDatasetRequest(ctx, dataset); AddDatasetResponse(ctx, status, dataset); }); break; case Commands::GetPendingDatasetRequest::Id: HandleCommand(ctxt, [this](HandlerContext & ctx, const auto & req) { Thread::OperationalDataset dataset; - Status status = HandleGetPendingDatasetRequest(IsCommandOverCASESession(ctx), dataset); + Status status = HandleGetPendingDatasetRequest(ctx, dataset); AddDatasetResponse(ctx, status, dataset); }); break; case Commands::SetActiveDatasetRequest::Id: HandleCommand(ctxt, [this](HandlerContext & ctx, const auto & req) { mPath = ctx.mRequestPath; - Status status = HandleSetActiveDatasetRequest(&ctx.mCommandHandler, req); + Status status = HandleSetActiveDatasetRequest(ctx, req); if (status != Status::Success) { // If status is not Success, we should immediately report the status. Otherwise the async work will report the @@ -168,7 +175,8 @@ void ServerInstance::InvokeCommand(HandlerContext & ctxt) break; case Commands::SetPendingDatasetRequest::Id: HandleCommand(ctxt, [this](HandlerContext & ctx, const auto & req) { - ctx.mCommandHandler.AddStatus(ctx.mRequestPath, HandleSetPendingDatasetRequest(req)); + Status status = HandleSetPendingDatasetRequest(ctx, req); + ctx.mCommandHandler.AddStatus(ctx.mRequestPath, status); }); break; default: @@ -199,16 +207,28 @@ CHIP_ERROR ServerInstance::ReadBorderAgentID(MutableByteSpan & outBorderAgentId) return CHIP_NO_ERROR; } -Optional ServerInstance::ReadActiveDatasetTimestamp() +std::optional 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 std::make_optional(activeDatasetTimestampValue); + } + return std::nullopt; +} + +std::optional ServerInstance::ReadPendingDatasetTimestamp() +{ + uint64_t pendingDatasetTimestampValue = 0; + Thread::OperationalDataset pendingDataset; + if ((mDelegate->GetDataset(pendingDataset, Delegate::DatasetType::kPending) == CHIP_NO_ERROR) && + (pendingDataset.GetActiveTimestamp(pendingDatasetTimestampValue) == CHIP_NO_ERROR)) + { + return std::make_optional(pendingDatasetTimestampValue); } - return NullOptional; + return std::nullopt; } CHIP_ERROR ServerInstance::Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) @@ -259,9 +279,13 @@ CHIP_ERROR ServerInstance::Read(const ConcreteReadAttributePath & aPath, Attribu break; } case Attributes::ActiveDatasetTimestamp::Id: { - Optional activeDatasetTimestamp = ReadActiveDatasetTimestamp(); - status = activeDatasetTimestamp.HasValue() ? aEncoder.Encode(DataModel::MakeNullable(activeDatasetTimestamp.Value())) - : aEncoder.EncodeNull(); + std::optional activeDatasetTimestamp = ReadActiveDatasetTimestamp(); + status = activeDatasetTimestamp.has_value() ? aEncoder.Encode(activeDatasetTimestamp.value()) : aEncoder.EncodeNull(); + break; + } + case Attributes::PendingDatasetTimestamp::Id: { + std::optional pendingDatasetTimestamp = ReadPendingDatasetTimestamp(); + status = pendingDatasetTimestamp.has_value() ? aEncoder.Encode(pendingDatasetTimestamp.value()) : aEncoder.EncodeNull(); break; } default: diff --git a/src/app/clusters/thread-border-router-management-server/thread-border-router-management-server.h b/src/app/clusters/thread-border-router-management-server/thread-border-router-management-server.h index 639e7b13e77ac0..ae8f65665abb62 100644 --- a/src/app/clusters/thread-border-router-management-server/thread-border-router-management-server.h +++ b/src/app/clusters/thread-border-router-management-server/thread-border-router-management-server.h @@ -64,25 +64,30 @@ class ServerInstance : public CommandHandlerInterface, // TODO: Split the business logic from the unit test class friend class TestThreadBorderRouterManagementCluster; // Command Handlers - Status HandleGetActiveDatasetRequest(bool isOverCASESession, Thread::OperationalDataset & dataset) + Status HandleGetActiveDatasetRequest(HandlerContext & ctx, Thread::OperationalDataset & dataset) { - return HandleGetDatasetRequest(isOverCASESession, Delegate::DatasetType::kActive, dataset); + return HandleGetDatasetRequest(ctx, Delegate::DatasetType::kActive, dataset); } - Status HandleGetPendingDatasetRequest(bool isOverCASESession, Thread::OperationalDataset & dataset) + Status HandleGetPendingDatasetRequest(HandlerContext & ctx, Thread::OperationalDataset & dataset) { - return HandleGetDatasetRequest(isOverCASESession, Delegate::DatasetType::kPending, dataset); + return HandleGetDatasetRequest(ctx, Delegate::DatasetType::kPending, dataset); } - Status HandleSetActiveDatasetRequest(CommandHandler * commandHandler, - const Commands::SetActiveDatasetRequest::DecodableType & req); - Status HandleSetPendingDatasetRequest(const Commands::SetPendingDatasetRequest::DecodableType & req); - Status HandleGetDatasetRequest(bool isOverCASESession, Delegate::DatasetType type, Thread::OperationalDataset & dataset); + Status HandleSetActiveDatasetRequest(HandlerContext & ctx, const Commands::SetActiveDatasetRequest::DecodableType & req); + Status HandleSetPendingDatasetRequest(HandlerContext & ctx, const Commands::SetPendingDatasetRequest::DecodableType & req); + Status HandleGetDatasetRequest(HandlerContext & ctx, Delegate::DatasetType type, Thread::OperationalDataset & dataset); // Attribute Read handlers void ReadFeatureMap(BitFlags & feature); - Optional ReadActiveDatasetTimestamp(); + std::optional ReadActiveDatasetTimestamp(); + std::optional ReadPendingDatasetTimestamp(); CHIP_ERROR ReadBorderRouterName(MutableCharSpan & borderRouterName); CHIP_ERROR ReadBorderAgentID(MutableByteSpan & borderAgentId); +#if CONFIG_BUILD_FOR_HOST_UNIT_TEST + void SetSkipCASESessionCheck(bool skipCheck) { mSkipCASESessionCheck = skipCheck; } + bool mSkipCASESessionCheck; +#endif + bool IsCommandOverCASESession(CommandHandlerInterface::HandlerContext & ctx); static void OnPlatformEventHandler(const DeviceLayer::ChipDeviceEvent * event, intptr_t arg); void OnFailSafeTimerExpired(); void CommitSavedBreadcrumb(); diff --git a/src/app/tests/TestThreadBorderRouterManagementCluster.cpp b/src/app/tests/TestThreadBorderRouterManagementCluster.cpp index a6915824fb2ea2..92018cf4900cc8 100644 --- a/src/app/tests/TestThreadBorderRouterManagementCluster.cpp +++ b/src/app/tests/TestThreadBorderRouterManagementCluster.cpp @@ -17,18 +17,23 @@ #include #include +#include +#include #include #include #include #include #include #include +#include #include +#include #include #include #include #include #include +#include #include #include @@ -153,10 +158,10 @@ static FailSafeContext sTestFailsafeContext; static TestDelegate sTestDelegate; static ServerInstance sTestSeverInstance(kTestEndpointId, &sTestDelegate, sTestFailsafeContext); -class TestSetActiveDatasetCommandHandler : public CommandHandler +class TestCommandHandler : public CommandHandler { public: - TestSetActiveDatasetCommandHandler() : mClusterStatus(Protocols::InteractionModel::Status::Success) {} + TestCommandHandler() : mClusterStatus(Protocols::InteractionModel::Status::Success) {} CHIP_ERROR FallibleAddStatus(const ConcreteCommandPath & aRequestCommandPath, const Protocols::InteractionModel::ClusterStatusCode & aStatus, const char * context = nullptr) { @@ -197,7 +202,7 @@ class TestSetActiveDatasetCommandHandler : public CommandHandler Protocols::InteractionModel::ClusterStatusCode mClusterStatus; }; -TestSetActiveDatasetCommandHandler sTestCommandHandler; +TestCommandHandler sTestCommandHandler; class TestThreadBorderRouterManagementCluster : public ::testing::Test { @@ -256,25 +261,31 @@ TEST_F_FROM_FIXTURE(TestThreadBorderRouterManagementCluster, TestAttributeRead) EXPECT_TRUE(agentIdSpan.data_equal(ByteSpan(sTestDelegate.kTestBorderAgentId))); // ActiveDatasetTimestamp attribute // The active dataset timestamp should be null when no active dataset is configured - Optional timestamp = sTestSeverInstance.ReadActiveDatasetTimestamp(); - EXPECT_FALSE(timestamp.HasValue()); + std::optional timestamp = sTestSeverInstance.ReadActiveDatasetTimestamp(); + EXPECT_FALSE(timestamp.has_value()); } TEST_F_FROM_FIXTURE(TestThreadBorderRouterManagementCluster, TestCommandHandle) { // Test GetActiveDatasetRequest and GetPendingDatasetRequest commands Thread::OperationalDataset dataset; + ThreadBorderRouterManagement::Commands::SetActiveDatasetRequest::DecodableType req1; + Commands::SetPendingDatasetRequest::DecodableType req2; using DatasetType = Delegate::DatasetType; using Status = Protocols::InteractionModel::Status; - // The GetDataset requests should over CASE session. - EXPECT_EQ(sTestSeverInstance.HandleGetDatasetRequest(false /* isOverCASESession */, DatasetType::kActive, dataset), - Status::UnsupportedAccess); - EXPECT_EQ(sTestSeverInstance.HandleGetDatasetRequest(false, DatasetType::kPending, dataset), Status::UnsupportedAccess); + ConcreteCommandPath testPath(kInvalidEndpointId, kInvalidClusterId, kInvalidCommandId); + TLV::TLVReader testTLVReader; + CommandHandlerInterface::HandlerContext ctx(sTestCommandHandler, testPath, testTLVReader); + // All the command should be over CASE session. + EXPECT_EQ(sTestSeverInstance.HandleGetDatasetRequest(ctx, DatasetType::kActive, dataset), Status::UnsupportedAccess); + EXPECT_EQ(sTestSeverInstance.HandleGetDatasetRequest(ctx, DatasetType::kPending, dataset), Status::UnsupportedAccess); + EXPECT_EQ(sTestSeverInstance.HandleSetActiveDatasetRequest(ctx, req1), Status::UnsupportedAccess); + EXPECT_EQ(sTestSeverInstance.HandleSetPendingDatasetRequest(ctx, req2), Status::UnsupportedAccess); + sTestSeverInstance.SetSkipCASESessionCheck(true); // The GetDataset should return NotFound when no dataset is configured. - EXPECT_EQ(sTestSeverInstance.HandleGetDatasetRequest(true, DatasetType::kActive, dataset), Status::NotFound); - EXPECT_EQ(sTestSeverInstance.HandleGetDatasetRequest(true, DatasetType::kPending, dataset), Status::NotFound); + EXPECT_EQ(sTestSeverInstance.HandleGetDatasetRequest(ctx, DatasetType::kActive, dataset), Status::NotFound); + EXPECT_EQ(sTestSeverInstance.HandleGetDatasetRequest(ctx, DatasetType::kPending, dataset), Status::NotFound); // Test SetActiveDatasetRequest - ThreadBorderRouterManagement::Commands::SetActiveDatasetRequest::DecodableType req1; uint8_t invalidDataset[] = { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66 }; uint8_t validDataset[] = { 0x0e, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x0b, 0x35, 0x06, 0x00, 0x04, 0x00, 0x1f, 0xff, 0xe0, 0x02, 0x08, 0xde, 0xaa, 0x00, 0xbe, 0xef, 0x00, 0xca, 0xef, 0x07, @@ -282,19 +293,19 @@ TEST_F_FROM_FIXTURE(TestThreadBorderRouterManagementCluster, TestCommandHandle) 0xc5, 0x25, 0x7f, 0x68, 0x4c, 0x54, 0x9d, 0x6a, 0x57, 0x5e, 0x03, 0x0a, 0x4f, 0x70, 0x65, 0x6e, 0x54, 0x68, 0x72, 0x65, 0x61, 0x64, 0x01, 0x02, 0xc1, 0x15, 0x04, 0x10, 0xcb, 0x13, 0x47, 0xeb, 0x0c, 0xd4, 0xb3, 0x5c, 0xd1, 0x42, 0xda, 0x5e, 0x6d, 0xf1, 0x8b, 0x88, 0x0c, 0x04, 0x02, 0xa0, 0xf7, 0xf8 }; - Optional activeDatasetTimestamp = chip::NullOptional; - activeDatasetTimestamp = sTestSeverInstance.ReadActiveDatasetTimestamp(); - EXPECT_FALSE(activeDatasetTimestamp.HasValue()); + std::optional activeDatasetTimestamp = std::nullopt; + activeDatasetTimestamp = sTestSeverInstance.ReadActiveDatasetTimestamp(); + EXPECT_FALSE(activeDatasetTimestamp.has_value()); req1.activeDataset = ByteSpan(invalidDataset); // SetActiveDatasetRequest is FailsafeRequired. - EXPECT_EQ(sTestSeverInstance.HandleSetActiveDatasetRequest(&sTestCommandHandler, req1), Status::FailsafeRequired); + EXPECT_EQ(sTestSeverInstance.HandleSetActiveDatasetRequest(ctx, req1), Status::FailsafeRequired); EXPECT_EQ(sTestFailsafeContext.ArmFailSafe(kTestAccessingFabricIndex, System::Clock::Seconds16(1)), CHIP_NO_ERROR); // SetActiveDatasetRequest should return InvalidCommand when dataset is invalid. - EXPECT_EQ(sTestSeverInstance.HandleSetActiveDatasetRequest(&sTestCommandHandler, req1), Status::InvalidCommand); + EXPECT_EQ(sTestSeverInstance.HandleSetActiveDatasetRequest(ctx, req1), Status::InvalidCommand); req1.activeDataset = ByteSpan(validDataset); - EXPECT_EQ(sTestSeverInstance.HandleSetActiveDatasetRequest(&sTestCommandHandler, req1), Status::Success); + EXPECT_EQ(sTestSeverInstance.HandleSetActiveDatasetRequest(ctx, req1), Status::Success); // When the Server is handling a SetActiveDatasetRequest command, it should return Busy after receiving another one. - EXPECT_EQ(sTestSeverInstance.HandleSetActiveDatasetRequest(&sTestCommandHandler, req1), Status::Busy); + EXPECT_EQ(sTestSeverInstance.HandleSetActiveDatasetRequest(ctx, req1), Status::Busy); EXPECT_FALSE(sTestDelegate.mInterfaceEnabled); EXPECT_EQ(sTestDelegate.mSetActiveDatasetCommandSequenceNum, static_cast(1)); // Activate the dataset. @@ -303,30 +314,29 @@ TEST_F_FROM_FIXTURE(TestThreadBorderRouterManagementCluster, TestCommandHandle) Protocols::InteractionModel::ClusterStatusCode(Protocols::InteractionModel::Status::Success)); sTestFailsafeContext.DisarmFailSafe(); // The Dataset should be updated. - EXPECT_EQ(sTestSeverInstance.HandleGetDatasetRequest(true, DatasetType::kActive, dataset), Status::Success); + EXPECT_EQ(sTestSeverInstance.HandleGetDatasetRequest(ctx, DatasetType::kActive, dataset), Status::Success); EXPECT_TRUE(dataset.AsByteSpan().data_equal(ByteSpan(validDataset))); EXPECT_TRUE(sTestDelegate.mInterfaceEnabled); activeDatasetTimestamp = sTestSeverInstance.ReadActiveDatasetTimestamp(); // activeDatasetTimestamp should have value. - EXPECT_TRUE(activeDatasetTimestamp.HasValue()); + EXPECT_TRUE(activeDatasetTimestamp.has_value()); EXPECT_EQ(sTestFailsafeContext.ArmFailSafe(kTestAccessingFabricIndex, System::Clock::Seconds16(1)), CHIP_NO_ERROR); // When ActiveDatasetTimestamp is not null, the set active dataset request should return InvalidInState. - EXPECT_EQ(sTestSeverInstance.HandleSetActiveDatasetRequest(&sTestCommandHandler, req1), Status::InvalidInState); + EXPECT_EQ(sTestSeverInstance.HandleSetActiveDatasetRequest(ctx, req1), Status::InvalidInState); sTestFailsafeContext.DisarmFailSafe(); // Test SetPendingDatasetRequest command - Commands::SetPendingDatasetRequest::DecodableType req2; sTestDelegate.mPanChangeSupported = false; req2.pendingDataset = ByteSpan(validDataset); // SetPendingDatasetRequest is supported when PANChange feature is enabled. - EXPECT_EQ(sTestSeverInstance.HandleSetPendingDatasetRequest(req2), Status::UnsupportedCommand); + EXPECT_EQ(sTestSeverInstance.HandleSetPendingDatasetRequest(ctx, req2), Status::UnsupportedCommand); sTestDelegate.mPanChangeSupported = true; req2.pendingDataset = ByteSpan(invalidDataset); // SetPendingDatasetRequest should return InvalidCommand when dataset is invalid. - EXPECT_EQ(sTestSeverInstance.HandleSetPendingDatasetRequest(req2), Status::InvalidCommand); + EXPECT_EQ(sTestSeverInstance.HandleSetPendingDatasetRequest(ctx, req2), Status::InvalidCommand); req2.pendingDataset = ByteSpan(validDataset); // Success SetPendingDatasetRequest - EXPECT_EQ(sTestSeverInstance.HandleSetPendingDatasetRequest(req2), Status::Success); - EXPECT_EQ(sTestSeverInstance.HandleGetDatasetRequest(true, DatasetType::kPending, dataset), Status::Success); + EXPECT_EQ(sTestSeverInstance.HandleSetPendingDatasetRequest(ctx, req2), Status::Success); + EXPECT_EQ(sTestSeverInstance.HandleGetDatasetRequest(ctx, DatasetType::kPending, dataset), Status::Success); EXPECT_TRUE(dataset.AsByteSpan().data_equal(ByteSpan(validDataset))); } diff --git a/src/app/tests/suites/certification/Test_TC_TBRM_2_1.yaml b/src/app/tests/suites/certification/Test_TC_TBRM_2_1.yaml index dd58b93543dfc7..7ee1e74fb0123d 100644 --- a/src/app/tests/suites/certification/Test_TC_TBRM_2_1.yaml +++ b/src/app/tests/suites/certification/Test_TC_TBRM_2_1.yaml @@ -74,10 +74,10 @@ tests: response: constraints: type: int64u - # TODO: Attribute missing from cluster XML - # - label: "TH reads the PendingDatasetTimestamp attribute from the DUT" - # command: readAttribute - # attribute: PendingDatasetTimestamp - # response: - # constraints: - # type: int64u + + - label: "TH reads the PendingDatasetTimestamp attribute from the DUT" + command: readAttribute + attribute: PendingDatasetTimestamp + response: + constraints: + type: int64u diff --git a/src/app/zap-templates/zcl/data-model/chip/thread-border-router-management-cluster.xml b/src/app/zap-templates/zcl/data-model/chip/thread-border-router-management-cluster.xml index d7565e0262c9da..d05ce70c34aca3 100644 --- a/src/app/zap-templates/zcl/data-model/chip/thread-border-router-management-cluster.xml +++ b/src/app/zap-templates/zcl/data-model/chip/thread-border-router-management-cluster.xml @@ -43,6 +43,8 @@ limitations under the License. ActiveDatasetTimestamp + PendingDatasetTimestamp + Command to request the active operational dataset of the Thread network to which the border router is connected. This command must be sent over a valid CASE session diff --git a/src/app/zap-templates/zcl/zcl-with-test-extensions.json b/src/app/zap-templates/zcl/zcl-with-test-extensions.json index 4de0247c17d86a..12733488366d1b 100644 --- a/src/app/zap-templates/zcl/zcl-with-test-extensions.json +++ b/src/app/zap-templates/zcl/zcl-with-test-extensions.json @@ -320,6 +320,7 @@ "ThreadVersion", "InterfaceEnabled", "ActiveDatasetTimestamp", + "PendingDatasetTimestamp", "FeatureMap" ], "Thread Network Diagnostics": [ diff --git a/src/app/zap-templates/zcl/zcl.json b/src/app/zap-templates/zcl/zcl.json index 3a17b76c4723b9..f4979fbe49c72d 100644 --- a/src/app/zap-templates/zcl/zcl.json +++ b/src/app/zap-templates/zcl/zcl.json @@ -318,6 +318,7 @@ "ThreadVersion", "InterfaceEnabled", "ActiveDatasetTimestamp", + "PendingDatasetTimestamp", "FeatureMap" ], "Thread Network Diagnostics": [ diff --git a/src/controller/data_model/controller-clusters.matter b/src/controller/data_model/controller-clusters.matter index 810f82819c1a19..a6dad3fe9dc86d 100644 --- a/src/controller/data_model/controller-clusters.matter +++ b/src/controller/data_model/controller-clusters.matter @@ -8243,6 +8243,7 @@ provisional cluster ThreadBorderRouterManagement = 1106 { provisional readonly attribute int16u threadVersion = 2; provisional readonly attribute boolean interfaceEnabled = 3; provisional readonly attribute nullable int64u activeDatasetTimestamp = 4; + provisional readonly attribute nullable int64u pendingDatasetTimestamp = 5; readonly attribute command_id generatedCommandList[] = 65528; readonly attribute command_id acceptedCommandList[] = 65529; readonly attribute event_id eventList[] = 65530; diff --git a/src/controller/java/generated/java/chip/devicecontroller/ChipClusters.java b/src/controller/java/generated/java/chip/devicecontroller/ChipClusters.java index 10863976c661a8..cdb08fa431c02c 100644 --- a/src/controller/java/generated/java/chip/devicecontroller/ChipClusters.java +++ b/src/controller/java/generated/java/chip/devicecontroller/ChipClusters.java @@ -54619,6 +54619,7 @@ public static class ThreadBorderRouterManagementCluster extends BaseChipCluster private static final long THREAD_VERSION_ATTRIBUTE_ID = 2L; private static final long INTERFACE_ENABLED_ATTRIBUTE_ID = 3L; private static final long ACTIVE_DATASET_TIMESTAMP_ATTRIBUTE_ID = 4L; + private static final long PENDING_DATASET_TIMESTAMP_ATTRIBUTE_ID = 5L; private static final long GENERATED_COMMAND_LIST_ATTRIBUTE_ID = 65528L; private static final long ACCEPTED_COMMAND_LIST_ATTRIBUTE_ID = 65529L; private static final long EVENT_LIST_ATTRIBUTE_ID = 65530L; @@ -54740,6 +54741,10 @@ public interface ActiveDatasetTimestampAttributeCallback extends BaseAttributeCa void onSuccess(@Nullable Long value); } + public interface PendingDatasetTimestampAttributeCallback extends BaseAttributeCallback { + void onSuccess(@Nullable Long value); + } + public interface GeneratedCommandListAttributeCallback extends BaseAttributeCallback { void onSuccess(List value); } @@ -54886,6 +54891,32 @@ public void onSuccess(byte[] tlv) { }, ACTIVE_DATASET_TIMESTAMP_ATTRIBUTE_ID, minInterval, maxInterval); } + public void readPendingDatasetTimestampAttribute( + PendingDatasetTimestampAttributeCallback callback) { + ChipAttributePath path = ChipAttributePath.newInstance(endpointId, clusterId, PENDING_DATASET_TIMESTAMP_ATTRIBUTE_ID); + + readAttribute(new ReportCallbackImpl(callback, path) { + @Override + public void onSuccess(byte[] tlv) { + @Nullable Long value = ChipTLVValueDecoder.decodeAttributeValue(path, tlv); + callback.onSuccess(value); + } + }, PENDING_DATASET_TIMESTAMP_ATTRIBUTE_ID, true); + } + + public void subscribePendingDatasetTimestampAttribute( + PendingDatasetTimestampAttributeCallback callback, int minInterval, int maxInterval) { + ChipAttributePath path = ChipAttributePath.newInstance(endpointId, clusterId, PENDING_DATASET_TIMESTAMP_ATTRIBUTE_ID); + + subscribeAttribute(new ReportCallbackImpl(callback, path) { + @Override + public void onSuccess(byte[] tlv) { + @Nullable Long value = ChipTLVValueDecoder.decodeAttributeValue(path, tlv); + callback.onSuccess(value); + } + }, PENDING_DATASET_TIMESTAMP_ATTRIBUTE_ID, minInterval, maxInterval); + } + public void readGeneratedCommandListAttribute( GeneratedCommandListAttributeCallback callback) { ChipAttributePath path = ChipAttributePath.newInstance(endpointId, clusterId, GENERATED_COMMAND_LIST_ATTRIBUTE_ID); diff --git a/src/controller/java/generated/java/chip/devicecontroller/ClusterIDMapping.java b/src/controller/java/generated/java/chip/devicecontroller/ClusterIDMapping.java index 9a5fd5658ea2dd..6859e8b7f1e5b3 100644 --- a/src/controller/java/generated/java/chip/devicecontroller/ClusterIDMapping.java +++ b/src/controller/java/generated/java/chip/devicecontroller/ClusterIDMapping.java @@ -14867,6 +14867,7 @@ public enum Attribute { ThreadVersion(2L), InterfaceEnabled(3L), ActiveDatasetTimestamp(4L), + PendingDatasetTimestamp(5L), GeneratedCommandList(65528L), AcceptedCommandList(65529L), EventList(65530L), diff --git a/src/controller/java/generated/java/chip/devicecontroller/ClusterInfoMapping.java b/src/controller/java/generated/java/chip/devicecontroller/ClusterInfoMapping.java index d2703a0146de5a..1295c78431d151 100644 --- a/src/controller/java/generated/java/chip/devicecontroller/ClusterInfoMapping.java +++ b/src/controller/java/generated/java/chip/devicecontroller/ClusterInfoMapping.java @@ -18094,6 +18094,27 @@ public void onError(Exception ex) { } } + public static class DelegatedThreadBorderRouterManagementClusterPendingDatasetTimestampAttributeCallback implements ChipClusters.ThreadBorderRouterManagementCluster.PendingDatasetTimestampAttributeCallback, DelegatedClusterCallback { + private ClusterCommandCallback callback; + @Override + public void setCallbackDelegate(ClusterCommandCallback callback) { + this.callback = callback; + } + + @Override + public void onSuccess(@Nullable Long value) { + Map responseValues = new LinkedHashMap<>(); + CommandResponseInfo commandResponseInfo = new CommandResponseInfo("value", "Long"); + responseValues.put(commandResponseInfo, value); + callback.onSuccess(responseValues); + } + + @Override + public void onError(Exception ex) { + callback.onFailure(ex); + } + } + public static class DelegatedThreadBorderRouterManagementClusterGeneratedCommandListAttributeCallback implements ChipClusters.ThreadBorderRouterManagementCluster.GeneratedCommandListAttributeCallback, DelegatedClusterCallback { private ClusterCommandCallback callback; @Override diff --git a/src/controller/java/generated/java/chip/devicecontroller/ClusterReadMapping.java b/src/controller/java/generated/java/chip/devicecontroller/ClusterReadMapping.java index 7aa5a710554223..c96de698113955 100644 --- a/src/controller/java/generated/java/chip/devicecontroller/ClusterReadMapping.java +++ b/src/controller/java/generated/java/chip/devicecontroller/ClusterReadMapping.java @@ -17129,6 +17129,17 @@ private static Map readThreadBorderRouterManagementInte readThreadBorderRouterManagementActiveDatasetTimestampCommandParams ); result.put("readActiveDatasetTimestampAttribute", readThreadBorderRouterManagementActiveDatasetTimestampAttributeInteractionInfo); + Map readThreadBorderRouterManagementPendingDatasetTimestampCommandParams = new LinkedHashMap(); + InteractionInfo readThreadBorderRouterManagementPendingDatasetTimestampAttributeInteractionInfo = new InteractionInfo( + (cluster, callback, commandArguments) -> { + ((ChipClusters.ThreadBorderRouterManagementCluster) cluster).readPendingDatasetTimestampAttribute( + (ChipClusters.ThreadBorderRouterManagementCluster.PendingDatasetTimestampAttributeCallback) callback + ); + }, + () -> new ClusterInfoMapping.DelegatedThreadBorderRouterManagementClusterPendingDatasetTimestampAttributeCallback(), + readThreadBorderRouterManagementPendingDatasetTimestampCommandParams + ); + result.put("readPendingDatasetTimestampAttribute", readThreadBorderRouterManagementPendingDatasetTimestampAttributeInteractionInfo); Map readThreadBorderRouterManagementGeneratedCommandListCommandParams = new LinkedHashMap(); InteractionInfo readThreadBorderRouterManagementGeneratedCommandListAttributeInteractionInfo = new InteractionInfo( (cluster, callback, commandArguments) -> { diff --git a/src/controller/java/generated/java/matter/controller/cluster/clusters/ThreadBorderRouterManagementCluster.kt b/src/controller/java/generated/java/matter/controller/cluster/clusters/ThreadBorderRouterManagementCluster.kt index b212ff7904d2fc..efbb1b5baff7e4 100644 --- a/src/controller/java/generated/java/matter/controller/cluster/clusters/ThreadBorderRouterManagementCluster.kt +++ b/src/controller/java/generated/java/matter/controller/cluster/clusters/ThreadBorderRouterManagementCluster.kt @@ -58,6 +58,17 @@ class ThreadBorderRouterManagementCluster( object SubscriptionEstablished : ActiveDatasetTimestampAttributeSubscriptionState() } + class PendingDatasetTimestampAttribute(val value: ULong?) + + sealed class PendingDatasetTimestampAttributeSubscriptionState { + data class Success(val value: ULong?) : PendingDatasetTimestampAttributeSubscriptionState() + + data class Error(val exception: Exception) : + PendingDatasetTimestampAttributeSubscriptionState() + + object SubscriptionEstablished : PendingDatasetTimestampAttributeSubscriptionState() + } + class GeneratedCommandListAttribute(val value: List) sealed class GeneratedCommandListAttributeSubscriptionState { @@ -655,6 +666,101 @@ class ThreadBorderRouterManagementCluster( } } + suspend fun readPendingDatasetTimestampAttribute(): PendingDatasetTimestampAttribute { + val ATTRIBUTE_ID: UInt = 5u + + val attributePath = + AttributePath(endpointId = endpointId, clusterId = CLUSTER_ID, attributeId = ATTRIBUTE_ID) + + val readRequest = ReadRequest(eventPaths = emptyList(), attributePaths = listOf(attributePath)) + + val response = controller.read(readRequest) + + if (response.successes.isEmpty()) { + logger.log(Level.WARNING, "Read command failed") + throw IllegalStateException("Read command failed with failures: ${response.failures}") + } + + logger.log(Level.FINE, "Read command succeeded") + + val attributeData = + response.successes.filterIsInstance().firstOrNull { + it.path.attributeId == ATTRIBUTE_ID + } + + requireNotNull(attributeData) { "Pendingdatasettimestamp attribute not found in response" } + + // Decode the TLV data into the appropriate type + val tlvReader = TlvReader(attributeData.data) + val decodedValue: ULong? = + if (!tlvReader.isNull()) { + tlvReader.getULong(AnonymousTag) + } else { + tlvReader.getNull(AnonymousTag) + null + } + + return PendingDatasetTimestampAttribute(decodedValue) + } + + suspend fun subscribePendingDatasetTimestampAttribute( + minInterval: Int, + maxInterval: Int, + ): Flow { + val ATTRIBUTE_ID: UInt = 5u + val attributePaths = + listOf( + AttributePath(endpointId = endpointId, clusterId = CLUSTER_ID, attributeId = ATTRIBUTE_ID) + ) + + val subscribeRequest: SubscribeRequest = + SubscribeRequest( + eventPaths = emptyList(), + attributePaths = attributePaths, + minInterval = Duration.ofSeconds(minInterval.toLong()), + maxInterval = Duration.ofSeconds(maxInterval.toLong()), + ) + + return controller.subscribe(subscribeRequest).transform { subscriptionState -> + when (subscriptionState) { + is SubscriptionState.SubscriptionErrorNotification -> { + emit( + PendingDatasetTimestampAttributeSubscriptionState.Error( + Exception( + "Subscription terminated with error code: ${subscriptionState.terminationCause}" + ) + ) + ) + } + is SubscriptionState.NodeStateUpdate -> { + val attributeData = + subscriptionState.updateState.successes + .filterIsInstance() + .firstOrNull { it.path.attributeId == ATTRIBUTE_ID } + + requireNotNull(attributeData) { + "Pendingdatasettimestamp attribute not found in Node State update" + } + + // Decode the TLV data into the appropriate type + val tlvReader = TlvReader(attributeData.data) + val decodedValue: ULong? = + if (!tlvReader.isNull()) { + tlvReader.getULong(AnonymousTag) + } else { + tlvReader.getNull(AnonymousTag) + null + } + + decodedValue?.let { emit(PendingDatasetTimestampAttributeSubscriptionState.Success(it)) } + } + SubscriptionState.SubscriptionEstablished -> { + emit(PendingDatasetTimestampAttributeSubscriptionState.SubscriptionEstablished) + } + } + } + } + suspend fun readGeneratedCommandListAttribute(): GeneratedCommandListAttribute { val ATTRIBUTE_ID: UInt = 65528u diff --git a/src/controller/java/zap-generated/CHIPAttributeTLVValueDecoder.cpp b/src/controller/java/zap-generated/CHIPAttributeTLVValueDecoder.cpp index ebcec56cd2b41a..ffa1de162de340 100644 --- a/src/controller/java/zap-generated/CHIPAttributeTLVValueDecoder.cpp +++ b/src/controller/java/zap-generated/CHIPAttributeTLVValueDecoder.cpp @@ -38890,6 +38890,29 @@ jobject DecodeAttributeValue(const app::ConcreteAttributePath & aPath, TLV::TLVR } return value; } + case Attributes::PendingDatasetTimestamp::Id: { + using TypeInfo = Attributes::PendingDatasetTimestamp::TypeInfo; + TypeInfo::DecodableType cppValue; + *aError = app::DataModel::Decode(aReader, cppValue); + if (*aError != CHIP_NO_ERROR) + { + return nullptr; + } + jobject value; + if (cppValue.IsNull()) + { + value = nullptr; + } + else + { + std::string valueClassName = "java/lang/Long"; + std::string valueCtorSignature = "(J)V"; + jlong jnivalue = static_cast(cppValue.Value()); + chip::JniReferences::GetInstance().CreateBoxedObject(valueClassName.c_str(), valueCtorSignature.c_str(), + jnivalue, value); + } + return value; + } case Attributes::GeneratedCommandList::Id: { using TypeInfo = Attributes::GeneratedCommandList::TypeInfo; TypeInfo::DecodableType cppValue; diff --git a/src/controller/python/chip/clusters/CHIPClusters.py b/src/controller/python/chip/clusters/CHIPClusters.py index e8972863a72dca..3c67676fded7ac 100644 --- a/src/controller/python/chip/clusters/CHIPClusters.py +++ b/src/controller/python/chip/clusters/CHIPClusters.py @@ -11970,6 +11970,12 @@ class ChipClusters: "type": "int", "reportable": True, }, + 0x00000005: { + "attributeName": "PendingDatasetTimestamp", + "attributeId": 0x00000005, + "type": "int", + "reportable": True, + }, 0x0000FFF8: { "attributeName": "GeneratedCommandList", "attributeId": 0x0000FFF8, diff --git a/src/controller/python/chip/clusters/Objects.py b/src/controller/python/chip/clusters/Objects.py index 670c7109a5ccb8..92ea67795df89d 100644 --- a/src/controller/python/chip/clusters/Objects.py +++ b/src/controller/python/chip/clusters/Objects.py @@ -42067,6 +42067,7 @@ def descriptor(cls) -> ClusterObjectDescriptor: ClusterObjectFieldDescriptor(Label="threadVersion", Tag=0x00000002, Type=uint), ClusterObjectFieldDescriptor(Label="interfaceEnabled", Tag=0x00000003, Type=bool), ClusterObjectFieldDescriptor(Label="activeDatasetTimestamp", Tag=0x00000004, Type=typing.Union[Nullable, uint]), + ClusterObjectFieldDescriptor(Label="pendingDatasetTimestamp", Tag=0x00000005, Type=typing.Union[Nullable, uint]), ClusterObjectFieldDescriptor(Label="generatedCommandList", Tag=0x0000FFF8, Type=typing.List[uint]), ClusterObjectFieldDescriptor(Label="acceptedCommandList", Tag=0x0000FFF9, Type=typing.List[uint]), ClusterObjectFieldDescriptor(Label="eventList", Tag=0x0000FFFA, Type=typing.List[uint]), @@ -42080,6 +42081,7 @@ def descriptor(cls) -> ClusterObjectDescriptor: threadVersion: 'uint' = None interfaceEnabled: 'bool' = None activeDatasetTimestamp: 'typing.Union[Nullable, uint]' = None + pendingDatasetTimestamp: 'typing.Union[Nullable, uint]' = None generatedCommandList: 'typing.List[uint]' = None acceptedCommandList: 'typing.List[uint]' = None eventList: 'typing.List[uint]' = None @@ -42249,6 +42251,22 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor: value: 'typing.Union[Nullable, uint]' = NullValue + @dataclass + class PendingDatasetTimestamp(ClusterAttributeDescriptor): + @ChipUtility.classproperty + def cluster_id(cls) -> int: + return 0x00000452 + + @ChipUtility.classproperty + def attribute_id(cls) -> int: + return 0x00000005 + + @ChipUtility.classproperty + def attribute_type(cls) -> ClusterObjectFieldDescriptor: + return ClusterObjectFieldDescriptor(Type=typing.Union[Nullable, uint]) + + value: 'typing.Union[Nullable, uint]' = NullValue + @dataclass class GeneratedCommandList(ClusterAttributeDescriptor): @ChipUtility.classproperty diff --git a/src/darwin/Framework/CHIP/zap-generated/MTRAttributeSpecifiedCheck.mm b/src/darwin/Framework/CHIP/zap-generated/MTRAttributeSpecifiedCheck.mm index 06aa9c017be027..925ef9cb90f1dc 100644 --- a/src/darwin/Framework/CHIP/zap-generated/MTRAttributeSpecifiedCheck.mm +++ b/src/darwin/Framework/CHIP/zap-generated/MTRAttributeSpecifiedCheck.mm @@ -5490,6 +5490,9 @@ static BOOL AttributeIsSpecifiedInThreadBorderRouterManagementCluster(AttributeI case Attributes::ActiveDatasetTimestamp::Id: { return YES; } + case Attributes::PendingDatasetTimestamp::Id: { + return YES; + } case Attributes::GeneratedCommandList::Id: { return YES; } diff --git a/src/darwin/Framework/CHIP/zap-generated/MTRAttributeTLVValueDecoder.mm b/src/darwin/Framework/CHIP/zap-generated/MTRAttributeTLVValueDecoder.mm index 95001d3ca68720..332c53d5cb3431 100644 --- a/src/darwin/Framework/CHIP/zap-generated/MTRAttributeTLVValueDecoder.mm +++ b/src/darwin/Framework/CHIP/zap-generated/MTRAttributeTLVValueDecoder.mm @@ -15879,6 +15879,21 @@ static id _Nullable DecodeAttributeValueForThreadBorderRouterManagementCluster(A } return value; } + case Attributes::PendingDatasetTimestamp::Id: { + using TypeInfo = Attributes::PendingDatasetTimestamp::TypeInfo; + TypeInfo::DecodableType cppValue; + *aError = DataModel::Decode(aReader, cppValue); + if (*aError != CHIP_NO_ERROR) { + return nil; + } + NSNumber * _Nullable value; + if (cppValue.IsNull()) { + value = nil; + } else { + value = [NSNumber numberWithUnsignedLongLong:cppValue.Value()]; + } + return value; + } default: { break; } diff --git a/src/darwin/Framework/CHIP/zap-generated/MTRBaseClusters.h b/src/darwin/Framework/CHIP/zap-generated/MTRBaseClusters.h index b8dfaa140e03e3..5456773cce62cb 100644 --- a/src/darwin/Framework/CHIP/zap-generated/MTRBaseClusters.h +++ b/src/darwin/Framework/CHIP/zap-generated/MTRBaseClusters.h @@ -13493,6 +13493,12 @@ MTR_PROVISIONALLY_AVAILABLE reportHandler:(void (^)(NSNumber * _Nullable value, NSError * _Nullable error))reportHandler MTR_PROVISIONALLY_AVAILABLE; + (void)readAttributeActiveDatasetTimestampWithClusterStateCache:(MTRClusterStateCacheContainer *)clusterStateCacheContainer endpoint:(NSNumber *)endpoint queue:(dispatch_queue_t)queue completion:(void (^)(NSNumber * _Nullable value, NSError * _Nullable error))completion MTR_PROVISIONALLY_AVAILABLE; +- (void)readAttributePendingDatasetTimestampWithCompletion:(void (^)(NSNumber * _Nullable value, NSError * _Nullable error))completion MTR_PROVISIONALLY_AVAILABLE; +- (void)subscribeAttributePendingDatasetTimestampWithParams:(MTRSubscribeParams *)params + subscriptionEstablished:(MTRSubscriptionEstablishedHandler _Nullable)subscriptionEstablished + reportHandler:(void (^)(NSNumber * _Nullable value, NSError * _Nullable error))reportHandler MTR_PROVISIONALLY_AVAILABLE; ++ (void)readAttributePendingDatasetTimestampWithClusterStateCache:(MTRClusterStateCacheContainer *)clusterStateCacheContainer endpoint:(NSNumber *)endpoint queue:(dispatch_queue_t)queue completion:(void (^)(NSNumber * _Nullable value, NSError * _Nullable error))completion MTR_PROVISIONALLY_AVAILABLE; + - (void)readAttributeGeneratedCommandListWithCompletion:(void (^)(NSArray * _Nullable value, NSError * _Nullable error))completion MTR_PROVISIONALLY_AVAILABLE; - (void)subscribeAttributeGeneratedCommandListWithParams:(MTRSubscribeParams *)params subscriptionEstablished:(MTRSubscriptionEstablishedHandler _Nullable)subscriptionEstablished diff --git a/src/darwin/Framework/CHIP/zap-generated/MTRBaseClusters.mm b/src/darwin/Framework/CHIP/zap-generated/MTRBaseClusters.mm index 2cd1653c867faa..992d7ce644acd0 100644 --- a/src/darwin/Framework/CHIP/zap-generated/MTRBaseClusters.mm +++ b/src/darwin/Framework/CHIP/zap-generated/MTRBaseClusters.mm @@ -95178,6 +95178,42 @@ + (void)readAttributeActiveDatasetTimestampWithClusterStateCache:(MTRClusterStat completion:completion]; } +- (void)readAttributePendingDatasetTimestampWithCompletion:(void (^)(NSNumber * _Nullable value, NSError * _Nullable error))completion +{ + using TypeInfo = ThreadBorderRouterManagement::Attributes::PendingDatasetTimestamp::TypeInfo; + [self.device _readKnownAttributeWithEndpointID:self.endpointID + clusterID:@(TypeInfo::GetClusterId()) + attributeID:@(TypeInfo::GetAttributeId()) + params:nil + queue:self.callbackQueue + completion:completion]; +} + +- (void)subscribeAttributePendingDatasetTimestampWithParams:(MTRSubscribeParams * _Nonnull)params + subscriptionEstablished:(MTRSubscriptionEstablishedHandler _Nullable)subscriptionEstablished + reportHandler:(void (^)(NSNumber * _Nullable value, NSError * _Nullable error))reportHandler +{ + using TypeInfo = ThreadBorderRouterManagement::Attributes::PendingDatasetTimestamp::TypeInfo; + [self.device _subscribeToKnownAttributeWithEndpointID:self.endpointID + clusterID:@(TypeInfo::GetClusterId()) + attributeID:@(TypeInfo::GetAttributeId()) + params:params + queue:self.callbackQueue + reportHandler:reportHandler + subscriptionEstablished:subscriptionEstablished]; +} + ++ (void)readAttributePendingDatasetTimestampWithClusterStateCache:(MTRClusterStateCacheContainer *)clusterStateCacheContainer endpoint:(NSNumber *)endpoint queue:(dispatch_queue_t)queue completion:(void (^)(NSNumber * _Nullable value, NSError * _Nullable error))completion +{ + using TypeInfo = ThreadBorderRouterManagement::Attributes::PendingDatasetTimestamp::TypeInfo; + [clusterStateCacheContainer + _readKnownCachedAttributeWithEndpointID:static_cast([endpoint unsignedShortValue]) + clusterID:TypeInfo::GetClusterId() + attributeID:TypeInfo::GetAttributeId() + queue:queue + completion:completion]; +} + - (void)readAttributeGeneratedCommandListWithCompletion:(void (^)(NSArray * _Nullable value, NSError * _Nullable error))completion { using TypeInfo = ThreadBorderRouterManagement::Attributes::GeneratedCommandList::TypeInfo; diff --git a/src/darwin/Framework/CHIP/zap-generated/MTRClusterConstants.h b/src/darwin/Framework/CHIP/zap-generated/MTRClusterConstants.h index aa1973220085ec..2e5c8cc7dc1b44 100644 --- a/src/darwin/Framework/CHIP/zap-generated/MTRClusterConstants.h +++ b/src/darwin/Framework/CHIP/zap-generated/MTRClusterConstants.h @@ -4432,6 +4432,7 @@ typedef NS_ENUM(uint32_t, MTRAttributeIDType) { MTRAttributeIDTypeClusterThreadBorderRouterManagementAttributeThreadVersionID MTR_PROVISIONALLY_AVAILABLE = 0x00000002, MTRAttributeIDTypeClusterThreadBorderRouterManagementAttributeInterfaceEnabledID MTR_PROVISIONALLY_AVAILABLE = 0x00000003, MTRAttributeIDTypeClusterThreadBorderRouterManagementAttributeActiveDatasetTimestampID MTR_PROVISIONALLY_AVAILABLE = 0x00000004, + MTRAttributeIDTypeClusterThreadBorderRouterManagementAttributePendingDatasetTimestampID MTR_PROVISIONALLY_AVAILABLE = 0x00000005, MTRAttributeIDTypeClusterThreadBorderRouterManagementAttributeGeneratedCommandListID MTR_PROVISIONALLY_AVAILABLE = MTRAttributeIDTypeGlobalAttributeGeneratedCommandListID, MTRAttributeIDTypeClusterThreadBorderRouterManagementAttributeAcceptedCommandListID MTR_PROVISIONALLY_AVAILABLE = MTRAttributeIDTypeGlobalAttributeAcceptedCommandListID, MTRAttributeIDTypeClusterThreadBorderRouterManagementAttributeEventListID MTR_PROVISIONALLY_AVAILABLE = MTRAttributeIDTypeGlobalAttributeEventListID, diff --git a/src/darwin/Framework/CHIP/zap-generated/MTRClusterNames.mm b/src/darwin/Framework/CHIP/zap-generated/MTRClusterNames.mm index 3383cab3cec1aa..fa08192303b8fd 100644 --- a/src/darwin/Framework/CHIP/zap-generated/MTRClusterNames.mm +++ b/src/darwin/Framework/CHIP/zap-generated/MTRClusterNames.mm @@ -7598,6 +7598,10 @@ result = @"ActiveDatasetTimestamp"; break; + case MTRAttributeIDTypeClusterThreadBorderRouterManagementAttributePendingDatasetTimestampID: + result = @"PendingDatasetTimestamp"; + break; + case MTRAttributeIDTypeClusterThreadBorderRouterManagementAttributeGeneratedCommandListID: result = @"GeneratedCommandList"; break; diff --git a/src/darwin/Framework/CHIP/zap-generated/MTRClusters.h b/src/darwin/Framework/CHIP/zap-generated/MTRClusters.h index 6b441121b72413..4a2c3d6e51b156 100644 --- a/src/darwin/Framework/CHIP/zap-generated/MTRClusters.h +++ b/src/darwin/Framework/CHIP/zap-generated/MTRClusters.h @@ -6230,6 +6230,8 @@ MTR_PROVISIONALLY_AVAILABLE - (NSDictionary * _Nullable)readAttributeActiveDatasetTimestampWithParams:(MTRReadParams * _Nullable)params MTR_PROVISIONALLY_AVAILABLE; +- (NSDictionary * _Nullable)readAttributePendingDatasetTimestampWithParams:(MTRReadParams * _Nullable)params MTR_PROVISIONALLY_AVAILABLE; + - (NSDictionary * _Nullable)readAttributeGeneratedCommandListWithParams:(MTRReadParams * _Nullable)params MTR_PROVISIONALLY_AVAILABLE; - (NSDictionary * _Nullable)readAttributeAcceptedCommandListWithParams:(MTRReadParams * _Nullable)params MTR_PROVISIONALLY_AVAILABLE; diff --git a/src/darwin/Framework/CHIP/zap-generated/MTRClusters.mm b/src/darwin/Framework/CHIP/zap-generated/MTRClusters.mm index 361abcf5698306..90ff96712313c3 100644 --- a/src/darwin/Framework/CHIP/zap-generated/MTRClusters.mm +++ b/src/darwin/Framework/CHIP/zap-generated/MTRClusters.mm @@ -17340,6 +17340,11 @@ - (void)setPendingDatasetRequestWithParams:(MTRThreadBorderRouterManagementClust return [self.device readAttributeWithEndpointID:self.endpointID clusterID:@(MTRClusterIDTypeThreadBorderRouterManagementID) attributeID:@(MTRAttributeIDTypeClusterThreadBorderRouterManagementAttributeActiveDatasetTimestampID) params:params]; } +- (NSDictionary * _Nullable)readAttributePendingDatasetTimestampWithParams:(MTRReadParams * _Nullable)params +{ + return [self.device readAttributeWithEndpointID:self.endpointID clusterID:@(MTRClusterIDTypeThreadBorderRouterManagementID) attributeID:@(MTRAttributeIDTypeClusterThreadBorderRouterManagementAttributePendingDatasetTimestampID) params:params]; +} + - (NSDictionary * _Nullable)readAttributeGeneratedCommandListWithParams:(MTRReadParams * _Nullable)params { return [self.device readAttributeWithEndpointID:self.endpointID clusterID:@(MTRClusterIDTypeThreadBorderRouterManagementID) attributeID:@(MTRAttributeIDTypeClusterThreadBorderRouterManagementAttributeGeneratedCommandListID) params:params]; diff --git a/src/platform/OpenThread/GenericThreadBorderRouterDelegate.cpp b/src/platform/OpenThread/GenericThreadBorderRouterDelegate.cpp index e1efc5357520af..b08b0ab47321b3 100644 --- a/src/platform/OpenThread/GenericThreadBorderRouterDelegate.cpp +++ b/src/platform/OpenThread/GenericThreadBorderRouterDelegate.cpp @@ -167,6 +167,12 @@ void GenericOpenThreadBorderRouterDelegate::OnPlatformEventHandler(const DeviceL delegate->mpAttributeChangeCallback->ReportAttributeChanged(Attributes::ActiveDatasetTimestamp::Id); }); } + if (event->ThreadStateChange.OpenThread.Flags & OT_CHANGED_PENDING_DATASET) + { + DeviceLayer::SystemLayer().ScheduleLambda([delegate]() { + delegate->mpAttributeChangeCallback->ReportAttributeChanged(Attributes::PendingDatasetTimestamp::Id); + }); + } } } diff --git a/zzz_generated/app-common/app-common/zap-generated/cluster-objects.cpp b/zzz_generated/app-common/app-common/zap-generated/cluster-objects.cpp index bea7e010160be2..878090f033d558 100644 --- a/zzz_generated/app-common/app-common/zap-generated/cluster-objects.cpp +++ b/zzz_generated/app-common/app-common/zap-generated/cluster-objects.cpp @@ -24398,6 +24398,8 @@ CHIP_ERROR TypeInfo::DecodableType::Decode(TLV::TLVReader & reader, const Concre return DataModel::Decode(reader, interfaceEnabled); case Attributes::ActiveDatasetTimestamp::TypeInfo::GetAttributeId(): return DataModel::Decode(reader, activeDatasetTimestamp); + case Attributes::PendingDatasetTimestamp::TypeInfo::GetAttributeId(): + return DataModel::Decode(reader, pendingDatasetTimestamp); case Attributes::GeneratedCommandList::TypeInfo::GetAttributeId(): return DataModel::Decode(reader, generatedCommandList); case Attributes::AcceptedCommandList::TypeInfo::GetAttributeId(): diff --git a/zzz_generated/app-common/app-common/zap-generated/cluster-objects.h b/zzz_generated/app-common/app-common/zap-generated/cluster-objects.h index 07107feb2df637..472ce88303daaa 100644 --- a/zzz_generated/app-common/app-common/zap-generated/cluster-objects.h +++ b/zzz_generated/app-common/app-common/zap-generated/cluster-objects.h @@ -36667,6 +36667,18 @@ struct TypeInfo static constexpr bool MustUseTimedWrite() { return false; } }; } // namespace ActiveDatasetTimestamp +namespace PendingDatasetTimestamp { +struct TypeInfo +{ + using Type = chip::app::DataModel::Nullable; + using DecodableType = chip::app::DataModel::Nullable; + using DecodableArgType = const chip::app::DataModel::Nullable &; + + static constexpr ClusterId GetClusterId() { return Clusters::ThreadBorderRouterManagement::Id; } + static constexpr AttributeId GetAttributeId() { return Attributes::PendingDatasetTimestamp::Id; } + static constexpr bool MustUseTimedWrite() { return false; } +}; +} // namespace PendingDatasetTimestamp namespace GeneratedCommandList { struct TypeInfo : public Clusters::Globals::Attributes::GeneratedCommandList::TypeInfo { @@ -36717,6 +36729,7 @@ struct TypeInfo Attributes::ThreadVersion::TypeInfo::DecodableType threadVersion = static_cast(0); Attributes::InterfaceEnabled::TypeInfo::DecodableType interfaceEnabled = static_cast(0); Attributes::ActiveDatasetTimestamp::TypeInfo::DecodableType activeDatasetTimestamp; + Attributes::PendingDatasetTimestamp::TypeInfo::DecodableType pendingDatasetTimestamp; Attributes::GeneratedCommandList::TypeInfo::DecodableType generatedCommandList; Attributes::AcceptedCommandList::TypeInfo::DecodableType acceptedCommandList; Attributes::EventList::TypeInfo::DecodableType eventList; diff --git a/zzz_generated/app-common/app-common/zap-generated/ids/Attributes.h b/zzz_generated/app-common/app-common/zap-generated/ids/Attributes.h index b78c1e3410993a..85d56a98880f99 100644 --- a/zzz_generated/app-common/app-common/zap-generated/ids/Attributes.h +++ b/zzz_generated/app-common/app-common/zap-generated/ids/Attributes.h @@ -6826,6 +6826,10 @@ namespace ActiveDatasetTimestamp { static constexpr AttributeId Id = 0x00000004; } // namespace ActiveDatasetTimestamp +namespace PendingDatasetTimestamp { +static constexpr AttributeId Id = 0x00000005; +} // namespace PendingDatasetTimestamp + namespace GeneratedCommandList { static constexpr AttributeId Id = Globals::Attributes::GeneratedCommandList::Id; } // namespace GeneratedCommandList diff --git a/zzz_generated/chip-tool/zap-generated/cluster/Commands.h b/zzz_generated/chip-tool/zap-generated/cluster/Commands.h index 98d6d9caee0e97..3bd79612c9050c 100644 --- a/zzz_generated/chip-tool/zap-generated/cluster/Commands.h +++ b/zzz_generated/chip-tool/zap-generated/cluster/Commands.h @@ -11337,6 +11337,7 @@ class WiFiNetworkManagementNetworkPassphraseRequest : public ClusterCommand | * ThreadVersion | 0x0002 | | * InterfaceEnabled | 0x0003 | | * ActiveDatasetTimestamp | 0x0004 | +| * PendingDatasetTimestamp | 0x0005 | | * GeneratedCommandList | 0xFFF8 | | * AcceptedCommandList | 0xFFF9 | | * EventList | 0xFFFA | @@ -25680,19 +25681,20 @@ void registerClusterThreadBorderRouterManagement(Commands & commands, Credential // // Attributes // - make_unique(Id, credsIssuerConfig), // - make_unique(Id, "border-router-name", Attributes::BorderRouterName::Id, credsIssuerConfig), // - make_unique(Id, "border-agent-id", Attributes::BorderAgentID::Id, credsIssuerConfig), // - make_unique(Id, "thread-version", Attributes::ThreadVersion::Id, credsIssuerConfig), // - make_unique(Id, "interface-enabled", Attributes::InterfaceEnabled::Id, credsIssuerConfig), // - make_unique(Id, "active-dataset-timestamp", Attributes::ActiveDatasetTimestamp::Id, credsIssuerConfig), // - make_unique(Id, "generated-command-list", Attributes::GeneratedCommandList::Id, credsIssuerConfig), // - make_unique(Id, "accepted-command-list", Attributes::AcceptedCommandList::Id, credsIssuerConfig), // - make_unique(Id, "event-list", Attributes::EventList::Id, credsIssuerConfig), // - make_unique(Id, "attribute-list", Attributes::AttributeList::Id, credsIssuerConfig), // - make_unique(Id, "feature-map", Attributes::FeatureMap::Id, credsIssuerConfig), // - make_unique(Id, "cluster-revision", Attributes::ClusterRevision::Id, credsIssuerConfig), // - make_unique>(Id, credsIssuerConfig), // + make_unique(Id, credsIssuerConfig), // + make_unique(Id, "border-router-name", Attributes::BorderRouterName::Id, credsIssuerConfig), // + make_unique(Id, "border-agent-id", Attributes::BorderAgentID::Id, credsIssuerConfig), // + make_unique(Id, "thread-version", Attributes::ThreadVersion::Id, credsIssuerConfig), // + make_unique(Id, "interface-enabled", Attributes::InterfaceEnabled::Id, credsIssuerConfig), // + make_unique(Id, "active-dataset-timestamp", Attributes::ActiveDatasetTimestamp::Id, credsIssuerConfig), // + make_unique(Id, "pending-dataset-timestamp", Attributes::PendingDatasetTimestamp::Id, credsIssuerConfig), // + make_unique(Id, "generated-command-list", Attributes::GeneratedCommandList::Id, credsIssuerConfig), // + make_unique(Id, "accepted-command-list", Attributes::AcceptedCommandList::Id, credsIssuerConfig), // + make_unique(Id, "event-list", Attributes::EventList::Id, credsIssuerConfig), // + make_unique(Id, "attribute-list", Attributes::AttributeList::Id, credsIssuerConfig), // + make_unique(Id, "feature-map", Attributes::FeatureMap::Id, credsIssuerConfig), // + make_unique(Id, "cluster-revision", Attributes::ClusterRevision::Id, credsIssuerConfig), // + make_unique>(Id, credsIssuerConfig), // make_unique>(Id, "border-router-name", Attributes::BorderRouterName::Id, WriteCommandType::kForceWrite, credsIssuerConfig), // make_unique>(Id, "border-agent-id", Attributes::BorderAgentID::Id, @@ -25704,6 +25706,9 @@ void registerClusterThreadBorderRouterManagement(Commands & commands, Credential make_unique>>(Id, "active-dataset-timestamp", 0, UINT64_MAX, Attributes::ActiveDatasetTimestamp::Id, WriteCommandType::kForceWrite, credsIssuerConfig), // + make_unique>>(Id, "pending-dataset-timestamp", 0, UINT64_MAX, + Attributes::PendingDatasetTimestamp::Id, + WriteCommandType::kForceWrite, credsIssuerConfig), // make_unique>>( Id, "generated-command-list", Attributes::GeneratedCommandList::Id, WriteCommandType::kForceWrite, credsIssuerConfig), // @@ -25723,6 +25728,8 @@ void registerClusterThreadBorderRouterManagement(Commands & commands, Credential make_unique(Id, "thread-version", Attributes::ThreadVersion::Id, credsIssuerConfig), // make_unique(Id, "interface-enabled", Attributes::InterfaceEnabled::Id, credsIssuerConfig), // make_unique(Id, "active-dataset-timestamp", Attributes::ActiveDatasetTimestamp::Id, + credsIssuerConfig), // + make_unique(Id, "pending-dataset-timestamp", Attributes::PendingDatasetTimestamp::Id, credsIssuerConfig), // make_unique(Id, "generated-command-list", Attributes::GeneratedCommandList::Id, credsIssuerConfig), // make_unique(Id, "accepted-command-list", Attributes::AcceptedCommandList::Id, credsIssuerConfig), // diff --git a/zzz_generated/chip-tool/zap-generated/cluster/logging/DataModelLogger.cpp b/zzz_generated/chip-tool/zap-generated/cluster/logging/DataModelLogger.cpp index afebe69c9407ed..51f73a1804a85b 100644 --- a/zzz_generated/chip-tool/zap-generated/cluster/logging/DataModelLogger.cpp +++ b/zzz_generated/chip-tool/zap-generated/cluster/logging/DataModelLogger.cpp @@ -17232,6 +17232,11 @@ CHIP_ERROR DataModelLogger::LogAttribute(const chip::app::ConcreteDataAttributeP ReturnErrorOnFailure(chip::app::DataModel::Decode(*data, value)); return DataModelLogger::LogValue("ActiveDatasetTimestamp", 1, value); } + case ThreadBorderRouterManagement::Attributes::PendingDatasetTimestamp::Id: { + chip::app::DataModel::Nullable value; + ReturnErrorOnFailure(chip::app::DataModel::Decode(*data, value)); + return DataModelLogger::LogValue("PendingDatasetTimestamp", 1, value); + } case ThreadBorderRouterManagement::Attributes::GeneratedCommandList::Id: { chip::app::DataModel::DecodableList value; ReturnErrorOnFailure(chip::app::DataModel::Decode(*data, value)); diff --git a/zzz_generated/darwin-framework-tool/zap-generated/cluster/Commands.h b/zzz_generated/darwin-framework-tool/zap-generated/cluster/Commands.h index 8e18e60aaa7e63..3ca43d867fec72 100644 --- a/zzz_generated/darwin-framework-tool/zap-generated/cluster/Commands.h +++ b/zzz_generated/darwin-framework-tool/zap-generated/cluster/Commands.h @@ -147933,6 +147933,7 @@ class SubscribeAttributeWiFiNetworkManagementClusterRevision : public SubscribeA | * ThreadVersion | 0x0002 | | * InterfaceEnabled | 0x0003 | | * ActiveDatasetTimestamp | 0x0004 | +| * PendingDatasetTimestamp | 0x0005 | | * GeneratedCommandList | 0xFFF8 | | * AcceptedCommandList | 0xFFF9 | | * EventList | 0xFFFA | @@ -148591,6 +148592,91 @@ class SubscribeAttributeThreadBorderRouterManagementActiveDatasetTimestamp : pub #endif // MTR_ENABLE_PROVISIONAL #if MTR_ENABLE_PROVISIONAL +/* + * Attribute PendingDatasetTimestamp + */ +class ReadThreadBorderRouterManagementPendingDatasetTimestamp : public ReadAttribute { +public: + ReadThreadBorderRouterManagementPendingDatasetTimestamp() + : ReadAttribute("pending-dataset-timestamp") + { + } + + ~ReadThreadBorderRouterManagementPendingDatasetTimestamp() + { + } + + CHIP_ERROR SendCommand(MTRBaseDevice * device, chip::EndpointId endpointId) override + { + constexpr chip::ClusterId clusterId = chip::app::Clusters::ThreadBorderRouterManagement::Id; + constexpr chip::AttributeId attributeId = chip::app::Clusters::ThreadBorderRouterManagement::Attributes::PendingDatasetTimestamp::Id; + + ChipLogProgress(chipTool, "Sending cluster (0x%08" PRIX32 ") ReadAttribute (0x%08" PRIX32 ") on endpoint %u", endpointId, clusterId, attributeId); + + dispatch_queue_t callbackQueue = dispatch_queue_create("com.chip.command", DISPATCH_QUEUE_SERIAL); + __auto_type * cluster = [[MTRBaseClusterThreadBorderRouterManagement alloc] initWithDevice:device endpointID:@(endpointId) queue:callbackQueue]; + [cluster readAttributePendingDatasetTimestampWithCompletion:^(NSNumber * _Nullable value, NSError * _Nullable error) { + NSLog(@"ThreadBorderRouterManagement.PendingDatasetTimestamp response %@", [value description]); + if (error == nil) { + RemoteDataModelLogger::LogAttributeAsJSON(@(endpointId), @(clusterId), @(attributeId), value); + } else { + LogNSError("ThreadBorderRouterManagement PendingDatasetTimestamp read Error", error); + RemoteDataModelLogger::LogAttributeErrorAsJSON(@(endpointId), @(clusterId), @(attributeId), error); + } + SetCommandExitStatus(error); + }]; + return CHIP_NO_ERROR; + } +}; + +class SubscribeAttributeThreadBorderRouterManagementPendingDatasetTimestamp : public SubscribeAttribute { +public: + SubscribeAttributeThreadBorderRouterManagementPendingDatasetTimestamp() + : SubscribeAttribute("pending-dataset-timestamp") + { + } + + ~SubscribeAttributeThreadBorderRouterManagementPendingDatasetTimestamp() + { + } + + CHIP_ERROR SendCommand(MTRBaseDevice * device, chip::EndpointId endpointId) override + { + constexpr chip::ClusterId clusterId = chip::app::Clusters::ThreadBorderRouterManagement::Id; + constexpr chip::CommandId attributeId = chip::app::Clusters::ThreadBorderRouterManagement::Attributes::PendingDatasetTimestamp::Id; + + ChipLogProgress(chipTool, "Sending cluster (0x%08" PRIX32 ") ReportAttribute (0x%08" PRIX32 ") on endpoint %u", clusterId, attributeId, endpointId); + dispatch_queue_t callbackQueue = dispatch_queue_create("com.chip.command", DISPATCH_QUEUE_SERIAL); + __auto_type * cluster = [[MTRBaseClusterThreadBorderRouterManagement alloc] initWithDevice:device endpointID:@(endpointId) queue:callbackQueue]; + __auto_type * params = [[MTRSubscribeParams alloc] initWithMinInterval:@(mMinInterval) maxInterval:@(mMaxInterval)]; + if (mKeepSubscriptions.HasValue()) { + params.replaceExistingSubscriptions = !mKeepSubscriptions.Value(); + } + if (mFabricFiltered.HasValue()) { + params.filterByFabric = mFabricFiltered.Value(); + } + if (mAutoResubscribe.HasValue()) { + params.resubscribeAutomatically = mAutoResubscribe.Value(); + } + [cluster subscribeAttributePendingDatasetTimestampWithParams:params + subscriptionEstablished:^() { mSubscriptionEstablished = YES; } + reportHandler:^(NSNumber * _Nullable value, NSError * _Nullable error) { + NSLog(@"ThreadBorderRouterManagement.PendingDatasetTimestamp response %@", [value description]); + if (error == nil) { + RemoteDataModelLogger::LogAttributeAsJSON(@(endpointId), @(clusterId), @(attributeId), value); + } else { + RemoteDataModelLogger::LogAttributeErrorAsJSON(@(endpointId), @(clusterId), @(attributeId), error); + } + SetCommandExitStatus(error); + }]; + + return CHIP_NO_ERROR; + } +}; + +#endif // MTR_ENABLE_PROVISIONAL +#if MTR_ENABLE_PROVISIONAL + /* * Attribute GeneratedCommandList */ @@ -197664,6 +197750,10 @@ void registerClusterThreadBorderRouterManagement(Commands & commands) make_unique(), // make_unique(), // #endif // MTR_ENABLE_PROVISIONAL +#if MTR_ENABLE_PROVISIONAL + make_unique(), // + make_unique(), // +#endif // MTR_ENABLE_PROVISIONAL #if MTR_ENABLE_PROVISIONAL make_unique(), // make_unique(), //