@matter/protocol 0.13.1-alpha.0-20250520-d699cd56d → 0.14.0-alpha.0-20250521-979eda05d

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 (230) hide show
  1. package/dist/cjs/action/Interactable.d.ts +1 -1
  2. package/dist/cjs/action/Interactable.d.ts.map +1 -1
  3. package/dist/cjs/action/client/ClientInteraction.d.ts +1 -1
  4. package/dist/cjs/action/client/ClientInteraction.d.ts.map +1 -1
  5. package/dist/cjs/action/client/ClientInteraction.js +50 -8
  6. package/dist/cjs/action/client/ClientInteraction.js.map +1 -1
  7. package/dist/cjs/action/protocols.d.ts +49 -15
  8. package/dist/cjs/action/protocols.d.ts.map +1 -1
  9. package/dist/cjs/action/request/Invoke.d.ts +10 -1
  10. package/dist/cjs/action/request/Invoke.d.ts.map +1 -1
  11. package/dist/cjs/action/request/Invoke.js +17 -5
  12. package/dist/cjs/action/request/Invoke.js.map +1 -1
  13. package/dist/cjs/action/request/Read.d.ts.map +1 -1
  14. package/dist/cjs/action/request/Read.js +1 -1
  15. package/dist/cjs/action/request/Read.js.map +1 -1
  16. package/dist/cjs/action/request/Write.d.ts +2 -0
  17. package/dist/cjs/action/request/Write.d.ts.map +1 -1
  18. package/dist/cjs/action/request/Write.js.map +1 -1
  19. package/dist/cjs/action/response/InvokeResult.d.ts +22 -6
  20. package/dist/cjs/action/response/InvokeResult.d.ts.map +1 -1
  21. package/dist/cjs/action/server/AttributeReadResponse.d.ts.map +1 -1
  22. package/dist/cjs/action/server/AttributeReadResponse.js +1 -2
  23. package/dist/cjs/action/server/AttributeReadResponse.js.map +2 -2
  24. package/dist/cjs/action/server/CommandInvokeResponse.d.ts +25 -0
  25. package/dist/cjs/action/server/CommandInvokeResponse.d.ts.map +1 -0
  26. package/dist/cjs/action/server/CommandInvokeResponse.js +331 -0
  27. package/dist/cjs/action/server/CommandInvokeResponse.js.map +6 -0
  28. package/dist/cjs/action/server/EventReadResponse.js +1 -1
  29. package/dist/cjs/action/server/ServerInteraction.d.ts +1 -3
  30. package/dist/cjs/action/server/ServerInteraction.d.ts.map +1 -1
  31. package/dist/cjs/action/server/ServerInteraction.js +4 -2
  32. package/dist/cjs/action/server/ServerInteraction.js.map +1 -1
  33. package/dist/cjs/action/server/index.d.ts +1 -0
  34. package/dist/cjs/action/server/index.d.ts.map +1 -1
  35. package/dist/cjs/action/server/index.js +1 -0
  36. package/dist/cjs/action/server/index.js.map +1 -1
  37. package/dist/cjs/cluster/client/AttributeClient.d.ts +2 -0
  38. package/dist/cjs/cluster/client/AttributeClient.d.ts.map +1 -1
  39. package/dist/cjs/cluster/client/AttributeClient.js +10 -0
  40. package/dist/cjs/cluster/client/AttributeClient.js.map +1 -1
  41. package/dist/cjs/cluster/client/ClusterClientTypes.d.ts +0 -4
  42. package/dist/cjs/cluster/client/ClusterClientTypes.d.ts.map +1 -1
  43. package/dist/cjs/cluster/index.d.ts +0 -1
  44. package/dist/cjs/cluster/index.d.ts.map +1 -1
  45. package/dist/cjs/cluster/index.js +0 -1
  46. package/dist/cjs/cluster/index.js.map +1 -1
  47. package/dist/cjs/index.d.ts +0 -1
  48. package/dist/cjs/index.d.ts.map +1 -1
  49. package/dist/cjs/index.js +0 -1
  50. package/dist/cjs/index.js.map +1 -1
  51. package/dist/cjs/interaction/AttributeDataEncoder.d.ts +1 -1
  52. package/dist/cjs/interaction/InteractionClient.d.ts +13 -0
  53. package/dist/cjs/interaction/InteractionClient.d.ts.map +1 -1
  54. package/dist/cjs/interaction/InteractionClient.js +14 -0
  55. package/dist/cjs/interaction/InteractionClient.js.map +1 -1
  56. package/dist/cjs/interaction/InteractionMessenger.d.ts +8 -8
  57. package/dist/cjs/interaction/index.d.ts +0 -1
  58. package/dist/cjs/interaction/index.d.ts.map +1 -1
  59. package/dist/cjs/interaction/index.js +0 -1
  60. package/dist/cjs/interaction/index.js.map +1 -1
  61. package/dist/esm/action/Interactable.d.ts +1 -1
  62. package/dist/esm/action/Interactable.d.ts.map +1 -1
  63. package/dist/esm/action/client/ClientInteraction.d.ts +1 -1
  64. package/dist/esm/action/client/ClientInteraction.d.ts.map +1 -1
  65. package/dist/esm/action/client/ClientInteraction.js +50 -8
  66. package/dist/esm/action/client/ClientInteraction.js.map +1 -1
  67. package/dist/esm/action/protocols.d.ts +49 -15
  68. package/dist/esm/action/protocols.d.ts.map +1 -1
  69. package/dist/esm/action/request/Invoke.d.ts +10 -1
  70. package/dist/esm/action/request/Invoke.d.ts.map +1 -1
  71. package/dist/esm/action/request/Invoke.js +17 -5
  72. package/dist/esm/action/request/Invoke.js.map +1 -1
  73. package/dist/esm/action/request/Read.d.ts.map +1 -1
  74. package/dist/esm/action/request/Read.js +1 -1
  75. package/dist/esm/action/request/Read.js.map +1 -1
  76. package/dist/esm/action/request/Write.d.ts +2 -0
  77. package/dist/esm/action/request/Write.d.ts.map +1 -1
  78. package/dist/esm/action/request/Write.js.map +1 -1
  79. package/dist/esm/action/response/InvokeResult.d.ts +22 -6
  80. package/dist/esm/action/response/InvokeResult.d.ts.map +1 -1
  81. package/dist/esm/action/server/AttributeReadResponse.d.ts.map +1 -1
  82. package/dist/esm/action/server/AttributeReadResponse.js +1 -1
  83. package/dist/esm/action/server/AttributeReadResponse.js.map +1 -1
  84. package/dist/esm/action/server/CommandInvokeResponse.d.ts +25 -0
  85. package/dist/esm/action/server/CommandInvokeResponse.d.ts.map +1 -0
  86. package/dist/esm/action/server/CommandInvokeResponse.js +317 -0
  87. package/dist/esm/action/server/CommandInvokeResponse.js.map +6 -0
  88. package/dist/esm/action/server/EventReadResponse.js +1 -1
  89. package/dist/esm/action/server/ServerInteraction.d.ts +1 -3
  90. package/dist/esm/action/server/ServerInteraction.d.ts.map +1 -1
  91. package/dist/esm/action/server/ServerInteraction.js +4 -2
  92. package/dist/esm/action/server/ServerInteraction.js.map +1 -1
  93. package/dist/esm/action/server/index.d.ts +1 -0
  94. package/dist/esm/action/server/index.d.ts.map +1 -1
  95. package/dist/esm/action/server/index.js +1 -0
  96. package/dist/esm/action/server/index.js.map +1 -1
  97. package/dist/esm/cluster/client/AttributeClient.d.ts +2 -0
  98. package/dist/esm/cluster/client/AttributeClient.d.ts.map +1 -1
  99. package/dist/esm/cluster/client/AttributeClient.js +10 -0
  100. package/dist/esm/cluster/client/AttributeClient.js.map +1 -1
  101. package/dist/esm/cluster/client/ClusterClientTypes.d.ts +0 -4
  102. package/dist/esm/cluster/client/ClusterClientTypes.d.ts.map +1 -1
  103. package/dist/esm/cluster/index.d.ts +0 -1
  104. package/dist/esm/cluster/index.d.ts.map +1 -1
  105. package/dist/esm/cluster/index.js +0 -1
  106. package/dist/esm/cluster/index.js.map +1 -1
  107. package/dist/esm/index.d.ts +0 -1
  108. package/dist/esm/index.d.ts.map +1 -1
  109. package/dist/esm/index.js +0 -1
  110. package/dist/esm/index.js.map +1 -1
  111. package/dist/esm/interaction/AttributeDataEncoder.d.ts +1 -1
  112. package/dist/esm/interaction/InteractionClient.d.ts +13 -0
  113. package/dist/esm/interaction/InteractionClient.d.ts.map +1 -1
  114. package/dist/esm/interaction/InteractionClient.js +14 -0
  115. package/dist/esm/interaction/InteractionClient.js.map +1 -1
  116. package/dist/esm/interaction/InteractionMessenger.d.ts +8 -8
  117. package/dist/esm/interaction/index.d.ts +0 -1
  118. package/dist/esm/interaction/index.d.ts.map +1 -1
  119. package/dist/esm/interaction/index.js +0 -1
  120. package/dist/esm/interaction/index.js.map +1 -1
  121. package/package.json +6 -6
  122. package/src/action/Interactable.ts +1 -1
  123. package/src/action/client/ClientInteraction.ts +53 -16
  124. package/src/action/protocols.ts +70 -16
  125. package/src/action/request/Invoke.ts +32 -5
  126. package/src/action/request/Read.ts +1 -1
  127. package/src/action/request/Write.ts +4 -1
  128. package/src/action/response/InvokeResult.ts +26 -6
  129. package/src/action/response/ReadResult.ts +1 -1
  130. package/src/action/server/AttributeReadResponse.ts +1 -1
  131. package/src/action/server/CommandInvokeResponse.ts +427 -0
  132. package/src/action/server/EventReadResponse.ts +1 -1
  133. package/src/action/server/ServerInteraction.ts +6 -5
  134. package/src/action/server/index.ts +1 -0
  135. package/src/cluster/client/AttributeClient.ts +12 -0
  136. package/src/cluster/client/ClusterClientTypes.ts +0 -6
  137. package/src/cluster/index.ts +0 -1
  138. package/src/index.ts +0 -1
  139. package/src/interaction/InteractionClient.ts +24 -0
  140. package/src/interaction/index.ts +0 -1
  141. package/dist/cjs/cluster/server/AttributeServer.d.ts +0 -307
  142. package/dist/cjs/cluster/server/AttributeServer.d.ts.map +0 -1
  143. package/dist/cjs/cluster/server/AttributeServer.js +0 -736
  144. package/dist/cjs/cluster/server/AttributeServer.js.map +0 -6
  145. package/dist/cjs/cluster/server/ClusterDatasource.d.ts +0 -16
  146. package/dist/cjs/cluster/server/ClusterDatasource.d.ts.map +0 -1
  147. package/dist/cjs/cluster/server/ClusterDatasource.js +0 -22
  148. package/dist/cjs/cluster/server/ClusterDatasource.js.map +0 -6
  149. package/dist/cjs/cluster/server/ClusterServer.d.ts +0 -40
  150. package/dist/cjs/cluster/server/ClusterServer.d.ts.map +0 -1
  151. package/dist/cjs/cluster/server/ClusterServer.js +0 -22
  152. package/dist/cjs/cluster/server/ClusterServer.js.map +0 -6
  153. package/dist/cjs/cluster/server/CommandServer.d.ts +0 -34
  154. package/dist/cjs/cluster/server/CommandServer.d.ts.map +0 -1
  155. package/dist/cjs/cluster/server/CommandServer.js +0 -76
  156. package/dist/cjs/cluster/server/CommandServer.js.map +0 -6
  157. package/dist/cjs/cluster/server/EventServer.d.ts +0 -42
  158. package/dist/cjs/cluster/server/EventServer.d.ts.map +0 -1
  159. package/dist/cjs/cluster/server/EventServer.js +0 -152
  160. package/dist/cjs/cluster/server/EventServer.js.map +0 -6
  161. package/dist/cjs/cluster/server/index.d.ts +0 -11
  162. package/dist/cjs/cluster/server/index.d.ts.map +0 -1
  163. package/dist/cjs/cluster/server/index.js +0 -28
  164. package/dist/cjs/cluster/server/index.js.map +0 -6
  165. package/dist/cjs/endpoint/EndpointInterface.d.ts +0 -37
  166. package/dist/cjs/endpoint/EndpointInterface.d.ts.map +0 -1
  167. package/dist/cjs/endpoint/EndpointInterface.js +0 -22
  168. package/dist/cjs/endpoint/EndpointInterface.js.map +0 -6
  169. package/dist/cjs/endpoint/EndpointStructureLogger.d.ts +0 -31
  170. package/dist/cjs/endpoint/EndpointStructureLogger.d.ts.map +0 -1
  171. package/dist/cjs/endpoint/EndpointStructureLogger.js +0 -230
  172. package/dist/cjs/endpoint/EndpointStructureLogger.js.map +0 -6
  173. package/dist/cjs/endpoint/index.d.ts +0 -8
  174. package/dist/cjs/endpoint/index.d.ts.map +0 -1
  175. package/dist/cjs/endpoint/index.js +0 -25
  176. package/dist/cjs/endpoint/index.js.map +0 -6
  177. package/dist/cjs/interaction/InteractionEndpointStructure.d.ts +0 -95
  178. package/dist/cjs/interaction/InteractionEndpointStructure.d.ts.map +0 -1
  179. package/dist/cjs/interaction/InteractionEndpointStructure.js +0 -359
  180. package/dist/cjs/interaction/InteractionEndpointStructure.js.map +0 -6
  181. package/dist/esm/cluster/server/AttributeServer.d.ts +0 -307
  182. package/dist/esm/cluster/server/AttributeServer.d.ts.map +0 -1
  183. package/dist/esm/cluster/server/AttributeServer.js +0 -729
  184. package/dist/esm/cluster/server/AttributeServer.js.map +0 -6
  185. package/dist/esm/cluster/server/ClusterDatasource.d.ts +0 -16
  186. package/dist/esm/cluster/server/ClusterDatasource.d.ts.map +0 -1
  187. package/dist/esm/cluster/server/ClusterDatasource.js +0 -6
  188. package/dist/esm/cluster/server/ClusterDatasource.js.map +0 -6
  189. package/dist/esm/cluster/server/ClusterServer.d.ts +0 -40
  190. package/dist/esm/cluster/server/ClusterServer.d.ts.map +0 -1
  191. package/dist/esm/cluster/server/ClusterServer.js +0 -6
  192. package/dist/esm/cluster/server/ClusterServer.js.map +0 -6
  193. package/dist/esm/cluster/server/CommandServer.d.ts +0 -34
  194. package/dist/esm/cluster/server/CommandServer.d.ts.map +0 -1
  195. package/dist/esm/cluster/server/CommandServer.js +0 -56
  196. package/dist/esm/cluster/server/CommandServer.js.map +0 -6
  197. package/dist/esm/cluster/server/EventServer.d.ts +0 -42
  198. package/dist/esm/cluster/server/EventServer.d.ts.map +0 -1
  199. package/dist/esm/cluster/server/EventServer.js +0 -141
  200. package/dist/esm/cluster/server/EventServer.js.map +0 -6
  201. package/dist/esm/cluster/server/index.d.ts +0 -11
  202. package/dist/esm/cluster/server/index.d.ts.map +0 -1
  203. package/dist/esm/cluster/server/index.js +0 -11
  204. package/dist/esm/cluster/server/index.js.map +0 -6
  205. package/dist/esm/endpoint/EndpointInterface.d.ts +0 -37
  206. package/dist/esm/endpoint/EndpointInterface.d.ts.map +0 -1
  207. package/dist/esm/endpoint/EndpointInterface.js +0 -6
  208. package/dist/esm/endpoint/EndpointInterface.js.map +0 -6
  209. package/dist/esm/endpoint/EndpointStructureLogger.d.ts +0 -31
  210. package/dist/esm/endpoint/EndpointStructureLogger.d.ts.map +0 -1
  211. package/dist/esm/endpoint/EndpointStructureLogger.js +0 -210
  212. package/dist/esm/endpoint/EndpointStructureLogger.js.map +0 -6
  213. package/dist/esm/endpoint/index.d.ts +0 -8
  214. package/dist/esm/endpoint/index.d.ts.map +0 -1
  215. package/dist/esm/endpoint/index.js +0 -8
  216. package/dist/esm/endpoint/index.js.map +0 -6
  217. package/dist/esm/interaction/InteractionEndpointStructure.d.ts +0 -95
  218. package/dist/esm/interaction/InteractionEndpointStructure.d.ts.map +0 -1
  219. package/dist/esm/interaction/InteractionEndpointStructure.js +0 -355
  220. package/dist/esm/interaction/InteractionEndpointStructure.js.map +0 -6
  221. package/src/cluster/server/AttributeServer.ts +0 -997
  222. package/src/cluster/server/ClusterDatasource.ts +0 -17
  223. package/src/cluster/server/ClusterServer.ts +0 -46
  224. package/src/cluster/server/CommandServer.ts +0 -89
  225. package/src/cluster/server/EventServer.ts +0 -202
  226. package/src/cluster/server/index.ts +0 -11
  227. package/src/endpoint/EndpointInterface.ts +0 -41
  228. package/src/endpoint/EndpointStructureLogger.ts +0 -270
  229. package/src/endpoint/index.ts +0 -8
  230. package/src/interaction/InteractionEndpointStructure.ts +0 -510
