Matter SDK Coverage Report
Current view: top level - app/server - JointFabricDatastore.cpp (source / functions) Coverage Total Hit
Test: SHA:2a48c1efeab1c0f76f3adb3a0940b0f7de706453 Lines: 50.4 % 653 329
Test Date: 2026-01-31 08:14:20 Functions: 55.2 % 87 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_ERROR_INVALID_ARGUMENT);
     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 :     for (auto it = mGroupKeySetList.begin(); it != mGroupKeySetList.end(); ++it)
     888              :     {
     889            1 :         if (it->groupKeySetID == groupKeySetId)
     890              :         {
     891            1 :             mGroupKeySetList.erase(it);
     892            1 :             return CHIP_NO_ERROR;
     893              :         }
     894              :     }
     895              : 
     896            0 :     return CHIP_ERROR_NOT_FOUND;
     897              : }
     898              : 
     899              : CHIP_ERROR
     900            1 : JointFabricDatastore::UpdateGroupKeySetEntry(
     901              :     Clusters::JointFabricDatastore::Structs::DatastoreGroupKeySetStruct::Type & groupKeySet)
     902              : {
     903            1 :     for (auto & entry : mGroupKeySetList)
     904              :     {
     905            1 :         if (entry.groupKeySetID == groupKeySet.groupKeySetID)
     906              :         {
     907            1 :             entry = groupKeySet;
     908              : 
     909            1 :             ReturnErrorOnFailure(UpdateNodeKeySetList(entry));
     910              : 
     911            1 :             return CHIP_NO_ERROR;
     912              :         }
     913              :     }
     914              : 
     915            0 :     return CHIP_ERROR_NOT_FOUND;
     916              : }
     917              : 
     918              : CHIP_ERROR
     919            2 : JointFabricDatastore::AddAdmin(
     920              :     Clusters::JointFabricDatastore::Structs::DatastoreAdministratorInformationEntryStruct::Type & adminId)
     921              : {
     922            2 :     VerifyOrReturnError(IsAdminEntryPresent(adminId.nodeID) == false, CHIP_ERROR_INVALID_ARGUMENT);
     923            2 :     VerifyOrReturnError(mAdminEntries.size() < kMaxAdminNodes, CHIP_ERROR_NO_MEMORY);
     924              : 
     925            2 :     mAdminEntries.push_back(adminId);
     926              : 
     927            2 :     return CHIP_NO_ERROR;
     928              : }
     929              : 
     930            2 : bool JointFabricDatastore::IsAdminEntryPresent(NodeId nodeId)
     931              : {
     932            2 :     for (auto & entry : mAdminEntries)
     933              :     {
     934            0 :         if (entry.nodeID == nodeId)
     935              :         {
     936            0 :             return true;
     937              :         }
     938              :     }
     939              : 
     940            2 :     return false;
     941              : }
     942              : 
     943            1 : CHIP_ERROR JointFabricDatastore::UpdateAdmin(NodeId nodeId, CharSpan friendlyName, ByteSpan icac)
     944              : {
     945            1 :     for (auto & entry : mAdminEntries)
     946              :     {
     947            1 :         if (entry.nodeID == nodeId)
     948              :         {
     949            1 :             entry.friendlyName = friendlyName;
     950            1 :             entry.icac         = icac;
     951            1 :             return CHIP_NO_ERROR;
     952              :         }
     953              :     }
     954              : 
     955            0 :     return CHIP_ERROR_NOT_FOUND;
     956              : }
     957              : 
     958            1 : CHIP_ERROR JointFabricDatastore::RemoveAdmin(NodeId nodeId)
     959              : {
     960            1 :     for (auto it = mAdminEntries.begin(); it != mAdminEntries.end(); ++it)
     961              :     {
     962            1 :         if (it->nodeID == nodeId)
     963              :         {
     964            1 :             mAdminEntries.erase(it);
     965            1 :             return CHIP_NO_ERROR;
     966              :         }
     967              :     }
     968              : 
     969            0 :     return CHIP_ERROR_NOT_FOUND;
     970              : }
     971              : 
     972              : CHIP_ERROR
     973            1 : JointFabricDatastore::UpdateNodeKeySetList(Clusters::JointFabricDatastore::Structs::DatastoreGroupKeySetStruct::Type & groupKeySet)
     974              : {
     975            1 :     for (size_t i = 0; i < mNodeKeySetEntries.size(); ++i)
     976              :     {
     977            1 :         auto & entry = mNodeKeySetEntries[i];
     978            1 :         if (entry.groupKeySetID == groupKeySet.groupKeySetID)
     979              :         {
     980            1 :             size_t index = i;
     981            2 :             ReturnErrorOnFailure(mDelegate->SyncNode(entry.nodeID, groupKeySet, [this, index]() {
     982              :                 mNodeKeySetEntries[index].statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kCommitted;
     983              :             }));
     984              : 
     985            1 :             return CHIP_NO_ERROR;
     986              :         }
     987              :     }
     988              : 
     989            0 :     return CHIP_ERROR_NOT_FOUND;
     990              : }
     991              : 
     992            0 : CHIP_ERROR JointFabricDatastore::RemoveKeySet(uint16_t groupKeySetId)
     993              : {
     994            0 :     for (auto it = mNodeKeySetEntries.begin(); it != mNodeKeySetEntries.end(); ++it)
     995              :     {
     996            0 :         if (it->groupKeySetID == groupKeySetId)
     997              :         {
     998            0 :             if (it->statusEntry.state != Clusters::JointFabricDatastore::DatastoreStateEnum::kDeletePending)
     999              :             {
    1000            0 :                 return CHIP_IM_GLOBAL_STATUS(ConstraintError); // Cannot remove a key set that is not pending
    1001              :             }
    1002              : 
    1003            0 :             ReturnErrorOnFailure(RemoveGroupKeySetEntry(groupKeySetId));
    1004              : 
    1005            0 :             return CHIP_NO_ERROR;
    1006              :         }
    1007              :     }
    1008              : 
    1009            0 :     return CHIP_ERROR_NOT_FOUND;
    1010              : }
    1011              : 
    1012            4 : CHIP_ERROR JointFabricDatastore::AddGroup(const Clusters::JointFabricDatastore::Commands::AddGroup::DecodableType & commandData)
    1013              : {
    1014            4 :     size_t index = 0;
    1015              :     // Check if the group ID already exists in the datastore
    1016            8 :     VerifyOrReturnError(IsGroupIDInDatastore(commandData.groupID, index) == CHIP_ERROR_NOT_FOUND,
    1017              :                         CHIP_IM_GLOBAL_STATUS(ConstraintError));
    1018              : 
    1019            4 :     if (commandData.groupCAT.ValueOr(0) == kAdminCATIdentifier || commandData.groupCAT.ValueOr(0) == kAnchorCATIdentifier)
    1020              :     {
    1021              :         // If the group is an AdminCAT or AnchorCAT, we cannot add it
    1022            0 :         return CHIP_IM_GLOBAL_STATUS(ConstraintError);
    1023              :     }
    1024              : 
    1025            4 :     Clusters::JointFabricDatastore::Structs::DatastoreGroupInformationEntryStruct::Type groupEntry;
    1026            4 :     groupEntry.groupID         = commandData.groupID;
    1027            4 :     groupEntry.friendlyName    = commandData.friendlyName;
    1028            4 :     groupEntry.groupKeySetID   = commandData.groupKeySetID;
    1029            4 :     groupEntry.groupCAT        = commandData.groupCAT;
    1030            4 :     groupEntry.groupCATVersion = commandData.groupCATVersion;
    1031            4 :     groupEntry.groupPermission = commandData.groupPermission;
    1032              : 
    1033              :     // Add the group entry to the datastore
    1034            4 :     mGroupInformationEntries.push_back(groupEntry);
    1035              : 
    1036            4 :     return CHIP_NO_ERROR;
    1037              : }
    1038              : 
    1039              : CHIP_ERROR
    1040            1 : JointFabricDatastore::UpdateGroup(const Clusters::JointFabricDatastore::Commands::UpdateGroup::DecodableType & commandData)
    1041              : {
    1042            1 :     VerifyOrReturnError(mDelegate != nullptr, CHIP_ERROR_INCORRECT_STATE);
    1043              : 
    1044            1 :     size_t index = 0;
    1045              :     // Check if the group ID exists in the datastore
    1046            2 :     VerifyOrReturnError(IsGroupIDInDatastore(commandData.groupID, index) == CHIP_NO_ERROR, CHIP_IM_GLOBAL_STATUS(ConstraintError));
    1047              : 
    1048            1 :     if (commandData.groupCAT.ValueOr(0) == kAdminCATIdentifier || commandData.groupCAT.ValueOr(0) == kAnchorCATIdentifier)
    1049              :     {
    1050              :         // If the group is an AdminCAT or AnchorCAT, we cannot update it
    1051            0 :         return CHIP_IM_GLOBAL_STATUS(ConstraintError);
    1052              :     }
    1053              : 
    1054              :     // Update the group entry with the new data
    1055            1 :     if (commandData.friendlyName.IsNull() == false)
    1056              :     {
    1057            0 :         if (mGroupInformationEntries[index].friendlyName.data_equal(commandData.friendlyName.Value()) == false)
    1058              :         {
    1059              :             // Friendly name changed. For every endpoint that references this group, mark the endpoint's
    1060              :             // GroupIDList entry as pending and attempt to push the change to the node. If the push
    1061              :             // fails, leave the entry as pending so a subsequent Refresh can apply it.
    1062            0 :             const GroupId updatedGroupId = commandData.groupID;
    1063            0 :             for (size_t i = 0; i < mEndpointGroupIDEntries.size(); ++i)
    1064              :             {
    1065            0 :                 auto & epGroupEntry = mEndpointGroupIDEntries[i];
    1066            0 :                 if (epGroupEntry.groupID == updatedGroupId)
    1067              :                 {
    1068            0 :                     epGroupEntry.statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kPending;
    1069              : 
    1070              :                     // Make a copy to send to the node. Do not fail the entire UpdateGroup if SyncNode
    1071              :                     // returns an error; leave the entry pending for a later refresh per spec.
    1072            0 :                     auto entryToSync = epGroupEntry;
    1073              : 
    1074            0 :                     CHIP_ERROR syncErr = mDelegate->SyncNode(epGroupEntry.nodeID, entryToSync, [this, i]() {
    1075            0 :                         mEndpointGroupIDEntries[i].statusEntry.state =
    1076              :                             Clusters::JointFabricDatastore::DatastoreStateEnum::kCommitted;
    1077            0 :                     });
    1078              : 
    1079            0 :                     if (syncErr != CHIP_NO_ERROR)
    1080              :                     {
    1081            0 :                         ChipLogError(DataManagement, "Failed to sync node for group friendly name update, leaving as pending: %s",
    1082              :                                      ErrorStr(syncErr));
    1083              :                     }
    1084              :                 }
    1085              :             }
    1086              : 
    1087              :             // Update the friendly name in the datastore
    1088            0 :             mGroupInformationEntries[index].friendlyName = commandData.friendlyName.Value();
    1089              :         }
    1090              :     }
    1091            1 :     if (commandData.groupKeySetID.IsNull() == false)
    1092              :     {
    1093            0 :         if (mGroupInformationEntries[index].groupKeySetID != commandData.groupKeySetID.Value())
    1094              :         {
    1095              :             // If the groupKeySetID is being updated, we need to ensure that the new key set exists
    1096            0 :             ReturnErrorOnFailure(AddNodeKeySetEntry(commandData.groupID, commandData.groupKeySetID.Value()));
    1097            0 :             ReturnErrorOnFailure(RemoveNodeKeySetEntry(
    1098              :                 commandData.groupID, mGroupInformationEntries[index].groupKeySetID.Value())); // Remove the old key set
    1099              :         }
    1100            0 :         mGroupInformationEntries[index].groupKeySetID = commandData.groupKeySetID.Value();
    1101              :     }
    1102              : 
    1103            1 :     bool anyGroupCATFieldUpdated = false;
    1104              : 
    1105            1 :     if (commandData.groupCAT.IsNull() == false)
    1106              :     {
    1107            0 :         if (mGroupInformationEntries[index].groupCAT != commandData.groupCAT.Value())
    1108              :         {
    1109            0 :             anyGroupCATFieldUpdated = true;
    1110              :         }
    1111              :         // Update the groupCAT
    1112            0 :         mGroupInformationEntries[index].groupCAT = commandData.groupCAT.Value();
    1113              :     }
    1114            1 :     if (commandData.groupCATVersion.IsNull() == false)
    1115              :     {
    1116            0 :         if (mGroupInformationEntries[index].groupCATVersion != commandData.groupCATVersion.Value())
    1117              :         {
    1118            0 :             anyGroupCATFieldUpdated = true;
    1119              :         }
    1120            0 :         mGroupInformationEntries[index].groupCATVersion = commandData.groupCATVersion.Value();
    1121              :     }
    1122            1 :     if (commandData.groupPermission != Clusters::JointFabricDatastore::DatastoreAccessControlEntryPrivilegeEnum::kUnknownEnumValue)
    1123              :     {
    1124            0 :         if (mGroupInformationEntries[index].groupPermission != commandData.groupPermission)
    1125              :         {
    1126            0 :             anyGroupCATFieldUpdated = true;
    1127              :         }
    1128              :         // If the groupPermission is not set to kUnknownEnumValue, update it
    1129            0 :         mGroupInformationEntries[index].groupPermission = commandData.groupPermission;
    1130              :     }
    1131              : 
    1132            1 :     if (anyGroupCATFieldUpdated)
    1133              :     {
    1134            0 :         const GroupId updatedGroupId = commandData.groupID;
    1135              : 
    1136            0 :         for (size_t i = 0; i < mACLEntries.size(); ++i)
    1137              :         {
    1138            0 :             auto & acl = mACLEntries[i];
    1139              : 
    1140              :             // Determine if this ACL entry references the updated group
    1141            0 :             bool referencesGroup = false;
    1142            0 :             for (const auto & subject : acl.ACLEntry.subjects)
    1143              :             {
    1144              :                 // If the target has a group field and it matches the updated group, mark for update.
    1145              :                 // Use IsNull() to match other usages in this file.
    1146            0 :                 if (subject == static_cast<uint64_t>(updatedGroupId))
    1147              :                 {
    1148            0 :                     referencesGroup = true;
    1149            0 :                     break;
    1150              :                 }
    1151              :             }
    1152              : 
    1153            0 :             if (!referencesGroup)
    1154              :             {
    1155            0 :                 continue;
    1156              :             }
    1157              : 
    1158              :             // Update the ACL entry in the datastore to reflect the new group permission and mark Pending.
    1159            0 :             acl.ACLEntry.privilege = mGroupInformationEntries[index].groupPermission;
    1160            0 :             acl.statusEntry.state  = Clusters::JointFabricDatastore::DatastoreStateEnum::kPending;
    1161              : 
    1162              :             // Prepare an encoded entry to send to the node.
    1163            0 :             Clusters::JointFabricDatastore::Structs::DatastoreACLEntryStruct::Type entryToEncode;
    1164            0 :             entryToEncode.nodeID             = acl.nodeID;
    1165            0 :             entryToEncode.listID             = acl.listID;
    1166            0 :             entryToEncode.ACLEntry.authMode  = acl.ACLEntry.authMode;
    1167            0 :             entryToEncode.ACLEntry.privilege = acl.ACLEntry.privilege;
    1168            0 :             entryToEncode.ACLEntry.subjects =
    1169            0 :                 DataModel::List<const uint64_t>(acl.ACLEntry.subjects.data(), acl.ACLEntry.subjects.size());
    1170            0 :             entryToEncode.ACLEntry.targets =
    1171            0 :                 DataModel::List<const Clusters::JointFabricDatastore::Structs::DatastoreAccessControlTargetStruct::Type>(
    1172            0 :                     acl.ACLEntry.targets.data(), acl.ACLEntry.targets.size());
    1173            0 :             entryToEncode.statusEntry = acl.statusEntry;
    1174              : 
    1175              :             // Attempt to update the ACL on the node. On success, mark the ACL entry as Committed.
    1176              :             // Capture index 'i' to safely identify the entry inside the callback.
    1177            0 :             ReturnErrorOnFailure(mDelegate->SyncNode(acl.nodeID, entryToEncode, [this, i]() {
    1178              :                 if (i < mACLEntries.size())
    1179              :                 {
    1180              :                     mACLEntries[i].statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kCommitted;
    1181              :                 }
    1182              :             }));
    1183              :         }
    1184              :     }
    1185              : 
    1186            1 :     return CHIP_NO_ERROR;
    1187              : }
    1188              : 
    1189              : CHIP_ERROR
    1190            1 : JointFabricDatastore::RemoveGroup(const Clusters::JointFabricDatastore::Commands::RemoveGroup::DecodableType & commandData)
    1191              : {
    1192            1 :     size_t index = 0;
    1193              :     // Check if the group ID exists in the datastore
    1194            2 :     VerifyOrReturnError(IsGroupIDInDatastore(commandData.groupID, index) == CHIP_NO_ERROR, CHIP_IM_GLOBAL_STATUS(ConstraintError));
    1195              : 
    1196              :     // Remove the group entry from the datastore
    1197            1 :     auto it = mGroupInformationEntries.begin();
    1198            1 :     std::advance(it, index);
    1199              : 
    1200            1 :     if (it->groupCAT == kAdminCATIdentifier || it->groupCAT == kAnchorCATIdentifier)
    1201              :     {
    1202              :         // If the group is an AdminCAT or AnchorCAT, we cannot remove it
    1203            0 :         return CHIP_IM_GLOBAL_STATUS(ConstraintError);
    1204              :     }
    1205              : 
    1206            1 :     mGroupInformationEntries.erase(it);
    1207              : 
    1208            1 :     return CHIP_NO_ERROR;
    1209              : }
    1210              : 
    1211            8 : CHIP_ERROR JointFabricDatastore::IsGroupIDInDatastore(chip::GroupId groupId, size_t & index)
    1212              : {
    1213            8 :     for (auto & entry : mGroupInformationEntries)
    1214              :     {
    1215            4 :         if (entry.groupID == groupId)
    1216              :         {
    1217            4 :             index = static_cast<size_t>(&entry - &mGroupInformationEntries[0]);
    1218            4 :             return CHIP_NO_ERROR;
    1219              :         }
    1220              :     }
    1221              : 
    1222            4 :     return CHIP_ERROR_NOT_FOUND;
    1223              : }
    1224              : 
    1225            5 : CHIP_ERROR JointFabricDatastore::IsNodeIdInNodeInformationEntries(NodeId nodeId, size_t & index)
    1226              : {
    1227            5 :     for (auto & entry : mNodeInformationEntries)
    1228              :     {
    1229            3 :         if (entry.nodeID == nodeId)
    1230              :         {
    1231            3 :             index = static_cast<size_t>(&entry - &mNodeInformationEntries[0]);
    1232            3 :             return CHIP_NO_ERROR;
    1233              :         }
    1234              :     }
    1235              : 
    1236            2 :     return CHIP_ERROR_NOT_FOUND;
    1237              : }
    1238              : 
    1239            2 : CHIP_ERROR JointFabricDatastore::UpdateEndpointForNode(NodeId nodeId, chip::EndpointId endpointId, CharSpan friendlyName)
    1240              : {
    1241            2 :     for (auto & entry : mEndpointEntries)
    1242              :     {
    1243            1 :         if (entry.nodeID == nodeId && entry.endpointID == endpointId)
    1244              :         {
    1245            1 :             entry.friendlyName = friendlyName;
    1246            1 :             return CHIP_NO_ERROR;
    1247              :         }
    1248              :     }
    1249              : 
    1250            1 :     return CHIP_ERROR_NOT_FOUND;
    1251              : }
    1252              : 
    1253            6 : CHIP_ERROR JointFabricDatastore::IsNodeIdAndEndpointInEndpointInformationEntries(NodeId nodeId, EndpointId endpointId,
    1254              :                                                                                  size_t & index)
    1255              : {
    1256            6 :     for (auto & entry : mEndpointEntries)
    1257              :     {
    1258            5 :         if (entry.nodeID == nodeId && entry.endpointID == endpointId)
    1259              :         {
    1260            5 :             index = static_cast<size_t>(&entry - &mEndpointEntries[0]);
    1261            5 :             return CHIP_NO_ERROR;
    1262              :         }
    1263              :     }
    1264              : 
    1265            1 :     return CHIP_ERROR_NOT_FOUND;
    1266              : }
    1267              : 
    1268            1 : CHIP_ERROR JointFabricDatastore::AddGroupIDToEndpointForNode(NodeId nodeId, chip::EndpointId endpointId, chip::GroupId groupId)
    1269              : {
    1270            1 :     VerifyOrReturnError(mDelegate != nullptr, CHIP_ERROR_INCORRECT_STATE);
    1271              : 
    1272            1 :     size_t index = 0;
    1273            1 :     ReturnErrorOnFailure(IsNodeIdAndEndpointInEndpointInformationEntries(nodeId, endpointId, index));
    1274              : 
    1275            2 :     VerifyOrReturnError(IsGroupIDInDatastore(groupId, index) == CHIP_NO_ERROR, CHIP_IM_GLOBAL_STATUS(ConstraintError));
    1276              : 
    1277            1 :     if (mGroupInformationEntries[index].groupKeySetID.IsNull() == false)
    1278              :     {
    1279            1 :         uint16_t groupKeySetID = mGroupInformationEntries[index].groupKeySetID.Value();
    1280              : 
    1281              :         // make sure mNodeKeySetEntries contains an entry for this keyset and node, else add one and update device
    1282            1 :         bool nodeKeySetExists = false;
    1283            1 :         for (auto & entry : mNodeKeySetEntries)
    1284              :         {
    1285            1 :             if (entry.nodeID == nodeId && entry.groupKeySetID == groupKeySetID)
    1286              :             {
    1287            1 :                 nodeKeySetExists = true;
    1288            1 :                 break; // Found the group key set, no need to add it again
    1289              :             }
    1290              :         }
    1291              : 
    1292            1 :         if (!nodeKeySetExists)
    1293              :         {
    1294              :             // Create a new group key set entry if it doesn't exist
    1295            0 :             Clusters::JointFabricDatastore::Structs::DatastoreNodeKeySetEntryStruct::Type newNodeKeySet;
    1296            0 :             newNodeKeySet.nodeID            = nodeId;
    1297            0 :             newNodeKeySet.groupKeySetID     = groupKeySetID;
    1298            0 :             newNodeKeySet.statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kPending;
    1299              : 
    1300            0 :             mNodeKeySetEntries.push_back(newNodeKeySet);
    1301              : 
    1302            0 :             ReturnErrorOnFailure(mDelegate->SyncNode(nodeId, newNodeKeySet, [this]() {
    1303              :                 mNodeKeySetEntries.back().statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kCommitted;
    1304              :             }));
    1305              :         }
    1306              :     }
    1307              : 
    1308              :     // Check if the group ID already exists for the endpoint
    1309            1 :     for (auto & entry : mEndpointGroupIDEntries)
    1310              :     {
    1311            0 :         if (entry.nodeID == nodeId && entry.endpointID == endpointId && entry.groupID == groupId)
    1312              :         {
    1313            0 :             return CHIP_NO_ERROR;
    1314              :         }
    1315              :     }
    1316              : 
    1317            1 :     VerifyOrReturnError(mEndpointGroupIDEntries.size() < kMaxGroups, CHIP_ERROR_NO_MEMORY);
    1318              : 
    1319              :     // Create a new endpoint group ID entry
    1320            1 :     Clusters::JointFabricDatastore::Structs::DatastoreEndpointGroupIDEntryStruct::Type newGroupEntry;
    1321            1 :     newGroupEntry.nodeID            = nodeId;
    1322            1 :     newGroupEntry.endpointID        = endpointId;
    1323            1 :     newGroupEntry.groupID           = groupId;
    1324            1 :     newGroupEntry.statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kPending;
    1325              : 
    1326              :     // Add the new ACL entry to the datastore
    1327            1 :     mEndpointGroupIDEntries.push_back(newGroupEntry);
    1328              : 
    1329            2 :     return mDelegate->SyncNode(nodeId, newGroupEntry, [this]() {
    1330            1 :         mEndpointGroupIDEntries.back().statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kCommitted;
    1331            1 :     });
    1332              : }
    1333              : 
    1334            1 : CHIP_ERROR JointFabricDatastore::RemoveGroupIDFromEndpointForNode(NodeId nodeId, chip::EndpointId endpointId, chip::GroupId groupId)
    1335              : {
    1336            1 :     VerifyOrReturnError(mDelegate != nullptr, CHIP_ERROR_INCORRECT_STATE);
    1337              : 
    1338            1 :     size_t index = 0;
    1339            1 :     ReturnErrorOnFailure(IsNodeIdAndEndpointInEndpointInformationEntries(nodeId, endpointId, index));
    1340              : 
    1341            1 :     for (auto it = mEndpointGroupIDEntries.begin(); it != mEndpointGroupIDEntries.end(); ++it)
    1342              :     {
    1343            1 :         if (it->nodeID == nodeId && it->endpointID == endpointId && it->groupID == groupId)
    1344              :         {
    1345            1 :             it->statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kDeletePending;
    1346              : 
    1347              :             // zero-initialized struct to indicate deletion for the SyncNode call
    1348            1 :             Clusters::JointFabricDatastore::Structs::DatastoreEndpointGroupIDEntryStruct::Type endpointGroupIdNullEntry{ 0 };
    1349              : 
    1350            2 :             ReturnErrorOnFailure(
    1351              :                 mDelegate->SyncNode(nodeId, endpointGroupIdNullEntry, [this, it]() { mEndpointGroupIDEntries.erase(it); }));
    1352              : 
    1353            2 :             if (IsGroupIDInDatastore(groupId, index) == CHIP_NO_ERROR)
    1354              :             {
    1355            2 :                 for (auto it2 = mNodeKeySetEntries.begin(); it2 != mNodeKeySetEntries.end();)
    1356              :                 {
    1357            1 :                     bool incrementIndex = true;
    1358              : 
    1359            2 :                     if (it2->nodeID == nodeId && mGroupInformationEntries[index].groupKeySetID.IsNull() == false &&
    1360            1 :                         it2->groupKeySetID == mGroupInformationEntries[index].groupKeySetID.Value())
    1361              :                     {
    1362            1 :                         it2->statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kDeletePending;
    1363              : 
    1364              :                         // zero-initialized struct to indicate deletion for the SyncNode call
    1365            1 :                         Clusters::JointFabricDatastore::Structs::DatastoreNodeKeySetEntryStruct::Type nodeKeySetNullEntry{ 0 };
    1366            2 :                         ReturnErrorOnFailure(
    1367              :                             mDelegate->SyncNode(nodeId, nodeKeySetNullEntry, [this, it2]() { mNodeKeySetEntries.erase(it2); }));
    1368              : 
    1369            1 :                         incrementIndex = false;
    1370              :                     }
    1371              : 
    1372            1 :                     if (incrementIndex)
    1373              :                     {
    1374            0 :                         ++it2;
    1375              :                     }
    1376              :                     else
    1377              :                     {
    1378            1 :                         incrementIndex = true;
    1379              :                     }
    1380              :                 }
    1381              :             }
    1382              : 
    1383            1 :             return CHIP_NO_ERROR;
    1384              :         }
    1385              :     }
    1386              : 
    1387            0 :     return CHIP_ERROR_NOT_FOUND;
    1388              : }
    1389              : 
    1390              : // look-up the highest listId used so far, from Endpoint Binding Entries and ACL Entries
    1391            4 : CHIP_ERROR JointFabricDatastore::GenerateAndAssignAUniqueListID(uint16_t & listId)
    1392              : {
    1393            4 :     uint16_t highestListID = 0;
    1394            4 :     for (auto & entry : mEndpointBindingEntries)
    1395              :     {
    1396            0 :         if (entry.listID >= highestListID)
    1397              :         {
    1398            0 :             highestListID = entry.listID + 1;
    1399              :         }
    1400              :     }
    1401            4 :     for (auto & entry : mACLEntries)
    1402              :     {
    1403            0 :         if (entry.listID >= highestListID)
    1404              :         {
    1405            0 :             highestListID = entry.listID + 1;
    1406              :         }
    1407              :     }
    1408              : 
    1409            4 :     listId = highestListID;
    1410              : 
    1411            4 :     return CHIP_NO_ERROR;
    1412              : }
    1413              : 
    1414            0 : bool JointFabricDatastore::BindingMatches(
    1415              :     const Clusters::JointFabricDatastore::Structs::DatastoreBindingTargetStruct::Type & binding1,
    1416              :     const Clusters::JointFabricDatastore::Structs::DatastoreBindingTargetStruct::Type & binding2)
    1417              : {
    1418            0 :     if (binding1.node.HasValue() && binding2.node.HasValue())
    1419              :     {
    1420            0 :         if (binding1.node.Value() != binding2.node.Value())
    1421              :         {
    1422            0 :             return false;
    1423              :         }
    1424              :     }
    1425            0 :     else if (binding1.node.HasValue() || binding2.node.HasValue())
    1426              :     {
    1427            0 :         return false;
    1428              :     }
    1429              : 
    1430            0 :     if (binding1.group.HasValue() && binding2.group.HasValue())
    1431              :     {
    1432            0 :         if (binding1.group.Value() != binding2.group.Value())
    1433              :         {
    1434            0 :             return false;
    1435              :         }
    1436              :     }
    1437            0 :     else if (binding1.group.HasValue() || binding2.group.HasValue())
    1438              :     {
    1439            0 :         return false;
    1440              :     }
    1441              : 
    1442            0 :     if (binding1.endpoint.HasValue() && binding2.endpoint.HasValue())
    1443              :     {
    1444            0 :         if (binding1.endpoint.Value() != binding2.endpoint.Value())
    1445              :         {
    1446            0 :             return false;
    1447              :         }
    1448              :     }
    1449            0 :     else if (binding1.endpoint.HasValue() || binding2.endpoint.HasValue())
    1450              :     {
    1451            0 :         return false;
    1452              :     }
    1453              : 
    1454            0 :     if (binding1.cluster.HasValue() && binding2.cluster.HasValue())
    1455              :     {
    1456            0 :         if (binding1.cluster.Value() != binding2.cluster.Value())
    1457              :         {
    1458            0 :             return false;
    1459              :         }
    1460              :     }
    1461            0 :     else if (binding1.cluster.HasValue() || binding2.cluster.HasValue())
    1462              :     {
    1463            0 :         return false;
    1464              :     }
    1465              : 
    1466            0 :     return true;
    1467              : }
    1468              : 
    1469              : CHIP_ERROR
    1470            2 : JointFabricDatastore::AddBindingToEndpointForNode(
    1471              :     NodeId nodeId, chip::EndpointId endpointId,
    1472              :     const Clusters::JointFabricDatastore::Structs::DatastoreBindingTargetStruct::Type & binding)
    1473              : {
    1474            2 :     VerifyOrReturnError(mDelegate != nullptr, CHIP_ERROR_INCORRECT_STATE);
    1475              : 
    1476            2 :     size_t index = 0;
    1477            2 :     ReturnErrorOnFailure(IsNodeIdAndEndpointInEndpointInformationEntries(nodeId, endpointId, index));
    1478              : 
    1479              :     // Check if the group ID already exists for the endpoint
    1480            2 :     for (auto & entry : mEndpointBindingEntries)
    1481              :     {
    1482            0 :         if (entry.nodeID == nodeId && entry.endpointID == endpointId)
    1483              :         {
    1484            0 :             if (BindingMatches(entry.binding, binding))
    1485              :             {
    1486            0 :                 return CHIP_NO_ERROR;
    1487              :             }
    1488              :         }
    1489              :     }
    1490              : 
    1491            2 :     VerifyOrReturnError(mEndpointBindingEntries.size() < kMaxGroups, CHIP_ERROR_NO_MEMORY);
    1492              : 
    1493              :     // Create a new binding entry
    1494            2 :     Clusters::JointFabricDatastore::Structs::DatastoreEndpointBindingEntryStruct::Type newBindingEntry;
    1495            2 :     newBindingEntry.nodeID     = nodeId;
    1496            2 :     newBindingEntry.endpointID = endpointId;
    1497            2 :     newBindingEntry.binding    = binding;
    1498            2 :     ReturnErrorOnFailure(GenerateAndAssignAUniqueListID(newBindingEntry.listID));
    1499            2 :     newBindingEntry.statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kPending;
    1500              : 
    1501              :     // Add the new binding entry to the datastore
    1502            2 :     mEndpointBindingEntries.push_back(newBindingEntry);
    1503              : 
    1504            4 :     return mDelegate->SyncNode(nodeId, newBindingEntry, [this]() {
    1505            2 :         mEndpointBindingEntries.back().statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kCommitted;
    1506            2 :     });
    1507              : }
    1508              : 
    1509              : CHIP_ERROR
    1510            2 : JointFabricDatastore::RemoveBindingFromEndpointForNode(uint16_t listId, NodeId nodeId, chip::EndpointId endpointId)
    1511              : {
    1512            2 :     VerifyOrReturnError(mDelegate != nullptr, CHIP_ERROR_INCORRECT_STATE);
    1513              : 
    1514            2 :     size_t index = 0;
    1515            2 :     ReturnErrorOnFailure(IsNodeIdAndEndpointInEndpointInformationEntries(nodeId, endpointId, index));
    1516              : 
    1517            1 :     for (auto it = mEndpointBindingEntries.begin(); it != mEndpointBindingEntries.end(); ++it)
    1518              :     {
    1519            1 :         if (it->nodeID == nodeId && it->listID == listId && it->endpointID == endpointId)
    1520              :         {
    1521            1 :             it->statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kDeletePending;
    1522              : 
    1523              :             // zero-initialized struct to indicate deletion for the SyncNode call
    1524            1 :             Clusters::JointFabricDatastore::Structs::DatastoreEndpointBindingEntryStruct::Type nullEntry{ 0 };
    1525            2 :             return mDelegate->SyncNode(nodeId, nullEntry, [this, it]() { mEndpointBindingEntries.erase(it); });
    1526              :         }
    1527              :     }
    1528              : 
    1529            0 :     return CHIP_ERROR_NOT_FOUND;
    1530              : }
    1531              : 
    1532            0 : bool JointFabricDatastore::ACLTargetMatches(
    1533              :     const Clusters::JointFabricDatastore::Structs::DatastoreAccessControlTargetStruct::Type & target1,
    1534              :     const Clusters::JointFabricDatastore::Structs::DatastoreAccessControlTargetStruct::Type & target2)
    1535              : {
    1536            0 :     if (!target1.cluster.IsNull() && !target2.cluster.IsNull())
    1537              :     {
    1538            0 :         if (target1.cluster.Value() != target2.cluster.Value())
    1539              :         {
    1540            0 :             return false;
    1541              :         }
    1542              :     }
    1543            0 :     else if (!target1.cluster.IsNull() || !target2.cluster.IsNull())
    1544              :     {
    1545            0 :         return false;
    1546              :     }
    1547              : 
    1548            0 :     if (!target1.endpoint.IsNull() && !target2.endpoint.IsNull())
    1549              :     {
    1550            0 :         if (target1.endpoint.Value() != target2.endpoint.Value())
    1551              :         {
    1552            0 :             return false;
    1553              :         }
    1554              :     }
    1555            0 :     else if (!target1.endpoint.IsNull() || !target2.endpoint.IsNull())
    1556              :     {
    1557            0 :         return false;
    1558              :     }
    1559              : 
    1560            0 :     if (!target1.deviceType.IsNull() && !target2.deviceType.IsNull())
    1561              :     {
    1562            0 :         if (target1.deviceType.Value() != target2.deviceType.Value())
    1563              :         {
    1564            0 :             return false;
    1565              :         }
    1566              :     }
    1567            0 :     else if (!target1.deviceType.IsNull() || !target2.deviceType.IsNull())
    1568              :     {
    1569            0 :         return false;
    1570              :     }
    1571              : 
    1572            0 :     return true;
    1573              : }
    1574              : 
    1575            0 : bool JointFabricDatastore::ACLMatches(
    1576              :     const datastore::AccessControlEntryStruct & acl1,
    1577              :     const Clusters::JointFabricDatastore::Structs::DatastoreAccessControlEntryStruct::DecodableType & acl2)
    1578              : {
    1579            0 :     if (acl1.privilege != acl2.privilege)
    1580              :     {
    1581            0 :         return false;
    1582              :     }
    1583              : 
    1584            0 :     if (acl1.authMode != acl2.authMode)
    1585              :     {
    1586            0 :         return false;
    1587              :     }
    1588              : 
    1589              :     {
    1590            0 :         auto it1 = acl1.subjects.begin();
    1591            0 :         auto it2 = acl2.subjects.Value().begin();
    1592              : 
    1593            0 :         while (it1 != acl1.subjects.end() && it2.Next())
    1594              :         {
    1595            0 :             if (*it1 != it2.GetValue())
    1596              :             {
    1597            0 :                 return false;
    1598              :             }
    1599            0 :             ++it1;
    1600              :         }
    1601              : 
    1602            0 :         if (it2.Next())
    1603              :         {
    1604            0 :             return false; // acl2 has more subjects
    1605              :         }
    1606              :     }
    1607              : 
    1608              :     {
    1609            0 :         auto it1 = acl1.targets.begin();
    1610            0 :         auto it2 = acl2.targets.Value().begin();
    1611              : 
    1612            0 :         while (it1 != acl1.targets.end() && it2.Next())
    1613              :         {
    1614            0 :             if (ACLTargetMatches(*it1, it2.GetValue()) == false)
    1615              :             {
    1616            0 :                 return false;
    1617              :             }
    1618            0 :             ++it1;
    1619              :         }
    1620              : 
    1621            0 :         if (it2.Next())
    1622              :         {
    1623            0 :             return false; // acl2 has more targets
    1624              :         }
    1625              :     }
    1626              : 
    1627            0 :     return true;
    1628              : }
    1629              : 
    1630              : CHIP_ERROR
    1631            3 : JointFabricDatastore::AddACLToNode(
    1632              :     NodeId nodeId, const Clusters::JointFabricDatastore::Structs::DatastoreAccessControlEntryStruct::DecodableType & aclEntry)
    1633              : {
    1634            3 :     VerifyOrReturnError(mDelegate != nullptr, CHIP_ERROR_INCORRECT_STATE);
    1635              : 
    1636            3 :     size_t index = 0;
    1637            3 :     ReturnErrorOnFailure(IsNodeIdInNodeInformationEntries(nodeId, index));
    1638              : 
    1639              :     // Check if the ACL entry already exists for the node
    1640            2 :     for (auto & entry : mACLEntries)
    1641              :     {
    1642            0 :         if (entry.nodeID == nodeId)
    1643              :         {
    1644            0 :             if (ACLMatches(entry.ACLEntry, aclEntry))
    1645              :             {
    1646            0 :                 return CHIP_NO_ERROR;
    1647              :             }
    1648              :         }
    1649              :     }
    1650            2 :     VerifyOrReturnError(mACLEntries.size() < kMaxACLs, CHIP_ERROR_NO_MEMORY);
    1651              :     // Create a new ACL entry
    1652            2 :     datastore::ACLEntryStruct newACLEntry;
    1653            2 :     newACLEntry.nodeID             = nodeId;
    1654            2 :     newACLEntry.ACLEntry.privilege = aclEntry.privilege;
    1655            2 :     newACLEntry.ACLEntry.authMode  = aclEntry.authMode;
    1656              : 
    1657            2 :     newACLEntry.statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kPending;
    1658              : 
    1659            2 :     if (!aclEntry.subjects.IsNull())
    1660              :     {
    1661            0 :         auto iter = aclEntry.subjects.Value().begin();
    1662            0 :         while (iter.Next())
    1663              :         {
    1664            0 :             newACLEntry.ACLEntry.subjects.push_back(iter.GetValue());
    1665              :         }
    1666            0 :         ReturnErrorOnFailure(iter.GetStatus());
    1667              :     }
    1668              : 
    1669            2 :     if (!aclEntry.targets.IsNull())
    1670              :     {
    1671            0 :         auto iter = aclEntry.targets.Value().begin();
    1672            0 :         while (iter.Next())
    1673              :         {
    1674            0 :             newACLEntry.ACLEntry.targets.push_back(iter.GetValue());
    1675              :         }
    1676            0 :         ReturnErrorOnFailure(iter.GetStatus());
    1677              :     }
    1678              : 
    1679            2 :     ReturnErrorOnFailure(GenerateAndAssignAUniqueListID(newACLEntry.listID));
    1680              : 
    1681              :     // Add the new ACL entry to the datastore
    1682            2 :     mACLEntries.push_back(newACLEntry);
    1683              : 
    1684            2 :     Clusters::JointFabricDatastore::Structs::DatastoreACLEntryStruct::Type entryToEncode;
    1685            2 :     entryToEncode.nodeID             = newACLEntry.nodeID;
    1686            2 :     entryToEncode.listID             = newACLEntry.listID;
    1687            2 :     entryToEncode.ACLEntry.authMode  = newACLEntry.ACLEntry.authMode;
    1688            2 :     entryToEncode.ACLEntry.privilege = newACLEntry.ACLEntry.privilege;
    1689            2 :     entryToEncode.ACLEntry.subjects =
    1690            2 :         DataModel::List<const uint64_t>(newACLEntry.ACLEntry.subjects.data(), newACLEntry.ACLEntry.subjects.size());
    1691            2 :     entryToEncode.ACLEntry.targets =
    1692            4 :         DataModel::List<const Clusters::JointFabricDatastore::Structs::DatastoreAccessControlTargetStruct::Type>(
    1693            2 :             newACLEntry.ACLEntry.targets.data(), newACLEntry.ACLEntry.targets.size());
    1694            2 :     entryToEncode.statusEntry = newACLEntry.statusEntry;
    1695              : 
    1696            4 :     return mDelegate->SyncNode(nodeId, entryToEncode, [this]() {
    1697            2 :         mACLEntries.back().statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kCommitted;
    1698            2 :     });
    1699            2 : }
    1700              : 
    1701            2 : CHIP_ERROR JointFabricDatastore::RemoveACLFromNode(uint16_t listId, NodeId nodeId)
    1702              : {
    1703            2 :     VerifyOrReturnError(mDelegate != nullptr, CHIP_ERROR_INCORRECT_STATE);
    1704              : 
    1705            2 :     size_t index = 0;
    1706            2 :     ReturnErrorOnFailure(IsNodeIdInNodeInformationEntries(nodeId, index));
    1707              : 
    1708            1 :     for (auto it = mACLEntries.begin(); it != mACLEntries.end(); ++it)
    1709              :     {
    1710            1 :         if (it->nodeID == nodeId && it->listID == listId)
    1711              :         {
    1712            1 :             it->statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kDeletePending;
    1713              : 
    1714              :             // zero-initialized struct to indicate deletion for the SyncNode call
    1715            1 :             Clusters::JointFabricDatastore::Structs::DatastoreACLEntryStruct::Type nullEntry{ 0 };
    1716            2 :             return mDelegate->SyncNode(nodeId, nullEntry, [this, it]() { mACLEntries.erase(it); });
    1717              :         }
    1718              :     }
    1719              : 
    1720            0 :     return CHIP_ERROR_NOT_FOUND;
    1721              : }
    1722              : 
    1723            0 : CHIP_ERROR JointFabricDatastore::AddNodeKeySetEntry(GroupId groupId, uint16_t groupKeySetId)
    1724              : {
    1725            0 :     VerifyOrReturnError(mDelegate != nullptr, CHIP_ERROR_INCORRECT_STATE);
    1726              : 
    1727              :     // Find all nodes that are members of this group
    1728            0 :     std::unordered_set<NodeId> nodesInGroup;
    1729            0 :     for (const auto & entry : mEndpointGroupIDEntries)
    1730              :     {
    1731            0 :         if (entry.groupID == groupId)
    1732              :         {
    1733            0 :             nodesInGroup.insert(entry.nodeID);
    1734              :         }
    1735              :     }
    1736              : 
    1737            0 :     if (!nodesInGroup.empty())
    1738              :     {
    1739            0 :         for (const auto nodeId : nodesInGroup)
    1740              :         {
    1741              :             // Skip if a matching NodeKeySet entry already exists for this node
    1742            0 :             bool exists = false;
    1743            0 :             for (const auto & nkse : mNodeKeySetEntries)
    1744              :             {
    1745            0 :                 if (nkse.nodeID == nodeId && nkse.groupKeySetID == groupKeySetId)
    1746              :                 {
    1747            0 :                     exists = true;
    1748            0 :                     break;
    1749              :                 }
    1750              :             }
    1751            0 :             if (exists)
    1752              :             {
    1753            0 :                 continue;
    1754              :             }
    1755              : 
    1756            0 :             Clusters::JointFabricDatastore::Structs::DatastoreNodeKeySetEntryStruct::Type newEntry;
    1757            0 :             newEntry.nodeID            = nodeId;
    1758            0 :             newEntry.groupKeySetID     = groupKeySetId;
    1759            0 :             newEntry.statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kPending;
    1760              : 
    1761            0 :             mNodeKeySetEntries.push_back(newEntry);
    1762              : 
    1763            0 :             size_t index = mNodeKeySetEntries.size() - 1;
    1764              :             // Sync to the node and mark committed on success
    1765            0 :             ReturnErrorOnFailure(mDelegate->SyncNode(nodeId, newEntry, [this, index]() {
    1766              :                 if (index < mNodeKeySetEntries.size())
    1767              :                 {
    1768              :                     mNodeKeySetEntries[index].statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kCommitted;
    1769              :                 }
    1770              :             }));
    1771              :         }
    1772              :     }
    1773              : 
    1774            0 :     return CHIP_NO_ERROR;
    1775            0 : }
    1776              : 
    1777            0 : CHIP_ERROR JointFabricDatastore::RemoveNodeKeySetEntry(GroupId groupId, uint16_t groupKeySetId)
    1778              : {
    1779              :     // NOTE: this method assumes its ok to remove the keyset from each node (its not in use by any group)
    1780              : 
    1781              :     // Find all nodes that are members of this group
    1782            0 :     std::unordered_set<NodeId> nodesInGroup;
    1783            0 :     for (const auto & entry : mEndpointGroupIDEntries)
    1784              :     {
    1785            0 :         if (entry.groupID == groupId)
    1786              :         {
    1787            0 :             nodesInGroup.insert(entry.nodeID);
    1788              :         }
    1789              :     }
    1790              : 
    1791            0 :     bool any_node_removed = false;
    1792              : 
    1793            0 :     for (auto it = mNodeKeySetEntries.begin(); it != mNodeKeySetEntries.end();)
    1794              :     {
    1795            0 :         for (const auto & nodeId : nodesInGroup)
    1796              :         {
    1797            0 :             if (it->nodeID == nodeId && it->groupKeySetID == groupKeySetId)
    1798              :             {
    1799            0 :                 any_node_removed = true;
    1800              : 
    1801              :                 // zero-initialized struct to indicate deletion for the SyncNode call
    1802            0 :                 Clusters::JointFabricDatastore::Structs::DatastoreNodeKeySetEntryStruct::Type nullEntry{ 0 };
    1803              : 
    1804            0 :                 auto nodeIdToErase        = it->nodeID;
    1805            0 :                 auto groupKeySetIdToErase = it->groupKeySetID;
    1806            0 :                 ReturnErrorOnFailure(mDelegate->SyncNode(nodeId, nullEntry, [this, nodeIdToErase, groupKeySetIdToErase]() {
    1807              :                     mNodeKeySetEntries.erase(std::remove_if(mNodeKeySetEntries.begin(), mNodeKeySetEntries.end(),
    1808              :                                                             [&](const auto & entry) {
    1809              :                                                                 return entry.nodeID == nodeIdToErase &&
    1810              :                                                                     entry.groupKeySetID == groupKeySetIdToErase;
    1811              :                                                             }),
    1812              :                                              mNodeKeySetEntries.end());
    1813              :                 }));
    1814              :             }
    1815              :             else
    1816              :             {
    1817            0 :                 ++it;
    1818              :             }
    1819              :         }
    1820              :     }
    1821              : 
    1822            0 :     return any_node_removed ? CHIP_NO_ERROR : CHIP_ERROR_NOT_FOUND;
    1823            0 : }
    1824              : 
    1825            2 : CHIP_ERROR JointFabricDatastore::TestAddNodeKeySetEntry(GroupId groupId, uint16_t groupKeySetId, NodeId nodeId)
    1826              : {
    1827            2 :     VerifyOrReturnError(mDelegate != nullptr, CHIP_ERROR_INCORRECT_STATE);
    1828              : 
    1829            2 :     Clusters::JointFabricDatastore::Structs::DatastoreNodeKeySetEntryStruct::Type newEntry;
    1830            2 :     newEntry.nodeID            = nodeId;
    1831            2 :     newEntry.groupKeySetID     = groupKeySetId;
    1832            2 :     newEntry.statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kPending;
    1833              : 
    1834            2 :     mNodeKeySetEntries.push_back(newEntry);
    1835              : 
    1836            2 :     size_t index = mNodeKeySetEntries.size() - 1;
    1837              :     // Sync to the node and mark committed on success
    1838            4 :     return mDelegate->SyncNode(nodeId, newEntry, [this, index]() {
    1839            2 :         if (index < mNodeKeySetEntries.size())
    1840              :         {
    1841            2 :             mNodeKeySetEntries[index].statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kCommitted;
    1842              :         }
    1843            2 :     });
    1844              : }
    1845              : 
    1846            4 : CHIP_ERROR JointFabricDatastore::TestAddEndpointEntry(EndpointId endpointId, NodeId nodeId, CharSpan friendlyName)
    1847              : {
    1848            4 :     Clusters::JointFabricDatastore::Structs::DatastoreEndpointEntryStruct::Type newEntry;
    1849            4 :     newEntry.nodeID       = nodeId;
    1850            4 :     newEntry.endpointID   = endpointId;
    1851            4 :     newEntry.friendlyName = friendlyName;
    1852              : 
    1853            4 :     mEndpointEntries.push_back(newEntry);
    1854              : 
    1855            4 :     return CHIP_NO_ERROR;
    1856              : }
    1857              : 
    1858              : } // namespace app
    1859              : } // namespace chip
        

Generated by: LCOV version 2.0-1