@matter/protocol 0.13.1-alpha.0-20250520-d699cd56d → 0.14.0-alpha.0-20250524-51a7e1721

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 (264) 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/AttributeWriteResponse.d.ts.map +1 -1
  25. package/dist/cjs/action/server/AttributeWriteResponse.js +1 -14
  26. package/dist/cjs/action/server/AttributeWriteResponse.js.map +1 -1
  27. package/dist/cjs/action/server/CommandInvokeResponse.d.ts +25 -0
  28. package/dist/cjs/action/server/CommandInvokeResponse.d.ts.map +1 -0
  29. package/dist/cjs/action/server/CommandInvokeResponse.js +331 -0
  30. package/dist/cjs/action/server/CommandInvokeResponse.js.map +6 -0
  31. package/dist/cjs/action/server/EventReadResponse.js +1 -1
  32. package/dist/cjs/action/server/ServerInteraction.d.ts +1 -3
  33. package/dist/cjs/action/server/ServerInteraction.d.ts.map +1 -1
  34. package/dist/cjs/action/server/ServerInteraction.js +4 -2
  35. package/dist/cjs/action/server/ServerInteraction.js.map +1 -1
  36. package/dist/cjs/action/server/index.d.ts +1 -0
  37. package/dist/cjs/action/server/index.d.ts.map +1 -1
  38. package/dist/cjs/action/server/index.js +1 -0
  39. package/dist/cjs/action/server/index.js.map +1 -1
  40. package/dist/cjs/cluster/client/AttributeClient.d.ts +2 -0
  41. package/dist/cjs/cluster/client/AttributeClient.d.ts.map +1 -1
  42. package/dist/cjs/cluster/client/AttributeClient.js +10 -0
  43. package/dist/cjs/cluster/client/AttributeClient.js.map +1 -1
  44. package/dist/cjs/cluster/client/ClusterClientTypes.d.ts +0 -4
  45. package/dist/cjs/cluster/client/ClusterClientTypes.d.ts.map +1 -1
  46. package/dist/cjs/cluster/index.d.ts +0 -1
  47. package/dist/cjs/cluster/index.d.ts.map +1 -1
  48. package/dist/cjs/cluster/index.js +0 -1
  49. package/dist/cjs/cluster/index.js.map +1 -1
  50. package/dist/cjs/events/OccurrenceManager.d.ts +10 -2
  51. package/dist/cjs/events/OccurrenceManager.d.ts.map +1 -1
  52. package/dist/cjs/events/OccurrenceManager.js +57 -11
  53. package/dist/cjs/events/OccurrenceManager.js.map +1 -1
  54. package/dist/cjs/fabric/Fabric.d.ts +2 -11
  55. package/dist/cjs/fabric/Fabric.d.ts.map +1 -1
  56. package/dist/cjs/fabric/Fabric.js +1 -38
  57. package/dist/cjs/fabric/Fabric.js.map +1 -1
  58. package/dist/cjs/index.d.ts +0 -1
  59. package/dist/cjs/index.d.ts.map +1 -1
  60. package/dist/cjs/index.js +0 -1
  61. package/dist/cjs/index.js.map +1 -1
  62. package/dist/cjs/interaction/AttributeDataEncoder.d.ts +1 -1
  63. package/dist/cjs/interaction/InteractionClient.d.ts +13 -0
  64. package/dist/cjs/interaction/InteractionClient.d.ts.map +1 -1
  65. package/dist/cjs/interaction/InteractionClient.js +14 -0
  66. package/dist/cjs/interaction/InteractionClient.js.map +1 -1
  67. package/dist/cjs/interaction/InteractionMessenger.d.ts +8 -8
  68. package/dist/cjs/interaction/index.d.ts +0 -1
  69. package/dist/cjs/interaction/index.d.ts.map +1 -1
  70. package/dist/cjs/interaction/index.js +0 -1
  71. package/dist/cjs/interaction/index.js.map +1 -1
  72. package/dist/cjs/mdns/MdnsScanner.d.ts +23 -1
  73. package/dist/cjs/mdns/MdnsScanner.d.ts.map +1 -1
  74. package/dist/cjs/mdns/MdnsScanner.js +124 -11
  75. package/dist/cjs/mdns/MdnsScanner.js.map +2 -2
  76. package/dist/esm/action/Interactable.d.ts +1 -1
  77. package/dist/esm/action/Interactable.d.ts.map +1 -1
  78. package/dist/esm/action/client/ClientInteraction.d.ts +1 -1
  79. package/dist/esm/action/client/ClientInteraction.d.ts.map +1 -1
  80. package/dist/esm/action/client/ClientInteraction.js +50 -8
  81. package/dist/esm/action/client/ClientInteraction.js.map +1 -1
  82. package/dist/esm/action/protocols.d.ts +49 -15
  83. package/dist/esm/action/protocols.d.ts.map +1 -1
  84. package/dist/esm/action/request/Invoke.d.ts +10 -1
  85. package/dist/esm/action/request/Invoke.d.ts.map +1 -1
  86. package/dist/esm/action/request/Invoke.js +17 -5
  87. package/dist/esm/action/request/Invoke.js.map +1 -1
  88. package/dist/esm/action/request/Read.d.ts.map +1 -1
  89. package/dist/esm/action/request/Read.js +1 -1
  90. package/dist/esm/action/request/Read.js.map +1 -1
  91. package/dist/esm/action/request/Write.d.ts +2 -0
  92. package/dist/esm/action/request/Write.d.ts.map +1 -1
  93. package/dist/esm/action/request/Write.js.map +1 -1
  94. package/dist/esm/action/response/InvokeResult.d.ts +22 -6
  95. package/dist/esm/action/response/InvokeResult.d.ts.map +1 -1
  96. package/dist/esm/action/server/AttributeReadResponse.d.ts.map +1 -1
  97. package/dist/esm/action/server/AttributeReadResponse.js +1 -1
  98. package/dist/esm/action/server/AttributeReadResponse.js.map +1 -1
  99. package/dist/esm/action/server/AttributeWriteResponse.d.ts.map +1 -1
  100. package/dist/esm/action/server/AttributeWriteResponse.js +2 -15
  101. package/dist/esm/action/server/AttributeWriteResponse.js.map +1 -1
  102. package/dist/esm/action/server/CommandInvokeResponse.d.ts +25 -0
  103. package/dist/esm/action/server/CommandInvokeResponse.d.ts.map +1 -0
  104. package/dist/esm/action/server/CommandInvokeResponse.js +317 -0
  105. package/dist/esm/action/server/CommandInvokeResponse.js.map +6 -0
  106. package/dist/esm/action/server/EventReadResponse.js +1 -1
  107. package/dist/esm/action/server/ServerInteraction.d.ts +1 -3
  108. package/dist/esm/action/server/ServerInteraction.d.ts.map +1 -1
  109. package/dist/esm/action/server/ServerInteraction.js +4 -2
  110. package/dist/esm/action/server/ServerInteraction.js.map +1 -1
  111. package/dist/esm/action/server/index.d.ts +1 -0
  112. package/dist/esm/action/server/index.d.ts.map +1 -1
  113. package/dist/esm/action/server/index.js +1 -0
  114. package/dist/esm/action/server/index.js.map +1 -1
  115. package/dist/esm/cluster/client/AttributeClient.d.ts +2 -0
  116. package/dist/esm/cluster/client/AttributeClient.d.ts.map +1 -1
  117. package/dist/esm/cluster/client/AttributeClient.js +10 -0
  118. package/dist/esm/cluster/client/AttributeClient.js.map +1 -1
  119. package/dist/esm/cluster/client/ClusterClientTypes.d.ts +0 -4
  120. package/dist/esm/cluster/client/ClusterClientTypes.d.ts.map +1 -1
  121. package/dist/esm/cluster/index.d.ts +0 -1
  122. package/dist/esm/cluster/index.d.ts.map +1 -1
  123. package/dist/esm/cluster/index.js +0 -1
  124. package/dist/esm/cluster/index.js.map +1 -1
  125. package/dist/esm/events/OccurrenceManager.d.ts +10 -2
  126. package/dist/esm/events/OccurrenceManager.d.ts.map +1 -1
  127. package/dist/esm/events/OccurrenceManager.js +62 -12
  128. package/dist/esm/events/OccurrenceManager.js.map +1 -1
  129. package/dist/esm/fabric/Fabric.d.ts +2 -11
  130. package/dist/esm/fabric/Fabric.d.ts.map +1 -1
  131. package/dist/esm/fabric/Fabric.js +1 -38
  132. package/dist/esm/fabric/Fabric.js.map +1 -1
  133. package/dist/esm/index.d.ts +0 -1
  134. package/dist/esm/index.d.ts.map +1 -1
  135. package/dist/esm/index.js +0 -1
  136. package/dist/esm/index.js.map +1 -1
  137. package/dist/esm/interaction/AttributeDataEncoder.d.ts +1 -1
  138. package/dist/esm/interaction/InteractionClient.d.ts +13 -0
  139. package/dist/esm/interaction/InteractionClient.d.ts.map +1 -1
  140. package/dist/esm/interaction/InteractionClient.js +14 -0
  141. package/dist/esm/interaction/InteractionClient.js.map +1 -1
  142. package/dist/esm/interaction/InteractionMessenger.d.ts +8 -8
  143. package/dist/esm/interaction/index.d.ts +0 -1
  144. package/dist/esm/interaction/index.d.ts.map +1 -1
  145. package/dist/esm/interaction/index.js +0 -1
  146. package/dist/esm/interaction/index.js.map +1 -1
  147. package/dist/esm/mdns/MdnsScanner.d.ts +23 -1
  148. package/dist/esm/mdns/MdnsScanner.d.ts.map +1 -1
  149. package/dist/esm/mdns/MdnsScanner.js +127 -11
  150. package/dist/esm/mdns/MdnsScanner.js.map +2 -2
  151. package/package.json +6 -6
  152. package/src/action/Interactable.ts +1 -1
  153. package/src/action/client/ClientInteraction.ts +53 -16
  154. package/src/action/protocols.ts +70 -16
  155. package/src/action/request/Invoke.ts +32 -5
  156. package/src/action/request/Read.ts +1 -1
  157. package/src/action/request/Write.ts +4 -1
  158. package/src/action/response/InvokeResult.ts +26 -6
  159. package/src/action/response/ReadResult.ts +1 -1
  160. package/src/action/server/AttributeReadResponse.ts +1 -1
  161. package/src/action/server/AttributeWriteResponse.ts +2 -20
  162. package/src/action/server/CommandInvokeResponse.ts +427 -0
  163. package/src/action/server/EventReadResponse.ts +1 -1
  164. package/src/action/server/ServerInteraction.ts +6 -5
  165. package/src/action/server/index.ts +1 -0
  166. package/src/cluster/client/AttributeClient.ts +12 -0
  167. package/src/cluster/client/ClusterClientTypes.ts +0 -6
  168. package/src/cluster/index.ts +0 -1
  169. package/src/events/OccurrenceManager.ts +91 -11
  170. package/src/fabric/Fabric.ts +1 -49
  171. package/src/index.ts +0 -1
  172. package/src/interaction/InteractionClient.ts +24 -0
  173. package/src/interaction/index.ts +0 -1
  174. package/src/mdns/MdnsScanner.ts +187 -12
  175. package/dist/cjs/cluster/server/AttributeServer.d.ts +0 -307
  176. package/dist/cjs/cluster/server/AttributeServer.d.ts.map +0 -1
  177. package/dist/cjs/cluster/server/AttributeServer.js +0 -736
  178. package/dist/cjs/cluster/server/AttributeServer.js.map +0 -6
  179. package/dist/cjs/cluster/server/ClusterDatasource.d.ts +0 -16
  180. package/dist/cjs/cluster/server/ClusterDatasource.d.ts.map +0 -1
  181. package/dist/cjs/cluster/server/ClusterDatasource.js +0 -22
  182. package/dist/cjs/cluster/server/ClusterDatasource.js.map +0 -6
  183. package/dist/cjs/cluster/server/ClusterServer.d.ts +0 -40
  184. package/dist/cjs/cluster/server/ClusterServer.d.ts.map +0 -1
  185. package/dist/cjs/cluster/server/ClusterServer.js +0 -22
  186. package/dist/cjs/cluster/server/ClusterServer.js.map +0 -6
  187. package/dist/cjs/cluster/server/CommandServer.d.ts +0 -34
  188. package/dist/cjs/cluster/server/CommandServer.d.ts.map +0 -1
  189. package/dist/cjs/cluster/server/CommandServer.js +0 -76
  190. package/dist/cjs/cluster/server/CommandServer.js.map +0 -6
  191. package/dist/cjs/cluster/server/EventServer.d.ts +0 -42
  192. package/dist/cjs/cluster/server/EventServer.d.ts.map +0 -1
  193. package/dist/cjs/cluster/server/EventServer.js +0 -152
  194. package/dist/cjs/cluster/server/EventServer.js.map +0 -6
  195. package/dist/cjs/cluster/server/index.d.ts +0 -11
  196. package/dist/cjs/cluster/server/index.d.ts.map +0 -1
  197. package/dist/cjs/cluster/server/index.js +0 -28
  198. package/dist/cjs/cluster/server/index.js.map +0 -6
  199. package/dist/cjs/endpoint/EndpointInterface.d.ts +0 -37
  200. package/dist/cjs/endpoint/EndpointInterface.d.ts.map +0 -1
  201. package/dist/cjs/endpoint/EndpointInterface.js +0 -22
  202. package/dist/cjs/endpoint/EndpointInterface.js.map +0 -6
  203. package/dist/cjs/endpoint/EndpointStructureLogger.d.ts +0 -31
  204. package/dist/cjs/endpoint/EndpointStructureLogger.d.ts.map +0 -1
  205. package/dist/cjs/endpoint/EndpointStructureLogger.js +0 -230
  206. package/dist/cjs/endpoint/EndpointStructureLogger.js.map +0 -6
  207. package/dist/cjs/endpoint/index.d.ts +0 -8
  208. package/dist/cjs/endpoint/index.d.ts.map +0 -1
  209. package/dist/cjs/endpoint/index.js +0 -25
  210. package/dist/cjs/endpoint/index.js.map +0 -6
  211. package/dist/cjs/interaction/InteractionEndpointStructure.d.ts +0 -95
  212. package/dist/cjs/interaction/InteractionEndpointStructure.d.ts.map +0 -1
  213. package/dist/cjs/interaction/InteractionEndpointStructure.js +0 -359
  214. package/dist/cjs/interaction/InteractionEndpointStructure.js.map +0 -6
  215. package/dist/esm/cluster/server/AttributeServer.d.ts +0 -307
  216. package/dist/esm/cluster/server/AttributeServer.d.ts.map +0 -1
  217. package/dist/esm/cluster/server/AttributeServer.js +0 -729
  218. package/dist/esm/cluster/server/AttributeServer.js.map +0 -6
  219. package/dist/esm/cluster/server/ClusterDatasource.d.ts +0 -16
  220. package/dist/esm/cluster/server/ClusterDatasource.d.ts.map +0 -1
  221. package/dist/esm/cluster/server/ClusterDatasource.js +0 -6
  222. package/dist/esm/cluster/server/ClusterDatasource.js.map +0 -6
  223. package/dist/esm/cluster/server/ClusterServer.d.ts +0 -40
  224. package/dist/esm/cluster/server/ClusterServer.d.ts.map +0 -1
  225. package/dist/esm/cluster/server/ClusterServer.js +0 -6
  226. package/dist/esm/cluster/server/ClusterServer.js.map +0 -6
  227. package/dist/esm/cluster/server/CommandServer.d.ts +0 -34
  228. package/dist/esm/cluster/server/CommandServer.d.ts.map +0 -1
  229. package/dist/esm/cluster/server/CommandServer.js +0 -56
  230. package/dist/esm/cluster/server/CommandServer.js.map +0 -6
  231. package/dist/esm/cluster/server/EventServer.d.ts +0 -42
  232. package/dist/esm/cluster/server/EventServer.d.ts.map +0 -1
  233. package/dist/esm/cluster/server/EventServer.js +0 -141
  234. package/dist/esm/cluster/server/EventServer.js.map +0 -6
  235. package/dist/esm/cluster/server/index.d.ts +0 -11
  236. package/dist/esm/cluster/server/index.d.ts.map +0 -1
  237. package/dist/esm/cluster/server/index.js +0 -11
  238. package/dist/esm/cluster/server/index.js.map +0 -6
  239. package/dist/esm/endpoint/EndpointInterface.d.ts +0 -37
  240. package/dist/esm/endpoint/EndpointInterface.d.ts.map +0 -1
  241. package/dist/esm/endpoint/EndpointInterface.js +0 -6
  242. package/dist/esm/endpoint/EndpointInterface.js.map +0 -6
  243. package/dist/esm/endpoint/EndpointStructureLogger.d.ts +0 -31
  244. package/dist/esm/endpoint/EndpointStructureLogger.d.ts.map +0 -1
  245. package/dist/esm/endpoint/EndpointStructureLogger.js +0 -210
  246. package/dist/esm/endpoint/EndpointStructureLogger.js.map +0 -6
  247. package/dist/esm/endpoint/index.d.ts +0 -8
  248. package/dist/esm/endpoint/index.d.ts.map +0 -1
  249. package/dist/esm/endpoint/index.js +0 -8
  250. package/dist/esm/endpoint/index.js.map +0 -6
  251. package/dist/esm/interaction/InteractionEndpointStructure.d.ts +0 -95
  252. package/dist/esm/interaction/InteractionEndpointStructure.d.ts.map +0 -1
  253. package/dist/esm/interaction/InteractionEndpointStructure.js +0 -355
  254. package/dist/esm/interaction/InteractionEndpointStructure.js.map +0 -6
  255. package/src/cluster/server/AttributeServer.ts +0 -997
  256. package/src/cluster/server/ClusterDatasource.ts +0 -17
  257. package/src/cluster/server/ClusterServer.ts +0 -46
  258. package/src/cluster/server/CommandServer.ts +0 -89
  259. package/src/cluster/server/EventServer.ts +0 -202
  260. package/src/cluster/server/index.ts +0 -11
  261. package/src/endpoint/EndpointInterface.ts +0 -41
  262. package/src/endpoint/EndpointStructureLogger.ts +0 -270
  263. package/src/endpoint/index.ts +0 -8
  264. 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";