@@ -0,0 +1,427 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2022-2025 Project CHIP Authors
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import { CommandInvokeHandler, Invoke, InvokeResult } from "#action/index.js";
8
+ import { InteractionSession } from "#action/Interactable.js";
9
+ import { CommandTypeProtocol, EndpointProtocol, NodeProtocol } from "#action/protocols.js";
10
+ import { AccessControl } from "#action/server/AccessControl.js";
11
+ import { DataResponse, FallbackLimits } from "#action/server/DataResponse.js";
12
+ import { Diagnostic, InternalError, Logger } from "#general";
13
+ import { CommandModel, DataModelPath, ElementTag, FabricIndex as FabricIndexField } from "#model";
14
+ import {
15
+ CommandPath,
16
+ FabricIndex,
17
+ Status,
18
+ StatusCode,
19
+ StatusResponseError,
20
+ TlvSchema,
21
+ TlvStream,
22
+ ValidationError,
23
+ } from "#types";
24
+
25
+ const logger = Logger.get("CommandInvokeResponse");
26
+
27
+ /**
28
+ * Implements invoking of commands for matter "invoke" interactions.
29
+ *
30
+ * TODO - profile; ensure nested functions are properly JITed and/or inlined
31
+ */
32
+ export class CommandInvokeResponse<
33
+ SessionT extends InteractionSession = InteractionSession,
34
+ > extends DataResponse<SessionT> {
35
+ #fabricIndex: FabricIndex;
36
+
37
+ // The initial "chunk" may be a list of errors. As producers execute it is a set of records associated with the
38
+ // most recently touched endpoint. When the endpoint changes the previous chunk emits
39
+ #chunk?: InvokeResult.Data[];
40
+
41
+ // Each input CommandDataIB that does not have an error installs a producer. Producers run after validation and
42
+ // generate actual command data
43
+ #invokers?: Array<(this: CommandInvokeResponse) => AsyncIterable<InvokeResult.Chunk>>;
44
+
45
+ // The following state updates as data producers execute. This serves both to convey state between functions and as
46
+ // a cache between producers that touch the same endpoint and/or cluster
47
+ #currentEndpoint?: EndpointProtocol;
48
+
49
+ #registeredPaths = new Set<string>();
50
+ #registeredCommandRefs = new Set<number>();
51
+
52
+ // Count how many command status (on error) and command invokes (success) we have emitted
53
+ #statusCount = 0;
54
+ #successCount = 0;
55
+ #errorCount = 0;
56
+
57
+ constructor(node: NodeProtocol, session: SessionT) {
58
+ super(node, session);
59
+ this.#fabricIndex = session.fabric ?? FabricIndex.NO_FABRIC;
60
+ }
61
+
62
+ async *process<T extends Invoke>({ invokeRequests, suppressResponse }: T): InvokeResult {
63
+ const multipleInvokes = invokeRequests.length > 1;
64
+
65
+ // Register paths
66
+ for (const command of invokeRequests) {
67
+ const { commandPath: path, commandFields, commandRef } = command;
68
+ if (path.endpointId === undefined || path.clusterId === undefined || path.commandId === undefined) {
69
+ if (multipleInvokes) {
70
+ throw new StatusResponseError(
71
+ "Wildcard path must not be used with multiple invokes",
72
+ StatusCode.InvalidAction,
73
+ );
74
+ }
75
+ this.#processWildcard(path, commandRef, commandFields);
76
+ } else {
77
+ if (multipleInvokes && commandRef === undefined) {
78
+ throw new StatusResponseError(
79
+ "The CommandRef field must be specified for all commands in a batch invoke",
80
+ StatusCode.InvalidAction,
81
+ );
82
+ }
83
+ this.#processConcrete(path as InvokeResult.ConcreteCommandPath, commandRef, commandFields);
84
+ }
85
+ }
86
+
87
+ if (this.#invokers) {
88
+ for (const invoker of this.#invokers) {
89
+ for await (const chunk of invoker.apply(this)) {
90
+ if (!suppressResponse) {
91
+ yield chunk;
92
+ }
93
+ }
94
+ }
95
+ }
96
+
97
+ // We emit chunks lazily when the endpoint changes so there may be one remaining chunk. There may also be a
98
+ // chunk with errors even if there are no data producers
99
+ if (!suppressResponse && this.#chunk !== undefined) {
100
+ yield this.#chunk;
101
+ }
102
+ }
103
+
104
+ get counts() {
105
+ return {
106
+ status: this.#statusCount,
107
+ success: this.#successCount,
108
+ existent: this.#successCount + this.#errorCount,
109
+ };
110
+ }
111
+
112
+ /**
113
+ * Process a wildcard path and invoke commands on all endpoints that match the path.
114
+ */
115
+ #processWildcard(path: CommandPath, commandRef: number | undefined, commandFields: TlvStream | undefined) {
116
+ const { clusterId, commandId } = path;
117
+
118
+ // Formally, according to spec, we should check for node mismatch here but commandPath do not have a nodeId
119
+
120
+ // TODO: Add Group handling and validation
121
+ /*
122
+ if (isGroupSession && endpointId !== undefined) {
123
+ throw new StatusResponseError("Illegal write request with group ID and endpoint ID", StatusCode.InvalidAction);
124
+ }
125
+ */
126
+
127
+ if (clusterId === undefined || commandId === undefined) {
128
+ throw new StatusResponseError(
129
+ "Wildcard path write must specify a clusterId and commandId",
130
+ StatusCode.InvalidAction,
131
+ );
132
+ }
133
+
134
+ // If we are here, then endpointId must be wildcard aka undefined
135
+ this.#addInvoker(async function* invokeWildcardEndpoints(this: CommandInvokeResponse) {
136
+ for (const endpoint of this.node) {
137
+ yield* this.#processEndpointForWildcard(endpoint, path, commandRef, commandFields);
138
+ }
139
+ });
140
+ }
141
+
142
+ /**
143
+ * Invoke a command specified by a concrete path
144
+ */
145
+ #processConcrete(
146
+ path: InvokeResult.ConcreteCommandPath,
147
+ commandRef: number | undefined,
148
+ commandFields: TlvStream | undefined,
149
+ ) {
150
+ const { endpointId, clusterId, commandId } = path;
151
+
152
+ const pathKey = `${endpointId}-${clusterId}-${commandId}`;
153
+ if (this.#registeredPaths.has(pathKey)) {
154
+ throw new StatusResponseError(
155
+ `Duplicate concrete command path ${this.node.inspectPath(path)} on batch invoke`,
156
+ StatusCode.InvalidAction,
157
+ );
158
+ }
159
+ this.#registeredPaths.add(pathKey);
160
+ if (commandRef !== undefined) {
161
+ if (this.#registeredCommandRefs.has(commandRef)) {
162
+ throw new StatusResponseError(
163
+ `Duplicate commandRef ${commandRef} on batch invoke`,
164
+ StatusCode.InvalidAction,
165
+ );
166
+ }
167
+ this.#registeredCommandRefs.add(commandRef);
168
+ }
169
+
170
+ // Formally, according to spec, we should check for node mismatch here but commandPath do not have a nodeId
171
+
172
+ // Resolve path elements
173
+ const endpoint = this.node[endpointId];
174
+ const cluster = endpoint?.[clusterId];
175
+ const command = cluster?.type.commands[commandId];
176
+ let limits;
177
+ if (command === undefined) {
178
+ // We still need to authorize the user for access even though this path doesn't resolve. Spec is not
179
+ // explicit on what privilege level we should require as normally that information comes from the resolved
180
+ // command. So attempt to resolve via the active model
181
+ const modelAttr = this.node.matter
182
+ .member(path.clusterId, [ElementTag.Cluster])
183
+ ?.member(path.commandId, [ElementTag.Command]);
184
+
185
+ if (modelAttr) {
186
+ // OK cluster doesn't exist at that location, but we do understand semantically, so use limits from the
187
+ // model
188
+ limits = AccessControl(modelAttr as CommandModel).limits;
189
+ } else {
190
+ // We've got no idea. This effectively falls back to "view" privilege
191
+ limits = FallbackLimits;
192
+ }
193
+ } else {
194
+ limits = command.limits;
195
+ }
196
+
197
+ // Validate access. Order here prescribed by 1.4 core spec 8.4.3.2
198
+ // We need some fallback location if cluster is not defined
199
+ const location = {
200
+ ...(cluster?.location ?? {
201
+ path: DataModelPath.none,
202
+ endpoint: endpointId,
203
+ cluster: clusterId,
204
+ }),
205
+ owningFabric: this.session.fabric,
206
+ };
207
+
208
+ const permission = this.session.authorityAt(limits.writeLevel, location);
209
+ switch (permission) {
210
+ case AccessControl.Authority.Granted:
211
+ break;
212
+
213
+ case AccessControl.Authority.Unauthorized:
214
+ return this.#addStatus(path, commandRef, Status.UnsupportedAccess);
215
+
216
+ case AccessControl.Authority.Restricted:
217
+ return this.#addStatus(path, commandRef, Status.AccessRestricted);
218
+
219
+ default:
220
+ throw new InternalError(`Unsupported authorization state ${permission}`);
221
+ }
222
+
223
+ if (endpoint === undefined) {
224
+ return this.#addStatus(path, commandRef, Status.UnsupportedEndpoint);
225
+ }
226
+ if (cluster === undefined) {
227
+ return this.#addStatus(path, commandRef, Status.UnsupportedCluster);
228
+ }
229
+ if (command === undefined || !cluster.type.commands[command.id]) {
230
+ return this.#addStatus(path, commandRef, Status.UnsupportedCommand);
231
+ }
232
+
233
+ if (limits.fabricScoped && this.session.fabric === undefined) {
234
+ this.#errorCount++;
235
+ return this.#addStatus(path, commandRef, Status.UnsupportedAccess);
236
+ }
237
+
238
+ if (limits.timed && !this.session.timed) {
239
+ this.#errorCount++;
240
+ return this.#addStatus(path, commandRef, Status.NeedsTimedInteraction);
241
+ }
242
+
243
+ // This path contributes an command value
244
+ this.#addInvoker(async function* invokeConcretePath() {
245
+ // Update internal state for target endpoint
246
+ if (this.#currentEndpoint !== endpoint) {
247
+ if (this.#chunk) {
248
+ yield this.#chunk;
249
+ this.#chunk = undefined;
250
+ }
251
+ this.#currentEndpoint = endpoint;
252
+ }
253
+
254
+ await this.#invokeCommand(command, path, commandRef, commandFields, cluster.commands[command.id]);
255
+ });
256
+ }
257
+
258
+ /**
259
+ * Starts new chunk or adds to current chunk all values from {@link endpoint} selected by {@link path}.
260
+ *
261
+ * Emits previous chunk if it exists and was not for this endpoint. This means that our chunk size is one endpoint
262
+ * worth of data, except for the initial error chunk if there are path errors.
263
+ **
264
+ * TODO - skip endpoints for which subject is unauthorized as optimization
265
+ */
266
+ async *#processEndpointForWildcard(
267
+ endpoint: EndpointProtocol,
268
+ path: CommandPath,
269
+ commandRef: number | undefined,
270
+ commandFields: TlvStream | undefined,
271
+ ) {
272
+ const { clusterId } = path;
273
+
274
+ if (this.#currentEndpoint !== endpoint) {
275
+ if (this.#chunk) {
276
+ yield this.#chunk;
277
+ this.#chunk = undefined;
278
+ }
279
+ this.#currentEndpoint = endpoint;
280
+ }
281
+
282
+ const cluster = endpoint[clusterId];
283
+ if (cluster !== undefined) {
284
+ const { commandId } = path;
285
+
286
+ const command = cluster.type.commands[commandId];
287
+ if (command !== undefined) {
288
+ if (
289
+ this.session.authorityAt(command.limits.writeLevel, cluster.location) !==
290
+ AccessControl.Authority.Granted ||
291
+ (command.limits.timed && !this.session.timed)
292
+ ) {
293
+ return;
294
+ }
295
+
296
+ await this.#invokeCommand(
297
+ command,
298
+ {
299
+ ...path,
300
+ endpointId: endpoint.id,
301
+ },
302
+ commandRef,
303
+ commandFields,
304
+ cluster.commands[command.id],
305
+ );
306
+ }
307
+ }
308
+ }
309
+
310
+ /**
311
+ * Add a function that invokes commands and produces data. These functions are run after validation of input paths.
312
+ */
313
+ #addInvoker(producer: (this: CommandInvokeResponse) => AsyncIterable<InvokeResult.Chunk>) {
314
+ if (this.#invokers) {
315
+ this.#invokers.push(producer);
316
+ } else {
317
+ this.#invokers = [producer];
318
+ }
319
+ }
320
+
321
+ #addResponse(chunk: InvokeResult.Data) {
322
+ if (this.#chunk) {
323
+ this.#chunk.push(chunk);
324
+ } else {
325
+ this.#chunk = [chunk];
326
+ }
327
+ }
328
+
329
+ /**
330
+ * Add a status value.
331
+ */
332
+ #addStatus(
333
+ path: InvokeResult.ConcreteCommandPath,
334
+ commandRef: number | undefined,
335
+ status: Status,
336
+ clusterStatus?: number,
337
+ ) {
338
+ if (status !== StatusCode.Success) {
339
+ logger.info(
340
+ () =>
341
+ `Invoke error ${this.node.inspectPath(path)}: Status=${StatusCode[status]}(${status}), ClusterStatus=${clusterStatus}`,
342
+ );
343
+ }
344
+
345
+ const response: InvokeResult.CommandStatus = {
346
+ kind: "cmd-status",
347
+ path,
348
+ status,
349
+ clusterStatus,
350
+ commandRef,
351
+ };
352
+
353
+ if (status !== StatusCode.Success) {
354
+ this.#statusCount++;
355
+ }
356
+ this.#addResponse(response);
357
+ }
358
+
359
+ async #invokeCommand(
360
+ command: CommandTypeProtocol,
361
+ path: InvokeResult.ConcreteCommandPath,
362
+ commandRef: number | undefined,
363
+ commandFields: TlvStream | undefined,
364
+ invoker: CommandInvokeHandler,
365
+ ) {
366
+ try {
367
+ const { requestTlv, responseTlv } = command;
368
+ const request = this.#decodeWithSchema(requestTlv, commandFields);
369
+ requestTlv.validate(request);
370
+ const response = await invoker(request, this.session);
371
+ await this.session.transaction?.commit();
372
+
373
+ this.#successCount++;
374
+ const encodedResponse = responseTlv.encodeTlv(response);
375
+ if (encodedResponse.length === 0) {
376
+ this.#addStatus(path, commandRef, StatusCode.Success);
377
+ } else {
378
+ this.#addResponse({
379
+ kind: "cmd-response",
380
+ path: {
381
+ ...path,
382
+ commandId: command.responseId,
383
+ },
384
+ data: encodedResponse,
385
+ commandRef,
386
+ });
387
+ }
388
+ } catch (error) {
389
+ await this.session.transaction?.rollback();
390
+ if (StatusResponseError.is(error)) {
391
+ this.#errorCount++;
392
+
393
+ let errorCode = error.code;
394
+ const errorLogText = `Error ${Diagnostic.hex(errorCode)}${
395
+ error.clusterCode !== undefined ? `/${Diagnostic.hex(error.clusterCode)}` : ""
396
+ } while invoking command: ${error.message}`;
397
+
398
+ if (error instanceof ValidationError) {
399
+ logger.info(
400
+ `Validation-${errorLogText}${error.fieldName !== undefined ? ` in field ${error.fieldName}` : ""}`,
401
+ );
402
+ if (errorCode === StatusCode.InvalidAction) {
403
+ errorCode = StatusCode.InvalidCommand;
404
+ }
405
+ } else {
406
+ logger.info(errorLogText);
407
+ }
408
+
409
+ this.#addStatus(path, commandRef, errorCode, error.clusterCode);
410
+ return;
411
+ }
412
+ throw error;
413
+ }
414
+ }
415
+
416
+ #decodeWithSchema(tlv: TlvSchema<any>, value: TlvStream | undefined) {
417
+ if (value === undefined) {
418
+ return undefined; // The validation will fail if the schema expected data
419
+ }
420
+ return tlv.injectField(
421
+ tlv.decodeTlv(value),
422
+ <number>FabricIndexField.id,
423
+ this.#fabricIndex,
424
+ () => true, // We always inject the current fabricIndex for invokes
425
+ );
426
+ }
427
+ }
@@ -50,7 +50,7 @@ export class EventReadResponse<
50
50
  // Collected allowed and existing event paths to consider when reading events
