@matter/protocol 0.12.4-alpha.0-20250210-ad8edf096 → 0.12.4-alpha.0-20250212-b2729c9eb

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 (236) hide show
  1. package/dist/cjs/action/Interactable.d.ts +36 -0
  2. package/dist/cjs/action/Interactable.d.ts.map +1 -0
  3. package/dist/cjs/action/Interactable.js +22 -0
  4. package/dist/cjs/action/Interactable.js.map +6 -0
  5. package/dist/cjs/action/Val.d.ts +94 -0
  6. package/dist/cjs/action/Val.d.ts.map +1 -0
  7. package/dist/cjs/action/Val.js +33 -0
  8. package/dist/cjs/action/Val.js.map +6 -0
  9. package/dist/cjs/action/errors.d.ts +69 -0
  10. package/dist/cjs/action/errors.d.ts.map +1 -0
  11. package/dist/cjs/action/errors.js +108 -0
  12. package/dist/cjs/action/errors.js.map +6 -0
  13. package/dist/cjs/action/index.d.ts +13 -0
  14. package/dist/cjs/action/index.d.ts.map +1 -0
  15. package/dist/cjs/action/index.js +30 -0
  16. package/dist/cjs/action/index.js.map +6 -0
  17. package/dist/cjs/action/protocols.d.ts +118 -0
  18. package/dist/cjs/action/protocols.d.ts.map +1 -0
  19. package/dist/cjs/action/protocols.js +22 -0
  20. package/dist/cjs/action/protocols.js.map +6 -0
  21. package/dist/cjs/action/request/Invoke.d.ts +35 -0
  22. package/dist/cjs/action/request/Invoke.d.ts.map +1 -0
  23. package/dist/cjs/action/request/Invoke.js +80 -0
  24. package/dist/cjs/action/request/Invoke.js.map +6 -0
  25. package/dist/cjs/action/request/MalformedRequestError.d.ts +14 -0
  26. package/dist/cjs/action/request/MalformedRequestError.d.ts.map +1 -0
  27. package/dist/cjs/action/request/MalformedRequestError.js +32 -0
  28. package/dist/cjs/action/request/MalformedRequestError.js.map +6 -0
  29. package/dist/cjs/action/request/Read.d.ts +141 -0
  30. package/dist/cjs/action/request/Read.d.ts.map +1 -0
  31. package/dist/cjs/action/request/Read.js +173 -0
  32. package/dist/cjs/action/request/Read.js.map +6 -0
  33. package/dist/cjs/action/request/Specifier.d.ts +82 -0
  34. package/dist/cjs/action/request/Specifier.d.ts.map +1 -0
  35. package/dist/cjs/action/request/Specifier.js +88 -0
  36. package/dist/cjs/action/request/Specifier.js.map +6 -0
  37. package/dist/cjs/action/request/Subscribe.d.ts +26 -0
  38. package/dist/cjs/action/request/Subscribe.d.ts.map +1 -0
  39. package/dist/cjs/action/request/Subscribe.js +50 -0
  40. package/dist/cjs/action/request/Subscribe.js.map +6 -0
  41. package/dist/cjs/action/request/Write.d.ts +10 -0
  42. package/dist/cjs/action/request/Write.d.ts.map +1 -0
  43. package/dist/cjs/action/request/Write.js +22 -0
  44. package/dist/cjs/action/request/Write.js.map +6 -0
  45. package/dist/cjs/action/request/index.d.ts +11 -0
  46. package/dist/cjs/action/request/index.d.ts.map +1 -0
  47. package/dist/cjs/action/request/index.js +28 -0
  48. package/dist/cjs/action/request/index.js.map +6 -0
  49. package/dist/cjs/action/response/InvokeResult.d.ts +15 -0
  50. package/dist/cjs/action/response/InvokeResult.d.ts.map +1 -0
  51. package/dist/cjs/action/response/InvokeResult.js +22 -0
  52. package/dist/cjs/action/response/InvokeResult.js.map +6 -0
  53. package/dist/cjs/action/response/ReadResult.d.ts +63 -0
  54. package/dist/cjs/action/response/ReadResult.d.ts.map +1 -0
  55. package/dist/cjs/action/response/ReadResult.js +22 -0
  56. package/dist/cjs/action/response/ReadResult.js.map +6 -0
  57. package/dist/cjs/action/response/SubscribeResult.d.ts +13 -0
  58. package/dist/cjs/action/response/SubscribeResult.d.ts.map +1 -0
  59. package/dist/cjs/action/response/SubscribeResult.js +22 -0
  60. package/dist/cjs/action/response/SubscribeResult.js.map +6 -0
  61. package/dist/cjs/action/response/WriteResult.d.ts +12 -0
  62. package/dist/cjs/action/response/WriteResult.d.ts.map +1 -0
  63. package/dist/cjs/action/response/WriteResult.js +22 -0
  64. package/dist/cjs/action/response/WriteResult.js.map +6 -0
  65. package/dist/cjs/action/response/index.d.ts +10 -0
  66. package/dist/cjs/action/response/index.d.ts.map +1 -0
  67. package/dist/cjs/action/response/index.js +27 -0
  68. package/dist/cjs/action/response/index.js.map +6 -0
  69. package/dist/cjs/action/server/AccessControl.d.ts +152 -0
  70. package/dist/cjs/action/server/AccessControl.d.ts.map +1 -0
  71. package/dist/cjs/action/server/AccessControl.js +287 -0
  72. package/dist/cjs/action/server/AccessControl.js.map +6 -0
  73. package/dist/cjs/action/server/AttributeResponse.d.ts +36 -0
  74. package/dist/cjs/action/server/AttributeResponse.d.ts.map +1 -0
  75. package/dist/cjs/action/server/AttributeResponse.js +352 -0
  76. package/dist/cjs/action/server/AttributeResponse.js.map +6 -0
  77. package/dist/cjs/action/server/ServerInteraction.d.ts +35 -0
  78. package/dist/cjs/action/server/ServerInteraction.d.ts.map +1 -0
  79. package/dist/cjs/action/server/ServerInteraction.js +52 -0
  80. package/dist/cjs/action/server/ServerInteraction.js.map +6 -0
  81. package/dist/cjs/action/server/index.d.ts +9 -0
  82. package/dist/cjs/action/server/index.d.ts.map +1 -0
  83. package/dist/cjs/action/server/index.js +26 -0
  84. package/dist/cjs/action/server/index.js.map +6 -0
  85. package/dist/cjs/index.d.ts +1 -0
  86. package/dist/cjs/index.d.ts.map +1 -1
  87. package/dist/cjs/index.js +1 -0
  88. package/dist/cjs/index.js.map +1 -1
  89. package/dist/cjs/interaction/AccessControlManager.d.ts +1 -1
  90. package/dist/cjs/interaction/AccessControlManager.d.ts.map +1 -1
  91. package/dist/cjs/interaction/AccessControlManager.js +2 -2
  92. package/dist/cjs/interaction/AccessControlManager.js.map +1 -1
  93. package/dist/cjs/interaction/AttributeDataEncoder.d.ts +1 -3
  94. package/dist/cjs/interaction/AttributeDataEncoder.d.ts.map +1 -1
  95. package/dist/cjs/interaction/InteractionMessenger.d.ts +5 -5
  96. package/dist/cjs/interaction/InteractionMessenger.d.ts.map +1 -1
  97. package/dist/cjs/interaction/InteractionServer.d.ts +1 -1
  98. package/dist/cjs/interaction/InteractionServer.d.ts.map +1 -1
  99. package/dist/cjs/interaction/InteractionServer.js +2 -2
  100. package/dist/cjs/interaction/InteractionServer.js.map +1 -1
  101. package/dist/cjs/interaction/ServerSubscription.d.ts +2 -2
  102. package/dist/cjs/interaction/ServerSubscription.d.ts.map +1 -1
  103. package/dist/cjs/interaction/ServerSubscription.js +0 -1
  104. package/dist/cjs/interaction/ServerSubscription.js.map +1 -1
  105. package/dist/cjs/mdns/MdnsScanner.d.ts +2 -2
  106. package/dist/esm/action/Interactable.d.ts +36 -0
  107. package/dist/esm/action/Interactable.d.ts.map +1 -0
  108. package/dist/esm/action/Interactable.js +6 -0
  109. package/dist/esm/action/Interactable.js.map +6 -0
  110. package/dist/esm/action/Val.d.ts +94 -0
  111. package/dist/esm/action/Val.d.ts.map +1 -0
  112. package/dist/esm/action/Val.js +13 -0
  113. package/dist/esm/action/Val.js.map +6 -0
  114. package/dist/esm/action/errors.d.ts +69 -0
  115. package/dist/esm/action/errors.d.ts.map +1 -0
  116. package/dist/esm/action/errors.js +88 -0
  117. package/dist/esm/action/errors.js.map +6 -0
  118. package/dist/esm/action/index.d.ts +13 -0
  119. package/dist/esm/action/index.d.ts.map +1 -0
  120. package/dist/esm/action/index.js +13 -0
  121. package/dist/esm/action/index.js.map +6 -0
  122. package/dist/esm/action/protocols.d.ts +118 -0
  123. package/dist/esm/action/protocols.d.ts.map +1 -0
  124. package/dist/esm/action/protocols.js +6 -0
  125. package/dist/esm/action/protocols.js.map +6 -0
  126. package/dist/esm/action/request/Invoke.d.ts +35 -0
  127. package/dist/esm/action/request/Invoke.d.ts.map +1 -0
  128. package/dist/esm/action/request/Invoke.js +60 -0
  129. package/dist/esm/action/request/Invoke.js.map +6 -0
  130. package/dist/esm/action/request/MalformedRequestError.d.ts +14 -0
  131. package/dist/esm/action/request/MalformedRequestError.d.ts.map +1 -0
  132. package/dist/esm/action/request/MalformedRequestError.js +12 -0
  133. package/dist/esm/action/request/MalformedRequestError.js.map +6 -0
  134. package/dist/esm/action/request/Read.d.ts +141 -0
  135. package/dist/esm/action/request/Read.d.ts.map +1 -0
  136. package/dist/esm/action/request/Read.js +153 -0
  137. package/dist/esm/action/request/Read.js.map +6 -0
  138. package/dist/esm/action/request/Specifier.d.ts +82 -0
  139. package/dist/esm/action/request/Specifier.d.ts.map +1 -0
  140. package/dist/esm/action/request/Specifier.js +68 -0
  141. package/dist/esm/action/request/Specifier.js.map +6 -0
  142. package/dist/esm/action/request/Subscribe.d.ts +26 -0
  143. package/dist/esm/action/request/Subscribe.d.ts.map +1 -0
  144. package/dist/esm/action/request/Subscribe.js +30 -0
  145. package/dist/esm/action/request/Subscribe.js.map +6 -0
  146. package/dist/esm/action/request/Write.d.ts +10 -0
  147. package/dist/esm/action/request/Write.d.ts.map +1 -0
  148. package/dist/esm/action/request/Write.js +6 -0
  149. package/dist/esm/action/request/Write.js.map +6 -0
  150. package/dist/esm/action/request/index.d.ts +11 -0
  151. package/dist/esm/action/request/index.d.ts.map +1 -0
  152. package/dist/esm/action/request/index.js +11 -0
  153. package/dist/esm/action/request/index.js.map +6 -0
  154. package/dist/esm/action/response/InvokeResult.d.ts +15 -0
  155. package/dist/esm/action/response/InvokeResult.d.ts.map +1 -0
  156. package/dist/esm/action/response/InvokeResult.js +6 -0
  157. package/dist/esm/action/response/InvokeResult.js.map +6 -0
  158. package/dist/esm/action/response/ReadResult.d.ts +63 -0
  159. package/dist/esm/action/response/ReadResult.d.ts.map +1 -0
  160. package/dist/esm/action/response/ReadResult.js +6 -0
  161. package/dist/esm/action/response/ReadResult.js.map +6 -0
  162. package/dist/esm/action/response/SubscribeResult.d.ts +13 -0
  163. package/dist/esm/action/response/SubscribeResult.d.ts.map +1 -0
  164. package/dist/esm/action/response/SubscribeResult.js +6 -0
  165. package/dist/esm/action/response/SubscribeResult.js.map +6 -0
  166. package/dist/esm/action/response/WriteResult.d.ts +12 -0
  167. package/dist/esm/action/response/WriteResult.d.ts.map +1 -0
  168. package/dist/esm/action/response/WriteResult.js +6 -0
  169. package/dist/esm/action/response/WriteResult.js.map +6 -0
  170. package/dist/esm/action/response/index.d.ts +10 -0
  171. package/dist/esm/action/response/index.d.ts.map +1 -0
  172. package/dist/esm/action/response/index.js +10 -0
  173. package/dist/esm/action/response/index.js.map +6 -0
  174. package/dist/esm/action/server/AccessControl.d.ts +152 -0
  175. package/dist/esm/action/server/AccessControl.d.ts.map +1 -0
  176. package/dist/esm/action/server/AccessControl.js +267 -0
  177. package/dist/esm/action/server/AccessControl.js.map +6 -0
  178. package/dist/esm/action/server/AttributeResponse.d.ts +36 -0
  179. package/dist/esm/action/server/AttributeResponse.d.ts.map +1 -0
  180. package/dist/esm/action/server/AttributeResponse.js +339 -0
  181. package/dist/esm/action/server/AttributeResponse.js.map +6 -0
  182. package/dist/esm/action/server/ServerInteraction.d.ts +35 -0
  183. package/dist/esm/action/server/ServerInteraction.d.ts.map +1 -0
  184. package/dist/esm/action/server/ServerInteraction.js +32 -0
  185. package/dist/esm/action/server/ServerInteraction.js.map +6 -0
  186. package/dist/esm/action/server/index.d.ts +9 -0
  187. package/dist/esm/action/server/index.d.ts.map +1 -0
  188. package/dist/esm/action/server/index.js +9 -0
  189. package/dist/esm/action/server/index.js.map +6 -0
  190. package/dist/esm/index.d.ts +1 -0
  191. package/dist/esm/index.d.ts.map +1 -1
  192. package/dist/esm/index.js +1 -0
  193. package/dist/esm/index.js.map +1 -1
  194. package/dist/esm/interaction/AccessControlManager.d.ts +1 -1
  195. package/dist/esm/interaction/AccessControlManager.d.ts.map +1 -1
  196. package/dist/esm/interaction/AccessControlManager.js +2 -2
  197. package/dist/esm/interaction/AccessControlManager.js.map +1 -1
  198. package/dist/esm/interaction/AttributeDataEncoder.d.ts +1 -3
  199. package/dist/esm/interaction/AttributeDataEncoder.d.ts.map +1 -1
  200. package/dist/esm/interaction/InteractionMessenger.d.ts +5 -5
  201. package/dist/esm/interaction/InteractionMessenger.d.ts.map +1 -1
  202. package/dist/esm/interaction/InteractionServer.d.ts +1 -1
  203. package/dist/esm/interaction/InteractionServer.d.ts.map +1 -1
  204. package/dist/esm/interaction/InteractionServer.js +2 -2
  205. package/dist/esm/interaction/InteractionServer.js.map +1 -1
  206. package/dist/esm/interaction/ServerSubscription.d.ts +2 -2
  207. package/dist/esm/interaction/ServerSubscription.d.ts.map +1 -1
  208. package/dist/esm/interaction/ServerSubscription.js +0 -1
  209. package/dist/esm/interaction/ServerSubscription.js.map +1 -1
  210. package/dist/esm/mdns/MdnsScanner.d.ts +2 -2
  211. package/package.json +6 -6
  212. package/src/action/Interactable.ts +40 -0
  213. package/src/action/Val.ts +111 -0
  214. package/src/action/errors.ts +119 -0
  215. package/src/action/index.ts +13 -0
  216. package/src/action/protocols.ts +134 -0
  217. package/src/action/request/Invoke.ts +93 -0
  218. package/src/action/request/MalformedRequestError.ts +14 -0
  219. package/src/action/request/Read.ts +356 -0
  220. package/src/action/request/Specifier.ts +146 -0
  221. package/src/action/request/Subscribe.ts +54 -0
  222. package/src/action/request/Write.ts +13 -0
  223. package/src/action/request/index.ts +11 -0
  224. package/src/action/response/InvokeResult.ts +17 -0
  225. package/src/action/response/ReadResult.ts +89 -0
  226. package/src/action/response/SubscribeResult.ts +14 -0
  227. package/src/action/response/WriteResult.ts +13 -0
  228. package/src/action/response/index.ts +10 -0
  229. package/src/action/server/AccessControl.ts +494 -0
  230. package/src/action/server/AttributeResponse.ts +413 -0
  231. package/src/action/server/ServerInteraction.ts +64 -0
  232. package/src/action/server/index.ts +9 -0
  233. package/src/index.ts +1 -0
  234. package/src/interaction/AccessControlManager.ts +3 -3
  235. package/src/interaction/InteractionServer.ts +2 -3
  236. package/src/interaction/ServerSubscription.ts +0 -2
