@matter/node 0.14.0 → 0.14.1-alpha.0-20250606-a9bcd03f9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/behavior/Behavior.d.ts +2 -1
- package/dist/cjs/behavior/Behavior.d.ts.map +1 -1
- package/dist/cjs/behavior/Behavior.js +1 -1
- package/dist/cjs/behavior/Behavior.js.map +1 -1
- package/dist/cjs/behavior/Transitions.js +2 -2
- package/dist/cjs/behavior/Transitions.js.map +1 -1
- package/dist/cjs/behavior/context/server/OnlineContext.d.ts +3 -3
- package/dist/cjs/behavior/context/server/OnlineContext.d.ts.map +1 -1
- package/dist/cjs/behavior/context/server/OnlineContext.js +12 -9
- package/dist/cjs/behavior/context/server/OnlineContext.js.map +1 -1
- package/dist/cjs/behavior/internal/ClientBehaviorBacking.d.ts +2 -1
- package/dist/cjs/behavior/internal/ClientBehaviorBacking.d.ts.map +1 -1
- package/dist/cjs/behavior/internal/ClientBehaviorBacking.js.map +1 -1
- package/dist/cjs/behavior/system/network/NetworkBehavior.d.ts.map +1 -1
- package/dist/cjs/behavior/system/network/NetworkBehavior.js +2 -1
- package/dist/cjs/behavior/system/network/NetworkBehavior.js.map +1 -1
- package/dist/cjs/behavior/system/network/ServerGroupNetworking.d.ts +18 -0
- package/dist/cjs/behavior/system/network/ServerGroupNetworking.d.ts.map +1 -0
- package/dist/cjs/behavior/system/network/ServerGroupNetworking.js +150 -0
- package/dist/cjs/behavior/system/network/ServerGroupNetworking.js.map +6 -0
- package/dist/cjs/behavior/system/network/ServerNetworkRuntime.d.ts.map +1 -1
- package/dist/cjs/behavior/system/network/ServerNetworkRuntime.js +25 -7
- package/dist/cjs/behavior/system/network/ServerNetworkRuntime.js.map +1 -1
- package/dist/cjs/behavior/system/sessions/SessionsBehavior.d.ts.map +1 -1
- package/dist/cjs/behavior/system/sessions/SessionsBehavior.js.map +1 -1
- package/dist/cjs/behaviors/access-control/AccessControlServer.d.ts.map +1 -1
- package/dist/cjs/behaviors/access-control/AccessControlServer.js +18 -12
- package/dist/cjs/behaviors/access-control/AccessControlServer.js.map +1 -1
- package/dist/cjs/behaviors/color-control/ColorControlServer.d.ts +4 -4
- package/dist/cjs/behaviors/color-control/ColorControlServer.d.ts.map +1 -1
- package/dist/cjs/behaviors/color-control/ColorControlServer.js +3 -3
- package/dist/cjs/behaviors/color-control/ColorControlServer.js.map +1 -1
- package/dist/cjs/behaviors/general-commissioning/GeneralCommissioningServer.d.ts.map +1 -1
- package/dist/cjs/behaviors/general-commissioning/GeneralCommissioningServer.js +5 -4
- package/dist/cjs/behaviors/general-commissioning/GeneralCommissioningServer.js.map +1 -1
- package/dist/cjs/behaviors/general-commissioning/ServerNodeFailsafeContext.d.ts +1 -1
- package/dist/cjs/behaviors/general-commissioning/ServerNodeFailsafeContext.d.ts.map +1 -1
- package/dist/cjs/behaviors/group-key-management/GroupKeyManagementServer.d.ts +27 -4
- package/dist/cjs/behaviors/group-key-management/GroupKeyManagementServer.d.ts.map +1 -1
- package/dist/cjs/behaviors/group-key-management/GroupKeyManagementServer.js +340 -21
- package/dist/cjs/behaviors/group-key-management/GroupKeyManagementServer.js.map +2 -2
- package/dist/cjs/behaviors/groups/GroupsServer.d.ts +19 -3
- package/dist/cjs/behaviors/groups/GroupsServer.d.ts.map +1 -1
- package/dist/cjs/behaviors/groups/GroupsServer.js +138 -1
- package/dist/cjs/behaviors/groups/GroupsServer.js.map +2 -2
- package/dist/cjs/behaviors/level-control/LevelControlServer.d.ts +5 -5
- package/dist/cjs/behaviors/level-control/LevelControlServer.d.ts.map +1 -1
- package/dist/cjs/behaviors/level-control/LevelControlServer.js +3 -3
- package/dist/cjs/behaviors/level-control/LevelControlServer.js.map +1 -1
- package/dist/cjs/behaviors/mode-select/ModeSelectServer.d.ts.map +1 -1
- package/dist/cjs/behaviors/mode-select/ModeSelectServer.js +3 -3
- package/dist/cjs/behaviors/mode-select/ModeSelectServer.js.map +1 -1
- package/dist/cjs/behaviors/on-off/OnOffServer.d.ts.map +1 -1
- package/dist/cjs/behaviors/on-off/OnOffServer.js +3 -3
- package/dist/cjs/behaviors/on-off/OnOffServer.js.map +1 -1
- package/dist/cjs/behaviors/operational-credentials/OperationalCredentialsServer.d.ts.map +1 -1
- package/dist/cjs/behaviors/operational-credentials/OperationalCredentialsServer.js +14 -8
- package/dist/cjs/behaviors/operational-credentials/OperationalCredentialsServer.js.map +1 -1
- package/dist/cjs/endpoint/Endpoint.js +1 -1
- package/dist/cjs/endpoint/Endpoint.js.map +1 -1
- package/dist/cjs/node/Node.d.ts +1 -1
- package/dist/cjs/node/Node.d.ts.map +1 -1
- package/dist/cjs/node/Node.js +2 -2
- package/dist/cjs/node/Node.js.map +1 -1
- package/dist/cjs/node/ServerNode.d.ts +1 -1
- package/dist/cjs/node/ServerNode.d.ts.map +1 -1
- package/dist/cjs/node/ServerNode.js +2 -2
- package/dist/cjs/node/ServerNode.js.map +1 -1
- package/dist/cjs/node/client/NodePeerAddressStore.d.ts +1 -1
- package/dist/cjs/node/client/NodePeerAddressStore.d.ts.map +1 -1
- package/dist/cjs/node/server/InteractionServer.d.ts +2 -2
- package/dist/cjs/node/server/InteractionServer.d.ts.map +1 -1
- package/dist/cjs/node/server/InteractionServer.js +1 -1
- package/dist/cjs/node/server/InteractionServer.js.map +1 -1
- package/dist/cjs/node/server/ServerSubscription.d.ts +4 -4
- package/dist/cjs/node/server/ServerSubscription.d.ts.map +1 -1
- package/dist/cjs/node/server/ServerSubscription.js.map +1 -1
- package/dist/esm/behavior/Behavior.d.ts +2 -1
- package/dist/esm/behavior/Behavior.d.ts.map +1 -1
- package/dist/esm/behavior/Behavior.js +2 -2
- package/dist/esm/behavior/Behavior.js.map +1 -1
- package/dist/esm/behavior/Transitions.js +1 -1
- package/dist/esm/behavior/context/server/OnlineContext.d.ts +3 -3
- package/dist/esm/behavior/context/server/OnlineContext.d.ts.map +1 -1
- package/dist/esm/behavior/context/server/OnlineContext.js +13 -10
- package/dist/esm/behavior/context/server/OnlineContext.js.map +1 -1
- package/dist/esm/behavior/internal/ClientBehaviorBacking.d.ts +2 -1
- package/dist/esm/behavior/internal/ClientBehaviorBacking.d.ts.map +1 -1
- package/dist/esm/behavior/internal/ClientBehaviorBacking.js.map +1 -1
- package/dist/esm/behavior/system/network/NetworkBehavior.d.ts.map +1 -1
- package/dist/esm/behavior/system/network/NetworkBehavior.js +2 -1
- package/dist/esm/behavior/system/network/NetworkBehavior.js.map +1 -1
- package/dist/esm/behavior/system/network/ServerGroupNetworking.d.ts +18 -0
- package/dist/esm/behavior/system/network/ServerGroupNetworking.d.ts.map +1 -0
- package/dist/esm/behavior/system/network/ServerGroupNetworking.js +130 -0
- package/dist/esm/behavior/system/network/ServerGroupNetworking.js.map +6 -0
- package/dist/esm/behavior/system/network/ServerNetworkRuntime.d.ts.map +1 -1
- package/dist/esm/behavior/system/network/ServerNetworkRuntime.js +23 -5
- package/dist/esm/behavior/system/network/ServerNetworkRuntime.js.map +1 -1
- package/dist/esm/behavior/system/sessions/SessionsBehavior.d.ts.map +1 -1
- package/dist/esm/behavior/system/sessions/SessionsBehavior.js.map +1 -1
- package/dist/esm/behaviors/access-control/AccessControlServer.d.ts.map +1 -1
- package/dist/esm/behaviors/access-control/AccessControlServer.js +20 -13
- package/dist/esm/behaviors/access-control/AccessControlServer.js.map +1 -1
- package/dist/esm/behaviors/color-control/ColorControlServer.d.ts +4 -4
- package/dist/esm/behaviors/color-control/ColorControlServer.d.ts.map +1 -1
- package/dist/esm/behaviors/color-control/ColorControlServer.js +3 -3
- package/dist/esm/behaviors/color-control/ColorControlServer.js.map +1 -1
- package/dist/esm/behaviors/general-commissioning/GeneralCommissioningServer.d.ts.map +1 -1
- package/dist/esm/behaviors/general-commissioning/GeneralCommissioningServer.js +6 -5
- package/dist/esm/behaviors/general-commissioning/GeneralCommissioningServer.js.map +1 -1
- package/dist/esm/behaviors/general-commissioning/ServerNodeFailsafeContext.d.ts +1 -1
- package/dist/esm/behaviors/general-commissioning/ServerNodeFailsafeContext.d.ts.map +1 -1
- package/dist/esm/behaviors/group-key-management/GroupKeyManagementServer.d.ts +27 -4
- package/dist/esm/behaviors/group-key-management/GroupKeyManagementServer.d.ts.map +1 -1
- package/dist/esm/behaviors/group-key-management/GroupKeyManagementServer.js +342 -23
- package/dist/esm/behaviors/group-key-management/GroupKeyManagementServer.js.map +2 -2
- package/dist/esm/behaviors/groups/GroupsServer.d.ts +19 -3
- package/dist/esm/behaviors/groups/GroupsServer.d.ts.map +1 -1
- package/dist/esm/behaviors/groups/GroupsServer.js +147 -1
- package/dist/esm/behaviors/groups/GroupsServer.js.map +1 -1
- package/dist/esm/behaviors/level-control/LevelControlServer.d.ts +5 -5
- package/dist/esm/behaviors/level-control/LevelControlServer.d.ts.map +1 -1
- package/dist/esm/behaviors/level-control/LevelControlServer.js +3 -3
- package/dist/esm/behaviors/level-control/LevelControlServer.js.map +1 -1
- package/dist/esm/behaviors/mode-select/ModeSelectServer.d.ts.map +1 -1
- package/dist/esm/behaviors/mode-select/ModeSelectServer.js +3 -3
- package/dist/esm/behaviors/mode-select/ModeSelectServer.js.map +1 -1
- package/dist/esm/behaviors/on-off/OnOffServer.d.ts.map +1 -1
- package/dist/esm/behaviors/on-off/OnOffServer.js +3 -3
- package/dist/esm/behaviors/on-off/OnOffServer.js.map +1 -1
- package/dist/esm/behaviors/operational-credentials/OperationalCredentialsServer.d.ts.map +1 -1
- package/dist/esm/behaviors/operational-credentials/OperationalCredentialsServer.js +15 -9
- package/dist/esm/behaviors/operational-credentials/OperationalCredentialsServer.js.map +1 -1
- package/dist/esm/endpoint/Endpoint.js +1 -1
- package/dist/esm/endpoint/Endpoint.js.map +1 -1
- package/dist/esm/node/Node.d.ts +1 -1
- package/dist/esm/node/Node.d.ts.map +1 -1
- package/dist/esm/node/Node.js +1 -1
- package/dist/esm/node/Node.js.map +1 -1
- package/dist/esm/node/ServerNode.d.ts +1 -1
- package/dist/esm/node/ServerNode.d.ts.map +1 -1
- package/dist/esm/node/ServerNode.js +1 -1
- package/dist/esm/node/client/NodePeerAddressStore.d.ts +1 -1
- package/dist/esm/node/client/NodePeerAddressStore.d.ts.map +1 -1
- package/dist/esm/node/server/InteractionServer.d.ts +2 -2
- package/dist/esm/node/server/InteractionServer.d.ts.map +1 -1
- package/dist/esm/node/server/InteractionServer.js +2 -2
- package/dist/esm/node/server/InteractionServer.js.map +1 -1
- package/dist/esm/node/server/ServerSubscription.d.ts +4 -4
- package/dist/esm/node/server/ServerSubscription.d.ts.map +1 -1
- package/dist/esm/node/server/ServerSubscription.js.map +1 -1
- package/package.json +7 -7
- package/src/behavior/Behavior.ts +2 -2
- package/src/behavior/Transitions.ts +1 -1
- package/src/behavior/context/server/OnlineContext.ts +21 -19
- package/src/behavior/internal/ClientBehaviorBacking.ts +2 -1
- package/src/behavior/system/commissioning/CommissioningClient.ts +1 -1
- package/src/behavior/system/network/NetworkBehavior.ts +2 -1
- package/src/behavior/system/network/ServerGroupNetworking.ts +150 -0
- package/src/behavior/system/network/ServerNetworkRuntime.ts +27 -5
- package/src/behavior/system/sessions/SessionsBehavior.ts +5 -5
- package/src/behaviors/access-control/AccessControlServer.ts +21 -14
- package/src/behaviors/color-control/ColorControlServer.ts +4 -4
- package/src/behaviors/general-commissioning/GeneralCommissioningServer.ts +8 -7
- package/src/behaviors/general-commissioning/ServerNodeFailsafeContext.ts +1 -1
- package/src/behaviors/group-key-management/GroupKeyManagementServer.ts +441 -26
- package/src/behaviors/groups/GroupsServer.ts +181 -3
- package/src/behaviors/level-control/LevelControlServer.ts +5 -5
- package/src/behaviors/mode-select/ModeSelectServer.ts +3 -3
- package/src/behaviors/on-off/OnOffServer.ts +3 -3
- package/src/behaviors/operational-credentials/OperationalCredentialsServer.ts +17 -9
- package/src/endpoint/Endpoint.ts +1 -1
- package/src/node/Node.ts +1 -1
- package/src/node/ServerNode.ts +1 -1
- package/src/node/client/NodePeerAddressStore.ts +1 -1
- package/src/node/server/InteractionServer.ts +5 -6
- package/src/node/server/ServerSubscription.ts +3 -4
|
@@ -4,41 +4,363 @@
|
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { ActionContext } from "#behavior/context/ActionContext.js";
|
|
8
|
+
import { GroupsBehavior } from "#behaviors/groups";
|
|
7
9
|
import { GroupKeyManagement } from "#clusters/group-key-management";
|
|
8
|
-
import { ImplementationError, MaybePromise } from "#general";
|
|
9
|
-
import {
|
|
10
|
+
import { deepCopy, ImplementationError, Logger, MaybePromise } from "#general";
|
|
11
|
+
import { DatatypeModel, FieldElement } from "#model";
|
|
12
|
+
import { NodeLifecycle } from "#node/NodeLifecycle.js";
|
|
13
|
+
import { Fabric, FabricManager, SecureSession } from "#protocol";
|
|
14
|
+
import { EndpointNumber, FabricIndex, GroupId, StatusCode, StatusResponseError } from "#types";
|
|
10
15
|
import { GroupKeyManagementBehavior } from "./GroupKeyManagementBehavior.js";
|
|
11
16
|
|
|
17
|
+
const logger = Logger.get("GroupKeyManagementServer");
|
|
18
|
+
|
|
19
|
+
const MAX_64BIT_TIME = BigInt("0xffffffffffffffff");
|
|
20
|
+
|
|
21
|
+
// Enhance the schema by a fabric scoped structure for the GroupKeySetStruct to enable persistence
|
|
22
|
+
const groupKeySetStruct = GroupKeyManagementBehavior.schema!.get(DatatypeModel, "GroupKeySetStruct")!;
|
|
23
|
+
const groupKeySetStructFS = groupKeySetStruct.extend({
|
|
24
|
+
name: "GroupKeySetStructFS",
|
|
25
|
+
children: [FieldElement({ name: "FabricIndex", id: 0xfe, type: "FabricIndex" })],
|
|
26
|
+
});
|
|
27
|
+
const schema = GroupKeyManagementBehavior.schema!.extend({
|
|
28
|
+
children: [
|
|
29
|
+
groupKeySetStructFS,
|
|
30
|
+
FieldElement({ name: "groupKeySets", type: "GroupKeySetStructFS", quality: "N", access: "RW VM F" }),
|
|
31
|
+
],
|
|
32
|
+
});
|
|
33
|
+
|
|
12
34
|
/**
|
|
13
35
|
* This is the default server implementation of {@link GroupKeyManagementBehavior}.
|
|
14
36
|
*/
|
|
15
37
|
export class GroupKeyManagementServer extends GroupKeyManagementBehavior {
|
|
38
|
+
declare state: GroupKeyManagementServer.State;
|
|
39
|
+
schema = schema;
|
|
40
|
+
|
|
16
41
|
override initialize(): MaybePromise {
|
|
17
|
-
if (this.
|
|
18
|
-
throw new ImplementationError("
|
|
42
|
+
if (this.features.cacheAndSync) {
|
|
43
|
+
throw new ImplementationError("The CacheAndSync feature is provisional. Do not use it.");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Initialize if not persisted to enable persistence
|
|
47
|
+
if (this.state.groupKeySets === undefined) {
|
|
48
|
+
this.state.groupKeySets = [];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Validate the state
|
|
52
|
+
this.reactTo(this.events.groupKeyMap$Changing, this.#validateGroupKeyMap);
|
|
53
|
+
this.reactTo(this.events.groupTable$Changing, this.#validateGroupTable);
|
|
54
|
+
|
|
55
|
+
// Update the group key map when the group key sets change
|
|
56
|
+
this.reactTo(this.events.groupKeyMap$Changed, this.#updateGroupKeyMap);
|
|
57
|
+
|
|
58
|
+
const lifecycle = this.endpoint.lifecycle as NodeLifecycle;
|
|
59
|
+
this.reactTo(lifecycle.online, this.#online);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async #online() {
|
|
63
|
+
// Validate the maximum supported group keys and groups per fabric if they are set to minimum values.
|
|
64
|
+
if (this.state.maxGroupKeysPerFabric === 0 && this.state.maxGroupsPerFabric === 1) {
|
|
65
|
+
// We assume unchanged defaults
|
|
66
|
+
let groupsFound = false;
|
|
67
|
+
this.endpoint.visit(endpoint => {
|
|
68
|
+
if (!groupsFound && endpoint.behaviors.has(GroupsBehavior)) {
|
|
69
|
+
groupsFound = true;
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
if (groupsFound) {
|
|
73
|
+
throw new ImplementationError(
|
|
74
|
+
"One of the device types you use has a Groups cluster. Please adjust the Group Key Management cluster maximum defaults. You need to support groups management.",
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const fabrics = this.env.get(FabricManager);
|
|
80
|
+
|
|
81
|
+
// TODO handle delete fabric more generically later to remove fabric scoped data
|
|
82
|
+
// Added fabric always have no groups, so no need to initialize anything on adding the fabric
|
|
83
|
+
|
|
84
|
+
// Fabric was updated, so basically newly created, so we need to reinitialize the group key sets
|
|
85
|
+
this.reactTo(fabrics.events.updated, this.#handleFabricUpdate);
|
|
86
|
+
|
|
87
|
+
// When we have group key sets, we need to ensure that they are initialized on the Fabric group manager
|
|
88
|
+
if (this.state.groupKeySets.length) {
|
|
89
|
+
const groupKeysForFabric = new Map<FabricIndex, GroupKeyManagement.GroupKeySet[]>();
|
|
90
|
+
for (const groupKeySet of this.state.groupKeySets) {
|
|
91
|
+
const fabricIndex = groupKeySet.fabricIndex;
|
|
92
|
+
const keys = groupKeysForFabric.get(fabricIndex) ?? [];
|
|
93
|
+
keys.push(groupKeySet);
|
|
94
|
+
groupKeysForFabric.set(fabricIndex, keys);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
for (const [fabricIndex, keys] of groupKeysForFabric.entries()) {
|
|
98
|
+
const fabric = fabrics.for(fabricIndex);
|
|
99
|
+
for (const groupKeySet of keys) {
|
|
100
|
+
await fabric.groups.setFromGroupKeySet(groupKeySet);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (this.state.groupKeyMap.length) {
|
|
105
|
+
this.#updateGroupKeyMap(this.state.groupKeyMap);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Handle the recreation (update) of a fabric, so we need to reinitialize the group key sets */
|
|
110
|
+
async #handleFabricUpdate(fabric: Fabric) {
|
|
111
|
+
if (this.state.groupKeySets.length === 0) {
|
|
112
|
+
return; // No group key sets, so nothing to do
|
|
113
|
+
}
|
|
114
|
+
const fabricIndex = fabric.fabricIndex;
|
|
115
|
+
const groupKeysForFabric = this.state.groupKeySets.filter(
|
|
116
|
+
({ fabricIndex: entryIndex }) => entryIndex === fabricIndex,
|
|
117
|
+
);
|
|
118
|
+
for (const groupKeySet of groupKeysForFabric) {
|
|
119
|
+
await fabric.groups.setFromGroupKeySet(groupKeySet);
|
|
120
|
+
}
|
|
121
|
+
if (this.state.groupKeyMap.length) {
|
|
122
|
+
fabric.groups.groupKeyIdMap = new Map<GroupId, number>(
|
|
123
|
+
this.state.groupKeyMap
|
|
124
|
+
.filter(({ fabricIndex: entryIndex }) => entryIndex === fabricIndex)
|
|
125
|
+
.map(({ groupId, groupKeySetId }) => [groupId, groupKeySetId]),
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
if (this.state.groupTable.length) {
|
|
129
|
+
// Initialize the group table for the fabric
|
|
130
|
+
const groupTable = this.state.groupTable.filter(
|
|
131
|
+
({ fabricIndex: entryIndex }) => entryIndex === fabricIndex,
|
|
132
|
+
);
|
|
133
|
+
fabric.groups.endpoints.clear();
|
|
134
|
+
for (const entry of groupTable) {
|
|
135
|
+
fabric.groups.endpoints.set(entry.groupId, entry.endpoints);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
#validateGroupKeyMap(groupKeyMap: GroupKeyManagement.GroupKeyMap[]) {
|
|
141
|
+
const knownGroupKeys = new Set<string>();
|
|
142
|
+
for (const keySetId of this.state.groupKeySets) {
|
|
143
|
+
const { groupKeySetId, fabricIndex } = keySetId;
|
|
144
|
+
const id = `${fabricIndex}-${groupKeySetId}`;
|
|
145
|
+
knownGroupKeys.add(id);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const knownGroupIds = new Set<string>();
|
|
149
|
+
const groupIdsPerFabric = new Array<number>();
|
|
150
|
+
for (const entry of groupKeyMap) {
|
|
151
|
+
const { groupId, fabricIndex } = entry;
|
|
152
|
+
if (!GroupId.isApplicationGroupId(groupId)) {
|
|
153
|
+
throw new StatusResponseError(
|
|
154
|
+
"Only operational GroupIds are allowed in GroupKeyMap",
|
|
155
|
+
StatusCode.InvalidAction,
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
groupIdsPerFabric[fabricIndex] = (groupIdsPerFabric[fabricIndex] ?? 0) + 1;
|
|
160
|
+
const id = `${fabricIndex}-${groupId}`;
|
|
161
|
+
if (knownGroupIds.has(id)) {
|
|
162
|
+
throw new StatusResponseError(
|
|
163
|
+
`Duplicate GroupId ${groupId} for FabricIndex ${fabricIndex}`,
|
|
164
|
+
StatusCode.ConstraintError,
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
knownGroupIds.add(id);
|
|
168
|
+
|
|
169
|
+
// The spec does not mandate to check if a key set exists where a group is added for and Tests also try it
|
|
170
|
+
/*const keyId = `${fabricIndex}-${groupKeySetId}`;
|
|
171
|
+
if (!knownGroupKeys.has(keyId)) {
|
|
172
|
+
throw new StatusResponseError(
|
|
173
|
+
`GroupKeySetId ${groupKeySetId} for FabricIndex ${fabricIndex} not found`,
|
|
174
|
+
StatusCode.ConstraintError,
|
|
175
|
+
);
|
|
176
|
+
}*/
|
|
177
|
+
}
|
|
178
|
+
if (groupIdsPerFabric.some(count => count > this.state.maxGroupsPerFabric)) {
|
|
179
|
+
throw new StatusResponseError(
|
|
180
|
+
`Too many groups per fabric, maximum is ${this.state.maxGroupsPerFabric}`,
|
|
181
|
+
StatusCode.ResourceExhausted,
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
#validateGroupTable(groupTable: GroupKeyManagement.GroupInfoMap[]) {
|
|
187
|
+
const knownGroupIds = new Set<string>();
|
|
188
|
+
for (const entry of groupTable) {
|
|
189
|
+
const { groupId, fabricIndex, endpoints } = entry;
|
|
190
|
+
if (groupId === 0) {
|
|
191
|
+
throw new StatusResponseError(
|
|
192
|
+
"GroupId 0 can not be used as operational group id",
|
|
193
|
+
StatusCode.ConstraintError,
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const id = `${fabricIndex}-${groupId}`;
|
|
198
|
+
if (knownGroupIds.has(id)) {
|
|
199
|
+
throw new StatusResponseError(
|
|
200
|
+
`Duplicate GroupId ${groupId} for FabricIndex ${fabricIndex}`,
|
|
201
|
+
StatusCode.ConstraintError,
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
knownGroupIds.add(id);
|
|
205
|
+
|
|
206
|
+
const endpointIds = new Set<number>(endpoints);
|
|
207
|
+
if (endpointIds.size !== endpoints.length) {
|
|
208
|
+
throw new StatusResponseError(
|
|
209
|
+
`Duplicate endpoint IDs in GroupId ${groupId} for FabricIndex ${fabricIndex}`,
|
|
210
|
+
StatusCode.ConstraintError,
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
#updateGroupKeyMap(
|
|
217
|
+
groupKeyMap: GroupKeyManagement.GroupKeyMap[],
|
|
218
|
+
_oldMap?: GroupKeyManagement.GroupKeyMap[],
|
|
219
|
+
context?: ActionContext,
|
|
220
|
+
) {
|
|
221
|
+
if (context !== undefined && !context?.offline) {
|
|
222
|
+
const fabric = context.session?.associatedFabric;
|
|
223
|
+
const fabricIndex = fabric?.fabricIndex;
|
|
224
|
+
|
|
225
|
+
// Online context, so we can just update the relevant fabric
|
|
226
|
+
if (fabric !== undefined && fabricIndex !== undefined) {
|
|
227
|
+
fabric.groups.groupKeyIdMap = new Map<GroupId, number>(
|
|
228
|
+
groupKeyMap
|
|
229
|
+
.filter(({ fabricIndex: entryIndex }) => entryIndex === fabricIndex)
|
|
230
|
+
.map(({ groupId, groupKeySetId }) => [groupId, groupKeySetId]),
|
|
231
|
+
);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const fabrics = this.env.get(FabricManager);
|
|
237
|
+
const fabricMaps = new Map<FabricIndex, Map<GroupId, number>>();
|
|
238
|
+
for (const entry of groupKeyMap) {
|
|
239
|
+
const { fabricIndex, groupId, groupKeySetId } = entry;
|
|
240
|
+
|
|
241
|
+
const fabricMap = fabricMaps.get(fabricIndex) ?? new Map<GroupId, number>();
|
|
242
|
+
fabricMap.set(groupId, groupKeySetId);
|
|
243
|
+
fabricMaps.set(fabricIndex, fabricMap);
|
|
19
244
|
}
|
|
20
|
-
|
|
21
|
-
|
|
245
|
+
for (const fabric of fabrics) {
|
|
246
|
+
const fabricIndex = fabric.fabricIndex;
|
|
247
|
+
const map = fabricMaps.get(fabricIndex);
|
|
248
|
+
if (map === undefined) {
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
fabric.groups.groupKeyIdMap = map;
|
|
22
252
|
}
|
|
23
|
-
this.state.groupTable = [];
|
|
24
253
|
}
|
|
25
254
|
|
|
26
|
-
override keySetWrite(
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
255
|
+
override async keySetWrite({ groupKeySet }: GroupKeyManagement.KeySetWriteRequest) {
|
|
256
|
+
SecureSession.assert(this.session);
|
|
257
|
+
|
|
258
|
+
const {
|
|
259
|
+
groupKeySetId,
|
|
260
|
+
epochKey0,
|
|
261
|
+
epochKey1,
|
|
262
|
+
epochKey2,
|
|
263
|
+
epochStartTime0,
|
|
264
|
+
epochStartTime1,
|
|
265
|
+
epochStartTime2,
|
|
266
|
+
groupKeySecurityPolicy,
|
|
267
|
+
groupKeyMulticastPolicy = GroupKeyManagement.GroupKeyMulticastPolicy.PerGroupId,
|
|
268
|
+
} = groupKeySet;
|
|
269
|
+
|
|
270
|
+
// Unclear if that should be checked here, but it basically only makes sense here
|
|
271
|
+
// An epoch key marked with the maximum start time SHALL be disabled and render the corresponding epoch
|
|
272
|
+
// key slot unused.
|
|
273
|
+
if (groupKeySet.epochStartTime0 === MAX_64BIT_TIME) {
|
|
274
|
+
groupKeySet.epochStartTime0 = null;
|
|
275
|
+
groupKeySet.epochKey0 = null;
|
|
276
|
+
}
|
|
277
|
+
if (groupKeySet.epochStartTime1 === MAX_64BIT_TIME) {
|
|
278
|
+
groupKeySet.epochStartTime1 = null;
|
|
279
|
+
groupKeySet.epochKey1 = null;
|
|
280
|
+
}
|
|
281
|
+
if (groupKeySet.epochStartTime2 === MAX_64BIT_TIME) {
|
|
282
|
+
groupKeySet.epochStartTime2 = null;
|
|
283
|
+
groupKeySet.epochKey2 = null;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (epochKey0 === null || epochStartTime0 === null) {
|
|
287
|
+
throw new StatusResponseError("EpochKey0 and EpochStartTime0 must be set", StatusCode.InvalidCommand);
|
|
288
|
+
}
|
|
289
|
+
if (epochStartTime0 === 0) {
|
|
290
|
+
throw new StatusResponseError("EpochStartTime0 must not be 0", StatusCode.InvalidCommand);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (epochKey1 !== null && (epochStartTime1 === null || epochStartTime1 <= epochStartTime0)) {
|
|
294
|
+
throw new StatusResponseError(
|
|
295
|
+
"EpochStartTime1 must be set and greater than EpochStartTime0",
|
|
296
|
+
StatusCode.InvalidCommand,
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
if (epochKey1 === null && epochStartTime1 !== null) {
|
|
300
|
+
throw new StatusResponseError("EpochKey1 must be set if EpochStartTime1 is set", StatusCode.InvalidCommand);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (epochKey2 !== null && epochKey1 === null) {
|
|
304
|
+
throw new StatusResponseError("EpochKey1 must be set if EpochKey2 is set", StatusCode.InvalidCommand);
|
|
305
|
+
}
|
|
306
|
+
if (
|
|
307
|
+
epochKey2 !== null &&
|
|
308
|
+
(epochStartTime2 === null || epochStartTime1 === null || epochStartTime2 <= epochStartTime1)
|
|
309
|
+
) {
|
|
310
|
+
throw new StatusResponseError(
|
|
311
|
+
"EpochStartTime2 must be set and greater than EpochStartTime1",
|
|
312
|
+
StatusCode.InvalidCommand,
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
if (epochKey2 === null && epochStartTime2 !== null) {
|
|
316
|
+
throw new StatusResponseError("EpochKey2 must be set if EpochStartTime2 is set", StatusCode.InvalidCommand);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (groupKeySecurityPolicy !== GroupKeyManagement.GroupKeySecurityPolicy.TrustFirst) {
|
|
320
|
+
throw new StatusResponseError("GroupKeySecurityPolicy must be TrustFirst", StatusCode.InvalidCommand);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// GroupKeyMulticastPolicy is provisional and PerGroupId is the default, so do not allow other values for now
|
|
324
|
+
if (groupKeyMulticastPolicy !== GroupKeyManagement.GroupKeyMulticastPolicy.PerGroupId) {
|
|
325
|
+
throw new StatusResponseError("GroupKeyMulticastPolicy must be PerGroupId", StatusCode.InvalidCommand);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const fabric = this.session.associatedFabric;
|
|
329
|
+
const fabricIndex = fabric.fabricIndex;
|
|
330
|
+
|
|
331
|
+
// Replace or add the group key set to the internal persisted state
|
|
332
|
+
const existingIndex = this.state.groupKeySets.findIndex(
|
|
333
|
+
({ groupKeySetId: entryId, fabricIndex: entryIndex }) =>
|
|
334
|
+
entryIndex === fabricIndex && entryId === groupKeySetId,
|
|
30
335
|
);
|
|
336
|
+
if (existingIndex !== -1) {
|
|
337
|
+
// Update existing group key set
|
|
338
|
+
this.state.groupKeySets[existingIndex] = { ...groupKeySet, fabricIndex };
|
|
339
|
+
} else {
|
|
340
|
+
// Add new group key set
|
|
341
|
+
const keySetsOfFabric = this.state.groupKeySets.filter(
|
|
342
|
+
({ fabricIndex: entryIndex }) => entryIndex === fabricIndex,
|
|
343
|
+
).length;
|
|
344
|
+
if (keySetsOfFabric >= this.state.maxGroupKeysPerFabric) {
|
|
345
|
+
throw new StatusResponseError(
|
|
346
|
+
`Too many group key sets for fabric ${fabricIndex}, maximum is ${this.state.maxGroupKeysPerFabric}`,
|
|
347
|
+
StatusCode.ResourceExhausted,
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
this.state.groupKeySets.push({ ...groupKeySet, fabricIndex });
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Update the Fabric group manager to kick off the internal processes
|
|
354
|
+
await fabric.groups.setFromGroupKeySet(groupKeySet);
|
|
31
355
|
}
|
|
32
356
|
|
|
33
357
|
override keySetRead({
|
|
34
358
|
groupKeySetId,
|
|
35
|
-
}: GroupKeyManagement.KeySetReadRequest):
|
|
36
|
-
|
|
37
|
-
throw new ImplementationError("Session must be defined");
|
|
38
|
-
}
|
|
39
|
-
const fabric = this.context.session.associatedFabric;
|
|
359
|
+
}: GroupKeyManagement.KeySetReadRequest): GroupKeyManagement.KeySetReadResponse {
|
|
360
|
+
const fabric = this.session.associatedFabric;
|
|
40
361
|
|
|
41
|
-
|
|
362
|
+
// We use the fabric group manager to retrieve the group key set because he also has the id 0 and is synced anyway
|
|
363
|
+
const groupKeySet = fabric.groups.keySets.asGroupKeySet(groupKeySetId);
|
|
42
364
|
if (groupKeySet === undefined) {
|
|
43
365
|
throw new StatusResponseError(`GroupKeySet ${groupKeySetId} not found`, StatusCode.NotFound);
|
|
44
366
|
}
|
|
@@ -53,25 +375,118 @@ export class GroupKeyManagementServer extends GroupKeyManagementBehavior {
|
|
|
53
375
|
};
|
|
54
376
|
}
|
|
55
377
|
|
|
56
|
-
override keySetRemove({ groupKeySetId }: GroupKeyManagement.KeySetRemoveRequest)
|
|
378
|
+
override async keySetRemove({ groupKeySetId }: GroupKeyManagement.KeySetRemoveRequest) {
|
|
57
379
|
if (groupKeySetId === 0) {
|
|
58
380
|
throw new StatusResponseError(`GroupKeySet ${groupKeySetId} cannot be removed`, StatusCode.InvalidCommand);
|
|
59
381
|
}
|
|
60
382
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
383
|
+
const fabric = this.session.associatedFabric;
|
|
384
|
+
const fabricIndex = fabric.fabricIndex;
|
|
64
385
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
386
|
+
// Replace or add the group key set to the internal persisted state
|
|
387
|
+
const existingIndex = this.state.groupKeySets.findIndex(
|
|
388
|
+
({ groupKeySetId: entryId, fabricIndex: entryIndex }) =>
|
|
389
|
+
entryIndex === fabricIndex && entryId === groupKeySetId,
|
|
390
|
+
);
|
|
391
|
+
if (existingIndex === -1) {
|
|
392
|
+
throw new StatusResponseError(`GroupKeySet ${groupKeySetId} not found`, StatusCode.NotFound);
|
|
68
393
|
}
|
|
69
|
-
|
|
394
|
+
this.state.groupKeySets.splice(existingIndex, 1);
|
|
70
395
|
|
|
71
|
-
|
|
396
|
+
// If there exist any entries for the accessing fabric within the GroupKeyMap attribute that refer to the
|
|
397
|
+
// GroupKeySetID just removed, then these entries SHALL be removed from that list.
|
|
398
|
+
const groupKeyMap = deepCopy(this.state.groupKeyMap);
|
|
399
|
+
this.state.groupKeyMap = groupKeyMap.filter(({ groupKeySetId: entryId }) => groupKeySetId !== entryId);
|
|
400
|
+
|
|
401
|
+
// Sync to Fabric group manager to remove too
|
|
402
|
+
await fabric.groups.removeGroupKeySet(groupKeySetId);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
override keySetReadAllIndices(): GroupKeyManagement.KeySetReadAllIndicesResponse {
|
|
406
|
+
const fabric = this.session.associatedFabric;
|
|
407
|
+
const fabricIndex = fabric.fabricIndex;
|
|
408
|
+
|
|
409
|
+
const groupKeySetIDs = this.state.groupKeySets
|
|
410
|
+
.filter(({ fabricIndex: entryIndex }) => entryIndex === fabricIndex)
|
|
411
|
+
.map(({ groupKeySetId }) => groupKeySetId);
|
|
412
|
+
groupKeySetIDs.unshift(0); // Always include the group key set 0
|
|
72
413
|
|
|
73
414
|
return {
|
|
74
415
|
groupKeySetIDs,
|
|
75
416
|
};
|
|
76
417
|
}
|
|
418
|
+
|
|
419
|
+
addEndpointForGroup(fabric: Fabric, groupId: GroupId, endpointId: EndpointNumber, groupName: string): void {
|
|
420
|
+
const fabricIndex = fabric.fabricIndex;
|
|
421
|
+
// Add the group to the group table
|
|
422
|
+
const existingGroupIndex = this.state.groupTable.findIndex(
|
|
423
|
+
entry => entry.groupId === groupId && entry.fabricIndex === fabricIndex,
|
|
424
|
+
);
|
|
425
|
+
if (existingGroupIndex !== -1) {
|
|
426
|
+
// If the group already exists, add the endpoint
|
|
427
|
+
if (!this.state.groupTable[existingGroupIndex].endpoints.includes(endpointId)) {
|
|
428
|
+
this.state.groupTable[existingGroupIndex].endpoints.push(endpointId);
|
|
429
|
+
fabric.groups.endpoints.set(groupId, this.state.groupTable[existingGroupIndex].endpoints);
|
|
430
|
+
}
|
|
431
|
+
this.state.groupTable[existingGroupIndex].groupName = groupName;
|
|
432
|
+
} else {
|
|
433
|
+
// Create a new group entry
|
|
434
|
+
this.state.groupTable.push({
|
|
435
|
+
groupId,
|
|
436
|
+
endpoints: [endpointId],
|
|
437
|
+
groupName,
|
|
438
|
+
fabricIndex,
|
|
439
|
+
});
|
|
440
|
+
fabric.groups.endpoints.set(groupId, [endpointId]);
|
|
441
|
+
}
|
|
442
|
+
logger.info(
|
|
443
|
+
`Added endpoint ${endpointId} to group ${groupId} on fabric ${fabricIndex} with name "${groupName}"`,
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Remove endpoint from the provided group, or all groups if no groupId is provided.
|
|
449
|
+
*/
|
|
450
|
+
removeEndpoint(fabric: Fabric, endpointId: EndpointNumber, groupId?: GroupId): boolean {
|
|
451
|
+
let existing = false;
|
|
452
|
+
// Remove the endpoint from the group table
|
|
453
|
+
const groupTable = this.state.groupTable;
|
|
454
|
+
const fabricIndex = fabric.fabricIndex;
|
|
455
|
+
|
|
456
|
+
// Remove the endpoint from all groups
|
|
457
|
+
for (const entry of groupTable) {
|
|
458
|
+
if (entry.fabricIndex !== fabricIndex || (groupId !== undefined && entry.groupId !== groupId)) {
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
const endpointExists = entry.endpoints.includes(endpointId);
|
|
462
|
+
if (endpointExists) {
|
|
463
|
+
if (entry.endpoints.length === 1 && entry.endpoints[0] === endpointId) {
|
|
464
|
+
const groupId = entry.groupId;
|
|
465
|
+
// If no endpoints left, remove the group entry
|
|
466
|
+
const index = groupTable.indexOf(entry);
|
|
467
|
+
groupTable.splice(index, 1);
|
|
468
|
+
fabric.groups.endpoints.delete(groupId);
|
|
469
|
+
} else {
|
|
470
|
+
entry.endpoints = entry.endpoints.filter(id => id !== endpointId);
|
|
471
|
+
fabric.groups.endpoints.set(entry.groupId, entry.endpoints);
|
|
472
|
+
}
|
|
473
|
+
existing = true;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
return existing;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
export namespace GroupKeyManagementServer {
|
|
481
|
+
export class State extends GroupKeyManagementBehavior.State {
|
|
482
|
+
/**
|
|
483
|
+
* Extended state to hold and persist the group key sets for this server. This structure contains all
|
|
484
|
+
* GroupKeySet entries for all fabrics beside the fabric specific entries of the groupKeyset 0
|
|
485
|
+
*/
|
|
486
|
+
groupKeySets: (GroupKeyManagement.GroupKeySet & { fabricIndex: FabricIndex })[] = [];
|
|
487
|
+
|
|
488
|
+
// Overwrite defaults to allow more than 3 group keys and 4 groups per fabric, because we can
|
|
489
|
+
override maxGroupKeysPerFabric = 20; // The Minimum would be 3;
|
|
490
|
+
override maxGroupsPerFabric = 21; // The Minimum would be 4;
|
|
491
|
+
}
|
|
77
492
|
}
|