51
51
  #allowedEventPaths = new Map<string, TlvSchema<unknown>>();
52
52
 
53
- // Count how many attribute status (on error) and attribute values (on success) we have emitted
53
+ // Count how many events status (on error) and event values (on success) we have emitted
54
54
  #statusCount = 0;
55
55
  #valueCount = 0;
56
56
 
@@ -14,6 +14,7 @@ import { InvokeResult } from "#action/response/InvokeResult.js";
14
14
  import { ReadResult } from "#action/response/ReadResult.js";
15
15
  import { SubscribeResult } from "#action/response/SubscribeResult.js";
16
16
  import { WriteResult } from "#action/response/WriteResult.js";
17
+ import { CommandInvokeResponse } from "#action/server/CommandInvokeResponse.js";
17
18
  import { EventReadResponse } from "#action/server/EventReadResponse.js";
18
19
  import { Logger, NotImplementedError } from "#general";
19
20
  import { AttributeReadResponse } from "./AttributeReadResponse.js";
@@ -28,8 +29,6 @@ const logger = Logger.get("ServerInteraction");
28
29
  * completion there will be redundancy with other components including:
29
30
  *
30
31
  * - InteractionServer (significant overlap with this class)
31
- *
32
- * - InteractionEndpointStructure ({@link NodeProtocol} is largely duplicative)
33
32
  */
