Matter SDK Coverage Report
Current view: top level - app/server - JointFabricDatastore.cpp (source / functions) Coverage Total Hit
Test: SHA:e021a368d10ac6f3f201c101585146211fdcdaa2 Lines: 49.7 % 682 339
Test Date: 2026-02-13 08:13:38 Functions: 53.9 % 89 48

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

Generated by: LCOV version 2.0-1