Matter SDK Coverage Report
Current view: top level - app/server - JointFabricDatastore.cpp (source / functions) Coverage Total Hit
Test: SHA:3f9cd168e84cd831b7699126f5296f5c5498690f Lines: 52.7 % 733 386
Test Date: 2026-04-27 19:52:19 Functions: 57.9 % 95 55

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

Generated by: LCOV version 2.0-1