diff --git a/scripts/tools/check_includes_config.py b/scripts/tools/check_includes_config.py index 94b3742ca6d873..a83f9db2b14404 100644 --- a/scripts/tools/check_includes_config.py +++ b/scripts/tools/check_includes_config.py @@ -198,4 +198,8 @@ 'src/access/AccessRestrictionProvider.h': {'vector', 'map'}, # nrfconnect test runner 'src/test_driver/nrfconnect/main/runner.cpp': {'vector'}, + + # Not intended for embedded clients + 'src/app/server/JointFabricDatastore.cpp': {'vector'}, + 'src/app/server/JointFabricDatastore.h': {'vector'}, } diff --git a/src/app/clusters/joint-fabric-administrator-server/joint-fabric-administrator-server.cpp b/src/app/clusters/joint-fabric-administrator-server/joint-fabric-administrator-server.cpp index d6ea771c67e7e7..9a5f7b8451c0cd 100644 --- a/src/app/clusters/joint-fabric-administrator-server/joint-fabric-administrator-server.cpp +++ b/src/app/clusters/joint-fabric-administrator-server/joint-fabric-administrator-server.cpp @@ -91,10 +91,9 @@ CHIP_ERROR JointFabricAdministratorAttrAccess::Read(const ConcreteReadAttributeP return CHIP_NO_ERROR; } -// TODO CHIP_ERROR JointFabricAdministratorAttrAccess::ReadAdministratorFabricIndex(AttributeValueEncoder & aEncoder) { - return CHIP_ERROR_NOT_IMPLEMENTED; + return aEncoder.Encode(Server::GetInstance().GetJointFabricDatastore().GetAdministratorFabricIndex()); } void MatterJointFabricAdministratorPluginServerInitCallback() diff --git a/src/app/clusters/joint-fabric-datastore-server/joint-fabric-datastore-server.cpp b/src/app/clusters/joint-fabric-datastore-server/joint-fabric-datastore-server.cpp index 20b84f90735b08..5dd948a579129b 100644 --- a/src/app/clusters/joint-fabric-datastore-server/joint-fabric-datastore-server.cpp +++ b/src/app/clusters/joint-fabric-datastore-server/joint-fabric-datastore-server.cpp @@ -35,13 +35,15 @@ using chip::Protocols::InteractionModel::Status; namespace JointFabricDatastoreCluster = chip::app::Clusters::JointFabricDatastore; -class JointFabricDatastoreAttrAccess : public AttributeAccessInterface +class JointFabricDatastoreAttrAccess : public AttributeAccessInterface, public app::JointFabricDatastore::Listener { public: JointFabricDatastoreAttrAccess() : AttributeAccessInterface(Optional::Missing(), JointFabricDatastoreCluster::Id) {} CHIP_ERROR Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) override; + void MarkNodeListChanged() override; + private: CHIP_ERROR ReadAnchorNodeId(AttributeValueEncoder & aEncoder); CHIP_ERROR ReadAnchorVendorId(AttributeValueEncoder & aEncoder); @@ -81,41 +83,94 @@ CHIP_ERROR JointFabricDatastoreAttrAccess::Read(const ConcreteReadAttributePath return CHIP_NO_ERROR; } -// TODO CHIP_ERROR JointFabricDatastoreAttrAccess::ReadAnchorNodeId(AttributeValueEncoder & aEncoder) { - return CHIP_ERROR_NOT_IMPLEMENTED; + NodeId anchorNodeId = Server::GetInstance().GetJointFabricDatastore().GetAnchorNodeId(); + ReturnErrorOnFailure(aEncoder.Encode(anchorNodeId)); + return CHIP_NO_ERROR; } -// TODO CHIP_ERROR JointFabricDatastoreAttrAccess::ReadAnchorVendorId(AttributeValueEncoder & aEncoder) { - return CHIP_ERROR_NOT_IMPLEMENTED; + VendorId anchorVendorId = Server::GetInstance().GetJointFabricDatastore().GetAnchorVendorId(); + ReturnErrorOnFailure(aEncoder.Encode(anchorVendorId)); + return CHIP_NO_ERROR; } -// TODO CHIP_ERROR JointFabricDatastoreAttrAccess::ReadGroupKeySetList(AttributeValueEncoder & aEncoder) { - return CHIP_ERROR_NOT_IMPLEMENTED; + return aEncoder.EncodeList([&](const auto & encoder) -> CHIP_ERROR { + auto entries = Server::GetInstance().GetJointFabricDatastore().GetGroupKeySetList(); + + for (auto & entry : entries) + { + ReturnErrorOnFailure(encoder.Encode(entry)); + } + return CHIP_NO_ERROR; + }); } -// TODO CHIP_ERROR JointFabricDatastoreAttrAccess::ReadAdminList(AttributeValueEncoder & aEncoder) { - return CHIP_ERROR_NOT_IMPLEMENTED; + return aEncoder.EncodeList([&](const auto & encoder) -> CHIP_ERROR { + auto entries = Server::GetInstance().GetJointFabricDatastore().GetAdminEntries(); + + for (auto & entry : entries) + { + ReturnErrorOnFailure(encoder.Encode(entry)); + } + return CHIP_NO_ERROR; + }); } -// TODO CHIP_ERROR JointFabricDatastoreAttrAccess::ReadNodeList(AttributeValueEncoder & aEncoder) { - return CHIP_ERROR_NOT_IMPLEMENTED; + return aEncoder.EncodeList([&](const auto & encoder) -> CHIP_ERROR { + auto entries = Server::GetInstance().GetJointFabricDatastore().GetNodeInformationEntries(); + + for (auto & entry : entries) + { + ReturnErrorOnFailure(encoder.Encode(entry)); + } + return CHIP_NO_ERROR; + }); +} + +void JointFabricDatastoreAttrAccess::MarkNodeListChanged() +{ + MatterReportingAttributeChangeCallback(kRootEndpointId, JointFabricDatastoreCluster::Id, + JointFabricDatastoreCluster::Attributes::NodeList::Id); } -// TODO bool emberAfJointFabricDatastoreClusterAddAdminCallback( CommandHandler * commandObj, const ConcreteCommandPath & commandPath, const JointFabricDatastoreCluster::Commands::AddAdmin::DecodableType & commandData) { + CHIP_ERROR err = CHIP_NO_ERROR; + app::JointFabricDatastore & jointFabricDatastore = Server::GetInstance().GetJointFabricDatastore(); + + auto nodeID = commandData.nodeID; + auto friendlyName = commandData.friendlyName; + auto vendorID = commandData.vendorID; + auto icac = commandData.icac; + + JointFabricDatastoreCluster::Structs::DatastoreAdministratorInformationEntryStruct::DecodableType adminInformationEntry = { + .nodeID = nodeID, .friendlyName = friendlyName, .vendorID = vendorID, .icac = icac + }; + + SuccessOrExit(err = jointFabricDatastore.AddAdmin(adminInformationEntry)); + +exit: + if (err == CHIP_NO_ERROR) + { + commandObj->AddStatus(commandPath, Protocols::InteractionModel::Status::Success); + } + else + { + ChipLogError(DataManagement, "JointFabricDatastoreCluster: failed with error: %" CHIP_ERROR_FORMAT, err.Format()); + commandObj->AddStatus(commandPath, Protocols::InteractionModel::ClusterStatusCode(err)); + } + return true; } @@ -127,43 +182,121 @@ bool emberAfJointFabricDatastoreClusterAddGroupCallback( return true; } -// TODO bool emberAfJointFabricDatastoreClusterAddKeySetCallback( CommandHandler * commandObj, const ConcreteCommandPath & commandPath, const JointFabricDatastoreCluster::Commands::AddKeySet::DecodableType & commandData) { + CHIP_ERROR err = CHIP_NO_ERROR; + JointFabricDatastoreCluster::Structs::DatastoreGroupKeySetStruct::DecodableType groupKeySet = commandData.groupKeySet; + app::JointFabricDatastore & jointFabricDatastore = Server::GetInstance().GetJointFabricDatastore(); + + VerifyOrExit(jointFabricDatastore.IsGroupKeySetEntryPresent(groupKeySet.groupKeySetID) == false, + err = CHIP_ERROR_INVALID_ARGUMENT); + SuccessOrExit(err = jointFabricDatastore.AddGroupKeySetEntry(groupKeySet)); + +exit: + if (err == CHIP_NO_ERROR) + { + commandObj->AddStatus(commandPath, Protocols::InteractionModel::Status::Success); + } + else + { + ChipLogError(DataManagement, "JointFabricDatastoreCluster: failed with error: %" CHIP_ERROR_FORMAT, err.Format()); + commandObj->AddStatus(commandPath, Protocols::InteractionModel::ClusterStatusCode(err)); + } + return true; } -// TODO bool emberAfJointFabricDatastoreClusterRemoveNodeCallback( CommandHandler * commandObj, const ConcreteCommandPath & commandPath, const JointFabricDatastoreCluster::Commands::RemoveNode::DecodableType & commandData) { + NodeId nodeId = commandData.nodeID; + + CHIP_ERROR err = Server::GetInstance().GetJointFabricDatastore().RemoveNode(nodeId); + + if (err == CHIP_NO_ERROR) + { + commandObj->AddStatus(commandPath, Protocols::InteractionModel::Status::Success); + } + else + { + ChipLogError(DataManagement, "JointFabricDatastoreCluster: failed with error: %" CHIP_ERROR_FORMAT, err.Format()); + commandObj->AddStatus(commandPath, Protocols::InteractionModel::ClusterStatusCode(err)); + } + return true; } -// TODO bool emberAfJointFabricDatastoreClusterUpdateNodeCallback( CommandHandler * commandObj, const ConcreteCommandPath & commandPath, const JointFabricDatastoreCluster::Commands::UpdateNode::DecodableType & commandData) { + NodeId nodeId = commandData.nodeID; + const CharSpan & friendlyName = commandData.friendlyName; + + CHIP_ERROR err = Server::GetInstance().GetJointFabricDatastore().UpdateNode(nodeId, friendlyName); + + if (err == CHIP_NO_ERROR) + { + commandObj->AddStatus(commandPath, Protocols::InteractionModel::Status::Success); + } + else + { + ChipLogError(DataManagement, "JointFabricDatastoreCluster: failed with error: %" CHIP_ERROR_FORMAT, err.Format()); + commandObj->AddStatus(commandPath, Protocols::InteractionModel::ClusterStatusCode(err)); + } + return true; } -// TODO bool emberAfJointFabricDatastoreClusterRefreshNodeCallback( CommandHandler * commandObj, const ConcreteCommandPath & commandPath, const JointFabricDatastoreCluster::Commands::RefreshNode::DecodableType & commandData) { + CHIP_ERROR err = CHIP_NO_ERROR; + NodeId nodeId = commandData.nodeID; + + app::JointFabricDatastore & jointFabricDatastore = Server::GetInstance().GetJointFabricDatastore(); + + SuccessOrExit(err = jointFabricDatastore.RefreshNode(nodeId)); + +exit: + if (err == CHIP_NO_ERROR) + { + commandObj->AddStatus(commandPath, Protocols::InteractionModel::Status::Success); + } + else + { + ChipLogError(DataManagement, "JointFabricDatastoreCluster: failed with error: %" CHIP_ERROR_FORMAT, err.Format()); + commandObj->AddStatus(commandPath, Protocols::InteractionModel::ClusterStatusCode(err)); + } + return true; } -// TODO bool emberAfJointFabricDatastoreClusterRemoveAdminCallback( CommandHandler * commandObj, const ConcreteCommandPath & commandPath, const JointFabricDatastoreCluster::Commands::RemoveAdmin::DecodableType & commandData) { + CHIP_ERROR err = CHIP_NO_ERROR; + auto nodeId = commandData.nodeID; + app::JointFabricDatastore & jointFabricDatastore = Server::GetInstance().GetJointFabricDatastore(); + + SuccessOrExit(err = jointFabricDatastore.RemoveAdmin(nodeId)); + +exit: + if (err == CHIP_NO_ERROR) + { + commandObj->AddStatus(commandPath, Protocols::InteractionModel::Status::Success); + } + else + { + ChipLogError(DataManagement, "JointFabricDatastoreCluster: failed with error: %" CHIP_ERROR_FORMAT, err.Format()); + commandObj->AddStatus(commandPath, Protocols::InteractionModel::ClusterStatusCode(err)); + } + return true; } @@ -175,11 +308,29 @@ bool emberAfJointFabricDatastoreClusterRemoveGroupCallback( return true; } -// TODO bool emberAfJointFabricDatastoreClusterUpdateAdminCallback( CommandHandler * commandObj, const ConcreteCommandPath & commandPath, const JointFabricDatastoreCluster::Commands::UpdateAdmin::DecodableType & commandData) { + CHIP_ERROR err = CHIP_NO_ERROR; + auto nodeId = commandData.nodeID.Value(); + auto friendlyName = commandData.friendlyName.Value(); + auto icac = commandData.icac.Value(); + app::JointFabricDatastore & jointFabricDatastore = Server::GetInstance().GetJointFabricDatastore(); + + SuccessOrExit(err = jointFabricDatastore.UpdateAdmin(nodeId, friendlyName, icac)); + +exit: + if (err == CHIP_NO_ERROR) + { + commandObj->AddStatus(commandPath, Protocols::InteractionModel::Status::Success); + } + else + { + ChipLogError(DataManagement, "JointFabricDatastoreCluster: failed with error: %" CHIP_ERROR_FORMAT, err.Format()); + commandObj->AddStatus(commandPath, Protocols::InteractionModel::ClusterStatusCode(err)); + } + return true; } @@ -199,11 +350,27 @@ bool emberAfJointFabricDatastoreClusterAddACLToNodeCallback( return true; } -// TODO bool emberAfJointFabricDatastoreClusterRemoveKeySetCallback( CommandHandler * commandObj, const ConcreteCommandPath & commandPath, const JointFabricDatastoreCluster::Commands::RemoveKeySet::DecodableType & commandData) { + CHIP_ERROR err = CHIP_NO_ERROR; + uint16_t groupKeySetId = commandData.groupKeySetID; + app::JointFabricDatastore & jointFabricDatastore = Server::GetInstance().GetJointFabricDatastore(); + + SuccessOrExit(err = jointFabricDatastore.RemoveGroupKeySetEntry(groupKeySetId)); + +exit: + if (err == CHIP_NO_ERROR) + { + commandObj->AddStatus(commandPath, Protocols::InteractionModel::Status::Success); + } + else + { + ChipLogError(DataManagement, "JointFabricDatastoreCluster: failed with error: %" CHIP_ERROR_FORMAT, err.Format()); + commandObj->AddStatus(commandPath, Protocols::InteractionModel::ClusterStatusCode(err)); + } + return true; } @@ -215,11 +382,25 @@ bool emberAfJointFabricDatastoreClusterUpdateKeySetCallback( return true; } -// TODO bool emberAfJointFabricDatastoreClusterAddPendingNodeCallback( CommandHandler * commandObj, const ConcreteCommandPath & commandPath, const JointFabricDatastoreCluster::Commands::AddPendingNode::DecodableType & commandData) { + NodeId nodeId = commandData.nodeID; + const CharSpan & friendlyName = commandData.friendlyName; + + CHIP_ERROR err = Server::GetInstance().GetJointFabricDatastore().AddPendingNode(nodeId, friendlyName); + + if (err == CHIP_NO_ERROR) + { + commandObj->AddStatus(commandPath, Protocols::InteractionModel::Status::Success); + } + else + { + ChipLogError(DataManagement, "JointFabricDatastoreCluster: failed with error: %" CHIP_ERROR_FORMAT, err.Format()); + commandObj->AddStatus(commandPath, Protocols::InteractionModel::ClusterStatusCode(err)); + } + return true; } @@ -275,4 +456,6 @@ void MatterJointFabricDatastorePluginServerInitCallback() { ChipLogProgress(Zcl, "Initiating Joint Fabric Datastore cluster."); AttributeAccessInterfaceRegistry::Instance().Register(&gJointFabricDatastoreAttrAccess); + + Server::GetInstance().GetJointFabricDatastore().AddListener(gJointFabricDatastoreAttrAccess); } diff --git a/src/app/server/BUILD.gn b/src/app/server/BUILD.gn index 75f133b6610e30..aae223bec329c4 100644 --- a/src/app/server/BUILD.gn +++ b/src/app/server/BUILD.gn @@ -57,6 +57,8 @@ static_library("server") { "Dnssd.h", "EchoHandler.cpp", "EchoHandler.h", + "JointFabricDatastore.cpp", + "JointFabricDatastore.h", "Server.cpp", "Server.h", ] diff --git a/src/app/server/JointFabricDatastore.cpp b/src/app/server/JointFabricDatastore.cpp new file mode 100644 index 00000000000000..c750223fb8b106 --- /dev/null +++ b/src/app/server/JointFabricDatastore.cpp @@ -0,0 +1,279 @@ +/* + * + * Copyright (c) 2025 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 + +namespace chip { +namespace app { + +void JointFabricDatastore::AddListener(Listener & listener) +{ + if (mListeners == nullptr) + { + mListeners = &listener; + listener.mNext = nullptr; + return; + } + + for (Listener * l = mListeners; /**/; l = l->mNext) + { + if (l == &listener) + { + return; + } + + if (l->mNext == nullptr) + { + l->mNext = &listener; + listener.mNext = nullptr; + return; + } + } +} + +void JointFabricDatastore::RemoveListener(Listener & listener) +{ + if (mListeners == &listener) + { + mListeners = listener.mNext; + listener.mNext = nullptr; + return; + } + + for (Listener * l = mListeners; l != nullptr; l = l->mNext) + { + if (l->mNext == &listener) + { + l->mNext = listener.mNext; + listener.mNext = nullptr; + return; + } + } +} + +CHIP_ERROR JointFabricDatastore::AddPendingNode(NodeId nodeId, const CharSpan & friendlyName) +{ + VerifyOrReturnError(mNodeInformationEntries.size() < kMaxNodes, CHIP_ERROR_NO_MEMORY); + + mNodeInformationEntries.push_back(GenericDatastoreNodeInformationEntry( + nodeId, Clusters::JointFabricDatastore::DatastoreStateEnum::kPending, MakeOptional(friendlyName))); + + for (Listener * listener = mListeners; listener != nullptr; listener = listener->mNext) + { + listener->MarkNodeListChanged(); + } + + return CHIP_NO_ERROR; +} + +CHIP_ERROR JointFabricDatastore::UpdateNode(NodeId nodeId, const CharSpan & friendlyName) +{ + for (auto & entry : mNodeInformationEntries) + { + if (entry.nodeID == nodeId) + { + entry.Set(MakeOptional(friendlyName)); + + for (Listener * listener = mListeners; listener != nullptr; listener = listener->mNext) + { + listener->MarkNodeListChanged(); + } + + return CHIP_NO_ERROR; + } + } + + return CHIP_ERROR_KEY_NOT_FOUND; +} + +CHIP_ERROR JointFabricDatastore::RemoveNode(NodeId nodeId) +{ + for (auto it = mNodeInformationEntries.begin(); it != mNodeInformationEntries.end(); ++it) + { + if (it->nodeID == nodeId) + { + mNodeInformationEntries.erase(it); + + for (Listener * listener = mListeners; listener != nullptr; listener = listener->mNext) + { + listener->MarkNodeListChanged(); + } + + return CHIP_NO_ERROR; + } + } + + return CHIP_ERROR_KEY_NOT_FOUND; +} + +CHIP_ERROR JointFabricDatastore::RefreshNode(NodeId nodeId) +{ + // 1. && 2. + ReturnErrorOnFailure(SetNode(nodeId, Clusters::JointFabricDatastore::DatastoreStateEnum::kPending)); + + // 3. TODO: Read the PartsList of the Descriptor cluster from the Node. + + // 4. TODO + + // 5. TODO + + // 6. + ReturnErrorOnFailure(SetNode(nodeId, Clusters::JointFabricDatastore::DatastoreStateEnum::kCommitted)); + + for (Listener * listener = mListeners; listener != nullptr; listener = listener->mNext) + { + listener->MarkNodeListChanged(); + } + + return CHIP_NO_ERROR; +} + +CHIP_ERROR JointFabricDatastore::SetNode(NodeId nodeId, Clusters::JointFabricDatastore::DatastoreStateEnum state) +{ + size_t index = 0; + ReturnErrorOnFailure(IsNodeIDInDatastore(nodeId, index)); + mNodeInformationEntries[index].commissioningStatusEntry.state = state; + return CHIP_NO_ERROR; +} + +CHIP_ERROR JointFabricDatastore::IsNodeIDInDatastore(NodeId nodeId, size_t & index) +{ + for (auto & entry : mNodeInformationEntries) + { + if (entry.nodeID == nodeId) + { + index = static_cast(&entry - &mNodeInformationEntries[0]); + return CHIP_NO_ERROR; + } + } + + return CHIP_ERROR_KEY_NOT_FOUND; +} + +CHIP_ERROR +JointFabricDatastore::AddGroupKeySetEntry(Clusters::JointFabricDatastore::Structs::DatastoreGroupKeySetStruct::Type & groupKeySet) +{ + VerifyOrReturnError(IsGroupKeySetEntryPresent(groupKeySet.groupKeySetID) == false, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(mGroupKeySetList.size() < kMaxGroupKeySet, CHIP_ERROR_NO_MEMORY); + + mGroupKeySetList.push_back(groupKeySet); + + return CHIP_NO_ERROR; +} + +bool JointFabricDatastore::IsGroupKeySetEntryPresent(uint16_t groupKeySetId) +{ + for (auto & entry : mGroupKeySetList) + { + if (entry.groupKeySetID == groupKeySetId) + { + return true; + } + } + + return false; +} + +CHIP_ERROR JointFabricDatastore::RemoveGroupKeySetEntry(uint16_t groupKeySetId) +{ + for (auto it = mGroupKeySetList.begin(); it != mGroupKeySetList.end(); ++it) + { + if (it->groupKeySetID == groupKeySetId) + { + mGroupKeySetList.erase(it); + return CHIP_NO_ERROR; + } + } + + return CHIP_ERROR_KEY_NOT_FOUND; +} + +CHIP_ERROR +JointFabricDatastore::UpdateGroupKeySetEntry( + Clusters::JointFabricDatastore::Structs::DatastoreGroupKeySetStruct::Type & groupKeySet) +{ + for (auto & entry : mGroupKeySetList) + { + if (entry.groupKeySetID == groupKeySet.groupKeySetID) + { + entry = groupKeySet; + + // TODO: RefreshNodes + + return CHIP_NO_ERROR; + } + } + + return CHIP_ERROR_KEY_NOT_FOUND; +} + +CHIP_ERROR +JointFabricDatastore::AddAdmin( + Clusters::JointFabricDatastore::Structs::DatastoreAdministratorInformationEntryStruct::Type & adminId) +{ + VerifyOrReturnError(IsAdminEntryPresent(adminId.nodeID) == false, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(mAdminEntries.size() < kMaxAdminNodes, CHIP_ERROR_NO_MEMORY); + + mAdminEntries.push_back(adminId); + + return CHIP_NO_ERROR; +} + +bool JointFabricDatastore::IsAdminEntryPresent(NodeId nodeId) +{ + for (auto & entry : mAdminEntries) + { + if (entry.nodeID == nodeId) + { + return true; + } + } + + return false; +} + +CHIP_ERROR JointFabricDatastore::UpdateAdmin(NodeId nodeId, CharSpan friendlyName, ByteSpan icac) +{ + for (auto & entry : mAdminEntries) + { + if (entry.nodeID == nodeId) + { + entry.friendlyName = friendlyName; + entry.icac = icac; + return CHIP_NO_ERROR; + } + } + + return CHIP_ERROR_KEY_NOT_FOUND; +} + +CHIP_ERROR JointFabricDatastore::RemoveAdmin(NodeId nodeId) +{ + for (auto it = mAdminEntries.begin(); it != mAdminEntries.end(); ++it) + { + if (it->nodeID == nodeId) + { + mAdminEntries.erase(it); + return CHIP_NO_ERROR; + } + } + + return CHIP_ERROR_KEY_NOT_FOUND; +} + +} // namespace app +} // namespace chip diff --git a/src/app/server/JointFabricDatastore.h b/src/app/server/JointFabricDatastore.h new file mode 100644 index 00000000000000..6cef2e540ed10d --- /dev/null +++ b/src/app/server/JointFabricDatastore.h @@ -0,0 +1,198 @@ +/* + * + * Copyright (c) 2025 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. + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace chip { +namespace app { + +/** + * A struct which extends the DatastoreNodeInformationEntry type with FriendlyName buffer reservation. + */ +struct GenericDatastoreNodeInformationEntry + : public Clusters::JointFabricDatastore::Structs::DatastoreNodeInformationEntryStruct::Type +{ + GenericDatastoreNodeInformationEntry(NodeId nodeId = 0, + Clusters::JointFabricDatastore::DatastoreStateEnum state = + Clusters::JointFabricDatastore::DatastoreStateEnum::kUnknownEnumValue, + Optional label = NullOptional) + { + Set(nodeId, state, label); + } + + GenericDatastoreNodeInformationEntry(const GenericDatastoreNodeInformationEntry & op) { *this = op; } + + GenericDatastoreNodeInformationEntry & operator=(const GenericDatastoreNodeInformationEntry & op) + { + Set(op.nodeID, op.commissioningStatusEntry.state, MakeOptional(op.friendlyName)); + return *this; + } + + void Set(NodeId nodeId, Clusters::JointFabricDatastore::DatastoreStateEnum state, Optional label = NullOptional) + { + this->nodeID = nodeId; + this->commissioningStatusEntry.state = state; + Set(label); + } + + void Set(Optional label = NullOptional) + { + if (label.HasValue()) + { + memset(mFriendlyNameBuffer, 0, sizeof(mFriendlyNameBuffer)); + if (label.Value().size() > sizeof(mFriendlyNameBuffer)) + { + memcpy(mFriendlyNameBuffer, label.Value().data(), sizeof(mFriendlyNameBuffer)); + this->friendlyName = CharSpan(mFriendlyNameBuffer, sizeof(mFriendlyNameBuffer)); + } + else + { + memcpy(mFriendlyNameBuffer, label.Value().data(), label.Value().size()); + this->friendlyName = CharSpan(mFriendlyNameBuffer, label.Value().size()); + } + } + else + { + this->friendlyName = CharSpan(); + } + } + +private: + static constexpr size_t kFriendlyNameMaxSize = 32u; + + char mFriendlyNameBuffer[kFriendlyNameMaxSize]; +}; + +class JointFabricDatastore +{ +public: + static JointFabricDatastore & GetInstance() + { + static JointFabricDatastore sInstance; + return sInstance; + } + + CHIP_ERROR SetAdministratorFabricIndex(FabricIndex fabricIndex) + { + mAdministratorFabricIndex = fabricIndex; + return CHIP_NO_ERROR; + } + FabricIndex GetAdministratorFabricIndex() { return mAdministratorFabricIndex; } + + CHIP_ERROR SetAnchorNodeId(NodeId anchorNodeId) + { + mAnchorNodeId = anchorNodeId; + return CHIP_NO_ERROR; + } + NodeId GetAnchorNodeId() { return mAnchorNodeId; } + + CHIP_ERROR SetAnchorVendorId(VendorId anchorVendorId) + { + mAnchorVendorId = anchorVendorId; + return CHIP_NO_ERROR; + } + VendorId GetAnchorVendorId() { return mAnchorVendorId; } + + CHIP_ERROR AddPendingNode(NodeId nodeId, const CharSpan & friendlyName); + CHIP_ERROR UpdateNode(NodeId nodeId, const CharSpan & friendlyName); + CHIP_ERROR RemoveNode(NodeId nodeId); + CHIP_ERROR RefreshNode(NodeId nodeId); + + CHIP_ERROR SetNode(NodeId nodeId, Clusters::JointFabricDatastore::DatastoreStateEnum state); + + CHIP_ERROR AddGroupKeySetEntry(Clusters::JointFabricDatastore::Structs::DatastoreGroupKeySetStruct::Type & groupKeySet); + bool IsGroupKeySetEntryPresent(uint16_t groupKeySetId); + CHIP_ERROR RemoveGroupKeySetEntry(uint16_t groupKeySetId); + CHIP_ERROR UpdateGroupKeySetEntry(Clusters::JointFabricDatastore::Structs::DatastoreGroupKeySetStruct::Type & groupKeySet); + + CHIP_ERROR AddAdmin(Clusters::JointFabricDatastore::Structs::DatastoreAdministratorInformationEntryStruct::Type & adminId); + bool IsAdminEntryPresent(NodeId nodeId); + CHIP_ERROR UpdateAdmin(NodeId nodeId, CharSpan friendlyName, ByteSpan icac); + CHIP_ERROR RemoveAdmin(NodeId nodeId); + + const std::vector & GetGroupKeySetList() + { + return mGroupKeySetList; + } + const std::vector & GetNodeInformationEntries() { return mNodeInformationEntries; } + const std::vector & + GetAdminEntries() + { + return mAdminEntries; + } + + /** + * Used to notify of changes in the node list and more TODO. + */ + class Listener + { + public: + virtual ~Listener() = default; + + /** + * Notifies of a change in the node list. + */ + virtual void MarkNodeListChanged() = 0; + + private: + Listener * mNext = nullptr; + + friend class JointFabricDatastore; + }; + + /** + * Add a listener to be notified of changes in the Joint Fabric Datastore. + * + * @param [in] listener The listener to add. + */ + void AddListener(Listener & listener); + + /** + * Remove a listener from being notified of changes in the Joint Fabric Datastore. + * + * @param [in] listener The listener to remove. + */ + void RemoveListener(Listener & listener); + +private: + static constexpr size_t kMaxNodes = 256; + static constexpr size_t kMaxAdminNodes = 32; + static constexpr size_t kMaxGroups = kMaxNodes / 16; + static constexpr size_t kMaxGroupKeySet = kMaxGroups * 16; + + FabricIndex mAdministratorFabricIndex = kUndefinedFabricIndex; + + NodeId mAnchorNodeId = kUndefinedNodeId; + VendorId mAnchorVendorId = VendorId::NotSpecified; + + std::vector mNodeInformationEntries; + std::vector mGroupKeySetList; + std::vector mAdminEntries; + + Listener * mListeners = nullptr; + + CHIP_ERROR IsNodeIDInDatastore(NodeId nodeId, size_t & index); +}; + +} // namespace app +} // namespace chip diff --git a/src/app/server/Server.h b/src/app/server/Server.h index aeb458d0371ae9..39a90b66a74280 100644 --- a/src/app/server/Server.h +++ b/src/app/server/Server.h @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -416,6 +417,8 @@ class Server app::reporting::ReportScheduler * GetReportScheduler() { return mReportScheduler; } + app::JointFabricDatastore & GetJointFabricDatastore() { return mJointFabricDatastore; } + #if CHIP_CONFIG_ENABLE_ICD_SERVER app::ICDManager & GetICDManager() { return mICDManager; } @@ -695,6 +698,8 @@ class Server Access::AccessControl mAccessControl; app::AclStorage * mAclStorage; + app::JointFabricDatastore mJointFabricDatastore; + TestEventTriggerDelegate * mTestEventTriggerDelegate; Crypto::OperationalKeystore * mOperationalKeystore; Credentials::OperationalCertificateStore * mOpCertStore;