@@ -0,0 +1,413 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2022-2025 Project CHIP Authors
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import { AttributeTypeProtocol, ClusterProtocol, EndpointProtocol, NodeProtocol } from "#action/protocols.js";
8
+ import { Read } from "#action/request/Read.js";
9
+ import { ReadResult } from "#action/response/ReadResult.js";
10
+ import { AccessControl } from "#action/server/AccessControl.js";
11
+ import { Val } from "#action/Val.js";
12
+ import { InternalError } from "#general";
13
+ import { AccessLevel, AttributeModel, ElementTag } from "#model";
14
+ import {
15
+ AttributePath,
16
+ BitmapSchema,
17
+ ClusterId,
18
+ EndpointNumber,
19
+ GlobalAttributes,
20
+ NodeId,
21
+ Status,
22
+ StatusResponseError,
23
+ WildcardPathFlagsBitmap,
24
+ } from "#types";
25
+
26
+ export const GlobalAttrIds = new Set(Object.values(GlobalAttributes).map(attr => attr.id));
27
+ export const WildcardPathFlagsCodec = BitmapSchema(WildcardPathFlagsBitmap);
28
+ export const FallbackLimits: AccessControl.Limits = {
29
+ fabricScoped: false,
30
+ fabricSensitive: false,
31
+ readable: true,
32
+ readLevel: AccessLevel.View,
33
+ timed: false,
34
+ writable: true,
35
+ writeLevel: AccessLevel.Administer,
36
+ };
37
+
38
+ /**
39
+ * Implements read of attribute data for Matter "read" and "subscribe" interactions.
40
+ *
41
+ * TODO - profile; ensure nested functions are properly JITed and/or inlined
42
+ */
43
+ export class AttributeResponse<SessionT extends AccessControl.Session = AccessControl.Session> {
44
+ // Configuration
45
+ #session: SessionT;
46
+ #node: NodeProtocol;
47
+ #versions?: Record<EndpointNumber, Record<ClusterId, number>> | undefined;
48
+
49
+ // Each input AttributePathIB that does not have an error installs a producer. Producers run after validation and
50
+ // generate actual attribute data
51
+ #dataProducers?: Array<(this: AttributeResponse) => Iterable<ReadResult.Chunk>>;
52
+
53
+ // The initial "chunk" may be a list of errors. As producers execute it is a set of records associated with the
54
+ // most recently touched endpoint. When the endpoint changes the previous chunk emits
55
+ #chunk?: ReadResult.Report[];
56
+
57
+ // The following state updates as data producers execute. This serves both to convey state between functions and as
58
+ // a cache between producers that touch the same endpoint and/or cluster
59
+ #currentEndpoint?: EndpointProtocol;
60
+ #currentCluster?: ClusterProtocol;
61
+ #currentState?: Val.ProtocolStruct;
62
+ #wildcardPathFlags = 0;
63
+
64
+ // The node ID may be expensive to retrieve and is invariant so we cache it here
65
+ #cachedNodeId?: NodeId;
66
+
67
+ constructor(node: NodeProtocol, session: SessionT, { dataVersionFilters, attributeRequests }: Read.Attributes) {
68
+ this.#node = node;
69
+ this.#session = session;
70
+
71
+ const nodeId = session.fabric === undefined ? NodeId.UNSPECIFIED_NODE_ID : this.#nodeId;
72
+
73
+ // Index versions
74
+ if (dataVersionFilters?.length) {
75
+ this.#versions = {};
76
+ for (const {
77
+ path: { nodeId: filterNodeId, endpointId, clusterId },
78
+ dataVersion,
79
+ } of dataVersionFilters) {
80
+ if (filterNodeId !== undefined && filterNodeId !== nodeId) {
81
+ continue;
82
+ }
83
+ if (typeof endpointId !== "number") {
84
+ // Grr github advanced security
85
+ continue;
86
+ }
87
+ (this.#versions[endpointId] ?? (this.#versions[endpointId] = {}))[clusterId] = dataVersion;
88
+ }
89
+ }
90
+
91
+ // Register paths
92
+ for (const path of attributeRequests) {
93
+ if (path.endpointId === undefined || path.clusterId === undefined || path.attributeId === undefined) {
94
+ this.#addWildcard(path);
95
+ } else {
96
+ this.#addConcrete(path as ReadResult.ConcreteAttributePath);
97
+ }
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Emits chunks produced by paths added via {@link #addWildcard} and {@link #addConcrete}.
103
+ */
104
+ *[Symbol.iterator](): Generator<ReadResult.Chunk, void, void> {
105
+ if (this.#dataProducers) {
106
+ for (const producer of this.#dataProducers) {
107
+ yield* producer.apply(this);
108
+ }
109
+ }
110
+
111
+ // We emit chunks lazily when the endpoint changes so there may be one remaining chunk. There may also be a
112
+ // chunk with errors even if there are no data producers
113
+ if (this.#chunk !== undefined) {
114
+ yield this.#chunk;
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Validate a wildcard path and update internal state.
120
+ */
121
+ #addWildcard(path: AttributePath) {
122
+ const { nodeId, endpointId, clusterId, attributeId, wildcardPathFlags } = path;
123
+
124
+ if (nodeId !== undefined && nodeId !== this.#nodeId) {
125
+ return;
126
+ }
127
+
128
+ const wpf = wildcardPathFlags ? WildcardPathFlagsCodec.encode(wildcardPathFlags) : 0;
129
+
130
+ if (clusterId === undefined && attributeId !== undefined && !GlobalAttrIds.has(attributeId)) {
131
+ throw new StatusResponseError(
132
+ `Illegal read of wildcard cluster with non-global attribute #${attributeId}`,
133
+ Status.InvalidAction,
134
+ );
135
+ }
136
+
137
+ if (endpointId === undefined) {
138
+ this.#addProducer(function* (this: AttributeResponse) {
139
+ this.#wildcardPathFlags = wpf;
140
+ for (const endpoint of this.#node) {
141
+ yield* this.#readEndpointForWildcard(endpoint, path);
142
+ }
143
+ });
144
+ return;
145
+ }
146
+
147
+ const endpoint = this.#node[endpointId];
148
+ if (endpoint) {
149
+ this.#addProducer(function (this: AttributeResponse) {
150
+ this.#wildcardPathFlags = wpf;
151
+ return this.#readEndpointForWildcard(endpoint, path);
152
+ });
153
+ }
154
+ }
155
+
156
+ /**
157
+ * Validate a concrete path and update internal state.
158
+ */
159
+ #addConcrete(path: ReadResult.ConcreteAttributePath) {
160
+ const { nodeId, endpointId, clusterId, attributeId } = path;
161
+ if (nodeId !== undefined && this.#nodeId !== nodeId) {
162
+ this.#addStatus(path, Status.UnsupportedNode);
163
+ }
164
+
165
+ // Resolve path elements
166
+ const endpoint = this.#node[endpointId];
167
+ const cluster = endpoint?.[clusterId];
168
+ const attribute = cluster?.type.attributes[attributeId];
169
+ let limits;
170
+ if (attribute === undefined) {
171
+ // We still need to authorize the user for access even though this path doesn't resolve. Spec is not
172
+ // explicit on what privilege level we should require as normally that information comes from the resolved
173
+ // attribute. So attempt to resolve via the active model
174
+ const modelAttr = this.#node.matter
175
+ .member(path.clusterId, [ElementTag.Cluster])
176
+ ?.member(path.attributeId, [ElementTag.Attribute]);
177
+
178
+ if (modelAttr) {
179
+ // OK cluster doesn't exist at that location, but we do understand semantically, so use limits from the
180
+ // model
181
+ limits = AccessControl(modelAttr as AttributeModel).limits;
182
+ } else {
183
+ // We've got no idea. This effectively falls back to "view" privilege
184
+ limits = FallbackLimits;
185
+ }
186
+ } else {
187
+ limits = attribute.limits;
188
+ }
189
+
190
+ // Validate access. Order here prescribed by 1.4 core spec 8.4.3.2
191
+ switch (this.#session.authorityAt(limits.readLevel)) {
192
+ case AccessControl.Authority.Granted:
193
+ break;
194
+
195
+ case AccessControl.Authority.Unauthorized:
196
+ this.#addStatus(path, Status.UnsupportedAccess);
197
+ return;
198
+
199
+ case AccessControl.Authority.Restricted:
200
+ this.#addStatus(path, Status.AccessRestricted);
201
+ return;
202
+
203
+ default:
204
+ throw new InternalError(
205
+ `Unsupported authorization state ${this.#session.authorityAt(limits.readLevel)}`,
206
+ );
207
+ }
208
+ if (endpoint === undefined) {
209
+ this.#addStatus(path, Status.UnsupportedEndpoint);
210
+ return;
211
+ }
212
+ if (cluster === undefined) {
213
+ this.#addStatus(path, Status.UnsupportedCluster);
214
+ return;
215
+ }
216
+ if (attribute === undefined) {
217
+ this.#addStatus(path, Status.UnsupportedAttribute);
218
+ return;
219
+ }
220
+ if (!limits.readable) {
221
+ this.#addStatus(path, Status.UnsupportedRead);
222
+ return;
223
+ }
224
+
225
+ // Skip if version is unchanged
226
+ const skipVersion = this.#versions?.[path.endpointId]?.[path.clusterId];
227
+ if (skipVersion !== undefined && skipVersion === cluster.version) {
228
+ return;
229
+ }
230
+
231
+ // This path contributes an attribute value
232
+ this.#addProducer(function* () {
233
+ // Update internal state for target endpoint
234
+ if (this.#currentEndpoint !== endpoint) {
235
+ if (this.#chunk) {
236
+ yield this.#chunk;
237
+ this.#chunk = undefined;
238
+ }
239
+ this.#currentEndpoint = endpoint;
240
+ this.#currentCluster = cluster;
241
+ this.#currentState = cluster.open(this.#session);
242
+ } else if (this.#currentCluster !== cluster) {
243
+ this.#currentCluster = cluster;
244
+ this.#currentState = cluster.open(this.#session);
245
+ } else if (this.#currentState === undefined) {
246
+ this.#currentState = cluster.open(this.#session);
247
+ }
248
+
249
+ // Perform actual read
250
+ this.#addValue(path, this.#currentState);
251
+ });
252
+ }
253
+
254
+ /**
255
+ * Starts new chunk or adds to current chunk all values from {@link endpoint} selected by {@link path}.
256
+ *
257
+ * Emits previous chunk if it exists and was not for this endpoint. This means that our chunk size is one endpoint
258
+ * worth of data, except for the initial error chunk if there are path errors.
259
+ *
260
+ * {@link this.#wildcardPathFlags} to numeric bitmap must be set prior to invocation.
261
+ *
262
+ * TODO - skip endpoints for which subject is unauthorized
263
+ */
264
+ *#readEndpointForWildcard(endpoint: EndpointProtocol, path: AttributePath) {
265
+ if (endpoint.wildcardPathFlags & this.#wildcardPathFlags) {
266
+ return;
267
+ }
268
+
269
+ if (this.#currentEndpoint !== endpoint) {
270
+ if (this.#chunk) {
271
+ yield this.#chunk;
272
+ this.#chunk = undefined;
273
+ }
274
+ this.#currentEndpoint = endpoint;
275
+ this.#currentCluster = undefined;
276
+ }
277
+
278
+ const { clusterId } = path;
279
+ if (clusterId === undefined) {
280
+ for (const cluster of endpoint) {
281
+ this.#readClusterForWildcard(cluster, path);
282
+ }
283
+ } else {
284
+ const cluster = endpoint[clusterId];
285
+ if (cluster !== undefined) {
286
+ this.#readClusterForWildcard(cluster, path);
287
+ }
288
+ }
289
+ }
290
+
291
+ /**
292
+ * Read values from a specific {@link cluster} for a wildcard path.
293
+ *
294
+ * Depends on state initialized by {@link #readEndpointForWildcard}.
295
+ *
296
+ * TODO - skip clusters for which subject is unauthorized
297
+ */
298
+ #readClusterForWildcard(cluster: ClusterProtocol, path: AttributePath) {
299
+ if (cluster.type.wildcardPathFlags & this.#wildcardPathFlags) {
300
+ return;
301
+ }
302
+
303
+ if (this.#currentCluster !== cluster) {
304
+ this.#currentCluster = cluster;
305
+ this.#currentState = undefined;
306
+ }
307
+
308
+ const skipVersion = this.#versions?.[this.#currentEndpoint!.id]?.[cluster.type.id];
309
+ if (skipVersion !== undefined && skipVersion === cluster.version) {
310
+ return;
311
+ }
312
+
313
+ const { attributeId } = path;
314
+ if (attributeId === undefined) {
315
+ for (const attribute of cluster.type.attributes) {
316
+ this.#readAttributeForWildcard(attribute, path);
317
+ }
318
+ } else {
319
+ const attribute = cluster.type.attributes[attributeId];
320
+ if (attribute !== undefined) {
321
+ this.#readAttributeForWildcard(attribute, path);
322
+ }
323
+ }
324
+ }
325
+
326
+ /**
327
+ * Read values from a specific {@link attribute} for a wildcard path.
328
+ *
329
+ * Depends on state initialized by {@link #readClusterForWildcard}.
330
+ */
331
+ #readAttributeForWildcard(attribute: AttributeTypeProtocol, path: AttributePath) {
332
+ if (attribute.wildcardPathFlags & this.#wildcardPathFlags) {
333
+ return;
334
+ }
335
+
336
+ if (
337
+ !attribute.limits.readable ||
338
+ this.#session.authorityAt(attribute.limits.readLevel, this.#currentCluster!.location) !==
339
+ AccessControl.Authority.Granted
340
+ ) {
341
+ return;
342
+ }
343
+
344
+ if (this.#currentState === undefined) {
345
+ this.#currentState = this.#currentCluster!.open(this.#session);
346
+ }
347
+ this.#addValue(
348
+ {
349
+ ...path,
350
+ endpointId: this.#currentEndpoint?.id as EndpointNumber,
351
+ clusterId: this.#currentCluster?.type.id as ClusterId,
352
+ attributeId: attribute.id,
353
+ },
354
+ this.#currentState[attribute.id],
355
+ );
356
+ }
357
+
358
+ /**
359
+ * Add a function that produces data. These functions are run after validation of input paths.
360
+ */
361
+ #addProducer(producer: (this: AttributeResponse) => Iterable<ReadResult.Chunk>) {
362
+ if (this.#dataProducers) {
363
+ this.#dataProducers.push(producer);
364
+ } else {
365
+ this.#dataProducers = [producer];
366
+ }
367
+ }
368
+
369
+ /**
370
+ * Add a status value.
371
+ */
372
+ #addStatus(path: ReadResult.ConcreteAttributePath, status: Status) {
373
+ const report: ReadResult.GlobalAttributeStatus = {
374
+ kind: "attr-status",
375
+ path,
376
+ status,
377
+ };
378
+
379
+ if (this.#chunk) {
380
+ this.#chunk.push(report);
381
+ } else {
382
+ this.#chunk = [report];
383
+ }
384
+ }
385
+
386
+ /**
387
+ * Add an attribute value.
388
+ */
389
+ #addValue(path: ReadResult.ConcreteAttributePath, value: unknown) {
390
+ const report: ReadResult.AttributeValue = {
391
+ kind: "attr-value",
392
+ path,
393
+ value,
394
+ };
395
+
396
+ if (this.#chunk) {
397
+ this.#chunk.push(report);
398
+ } else {
399
+ this.#chunk = [report];
400
+ }
401
+ }
402
+
403
+ /**
404
+ * The node ID used to filter paths with node ID specified. Unsure if this is ever actually used.
405
+ */
406
+ get #nodeId() {
407
+ if (this.#cachedNodeId === undefined) {
408
+ this.#cachedNodeId =
409
+ (this.#session.fabric && this.#node.nodeIdFor(this.#session.fabric)) ?? NodeId.UNSPECIFIED_NODE_ID;
410
+ }
411
+ return this.#cachedNodeId;
412
+ }
413
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2022-2025 Project CHIP Authors
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import { Interactable } from "#action/Interactable.js";
8
+ import { NodeProtocol } from "#action/protocols.js";
9
+ import { Invoke } from "#action/request/Invoke.js";
10
+ import { Read } from "#action/request/Read.js";
11
+ import { Subscribe } from "#action/request/Subscribe.js";
12
+ import { Write } from "#action/request/Write.js";
13
+ import { InvokeResult } from "#action/response/InvokeResult.js";
14
+ import { ReadResult } from "#action/response/ReadResult.js";
15
+ import { SubscribeResult } from "#action/response/SubscribeResult.js";
16
+ import { WriteResult } from "#action/response/WriteResult.js";
17
+ import { AccessControl } from "#action/server/AccessControl.js";
18
+ import { NotImplementedError } from "#general";
19
+ import { AttributeResponse } from "./AttributeResponse.js";
20
+
21
+ /**
22
+ * Implementation of server interaction.
23
+ *
24
+ * This implementation currently focuses on read of attribute data with other actions to be implemented later. Until
25
+ * completion there will be redundancy with other components including:
26
+ *
27
+ * - InteractionServer (significant overlap with this class)
28
+ *
29
+ * - InteractionEndpointStructure ({@link NodeProtocol} is largely duplicative)
30
+ */
31
+ export class ServerInteraction<SessionT extends AccessControl.Session = AccessControl.Session>
32
+ implements Interactable<SessionT>
33
+ {
34
+ #node: NodeProtocol;
35
+
36
+ constructor(node: NodeProtocol) {
37
+ this.#node = node;
38
+ }
39
+
40
+ async *read(request: Read, session: SessionT): ReadResult {
41
+ // TODO - validate request
42
+
43
+ if (Read.isAttribute(request)) {
44
+ yield* new AttributeResponse(this.#node, session, request);
45
+ }
46
+
47
+ // TODO - event reads
48
+ }
49
+
50
+ subscribe(_request: Subscribe, _session?: SessionT): SubscribeResult {
51
+ // TODO
52
+ throw new NotImplementedError();
53
+ }
54
+
55
+ write<T extends Write>(_request: T, _session?: SessionT): WriteResult<T> {
56
+ // TODO
57
+ throw new NotImplementedError();
58
+ }
59
+
60
+ invoke<T extends Invoke>(_request: T, _session?: SessionT): InvokeResult<T> {
61
+ // TODO
62
+ throw new NotImplementedError();
63
+ }
64
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2022-2025 Project CHIP Authors
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ export * from "./AccessControl.js";
8
+ export * from "./AttributeResponse.js";
9
+ export * from "./ServerInteraction.js";
package/src/index.ts CHANGED
@@ -4,6 +4,7 @@
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
6
 
7
+ export * from "./action/index.js";
7
8
  export * from "./ble/index.js";
8
9
  export * from "./certificate/index.js";
9
10
  export * from "./cluster/index.js";
@@ -30,7 +30,7 @@ export type AclExtensionEntry = AccessControl.AccessControlExtension;
30
30
  export type AclExtensionList = AclExtensionEntry[];
31
31
 
32
32
  export type AclEndpointContext = {
33
- number: EndpointNumber;
33
+ id: EndpointNumber;
34
34
  deviceTypes: DeviceTypeId[];
35
35
  };
36
36
 
@@ -163,7 +163,7 @@ export class AccessControlManager {
163
163
  }
164
164
 
165
165
  logger.notice(
166
- `Failed access control check for ${endpoint.number}/0x${toHex(clusterId)} and fabricIndex ${session.associatedFabric.fabricIndex}, acl=`,
166
+ `Failed access control check for ${endpoint.id}/0x${toHex(clusterId)} and fabricIndex ${session.associatedFabric.fabricIndex}, acl=`,
167
167
  this.#getAccessControlEntriesForFabric(session.associatedFabric),
168
168
  "with ISD=",
169
169
  this.#getIsdFromMessage(session),
@@ -180,7 +180,7 @@ export class AccessControlManager {
180
180
  * Determines the granted privileges for the given session, endpoint, and cluster ID and returns them.
181
181
  */
182
182
  getGrantedPrivileges(session: SecureSession, endpoint: AclEndpointContext, clusterId: ClusterId): AccessLevel[] {
183
- const endpointId = endpoint.number;
183
+ const endpointId = endpoint.id;
184
184
  const fabric = session.fabric;
185
185
  const subjectDesc = this.#getIsdFromMessage(session);
186
186
  const acl = fabric ? this.#getAccessControlEntriesForFabric(fabric) : [ImplicitDefaultPaseAclEntry];
@@ -589,7 +589,6 @@ export class InteractionServer implements ProtocolHandler, InteractionRecipient
589
589
  * This can currently only be used for subscriptions because errors are ignored!
590
590
  */
591
591
  protected readEndpointAttributesForSubscription(
592
- _endpointId: EndpointNumber,
593
592
  attributes: { path: AttributePath; attribute: AnyAttributeServer<any> }[],
594
593
  exchange: MessageExchange,
595
594
  isFabricFiltered: boolean,
@@ -1079,8 +1078,8 @@ export class InteractionServer implements ProtocolHandler, InteractionRecipient
1079
1078
  readAttribute: (path, attribute, offline) =>
1080
1079
  this.readAttribute(path, attribute, exchange, isFabricFiltered, message, offline),
1081
1080
 
1082
- readEndpointAttributesForSubscription: (endpointId, attributes) =>
1083
- this.readEndpointAttributesForSubscription(endpointId, attributes, exchange, isFabricFiltered, message),
1081
+ readEndpointAttributesForSubscription: attributes =>
1082
+ this.readEndpointAttributesForSubscription(attributes, exchange, isFabricFiltered, message),
1084
1083
 
1085
1084
  readEvent: (path, event, eventFilters) =>
1086
1085
  this.readEvent(path, eventFilters, event, exchange, isFabricFiltered, message),
@@ -136,7 +136,6 @@ export interface ServerSubscriptionContext {
136
136
  offline?: boolean,
137
137
  ): { version: number; value: unknown };
138
138
  readEndpointAttributesForSubscription(
139
- endpointId: EndpointNumber,
140
139
  attributes: { path: AttributePath; attribute: AnyAttributeServer<unknown>; offline?: boolean }[],
141
140
  ): { path: AttributePath; attribute: AnyAttributeServer<unknown>; version: number; value: unknown }[];
142
141
  readEvent(
@@ -736,7 +735,6 @@ export class ServerSubscription extends Subscription {
736
735
  const endpointAttributes = attributesPerCluster.get(endpointId)!;
737
736
  attributesPerCluster.delete(endpointId);
738
737
  for (const { path, attribute, value, version } of this.#context.readEndpointAttributesForSubscription(
739
- endpointId,
740
738
  endpointAttributes,
741
739
  )) {
742
740
  if (value === undefined) continue;