Skip to content

Commit d4bc686

Browse files
authored
Implement Joint Fabric Node management commands (project-chip#41535)
1 parent f160475 commit d4bc686

File tree

9 files changed

+526
-33
lines changed

9 files changed

+526
-33
lines changed

examples/jf-admin-app/linux/BUILD.gn

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,10 @@ config("includes") {
3636

3737
executable("jfa-app") {
3838
sources = [
39+
"JFADatastoreSync.cpp",
3940
"JFAManager.cpp",
4041
"include/CHIPProjectAppConfig.h",
42+
"include/JFADatastoreSync.h",
4143
"include/JFAManager.h",
4244
"include/JFARpc.h",
4345
"main.cpp",

examples/jf-admin-app/linux/JFADatastoreSync.cpp

Lines changed: 341 additions & 0 deletions
Large diffs are not rendered by default.

examples/jf-admin-app/linux/args.gni

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,7 @@ chip_enable_additional_data_advertising = true
3434
chip_enable_rotating_device_id = true
3535

3636
chip_enable_read_client = true
37+
chip_build_controller = true
38+
chip_support_commissioning_in_controller = true
3739

3840
chip_device_config_enable_joint_fabric = true

examples/jf-admin-app/linux/include/CHIPProjectAppConfig.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
// include the CHIPProjectConfig from config/standalone
3131
#include <CHIPProjectConfig.h>
3232

33-
#define CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY 0
33+
#define CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY 1
3434

3535
#define CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY_CLIENT 0
3636

@@ -45,3 +45,5 @@
4545
#define CHIP_DEVICE_ENABLE_PORT_PARAMS 1
4646

4747
#define CHIP_DEVICE_CONFIG_DEVICE_NAME "JF Admin"
48+
49+
#define CHIP_DEVICE_CONFIG_ENABLE_BOTH_COMMISSIONER_AND_COMMISSIONEE 1
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
*
3+
* Copyright (c) 2025 Project CHIP Authors
4+
* All rights reserved.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
#pragma once
20+
21+
#include <app/server/JointFabricDatastore.h>
22+
#include <app/server/Server.h>
23+
#include <lib/core/CHIPError.h>
24+
25+
namespace chip {
26+
27+
class JFADatastoreSync : public app::JointFabricDatastore::Delegate
28+
{
29+
public:
30+
JFADatastoreSync() {}
31+
32+
CHIP_ERROR Init(Server & server);
33+
34+
/* app::JointFabricDatastore::Delegate */
35+
CHIP_ERROR
36+
SyncNode(NodeId nodeId,
37+
const app::Clusters::JointFabricDatastore::Structs::DatastoreEndpointGroupIDEntryStruct::Type & endpointGroupIDEntry,
38+
std::function<void()> onSuccess) override;
39+
CHIP_ERROR
40+
SyncNode(NodeId nodeId,
41+
const app::Clusters::JointFabricDatastore::Structs::DatastoreNodeKeySetEntryStruct::Type & nodeKeySetEntry,
42+
std::function<void()> onSuccess) override;
43+
CHIP_ERROR
44+
SyncNode(NodeId nodeId,
45+
const app::Clusters::JointFabricDatastore::Structs::DatastoreEndpointBindingEntryStruct::Type & bindingEntry,
46+
std::function<void()> onSuccess) override;
47+
CHIP_ERROR SyncNode(NodeId nodeId, const app::Clusters::JointFabricDatastore::Structs::DatastoreACLEntryStruct::Type & aclEntry,
48+
std::function<void()> onSuccess) override;
49+
50+
private:
51+
friend JFADatastoreSync & JFADSync(void);
52+
53+
static JFADatastoreSync sJFDS;
54+
55+
Server * mServer = nullptr;
56+
};
57+
58+
inline JFADatastoreSync & JFADSync(void)
59+
{
60+
return JFADatastoreSync::sJFDS;
61+
}
62+
63+
} // namespace chip

examples/jf-admin-app/linux/main.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
* limitations under the License.
1717
*/
1818

19+
#include "JFADatastoreSync.h"
1920
#include "JFAManager.h"
2021
#include "rpc/RpcServer.h"
2122
#include <AppMain.h>
@@ -99,7 +100,9 @@ void ApplicationInit()
99100
}
100101

101102
JFAMgr().Init(Server::GetInstance());
103+
JFADSync().Init(Server::GetInstance());
102104
Server::GetInstance().GetJointFabricAdministrator().SetDelegate(&JFAMgr());
105+
Server::GetInstance().GetJointFabricDatastore().SetDelegate(&JFADSync());
103106

104107
PlatformMgrImpl().AddEventHandler(EventHandler, 0);
105108
}

examples/platform/linux/CommissionerMain.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -244,9 +244,13 @@ void ShutdownCommissioner()
244244
class PairingCommand : public Controller::DevicePairingDelegate
245245
{
246246
public:
247-
PairingCommand() :
247+
PairingCommand()
248+
#if CHIP_DEVICE_CONFIG_APP_PLATFORM_ENABLED
249+
:
248250
mOnDeviceConnectedCallback(OnDeviceConnectedFn, this),
249-
mOnDeviceConnectionFailureCallback(OnDeviceConnectionFailureFn, this){};
251+
mOnDeviceConnectionFailureCallback(OnDeviceConnectionFailureFn, this)
252+
#endif // CHIP_DEVICE_CONFIG_APP_PLATFORM_ENABLED
253+
{}
250254

251255
/////////// DevicePairingDelegate Interface /////////
252256
void OnStatusUpdate(Controller::DevicePairingDelegate::Status status) override;

src/app/server/JointFabricDatastore.cpp

Lines changed: 57 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,11 @@ void JointFabricDatastore::RemoveListener(Listener & listener)
7070
CHIP_ERROR JointFabricDatastore::AddPendingNode(NodeId nodeId, const CharSpan & friendlyName)
7171
{
7272
VerifyOrReturnError(mNodeInformationEntries.size() < kMaxNodes, CHIP_ERROR_NO_MEMORY);
73+
// check that nodeId does not already exist
74+
VerifyOrReturnError(
75+
std::none_of(mNodeInformationEntries.begin(), mNodeInformationEntries.end(),
76+
[nodeId](const GenericDatastoreNodeInformationEntry & entry) { return entry.nodeID == nodeId; }),
77+
CHIP_IM_GLOBAL_STATUS(ConstraintError));
7378

7479
mNodeInformationEntries.push_back(GenericDatastoreNodeInformationEntry(
7580
nodeId, Clusters::JointFabricDatastore::DatastoreStateEnum::kPending, MakeOptional(friendlyName)));
@@ -659,6 +664,8 @@ CHIP_ERROR JointFabricDatastore::IsNodeIdAndEndpointInEndpointInformationEntries
659664

660665
CHIP_ERROR JointFabricDatastore::AddGroupIDToEndpointForNode(NodeId nodeId, chip::EndpointId endpointId, chip::GroupId groupId)
661666
{
667+
VerifyOrReturnError(mDelegate != nullptr, CHIP_ERROR_INCORRECT_STATE);
668+
662669
size_t index = 0;
663670
ReturnErrorOnFailure(IsNodeIdAndEndpointInEndpointInformationEntries(nodeId, endpointId, index));
664671

@@ -687,11 +694,11 @@ CHIP_ERROR JointFabricDatastore::AddGroupIDToEndpointForNode(NodeId nodeId, chip
687694
newNodeKeySet.groupKeySetID = groupKeySetID;
688695
newNodeKeySet.statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kPending;
689696

690-
// TODO: Update device
691-
692697
mNodeKeySetEntries.push_back(newNodeKeySet);
693698

694-
mNodeKeySetEntries.back().statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kCommitted;
699+
ReturnErrorOnFailure(mDelegate->SyncNode(nodeId, newNodeKeySet, [this]() {
700+
mNodeKeySetEntries.back().statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kCommitted;
701+
}));
695702
}
696703
}
697704

@@ -713,18 +720,18 @@ CHIP_ERROR JointFabricDatastore::AddGroupIDToEndpointForNode(NodeId nodeId, chip
713720
newGroupEntry.groupID = groupId;
714721
newGroupEntry.statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kPending;
715722

716-
// TODO: Update device
717-
718723
// Add the new ACL entry to the datastore
719724
mEndpointGroupIDEntries.push_back(newGroupEntry);
720725

721-
mEndpointGroupIDEntries.back().statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kCommitted;
722-
723-
return CHIP_NO_ERROR;
726+
return mDelegate->SyncNode(nodeId, newGroupEntry, [this]() {
727+
mEndpointGroupIDEntries.back().statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kCommitted;
728+
});
724729
}
725730

726731
CHIP_ERROR JointFabricDatastore::RemoveGroupIDFromEndpointForNode(NodeId nodeId, chip::EndpointId endpointId, chip::GroupId groupId)
727732
{
733+
VerifyOrReturnError(mDelegate != nullptr, CHIP_ERROR_INCORRECT_STATE);
734+
728735
size_t index = 0;
729736
ReturnErrorOnFailure(IsNodeIdAndEndpointInEndpointInformationEntries(nodeId, endpointId, index));
730737

@@ -734,9 +741,11 @@ CHIP_ERROR JointFabricDatastore::RemoveGroupIDFromEndpointForNode(NodeId nodeId,
734741
{
735742
it->statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kDeletePending;
736743

737-
// TODO: Update device
744+
// zero-initialized struct to indicate deletion for the SyncNode call
745+
Clusters::JointFabricDatastore::Structs::DatastoreEndpointGroupIDEntryStruct::Type endpointGroupIdNullEntry{ 0 };
738746

739-
mEndpointGroupIDEntries.erase(it);
747+
ReturnErrorOnFailure(
748+
mDelegate->SyncNode(nodeId, endpointGroupIdNullEntry, [this, it]() { mEndpointGroupIDEntries.erase(it); }));
740749

741750
if (IsGroupIDInDatastore(groupId, index) == CHIP_NO_ERROR)
742751
{
@@ -748,8 +757,11 @@ CHIP_ERROR JointFabricDatastore::RemoveGroupIDFromEndpointForNode(NodeId nodeId,
748757
it2->groupKeySetID == mGroupInformationEntries[index].groupKeySetID.Value())
749758
{
750759
it2->statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kDeletePending;
751-
// TODO: Update device
752-
it2 = mNodeKeySetEntries.erase(it2);
760+
761+
// zero-initialized struct to indicate deletion for the SyncNode call
762+
Clusters::JointFabricDatastore::Structs::DatastoreNodeKeySetEntryStruct::Type nodeKeySetNullEntry{ 0 };
763+
ReturnErrorOnFailure(
764+
mDelegate->SyncNode(nodeId, nodeKeySetNullEntry, [this, it2]() { mNodeKeySetEntries.erase(it2); }));
753765

754766
incrementIndex = false;
755767
}
@@ -856,6 +868,8 @@ JointFabricDatastore::AddBindingToEndpointForNode(
856868
NodeId nodeId, chip::EndpointId endpointId,
857869
const Clusters::JointFabricDatastore::Structs::DatastoreBindingTargetStruct::Type & binding)
858870
{
871+
VerifyOrReturnError(mDelegate != nullptr, CHIP_ERROR_INCORRECT_STATE);
872+
859873
size_t index = 0;
860874
ReturnErrorOnFailure(IsNodeIdAndEndpointInEndpointInformationEntries(nodeId, endpointId, index));
861875

@@ -884,16 +898,16 @@ JointFabricDatastore::AddBindingToEndpointForNode(
884898
// Add the new binding entry to the datastore
885899
mEndpointBindingEntries.push_back(newBindingEntry);
886900

887-
// TODO: Update device
888-
889-
mEndpointBindingEntries.back().statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kCommitted;
890-
891-
return CHIP_NO_ERROR;
901+
return mDelegate->SyncNode(nodeId, newBindingEntry, [this]() {
902+
mEndpointBindingEntries.back().statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kCommitted;
903+
});
892904
}
893905

894906
CHIP_ERROR
895907
JointFabricDatastore::RemoveBindingFromEndpointForNode(uint16_t listId, NodeId nodeId, chip::EndpointId endpointId)
896908
{
909+
VerifyOrReturnError(mDelegate != nullptr, CHIP_ERROR_INCORRECT_STATE);
910+
897911
size_t index = 0;
898912
ReturnErrorOnFailure(IsNodeIdAndEndpointInEndpointInformationEntries(nodeId, endpointId, index));
899913

@@ -903,11 +917,9 @@ JointFabricDatastore::RemoveBindingFromEndpointForNode(uint16_t listId, NodeId n
903917
{
904918
it->statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kDeletePending;
905919

906-
// TODO: Update device
907-
908-
mEndpointBindingEntries.erase(it);
909-
910-
return CHIP_NO_ERROR;
920+
// zero-initialized struct to indicate deletion for the SyncNode call
921+
Clusters::JointFabricDatastore::Structs::DatastoreEndpointBindingEntryStruct::Type nullEntry{ 0 };
922+
return mDelegate->SyncNode(nodeId, nullEntry, [this, it]() { mEndpointBindingEntries.erase(it); });
911923
}
912924
}
913925

@@ -1016,6 +1028,8 @@ CHIP_ERROR
10161028
JointFabricDatastore::AddACLToNode(
10171029
NodeId nodeId, const Clusters::JointFabricDatastore::Structs::DatastoreAccessControlEntryStruct::DecodableType & aclEntry)
10181030
{
1031+
VerifyOrReturnError(mDelegate != nullptr, CHIP_ERROR_INCORRECT_STATE);
1032+
10191033
size_t index = 0;
10201034
ReturnErrorOnFailure(IsNodeIdInNodeInformationEntries(nodeId, index));
10211035

@@ -1062,15 +1076,27 @@ JointFabricDatastore::AddACLToNode(
10621076
// Add the new ACL entry to the datastore
10631077
mACLEntries.push_back(newACLEntry);
10641078

1065-
// TODO: Update device
1066-
1067-
mACLEntries.back().statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kCommitted;
1068-
1069-
return CHIP_NO_ERROR;
1079+
Clusters::JointFabricDatastore::Structs::DatastoreACLEntryStruct::Type entryToEncode;
1080+
entryToEncode.nodeID = newACLEntry.nodeID;
1081+
entryToEncode.listID = newACLEntry.listID;
1082+
entryToEncode.ACLEntry.authMode = newACLEntry.ACLEntry.authMode;
1083+
entryToEncode.ACLEntry.privilege = newACLEntry.ACLEntry.privilege;
1084+
entryToEncode.ACLEntry.subjects =
1085+
DataModel::List<const uint64_t>(newACLEntry.ACLEntry.subjects.data(), newACLEntry.ACLEntry.subjects.size());
1086+
entryToEncode.ACLEntry.targets =
1087+
DataModel::List<const Clusters::JointFabricDatastore::Structs::DatastoreAccessControlTargetStruct::Type>(
1088+
newACLEntry.ACLEntry.targets.data(), newACLEntry.ACLEntry.targets.size());
1089+
entryToEncode.statusEntry = newACLEntry.statusEntry;
1090+
1091+
return mDelegate->SyncNode(nodeId, entryToEncode, [this]() {
1092+
mACLEntries.back().statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kCommitted;
1093+
});
10701094
}
10711095

10721096
CHIP_ERROR JointFabricDatastore::RemoveACLFromNode(uint16_t listId, NodeId nodeId)
10731097
{
1098+
VerifyOrReturnError(mDelegate != nullptr, CHIP_ERROR_INCORRECT_STATE);
1099+
10741100
size_t index = 0;
10751101
ReturnErrorOnFailure(IsNodeIdInNodeInformationEntries(nodeId, index));
10761102

@@ -1079,9 +1105,10 @@ CHIP_ERROR JointFabricDatastore::RemoveACLFromNode(uint16_t listId, NodeId nodeI
10791105
if (it->nodeID == nodeId && it->listID == listId)
10801106
{
10811107
it->statusEntry.state = Clusters::JointFabricDatastore::DatastoreStateEnum::kDeletePending;
1082-
// TODO: Update device
1083-
mACLEntries.erase(it);
1084-
return CHIP_NO_ERROR;
1108+
1109+
// zero-initialized struct to indicate deletion for the SyncNode call
1110+
Clusters::JointFabricDatastore::Structs::DatastoreACLEntryStruct::Type nullEntry{ 0 };
1111+
return mDelegate->SyncNode(nodeId, nullEntry, [this, it]() { mACLEntries.erase(it); });
10851112
}
10861113
}
10871114

src/app/server/JointFabricDatastore.h

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include <app-common/zap-generated/cluster-objects.h>
2121
#include <app/data-model-provider/MetadataTypes.h>
2222
#include <credentials/CHIPCert.h>
23+
#include <functional>
2324
#include <lib/core/CHIPPersistentStorageDelegate.h>
2425
#include <lib/core/CHIPVendorIdentifiers.hpp>
2526
#include <lib/core/NodeId.h>
@@ -117,6 +118,44 @@ class JointFabricDatastore
117118
return sInstance;
118119
}
119120

121+
class Delegate
122+
{
123+
public:
124+
Delegate() {}
125+
virtual ~Delegate() {}
126+
127+
virtual CHIP_ERROR
128+
SyncNode(NodeId nodeId,
129+
const Clusters::JointFabricDatastore::Structs::DatastoreEndpointGroupIDEntryStruct::Type & endpointGroupIDEntry,
130+
std::function<void()> onSuccess)
131+
{
132+
return CHIP_ERROR_NOT_IMPLEMENTED;
133+
}
134+
135+
virtual CHIP_ERROR
136+
SyncNode(NodeId nodeId,
137+
const Clusters::JointFabricDatastore::Structs::DatastoreNodeKeySetEntryStruct::Type & nodeKeySetEntry,
138+
std::function<void()> onSuccess)
139+
{
140+
return CHIP_ERROR_NOT_IMPLEMENTED;
141+
}
142+
143+
virtual CHIP_ERROR
144+
SyncNode(NodeId nodeId,
145+
const Clusters::JointFabricDatastore::Structs::DatastoreEndpointBindingEntryStruct::Type & bindingEntry,
146+
std::function<void()> onSuccess)
147+
{
148+
return CHIP_ERROR_NOT_IMPLEMENTED;
149+
}
150+
151+
virtual CHIP_ERROR SyncNode(NodeId nodeId,
152+
const Clusters::JointFabricDatastore::Structs::DatastoreACLEntryStruct::Type & aclEntry,
153+
std::function<void()> onSuccess)
154+
{
155+
return CHIP_ERROR_NOT_IMPLEMENTED;
156+
}
157+
};
158+
120159
ByteSpan GetAnchorRootCA() const { return ByteSpan(mAnchorRootCA, mAnchorRootCALength); }
121160

122161
CHIP_ERROR SetAnchorNodeId(NodeId anchorNodeId)
@@ -256,6 +295,14 @@ class JointFabricDatastore
256295
*/
257296
void RemoveListener(Listener & listener);
258297

298+
CHIP_ERROR SetDelegate(Delegate * delegate)
299+
{
300+
VerifyOrReturnError(delegate != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
301+
mDelegate = delegate;
302+
303+
return CHIP_NO_ERROR;
304+
}
305+
259306
private:
260307
static constexpr size_t kMaxNodes = 256;
261308
static constexpr size_t kMaxAdminNodes = 32;
@@ -304,6 +351,8 @@ class JointFabricDatastore
304351

305352
CHIP_ERROR AddNodeKeySetEntry(GroupId groupId, uint16_t groupKeySetId);
306353
CHIP_ERROR RemoveNodeKeySetEntry(GroupId groupId, uint16_t groupKeySetId);
354+
355+
Delegate * mDelegate = nullptr;
307356
};
308357

309358
} // namespace app

0 commit comments

Comments
 (0)