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

Generated by: LCOV version 2.0-1