34
33
  export class ServerInteraction<SessionT extends InteractionSession = InteractionSession>
35
34
  implements Interactable<SessionT>
@@ -73,8 +72,10 @@ export class ServerInteraction<SessionT extends InteractionSession = Interaction
73
72
  return writer.process(request);
74
73
  }
75
74
 
76
- invoke<T extends Invoke>(_request: T, _session?: SessionT): InvokeResult<T> {
77
- // TODO
78
- throw new NotImplementedError();
75
+ invoke(request: Invoke, session: SessionT): InvokeResult {
76
+ // TODO - validate request
77
+
78
+ const invoker = new CommandInvokeResponse(this.#node, session);
79
+ return invoker.process(request);
79
80
  }
80
81
  }
@@ -8,6 +8,7 @@ export * from "./AccessControl.js";
8
8
  export * from "./AttributeReadResponse.js";
9
9
  export * from "./AttributeSubscriptionResponse.js";
10
10
  export * from "./AttributeWriteResponse.js";
11
+ export * from "./CommandInvokeResponse.js";
11
12
  export * from "./DataResponse.js";
12
13
  export * from "./EventReadResponse.js";
13
14
  export * from "./ServerInteraction.js";
@@ -106,6 +106,18 @@ export class AttributeClient<T = any> {
106
106
  });
