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
|