Matter SDK Coverage Report
Current view: top level - app/server - JointFabricDatastore.cpp (source / functions) Coverage Total Hit
Test: SHA:f1767a8b0a3778fdf31b1d979afbdf544892fd94 Lines: 45.8 % 791 362
Test Date: 2026-06-03 07:35:21 Functions: 49.0 % 98 48

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

Generated by: LCOV version 2.0-1