@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.
Files changed (178) hide show
  1. package/dist/cjs/behavior/Behavior.d.ts +2 -1
  2. package/dist/cjs/behavior/Behavior.d.ts.map +1 -1
  3. package/dist/cjs/behavior/Behavior.js +1 -1
  4. package/dist/cjs/behavior/Behavior.js.map +1 -1
  5. package/dist/cjs/behavior/Transitions.js +2 -2
  6. package/dist/cjs/behavior/Transitions.js.map +1 -1
  7. package/dist/cjs/behavior/context/server/OnlineContext.d.ts +3 -3
  8. package/dist/cjs/behavior/context/server/OnlineContext.d.ts.map +1 -1
  9. package/dist/cjs/behavior/context/server/OnlineContext.js +12 -9
  10. package/dist/cjs/behavior/context/server/OnlineContext.js.map +1 -1
  11. package/dist/cjs/behavior/internal/ClientBehaviorBacking.d.ts +2 -1
  12. package/dist/cjs/behavior/internal/ClientBehaviorBacking.d.ts.map +1 -1
  13. package/dist/cjs/behavior/internal/ClientBehaviorBacking.js.map +1 -1
  14. package/dist/cjs/behavior/system/network/NetworkBehavior.d.ts.map +1 -1
  15. package/dist/cjs/behavior/system/network/NetworkBehavior.js +2 -1
  16. package/dist/cjs/behavior/system/network/NetworkBehavior.js.map +1 -1
  17. package/dist/cjs/behavior/system/network/ServerGroupNetworking.d.ts +18 -0
  18. package/dist/cjs/behavior/system/network/ServerGroupNetworking.d.ts.map +1 -0
  19. package/dist/cjs/behavior/system/network/ServerGroupNetworking.js +150 -0
  20. package/dist/cjs/behavior/system/network/ServerGroupNetworking.js.map +6 -0
  21. package/dist/cjs/behavior/system/network/ServerNetworkRuntime.d.ts.map +1 -1
  22. package/dist/cjs/behavior/system/network/ServerNetworkRuntime.js +25 -7
  23. package/dist/cjs/behavior/system/network/ServerNetworkRuntime.js.map +1 -1
  24. package/dist/cjs/behavior/system/sessions/SessionsBehavior.d.ts.map +1 -1
  25. package/dist/cjs/behavior/system/sessions/SessionsBehavior.js.map +1 -1
  26. package/dist/cjs/behaviors/access-control/AccessControlServer.d.ts.map +1 -1
  27. package/dist/cjs/behaviors/access-control/AccessControlServer.js +18 -12
  28. package/dist/cjs/behaviors/access-control/AccessControlServer.js.map +1 -1
  29. package/dist/cjs/behaviors/color-control/ColorControlServer.d.ts +4 -4
  30. package/dist/cjs/behaviors/color-control/ColorControlServer.d.ts.map +1 -1
  31. package/dist/cjs/behaviors/color-control/ColorControlServer.js +3 -3
  32. package/dist/cjs/behaviors/color-control/ColorControlServer.js.map +1 -1
  33. package/dist/cjs/behaviors/general-commissioning/GeneralCommissioningServer.d.ts.map +1 -1
  34. package/dist/cjs/behaviors/general-commissioning/GeneralCommissioningServer.js +5 -4
  35. package/dist/cjs/behaviors/general-commissioning/GeneralCommissioningServer.js.map +1 -1
  36. package/dist/cjs/behaviors/general-commissioning/ServerNodeFailsafeContext.d.ts +1 -1
  37. package/dist/cjs/behaviors/general-commissioning/ServerNodeFailsafeContext.d.ts.map +1 -1
  38. package/dist/cjs/behaviors/group-key-management/GroupKeyManagementServer.d.ts +27 -4
  39. package/dist/cjs/behaviors/group-key-management/GroupKeyManagementServer.d.ts.map +1 -1
  40. package/dist/cjs/behaviors/group-key-management/GroupKeyManagementServer.js +340 -21
  41. package/dist/cjs/behaviors/group-key-management/GroupKeyManagementServer.js.map +2 -2
  42. package/dist/cjs/behaviors/groups/GroupsServer.d.ts +19 -3
  43. package/dist/cjs/behaviors/groups/GroupsServer.d.ts.map +1 -1
  44. package/dist/cjs/behaviors/groups/GroupsServer.js +138 -1
  45. package/dist/cjs/behaviors/groups/GroupsServer.js.map +2 -2
  46. package/dist/cjs/behaviors/level-control/LevelControlServer.d.ts +5 -5
  47. package/dist/cjs/behaviors/level-control/LevelControlServer.d.ts.map +1 -1
  48. package/dist/cjs/behaviors/level-control/LevelControlServer.js +3 -3
  49. package/dist/cjs/behaviors/level-control/LevelControlServer.js.map +1 -1
  50. package/dist/cjs/behaviors/mode-select/ModeSelectServer.d.ts.map +1 -1
  51. package/dist/cjs/behaviors/mode-select/ModeSelectServer.js +3 -3
  52. package/dist/cjs/behaviors/mode-select/ModeSelectServer.js.map +1 -1
  53. package/dist/cjs/behaviors/on-off/OnOffServer.d.ts.map +1 -1
  54. package/dist/cjs/behaviors/on-off/OnOffServer.js +3 -3
  55. package/dist/cjs/behaviors/on-off/OnOffServer.js.map +1 -1
  56. package/dist/cjs/behaviors/operational-credentials/OperationalCredentialsServer.d.ts.map +1 -1
  57. package/dist/cjs/behaviors/operational-credentials/OperationalCredentialsServer.js +14 -8
  58. package/dist/cjs/behaviors/operational-credentials/OperationalCredentialsServer.js.map +1 -1
  59. package/dist/cjs/endpoint/Endpoint.js +1 -1
  60. package/dist/cjs/endpoint/Endpoint.js.map +1 -1
  61. package/dist/cjs/node/Node.d.ts +1 -1
  62. package/dist/cjs/node/Node.d.ts.map +1 -1
  63. package/dist/cjs/node/Node.js +2 -2
  64. package/dist/cjs/node/Node.js.map +1 -1
  65. package/dist/cjs/node/ServerNode.d.ts +1 -1
  66. package/dist/cjs/node/ServerNode.d.ts.map +1 -1
  67. package/dist/cjs/node/ServerNode.js +2 -2
  68. package/dist/cjs/node/ServerNode.js.map +1 -1
  69. package/dist/cjs/node/client/NodePeerAddressStore.d.ts +1 -1
  70. package/dist/cjs/node/client/NodePeerAddressStore.d.ts.map +1 -1
  71. package/dist/cjs/node/server/InteractionServer.d.ts +2 -2
  72. package/dist/cjs/node/server/InteractionServer.d.ts.map +1 -1
  73. package/dist/cjs/node/server/InteractionServer.js +1 -1
  74. package/dist/cjs/node/server/InteractionServer.js.map +1 -1
  75. package/dist/cjs/node/server/ServerSubscription.d.ts +4 -4
  76. package/dist/cjs/node/server/ServerSubscription.d.ts.map +1 -1
  77. package/dist/cjs/node/server/ServerSubscription.js.map +1 -1
  78. package/dist/esm/behavior/Behavior.d.ts +2 -1
  79. package/dist/esm/behavior/Behavior.d.ts.map +1 -1
  80. package/dist/esm/behavior/Behavior.js +2 -2
  81. package/dist/esm/behavior/Behavior.js.map +1 -1
  82. package/dist/esm/behavior/Transitions.js +1 -1
  83. package/dist/esm/behavior/context/server/OnlineContext.d.ts +3 -3
  84. package/dist/esm/behavior/context/server/OnlineContext.d.ts.map +1 -1
  85. package/dist/esm/behavior/context/server/OnlineContext.js +13 -10
  86. package/dist/esm/behavior/context/server/OnlineContext.js.map +1 -1
  87. package/dist/esm/behavior/internal/ClientBehaviorBacking.d.ts +2 -1
  88. package/dist/esm/behavior/internal/ClientBehaviorBacking.d.ts.map +1 -1
  89. package/dist/esm/behavior/internal/ClientBehaviorBacking.js.map +1 -1
  90. package/dist/esm/behavior/system/network/NetworkBehavior.d.ts.map +1 -1
  91. package/dist/esm/behavior/system/network/NetworkBehavior.js +2 -1
  92. package/dist/esm/behavior/system/network/NetworkBehavior.js.map +1 -1
  93. package/dist/esm/behavior/system/network/ServerGroupNetworking.d.ts +18 -0
  94. package/dist/esm/behavior/system/network/ServerGroupNetworking.d.ts.map +1 -0
  95. package/dist/esm/behavior/system/network/ServerGroupNetworking.js +130 -0
  96. package/dist/esm/behavior/system/network/ServerGroupNetworking.js.map +6 -0
  97. package/dist/esm/behavior/system/network/ServerNetworkRuntime.d.ts.map +1 -1
  98. package/dist/esm/behavior/system/network/ServerNetworkRuntime.js +23 -5
  99. package/dist/esm/behavior/system/network/ServerNetworkRuntime.js.map +1 -1
  100. package/dist/esm/behavior/system/sessions/SessionsBehavior.d.ts.map +1 -1
  101. package/dist/esm/behavior/system/sessions/SessionsBehavior.js.map +1 -1
  102. package/dist/esm/behaviors/access-control/AccessControlServer.d.ts.map +1 -1
  103. package/dist/esm/behaviors/access-control/AccessControlServer.js +20 -13
  104. package/dist/esm/behaviors/access-control/AccessControlServer.js.map +1 -1
  105. package/dist/esm/behaviors/color-control/ColorControlServer.d.ts +4 -4
  106. package/dist/esm/behaviors/color-control/ColorControlServer.d.ts.map +1 -1
  107. package/dist/esm/behaviors/color-control/ColorControlServer.js +3 -3
  108. package/dist/esm/behaviors/color-control/ColorControlServer.js.map +1 -1
  109. package/dist/esm/behaviors/general-commissioning/GeneralCommissioningServer.d.ts.map +1 -1
  110. package/dist/esm/behaviors/general-commissioning/GeneralCommissioningServer.js +6 -5
  111. package/dist/esm/behaviors/general-commissioning/GeneralCommissioningServer.js.map +1 -1
  112. package/dist/esm/behaviors/general-commissioning/ServerNodeFailsafeContext.d.ts +1 -1
  113. package/dist/esm/behaviors/general-commissioning/ServerNodeFailsafeContext.d.ts.map +1 -1
  114. package/dist/esm/behaviors/group-key-management/GroupKeyManagementServer.d.ts +27 -4
  115. package/dist/esm/behaviors/group-key-management/GroupKeyManagementServer.d.ts.map +1 -1
  116. package/dist/esm/behaviors/group-key-management/GroupKeyManagementServer.js +342 -23
  117. package/dist/esm/behaviors/group-key-management/GroupKeyManagementServer.js.map +2 -2
  118. package/dist/esm/behaviors/groups/GroupsServer.d.ts +19 -3
  119. package/dist/esm/behaviors/groups/GroupsServer.d.ts.map +1 -1
  120. package/dist/esm/behaviors/groups/GroupsServer.js +147 -1
  121. package/dist/esm/behaviors/groups/GroupsServer.js.map +1 -1
  122. package/dist/esm/behaviors/level-control/LevelControlServer.d.ts +5 -5
  123. package/dist/esm/behaviors/level-control/LevelControlServer.d.ts.map +1 -1
  124. package/dist/esm/behaviors/level-control/LevelControlServer.js +3 -3
  125. package/dist/esm/behaviors/level-control/LevelControlServer.js.map +1 -1
  126. package/dist/esm/behaviors/mode-select/ModeSelectServer.d.ts.map +1 -1
  127. package/dist/esm/behaviors/mode-select/ModeSelectServer.js +3 -3
  128. package/dist/esm/behaviors/mode-select/ModeSelectServer.js.map +1 -1
  129. package/dist/esm/behaviors/on-off/OnOffServer.d.ts.map +1 -1
  130. package/dist/esm/behaviors/on-off/OnOffServer.js +3 -3
  131. package/dist/esm/behaviors/on-off/OnOffServer.js.map +1 -1
  132. package/dist/esm/behaviors/operational-credentials/OperationalCredentialsServer.d.ts.map +1 -1
  133. package/dist/esm/behaviors/operational-credentials/OperationalCredentialsServer.js +15 -9
  134. package/dist/esm/behaviors/operational-credentials/OperationalCredentialsServer.js.map +1 -1
  135. package/dist/esm/endpoint/Endpoint.js +1 -1
  136. package/dist/esm/endpoint/Endpoint.js.map +1 -1
  137. package/dist/esm/node/Node.d.ts +1 -1
  138. package/dist/esm/node/Node.d.ts.map +1 -1
  139. package/dist/esm/node/Node.js +1 -1
  140. package/dist/esm/node/Node.js.map +1 -1
  141. package/dist/esm/node/ServerNode.d.ts +1 -1
  142. package/dist/esm/node/ServerNode.d.ts.map +1 -1
  143. package/dist/esm/node/ServerNode.js +1 -1
  144. package/dist/esm/node/client/NodePeerAddressStore.d.ts +1 -1
  145. package/dist/esm/node/client/NodePeerAddressStore.d.ts.map +1 -1
  146. package/dist/esm/node/server/InteractionServer.d.ts +2 -2
  147. package/dist/esm/node/server/InteractionServer.d.ts.map +1 -1
  148. package/dist/esm/node/server/InteractionServer.js +2 -2
  149. package/dist/esm/node/server/InteractionServer.js.map +1 -1
  150. package/dist/esm/node/server/ServerSubscription.d.ts +4 -4
  151. package/dist/esm/node/server/ServerSubscription.d.ts.map +1 -1
  152. package/dist/esm/node/server/ServerSubscription.js.map +1 -1
  153. package/package.json +7 -7
  154. package/src/behavior/Behavior.ts +2 -2
  155. package/src/behavior/Transitions.ts +1 -1
  156. package/src/behavior/context/server/OnlineContext.ts +21 -19
  157. package/src/behavior/internal/ClientBehaviorBacking.ts +2 -1
  158. package/src/behavior/system/commissioning/CommissioningClient.ts +1 -1
  159. package/src/behavior/system/network/NetworkBehavior.ts +2 -1
  160. package/src/behavior/system/network/ServerGroupNetworking.ts +150 -0
  161. package/src/behavior/system/network/ServerNetworkRuntime.ts +27 -5
  162. package/src/behavior/system/sessions/SessionsBehavior.ts +5 -5
  163. package/src/behaviors/access-control/AccessControlServer.ts +21 -14
  164. package/src/behaviors/color-control/ColorControlServer.ts +4 -4
  165. package/src/behaviors/general-commissioning/GeneralCommissioningServer.ts +8 -7
  166. package/src/behaviors/general-commissioning/ServerNodeFailsafeContext.ts +1 -1
  167. package/src/behaviors/group-key-management/GroupKeyManagementServer.ts +441 -26
  168. package/src/behaviors/groups/GroupsServer.ts +181 -3
  169. package/src/behaviors/level-control/LevelControlServer.ts +5 -5
  170. package/src/behaviors/mode-select/ModeSelectServer.ts +3 -3
  171. package/src/behaviors/on-off/OnOffServer.ts +3 -3
  172. package/src/behaviors/operational-credentials/OperationalCredentialsServer.ts +17 -9
  173. package/src/endpoint/Endpoint.ts +1 -1
  174. package/src/node/Node.ts +1 -1
  175. package/src/node/ServerNode.ts +1 -1
  176. package/src/node/client/NodePeerAddressStore.ts +1 -1
  177. package/src/node/server/InteractionServer.ts +5 -6
  178. 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 { StatusCode, StatusResponseError } from "#types";
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.state.maxGroupKeysPerFabric !== 1) {
18
- throw new ImplementationError("maxGroupKeysPerFabric must be 1 for now.");
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
- if (this.state.maxGroupsPerFabric !== 0) {
21
- throw new ImplementationError("maxGroupsPerFabric must be 0 for now.");
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(): MaybePromise {
27
- throw new StatusResponseError(
28
- "We do not support additional groups beyond the IPK",
29
- StatusCode.ResourceExhausted,
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): MaybePromise<GroupKeyManagement.KeySetReadResponse> {
36
- if (this.context.session === undefined) {
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
- const groupKeySet = fabric.getGroupKeySet(groupKeySetId);
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): MaybePromise {
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
- // We only have key 0, so we can't remove it
62
- throw new StatusResponseError(`GroupKeySet ${groupKeySetId} not found`, StatusCode.NotFound);
63
- }
383
+ const fabric = this.session.associatedFabric;
384
+ const fabricIndex = fabric.fabricIndex;
64
385
 
65
- override keySetReadAllIndices(): MaybePromise<GroupKeyManagement.KeySetReadAllIndicesResponse> {
66
- if (this.context.session === undefined) {
67
- throw new ImplementationError("Session must be defined");
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
- const fabric = this.context.session.associatedFabric;
394
+ this.state.groupKeySets.splice(existingIndex, 1);
70
395
 
71
- const groupKeySetIDs = fabric.getAllGroupKeySets().map(({ groupKeySetId }) => groupKeySetId);
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
  }