@@ -16,7 +16,15 @@ import {
16
16
  MaybePromise,
17
17
  Observable,
18
18
  } from "#general";
19
- import { EventNumber, FabricIndex, resolveEventName, TlvEventFilter, TlvEventPath, TypeFromSchema } from "#types";
19
+ import {
20
+ EventNumber,
21
+ EventPriority,
22
+ FabricIndex,
23
+ resolveEventName,
24
+ TlvEventFilter,
25
+ TlvEventPath,
26
+ TypeFromSchema,
27
+ } from "#types";
20
28
  import { EventStore, OccurrenceSummary } from "./EventStore.js";
21
29
  import { NumberedOccurrence, Occurrence } from "./Occurrence.js";
22
30
 
@@ -49,7 +57,7 @@ export class OccurrenceManager {
49
57
 
50
58
  // As we don't (yet) have storage with secondary indices we currently maintain indices in memory regardless of
51
59
  // whether underlying store is volatile
52
- readonly #occurrences = new Array<OccurrenceSummary>();
60
+ #occurrences = new Array<OccurrenceSummary>();
53
61
 
54
62
  #construction: Construction<OccurrenceManager>;
55
63
 
@@ -298,23 +306,82 @@ export class OccurrenceManager {
298
306
  }
299
307
 
300
308
  #dropOldOccurrences() {
301
- const count = this.#storedEventCount - this.#bufferConfig.minEventAllowance;
302
- if (count <= 0) {
309
+ let toDelete = this.#storedEventCount - this.#bufferConfig.minEventAllowance;
310
+ if (toDelete <= 0) {
303
311
  return;
304
312
  }
305
313
 
306
- logger.debug(`Event store is full; dropping ${count} old occurrence${count === 1 ? "s" : ""}`);
314
+ logger.debug(`Event store is full; dropping ${toDelete} old occurrence${toDelete === 1 ? "s" : ""}`);
315
+
316
+ const prioData = {
317
+ [EventPriority.Info]: {
318
+ count: this.#bufferConfig.minPriorityEventAllowance["info"],
319
+ minPosition: -1,
320
+ },
321
+ [EventPriority.Debug]: {
322
+ count: this.#bufferConfig.minPriorityEventAllowance["debug"],
323
+ minPosition: -1,
324
+ },
325
+ };
307
326
 
308
327
  const asyncDrops = Array<PromiseLike<void>>();
309
328
 
310
- for (const entry of this.#occurrences.splice(0, count)) {
311
- const drop = MaybePromise.catch(this.#store.delete(entry.number), error =>
312
- logger.warn(`Error dropping occurrence #${entry}: ${error}`),
313
- );
314
- if (MaybePromise.is(drop)) {
315
- asyncDrops.push(drop);
329
+ // Find out on which index positions we reached the minimum allowed entries for each priority.
330
+ // For that we count the entries for Non-Critical priorities from the end of the list to the beginning.
331
+ for (let i = this.#occurrences.length - 1; i >= 0; i--) {
332
+ const { priority } = this.#occurrences[i];
333
+
334
+ if (priority !== EventPriority.Critical) {
335
+ const data = prioData[priority];
336
+ if (data.count > 0) {
337
+ data.count--;
338
+ if (data.count === 0) {
339
+ data.minPosition = i;
340
+ if (
341
+ prioData[EventPriority.Info].minPosition > -1 &&
342
+ prioData[EventPriority.Debug].minPosition > -1
343
+ ) {
344
+ // We have found the minimum position for both priorities, we can stop
345
+ break;
346
+ }
347
+ }
348
+ }
349
+ }
350
+ }
351
+
352
+ // Now we can drop the occurrences from the beginning of the list until we reach the minimum allowed entries
353
+ // for each priority, started with the lowest priority, until we reached the maximum number of entries we need
354
+ // to remove. Critical events are removed last from the beginning as many as needed to reach the maximum number
355
+ // of entries to remove.
356
+ // Deleted entries are marked as undefined in the array, so we can filter them out later in one pass.
357
+ const occurrences = this.#occurrences as Array<OccurrenceSummary | undefined>;
358
+ for (const priority of [EventPriority.Debug, EventPriority.Info, EventPriority.Critical]) {
359
+ const checkUpTo =
360
+ priority === EventPriority.Critical ? this.#storedEventCount : prioData[priority].minPosition;
361
+ if (checkUpTo === -1) {
362
+ // We have less than the minimum of this event type, so we can not remove any
363
+ continue;
364
+ }
365
+ for (let i = 0; i < checkUpTo && toDelete > 0; i++) {
366
+ if (occurrences[i] === undefined) continue;
367
+ const { priority: entryPriority, number } = occurrences[i]!;
368
+ if (entryPriority === priority) {
369
+ const drop = MaybePromise.catch(this.#store.delete(number), error =>
370
+ logger.warn(`Error dropping occurrence #${number}: ${error}`),
371
+ );
372
+ if (MaybePromise.is(drop)) {
373
+ asyncDrops.push(drop);
374
+ }
375
+ occurrences[i] = undefined;
376
+ toDelete--;
377
+ }
378
+ }
379
+ if (toDelete <= 0) {
380
+ break;
316
381
  }
317
382
  }
383
+ this.#occurrences = occurrences.filter(entry => entry) as OccurrenceSummary[];
384
+
318
385
  this.#storedEventCount = this.#occurrences.length;
319
386
 
320
387
  if (asyncDrops.length) {
@@ -342,10 +409,23 @@ export namespace OccurrenceManager {
342
409
  * {@link minimumEventAllowance}.
343
410
  */
344
411
  maxEventAllowance: number;
412
+
413
+ /**
414
+ * Minimum allowances by priority. This ensures a minimum number of non-critical events avoid LRU
415
+ * harvesting. Critical events take the rest of the entries up to the defined global minimum.
416
+ */
417
+ minPriorityEventAllowance: {
418
+ info: number;
419
+ debug: number;
420
+ };
345
421
  }
346
422
 
347
423
  export const DefaultBufferConfig: BufferConfig = {
348
424
  minEventAllowance: 10_000,
349
425
  maxEventAllowance: 11_000,
426
+ minPriorityEventAllowance: {
427
+ info: 2_000,
428
+ debug: 2_000,
429
+ },
350
430
  };
351
431
  }