107
107
  }
108
108
 
109
+ get fabricScoped() {
110
+ return this.#isFabricScoped;
111
+ }
112
+
113
+ getLocal() {
114
+ return this.#interactionClient.getStoredAttribute({
115
+ endpointId: this.endpointId,
116
+ clusterId: this.clusterId,
117
+ attribute: this.attribute,
118
+ });
119
+ }
120
+
109
121
  /**
110
122
  * Get the value of the attribute. Fabric scoped reads are always done with the remote.
111
123
  * The `requestFromRemote` parameter allowed to force or prevent remote reads:
@@ -229,12 +229,6 @@ export type ClusterClientObj<T extends ClusterType = ClusterType> = {
229
229
  /** Returns if a given Attribute with provided name is present and supported at the connected cluster server. */
230
230
  isAttributeSupportedByName: (attributeName: string) => boolean;
231
231
 
232
- /** Returns if a given Event Id is present and supported at the connected cluster server. */
233
- isEventSupported: (eventId: EventId) => boolean;
234
-
235
- /** Returns if a given Event with provided name is present and supported at the connected cluster server. */
236
- isEventSupportedByName: (eventName: string) => boolean;
237
-
238
232
  /** Returns if a given Command Id is present and supported at the connected cluster server. */
239
233
  isCommandSupported: (commandId: CommandId) => boolean;
