Matter SDK Coverage Report
Current view: top level - app/server - JointFabricDatastore.cpp (source / functions) Coverage Total Hit
Test: SHA:6c8f029dd2432dc900f1c9245c324e69bd79e40a Lines: 51.7 % 820 424
Test Date: 2026-08-08 07:40:09 Functions: 51.8 % 112 58

            Line data    Source code
       1              : /*
       2              :  *
       3              :  *    Copyright (c) 2025 Project CHIP Authors
       4              :  *
       5              :  *    Licensed under the Apache License, Version 2.0 (the "License");
       6              :  *    you may not use this file except in compliance with the License.
       7              :  *    You may obtain a copy of the License at
       8              :  *
       9              :  *        http://www.apache.org/licenses/LICENSE-2.0
      10              :  *
      11              :  *    Unless required by applicable law or agreed to in writing, software
      12              :  *    distributed under the License is distributed on an "AS IS" BASIS,
      13              :  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      14              :  *    See the License for the specific language governing permissions and
      15              :  *    limitations under the License.
      16              :  */
      17              : 
      18              : #include <app/server/JointFabricDatastore.h>
      19              : 
      20              : #include <algorithm>
      21              : #include <cstring>
      22              : #include <unordered_set>
      23              : 
      24              : namespace chip {
      25              : namespace app {
      26              : 
      27              : namespace {
      28              : /**
      29              :  * Validate that an input is the correct length to be an Epoch Key. Null keys are considered valid.
      30              :  */
      31            9 : bool EpochKeyFitsStorage(const DataModel::Nullable<ByteSpan> & key)
      32              : {
      33              :     using EpochKeyStorage = Crypto::SensitiveDataBuffer<Crypto::CHIP_CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES>;
      34            9 :     return key.IsNull() || key.Value().size() <= EpochKeyStorage::Capacity();
      35              : }
      36              : } // namespace
      37              : 
      38            3 : CHIP_ERROR JointFabricDatastore::CopyGroupKeySetWithOwnedSpans(
      39              :     const Clusters::JointFabricDatastore::Structs::DatastoreGroupKeySetStruct::Type & source,
      40              :     Clusters::JointFabricDatastore::Structs::DatastoreGroupKeySetStruct::Type & destination)
      41              : {
      42              :     // Validate before mutating any storage so a non-conformant input leaves existing entries untouched.
      43            3 :     VerifyOrReturnError(EpochKeyFitsStorage(source.epochKey0) && EpochKeyFitsStorage(source.epochKey1) &&
      44              :                             EpochKeyFitsStorage(source.epochKey2),
      45              :                         CHIP_IM_GLOBAL_STATUS(ConstraintError));
      46              : 
      47            3 :     auto & storage = mGroupKeySetStorage[source.groupKeySetID];
      48              : 
      49            3 :     destination.groupKeySetID          = source.groupKeySetID;
      50            3 :     destination.groupKeySecurityPolicy = source.groupKeySecurityPolicy;
      51              : 
      52            3 :     CopyByteSpanWithOwnedStorage(source.epochKey0, storage.epochKey0, destination.epochKey0);
      53            3 :     CopyByteSpanWithOwnedStorage(source.epochKey1, storage.epochKey1, destination.epochKey1);
      54            3 :     CopyByteSpanWithOwnedStorage(source.epochKey2, storage.epochKey2, destination.epochKey2);
      55              : 
      56            3 :     CopyNullableValue(source.epochStartTime0, destination.epochStartTime0);
      57            3 :     CopyNullableValue(source.epochStartTime1, destination.epochStartTime1);
      58            3 :     CopyNullableValue(source.epochStartTime2, destination.epochStartTime2);
      59              : 
      60            3 :     return CHIP_NO_ERROR;
      61              : }
      62              : 
      63            0 : void JointFabricDatastore::RemoveGroupKeySetStorage(uint16_t groupKeySetId)
      64              : {
      65              :     // The epoch-key buffers self-zeroize in their destructors when the entry is erased.
      66            0 :     mGroupKeySetStorage.erase(groupKeySetId);
      67            0 : }
      68              : 
      69            5 : void JointFabricDatastore::SetGroupInformationFriendlyNameWithOwnedStorage(
      70              :     GroupId groupId, const CharSpan & friendlyName,
      71              :     Clusters::JointFabricDatastore::Structs::DatastoreGroupInformationEntryStruct::Type & destination)
      72              : {
      73            5 :     auto & storage = mGroupInformationStorage[groupId];
      74            5 :     storage.friendlyName.assign(friendlyName.data(), friendlyName.data() + friendlyName.size());
      75            5 :     destination.friendlyName = CharSpan(storage.friendlyName.data(), storage.friendlyName.size());
      76            5 : }
      77              : 
      78            0 : void JointFabricDatastore::RemoveGroupInformationStorage(GroupId groupId)
      79              : {
      80            0 :     mGroupInformationStorage.erase(groupId);
      81            0 : }
      82              : 
      83            2 : CHIP_ERROR JointFabricDatastore::SetAdminEntryWithOwnedStorage(
      84              :     NodeId nodeId, const CharSpan & friendlyName, const ByteSpan & icac,
      85              :     Clusters::JointFabricDatastore::Structs::DatastoreAdministratorInformationEntryStruct::Type & destination)
      86              : {
      87            2 :     auto & storage = mAdminEntryStorage[nodeId];
      88              : 
      89            2 :     storage.friendlyName.assign(friendlyName.data(), friendlyName.data() + friendlyName.size());
      90            2 :     destination.friendlyName = CharSpan(storage.friendlyName.data(), storage.friendlyName.size());
      91              : 
      92            2 :     ReturnErrorOnFailure(storage.icac.SetLength(icac.size()));
      93            2 :     memcpy(storage.icac.Bytes(), icac.data(), icac.size());
      94            2 :     destination.icac = storage.icac.Span();
      95              : 
      96            2 :     return CHIP_NO_ERROR;
      97              : }
      98              : 
      99            0 : void JointFabricDatastore::RemoveAdminEntryStorage(NodeId nodeId)
     100              : {
     101              :     // The ICAC buffer self-zeroizes in its destructor when the entry is erased.
     102            0 :     mAdminEntryStorage.erase(nodeId);
     103            0 : }
     104              : 
     105            8 : void JointFabricDatastore::SetEndpointFriendlyNameWithOwnedStorage(
     106              :     NodeId nodeId, EndpointId endpointId, const CharSpan & friendlyName,
     107              :     Clusters::JointFabricDatastore::Structs::DatastoreEndpointEntryStruct::Type & destination)
     108              : {
     109            8 :     auto & storage = mEndpointFriendlyNameStorage[{ nodeId, endpointId }];
     110            8 :     storage.assign(friendlyName.data(), friendlyName.data() + friendlyName.size());
     111            8 :     destination.friendlyName = CharSpan(storage.data(), storage.size());
     112            8 : }
     113              : 
     114            0 : void JointFabricDatastore::RemoveEndpointFriendlyNameStorage(NodeId nodeId, EndpointId endpointId)
     115              : {
     116            0 :     mEndpointFriendlyNameStorage.erase({ nodeId, endpointId });
     117            0 : }
     118              : 
     119            9 : void JointFabricDatastore::CopyByteSpanWithOwnedStorage(const DataModel::Nullable<ByteSpan> & source, EpochKeyStorage & storage,
     120              :                                                         DataModel::Nullable<ByteSpan> & destination)
     121              : {
     122              :     // Over-length epoch keys are rejected by CopyGroupKeySetWithOwnedSpans before reaching here, so the
     123              :     // SetLength below is expected to succeed; the failure branch remains as a defensive fallback only.
     124           14 :     if (!source.IsNull() && storage.SetLength(source.Value().size()) == CHIP_NO_ERROR)
     125              :     {
     126            5 :         memcpy(storage.Bytes(), source.Value().data(), source.Value().size());
     127            5 :         destination = storage.Span();
     128              :     }
     129              :     else
     130              :     {
     131            4 :         storage.Clear();
     132            4 :         destination.SetNull();
     133              :     }
     134            9 : }
     135              : 
     136            3 : void JointFabricDatastore::AddListener(Listener & listener)
     137              : {
     138            3 :     if (mListeners == nullptr)
     139              :     {
     140            3 :         mListeners     = &listener;
     141            3 :         listener.mNext = nullptr;
     142            3 :         return;
     143              :     }
     144              : 
     145            0 :     for (Listener * l = mListeners; /**/; l = l->mNext)
     146              :     {
     147            0 :         if (l == &listener)
     148              :         {
     149            0 :             return;
     150              :         }
     151              : 
     152            0 :         if (l->mNext == nullptr)
     153              :         {
     154            0 :             l->mNext       = &listener;
     155            0 :             listener.mNext = nullptr;
     156            0 :             return;
     157              :         }
     158              :     }
     159              : }
     160              : 
     161            1 : void JointFabricDatastore::RemoveListener(Listener & listener)
     162              : {
     163            1 :     if (mListeners == &listener)
     164              :     {
     165            1 :         mListeners     = listener.mNext;
     166            1 :         listener.mNext = nullptr;
     167            1 :         return;
     168              :     }
     169              : 
     170            0 :     for (Listener * l = mListeners; l != nullptr; l = l->mNext)
     171              :     {
     172            0 :         if (l->mNext == &listener)
     173              :         {
     174            0 :             l->mNext       = listener.mNext;
     175            0 :             listener.mNext = nullptr;
     176            0 :             return;
     177              :         }
     178              :     }
     179              : }
     180              : 
     181           24 : CHIP_ERROR JointFabricDatastore::AddPendingNode(NodeId nodeId, const CharSpan & friendlyName)
     182              : {
     183           24 :     VerifyOrReturnError(mNodeInformationEntries.size() < kMaxNodes, CHIP_ERROR_NO_MEMORY);
     184              :     // check that nodeId does not already exist
     185          102 :     VerifyOrReturnError(
     186              :         std::none_of(mNodeInformationEntries.begin(), mNodeInformationEntries.end(),
     187              :                      [nodeId](const GenericDatastoreNodeInformationEntry & entry) { return entry.nodeID == nodeId; }),
     188              :         CHIP_IM_GLOBAL_STATUS(ConstraintError));
     189              : 
     190           24 :     mNodeInformationEntries.push_back(GenericDatastoreNodeInformationEntry(
     191           24 :         nodeId, Clusters::JointFabricDatastore::DatastoreStateEnum::kPending, MakeOptional(friendlyName)));
     192              : 
     193           26 :     for (Listener * listener = mListeners; listener != nullptr; listener = listener->mNext)
     194              :     {
     195            2 :         listener->MarkNodeListChanged();
     196              :     }
     197              : 
     198           24 :     return CHIP_NO_ERROR;
     199              : }
     200              : 
     201            1 : CHIP_ERROR JointFabricDatastore::UpdateNode(NodeId nodeId, const CharSpan & friendlyName)
     202              : {
     203            1 :     for (auto & entry : mNodeInformationEntries)
     204              :     {
     205            1 :         if (entry.nodeID == nodeId)
     206              :         {
     207            1 :             entry.Set(MakeOptional(friendlyName));
     208              : 
     209            2 :             for (Listener * listener = mListeners; listener != nullptr; listener = listener->mNext)
     210              :             {
     211            1 :                 listener->MarkNodeListChanged();
     212              :             }
     213              : 
     214            1 :             return CHIP_NO_ERROR;
     215              :         }
     216              :     }
     217              : 
     218            0 :     return CHIP_IM_GLOBAL_STATUS(ConstraintError);
     219              : }
     220              : 
     221            0 : CHIP_ERROR JointFabricDatastore::RemoveNode(NodeId nodeId)
     222              : {
     223            0 :     for (auto it = mNodeInformationEntries.begin(); it != mNodeInformationEntries.end(); ++it)
     224              :     {
     225            0 :         if (it->nodeID == nodeId)
     226              :         {
     227            0 :             mNodeInformationEntries.erase(it);
     228              : 
     229            0 :             for (Listener * listener = mListeners; listener != nullptr; listener = listener->mNext)
     230              :             {
     231            0 :                 listener->MarkNodeListChanged();
     232              :             }
     233              : 
     234            0 :             return CHIP_NO_ERROR;
     235              :         }
     236              :     }
     237              : 
     238            0 :     return CHIP_IM_GLOBAL_STATUS(ConstraintError);
     239              : }
     240              : 
     241            2 : CHIP_ERROR JointFabricDatastore::RefreshNode(NodeId nodeId)
     242              : {
     243            2 :     VerifyOrReturnError(mDelegate != nullptr, CHIP_ERROR_INCORRECT_STATE);
     244            2 :     VerifyOrReturnError(mRefreshingNodeId == kUndefinedNodeId, CHIP_ERROR_INCORRECT_STATE);
     245            2 :     VerifyOrReturnError(mRefreshState == kIdle, CHIP_ERROR_INCORRECT_STATE);
     246              : 
     247            2 :     mRefreshingNodeId = nodeId;
     248              : 
     249            2 :     ReturnErrorOnFailure(ContinueRefresh());
     250              : 
     251            1 :     return CHIP_NO_ERROR;
     252              : }
     253              : 
     254           12 : CHIP_ERROR JointFabricDatastore::ContinueRefresh()
     255              : {
     256              : 
     257           12 :     switch (mRefreshState)
     258              :     {
     259            2 :     case kIdle: {
     260              :         // 1. Confirm that a Node Information Entry exists for the given NodeID, and if not, return NOT_FOUND.
     261              :         // 2. Set the Node Information Entry's state to Pending.
     262            2 :         ReturnErrorOnFailure(SetNode(mRefreshingNodeId, Clusters::JointFabricDatastore::DatastoreStateEnum::kPending));
     263              : 
     264              :         // Request endpoints from the device's Descriptor cluster and transition to the
     265              :         // kRefreshingEndpoints state. The delegate call is asynchronous and will invoke
     266              :         // the provided callback when complete; the callback stores the received list
     267              :         // and calls ContinueRefresh() to advance the state machine.
     268              : 
     269            4 :         ReturnErrorOnFailure(mDelegate->FetchEndpointList(
     270              :             mRefreshingNodeId,
     271              :             [this](CHIP_ERROR err,
     272              :                    const std::vector<Clusters::JointFabricDatastore::Structs::DatastoreEndpointEntryStruct::Type> & endpoints) {
     273              :                 if (err == CHIP_NO_ERROR)
     274              :                 {
     275              :                     // Store the fetched endpoints for processing in the next state.
     276              :                     mRefreshingEndpointsList = endpoints;
     277              : 
     278              :                     // Advance the state machine to process the endpoints.
     279              :                     mRefreshState = kRefreshingEndpoints;
     280              :                 }
     281              :                 else
     282              :                 {
     283              :                     // Leave node as pending but tear down the refresh state.
     284              :                     mRefreshingNodeId = kUndefinedNodeId;
     285              :                     mRefreshState     = kIdle;
     286              :                     return;
     287              :                 }
     288              : 
     289              :                 // Continue the state machine (will enter kRefreshingEndpoints branch
     290              :                 // when successful and process mRefreshingEndpointsList).
     291              :                 if (ContinueRefresh() != CHIP_NO_ERROR)
     292              :                 {
     293              :                     // Ignore errors in continuation from within the callback.
     294              :                 }
     295              :             }));
     296              :     }
     297            1 :     break;
     298            1 :     case kRefreshingEndpoints: {
     299              :         // 3. cycle through mRefreshingEndpointsList and add them to the endpoint entries
     300            1 :         for (const auto & endpoint : mRefreshingEndpointsList)
     301              :         {
     302            0 :             auto it = std::find_if(
     303              :                 mEndpointEntries.begin(), mEndpointEntries.end(),
     304            0 :                 [this, &endpoint](const Clusters::JointFabricDatastore::Structs::DatastoreEndpointEntryStruct::Type & entry) {
     305            0 :                     return entry.nodeID == mRefreshingNodeId && entry.endpointID == endpoint.endpointID;
     306              :                 });
     307            0 :             if (it == mEndpointEntries.end())
     308              :             {
     309            0 :                 Clusters::JointFabricDatastore::Structs::DatastoreEndpointEntryStruct::Type newEntry;
     310            0 :                 newEntry.endpointID = endpoint.endpointID;
     311            0 :                 newEntry.nodeID     = mRefreshingNodeId;
     312            0 :                 mEndpointEntries.push_back(newEntry);
     313              :             }
     314              :         }
     315              : 
     316              :         // TODO: sync friendly name between datastore entry and basic cluster
     317              : 
     318              :         // Remove EndpointEntries that are not in the mRefreshingEndpointsList
     319            2 :         mEndpointEntries.erase(
     320            1 :             std::remove_if(mEndpointEntries.begin(), mEndpointEntries.end(),
     321            0 :                            [&](const auto & entry) {
     322            0 :                                if (entry.nodeID != mRefreshingNodeId)
     323              :                                {
     324            0 :                                    return false;
     325              :                                }
     326              :                                const bool shouldRemove =
     327            0 :                                    std::none_of(mRefreshingEndpointsList.begin(), mRefreshingEndpointsList.end(),
     328            0 :                                                 [&](const auto & endpoint) { return entry.endpointID == endpoint.endpointID; });
     329            0 :                                if (shouldRemove)
     330              :                                {
     331            0 :                                    RemoveEndpointFriendlyNameStorage(entry.nodeID, entry.endpointID);
     332              :                                }
     333            0 :                                return shouldRemove;
     334              :                            }),
     335            1 :             mEndpointEntries.end());
     336              : 
     337            1 :         if (std::none_of(mRefreshingEndpointsList.begin(), mRefreshingEndpointsList.end(),
     338            0 :                          [](const auto & endpoint) { return endpoint.endpointID == kRootEndpointId; }))
     339              :         {
     340            1 :             Clusters::JointFabricDatastore::Structs::DatastoreEndpointEntryStruct::Type rootEndpoint;
     341            1 :             rootEndpoint.nodeID     = mRefreshingNodeId;
     342            1 :             rootEndpoint.endpointID = kRootEndpointId;
     343            1 :             mRefreshingEndpointsList.push_back(rootEndpoint);
     344              :         }
     345              : 
     346              :         // Start fetching groups from the first endpoint
     347            1 :         mRefreshingEndpointIndex = 0;
     348            1 :         mRefreshState            = kRefreshingGroups;
     349              : 
     350              :         // Fall through to kRefreshingGroups to start fetching
     351            1 :         return ContinueRefresh();
     352              :     }
     353              :     break;
     354              : 
     355            2 :     case kRefreshingGroups: {
     356              :         // Check if we still have endpoints to process for group fetching
     357            2 :         if (mRefreshingEndpointIndex < mRefreshingEndpointsList.size())
     358              :         {
     359              :             // Fetch group list for the current endpoint
     360            1 :             EndpointId currentEndpointId = mRefreshingEndpointsList[mRefreshingEndpointIndex].endpointID;
     361              : 
     362            4 :             ReturnErrorOnFailure(mDelegate->FetchEndpointGroupList(
     363              :                 mRefreshingNodeId, currentEndpointId,
     364              :                 [this, currentEndpointId](
     365              :                     CHIP_ERROR err,
     366              :                     const std::vector<Clusters::JointFabricDatastore::Structs::DatastoreGroupInformationEntryStruct::Type> &
     367              :                         endpointGroups) {
     368              :                     if (err == CHIP_NO_ERROR)
     369              :                     {
     370              :                         // Convert endpointGroups to mEndpointGroupIDEntries for this specific endpoint
     371              :                         for (const auto & endpointGroup : endpointGroups)
     372              :                         {
     373              :                             auto it = std::find_if(
     374              :                                 mEndpointGroupIDEntries.begin(), mEndpointGroupIDEntries.end(),
     375              :                                 [this, currentEndpointId, &endpointGroup](
     376              :                                     const Clusters::JointFabricDatastore::Structs::DatastoreEndpointGroupIDEntryStruct::Type &
     377              :                                         entry) {
     378              :                                     return entry.nodeID == mRefreshingNodeId && entry.endpointID == currentEndpointId &&
     379              :                                         entry.groupID == endpointGroup.groupID;
     380              :                                 });
     381              : 
     382              :                             if (it == mEndpointGroupIDEntries.end())
     383              :                             {
     384              :                                 Clusters::JointFabricDatastore::Structs::DatastoreEndpointGroupIDEntryStruct::Type newEntry;
     385              :                                 newEntry.nodeID            = mRefreshingNodeId;
     386              :                                 newEntry.endpointID        = currentEndpointId;
     387              :                                 newEntry.groupID           = static_cast<GroupId>(endpointGroup.groupID);
     388              :                                 newEntry.statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kCommitted;
     389              :                                 mEndpointGroupIDEntries.push_back(newEntry);
     390              :                             }
     391              :                         }
     392              : 
     393              :                         // Remove entries not in endpointGroups for this specific endpoint
     394              :                         mEndpointGroupIDEntries.erase(
     395              :                             std::remove_if(mEndpointGroupIDEntries.begin(), mEndpointGroupIDEntries.end(),
     396              :                                            [&, currentEndpointId](const auto & entry) {
     397              :                                                if (entry.nodeID != mRefreshingNodeId || entry.endpointID != currentEndpointId)
     398              :                                                {
     399              :                                                    return false;
     400              :                                                }
     401              :                                                if (entry.statusEntry.state !=
     402              :                                                        Clusters::JointFabricDatastore::DatastoreStateEnum::kCommitted &&
     403              :                                                    entry.statusEntry.state !=
     404              :                                                        Clusters::JointFabricDatastore::DatastoreStateEnum::kDeletePending)
     405              :                                                {
     406              :                                                    return false;
     407              :                                                }
     408              :                                                return std::none_of(endpointGroups.begin(), endpointGroups.end(),
     409              :                                                                    [&](const auto & eg) { return entry.groupID == eg.groupID; });
     410              :                                            }),
     411              :                             mEndpointGroupIDEntries.end());
     412              :                     }
     413              : 
     414              :                     // Move to the next endpoint
     415              :                     mRefreshingEndpointIndex++;
     416              : 
     417              :                     // Continue to process next endpoint or move to syncing phase
     418              :                     if (ContinueRefresh() != CHIP_NO_ERROR)
     419              :                     {
     420              :                         // Ignore errors in continuation from within the callback.
     421              :                     }
     422              :                 }));
     423              : 
     424              :             // Return here - the callback will call ContinueRefresh() again
     425            1 :             return CHIP_NO_ERROR;
     426              :         }
     427              : 
     428              :         // All endpoints processed; now sync any pending/delete-pending entries
     429            1 :         for (auto it = mEndpointGroupIDEntries.begin(); it != mEndpointGroupIDEntries.end();)
     430              :         {
     431            0 :             if (it->nodeID == mRefreshingNodeId)
     432              :             {
     433            0 :                 if (it->statusEntry.state == Clusters::JointFabricDatastore::DatastoreStateEnum::kPending)
     434              :                 {
     435            0 :                     auto entryToSync      = *it;
     436            0 :                     const NodeId nodeId   = entryToSync.nodeID;
     437            0 :                     const EndpointId epId = entryToSync.endpointID;
     438            0 :                     const GroupId groupId = entryToSync.groupID;
     439            0 :                     ReturnErrorOnFailure(mDelegate->SyncNode(mRefreshingNodeId, entryToSync, [this, nodeId, epId, groupId]() {
     440              :                         detail::MarkEntryCommittedIfFound(mEndpointGroupIDEntries, [&](const auto & e) {
     441              :                             return e.nodeID == nodeId && e.endpointID == epId && e.groupID == groupId;
     442              :                         });
     443              :                     }));
     444              :                 }
     445            0 :                 else if (it->statusEntry.state == Clusters::JointFabricDatastore::DatastoreStateEnum::kDeletePending)
     446              :                 {
     447            0 :                     Clusters::JointFabricDatastore::Structs::DatastoreEndpointGroupIDEntryStruct::Type endpointGroupIdNullEntry{
     448              :                         0
     449              :                     };
     450              : 
     451            0 :                     auto entryToErase = *it;
     452            0 :                     ReturnErrorOnFailure(mDelegate->SyncNode(mRefreshingNodeId, endpointGroupIdNullEntry, [this, entryToErase]() {
     453              :                         mEndpointGroupIDEntries.erase(std::remove_if(mEndpointGroupIDEntries.begin(), mEndpointGroupIDEntries.end(),
     454              :                                                                      [&](const auto & entry) {
     455              :                                                                          return entry.nodeID == entryToErase.nodeID &&
     456              :                                                                              entry.endpointID == entryToErase.endpointID &&
     457              :                                                                              entry.groupID == entryToErase.groupID;
     458              :                                                                      }),
     459              :                                                       mEndpointGroupIDEntries.end());
     460              :                     }));
     461              :                 }
     462            0 :                 else if (it->statusEntry.state == Clusters::JointFabricDatastore::DatastoreStateEnum::kCommitFailed)
     463              :                 {
     464            0 :                     CHIP_ERROR failureCode(it->statusEntry.failureCode);
     465              : 
     466            0 :                     if (failureCode == CHIP_IM_GLOBAL_STATUS(ConstraintError) ||
     467            0 :                         failureCode == CHIP_IM_GLOBAL_STATUS(ResourceExhausted))
     468              :                     {
     469            0 :                         ++it;
     470            0 :                         continue;
     471              :                     }
     472              : 
     473              :                     // Retry or handle failure - for now skip
     474            0 :                     ++it;
     475            0 :                     continue;
     476            0 :                 }
     477              :             }
     478              : 
     479            0 :             ++it;
     480              :         }
     481              : 
     482              :         // Start fetching groups from the first endpoint
     483            1 :         mRefreshingEndpointIndex = 0;
     484            1 :         mRefreshState            = kRefreshingBindings;
     485              : 
     486              :         // Fall through to kRefreshingGroups to start fetching
     487            1 :         return ContinueRefresh();
     488              :     }
     489              :     break;
     490              : 
     491            2 :     case kRefreshingBindings: {
     492              :         // Check if we still have endpoints to process for group fetching
     493            2 :         if (mRefreshingEndpointIndex < mRefreshingEndpointsList.size())
     494              :         {
     495              :             // Fetch group list for the current endpoint
     496            1 :             EndpointId currentEndpointId = mRefreshingEndpointsList[mRefreshingEndpointIndex].endpointID;
     497              : 
     498            4 :             ReturnErrorOnFailure(mDelegate->FetchEndpointBindingList(
     499              :                 mRefreshingNodeId, currentEndpointId,
     500              :                 [this](CHIP_ERROR err,
     501              :                        const std::vector<Clusters::JointFabricDatastore::Structs::DatastoreEndpointBindingEntryStruct::Type> &
     502              :                            endpointBindings) {
     503              :                     if (err == CHIP_NO_ERROR)
     504              :                     {
     505              :                         // Convert endpointBindings to mEndpointBindingEntries
     506              :                         for (const auto & endpointBinding : endpointBindings)
     507              :                         {
     508              :                             auto it = std::find_if(
     509              :                                 mEndpointBindingEntries.begin(), mEndpointBindingEntries.end(),
     510              :                                 [this, &endpointBinding](
     511              :                                     const Clusters::JointFabricDatastore::Structs::DatastoreEndpointBindingEntryStruct::Type &
     512              :                                         entry) {
     513              :                                     return entry.nodeID == mRefreshingNodeId && entry.endpointID == endpointBinding.endpointID &&
     514              :                                         BindingMatches(entry.binding, endpointBinding.binding);
     515              :                                 });
     516              : 
     517              :                             if (it == mEndpointBindingEntries.end())
     518              :                             {
     519              :                                 Clusters::JointFabricDatastore::Structs::DatastoreEndpointBindingEntryStruct::Type newEntry;
     520              :                                 newEntry.nodeID     = mRefreshingNodeId;
     521              :                                 newEntry.endpointID = endpointBinding.endpointID;
     522              :                                 newEntry.binding    = endpointBinding.binding;
     523              :                                 if (GenerateAndAssignAUniqueListID(newEntry.listID) != CHIP_NO_ERROR)
     524              :                                 {
     525              :                                     // Unable to generate a unique List ID; skip this entry.
     526              :                                     continue;
     527              :                                 }
     528              :                                 newEntry.statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kCommitted;
     529              :                                 mEndpointBindingEntries.push_back(newEntry);
     530              :                             }
     531              :                         }
     532              : 
     533              :                         // Remove entries not in endpointBindings, but only if they are Committed or DeletePending
     534              :                         mEndpointBindingEntries.erase(
     535              :                             std::remove_if(
     536              :                                 mEndpointBindingEntries.begin(), mEndpointBindingEntries.end(),
     537              :                                 [&](const auto & entry) {
     538              :                                     if (entry.nodeID != mRefreshingNodeId)
     539              :                                     {
     540              :                                         return false;
     541              :                                     }
     542              :                                     if (entry.statusEntry.state != Clusters::JointFabricDatastore::DatastoreStateEnum::kCommitted &&
     543              :                                         entry.statusEntry.state !=
     544              :                                             Clusters::JointFabricDatastore::DatastoreStateEnum::kDeletePending)
     545              :                                     {
     546              :                                         return false;
     547              :                                     }
     548              :                                     return std::none_of(endpointBindings.begin(), endpointBindings.end(), [&](const auto & eb) {
     549              :                                         return entry.endpointID == eb.endpointID && BindingMatches(entry.binding, eb.binding);
     550              :                                     });
     551              :                                 }),
     552              :                             mEndpointBindingEntries.end());
     553              :                     }
     554              : 
     555              :                     // Move to the next endpoint
     556              :                     mRefreshingEndpointIndex++;
     557              : 
     558              :                     // Continue the state machine to let the kRefreshingBindings branch process mEndpointBindingList.
     559              :                     if (ContinueRefresh() != CHIP_NO_ERROR)
     560              :                     {
     561              :                         // Ignore errors in continuation from within the callback.
     562              :                     }
     563              :                 }));
     564              : 
     565              :             // Return here - the callback will call ContinueRefresh() again
     566            1 :             return CHIP_NO_ERROR;
     567              :         }
     568              : 
     569            1 :         for (auto it = mEndpointBindingEntries.begin(); it != mEndpointBindingEntries.end();)
     570              :         {
     571            0 :             if (it->nodeID == mRefreshingNodeId)
     572              :             {
     573            0 :                 if (it->statusEntry.state == Clusters::JointFabricDatastore::DatastoreStateEnum::kPending ||
     574            0 :                     it->statusEntry.state == Clusters::JointFabricDatastore::DatastoreStateEnum::kCommitted)
     575              :                 {
     576            0 :                     mRefreshingBindingEntries.push_back(*it);
     577              :                 }
     578            0 :                 else if (it->statusEntry.state == Clusters::JointFabricDatastore::DatastoreStateEnum::kCommitFailed)
     579              :                 {
     580            0 :                     CHIP_ERROR failureCode(it->statusEntry.failureCode);
     581              : 
     582            0 :                     if (failureCode == CHIP_IM_GLOBAL_STATUS(ConstraintError) ||
     583            0 :                         failureCode == CHIP_IM_GLOBAL_STATUS(ResourceExhausted))
     584              :                     {
     585              :                         // remove entry from the list
     586            0 :                         it = mEndpointBindingEntries.erase(it);
     587            0 :                         continue;
     588              :                     }
     589              : 
     590            0 :                     mRefreshingBindingEntries.push_back(*it);
     591              :                 }
     592              :             }
     593              : 
     594            0 :             ++it;
     595              :         }
     596              : 
     597            3 :         ReturnErrorOnFailure(mDelegate->SyncNode(mRefreshingNodeId, mRefreshingBindingEntries, [this]() {
     598              :             for (auto & entry : mEndpointBindingEntries)
     599              :             {
     600              :                 if (entry.nodeID == mRefreshingNodeId &&
     601              :                     (entry.statusEntry.state == Clusters::JointFabricDatastore::DatastoreStateEnum::kPending ||
     602              :                      entry.statusEntry.state == Clusters::JointFabricDatastore::DatastoreStateEnum::kCommitFailed))
     603              :                 {
     604              :                     for (const auto & bindingEntry : mRefreshingBindingEntries)
     605              :                     {
     606              :                         if (entry.endpointID == bindingEntry.endpointID && BindingMatches(entry.binding, bindingEntry.binding))
     607              :                         {
     608              :                             entry.statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kCommitted;
     609              :                             break;
     610              :                         }
     611              :                     }
     612              :                 }
     613              :             }
     614              : 
     615              :             // Remove all DeletePending entries for mRefreshingNodeId
     616              :             mEndpointBindingEntries.erase(std::remove_if(mEndpointBindingEntries.begin(), mEndpointBindingEntries.end(),
     617              :                                                          [this](const auto & entry) {
     618              :                                                              return entry.nodeID == mRefreshingNodeId &&
     619              :                                                                  entry.statusEntry.state ==
     620              :                                                                  Clusters::JointFabricDatastore::DatastoreStateEnum::kDeletePending;
     621              :                                                          }),
     622              :                                           mEndpointBindingEntries.end());
     623              : 
     624              :             // After syncing bindings, move to fetching group key sets
     625              :             mRefreshState = kFetchingGroupKeySetList;
     626              :             if (ContinueRefresh() != CHIP_NO_ERROR)
     627              :             {
     628              :                 // Ignore errors in continuation from within the callback.
     629              :             }
     630              :         }));
     631              :     }
     632            1 :     break;
     633            1 :     case kFetchingGroupKeySetList: {
     634            4 :         ReturnErrorOnFailure(
     635              :             mDelegate->FetchGroupKeySetList(mRefreshingNodeId, [this](CHIP_ERROR err, const std::vector<uint16_t> & groupKeySets) {
     636              :                 if (err == CHIP_NO_ERROR)
     637              :                 {
     638              :                     // Store the fetched group key sets for processing in the next state.
     639              :                     mRefreshingGroupKeySetIDs = groupKeySets;
     640              : 
     641              :                     // Advance the state machine to process the group key sets.
     642              :                     mRefreshState               = kFetchingGroupKeySets;
     643              :                     mRefreshingGroupKeySetIndex = 0;
     644              :                 }
     645              :                 else
     646              :                 {
     647              :                     // Leave node as pending but tear down the refresh state.
     648              :                     mRefreshingNodeId = kUndefinedNodeId;
     649              :                     mRefreshState     = kIdle;
     650              :                     return;
     651              :                 }
     652              : 
     653              :                 // Continue the state machine to let the kFetchingGroupKeySets branch process mRefreshingGroupKeySetIDs.
     654              :                 if (ContinueRefresh() != CHIP_NO_ERROR)
     655              :                 {
     656              :                     // Ignore errors in continuation from within the callback.
     657              :                 }
     658              :             }));
     659              :     }
     660            1 :     break;
     661            2 :     case kFetchingGroupKeySets: {
     662              :         // Request each Group Key Set from the device and transition to kRefreshingGroupKeySets once all indices are read.
     663            2 :         if (mRefreshingGroupKeySetIndex < mRefreshingGroupKeySetIDs.size())
     664              :         {
     665            1 :             const uint16_t groupKeySetID = mRefreshingGroupKeySetIDs[mRefreshingGroupKeySetIndex];
     666              : 
     667            2 :             return mDelegate->FetchGroupKeySet(
     668              :                 mRefreshingNodeId, groupKeySetID,
     669            1 :                 [this](CHIP_ERROR err,
     670            7 :                        const Clusters::JointFabricDatastore::Structs::DatastoreGroupKeySetStruct::Type & groupKeySet) {
     671            2 :                     if (err == CHIP_NO_ERROR)
     672              :                     {
     673            1 :                         auto it = std::find_if(
     674              :                             mGroupKeySetList.begin(), mGroupKeySetList.end(),
     675            0 :                             [&groupKeySet](
     676            0 :                                 const Clusters::JointFabricDatastore::Structs::DatastoreGroupKeySetStruct::Type & entry) {
     677            0 :                                 return entry.groupKeySetID == groupKeySet.groupKeySetID;
     678              :                             });
     679              : 
     680            1 :                         if (it == mGroupKeySetList.end())
     681              :                         {
     682            1 :                             Clusters::JointFabricDatastore::Structs::DatastoreGroupKeySetStruct::Type copiedKeySet;
     683            1 :                             LogErrorOnFailure(CopyGroupKeySetWithOwnedSpans(groupKeySet, copiedKeySet));
     684            1 :                             mGroupKeySetList.push_back(copiedKeySet);
     685              :                         }
     686              :                         else
     687              :                         {
     688              :                             // Update existing entry
     689            0 :                             LogErrorOnFailure(CopyGroupKeySetWithOwnedSpans(groupKeySet, *it));
     690              :                         }
     691              : 
     692            1 :                         ++mRefreshingGroupKeySetIndex;
     693              :                     }
     694              :                     else
     695              :                     {
     696              :                         // Leave node as pending but tear down the refresh state.
     697            0 :                         mRefreshingNodeId = kUndefinedNodeId;
     698            0 :                         mRefreshState     = kIdle;
     699            0 :                         return;
     700              :                     }
     701              : 
     702              :                     // Continue fetching key sets until complete, then process mGroupKeySetList in kRefreshingGroupKeySets.
     703            2 :                     if (ContinueRefresh() != CHIP_NO_ERROR)
     704              :                     {
     705              :                         // Ignore errors in continuation from within the callback.
     706              :                     }
     707            1 :                 });
     708              :         }
     709              : 
     710              :         // No group key sets to fetch; advance the state machine to process group key sets (which will be empty) and sync to
     711              :         // nodes.
     712            1 :         mRefreshState = kRefreshingGroupKeySets;
     713            1 :         return ContinueRefresh();
     714              :     }
     715              :     break;
     716            1 :     case kRefreshingGroupKeySets: {
     717              :         // 4. Ensure per-node key-set entries for each GroupKeySet are synced to devices.
     718            2 :         for (auto gksIt = mGroupKeySetList.begin(); gksIt != mGroupKeySetList.end(); ++gksIt)
     719              :         {
     720            1 :             const uint16_t groupKeySetId = gksIt->groupKeySetID;
     721              : 
     722            1 :             for (auto nkIt = mNodeKeySetEntries.begin(); nkIt != mNodeKeySetEntries.end();)
     723              :             {
     724            0 :                 if (nkIt->groupKeySetID != groupKeySetId)
     725              :                 {
     726            0 :                     ++nkIt;
     727            0 :                     continue;
     728              :                 }
     729              : 
     730              :                 // nkIt references the current groupKeySetId
     731            0 :                 if (nkIt->statusEntry.state == Clusters::JointFabricDatastore::DatastoreStateEnum::kPending)
     732              :                 {
     733              :                     // Make a copy of the group key set to send to the node.
     734            0 :                     const NodeId entryNodeId = nkIt->nodeID;
     735            0 :                     auto groupKeySet         = *gksIt;
     736            0 :                     ReturnErrorOnFailure(mDelegate->SyncNode(nkIt->nodeID, groupKeySet, [this, entryNodeId, groupKeySetId]() {
     737              :                         detail::MarkEntryCommittedIfFound(mNodeKeySetEntries, [&](const auto & e) {
     738              :                             return e.nodeID == entryNodeId && e.groupKeySetID == groupKeySetId;
     739              :                         });
     740              :                     }));
     741            0 :                     ++nkIt;
     742              :                 }
     743            0 :                 else if (nkIt->statusEntry.state == Clusters::JointFabricDatastore::DatastoreStateEnum::kDeletePending)
     744              :                 {
     745              :                     // zero-initialized struct to indicate deletion for the SyncNode call
     746            0 :                     Clusters::JointFabricDatastore::Structs::DatastoreNodeKeySetEntryStruct::Type nullEntry{ 0 };
     747              : 
     748            0 :                     auto nodeIdToErase        = nkIt->nodeID;
     749            0 :                     auto groupKeySetIdToErase = nkIt->groupKeySetID;
     750            0 :                     ReturnErrorOnFailure(
     751              :                         mDelegate->SyncNode(nkIt->nodeID, nullEntry, [this, nodeIdToErase, groupKeySetIdToErase]() {
     752              :                             mNodeKeySetEntries.erase(std::remove_if(mNodeKeySetEntries.begin(), mNodeKeySetEntries.end(),
     753              :                                                                     [&](const auto & entry) {
     754              :                                                                         return entry.nodeID == nodeIdToErase &&
     755              :                                                                             entry.groupKeySetID == groupKeySetIdToErase;
     756              :                                                                     }),
     757              :                                                      mNodeKeySetEntries.end());
     758              :                         }));
     759              :                 }
     760            0 :                 else if (nkIt->statusEntry.state == Clusters::JointFabricDatastore::DatastoreStateEnum::kCommitFailed)
     761              :                 {
     762            0 :                     CHIP_ERROR failureCode(nkIt->statusEntry.failureCode);
     763              : 
     764            0 :                     if (failureCode == CHIP_IM_GLOBAL_STATUS(ConstraintError) ||
     765            0 :                         failureCode == CHIP_IM_GLOBAL_STATUS(ResourceExhausted))
     766              :                     {
     767              :                         // remove entry from the list
     768            0 :                         nkIt = mNodeKeySetEntries.erase(nkIt);
     769              :                     }
     770              :                     else
     771              :                     {
     772              :                         // Retry the failed commit by attempting to SyncNode again.
     773            0 :                         const NodeId entryNodeId = nkIt->nodeID;
     774            0 :                         auto groupKeySet         = *gksIt;
     775            0 :                         ReturnErrorOnFailure(mDelegate->SyncNode(nkIt->nodeID, groupKeySet, [this, entryNodeId, groupKeySetId]() {
     776              :                             detail::MarkEntryCommittedIfFound(mNodeKeySetEntries, [&](const auto & e) {
     777              :                                 return e.nodeID == entryNodeId && e.groupKeySetID == groupKeySetId;
     778              :                             });
     779              :                         }));
     780            0 :                         ++nkIt;
     781              :                     }
     782              :                 }
     783              :                 else
     784              :                 {
     785            0 :                     ++nkIt;
     786              :                 }
     787              :             }
     788              :         }
     789              : 
     790              :         // Request ACL List from the device and transition to kRefreshingACLs.
     791            4 :         ReturnErrorOnFailure(mDelegate->FetchACLList(
     792              :             mRefreshingNodeId,
     793              :             [this](CHIP_ERROR err,
     794              :                    const std::vector<Clusters::JointFabricDatastore::Structs::DatastoreACLEntryStruct::Type> & acls) {
     795              :                 if (err == CHIP_NO_ERROR)
     796              :                 {
     797              :                     // Convert acls to mACLEntries
     798              :                     for (const auto & acl : acls)
     799              :                     {
     800              :                         auto it = std::find_if(mACLEntries.begin(), mACLEntries.end(),
     801              :                                                [this, &acl](const datastore::ACLEntryStruct & entry) {
     802              :                                                    return entry.nodeID == mRefreshingNodeId && entry.listID == acl.listID;
     803              :                                                });
     804              : 
     805              :                         if (it == mACLEntries.end())
     806              :                         {
     807              :                             datastore::ACLEntryStruct newEntry;
     808              :                             newEntry.nodeID             = mRefreshingNodeId;
     809              :                             newEntry.listID             = acl.listID;
     810              :                             newEntry.ACLEntry.authMode  = acl.ACLEntry.authMode;
     811              :                             newEntry.ACLEntry.privilege = acl.ACLEntry.privilege;
     812              : 
     813              :                             if (!acl.ACLEntry.subjects.IsNull())
     814              :                             {
     815              :                                 for (size_t subjectsIndex = 0; subjectsIndex < acl.ACLEntry.subjects.Value().size();
     816              :                                      ++subjectsIndex)
     817              :                                 {
     818              :                                     newEntry.ACLEntry.subjects.push_back(acl.ACLEntry.subjects.Value()[subjectsIndex]);
     819              :                                 }
     820              :                             }
     821              : 
     822              :                             if (!acl.ACLEntry.targets.IsNull())
     823              :                             {
     824              :                                 for (size_t targetsIndex = 0; targetsIndex < acl.ACLEntry.targets.Value().size(); ++targetsIndex)
     825              :                                 {
     826              :                                     newEntry.ACLEntry.targets.push_back(acl.ACLEntry.targets.Value()[targetsIndex]);
     827              :                                 }
     828              :                             }
     829              : 
     830              :                             newEntry.statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kCommitted;
     831              :                             mACLEntries.push_back(newEntry);
     832              :                         }
     833              :                     }
     834              : 
     835              :                     // Remove entries not in acls, but only if they are Committed or DeletePending
     836              :                     mACLEntries.erase(std::remove_if(mACLEntries.begin(), mACLEntries.end(),
     837              :                                                      [&](const auto & entry) {
     838              :                                                          if (entry.nodeID != mRefreshingNodeId)
     839              :                                                          {
     840              :                                                              return false;
     841              :                                                          }
     842              :                                                          if (entry.statusEntry.state !=
     843              :                                                                  Clusters::JointFabricDatastore::DatastoreStateEnum::kCommitted &&
     844              :                                                              entry.statusEntry.state !=
     845              :                                                                  Clusters::JointFabricDatastore::DatastoreStateEnum::kDeletePending)
     846              :                                                          {
     847              :                                                              return false;
     848              :                                                          }
     849              :                                                          return std::none_of(acls.begin(), acls.end(), [&](const auto & acl) {
     850              :                                                              return entry.listID == acl.listID;
     851              :                                                          });
     852              :                                                      }),
     853              :                                       mACLEntries.end());
     854              : 
     855              :                     // Advance the state machine to process the ACLs.
     856              :                     mRefreshState = kRefreshingACLs;
     857              :                 }
     858              :                 else
     859              :                 {
     860              :                     // Leave node as pending but tear down the refresh state.
     861              :                     mRefreshingNodeId = kUndefinedNodeId;
     862              :                     mRefreshState     = kIdle;
     863              :                     return;
     864              :                 }
     865              : 
     866              :                 // Continue the state machine to let the kRefreshingACLs branch process mACLList.
     867              :                 if (ContinueRefresh() != CHIP_NO_ERROR)
     868              :                 {
     869              :                     // Ignore errors in continuation from within the callback.
     870              :                 }
     871              :             }));
     872              :     }
     873            1 :     break;
     874            1 :     case kRefreshingACLs: {
     875              :         // 5.
     876            1 :         for (auto it = mACLEntries.begin(); it != mACLEntries.end();)
     877              :         {
     878            0 :             if (it->nodeID == mRefreshingNodeId)
     879              :             {
     880            0 :                 if (it->statusEntry.state == Clusters::JointFabricDatastore::DatastoreStateEnum::kPending ||
     881            0 :                     it->statusEntry.state == Clusters::JointFabricDatastore::DatastoreStateEnum::kCommitted)
     882              :                 {
     883              :                     {
     884              :                         // Prepare an encoded ACL entry to send to the node.
     885            0 :                         Clusters::JointFabricDatastore::Structs::DatastoreACLEntryStruct::Type entryToSync;
     886            0 :                         entryToSync.nodeID             = it->nodeID;
     887            0 :                         entryToSync.listID             = it->listID;
     888            0 :                         entryToSync.ACLEntry.authMode  = it->ACLEntry.authMode;
     889            0 :                         entryToSync.ACLEntry.privilege = it->ACLEntry.privilege;
     890            0 :                         entryToSync.ACLEntry.subjects =
     891            0 :                             DataModel::List<const uint64_t>(it->ACLEntry.subjects.data(), it->ACLEntry.subjects.size());
     892            0 :                         entryToSync.ACLEntry.targets = DataModel::List<
     893              :                             const Clusters::JointFabricDatastore::Structs::DatastoreAccessControlTargetStruct::Type>(
     894            0 :                             it->ACLEntry.targets.data(), it->ACLEntry.targets.size());
     895            0 :                         entryToSync.statusEntry = it->statusEntry;
     896              : 
     897            0 :                         mRefreshingACLEntries.push_back(entryToSync);
     898              :                     }
     899              :                 }
     900            0 :                 else if (it->statusEntry.state == Clusters::JointFabricDatastore::DatastoreStateEnum::kCommitFailed)
     901              :                 {
     902            0 :                     CHIP_ERROR failureCode(it->statusEntry.failureCode);
     903              : 
     904            0 :                     if (failureCode == CHIP_IM_GLOBAL_STATUS(ConstraintError) ||
     905            0 :                         failureCode == CHIP_IM_GLOBAL_STATUS(ResourceExhausted))
     906              :                     {
     907              :                         // remove entry from the list
     908            0 :                         it = mACLEntries.erase(it);
     909            0 :                         continue;
     910              :                     }
     911              : 
     912              :                     // Prepare an encoded ACL entry to retry the failed commit.
     913            0 :                     Clusters::JointFabricDatastore::Structs::DatastoreACLEntryStruct::Type entryToSync;
     914            0 :                     entryToSync.nodeID             = it->nodeID;
     915            0 :                     entryToSync.listID             = it->listID;
     916            0 :                     entryToSync.ACLEntry.authMode  = it->ACLEntry.authMode;
     917            0 :                     entryToSync.ACLEntry.privilege = it->ACLEntry.privilege;
     918            0 :                     entryToSync.ACLEntry.subjects =
     919            0 :                         DataModel::List<const uint64_t>(it->ACLEntry.subjects.data(), it->ACLEntry.subjects.size());
     920            0 :                     entryToSync.ACLEntry.targets =
     921            0 :                         DataModel::List<const Clusters::JointFabricDatastore::Structs::DatastoreAccessControlTargetStruct::Type>(
     922            0 :                             it->ACLEntry.targets.data(), it->ACLEntry.targets.size());
     923            0 :                     entryToSync.statusEntry = it->statusEntry;
     924              : 
     925            0 :                     mRefreshingACLEntries.push_back(entryToSync);
     926              :                 }
     927              :             }
     928              : 
     929            0 :             ++it;
     930              :         }
     931              : 
     932            2 :         ReturnErrorOnFailure(mDelegate->SyncNode(mRefreshingNodeId, mRefreshingACLEntries, [this]() {
     933              :             for (auto & entry : mACLEntries)
     934              :             {
     935              :                 if (entry.nodeID == mRefreshingNodeId &&
     936              :                     (entry.statusEntry.state == Clusters::JointFabricDatastore::DatastoreStateEnum::kPending ||
     937              :                      entry.statusEntry.state == Clusters::JointFabricDatastore::DatastoreStateEnum::kCommitFailed))
     938              :                 {
     939              :                     for (const auto & aclEntry : mRefreshingACLEntries)
     940              :                     {
     941              :                         if (entry.listID == aclEntry.listID)
     942              :                         {
     943              :                             entry.statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kCommitted;
     944              :                             break;
     945              :                         }
     946              :                     }
     947              :                 }
     948              :             }
     949              : 
     950              :             // Remove all DeletePending entries for mRefreshingNodeId
     951              :             mACLEntries.erase(std::remove_if(mACLEntries.begin(), mACLEntries.end(),
     952              :                                              [this](const auto & entry) {
     953              :                                                  return entry.nodeID == mRefreshingNodeId &&
     954              :                                                      entry.statusEntry.state ==
     955              :                                                      Clusters::JointFabricDatastore::DatastoreStateEnum::kDeletePending;
     956              :                                              }),
     957              :                               mACLEntries.end());
     958              :         }));
     959              : 
     960              :         // 6.
     961            1 :         ReturnErrorOnFailure(SetNode(mRefreshingNodeId, Clusters::JointFabricDatastore::DatastoreStateEnum::kCommitted));
     962              : 
     963            1 :         ChipLogDetail(AppServer, "Finished refreshing node (ID: 0x" ChipLogFormatX64 "). Node is now marked as Committed.",
     964              :                       ChipLogValueX64(mRefreshingNodeId));
     965              : 
     966            1 :         for (Listener * listener = mListeners; listener != nullptr; listener = listener->mNext)
     967              :         {
     968            0 :             listener->MarkNodeListChanged();
     969              :         }
     970              : 
     971            1 :         mRefreshingNodeId = kUndefinedNodeId;
     972            1 :         mRefreshState     = kIdle;
     973              :     }
     974            1 :     break;
     975              :     }
     976              : 
     977            5 :     return CHIP_NO_ERROR;
     978              : }
     979              : 
     980            3 : CHIP_ERROR JointFabricDatastore::SetNode(NodeId nodeId, Clusters::JointFabricDatastore::DatastoreStateEnum state)
     981              : {
     982            3 :     size_t index = 0;
     983            3 :     ReturnErrorOnFailure(IsNodeIDInDatastore(nodeId, index));
     984            2 :     mNodeInformationEntries[index].commissioningStatusEntry.state = state;
     985            2 :     return CHIP_NO_ERROR;
     986              : }
     987              : 
     988            3 : CHIP_ERROR JointFabricDatastore::IsNodeIDInDatastore(NodeId nodeId, size_t & index)
     989              : {
     990            3 :     for (auto & entry : mNodeInformationEntries)
     991              :     {
     992            2 :         if (entry.nodeID == nodeId)
     993              :         {
     994            2 :             index = static_cast<size_t>(&entry - &mNodeInformationEntries[0]);
     995            2 :             return CHIP_NO_ERROR;
     996              :         }
     997              :     }
     998              : 
     999            1 :     return CHIP_ERROR_NOT_FOUND;
    1000              : }
    1001              : 
    1002              : CHIP_ERROR
    1003            2 : JointFabricDatastore::AddGroupKeySetEntry(
    1004              :     const Clusters::JointFabricDatastore::Structs::DatastoreGroupKeySetStruct::Type & groupKeySet)
    1005              : {
    1006            2 :     VerifyOrReturnError(IsGroupKeySetEntryPresent(groupKeySet.groupKeySetID) == false, CHIP_IM_GLOBAL_STATUS(ConstraintError));
    1007            2 :     VerifyOrReturnError(mGroupKeySetList.size() < kMaxGroupKeySet, CHIP_ERROR_NO_MEMORY);
    1008              : 
    1009            2 :     Clusters::JointFabricDatastore::Structs::DatastoreGroupKeySetStruct::Type copiedKeySet;
    1010            2 :     ReturnErrorOnFailure(CopyGroupKeySetWithOwnedSpans(groupKeySet, copiedKeySet));
    1011              : 
    1012            2 :     mGroupKeySetList.push_back(copiedKeySet);
    1013              : 
    1014            2 :     return CHIP_NO_ERROR;
    1015              : }
    1016              : 
    1017            2 : bool JointFabricDatastore::IsGroupKeySetEntryPresent(uint16_t groupKeySetId)
    1018              : {
    1019            2 :     for (auto & entry : mGroupKeySetList)
    1020              :     {
    1021            0 :         if (entry.groupKeySetID == groupKeySetId)
    1022              :         {
    1023            0 :             return true;
    1024              :         }
    1025              :     }
    1026              : 
    1027            2 :     return false;
    1028              : }
    1029              : 
    1030            0 : CHIP_ERROR JointFabricDatastore::RemoveGroupKeySetEntry(uint16_t groupKeySetId)
    1031              : {
    1032            0 :     VerifyOrReturnValue(groupKeySetId != 0, CHIP_IM_GLOBAL_STATUS(ConstraintError));
    1033              : 
    1034            0 :     for (auto it = mGroupKeySetList.begin(); it != mGroupKeySetList.end(); ++it)
    1035              :     {
    1036            0 :         if (it->groupKeySetID == groupKeySetId)
    1037              :         {
    1038            0 :             RemoveGroupKeySetStorage(groupKeySetId);
    1039            0 :             mGroupKeySetList.erase(it);
    1040            0 :             return CHIP_NO_ERROR;
    1041              :         }
    1042              :     }
    1043              : 
    1044            0 :     return CHIP_IM_GLOBAL_STATUS(NotFound);
    1045              : }
    1046              : 
    1047              : CHIP_ERROR
    1048            0 : JointFabricDatastore::UpdateGroupKeySetEntry(
    1049              :     Clusters::JointFabricDatastore::Structs::DatastoreGroupKeySetStruct::Type & groupKeySet)
    1050              : {
    1051            0 :     for (auto & entry : mGroupKeySetList)
    1052              :     {
    1053            0 :         if (entry.groupKeySetID == groupKeySet.groupKeySetID)
    1054              :         {
    1055            0 :             LogErrorOnFailure(UpdateNodeKeySetList(groupKeySet));
    1056              : 
    1057            0 :             VerifyOrReturnValue(groupKeySet.groupKeySecurityPolicy <
    1058              :                                     Clusters::JointFabricDatastore::DatastoreGroupKeySecurityPolicyEnum::kUnknownEnumValue,
    1059              :                                 CHIP_IM_GLOBAL_STATUS(ConstraintError));
    1060              : 
    1061            0 :             ReturnErrorOnFailure(CopyGroupKeySetWithOwnedSpans(groupKeySet, entry));
    1062              : 
    1063            0 :             return CHIP_NO_ERROR;
    1064              :         }
    1065              :     }
    1066              : 
    1067            0 :     return CHIP_ERROR_NOT_FOUND;
    1068              : }
    1069              : 
    1070              : CHIP_ERROR
    1071            2 : JointFabricDatastore::AddAdmin(
    1072              :     const Clusters::JointFabricDatastore::Structs::DatastoreAdministratorInformationEntryStruct::Type & adminId)
    1073              : {
    1074            2 :     VerifyOrReturnError(IsAdminEntryPresent(adminId.nodeID) == false, CHIP_IM_GLOBAL_STATUS(ConstraintError));
    1075            2 :     VerifyOrReturnError(mAdminEntries.size() < kMaxAdminNodes, CHIP_ERROR_NO_MEMORY);
    1076              : 
    1077            2 :     Clusters::JointFabricDatastore::Structs::DatastoreAdministratorInformationEntryStruct::Type entryToStore;
    1078            2 :     entryToStore.nodeID   = adminId.nodeID;
    1079            2 :     entryToStore.vendorID = adminId.vendorID;
    1080              : 
    1081            2 :     ReturnErrorOnFailure(SetAdminEntryWithOwnedStorage(adminId.nodeID, adminId.friendlyName, adminId.icac, entryToStore));
    1082              : 
    1083            2 :     mAdminEntries.push_back(entryToStore);
    1084              : 
    1085            2 :     return CHIP_NO_ERROR;
    1086              : }
    1087              : 
    1088            2 : bool JointFabricDatastore::IsAdminEntryPresent(NodeId nodeId)
    1089              : {
    1090            2 :     for (auto & entry : mAdminEntries)
    1091              :     {
    1092            0 :         if (entry.nodeID == nodeId)
    1093              :         {
    1094            0 :             return true;
    1095              :         }
    1096              :     }
    1097              : 
    1098            2 :     return false;
    1099              : }
    1100              : 
    1101            1 : CHIP_ERROR JointFabricDatastore::UpdateAdmin(NodeId nodeId, Optional<CharSpan> friendlyName, Optional<ByteSpan> icac)
    1102              : {
    1103            1 :     for (auto & entry : mAdminEntries)
    1104              :     {
    1105            1 :         if (entry.nodeID == nodeId)
    1106              :         {
    1107            1 :             auto & storage = mAdminEntryStorage[nodeId];
    1108            1 :             if (friendlyName.HasValue())
    1109              :             {
    1110            1 :                 const auto & name = friendlyName.Value();
    1111            1 :                 storage.friendlyName.assign(name.data(), name.data() + name.size());
    1112            1 :                 entry.friendlyName = CharSpan(storage.friendlyName.data(), storage.friendlyName.size());
    1113              :             }
    1114            1 :             if (icac.HasValue())
    1115              :             {
    1116            1 :                 const auto & icacVal = icac.Value();
    1117            1 :                 ReturnErrorOnFailure(storage.icac.SetLength(icacVal.size()));
    1118            1 :                 memcpy(storage.icac.Bytes(), icacVal.data(), icacVal.size());
    1119            1 :                 entry.icac = storage.icac.Span();
    1120              :             }
    1121            1 :             return CHIP_NO_ERROR;
    1122              :         }
    1123              :     }
    1124              : 
    1125            0 :     return CHIP_ERROR_NOT_FOUND;
    1126              : }
    1127              : 
    1128            0 : CHIP_ERROR JointFabricDatastore::RemoveAdmin(NodeId nodeId)
    1129              : {
    1130            0 :     for (auto it = mAdminEntries.begin(); it != mAdminEntries.end(); ++it)
    1131              :     {
    1132            0 :         if (it->nodeID == nodeId)
    1133              :         {
    1134            0 :             mAdminEntries.erase(it);
    1135            0 :             RemoveAdminEntryStorage(nodeId);
    1136            0 :             return CHIP_NO_ERROR;
    1137              :         }
    1138              :     }
    1139              : 
    1140            0 :     return CHIP_ERROR_NOT_FOUND;
    1141              : }
    1142              : 
    1143              : CHIP_ERROR
    1144            0 : JointFabricDatastore::UpdateNodeKeySetList(Clusters::JointFabricDatastore::Structs::DatastoreGroupKeySetStruct::Type & groupKeySet)
    1145              : {
    1146            0 :     bool entryUpdated = false;
    1147              : 
    1148            0 :     for (size_t i = 0; i < mNodeKeySetEntries.size(); ++i)
    1149              :     {
    1150            0 :         auto & entry = mNodeKeySetEntries[i];
    1151            0 :         if (entry.groupKeySetID == groupKeySet.groupKeySetID)
    1152              :         {
    1153            0 :             if (groupKeySet.groupKeySecurityPolicy <
    1154              :                 Clusters::JointFabricDatastore::DatastoreGroupKeySecurityPolicyEnum::kUnknownEnumValue)
    1155              :             {
    1156              : 
    1157            0 :                 const NodeId entryNodeId          = entry.nodeID;
    1158            0 :                 const uint16_t entryGroupKeySetID = groupKeySet.groupKeySetID;
    1159            0 :                 LogErrorOnFailure(mDelegate->SyncNode(entry.nodeID, groupKeySet, [this, entryNodeId, entryGroupKeySetID]() {
    1160              :                     detail::MarkEntryCommittedIfFound(mNodeKeySetEntries, [&](const auto & e) {
    1161              :                         return e.nodeID == entryNodeId && e.groupKeySetID == entryGroupKeySetID;
    1162              :                     });
    1163              :                 }));
    1164              : 
    1165            0 :                 if (entryUpdated == false)
    1166              :                 {
    1167            0 :                     entryUpdated = true;
    1168              :                 }
    1169              :             }
    1170              :             else
    1171              :             {
    1172            0 :                 entry.statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kCommitFailed;
    1173            0 :                 return CHIP_IM_GLOBAL_STATUS(ConstraintError);
    1174              :             }
    1175              :         }
    1176              :     }
    1177              : 
    1178            0 :     return entryUpdated ? CHIP_NO_ERROR : CHIP_ERROR_NOT_FOUND;
    1179              : }
    1180              : 
    1181            0 : CHIP_ERROR JointFabricDatastore::RemoveKeySet(uint16_t groupKeySetId)
    1182              : {
    1183            0 :     for (auto it = mNodeKeySetEntries.begin(); it != mNodeKeySetEntries.end(); ++it)
    1184              :     {
    1185            0 :         if (it->groupKeySetID == groupKeySetId)
    1186              :         {
    1187            0 :             if (it->statusEntry.state != Clusters::JointFabricDatastore::DatastoreStateEnum::kDeletePending)
    1188              :             {
    1189            0 :                 return CHIP_IM_GLOBAL_STATUS(ConstraintError); // Cannot remove a key set that is not pending
    1190              :             }
    1191              : 
    1192            0 :             ReturnErrorOnFailure(RemoveGroupKeySetEntry(groupKeySetId));
    1193              : 
    1194            0 :             return CHIP_NO_ERROR;
    1195              :         }
    1196              :     }
    1197              : 
    1198            0 :     return CHIP_IM_GLOBAL_STATUS(NotFound);
    1199              : }
    1200              : 
    1201            5 : CHIP_ERROR JointFabricDatastore::AddGroup(const Clusters::JointFabricDatastore::Commands::AddGroup::DecodableType & commandData)
    1202              : {
    1203            5 :     size_t index = 0;
    1204              :     // Check if the group ID already exists in the datastore
    1205           10 :     VerifyOrReturnError(IsGroupIDInDatastore(commandData.groupID, index) == CHIP_ERROR_NOT_FOUND,
    1206              :                         CHIP_IM_GLOBAL_STATUS(ConstraintError));
    1207              : 
    1208            5 :     if (commandData.groupCAT.ValueOr(0) == kAdminCATIdentifier || commandData.groupCAT.ValueOr(0) == kAnchorCATIdentifier)
    1209              :     {
    1210              :         // If the group is an AdminCAT or AnchorCAT, we cannot add it
    1211            0 :         return CHIP_IM_GLOBAL_STATUS(ConstraintError);
    1212              :     }
    1213              : 
    1214            5 :     Clusters::JointFabricDatastore::Structs::DatastoreGroupInformationEntryStruct::Type groupEntry;
    1215            5 :     groupEntry.groupID         = commandData.groupID;
    1216            5 :     groupEntry.groupKeySetID   = commandData.groupKeySetID;
    1217            5 :     groupEntry.groupCAT        = commandData.groupCAT;
    1218            5 :     groupEntry.groupCATVersion = commandData.groupCATVersion;
    1219            5 :     groupEntry.groupPermission = commandData.groupPermission;
    1220            5 :     SetGroupInformationFriendlyNameWithOwnedStorage(commandData.groupID, commandData.friendlyName, groupEntry);
    1221              : 
    1222              :     // Add the group entry to the datastore
    1223            5 :     mGroupInformationEntries.push_back(groupEntry);
    1224              : 
    1225            5 :     return CHIP_NO_ERROR;
    1226              : }
    1227              : 
    1228              : CHIP_ERROR
    1229            0 : JointFabricDatastore::ForceAddGroup(const Clusters::JointFabricDatastore::Commands::AddGroup::DecodableType & commandData)
    1230              : {
    1231            0 :     size_t index = 0;
    1232              :     // Check if the group ID already exists in the datastore
    1233            0 :     VerifyOrReturnError(IsGroupIDInDatastore(commandData.groupID, index) == CHIP_ERROR_NOT_FOUND,
    1234              :                         CHIP_IM_GLOBAL_STATUS(ConstraintError));
    1235              : 
    1236            0 :     Clusters::JointFabricDatastore::Structs::DatastoreGroupInformationEntryStruct::Type groupEntry;
    1237            0 :     groupEntry.groupID         = commandData.groupID;
    1238            0 :     groupEntry.groupKeySetID   = commandData.groupKeySetID;
    1239            0 :     groupEntry.groupCAT        = commandData.groupCAT;
    1240            0 :     groupEntry.groupCATVersion = commandData.groupCATVersion;
    1241            0 :     groupEntry.groupPermission = commandData.groupPermission;
    1242            0 :     SetGroupInformationFriendlyNameWithOwnedStorage(commandData.groupID, commandData.friendlyName, groupEntry);
    1243              : 
    1244              :     // Add the group entry to the datastore
    1245            0 :     mGroupInformationEntries.push_back(groupEntry);
    1246              : 
    1247            0 :     return CHIP_NO_ERROR;
    1248              : }
    1249              : 
    1250              : CHIP_ERROR
    1251            3 : JointFabricDatastore::UpdateGroup(const Clusters::JointFabricDatastore::Commands::UpdateGroup::DecodableType & commandData)
    1252              : {
    1253            3 :     VerifyOrReturnError(mDelegate != nullptr, CHIP_ERROR_INCORRECT_STATE);
    1254              : 
    1255            3 :     size_t index = 0;
    1256              :     // Check if the group ID exists in the datastore
    1257            6 :     VerifyOrReturnError(IsGroupIDInDatastore(commandData.groupID, index) == CHIP_NO_ERROR, CHIP_IM_GLOBAL_STATUS(ConstraintError));
    1258              : 
    1259            6 :     if (mGroupInformationEntries[index].groupCAT.ValueOr(0) == kAdminCATIdentifier ||
    1260            6 :         mGroupInformationEntries[index].groupCAT.ValueOr(0) == kAnchorCATIdentifier)
    1261              :     {
    1262              :         // If the group is an AdminCAT or AnchorCAT, we cannot update it
    1263            0 :         return CHIP_IM_GLOBAL_STATUS(ConstraintError);
    1264              :     }
    1265              : 
    1266              :     // Update the group entry with the new data
    1267            3 :     if (commandData.friendlyName.IsNull() == false)
    1268              :     {
    1269            0 :         if (mGroupInformationEntries[index].friendlyName.data_equal(commandData.friendlyName.Value()) == false)
    1270              :         {
    1271              :             // Friendly name changed. For every endpoint that references this group, mark the endpoint's
    1272              :             // GroupIDList entry as pending and attempt to push the change to the node. If the push
    1273              :             // fails, leave the entry as pending so a subsequent Refresh can apply it.
    1274            0 :             const GroupId updatedGroupId = commandData.groupID;
    1275            0 :             for (size_t i = 0; i < mEndpointGroupIDEntries.size(); ++i)
    1276              :             {
    1277            0 :                 auto & epGroupEntry = mEndpointGroupIDEntries[i];
    1278            0 :                 if (epGroupEntry.groupID == updatedGroupId)
    1279              :                 {
    1280            0 :                     epGroupEntry.statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kPending;
    1281              : 
    1282              :                     // Make a copy to send to the node. Do not fail the entire UpdateGroup if SyncNode
    1283              :                     // returns an error; leave the entry pending for a later refresh per spec.
    1284            0 :                     auto entryToSync = epGroupEntry;
    1285              : 
    1286            0 :                     const NodeId entryNodeId         = epGroupEntry.nodeID;
    1287            0 :                     const EndpointId entryEndpointId = epGroupEntry.endpointID;
    1288            0 :                     CHIP_ERROR syncErr               = mDelegate->SyncNode(
    1289            0 :                         epGroupEntry.nodeID, entryToSync, [this, entryNodeId, entryEndpointId, updatedGroupId]() {
    1290            0 :                             detail::MarkEntryCommittedIfFound(mEndpointGroupIDEntries, [&](const auto & e) {
    1291            0 :                                 return e.nodeID == entryNodeId && e.endpointID == entryEndpointId && e.groupID == updatedGroupId;
    1292              :                             });
    1293            0 :                         });
    1294              : 
    1295            0 :                     if (syncErr != CHIP_NO_ERROR)
    1296              :                     {
    1297            0 :                         ChipLogError(DataManagement,
    1298              :                                      "Failed to sync node for group friendly name update, leaving as pending: %" CHIP_ERROR_FORMAT,
    1299              :                                      syncErr.Format());
    1300              :                     }
    1301              :                 }
    1302              :             }
    1303              : 
    1304              :             // Update the friendly name in the datastore
    1305            0 :             SetGroupInformationFriendlyNameWithOwnedStorage(static_cast<GroupId>(mGroupInformationEntries[index].groupID),
    1306            0 :                                                             commandData.friendlyName.Value(), mGroupInformationEntries[index]);
    1307              :         }
    1308              :     }
    1309            3 :     if (commandData.groupKeySetID.IsNull() == false)
    1310              :     {
    1311            1 :         if (mGroupInformationEntries[index].groupKeySetID.IsNull() ||
    1312            0 :             mGroupInformationEntries[index].groupKeySetID.Value() != commandData.groupKeySetID.Value())
    1313              :         {
    1314              :             // If the groupKeySetID is being updated, we need to ensure that the new key set exists
    1315            1 :             ReturnErrorOnFailure(AddNodeKeySetEntry(commandData.groupID, commandData.groupKeySetID.Value()));
    1316            1 :             if (!mGroupInformationEntries[index].groupKeySetID.IsNull())
    1317              :             {
    1318            0 :                 LogErrorOnFailure(RemoveNodeKeySetEntry(
    1319              :                     commandData.groupID, mGroupInformationEntries[index].groupKeySetID.Value())); // Remove the old key set
    1320              :             }
    1321              :         }
    1322            1 :         mGroupInformationEntries[index].groupKeySetID = commandData.groupKeySetID;
    1323              :     }
    1324              : 
    1325            3 :     bool anyGroupCATFieldUpdated = false;
    1326              : 
    1327            3 :     if (commandData.groupCAT.IsNull() == false)
    1328              :     {
    1329            1 :         if (mGroupInformationEntries[index].groupCAT.IsNull() ||
    1330            0 :             mGroupInformationEntries[index].groupCAT.Value() != commandData.groupCAT.Value())
    1331              :         {
    1332            1 :             anyGroupCATFieldUpdated = true;
    1333              :         }
    1334              :         // Update the groupCAT
    1335            1 :         mGroupInformationEntries[index].groupCAT = commandData.groupCAT;
    1336              :     }
    1337            3 :     if (commandData.groupCATVersion.IsNull() == false)
    1338              :     {
    1339            1 :         if (mGroupInformationEntries[index].groupCATVersion.IsNull() ||
    1340            0 :             mGroupInformationEntries[index].groupCATVersion.Value() != commandData.groupCATVersion.Value())
    1341              :         {
    1342            1 :             anyGroupCATFieldUpdated = true;
    1343              :         }
    1344            1 :         mGroupInformationEntries[index].groupCATVersion = commandData.groupCATVersion;
    1345              :     }
    1346            3 :     if (commandData.groupPermission.IsNull() == false &&
    1347            0 :         commandData.groupPermission.Value() !=
    1348              :             Clusters::JointFabricDatastore::DatastoreAccessControlEntryPrivilegeEnum::kUnknownEnumValue)
    1349              :     {
    1350            0 :         if (mGroupInformationEntries[index].groupPermission != commandData.groupPermission.Value())
    1351              :         {
    1352            0 :             anyGroupCATFieldUpdated = true;
    1353              :         }
    1354              :         // If the groupPermission is not set to kUnknownEnumValue, update it
    1355            0 :         mGroupInformationEntries[index].groupPermission = commandData.groupPermission.Value();
    1356              :     }
    1357              : 
    1358            3 :     if (anyGroupCATFieldUpdated)
    1359              :     {
    1360            2 :         const GroupId updatedGroupId = commandData.groupID;
    1361              : 
    1362            2 :         for (size_t i = 0; i < mACLEntries.size(); ++i)
    1363              :         {
    1364            0 :             auto & acl = mACLEntries[i];
    1365              : 
    1366              :             // Determine if this ACL entry references the updated group
    1367            0 :             bool referencesGroup = false;
    1368            0 :             for (const auto & subject : acl.ACLEntry.subjects)
    1369              :             {
    1370              :                 // If the target has a group field and it matches the updated group, mark for update.
    1371              :                 // Use IsNull() to match other usages in this file.
    1372            0 :                 if (subject == static_cast<uint64_t>(updatedGroupId))
    1373              :                 {
    1374            0 :                     referencesGroup = true;
    1375            0 :                     break;
    1376              :                 }
    1377              :             }
    1378              : 
    1379            0 :             if (!referencesGroup)
    1380              :             {
    1381            0 :                 continue;
    1382              :             }
    1383              : 
    1384              :             // Update the ACL entry in the datastore to reflect the new group permission and mark Pending.
    1385            0 :             acl.ACLEntry.privilege = mGroupInformationEntries[index].groupPermission;
    1386            0 :             acl.statusEntry.state  = Clusters::JointFabricDatastore::DatastoreStateEnum::kPending;
    1387              : 
    1388              :             // Prepare an encoded entry to send to the node.
    1389            0 :             Clusters::JointFabricDatastore::Structs::DatastoreACLEntryStruct::Type entryToEncode;
    1390            0 :             entryToEncode.nodeID             = acl.nodeID;
    1391            0 :             entryToEncode.listID             = acl.listID;
    1392            0 :             entryToEncode.ACLEntry.authMode  = acl.ACLEntry.authMode;
    1393            0 :             entryToEncode.ACLEntry.privilege = acl.ACLEntry.privilege;
    1394            0 :             entryToEncode.ACLEntry.subjects =
    1395            0 :                 DataModel::List<const uint64_t>(acl.ACLEntry.subjects.data(), acl.ACLEntry.subjects.size());
    1396            0 :             entryToEncode.ACLEntry.targets =
    1397            0 :                 DataModel::List<const Clusters::JointFabricDatastore::Structs::DatastoreAccessControlTargetStruct::Type>(
    1398            0 :                     acl.ACLEntry.targets.data(), acl.ACLEntry.targets.size());
    1399            0 :             entryToEncode.statusEntry = acl.statusEntry;
    1400              : 
    1401              :             // Attempt to update the ACL on the node. On success, mark the ACL entry as Committed.
    1402              :             // Re-resolve by stable key (nodeID + listID) inside the completion; capturing the loop
    1403              :             // index would mark the wrong/invalid slot if an interleaved Invoke mutated the vector.
    1404            0 :             const NodeId entryNodeId   = acl.nodeID;
    1405            0 :             const uint16_t entryListId = acl.listID;
    1406            0 :             ReturnErrorOnFailure(mDelegate->SyncNode(acl.nodeID, entryToEncode, [this, entryNodeId, entryListId]() {
    1407              :                 detail::MarkEntryCommittedIfFound(
    1408              :                     mACLEntries, [&](const auto & e) { return e.nodeID == entryNodeId && e.listID == entryListId; });
    1409              :             }));
    1410              :         }
    1411              :     }
    1412              : 
    1413            3 :     return CHIP_NO_ERROR;
    1414              : }
    1415              : 
    1416              : CHIP_ERROR
    1417            0 : JointFabricDatastore::RemoveGroup(const Clusters::JointFabricDatastore::Commands::RemoveGroup::DecodableType & commandData)
    1418              : {
    1419            0 :     size_t index = 0;
    1420              :     // Check if the group ID exists in the datastore
    1421            0 :     VerifyOrReturnError(IsGroupIDInDatastore(commandData.groupID, index) == CHIP_NO_ERROR, CHIP_IM_GLOBAL_STATUS(ConstraintError));
    1422              : 
    1423              :     // Remove the group entry from the datastore
    1424            0 :     auto it = mGroupInformationEntries.begin();
    1425            0 :     std::advance(it, index);
    1426              : 
    1427            0 :     if (it->groupCAT.ValueOr(0) == kAdminCATIdentifier || it->groupCAT.ValueOr(0) == kAnchorCATIdentifier)
    1428              :     {
    1429              :         // If the group is an AdminCAT or AnchorCAT, we cannot remove it
    1430            0 :         return CHIP_IM_GLOBAL_STATUS(ConstraintError);
    1431              :     }
    1432              : 
    1433            0 :     const GroupId removedGroupId = static_cast<GroupId>(it->groupID);
    1434            0 :     mGroupInformationEntries.erase(it);
    1435            0 :     RemoveGroupInformationStorage(removedGroupId);
    1436              : 
    1437            0 :     return CHIP_NO_ERROR;
    1438              : }
    1439              : 
    1440           10 : CHIP_ERROR JointFabricDatastore::IsGroupIDInDatastore(chip::GroupId groupId, size_t & index)
    1441              : {
    1442           10 :     for (auto & entry : mGroupInformationEntries)
    1443              :     {
    1444            5 :         if (entry.groupID == groupId)
    1445              :         {
    1446            5 :             index = static_cast<size_t>(&entry - &mGroupInformationEntries[0]);
    1447            5 :             return CHIP_NO_ERROR;
    1448              :         }
    1449              :     }
    1450              : 
    1451            5 :     return CHIP_ERROR_NOT_FOUND;
    1452              : }
    1453              : 
    1454           18 : CHIP_ERROR JointFabricDatastore::IsNodeIdInNodeInformationEntries(NodeId nodeId, size_t & index)
    1455              : {
    1456           96 :     for (auto & entry : mNodeInformationEntries)
    1457              :     {
    1458           96 :         if (entry.nodeID == nodeId)
    1459              :         {
    1460           18 :             index = static_cast<size_t>(&entry - &mNodeInformationEntries[0]);
    1461           18 :             return CHIP_NO_ERROR;
    1462              :         }
    1463              :     }
    1464              : 
    1465            0 :     return CHIP_IM_GLOBAL_STATUS(ConstraintError);
    1466              : }
    1467              : 
    1468            1 : CHIP_ERROR JointFabricDatastore::UpdateEndpointForNode(NodeId nodeId, chip::EndpointId endpointId, CharSpan friendlyName)
    1469              : {
    1470            1 :     for (auto & entry : mEndpointEntries)
    1471              :     {
    1472            1 :         if (entry.nodeID == nodeId && entry.endpointID == endpointId)
    1473              :         {
    1474            1 :             SetEndpointFriendlyNameWithOwnedStorage(nodeId, endpointId, friendlyName, entry);
    1475            1 :             return CHIP_NO_ERROR;
    1476              :         }
    1477              :     }
    1478              : 
    1479            0 :     return CHIP_IM_GLOBAL_STATUS(ConstraintError);
    1480              : }
    1481              : 
    1482           33 : CHIP_ERROR JointFabricDatastore::IsNodeIdAndEndpointInEndpointInformationEntries(NodeId nodeId, EndpointId endpointId,
    1483              :                                                                                  size_t & index)
    1484              : {
    1485           33 :     for (auto & entry : mEndpointEntries)
    1486              :     {
    1487           33 :         if (entry.nodeID == nodeId && entry.endpointID == endpointId)
    1488              :         {
    1489           33 :             index = static_cast<size_t>(&entry - &mEndpointEntries[0]);
    1490           33 :             return CHIP_NO_ERROR;
    1491              :         }
    1492              :     }
    1493              : 
    1494            0 :     return CHIP_IM_GLOBAL_STATUS(ConstraintError);
    1495              : }
    1496              : 
    1497            1 : CHIP_ERROR JointFabricDatastore::AddGroupIDToEndpointForNode(NodeId nodeId, chip::EndpointId endpointId, chip::GroupId groupId)
    1498              : {
    1499            1 :     VerifyOrReturnError(mDelegate != nullptr, CHIP_ERROR_INCORRECT_STATE);
    1500              : 
    1501            1 :     size_t index = 0;
    1502            1 :     ReturnErrorOnFailure(IsNodeIdAndEndpointInEndpointInformationEntries(nodeId, endpointId, index));
    1503              : 
    1504            2 :     VerifyOrReturnError(IsGroupIDInDatastore(groupId, index) == CHIP_NO_ERROR, CHIP_IM_GLOBAL_STATUS(ConstraintError));
    1505              : 
    1506            1 :     if (mGroupInformationEntries[index].groupKeySetID.IsNull() == false)
    1507              :     {
    1508            1 :         uint16_t groupKeySetID = mGroupInformationEntries[index].groupKeySetID.Value();
    1509              : 
    1510              :         // make sure mNodeKeySetEntries contains an entry for this keyset and node, else add one and update device
    1511            1 :         bool nodeKeySetExists = false;
    1512            1 :         for (auto & entry : mNodeKeySetEntries)
    1513              :         {
    1514            0 :             if (entry.nodeID == nodeId && entry.groupKeySetID == groupKeySetID)
    1515              :             {
    1516            0 :                 nodeKeySetExists = true;
    1517            0 :                 break; // Found the group key set, no need to add it again
    1518              :             }
    1519              :         }
    1520              : 
    1521            1 :         if (!nodeKeySetExists)
    1522              :         {
    1523              :             // Create a new group key set entry if it doesn't exist
    1524            1 :             Clusters::JointFabricDatastore::Structs::DatastoreNodeKeySetEntryStruct::Type newNodeKeySet;
    1525            1 :             newNodeKeySet.nodeID            = nodeId;
    1526            1 :             newNodeKeySet.groupKeySetID     = groupKeySetID;
    1527            1 :             newNodeKeySet.statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kPending;
    1528              : 
    1529            1 :             mNodeKeySetEntries.push_back(newNodeKeySet);
    1530              : 
    1531            3 :             ReturnErrorOnFailure(mDelegate->SyncNode(nodeId, newNodeKeySet, [this, nodeId, groupKeySetID]() {
    1532              :                 detail::MarkEntryCommittedIfFound(mNodeKeySetEntries, [&](const auto & entry) {
    1533              :                     return entry.nodeID == nodeId && entry.groupKeySetID == groupKeySetID;
    1534              :                 });
    1535              :             }));
    1536              :         }
    1537              :     }
    1538              : 
    1539              :     // Check if the group ID already exists for the endpoint
    1540            1 :     for (auto & entry : mEndpointGroupIDEntries)
    1541              :     {
    1542            0 :         if (entry.nodeID == nodeId && entry.endpointID == endpointId && entry.groupID == groupId)
    1543              :         {
    1544            0 :             return CHIP_NO_ERROR;
    1545              :         }
    1546              :     }
    1547              : 
    1548            1 :     VerifyOrReturnError(mEndpointGroupIDEntries.size() < kMaxGroups, CHIP_ERROR_NO_MEMORY);
    1549              : 
    1550              :     // Create a new endpoint group ID entry
    1551            1 :     Clusters::JointFabricDatastore::Structs::DatastoreEndpointGroupIDEntryStruct::Type newGroupEntry;
    1552            1 :     newGroupEntry.nodeID            = nodeId;
    1553            1 :     newGroupEntry.endpointID        = endpointId;
    1554            1 :     newGroupEntry.groupID           = groupId;
    1555            1 :     newGroupEntry.statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kPending;
    1556              : 
    1557              :     // Add the new ACL entry to the datastore
    1558            1 :     mEndpointGroupIDEntries.push_back(newGroupEntry);
    1559              : 
    1560            2 :     return mDelegate->SyncNode(nodeId, newGroupEntry, [this, nodeId, endpointId, groupId]() {
    1561            1 :         detail::MarkEntryCommittedIfFound(mEndpointGroupIDEntries, [&](const auto & entry) {
    1562            1 :             return entry.nodeID == nodeId && entry.endpointID == endpointId && entry.groupID == groupId;
    1563              :         });
    1564            1 :     });
    1565              : }
    1566              : 
    1567            1 : CHIP_ERROR JointFabricDatastore::RemoveGroupIDFromEndpointForNode(NodeId nodeId, chip::EndpointId endpointId, chip::GroupId groupId)
    1568              : {
    1569            1 :     VerifyOrReturnError(mDelegate != nullptr, CHIP_ERROR_INCORRECT_STATE);
    1570              : 
    1571            1 :     size_t index = 0;
    1572            1 :     ReturnErrorOnFailure(IsNodeIdAndEndpointInEndpointInformationEntries(nodeId, endpointId, index));
    1573              : 
    1574            1 :     for (auto it = mEndpointGroupIDEntries.begin(); it != mEndpointGroupIDEntries.end(); ++it)
    1575              :     {
    1576            1 :         if (it->nodeID == nodeId && it->endpointID == endpointId && it->groupID == groupId)
    1577              :         {
    1578            1 :             it->statusEntry.state       = Clusters::JointFabricDatastore::DatastoreStateEnum::kDeletePending;
    1579            1 :             const auto erasedNodeId     = it->nodeID;
    1580            1 :             const auto erasedEndpointId = it->endpointID;
    1581            1 :             const auto erasedGroupId    = it->groupID;
    1582            2 :             ReturnErrorOnFailure(mDelegate->SyncNode(nodeId, *it, [this, erasedNodeId, erasedEndpointId, erasedGroupId]() {
    1583              :                 for (auto eraseIt = mEndpointGroupIDEntries.begin(); eraseIt != mEndpointGroupIDEntries.end(); ++eraseIt)
    1584              :                 {
    1585              :                     if (eraseIt->nodeID == erasedNodeId && eraseIt->endpointID == erasedEndpointId &&
    1586              :                         eraseIt->groupID == erasedGroupId)
    1587              :                     {
    1588              :                         mEndpointGroupIDEntries.erase(eraseIt);
    1589              :                         break;
    1590              :                     }
    1591              :                 }
    1592              :             }));
    1593              : 
    1594            2 :             if (IsGroupIDInDatastore(groupId, index) == CHIP_NO_ERROR)
    1595              :             {
    1596            1 :                 for (auto it2 = mNodeKeySetEntries.begin(); it2 != mNodeKeySetEntries.end(); ++it2)
    1597              :                 {
    1598            2 :                     if (it2->nodeID == nodeId && mGroupInformationEntries[index].groupKeySetID.IsNull() == false &&
    1599            1 :                         it2->groupKeySetID == mGroupInformationEntries[index].groupKeySetID.Value())
    1600              :                     {
    1601            1 :                         it2->statusEntry.state         = Clusters::JointFabricDatastore::DatastoreStateEnum::kDeletePending;
    1602            1 :                         const auto erasedKeySetNodeId  = it2->nodeID;
    1603            1 :                         const auto erasedKeySetGroupId = it2->groupKeySetID;
    1604            2 :                         ReturnErrorOnFailure(mDelegate->SyncNode(nodeId, *it2, [this, erasedKeySetNodeId, erasedKeySetGroupId]() {
    1605              :                             for (auto eraseIt = mNodeKeySetEntries.begin(); eraseIt != mNodeKeySetEntries.end(); ++eraseIt)
    1606              :                             {
    1607              :                                 if (eraseIt->nodeID == erasedKeySetNodeId && eraseIt->groupKeySetID == erasedKeySetGroupId)
    1608              :                                 {
    1609              :                                     mNodeKeySetEntries.erase(eraseIt);
    1610              :                                     break;
    1611              :                                 }
    1612              :                             }
    1613              :                         }));
    1614              : 
    1615            1 :                         break;
    1616              :                     }
    1617              :                 }
    1618              :             }
    1619              : 
    1620            1 :             return CHIP_NO_ERROR;
    1621              :         }
    1622              :     }
    1623              : 
    1624            0 :     return CHIP_IM_GLOBAL_STATUS(NotFound);
    1625              : }
    1626              : 
    1627              : // look-up the highest listId used so far, from Endpoint Binding Entries and ACL Entries
    1628           44 : CHIP_ERROR JointFabricDatastore::GenerateAndAssignAUniqueListID(uint16_t & listId)
    1629              : {
    1630           44 :     uint16_t highestListID = 0;
    1631          201 :     for (auto & entry : mEndpointBindingEntries)
    1632              :     {
    1633          157 :         if (entry.listID >= highestListID)
    1634              :         {
    1635          157 :             highestListID = entry.listID + 1;
    1636              :         }
    1637              :     }
    1638          122 :     for (auto & entry : mACLEntries)
    1639              :     {
    1640           78 :         if (entry.listID >= highestListID)
    1641              :         {
    1642           78 :             highestListID = entry.listID + 1;
    1643              :         }
    1644              :     }
    1645              : 
    1646           44 :     listId = highestListID;
    1647              : 
    1648           44 :     return CHIP_NO_ERROR;
    1649              : }
    1650              : 
    1651          156 : bool JointFabricDatastore::BindingMatches(
    1652              :     const Clusters::JointFabricDatastore::Structs::DatastoreBindingTargetStruct::Type & binding1,
    1653              :     const Clusters::JointFabricDatastore::Structs::DatastoreBindingTargetStruct::Type & binding2)
    1654              : {
    1655          156 :     if (binding1.node.HasValue() && binding2.node.HasValue())
    1656              :     {
    1657          156 :         if (binding1.node.Value() != binding2.node.Value())
    1658              :         {
    1659          156 :             return false;
    1660              :         }
    1661              :     }
    1662            0 :     else if (binding1.node.HasValue() || binding2.node.HasValue())
    1663              :     {
    1664            0 :         return false;
    1665              :     }
    1666              : 
    1667            0 :     if (binding1.group.HasValue() && binding2.group.HasValue())
    1668              :     {
    1669            0 :         if (binding1.group.Value() != binding2.group.Value())
    1670              :         {
    1671            0 :             return false;
    1672              :         }
    1673              :     }
    1674            0 :     else if (binding1.group.HasValue() || binding2.group.HasValue())
    1675              :     {
    1676            0 :         return false;
    1677              :     }
    1678              : 
    1679            0 :     if (binding1.endpoint.HasValue() && binding2.endpoint.HasValue())
    1680              :     {
    1681            0 :         if (binding1.endpoint.Value() != binding2.endpoint.Value())
    1682              :         {
    1683            0 :             return false;
    1684              :         }
    1685              :     }
    1686            0 :     else if (binding1.endpoint.HasValue() || binding2.endpoint.HasValue())
    1687              :     {
    1688            0 :         return false;
    1689              :     }
    1690              : 
    1691            0 :     if (binding1.cluster.HasValue() && binding2.cluster.HasValue())
    1692              :     {
    1693            0 :         if (binding1.cluster.Value() != binding2.cluster.Value())
    1694              :         {
    1695            0 :             return false;
    1696              :         }
    1697              :     }
    1698            0 :     else if (binding1.cluster.HasValue() || binding2.cluster.HasValue())
    1699              :     {
    1700            0 :         return false;
    1701              :     }
    1702              : 
    1703            0 :     return true;
    1704              : }
    1705              : 
    1706              : CHIP_ERROR
    1707           29 : JointFabricDatastore::AddBindingToEndpointForNode(
    1708              :     NodeId nodeId, chip::EndpointId endpointId,
    1709              :     const Clusters::JointFabricDatastore::Structs::DatastoreBindingTargetStruct::Type & binding)
    1710              : {
    1711           29 :     VerifyOrReturnError(mDelegate != nullptr, CHIP_ERROR_INCORRECT_STATE);
    1712              : 
    1713           29 :     size_t index = 0;
    1714           29 :     ReturnErrorOnFailure(IsNodeIdAndEndpointInEndpointInformationEntries(nodeId, endpointId, index));
    1715              : 
    1716              :     // Check if the group ID already exists for the endpoint
    1717          185 :     for (auto & entry : mEndpointBindingEntries)
    1718              :     {
    1719          156 :         if (entry.nodeID == nodeId && entry.endpointID == endpointId)
    1720              :         {
    1721          156 :             if (BindingMatches(entry.binding, binding))
    1722              :             {
    1723            0 :                 return CHIP_NO_ERROR;
    1724              :             }
    1725              :         }
    1726              :     }
    1727              : 
    1728           29 :     VerifyOrReturnError(mEndpointBindingEntries.size() < kMaxGroups, CHIP_ERROR_NO_MEMORY);
    1729              : 
    1730              :     // Create a new binding entry
    1731           29 :     Clusters::JointFabricDatastore::Structs::DatastoreEndpointBindingEntryStruct::Type newBindingEntry;
    1732           29 :     newBindingEntry.nodeID     = nodeId;
    1733           29 :     newBindingEntry.endpointID = endpointId;
    1734           29 :     newBindingEntry.binding    = binding;
    1735           29 :     ReturnErrorOnFailure(GenerateAndAssignAUniqueListID(newBindingEntry.listID));
    1736           29 :     newBindingEntry.statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kPending;
    1737              : 
    1738              :     // Add the new binding entry to the datastore
    1739           29 :     mEndpointBindingEntries.push_back(newBindingEntry);
    1740              : 
    1741           29 :     const uint16_t listID = newBindingEntry.listID;
    1742           58 :     return mDelegate->SyncNode(nodeId, newBindingEntry, [this, nodeId, endpointId, listID]() {
    1743           29 :         detail::MarkEntryCommittedIfFound(mEndpointBindingEntries, [&](const auto & entry) {
    1744          185 :             return entry.nodeID == nodeId && entry.endpointID == endpointId && entry.listID == listID;
    1745              :         });
    1746           29 :     });
    1747              : }
    1748              : 
    1749              : CHIP_ERROR
    1750            2 : JointFabricDatastore::RemoveBindingFromEndpointForNode(uint16_t listId, NodeId nodeId, chip::EndpointId endpointId)
    1751              : {
    1752            2 :     VerifyOrReturnError(mDelegate != nullptr, CHIP_ERROR_INCORRECT_STATE);
    1753              : 
    1754            2 :     size_t index = 0;
    1755            2 :     ReturnErrorOnFailure(IsNodeIdAndEndpointInEndpointInformationEntries(nodeId, endpointId, index));
    1756              : 
    1757            2 :     for (auto it = mEndpointBindingEntries.begin(); it != mEndpointBindingEntries.end(); ++it)
    1758              :     {
    1759            2 :         if (it->nodeID == nodeId && it->listID == listId && it->endpointID == endpointId)
    1760              :         {
    1761            2 :             it->statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kDeletePending;
    1762              :             // Re-resolve by stable key inside the async completion instead of capturing the raw
    1763              :             // iterator (which dangles if an interleaved Add*/Remove* reallocates the vector).
    1764            4 :             return mDelegate->SyncNode(nodeId, *it, [this, listId, nodeId, endpointId]() {
    1765            2 :                 mEndpointBindingEntries.erase(std::remove_if(mEndpointBindingEntries.begin(), mEndpointBindingEntries.end(),
    1766           14 :                                                              [&](const auto & entry) {
    1767           16 :                                                                  return entry.nodeID == nodeId && entry.listID == listId &&
    1768           16 :                                                                      entry.endpointID == endpointId;
    1769              :                                                              }),
    1770            2 :                                               mEndpointBindingEntries.end());
    1771            2 :             });
    1772              :         }
    1773              :     }
    1774              : 
    1775            0 :     return CHIP_ERROR_NOT_FOUND;
    1776              : }
    1777              : 
    1778            0 : bool JointFabricDatastore::ACLTargetMatches(
    1779              :     const Clusters::JointFabricDatastore::Structs::DatastoreAccessControlTargetStruct::Type & target1,
    1780              :     const Clusters::JointFabricDatastore::Structs::DatastoreAccessControlTargetStruct::Type & target2)
    1781              : {
    1782            0 :     if (!target1.cluster.IsNull() && !target2.cluster.IsNull())
    1783              :     {
    1784            0 :         if (target1.cluster.Value() != target2.cluster.Value())
    1785              :         {
    1786            0 :             return false;
    1787              :         }
    1788              :     }
    1789            0 :     else if (!target1.cluster.IsNull() || !target2.cluster.IsNull())
    1790              :     {
    1791            0 :         return false;
    1792              :     }
    1793              : 
    1794            0 :     if (!target1.endpoint.IsNull() && !target2.endpoint.IsNull())
    1795              :     {
    1796            0 :         if (target1.endpoint.Value() != target2.endpoint.Value())
    1797              :         {
    1798            0 :             return false;
    1799              :         }
    1800              :     }
    1801            0 :     else if (!target1.endpoint.IsNull() || !target2.endpoint.IsNull())
    1802              :     {
    1803            0 :         return false;
    1804              :     }
    1805              : 
    1806            0 :     if (!target1.deviceType.IsNull() && !target2.deviceType.IsNull())
    1807              :     {
    1808            0 :         if (target1.deviceType.Value() != target2.deviceType.Value())
    1809              :         {
    1810            0 :             return false;
    1811              :         }
    1812              :     }
    1813            0 :     else if (!target1.deviceType.IsNull() || !target2.deviceType.IsNull())
    1814              :     {
    1815            0 :         return false;
    1816              :     }
    1817              : 
    1818            0 :     return true;
    1819              : }
    1820              : 
    1821            1 : bool JointFabricDatastore::ACLMatches(
    1822              :     const datastore::AccessControlEntryStruct & acl1,
    1823              :     const Clusters::JointFabricDatastore::Structs::DatastoreAccessControlEntryStruct::DecodableType & acl2)
    1824              : {
    1825            1 :     if (acl1.privilege != acl2.privilege)
    1826              :     {
    1827            0 :         return false;
    1828              :     }
    1829              : 
    1830            1 :     if (acl1.authMode != acl2.authMode)
    1831              :     {
    1832            0 :         return false;
    1833              :     }
    1834              : 
    1835            1 :     if (acl2.subjects.IsNull())
    1836              :     {
    1837            1 :         if (!acl1.subjects.empty())
    1838              :         {
    1839            0 :             return false;
    1840              :         }
    1841              :     }
    1842              :     else
    1843              :     {
    1844            0 :         auto it1 = acl1.subjects.begin();
    1845            0 :         auto it2 = acl2.subjects.Value().begin();
    1846              : 
    1847            0 :         while (it1 != acl1.subjects.end() && it2.Next())
    1848              :         {
    1849            0 :             if (*it1 != it2.GetValue())
    1850              :             {
    1851            0 :                 return false;
    1852              :             }
    1853            0 :             ++it1;
    1854              :         }
    1855              : 
    1856            0 :         if (it1 != acl1.subjects.end() || it2.Next())
    1857              :         {
    1858            0 :             return false;
    1859              :         }
    1860              :     }
    1861              : 
    1862            1 :     if (acl2.targets.IsNull())
    1863              :     {
    1864            1 :         if (!acl1.targets.empty())
    1865              :         {
    1866            0 :             return false;
    1867              :         }
    1868              :     }
    1869              :     else
    1870              :     {
    1871            0 :         auto it1 = acl1.targets.begin();
    1872            0 :         auto it2 = acl2.targets.Value().begin();
    1873              : 
    1874            0 :         while (it1 != acl1.targets.end() && it2.Next())
    1875              :         {
    1876            0 :             if (ACLTargetMatches(*it1, it2.GetValue()) == false)
    1877              :             {
    1878            0 :                 return false;
    1879              :             }
    1880            0 :             ++it1;
    1881              :         }
    1882              : 
    1883            0 :         if (it1 != acl1.targets.end() || it2.Next())
    1884              :         {
    1885            0 :             return false;
    1886              :         }
    1887              :     }
    1888              : 
    1889            1 :     return true;
    1890              : }
    1891              : 
    1892              : CHIP_ERROR
    1893           16 : JointFabricDatastore::AddACLToNode(
    1894              :     NodeId nodeId, const Clusters::JointFabricDatastore::Structs::DatastoreAccessControlEntryStruct::DecodableType & aclEntry)
    1895              : {
    1896           16 :     VerifyOrReturnError(mDelegate != nullptr, CHIP_ERROR_INCORRECT_STATE);
    1897              : 
    1898           16 :     size_t index = 0;
    1899           16 :     ReturnErrorOnFailure(IsNodeIdInNodeInformationEntries(nodeId, index));
    1900              : 
    1901              :     // Check if the ACL entry already exists for the node
    1902           94 :     for (auto & entry : mACLEntries)
    1903              :     {
    1904           79 :         if (entry.nodeID == nodeId)
    1905              :         {
    1906            1 :             if (ACLMatches(entry.ACLEntry, aclEntry))
    1907              :             {
    1908            1 :                 return CHIP_NO_ERROR;
    1909              :             }
    1910              :         }
    1911              :     }
    1912           15 :     VerifyOrReturnError(mACLEntries.size() < kMaxACLs, CHIP_ERROR_NO_MEMORY);
    1913              :     // Create a new ACL entry
    1914           15 :     datastore::ACLEntryStruct newACLEntry;
    1915           15 :     newACLEntry.nodeID             = nodeId;
    1916           15 :     newACLEntry.ACLEntry.privilege = aclEntry.privilege;
    1917           15 :     newACLEntry.ACLEntry.authMode  = aclEntry.authMode;
    1918              : 
    1919           15 :     newACLEntry.statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kPending;
    1920              : 
    1921           15 :     if (!aclEntry.subjects.IsNull())
    1922              :     {
    1923            0 :         auto iter = aclEntry.subjects.Value().begin();
    1924            0 :         while (iter.Next())
    1925              :         {
    1926            0 :             newACLEntry.ACLEntry.subjects.push_back(iter.GetValue());
    1927              :         }
    1928            0 :         ReturnErrorOnFailure(iter.GetStatus());
    1929              :     }
    1930              : 
    1931           15 :     if (!aclEntry.targets.IsNull())
    1932              :     {
    1933            0 :         auto iter = aclEntry.targets.Value().begin();
    1934            0 :         while (iter.Next())
    1935              :         {
    1936            0 :             newACLEntry.ACLEntry.targets.push_back(iter.GetValue());
    1937              :         }
    1938            0 :         ReturnErrorOnFailure(iter.GetStatus());
    1939              :     }
    1940              : 
    1941           15 :     ReturnErrorOnFailure(GenerateAndAssignAUniqueListID(newACLEntry.listID));
    1942              : 
    1943              :     // Add the new ACL entry to the datastore
    1944           15 :     mACLEntries.push_back(newACLEntry);
    1945           15 :     const auto & storedEntry = mACLEntries.back();
    1946              : 
    1947           15 :     Clusters::JointFabricDatastore::Structs::DatastoreACLEntryStruct::Type entryToEncode;
    1948           15 :     entryToEncode.nodeID             = storedEntry.nodeID;
    1949           15 :     entryToEncode.listID             = storedEntry.listID;
    1950           15 :     entryToEncode.ACLEntry.authMode  = storedEntry.ACLEntry.authMode;
    1951           15 :     entryToEncode.ACLEntry.privilege = storedEntry.ACLEntry.privilege;
    1952           15 :     entryToEncode.ACLEntry.subjects =
    1953           15 :         DataModel::List<const uint64_t>(storedEntry.ACLEntry.subjects.data(), storedEntry.ACLEntry.subjects.size());
    1954           15 :     entryToEncode.ACLEntry.targets =
    1955           15 :         DataModel::List<const Clusters::JointFabricDatastore::Structs::DatastoreAccessControlTargetStruct::Type>(
    1956              :             storedEntry.ACLEntry.targets.data(), storedEntry.ACLEntry.targets.size());
    1957           15 :     entryToEncode.statusEntry = storedEntry.statusEntry;
    1958              : 
    1959           15 :     const auto committedNodeId = storedEntry.nodeID;
    1960           15 :     const auto committedListId = storedEntry.listID;
    1961              : 
    1962           30 :     return mDelegate->SyncNode(nodeId, entryToEncode, [this, committedNodeId, committedListId]() {
    1963           93 :         for (auto & entry : mACLEntries)
    1964              :         {
    1965           93 :             if (entry.nodeID == committedNodeId && entry.listID == committedListId)
    1966              :             {
    1967           15 :                 entry.statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kCommitted;
    1968           15 :                 break;
    1969              :             }
    1970              :         }
    1971           15 :     });
    1972           15 : }
    1973              : 
    1974            2 : CHIP_ERROR JointFabricDatastore::RemoveACLFromNode(uint16_t listId, NodeId nodeId)
    1975              : {
    1976            2 :     VerifyOrReturnError(mDelegate != nullptr, CHIP_ERROR_INCORRECT_STATE);
    1977              : 
    1978            2 :     size_t index = 0;
    1979            2 :     ReturnErrorOnFailure(IsNodeIdInNodeInformationEntries(nodeId, index));
    1980              : 
    1981            2 :     for (auto it = mACLEntries.begin(); it != mACLEntries.end(); ++it)
    1982              :     {
    1983            2 :         if (it->nodeID == nodeId && it->listID == listId)
    1984              :         {
    1985            2 :             it->statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kDeletePending;
    1986              : 
    1987              :             // initialize struct to indicate nodeid/listid and status set to DeletePending for the SyncNode call to delete the ACL
    1988              :             // entry on the node
    1989            2 :             Clusters::JointFabricDatastore::Structs::DatastoreACLEntryStruct::Type entryToDelete{ 0 };
    1990            2 :             entryToDelete.nodeID            = it->nodeID;
    1991            2 :             entryToDelete.listID            = it->listID;
    1992            2 :             entryToDelete.statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kDeletePending;
    1993              :             // Re-resolve by stable key inside the async completion instead of capturing the raw
    1994              :             // iterator (which dangles if an interleaved Add*/Remove* reallocates the vector).
    1995            4 :             return mDelegate->SyncNode(nodeId, entryToDelete, [this, listId, nodeId]() {
    1996            4 :                 mACLEntries.erase(
    1997            2 :                     std::remove_if(mACLEntries.begin(), mACLEntries.end(),
    1998           14 :                                    [&](const auto & entry) { return entry.nodeID == nodeId && entry.listID == listId; }),
    1999            2 :                     mACLEntries.end());
    2000            2 :             });
    2001              :         }
    2002              :     }
    2003              : 
    2004            0 :     return CHIP_ERROR_NOT_FOUND;
    2005              : }
    2006              : 
    2007            1 : CHIP_ERROR JointFabricDatastore::AddNodeKeySetEntry(GroupId groupId, uint16_t groupKeySetId)
    2008              : {
    2009            1 :     VerifyOrReturnError(mDelegate != nullptr, CHIP_ERROR_INCORRECT_STATE);
    2010              : 
    2011              :     // Find all nodes that are members of this group
    2012            1 :     std::unordered_set<NodeId> nodesInGroup;
    2013            1 :     for (const auto & entry : mEndpointGroupIDEntries)
    2014              :     {
    2015            0 :         if (entry.groupID == groupId)
    2016              :         {
    2017            0 :             nodesInGroup.insert(entry.nodeID);
    2018              :         }
    2019              :     }
    2020              : 
    2021            1 :     if (!nodesInGroup.empty())
    2022              :     {
    2023            0 :         for (const auto nodeId : nodesInGroup)
    2024              :         {
    2025              :             // Skip if a matching NodeKeySet entry already exists for this node
    2026            0 :             bool exists = false;
    2027            0 :             for (const auto & nkse : mNodeKeySetEntries)
    2028              :             {
    2029            0 :                 if (nkse.nodeID == nodeId && nkse.groupKeySetID == groupKeySetId)
    2030              :                 {
    2031            0 :                     exists = true;
    2032            0 :                     break;
    2033              :                 }
    2034              :             }
    2035            0 :             if (exists)
    2036              :             {
    2037            0 :                 continue;
    2038              :             }
    2039              : 
    2040            0 :             Clusters::JointFabricDatastore::Structs::DatastoreNodeKeySetEntryStruct::Type newEntry;
    2041            0 :             newEntry.nodeID            = nodeId;
    2042            0 :             newEntry.groupKeySetID     = groupKeySetId;
    2043            0 :             newEntry.statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kPending;
    2044              : 
    2045            0 :             mNodeKeySetEntries.push_back(newEntry);
    2046              : 
    2047              :             // Sync to the node and mark committed on success. Re-resolve by stable key inside the
    2048              :             // completion; capturing the index would mark the wrong/invalid slot if an interleaved
    2049              :             // Invoke mutated the vector before the async completion fires.
    2050            0 :             ReturnErrorOnFailure(mDelegate->SyncNode(nodeId, newEntry, [this, nodeId, groupKeySetId]() {
    2051              :                 detail::MarkEntryCommittedIfFound(
    2052              :                     mNodeKeySetEntries, [&](const auto & e) { return e.nodeID == nodeId && e.groupKeySetID == groupKeySetId; });
    2053              :             }));
    2054              :         }
    2055              :     }
    2056              : 
    2057            1 :     return CHIP_NO_ERROR;
    2058            1 : }
    2059              : 
    2060            0 : CHIP_ERROR JointFabricDatastore::RemoveNodeKeySetEntry(GroupId groupId, uint16_t groupKeySetId)
    2061              : {
    2062              :     // NOTE: this method assumes its ok to remove the keyset from each node (its not in use by any group)
    2063              : 
    2064              :     // Find all nodes that are members of this group
    2065            0 :     std::unordered_set<NodeId> nodesInGroup;
    2066            0 :     for (const auto & entry : mEndpointGroupIDEntries)
    2067              :     {
    2068            0 :         if (entry.groupID == groupId)
    2069              :         {
    2070            0 :             nodesInGroup.insert(entry.nodeID);
    2071              :         }
    2072              :     }
    2073              : 
    2074            0 :     for (auto it = mNodeKeySetEntries.begin(); it != mNodeKeySetEntries.end(); ++it)
    2075              :     {
    2076            0 :         for (const auto & nodeId : nodesInGroup)
    2077              :         {
    2078            0 :             if (it->nodeID == nodeId && it->groupKeySetID == groupKeySetId)
    2079              :             {
    2080              :                 // zero-initialized struct to indicate deletion for the SyncNode call
    2081            0 :                 Clusters::JointFabricDatastore::Structs::DatastoreNodeKeySetEntryStruct::Type nullEntry{ 0 };
    2082              : 
    2083            0 :                 auto nodeIdToErase        = it->nodeID;
    2084            0 :                 auto groupKeySetIdToErase = it->groupKeySetID;
    2085            0 :                 ReturnErrorOnFailure(mDelegate->SyncNode(nodeId, nullEntry, [this, nodeIdToErase, groupKeySetIdToErase]() {
    2086              :                     mNodeKeySetEntries.erase(std::remove_if(mNodeKeySetEntries.begin(), mNodeKeySetEntries.end(),
    2087              :                                                             [&](const auto & entry) {
    2088              :                                                                 return entry.nodeID == nodeIdToErase &&
    2089              :                                                                     entry.groupKeySetID == groupKeySetIdToErase;
    2090              :                                                             }),
    2091              :                                              mNodeKeySetEntries.end());
    2092              :                 }));
    2093              : 
    2094            0 :                 return CHIP_NO_ERROR;
    2095              :             }
    2096              :         }
    2097              :     }
    2098              : 
    2099            0 :     return CHIP_ERROR_NOT_FOUND;
    2100            0 : }
    2101              : 
    2102            0 : CHIP_ERROR JointFabricDatastore::TestAddNodeKeySetEntry(GroupId groupId, uint16_t groupKeySetId, NodeId nodeId)
    2103              : {
    2104            0 :     VerifyOrReturnError(mDelegate != nullptr, CHIP_ERROR_INCORRECT_STATE);
    2105              : 
    2106            0 :     Clusters::JointFabricDatastore::Structs::DatastoreNodeKeySetEntryStruct::Type newEntry;
    2107            0 :     newEntry.nodeID            = nodeId;
    2108            0 :     newEntry.groupKeySetID     = groupKeySetId;
    2109            0 :     newEntry.statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kPending;
    2110              : 
    2111            0 :     mNodeKeySetEntries.push_back(newEntry);
    2112              : 
    2113              :     // Sync to the node and mark committed on success. Re-resolve by stable key inside the completion
    2114              :     // rather than capturing the index, which an interleaved Invoke could invalidate.
    2115            0 :     return mDelegate->SyncNode(nodeId, newEntry, [this, nodeId, groupKeySetId]() {
    2116            0 :         detail::MarkEntryCommittedIfFound(mNodeKeySetEntries,
    2117            0 :                                           [&](const auto & e) { return e.nodeID == nodeId && e.groupKeySetID == groupKeySetId; });
    2118            0 :     });
    2119              : }
    2120              : 
    2121            7 : CHIP_ERROR JointFabricDatastore::TestAddEndpointEntry(EndpointId endpointId, NodeId nodeId, CharSpan friendlyName)
    2122              : {
    2123            7 :     Clusters::JointFabricDatastore::Structs::DatastoreEndpointEntryStruct::Type newEntry;
    2124            7 :     newEntry.nodeID     = nodeId;
    2125            7 :     newEntry.endpointID = endpointId;
    2126            7 :     SetEndpointFriendlyNameWithOwnedStorage(nodeId, endpointId, friendlyName, newEntry);
    2127              : 
    2128            7 :     mEndpointEntries.push_back(newEntry);
    2129              : 
    2130            7 :     return CHIP_NO_ERROR;
    2131              : }
    2132              : 
    2133            0 : CHIP_ERROR JointFabricDatastore::ForceAddNodeKeySetEntry(uint16_t groupKeySetId, NodeId nodeId)
    2134              : {
    2135            0 :     Clusters::JointFabricDatastore::Structs::DatastoreNodeKeySetEntryStruct::Type newEntry;
    2136            0 :     newEntry.nodeID            = nodeId;
    2137            0 :     newEntry.groupKeySetID     = groupKeySetId;
    2138            0 :     newEntry.statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kCommitted;
    2139              : 
    2140            0 :     mNodeKeySetEntries.push_back(newEntry);
    2141            0 :     return CHIP_NO_ERROR;
    2142              : }
    2143              : 
    2144              : } // namespace app
    2145              : } // namespace chip
        

Generated by: LCOV version 2.0-1