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