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