240
234
 
@@ -5,4 +5,3 @@
5
5
  */
6
6
 
7
7
  export * from "./client/index.js";
8
- export * from "./server/index.js";
package/src/index.ts CHANGED
@@ -10,7 +10,6 @@ export * from "./certificate/index.js";
10
10
  export * from "./cluster/index.js";
11
11
  export * from "./codec/index.js";
12
12
  export * from "./common/index.js";
13
- export * from "./endpoint/index.js";
14
13
  export * from "./events/index.js";
15
14
  export * from "./fabric/index.js";
16
15
  export * from "./interaction/index.js";
@@ -398,6 +398,30 @@ export class InteractionClient {
398
398
  return response?.value;
399
399
  }
400
400
 
401
+ getStoredAttribute<A extends Attribute<any, any>>(options: {
402
+ endpointId: EndpointNumber;
403
+ clusterId: ClusterId;
404
+ attribute: A;
405
+ }): AttributeJsType<A> | undefined {
406
+ return this.getStoredAttributeWithVersion(options)?.value;
407
+ }
408
+
409
+ getStoredAttributeWithVersion<A extends Attribute<any, any>>(options: {
410
+ endpointId: EndpointNumber;
411
+ clusterId: ClusterId;
412
+ attribute: A;
413
+ }): { value: AttributeJsType<A>; version: number } | undefined {
414
+ const { endpointId, clusterId, attribute } = options;
415
+ const { id: attributeId } = attribute;
416
+ if (this.#nodeStore !== undefined) {
417
+ const { value, version } = this.#nodeStore.retrieveAttribute(endpointId, clusterId, attributeId) ?? {};
418
+ if (value !== undefined && version !== undefined) {
419
+ return { value, version } as { value: AttributeJsType<A>; version: number };
420
+ }
421
+ }
422
+ return undefined;
423
+ }
424
+
401
425
  async getAttributeWithVersion<A extends Attribute<any, any>>(options: {
402
426
  endpointId: EndpointNumber;
403
427
  clusterId: ClusterId;
@@ -9,7 +9,6 @@ export * from "./AttributeDataDecoder.js";
9
9
  export * from "./AttributeDataEncoder.js";
10
10
  export * from "./EventDataDecoder.js";
11
11
  export * from "./InteractionClient.js";
12
- export * from "./InteractionEndpointStructure.js";
13
12
  export * from "./InteractionMessenger.js";
14
13
  export * from "./Subscription.js";
15
14
  export * from "./SubscriptionClient.js";