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