@matter/node 0.13.0 → 0.13.1-alpha.0-20250501-80c86b03e
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/behavior/context/server/OnlineContext.d.ts.map +1 -1
- package/dist/cjs/behavior/context/server/OnlineContext.js +6 -3
- package/dist/cjs/behavior/context/server/OnlineContext.js.map +1 -1
- package/dist/cjs/behavior/system/controller/ControllerBehavior.js +2 -1
- package/dist/cjs/behavior/system/controller/ControllerBehavior.js.map +1 -1
- package/dist/cjs/behavior/system/network/ServerNetworkRuntime.d.ts.map +1 -1
- package/dist/cjs/behavior/system/network/ServerNetworkRuntime.js +7 -7
- package/dist/cjs/behavior/system/network/ServerNetworkRuntime.js.map +1 -1
- package/dist/cjs/behavior/system/subscription/SubscriptionBehavior.d.ts +2 -3
- package/dist/cjs/behavior/system/subscription/SubscriptionBehavior.d.ts.map +1 -1
- package/dist/cjs/behavior/system/subscription/SubscriptionBehavior.js.map +1 -1
- package/dist/cjs/node/server/InteractionServer.d.ts +84 -0
- package/dist/cjs/node/server/InteractionServer.d.ts.map +1 -0
- package/dist/cjs/node/server/InteractionServer.js +1326 -0
- package/dist/cjs/node/server/InteractionServer.js.map +6 -0
- package/dist/cjs/node/server/ProtocolService.d.ts.map +1 -1
- package/dist/cjs/node/server/ProtocolService.js +9 -8
- package/dist/cjs/node/server/ProtocolService.js.map +2 -2
- package/dist/cjs/node/server/index.d.ts +1 -1
- package/dist/cjs/node/server/index.d.ts.map +1 -1
- package/dist/cjs/node/server/index.js +1 -1
- package/dist/cjs/node/server/index.js.map +1 -1
- package/dist/esm/behavior/context/server/OnlineContext.d.ts.map +1 -1
- package/dist/esm/behavior/context/server/OnlineContext.js +6 -3
- package/dist/esm/behavior/context/server/OnlineContext.js.map +1 -1
- package/dist/esm/behavior/system/controller/ControllerBehavior.js +1 -1
- package/dist/esm/behavior/system/controller/ControllerBehavior.js.map +1 -1
- package/dist/esm/behavior/system/network/ServerNetworkRuntime.d.ts.map +1 -1
- package/dist/esm/behavior/system/network/ServerNetworkRuntime.js +2 -3
- package/dist/esm/behavior/system/network/ServerNetworkRuntime.js.map +1 -1
- package/dist/esm/behavior/system/subscription/SubscriptionBehavior.d.ts +2 -3
- package/dist/esm/behavior/system/subscription/SubscriptionBehavior.d.ts.map +1 -1
- package/dist/esm/behavior/system/subscription/SubscriptionBehavior.js.map +1 -1
- package/dist/esm/node/server/InteractionServer.d.ts +84 -0
- package/dist/esm/node/server/InteractionServer.d.ts.map +1 -0
- package/dist/esm/node/server/InteractionServer.js +1348 -0
- package/dist/esm/node/server/InteractionServer.js.map +6 -0
- package/dist/esm/node/server/ProtocolService.d.ts.map +1 -1
- package/dist/esm/node/server/ProtocolService.js +9 -8
- package/dist/esm/node/server/ProtocolService.js.map +1 -1
- package/dist/esm/node/server/index.d.ts +1 -1
- package/dist/esm/node/server/index.d.ts.map +1 -1
- package/dist/esm/node/server/index.js +1 -1
- package/package.json +7 -7
- package/src/behavior/context/server/OnlineContext.ts +9 -4
- package/src/behavior/system/controller/ControllerBehavior.ts +1 -1
- package/src/behavior/system/network/ServerNetworkRuntime.ts +4 -7
- package/src/behavior/system/subscription/SubscriptionBehavior.ts +2 -3
- package/src/node/server/InteractionServer.ts +1757 -0
- package/src/node/server/ProtocolService.ts +10 -8
- package/src/node/server/index.ts +1 -1
- package/dist/cjs/node/server/TransactionalInteractionServer.d.ts +0 -57
- package/dist/cjs/node/server/TransactionalInteractionServer.d.ts.map +0 -1
- package/dist/cjs/node/server/TransactionalInteractionServer.js +0 -334
- package/dist/cjs/node/server/TransactionalInteractionServer.js.map +0 -6
- package/dist/esm/node/server/TransactionalInteractionServer.d.ts +0 -57
- package/dist/esm/node/server/TransactionalInteractionServer.d.ts.map +0 -1
- package/dist/esm/node/server/TransactionalInteractionServer.js +0 -322
- package/dist/esm/node/server/TransactionalInteractionServer.js.map +0 -6
- package/src/node/server/TransactionalInteractionServer.ts +0 -413
|
@@ -0,0 +1,1326 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : Symbol.for("Symbol." + name);
|
|
7
|
+
var __typeError = (msg) => {
|
|
8
|
+
throw TypeError(msg);
|
|
9
|
+
};
|
|
10
|
+
var __export = (target, all) => {
|
|
11
|
+
for (var name in all)
|
|
12
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
13
|
+
};
|
|
14
|
+
var __copyProps = (to, from, except, desc) => {
|
|
15
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
16
|
+
for (let key of __getOwnPropNames(from))
|
|
17
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
18
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
19
|
+
}
|
|
20
|
+
return to;
|
|
21
|
+
};
|
|
22
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
23
|
+
var __using = (stack, value, async) => {
|
|
24
|
+
if (value != null) {
|
|
25
|
+
if (typeof value !== "object" && typeof value !== "function") __typeError("Object expected");
|
|
26
|
+
var dispose, inner;
|
|
27
|
+
if (async) dispose = value[__knownSymbol("asyncDispose")];
|
|
28
|
+
if (dispose === void 0) {
|
|
29
|
+
dispose = value[__knownSymbol("dispose")];
|
|
30
|
+
if (async) inner = dispose;
|
|
31
|
+
}
|
|
32
|
+
if (typeof dispose !== "function") __typeError("Object not disposable");
|
|
33
|
+
if (inner) dispose = function() {
|
|
34
|
+
try {
|
|
35
|
+
inner.call(this);
|
|
36
|
+
} catch (e) {
|
|
37
|
+
return Promise.reject(e);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
stack.push([async, dispose, value]);
|
|
41
|
+
} else if (async) {
|
|
42
|
+
stack.push([async]);
|
|
43
|
+
}
|
|
44
|
+
return value;
|
|
45
|
+
};
|
|
46
|
+
var __callDispose = (stack, error, hasError) => {
|
|
47
|
+
var E = typeof SuppressedError === "function" ? SuppressedError : function(e, s, m, _) {
|
|
48
|
+
return _ = Error(m), _.name = "SuppressedError", _.error = e, _.suppressed = s, _;
|
|
49
|
+
};
|
|
50
|
+
var fail = (e) => error = hasError ? new E(e, error, "An error was suppressed during disposal") : (hasError = true, e);
|
|
51
|
+
var next = (it) => {
|
|
52
|
+
while (it = stack.pop()) {
|
|
53
|
+
try {
|
|
54
|
+
var result = it[1] && it[1].call(it[2]);
|
|
55
|
+
if (it[0]) return Promise.resolve(result).then(next, (e) => (fail(e), next()));
|
|
56
|
+
} catch (e) {
|
|
57
|
+
fail(e);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (hasError) throw error;
|
|
61
|
+
};
|
|
62
|
+
return next();
|
|
63
|
+
};
|
|
64
|
+
var InteractionServer_exports = {};
|
|
65
|
+
__export(InteractionServer_exports, {
|
|
66
|
+
InteractionServer: () => InteractionServer
|
|
67
|
+
});
|
|
68
|
+
module.exports = __toCommonJS(InteractionServer_exports);
|
|
69
|
+
var import_ActionTracer = require("#behavior/context/ActionTracer.js");
|
|
70
|
+
var import_NodeActivity = require("#behavior/context/NodeActivity.js");
|
|
71
|
+
var import_OfflineContext = require("#behavior/context/server/OfflineContext.js");
|
|
72
|
+
var import_OnlineContext = require("#behavior/context/server/OnlineContext.js");
|
|
73
|
+
var import_access_control = require("#behaviors/access-control");
|
|
74
|
+
var import_access_control2 = require("#clusters/access-control");
|
|
75
|
+
var import_EndpointLifecycle = require("#endpoint/properties/EndpointLifecycle.js");
|
|
76
|
+
var import_EndpointServer = require("#endpoint/server/EndpointServer.js");
|
|
77
|
+
var import_general = require("#general");
|
|
78
|
+
var import_model = require("#model");
|
|
79
|
+
var import_protocol = require("#protocol");
|
|
80
|
+
var import_types = require("#types");
|
|
81
|
+
var import_protocol2 = require("@matter/protocol");
|
|
82
|
+
/**
|
|
83
|
+
* @license
|
|
84
|
+
* Copyright 2022-2025 Matter.js Authors
|
|
85
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
86
|
+
*/
|
|
87
|
+
const logger = import_general.Logger.get("InteractionServer");
|
|
88
|
+
const activityKey = Symbol("activity");
|
|
89
|
+
const AclClusterId = import_access_control2.AccessControlCluster.id;
|
|
90
|
+
const AclAttributeId = import_access_control2.AccessControlCluster.attributes.acl.id;
|
|
91
|
+
function isConcreteAttributePath(path) {
|
|
92
|
+
const { endpointId, clusterId, attributeId } = path;
|
|
93
|
+
return endpointId !== void 0 && clusterId !== void 0 && attributeId !== void 0;
|
|
94
|
+
}
|
|
95
|
+
function validateReadAttributesPath(path, isGroupSession = false) {
|
|
96
|
+
if (isGroupSession) {
|
|
97
|
+
throw new import_types.StatusResponseError("Illegal read request with group session", import_types.StatusCode.InvalidAction);
|
|
98
|
+
}
|
|
99
|
+
const { clusterId, attributeId } = path;
|
|
100
|
+
if (clusterId === void 0 && attributeId !== void 0) {
|
|
101
|
+
if (!import_model.GLOBAL_IDS.has(attributeId)) {
|
|
102
|
+
throw new import_types.StatusResponseError(
|
|
103
|
+
`Illegal read request for wildcard cluster and non global attribute ${attributeId}`,
|
|
104
|
+
import_types.StatusCode.InvalidAction
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function validateWriteAttributesPath(path, isGroupSession = false) {
|
|
110
|
+
const { endpointId, clusterId, attributeId } = path;
|
|
111
|
+
if (clusterId === void 0 || attributeId === void 0) {
|
|
112
|
+
throw new import_types.StatusResponseError(
|
|
113
|
+
"Illegal write request with wildcard cluster or attribute ID",
|
|
114
|
+
import_types.StatusCode.InvalidAction
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
if (isGroupSession && endpointId !== void 0) {
|
|
118
|
+
throw new import_types.StatusResponseError("Illegal write request with group ID and endpoint ID", import_types.StatusCode.InvalidAction);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function isConcreteEventPath(path) {
|
|
122
|
+
const { endpointId, clusterId, eventId } = path;
|
|
123
|
+
return endpointId !== void 0 && clusterId !== void 0 && eventId !== void 0;
|
|
124
|
+
}
|
|
125
|
+
function validateReadEventPath(path, isGroupSession = false) {
|
|
126
|
+
const { clusterId, eventId } = path;
|
|
127
|
+
if (clusterId === void 0 && eventId !== void 0) {
|
|
128
|
+
throw new import_types.StatusResponseError("Illegal read request with wildcard cluster ID", import_types.StatusCode.InvalidAction);
|
|
129
|
+
}
|
|
130
|
+
if (isGroupSession) {
|
|
131
|
+
throw new import_types.StatusResponseError("Illegal read request with group session", import_types.StatusCode.InvalidAction);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
function isConcreteCommandPath(path) {
|
|
135
|
+
const { endpointId, clusterId, commandId } = path;
|
|
136
|
+
return endpointId !== void 0 && clusterId !== void 0 && commandId !== void 0;
|
|
137
|
+
}
|
|
138
|
+
function validateCommandPath(path, isGroupSession = false) {
|
|
139
|
+
const { endpointId, clusterId, commandId } = path;
|
|
140
|
+
if (clusterId === void 0 || commandId === void 0) {
|
|
141
|
+
throw new import_types.StatusResponseError(
|
|
142
|
+
"Illegal write request with wildcard cluster or attribute ID",
|
|
143
|
+
import_types.StatusCode.InvalidAction
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
if (isGroupSession && endpointId !== void 0) {
|
|
147
|
+
throw new import_types.StatusResponseError("Illegal write request with group ID and endpoint ID", import_types.StatusCode.InvalidAction);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
function getMatterModelCluster(clusterId) {
|
|
151
|
+
return import_model.MatterModel.standard.get(import_model.ClusterModel, clusterId);
|
|
152
|
+
}
|
|
153
|
+
function getMatterModelClusterCommand(clusterId, commandId) {
|
|
154
|
+
return getMatterModelCluster(clusterId)?.get(import_model.CommandModel, commandId);
|
|
155
|
+
}
|
|
156
|
+
class InteractionServer {
|
|
157
|
+
id = import_types.INTERACTION_PROTOCOL_ID;
|
|
158
|
+
requiresSecureSession = true;
|
|
159
|
+
#context;
|
|
160
|
+
#nextSubscriptionId = import_general.Crypto.getRandomUInt32();
|
|
161
|
+
#isClosing = false;
|
|
162
|
+
#clientHandler;
|
|
163
|
+
#subscriptionConfig;
|
|
164
|
+
#maxPathsPerInvoke;
|
|
165
|
+
#subscriptionEstablishmentStarted = (0, import_general.Observable)();
|
|
166
|
+
#changeListener;
|
|
167
|
+
#node;
|
|
168
|
+
#activity;
|
|
169
|
+
#newActivityBlocked = false;
|
|
170
|
+
#aclServer;
|
|
171
|
+
#aclUpdateIsDelayedInExchange = /* @__PURE__ */ new Set();
|
|
172
|
+
#serverInteraction;
|
|
173
|
+
constructor(node, sessions) {
|
|
174
|
+
this.#context = {
|
|
175
|
+
sessions,
|
|
176
|
+
exchangeManager: node.env.get(import_protocol.ExchangeManager),
|
|
177
|
+
structure: new import_protocol.InteractionEndpointStructure()
|
|
178
|
+
};
|
|
179
|
+
this.#subscriptionConfig = import_protocol.ServerSubscriptionConfig.of(node.state.network.subscriptionOptions);
|
|
180
|
+
this.#maxPathsPerInvoke = node.state.basicInformation.maxPathsPerInvoke ?? import_types.DEFAULT_MAX_PATHS_PER_INVOKE;
|
|
181
|
+
this.#context.structure.change.on(async () => {
|
|
182
|
+
this.#context.sessions.updateAllSubscriptions();
|
|
183
|
+
});
|
|
184
|
+
this.#activity = node.env.get(import_NodeActivity.NodeActivity);
|
|
185
|
+
this.#node = node;
|
|
186
|
+
this.#serverInteraction = new import_protocol2.ServerInteraction(node.protocol);
|
|
187
|
+
this.#updateStructure();
|
|
188
|
+
this.#changeListener = (type, endpoint) => {
|
|
189
|
+
switch (type) {
|
|
190
|
+
case import_EndpointLifecycle.EndpointLifecycle.Change.ServersChanged:
|
|
191
|
+
import_EndpointServer.EndpointServer.forEndpoint(endpoint).updateServers();
|
|
192
|
+
this.#updateStructure();
|
|
193
|
+
break;
|
|
194
|
+
case import_EndpointLifecycle.EndpointLifecycle.Change.PartsReady:
|
|
195
|
+
case import_EndpointLifecycle.EndpointLifecycle.Change.ClientsChanged:
|
|
196
|
+
case import_EndpointLifecycle.EndpointLifecycle.Change.Destroyed:
|
|
197
|
+
this.#updateStructure();
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
node.lifecycle.changed.on(this.#changeListener);
|
|
202
|
+
}
|
|
203
|
+
async [Symbol.asyncDispose]() {
|
|
204
|
+
this.#node.lifecycle.changed.off(this.#changeListener);
|
|
205
|
+
await this.close();
|
|
206
|
+
this.#context.structure.close();
|
|
207
|
+
await import_EndpointServer.EndpointServer.forEndpoint(this.#node)[Symbol.asyncDispose]();
|
|
208
|
+
}
|
|
209
|
+
blockNewActivity() {
|
|
210
|
+
this.#newActivityBlocked = true;
|
|
211
|
+
}
|
|
212
|
+
get isClosing() {
|
|
213
|
+
return this.#isClosing;
|
|
214
|
+
}
|
|
215
|
+
get maxPathsPerInvoke() {
|
|
216
|
+
return this.#maxPathsPerInvoke;
|
|
217
|
+
}
|
|
218
|
+
get subscriptionEstablishmentStarted() {
|
|
219
|
+
return this.#subscriptionEstablishmentStarted;
|
|
220
|
+
}
|
|
221
|
+
async onNewExchange(exchange, message) {
|
|
222
|
+
var _stack = [];
|
|
223
|
+
try {
|
|
224
|
+
if (this.#newActivityBlocked || this.isClosing) {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
if (message.payloadHeader.messageType === import_protocol.MessageType.ReportData && this.clientHandler) {
|
|
228
|
+
return this.clientHandler.onNewExchange(exchange, message);
|
|
229
|
+
}
|
|
230
|
+
const activity = __using(_stack, this.#activity.begin(`session#${exchange.session.id.toString(16)}`));
|
|
231
|
+
exchange[activityKey] = activity;
|
|
232
|
+
return new import_protocol.InteractionServerMessenger(exchange).handleRequest(this).finally(() => delete exchange[activityKey]);
|
|
233
|
+
} catch (_) {
|
|
234
|
+
var _error = _, _hasError = true;
|
|
235
|
+
} finally {
|
|
236
|
+
__callDispose(_stack, _error, _hasError);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
get aclServer() {
|
|
240
|
+
if (this.#aclServer !== void 0) {
|
|
241
|
+
return this.#aclServer;
|
|
242
|
+
}
|
|
243
|
+
const aclServer = this.#node.act((agent) => agent.get(import_access_control.AccessControlServer));
|
|
244
|
+
if (import_general.MaybePromise.is(aclServer)) {
|
|
245
|
+
throw new import_general.InternalError("AccessControlServer should already be initialized.");
|
|
246
|
+
}
|
|
247
|
+
return this.#aclServer = aclServer;
|
|
248
|
+
}
|
|
249
|
+
get clientHandler() {
|
|
250
|
+
return this.#clientHandler;
|
|
251
|
+
}
|
|
252
|
+
set clientHandler(clientHandler) {
|
|
253
|
+
this.#clientHandler = clientHandler;
|
|
254
|
+
}
|
|
255
|
+
async #collectEventDataForRead({ eventRequests, eventFilters, isFabricFiltered }, exchange, message) {
|
|
256
|
+
let eventReportsPayload;
|
|
257
|
+
if (eventRequests) {
|
|
258
|
+
eventReportsPayload = [];
|
|
259
|
+
for (const requestPath of eventRequests) {
|
|
260
|
+
validateReadEventPath(requestPath);
|
|
261
|
+
const events = this.#context.structure.getEvents([requestPath]);
|
|
262
|
+
if (events.length === 0) {
|
|
263
|
+
if (isConcreteEventPath(requestPath)) {
|
|
264
|
+
const { endpointId, clusterId, eventId } = requestPath;
|
|
265
|
+
try {
|
|
266
|
+
this.#context.structure.validateConcreteEventPath(endpointId, clusterId, eventId);
|
|
267
|
+
throw new import_general.InternalError(
|
|
268
|
+
"validateConcreteEventPath should throw StatusResponseError but did not."
|
|
269
|
+
);
|
|
270
|
+
} catch (e) {
|
|
271
|
+
import_types.StatusResponseError.accept(e);
|
|
272
|
+
logger.debug(
|
|
273
|
+
`Read event from ${exchange.channel.name}: ${this.#context.structure.resolveEventName(requestPath)}: unsupported path: Status=${e.code}`
|
|
274
|
+
);
|
|
275
|
+
eventReportsPayload?.push({
|
|
276
|
+
hasFabricSensitiveData: false,
|
|
277
|
+
eventStatus: { path: requestPath, status: { status: e.code } }
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
logger.debug(
|
|
282
|
+
`Read event from ${exchange.channel.name}: ${this.#context.structure.resolveEventName(
|
|
283
|
+
requestPath
|
|
284
|
+
)}: ignore non-existing event`
|
|
285
|
+
);
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
const reportsForPath = new Array();
|
|
289
|
+
for (const { path, event } of events) {
|
|
290
|
+
try {
|
|
291
|
+
const matchingEvents = await this.readEvent(
|
|
292
|
+
path,
|
|
293
|
+
eventFilters,
|
|
294
|
+
event,
|
|
295
|
+
exchange,
|
|
296
|
+
isFabricFiltered,
|
|
297
|
+
message
|
|
298
|
+
);
|
|
299
|
+
logger.debug(
|
|
300
|
+
`Read event from ${exchange.channel.name}: ${this.#context.structure.resolveEventName(
|
|
301
|
+
path
|
|
302
|
+
)}=${import_general.Diagnostic.json(matchingEvents)}`
|
|
303
|
+
);
|
|
304
|
+
const { schema } = event;
|
|
305
|
+
reportsForPath.push(
|
|
306
|
+
...matchingEvents.map(({ number, priority, epochTimestamp, payload }) => ({
|
|
307
|
+
hasFabricSensitiveData: event.hasFabricSensitiveData,
|
|
308
|
+
eventData: {
|
|
309
|
+
path,
|
|
310
|
+
eventNumber: number,
|
|
311
|
+
priority,
|
|
312
|
+
epochTimestamp,
|
|
313
|
+
payload,
|
|
314
|
+
schema
|
|
315
|
+
}
|
|
316
|
+
}))
|
|
317
|
+
);
|
|
318
|
+
} catch (error) {
|
|
319
|
+
logger.error(
|
|
320
|
+
`Error while reading event from ${exchange.channel.name} to ${this.#context.structure.resolveEventName(path)}:`,
|
|
321
|
+
error
|
|
322
|
+
);
|
|
323
|
+
import_types.StatusResponseError.accept(error);
|
|
324
|
+
if (isConcreteEventPath(requestPath)) {
|
|
325
|
+
eventReportsPayload?.push({
|
|
326
|
+
hasFabricSensitiveData: false,
|
|
327
|
+
eventStatus: { path, status: { status: error.code } }
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
eventReportsPayload.push(
|
|
333
|
+
...reportsForPath.sort((a, b) => {
|
|
334
|
+
const eventNumberA = a.eventData?.eventNumber ?? (0, import_types.EventNumber)(0);
|
|
335
|
+
const eventNumberB = b.eventData?.eventNumber ?? (0, import_types.EventNumber)(0);
|
|
336
|
+
if (eventNumberA > eventNumberB) {
|
|
337
|
+
return 1;
|
|
338
|
+
} else if (eventNumberA < eventNumberB) {
|
|
339
|
+
return -1;
|
|
340
|
+
} else {
|
|
341
|
+
return 0;
|
|
342
|
+
}
|
|
343
|
+
})
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
return eventReportsPayload;
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Returns an iterator that yields the data reports and events data for the given read request.
|
|
351
|
+
*/
|
|
352
|
+
async *#iterateReadAttributesPaths(readRequest, eventReportsPayload, exchange, message) {
|
|
353
|
+
const { isFabricFiltered } = readRequest;
|
|
354
|
+
const context = (0, import_OnlineContext.OnlineContext)({
|
|
355
|
+
activity: exchange[activityKey],
|
|
356
|
+
fabricFiltered: isFabricFiltered,
|
|
357
|
+
message,
|
|
358
|
+
exchange,
|
|
359
|
+
tracer: this.#tracer,
|
|
360
|
+
actionType: import_ActionTracer.ActionTracer.ActionType.Read,
|
|
361
|
+
node: this.#node
|
|
362
|
+
}).beginReadOnly();
|
|
363
|
+
for await (const chunk of this.#serverInteraction.read(readRequest, context)) {
|
|
364
|
+
for (const report of chunk) {
|
|
365
|
+
switch (report.kind) {
|
|
366
|
+
case "attr-value": {
|
|
367
|
+
const { path, value: payload, version: dataVersion, tlv: schema } = report;
|
|
368
|
+
if (schema === void 0) {
|
|
369
|
+
throw new import_general.InternalError(`Attribute ${path.clusterId}/${path.attributeId} not found`);
|
|
370
|
+
}
|
|
371
|
+
const data = {
|
|
372
|
+
attributeData: {
|
|
373
|
+
path,
|
|
374
|
+
payload,
|
|
375
|
+
schema,
|
|
376
|
+
dataVersion
|
|
377
|
+
},
|
|
378
|
+
hasFabricSensitiveData: true
|
|
379
|
+
// With this we disable the validation for missing data in encoding, we trust behavior logic
|
|
380
|
+
};
|
|
381
|
+
yield data;
|
|
382
|
+
break;
|
|
383
|
+
}
|
|
384
|
+
case "attr-status": {
|
|
385
|
+
const { path, status } = report;
|
|
386
|
+
const statusReport = {
|
|
387
|
+
attributeStatus: {
|
|
388
|
+
path,
|
|
389
|
+
status: { status }
|
|
390
|
+
},
|
|
391
|
+
hasFabricSensitiveData: false
|
|
392
|
+
};
|
|
393
|
+
yield statusReport;
|
|
394
|
+
break;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
context[Symbol.dispose]();
|
|
400
|
+
if (eventReportsPayload !== void 0) {
|
|
401
|
+
for (const eventReport of eventReportsPayload) {
|
|
402
|
+
yield eventReport;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
async handleReadRequest(exchange, readRequest, message) {
|
|
407
|
+
const { attributeRequests, eventRequests, isFabricFiltered, interactionModelRevision } = readRequest;
|
|
408
|
+
logger.debug(
|
|
409
|
+
`Received read request from ${exchange.channel.name}: attributes:${attributeRequests?.map((path) => this.#context.structure.resolveAttributeName(path)).join(", ") ?? "none"}, events:${eventRequests?.map((path) => this.#context.structure.resolveEventName(path)).join(", ") ?? "none"} isFabricFiltered=${isFabricFiltered}`
|
|
410
|
+
);
|
|
411
|
+
if (interactionModelRevision > import_model.Specification.INTERACTION_MODEL_REVISION) {
|
|
412
|
+
logger.debug(
|
|
413
|
+
`Interaction model revision of sender ${interactionModelRevision} is higher than supported ${import_model.Specification.INTERACTION_MODEL_REVISION}.`
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
if (attributeRequests === void 0 && eventRequests === void 0) {
|
|
417
|
+
return {
|
|
418
|
+
dataReport: {
|
|
419
|
+
interactionModelRevision: import_model.Specification.INTERACTION_MODEL_REVISION,
|
|
420
|
+
suppressResponse: true
|
|
421
|
+
}
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
if (message.packetHeader.sessionType !== import_protocol.SessionType.Unicast) {
|
|
425
|
+
throw new import_types.StatusResponseError(
|
|
426
|
+
"Subscriptions are only allowed on unicast sessions",
|
|
427
|
+
import_types.StatusCode.InvalidAction
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
return {
|
|
431
|
+
dataReport: {
|
|
432
|
+
interactionModelRevision: import_model.Specification.INTERACTION_MODEL_REVISION,
|
|
433
|
+
suppressResponse: true
|
|
434
|
+
},
|
|
435
|
+
payload: this.#iterateReadAttributesPaths(
|
|
436
|
+
readRequest,
|
|
437
|
+
await this.#collectEventDataForRead(readRequest, exchange, message),
|
|
438
|
+
exchange,
|
|
439
|
+
message
|
|
440
|
+
)
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
readAttribute(path, attribute, exchange, fabricFiltered, message, offline = false) {
|
|
444
|
+
const readAttribute = () => attribute.getWithVersion(exchange.session, fabricFiltered, offline ? void 0 : message);
|
|
445
|
+
const endpoint = this.#context.structure.getEndpoint(path.endpointId);
|
|
446
|
+
if (!endpoint) {
|
|
447
|
+
throw new import_general.InternalError("Endpoint not found for ACL check. This should never happen.");
|
|
448
|
+
}
|
|
449
|
+
const result = offline ? import_OfflineContext.OfflineContext.act("offline-read", this.#activity, readAttribute) : (0, import_OnlineContext.OnlineContext)({
|
|
450
|
+
activity: exchange[activityKey],
|
|
451
|
+
fabricFiltered,
|
|
452
|
+
message,
|
|
453
|
+
exchange,
|
|
454
|
+
tracer: this.#tracer,
|
|
455
|
+
actionType: import_ActionTracer.ActionTracer.ActionType.Read,
|
|
456
|
+
node: this.#node
|
|
457
|
+
}).act(readAttribute);
|
|
458
|
+
if (import_general.MaybePromise.is(result)) {
|
|
459
|
+
throw new import_general.InternalError("Reads should not return a promise.");
|
|
460
|
+
}
|
|
461
|
+
return result;
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Reads the attributes for the given endpoint.
|
|
465
|
+
* This can currently only be used for subscriptions because errors are ignored!
|
|
466
|
+
*/
|
|
467
|
+
readEndpointAttributesForSubscription(attributes, exchange, fabricFiltered, message, offline = false) {
|
|
468
|
+
const readAttributes = () => {
|
|
469
|
+
const result2 = new Array();
|
|
470
|
+
for (const { path, attribute } of attributes) {
|
|
471
|
+
try {
|
|
472
|
+
const value = attribute.getWithVersion(
|
|
473
|
+
exchange.session,
|
|
474
|
+
fabricFiltered,
|
|
475
|
+
offline ? void 0 : message
|
|
476
|
+
);
|
|
477
|
+
result2.push({ path, attribute, value: value.value, version: value.version });
|
|
478
|
+
} catch (error) {
|
|
479
|
+
if (import_types.StatusResponseError.is(error, import_types.StatusCode.UnsupportedAccess)) {
|
|
480
|
+
logger.warn(
|
|
481
|
+
`Permission denied reading attribute ${this.#context.structure.resolveAttributeName(path)}`
|
|
482
|
+
);
|
|
483
|
+
} else {
|
|
484
|
+
logger.warn(
|
|
485
|
+
`Error reading attribute ${this.#context.structure.resolveAttributeName(path)}:`,
|
|
486
|
+
error
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
return result2;
|
|
492
|
+
};
|
|
493
|
+
const result = offline ? import_OfflineContext.OfflineContext.act("offline-read", this.#activity, readAttributes) : (0, import_OnlineContext.OnlineContext)({
|
|
494
|
+
activity: exchange[activityKey],
|
|
495
|
+
fabricFiltered,
|
|
496
|
+
message,
|
|
497
|
+
exchange,
|
|
498
|
+
tracer: this.#tracer,
|
|
499
|
+
actionType: import_ActionTracer.ActionTracer.ActionType.Read,
|
|
500
|
+
node: this.#node
|
|
501
|
+
}).act(readAttributes);
|
|
502
|
+
if (import_general.MaybePromise.is(result)) {
|
|
503
|
+
throw new import_general.InternalError("Online read should not return a promise.");
|
|
504
|
+
}
|
|
505
|
+
return result;
|
|
506
|
+
}
|
|
507
|
+
async readEvent(path, eventFilters, event, exchange, fabricFiltered, message) {
|
|
508
|
+
const readEvent = (context) => {
|
|
509
|
+
if (context.authorityAt(event.readAcl, {
|
|
510
|
+
endpoint: path.endpointId,
|
|
511
|
+
cluster: path.clusterId
|
|
512
|
+
}) !== import_protocol.AccessControl.Authority.Granted) {
|
|
513
|
+
throw new import_protocol.AccessDeniedError(
|
|
514
|
+
`Access to ${path.endpointId}/${import_general.Diagnostic.hex(path.clusterId)} denied on ${exchange.session.name}.`
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
return event.get(exchange.session, fabricFiltered, message, eventFilters);
|
|
518
|
+
};
|
|
519
|
+
return (0, import_OnlineContext.OnlineContext)({
|
|
520
|
+
activity: exchange[activityKey],
|
|
521
|
+
fabricFiltered,
|
|
522
|
+
message,
|
|
523
|
+
exchange,
|
|
524
|
+
tracer: this.#tracer,
|
|
525
|
+
actionType: import_ActionTracer.ActionTracer.ActionType.Read,
|
|
526
|
+
node: this.#node
|
|
527
|
+
}).act(readEvent);
|
|
528
|
+
}
|
|
529
|
+
async handleWriteRequest(exchange, writeRequest, message) {
|
|
530
|
+
let result;
|
|
531
|
+
try {
|
|
532
|
+
result = await this.#handleWriteRequestLogic(exchange, writeRequest, message);
|
|
533
|
+
} catch (error) {
|
|
534
|
+
if (this.#aclUpdateIsDelayedInExchange.has(exchange)) {
|
|
535
|
+
this.#aclUpdateIsDelayedInExchange.delete(exchange);
|
|
536
|
+
if (this.#aclUpdateIsDelayedInExchange.size === 0) {
|
|
537
|
+
this.aclServer.resetDelayedAccessControlList();
|
|
538
|
+
} else {
|
|
539
|
+
logger.error("One of multiple concurrent ACL writes failed, unhandled case for now.");
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
throw error;
|
|
543
|
+
}
|
|
544
|
+
if (this.#aclUpdateIsDelayedInExchange.has(exchange)) {
|
|
545
|
+
this.#aclUpdateIsDelayedInExchange.delete(exchange);
|
|
546
|
+
if (this.#aclUpdateIsDelayedInExchange.size === 0) {
|
|
547
|
+
this.aclServer.aclUpdateDelayed = false;
|
|
548
|
+
} else {
|
|
549
|
+
logger.info("Multiple concurrent ACL writes, waiting for all to finish.");
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
return result;
|
|
553
|
+
}
|
|
554
|
+
async #handleWriteRequestLogic(exchange, { suppressResponse, timedRequest, writeRequests, interactionModelRevision, moreChunkedMessages }, message) {
|
|
555
|
+
const sessionType = message.packetHeader.sessionType;
|
|
556
|
+
logger.debug(
|
|
557
|
+
`Received write request from ${exchange.channel.name}: ${writeRequests.map((req) => this.#context.structure.resolveAttributeName(req.path)).join(", ")}, suppressResponse=${suppressResponse}, moreChunkedMessages=${moreChunkedMessages}`
|
|
558
|
+
);
|
|
559
|
+
if (moreChunkedMessages && suppressResponse) {
|
|
560
|
+
throw new import_types.StatusResponseError(
|
|
561
|
+
"MoreChunkedMessages and SuppressResponse cannot be used together in write messages",
|
|
562
|
+
import_types.StatusCode.InvalidAction
|
|
563
|
+
);
|
|
564
|
+
}
|
|
565
|
+
if (interactionModelRevision > import_model.Specification.INTERACTION_MODEL_REVISION) {
|
|
566
|
+
logger.debug(
|
|
567
|
+
`Interaction model revision of sender ${interactionModelRevision} is higher than supported ${import_model.Specification.INTERACTION_MODEL_REVISION}.`
|
|
568
|
+
);
|
|
569
|
+
}
|
|
570
|
+
const receivedWithinTimedInteraction = exchange.hasActiveTimedInteraction();
|
|
571
|
+
if (receivedWithinTimedInteraction && moreChunkedMessages) {
|
|
572
|
+
throw new import_types.StatusResponseError(
|
|
573
|
+
"Write Request action that is part of a Timed Write Interaction SHALL NOT be chunked.",
|
|
574
|
+
import_types.StatusCode.InvalidAction
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
if (exchange.hasExpiredTimedInteraction()) {
|
|
578
|
+
exchange.clearTimedInteraction();
|
|
579
|
+
throw new import_types.StatusResponseError(`Timed request window expired. Decline write request.`, import_types.StatusCode.Timeout);
|
|
580
|
+
}
|
|
581
|
+
if (timedRequest !== exchange.hasTimedInteraction()) {
|
|
582
|
+
throw new import_types.StatusResponseError(
|
|
583
|
+
`timedRequest flag of write interaction (${timedRequest}) mismatch with expected timed interaction (${receivedWithinTimedInteraction}).`,
|
|
584
|
+
import_types.StatusCode.TimedRequestMismatch
|
|
585
|
+
);
|
|
586
|
+
}
|
|
587
|
+
if (receivedWithinTimedInteraction) {
|
|
588
|
+
logger.debug(
|
|
589
|
+
`Write request from ${exchange.channel.name} successfully received while timed interaction is running.`
|
|
590
|
+
);
|
|
591
|
+
exchange.clearTimedInteraction();
|
|
592
|
+
if (sessionType !== import_protocol.SessionType.Unicast) {
|
|
593
|
+
throw new import_types.StatusResponseError(
|
|
594
|
+
"Write requests are only allowed on unicast sessions when a timed interaction is running.",
|
|
595
|
+
import_types.StatusCode.InvalidAction
|
|
596
|
+
);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
if (sessionType === import_protocol.SessionType.Group && !suppressResponse) {
|
|
600
|
+
throw new import_types.StatusResponseError(
|
|
601
|
+
"Write requests are only allowed as group casts when suppressResponse=true.",
|
|
602
|
+
import_types.StatusCode.InvalidAction
|
|
603
|
+
);
|
|
604
|
+
}
|
|
605
|
+
const writeData = (0, import_protocol.expandPathsInAttributeData)(writeRequests, true);
|
|
606
|
+
const writeResults = new Array();
|
|
607
|
+
const attributeListWrites = /* @__PURE__ */ new Set();
|
|
608
|
+
const clusterDataVersionInfo = /* @__PURE__ */ new Map();
|
|
609
|
+
const inaccessiblePaths = /* @__PURE__ */ new Set();
|
|
610
|
+
for (const writeRequest of writeData) {
|
|
611
|
+
const { path: writePath, dataVersion } = writeRequest;
|
|
612
|
+
validateWriteAttributesPath(writePath);
|
|
613
|
+
const attributes = this.#context.structure.getAttributes([writePath], true);
|
|
614
|
+
if (attributes.length === 0) {
|
|
615
|
+
if (isConcreteAttributePath(writePath)) {
|
|
616
|
+
const { endpointId, clusterId, attributeId } = writePath;
|
|
617
|
+
try {
|
|
618
|
+
this.#context.structure.validateConcreteAttributePath(endpointId, clusterId, attributeId);
|
|
619
|
+
throw new import_types.StatusResponseError(
|
|
620
|
+
`Attribute ${attributeId} is not writable.`,
|
|
621
|
+
import_types.StatusCode.UnsupportedWrite
|
|
622
|
+
);
|
|
623
|
+
} catch (e) {
|
|
624
|
+
import_types.StatusResponseError.accept(e);
|
|
625
|
+
logger.debug(
|
|
626
|
+
`Write from ${exchange.channel.name}: ${this.#context.structure.resolveAttributeName(
|
|
627
|
+
writePath
|
|
628
|
+
)} not allowed: Status=${e.code}`
|
|
629
|
+
);
|
|
630
|
+
writeResults.push({ path: writePath, statusCode: e.code });
|
|
631
|
+
}
|
|
632
|
+
} else {
|
|
633
|
+
logger.debug(
|
|
634
|
+
`Write from ${exchange.channel.name}: ${this.#context.structure.resolveAttributeName(
|
|
635
|
+
writePath
|
|
636
|
+
)}: ignore non-existing (wildcard) attribute`
|
|
637
|
+
);
|
|
638
|
+
}
|
|
639
|
+
continue;
|
|
640
|
+
}
|
|
641
|
+
if (attributes.length === 1 && isConcreteAttributePath(writePath)) {
|
|
642
|
+
const { endpointId, clusterId } = writePath;
|
|
643
|
+
const { attribute } = attributes[0];
|
|
644
|
+
if (attribute.requiresTimedInteraction && !receivedWithinTimedInteraction) {
|
|
645
|
+
logger.debug(`This write requires a timed interaction which is not initialized.`);
|
|
646
|
+
writeResults.push({ path: writePath, statusCode: import_types.StatusCode.NeedsTimedInteraction });
|
|
647
|
+
continue;
|
|
648
|
+
}
|
|
649
|
+
if (attribute instanceof import_protocol.FabricScopedAttributeServer && (!exchange.session.isSecure || !exchange.session.fabric)) {
|
|
650
|
+
logger.debug(`This write requires a secure session with a fabric assigned which is missing.`);
|
|
651
|
+
writeResults.push({ path: writePath, statusCode: import_types.StatusCode.UnsupportedAccess });
|
|
652
|
+
continue;
|
|
653
|
+
}
|
|
654
|
+
if (dataVersion !== void 0) {
|
|
655
|
+
const datasource = this.#context.structure.getClusterServer(endpointId, clusterId)?.datasource;
|
|
656
|
+
const { nodeId } = writePath;
|
|
657
|
+
const clusterKey = (0, import_protocol.clusterPathToId)({ nodeId, endpointId, clusterId });
|
|
658
|
+
const currentDataVersion = clusterDataVersionInfo.get(clusterKey) ?? datasource?.version;
|
|
659
|
+
if (currentDataVersion !== void 0) {
|
|
660
|
+
if (dataVersion !== currentDataVersion) {
|
|
661
|
+
logger.debug(
|
|
662
|
+
`This write requires a specific data version (${dataVersion}) which do not match the current cluster data version (${currentDataVersion}).`
|
|
663
|
+
);
|
|
664
|
+
writeResults.push({ path: writePath, statusCode: import_types.StatusCode.DataVersionMismatch });
|
|
665
|
+
continue;
|
|
666
|
+
}
|
|
667
|
+
clusterDataVersionInfo.set(clusterKey, currentDataVersion);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
for (const { path, attribute } of attributes) {
|
|
672
|
+
const { schema, defaultValue } = attribute;
|
|
673
|
+
const pathId = (0, import_protocol.attributePathToId)(path);
|
|
674
|
+
try {
|
|
675
|
+
if (!(attribute instanceof import_protocol.AttributeServer) && !(attribute instanceof import_protocol.FabricScopedAttributeServer)) {
|
|
676
|
+
throw new import_types.StatusResponseError(
|
|
677
|
+
"Fixed attributes cannot be written",
|
|
678
|
+
import_types.StatusCode.UnsupportedWrite
|
|
679
|
+
);
|
|
680
|
+
}
|
|
681
|
+
if (inaccessiblePaths.has(pathId)) {
|
|
682
|
+
logger.debug(`This write is not allowed due to previous access denied.`);
|
|
683
|
+
continue;
|
|
684
|
+
}
|
|
685
|
+
const { endpointId } = path;
|
|
686
|
+
const { listIndex } = writePath;
|
|
687
|
+
const value = listIndex === void 0 ? (0, import_protocol.decodeAttributeValueWithSchema)(schema, [writeRequest], defaultValue) : (0, import_protocol.decodeListAttributeValueWithSchema)(
|
|
688
|
+
schema,
|
|
689
|
+
[writeRequest],
|
|
690
|
+
this.readAttribute(path, attribute, exchange, true, message).value ?? defaultValue
|
|
691
|
+
);
|
|
692
|
+
logger.debug(
|
|
693
|
+
`Handle write request from ${exchange.channel.name} resolved to: ${this.#context.structure.resolveAttributeName(path)}=${import_general.Diagnostic.json(
|
|
694
|
+
value
|
|
695
|
+
)} (listIndex=${listIndex}, for-version=${dataVersion})`
|
|
696
|
+
);
|
|
697
|
+
if (attribute.requiresTimedInteraction && !receivedWithinTimedInteraction) {
|
|
698
|
+
logger.debug(`This write requires a timed interaction which is not initialized.`);
|
|
699
|
+
throw new import_types.StatusResponseError(
|
|
700
|
+
"This write requires a timed interaction which is not initialized.",
|
|
701
|
+
import_types.StatusCode.NeedsTimedInteraction
|
|
702
|
+
);
|
|
703
|
+
}
|
|
704
|
+
await this.writeAttribute(
|
|
705
|
+
path,
|
|
706
|
+
attribute,
|
|
707
|
+
value,
|
|
708
|
+
exchange,
|
|
709
|
+
message,
|
|
710
|
+
this.#context.structure.getEndpoint(endpointId),
|
|
711
|
+
receivedWithinTimedInteraction,
|
|
712
|
+
schema instanceof import_types.ArraySchema
|
|
713
|
+
);
|
|
714
|
+
if (schema instanceof import_types.ArraySchema && !attributeListWrites.has(attribute)) {
|
|
715
|
+
attributeListWrites.add(attribute);
|
|
716
|
+
}
|
|
717
|
+
} catch (error) {
|
|
718
|
+
if (import_types.StatusResponseError.is(error, import_types.StatusCode.UnsupportedAccess)) {
|
|
719
|
+
inaccessiblePaths.add(pathId);
|
|
720
|
+
}
|
|
721
|
+
if (attributes.length === 1 && isConcreteAttributePath(writePath)) {
|
|
722
|
+
logger.error(
|
|
723
|
+
`Error while handling write request from ${exchange.channel.name} to ${this.#context.structure.resolveAttributeName(path)}:`,
|
|
724
|
+
error instanceof import_types.StatusResponseError ? error.message : error
|
|
725
|
+
);
|
|
726
|
+
if (error instanceof import_types.StatusResponseError) {
|
|
727
|
+
writeResults.push({ path, statusCode: error.code, clusterStatusCode: error.clusterCode });
|
|
728
|
+
continue;
|
|
729
|
+
}
|
|
730
|
+
writeResults.push({ path, statusCode: import_types.StatusCode.ConstraintError });
|
|
731
|
+
continue;
|
|
732
|
+
} else {
|
|
733
|
+
logger.debug(
|
|
734
|
+
`While handling write request from ${exchange.channel.name} to ${this.#context.structure.resolveAttributeName(path)} ignored: ${error.message}`
|
|
735
|
+
);
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
writeResults.push({ path, statusCode: import_types.StatusCode.Success });
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
const errorResults = writeResults.filter(({ statusCode }) => statusCode !== import_types.StatusCode.Success);
|
|
742
|
+
logger.debug(
|
|
743
|
+
`Write request from ${exchange.channel.name} done ${errorResults.length ? `with following errors: ${errorResults.map(
|
|
744
|
+
({ path, statusCode }) => `${this.#context.structure.resolveAttributeName(path)}=${import_general.Diagnostic.json(statusCode)}`
|
|
745
|
+
).join(", ")}` : "without errors"}`
|
|
746
|
+
);
|
|
747
|
+
const response = {
|
|
748
|
+
interactionModelRevision: import_model.Specification.INTERACTION_MODEL_REVISION,
|
|
749
|
+
writeResponses: writeResults.map(({ path, statusCode, clusterStatusCode }) => ({
|
|
750
|
+
path,
|
|
751
|
+
status: { status: statusCode, clusterStatus: clusterStatusCode }
|
|
752
|
+
}))
|
|
753
|
+
};
|
|
754
|
+
for (const attribute of attributeListWrites.values()) {
|
|
755
|
+
try {
|
|
756
|
+
attribute.triggerDelayedChangeEvents();
|
|
757
|
+
} catch (error) {
|
|
758
|
+
logger.error(
|
|
759
|
+
`Ignored Error while writing attribute from ${exchange.channel.name} to ${attribute.name}:`,
|
|
760
|
+
error
|
|
761
|
+
);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
return response;
|
|
765
|
+
}
|
|
766
|
+
async writeAttribute(path, attribute, value, exchange, message, _endpoint, timed, isListWrite = false) {
|
|
767
|
+
const writeAttribute = () => attribute.set(value, exchange.session, message, isListWrite);
|
|
768
|
+
if (path.endpointId === 0 && path.clusterId === AclClusterId && path.attributeId === AclAttributeId) {
|
|
769
|
+
this.aclServer.aclUpdateDelayed = true;
|
|
770
|
+
this.#aclUpdateIsDelayedInExchange.add(exchange);
|
|
771
|
+
} else {
|
|
772
|
+
if (this.#aclUpdateIsDelayedInExchange.has(exchange)) {
|
|
773
|
+
this.#aclUpdateIsDelayedInExchange.delete(exchange);
|
|
774
|
+
if (this.#aclUpdateIsDelayedInExchange.size === 0) {
|
|
775
|
+
this.aclServer.aclUpdateDelayed = false;
|
|
776
|
+
} else {
|
|
777
|
+
logger.info("Multiple concurrent ACL writes, waiting for all to finish.");
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
return (0, import_OnlineContext.OnlineContext)({
|
|
782
|
+
activity: exchange[activityKey],
|
|
783
|
+
timed,
|
|
784
|
+
message,
|
|
785
|
+
exchange,
|
|
786
|
+
fabricFiltered: true,
|
|
787
|
+
tracer: this.#tracer,
|
|
788
|
+
actionType: import_ActionTracer.ActionTracer.ActionType.Write,
|
|
789
|
+
node: this.#node
|
|
790
|
+
}).act(writeAttribute);
|
|
791
|
+
}
|
|
792
|
+
async handleSubscribeRequest(exchange, request, messenger, message) {
|
|
793
|
+
const {
|
|
794
|
+
minIntervalFloorSeconds,
|
|
795
|
+
maxIntervalCeilingSeconds,
|
|
796
|
+
attributeRequests,
|
|
797
|
+
dataVersionFilters,
|
|
798
|
+
eventRequests,
|
|
799
|
+
eventFilters,
|
|
800
|
+
keepSubscriptions,
|
|
801
|
+
isFabricFiltered,
|
|
802
|
+
interactionModelRevision
|
|
803
|
+
} = request;
|
|
804
|
+
logger.debug(
|
|
805
|
+
`Received subscribe request from ${exchange.channel.name} (keepSubscriptions=${keepSubscriptions}, isFabricFiltered=${isFabricFiltered})`
|
|
806
|
+
);
|
|
807
|
+
if (interactionModelRevision > import_model.Specification.INTERACTION_MODEL_REVISION) {
|
|
808
|
+
logger.debug(
|
|
809
|
+
`Interaction model revision of sender ${interactionModelRevision} is higher than supported ${import_model.Specification.INTERACTION_MODEL_REVISION}.`
|
|
810
|
+
);
|
|
811
|
+
}
|
|
812
|
+
if (message.packetHeader.sessionType !== import_protocol.SessionType.Unicast) {
|
|
813
|
+
throw new import_types.StatusResponseError(
|
|
814
|
+
"Subscriptions are only allowed on unicast sessions",
|
|
815
|
+
import_types.StatusCode.InvalidAction
|
|
816
|
+
);
|
|
817
|
+
}
|
|
818
|
+
(0, import_protocol.assertSecureSession)(exchange.session, "Subscriptions are only implemented on secure sessions");
|
|
819
|
+
const session = exchange.session;
|
|
820
|
+
const fabric = session.fabric;
|
|
821
|
+
if (fabric === void 0)
|
|
822
|
+
throw new import_types.StatusResponseError(
|
|
823
|
+
"Subscriptions are only implemented after a fabric has been assigned",
|
|
824
|
+
import_types.StatusCode.InvalidAction
|
|
825
|
+
);
|
|
826
|
+
if (!keepSubscriptions) {
|
|
827
|
+
const clearedCount = await this.#context.sessions.clearSubscriptionsForNode(
|
|
828
|
+
fabric.addressOf(session.peerNodeId),
|
|
829
|
+
true
|
|
830
|
+
);
|
|
831
|
+
if (clearedCount > 0) {
|
|
832
|
+
logger.debug(
|
|
833
|
+
`Cleared ${clearedCount} subscriptions for Subscriber node ${session.peerNodeId} because keepSubscriptions=false`
|
|
834
|
+
);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
if ((!Array.isArray(attributeRequests) || attributeRequests.length === 0) && (!Array.isArray(eventRequests) || eventRequests.length === 0)) {
|
|
838
|
+
throw new import_types.StatusResponseError("No attributes or events requested", import_types.StatusCode.InvalidAction);
|
|
839
|
+
}
|
|
840
|
+
logger.debug(
|
|
841
|
+
`Subscribe to attributes:${attributeRequests?.map((path) => this.#context.structure.resolveAttributeName(path)).join(", ") ?? "none"}, events:${eventRequests?.map((path) => this.#context.structure.resolveEventName(path)).join(", ") ?? "none"}`
|
|
842
|
+
);
|
|
843
|
+
if (dataVersionFilters !== void 0 && dataVersionFilters.length > 0) {
|
|
844
|
+
logger.debug(
|
|
845
|
+
`DataVersionFilters: ${dataVersionFilters.map(
|
|
846
|
+
({ path: { nodeId, endpointId, clusterId }, dataVersion }) => `${(0, import_protocol.clusterPathToId)({ nodeId, endpointId, clusterId })}=${dataVersion}`
|
|
847
|
+
).join(", ")}`
|
|
848
|
+
);
|
|
849
|
+
}
|
|
850
|
+
if (eventFilters !== void 0 && eventFilters.length > 0)
|
|
851
|
+
logger.debug(
|
|
852
|
+
`Event filters: ${eventFilters.map((filter) => `${filter.nodeId}/${filter.eventMin}`).join(", ")}`
|
|
853
|
+
);
|
|
854
|
+
attributeRequests?.forEach((path) => validateReadAttributesPath(path));
|
|
855
|
+
eventRequests?.forEach((path) => validateReadEventPath(path));
|
|
856
|
+
if (minIntervalFloorSeconds < 0) {
|
|
857
|
+
throw new import_types.StatusResponseError(
|
|
858
|
+
"minIntervalFloorSeconds should be greater or equal to 0",
|
|
859
|
+
import_types.StatusCode.InvalidAction
|
|
860
|
+
);
|
|
861
|
+
}
|
|
862
|
+
if (maxIntervalCeilingSeconds < 0) {
|
|
863
|
+
throw new import_types.StatusResponseError(
|
|
864
|
+
"maxIntervalCeilingSeconds should be greater or equal to 1",
|
|
865
|
+
import_types.StatusCode.InvalidAction
|
|
866
|
+
);
|
|
867
|
+
}
|
|
868
|
+
if (maxIntervalCeilingSeconds < minIntervalFloorSeconds) {
|
|
869
|
+
throw new import_types.StatusResponseError(
|
|
870
|
+
"maxIntervalCeilingSeconds should be greater or equal to minIntervalFloorSeconds",
|
|
871
|
+
import_types.StatusCode.InvalidAction
|
|
872
|
+
);
|
|
873
|
+
}
|
|
874
|
+
if (this.#nextSubscriptionId === 4294967295) this.#nextSubscriptionId = 0;
|
|
875
|
+
const subscriptionId = this.#nextSubscriptionId++;
|
|
876
|
+
this.#subscriptionEstablishmentStarted.emit(session.peerAddress);
|
|
877
|
+
let subscription;
|
|
878
|
+
try {
|
|
879
|
+
subscription = await this.#establishSubscription(
|
|
880
|
+
subscriptionId,
|
|
881
|
+
request,
|
|
882
|
+
messenger,
|
|
883
|
+
session,
|
|
884
|
+
exchange,
|
|
885
|
+
message
|
|
886
|
+
);
|
|
887
|
+
} catch (error) {
|
|
888
|
+
logger.error(
|
|
889
|
+
`Subscription ${subscriptionId} for Session ${session.id}: Error while sending initial data reports`,
|
|
890
|
+
error instanceof import_general.MatterError ? error.message : error
|
|
891
|
+
);
|
|
892
|
+
if (error instanceof import_types.StatusResponseError && !(error instanceof import_types.ReceivedStatusResponseError)) {
|
|
893
|
+
logger.info(`Sending status response ${error.code} for interaction error: ${error.message}`);
|
|
894
|
+
await messenger.sendStatus(error.code, {
|
|
895
|
+
logContext: {
|
|
896
|
+
for: "I/SubscriptionSeed-Status"
|
|
897
|
+
}
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
await messenger.close();
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
const maxInterval = subscription.maxInterval;
|
|
904
|
+
await messenger.send(
|
|
905
|
+
import_protocol.MessageType.SubscribeResponse,
|
|
906
|
+
import_types.TlvSubscribeResponse.encode({
|
|
907
|
+
subscriptionId,
|
|
908
|
+
maxInterval,
|
|
909
|
+
interactionModelRevision: import_model.Specification.INTERACTION_MODEL_REVISION
|
|
910
|
+
}),
|
|
911
|
+
{
|
|
912
|
+
logContext: {
|
|
913
|
+
subId: subscriptionId,
|
|
914
|
+
maxInterval
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
);
|
|
918
|
+
subscription.activate();
|
|
919
|
+
}
|
|
920
|
+
async #establishSubscription(id, {
|
|
921
|
+
minIntervalFloorSeconds,
|
|
922
|
+
maxIntervalCeilingSeconds,
|
|
923
|
+
attributeRequests,
|
|
924
|
+
dataVersionFilters,
|
|
925
|
+
eventRequests,
|
|
926
|
+
eventFilters,
|
|
927
|
+
isFabricFiltered
|
|
928
|
+
}, messenger, session, exchange, message) {
|
|
929
|
+
const context = {
|
|
930
|
+
session,
|
|
931
|
+
structure: this.#context.structure,
|
|
932
|
+
readAttribute: (path, attribute, offline) => this.readAttribute(path, attribute, exchange, isFabricFiltered, message, offline),
|
|
933
|
+
readEndpointAttributesForSubscription: (attributes) => this.readEndpointAttributesForSubscription(attributes, exchange, isFabricFiltered, message),
|
|
934
|
+
readEvent: (path, event, eventFilters2) => this.readEvent(path, eventFilters2, event, exchange, isFabricFiltered, message),
|
|
935
|
+
initiateExchange: (address, protocolId) => this.#context.exchangeManager.initiateExchange(address, protocolId)
|
|
936
|
+
};
|
|
937
|
+
const subscription = new import_protocol.ServerSubscription({
|
|
938
|
+
id,
|
|
939
|
+
context,
|
|
940
|
+
criteria: {
|
|
941
|
+
attributeRequests,
|
|
942
|
+
dataVersionFilters,
|
|
943
|
+
eventRequests,
|
|
944
|
+
eventFilters,
|
|
945
|
+
isFabricFiltered
|
|
946
|
+
},
|
|
947
|
+
minIntervalFloorSeconds,
|
|
948
|
+
maxIntervalCeilingSeconds,
|
|
949
|
+
subscriptionOptions: this.#subscriptionConfig
|
|
950
|
+
});
|
|
951
|
+
try {
|
|
952
|
+
await subscription.sendInitialReport(messenger);
|
|
953
|
+
} catch (error) {
|
|
954
|
+
await subscription.close();
|
|
955
|
+
throw error;
|
|
956
|
+
}
|
|
957
|
+
logger.info(
|
|
958
|
+
`Successfully created subscription ${id} for Session ${session.id} to ${session.peerAddress}. Updates: ${minIntervalFloorSeconds} - ${maxIntervalCeilingSeconds} => ${subscription.maxInterval} seconds (sendInterval = ${subscription.sendInterval} seconds)`
|
|
959
|
+
);
|
|
960
|
+
return subscription;
|
|
961
|
+
}
|
|
962
|
+
async establishFormerSubscription({
|
|
963
|
+
subscriptionId,
|
|
964
|
+
attributeRequests,
|
|
965
|
+
eventRequests,
|
|
966
|
+
isFabricFiltered,
|
|
967
|
+
minIntervalFloorSeconds,
|
|
968
|
+
maxIntervalCeilingSeconds,
|
|
969
|
+
maxInterval,
|
|
970
|
+
sendInterval
|
|
971
|
+
}, session) {
|
|
972
|
+
const exchange = this.#context.exchangeManager.initiateExchange(session.peerAddress, import_types.INTERACTION_PROTOCOL_ID);
|
|
973
|
+
const message = {};
|
|
974
|
+
logger.debug(
|
|
975
|
+
`Send DataReports to re-establish subscription ${subscriptionId} to `,
|
|
976
|
+
import_general.Diagnostic.dict({ isFabricFiltered, maxInterval, sendInterval })
|
|
977
|
+
);
|
|
978
|
+
const context = {
|
|
979
|
+
session,
|
|
980
|
+
structure: this.#context.structure,
|
|
981
|
+
readAttribute: (path, attribute, offline) => this.readAttribute(path, attribute, exchange, isFabricFiltered, message, offline),
|
|
982
|
+
readEndpointAttributesForSubscription: (attributes) => this.readEndpointAttributesForSubscription(attributes, exchange, isFabricFiltered, message),
|
|
983
|
+
readEvent: (path, event, eventFilters) => this.readEvent(path, eventFilters, event, exchange, isFabricFiltered, message),
|
|
984
|
+
initiateExchange: (address, protocolId) => this.#context.exchangeManager.initiateExchange(address, protocolId)
|
|
985
|
+
};
|
|
986
|
+
const subscription = new import_protocol.ServerSubscription({
|
|
987
|
+
id: subscriptionId,
|
|
988
|
+
context,
|
|
989
|
+
minIntervalFloorSeconds,
|
|
990
|
+
maxIntervalCeilingSeconds,
|
|
991
|
+
criteria: {
|
|
992
|
+
attributeRequests,
|
|
993
|
+
eventRequests,
|
|
994
|
+
isFabricFiltered
|
|
995
|
+
},
|
|
996
|
+
subscriptionOptions: this.#subscriptionConfig,
|
|
997
|
+
useAsMaxInterval: maxInterval,
|
|
998
|
+
useAsSendInterval: sendInterval
|
|
999
|
+
});
|
|
1000
|
+
try {
|
|
1001
|
+
await subscription.sendInitialReport(new import_protocol.InteractionServerMessenger(exchange));
|
|
1002
|
+
subscription.activate();
|
|
1003
|
+
logger.info(
|
|
1004
|
+
`Successfully re-established subscription ${subscriptionId} for Session ${session.id} to ${session.peerAddress}. Updates: ${minIntervalFloorSeconds} - ${maxIntervalCeilingSeconds} => ${subscription.maxInterval} seconds (sendInterval = ${subscription.sendInterval} seconds)`
|
|
1005
|
+
);
|
|
1006
|
+
} catch (error) {
|
|
1007
|
+
await subscription.close();
|
|
1008
|
+
throw error;
|
|
1009
|
+
}
|
|
1010
|
+
return subscription;
|
|
1011
|
+
}
|
|
1012
|
+
async handleInvokeRequest(exchange, { invokeRequests, timedRequest, suppressResponse, interactionModelRevision }, messenger, message) {
|
|
1013
|
+
logger.debug(
|
|
1014
|
+
`Received invoke request from ${exchange.channel.name}${invokeRequests.length > 0 ? ` with ${invokeRequests.length} commands` : ""}: ${invokeRequests.map(
|
|
1015
|
+
({ commandPath: { endpointId, clusterId, commandId } }) => this.#context.structure.resolveCommandName({ endpointId, clusterId, commandId })
|
|
1016
|
+
).join(", ")}, suppressResponse=${suppressResponse}`
|
|
1017
|
+
);
|
|
1018
|
+
if (interactionModelRevision > import_model.Specification.INTERACTION_MODEL_REVISION) {
|
|
1019
|
+
logger.debug(
|
|
1020
|
+
`Interaction model revision of sender ${interactionModelRevision} is higher than supported ${import_model.Specification.INTERACTION_MODEL_REVISION}.`
|
|
1021
|
+
);
|
|
1022
|
+
}
|
|
1023
|
+
const receivedWithinTimedInteraction = exchange.hasActiveTimedInteraction();
|
|
1024
|
+
if (exchange.hasExpiredTimedInteraction()) {
|
|
1025
|
+
exchange.clearTimedInteraction();
|
|
1026
|
+
throw new import_types.StatusResponseError(`Timed request window expired. Decline invoke request.`, import_types.StatusCode.Timeout);
|
|
1027
|
+
}
|
|
1028
|
+
if (timedRequest !== exchange.hasTimedInteraction()) {
|
|
1029
|
+
throw new import_types.StatusResponseError(
|
|
1030
|
+
`timedRequest flag of invoke interaction (${timedRequest}) mismatch with expected timed interaction (${receivedWithinTimedInteraction}).`,
|
|
1031
|
+
import_types.StatusCode.TimedRequestMismatch
|
|
1032
|
+
);
|
|
1033
|
+
}
|
|
1034
|
+
if (receivedWithinTimedInteraction) {
|
|
1035
|
+
logger.debug(`Invoke request from ${exchange.channel.name} received while timed interaction is running.`);
|
|
1036
|
+
exchange.clearTimedInteraction();
|
|
1037
|
+
if (message.packetHeader.sessionType !== import_protocol.SessionType.Unicast) {
|
|
1038
|
+
throw new import_types.StatusResponseError(
|
|
1039
|
+
"Invoke requests are only allowed on unicast sessions when a timed interaction is running.",
|
|
1040
|
+
import_types.StatusCode.InvalidAction
|
|
1041
|
+
);
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
if (invokeRequests.length > this.#maxPathsPerInvoke) {
|
|
1045
|
+
throw new import_types.StatusResponseError(
|
|
1046
|
+
`Only ${this.#maxPathsPerInvoke} invoke requests are supported in one message. This message contains ${invokeRequests.length}`,
|
|
1047
|
+
import_types.StatusCode.InvalidAction
|
|
1048
|
+
);
|
|
1049
|
+
}
|
|
1050
|
+
invokeRequests.forEach(({ commandPath }) => validateCommandPath(commandPath));
|
|
1051
|
+
if (invokeRequests.length > 1) {
|
|
1052
|
+
const pathsUsed = /* @__PURE__ */ new Set();
|
|
1053
|
+
const commandRefsUsed = /* @__PURE__ */ new Set();
|
|
1054
|
+
invokeRequests.forEach(({ commandPath, commandRef }) => {
|
|
1055
|
+
if (!isConcreteCommandPath(commandPath)) {
|
|
1056
|
+
throw new import_types.StatusResponseError("Illegal wildcard path in batch invoke", import_types.StatusCode.InvalidAction);
|
|
1057
|
+
}
|
|
1058
|
+
const commandPathId = (0, import_protocol.commandPathToId)(commandPath);
|
|
1059
|
+
if (pathsUsed.has(commandPathId)) {
|
|
1060
|
+
throw new import_types.StatusResponseError(
|
|
1061
|
+
`Duplicate command path (${commandPathId}) in batch invoke`,
|
|
1062
|
+
import_types.StatusCode.InvalidAction
|
|
1063
|
+
);
|
|
1064
|
+
}
|
|
1065
|
+
if (commandRef === void 0) {
|
|
1066
|
+
throw new import_types.StatusResponseError(
|
|
1067
|
+
`Command reference missing in batch invoke of ${commandPathId}`,
|
|
1068
|
+
import_types.StatusCode.InvalidAction
|
|
1069
|
+
);
|
|
1070
|
+
}
|
|
1071
|
+
if (commandRefsUsed.has(commandRef)) {
|
|
1072
|
+
throw new import_types.StatusResponseError(
|
|
1073
|
+
`Duplicate command reference ${commandRef} in invoke of ${commandPathId}`,
|
|
1074
|
+
import_types.StatusCode.InvalidAction
|
|
1075
|
+
);
|
|
1076
|
+
}
|
|
1077
|
+
pathsUsed.add(commandPathId);
|
|
1078
|
+
commandRefsUsed.add(commandRef);
|
|
1079
|
+
});
|
|
1080
|
+
}
|
|
1081
|
+
const isGroupSession = message.packetHeader.sessionType === import_protocol.SessionType.Group;
|
|
1082
|
+
const invokeResponseMessage = {
|
|
1083
|
+
suppressResponse: false,
|
|
1084
|
+
// Deprecated but must be present
|
|
1085
|
+
interactionModelRevision: import_model.Specification.INTERACTION_MODEL_REVISION,
|
|
1086
|
+
invokeResponses: [],
|
|
1087
|
+
moreChunkedMessages: invokeRequests.length > 1
|
|
1088
|
+
// Assume for now we have multiple responses when having multiple invokes
|
|
1089
|
+
};
|
|
1090
|
+
const emptyInvokeResponseBytes = import_types.TlvInvokeResponseForSend.encode(invokeResponseMessage);
|
|
1091
|
+
let messageSize = emptyInvokeResponseBytes.length;
|
|
1092
|
+
let invokeResultsProcessed = 0;
|
|
1093
|
+
const processResponseResult = async (invokeResponse) => {
|
|
1094
|
+
invokeResultsProcessed++;
|
|
1095
|
+
if (isGroupSession) {
|
|
1096
|
+
return;
|
|
1097
|
+
}
|
|
1098
|
+
const encodedInvokeResponse = import_types.TlvInvokeResponseData.encodeTlv(invokeResponse);
|
|
1099
|
+
const invokeResponseBytes = import_types.TlvAny.getEncodedByteLength(encodedInvokeResponse);
|
|
1100
|
+
if (messageSize + invokeResponseBytes > exchange.maxPayloadSize || invokeResultsProcessed === invokeRequests.length) {
|
|
1101
|
+
let lastMessageProcessed = false;
|
|
1102
|
+
if (messageSize + invokeResponseBytes <= exchange.maxPayloadSize) {
|
|
1103
|
+
invokeResponseMessage.invokeResponses.push(encodedInvokeResponse);
|
|
1104
|
+
lastMessageProcessed = true;
|
|
1105
|
+
}
|
|
1106
|
+
if (invokeResponseMessage.invokeResponses.length > 0) {
|
|
1107
|
+
if (invokeRequests.length > 1) {
|
|
1108
|
+
logger.debug(
|
|
1109
|
+
`Send ${lastMessageProcessed ? "final " : ""}invoke response for ${invokeResponseMessage.invokeResponses} commands`
|
|
1110
|
+
);
|
|
1111
|
+
}
|
|
1112
|
+
const moreChunkedMessages = lastMessageProcessed ? void 0 : true;
|
|
1113
|
+
await messenger.send(
|
|
1114
|
+
import_protocol.MessageType.InvokeResponse,
|
|
1115
|
+
import_types.TlvInvokeResponseForSend.encode({
|
|
1116
|
+
...invokeResponseMessage,
|
|
1117
|
+
moreChunkedMessages
|
|
1118
|
+
}),
|
|
1119
|
+
{
|
|
1120
|
+
logContext: {
|
|
1121
|
+
invokeMsgFlags: import_general.Diagnostic.asFlags({
|
|
1122
|
+
suppressResponse,
|
|
1123
|
+
moreChunkedMessages
|
|
1124
|
+
})
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
);
|
|
1128
|
+
invokeResponseMessage.invokeResponses = [];
|
|
1129
|
+
messageSize = emptyInvokeResponseBytes.length;
|
|
1130
|
+
}
|
|
1131
|
+
if (!lastMessageProcessed) {
|
|
1132
|
+
invokeResultsProcessed--;
|
|
1133
|
+
return processResponseResult(invokeResponse);
|
|
1134
|
+
}
|
|
1135
|
+
} else {
|
|
1136
|
+
invokeResponseMessage.invokeResponses.push(encodedInvokeResponse);
|
|
1137
|
+
messageSize += invokeResponseBytes;
|
|
1138
|
+
}
|
|
1139
|
+
};
|
|
1140
|
+
for (const { commandPath, commandFields, commandRef } of invokeRequests) {
|
|
1141
|
+
const commands = this.#context.structure.getCommands([commandPath]);
|
|
1142
|
+
if (commands.length === 0) {
|
|
1143
|
+
if (isConcreteCommandPath(commandPath)) {
|
|
1144
|
+
const { endpointId, clusterId, commandId } = commandPath;
|
|
1145
|
+
let result;
|
|
1146
|
+
try {
|
|
1147
|
+
this.#context.structure.validateConcreteCommandPath(endpointId, clusterId, commandId);
|
|
1148
|
+
throw new import_general.InternalError(
|
|
1149
|
+
"validateConcreteCommandPath should throw StatusResponseError but did not."
|
|
1150
|
+
);
|
|
1151
|
+
} catch (e) {
|
|
1152
|
+
import_types.StatusResponseError.accept(e);
|
|
1153
|
+
logger.debug(
|
|
1154
|
+
`Invoke from ${exchange.channel.name}: ${this.#context.structure.resolveCommandName(
|
|
1155
|
+
commandPath
|
|
1156
|
+
)} unsupported path: Status=${e.code}`
|
|
1157
|
+
);
|
|
1158
|
+
result = { status: { commandPath, status: { status: e.code }, commandRef } };
|
|
1159
|
+
}
|
|
1160
|
+
await processResponseResult(result);
|
|
1161
|
+
} else {
|
|
1162
|
+
logger.debug(
|
|
1163
|
+
`Invoke from ${exchange.channel.name}: ${this.#context.structure.resolveCommandName(
|
|
1164
|
+
commandPath
|
|
1165
|
+
)} ignore non-existing command`
|
|
1166
|
+
);
|
|
1167
|
+
}
|
|
1168
|
+
continue;
|
|
1169
|
+
}
|
|
1170
|
+
const isConcretePath = isConcreteCommandPath(commandPath);
|
|
1171
|
+
for (const { command, path } of commands) {
|
|
1172
|
+
const { endpointId, clusterId, commandId } = path;
|
|
1173
|
+
if (endpointId === void 0) {
|
|
1174
|
+
logger.error(
|
|
1175
|
+
`Invoke from ${exchange.channel.name}: ${this.#context.structure.resolveCommandName(
|
|
1176
|
+
path
|
|
1177
|
+
)} invalid path because empty endpoint!`
|
|
1178
|
+
);
|
|
1179
|
+
if (isConcretePath) {
|
|
1180
|
+
await processResponseResult({
|
|
1181
|
+
status: {
|
|
1182
|
+
commandPath: path,
|
|
1183
|
+
status: { status: import_types.StatusCode.UnsupportedEndpoint },
|
|
1184
|
+
commandRef
|
|
1185
|
+
}
|
|
1186
|
+
});
|
|
1187
|
+
}
|
|
1188
|
+
continue;
|
|
1189
|
+
}
|
|
1190
|
+
const endpoint = this.#context.structure.getEndpoint(endpointId);
|
|
1191
|
+
if (endpoint === void 0) {
|
|
1192
|
+
logger.error(
|
|
1193
|
+
`Invoke from ${exchange.channel.name}: ${this.#context.structure.resolveCommandName(
|
|
1194
|
+
path
|
|
1195
|
+
)} invalid path because endpoint not found!`
|
|
1196
|
+
);
|
|
1197
|
+
if (isConcretePath) {
|
|
1198
|
+
await processResponseResult({
|
|
1199
|
+
status: {
|
|
1200
|
+
commandPath: path,
|
|
1201
|
+
status: { status: import_types.StatusCode.UnsupportedEndpoint },
|
|
1202
|
+
commandRef
|
|
1203
|
+
}
|
|
1204
|
+
});
|
|
1205
|
+
}
|
|
1206
|
+
continue;
|
|
1207
|
+
}
|
|
1208
|
+
if (command.requiresTimedInteraction && !receivedWithinTimedInteraction) {
|
|
1209
|
+
logger.debug(`This invoke requires a timed interaction which is not initialized.`);
|
|
1210
|
+
if (isConcretePath) {
|
|
1211
|
+
await processResponseResult({
|
|
1212
|
+
status: {
|
|
1213
|
+
commandPath: path,
|
|
1214
|
+
status: { status: import_types.StatusCode.NeedsTimedInteraction },
|
|
1215
|
+
commandRef
|
|
1216
|
+
}
|
|
1217
|
+
});
|
|
1218
|
+
}
|
|
1219
|
+
continue;
|
|
1220
|
+
}
|
|
1221
|
+
if (getMatterModelClusterCommand(clusterId, commandId)?.fabricScoped && (!exchange.session.isSecure || !exchange.session.fabric)) {
|
|
1222
|
+
logger.debug(`This invoke requires a secure session with a fabric assigned which is missing.`);
|
|
1223
|
+
if (isConcretePath) {
|
|
1224
|
+
await processResponseResult({
|
|
1225
|
+
status: { commandPath: path, status: { status: import_types.StatusCode.UnsupportedAccess }, commandRef }
|
|
1226
|
+
});
|
|
1227
|
+
}
|
|
1228
|
+
continue;
|
|
1229
|
+
}
|
|
1230
|
+
let result;
|
|
1231
|
+
try {
|
|
1232
|
+
result = await this.invokeCommand(
|
|
1233
|
+
path,
|
|
1234
|
+
command,
|
|
1235
|
+
exchange,
|
|
1236
|
+
commandFields ?? import_types.TlvNoArguments.encodeTlv(commandFields),
|
|
1237
|
+
message,
|
|
1238
|
+
endpoint,
|
|
1239
|
+
receivedWithinTimedInteraction
|
|
1240
|
+
);
|
|
1241
|
+
} catch (e) {
|
|
1242
|
+
import_types.StatusResponseError.accept(e);
|
|
1243
|
+
let errorCode = e.code;
|
|
1244
|
+
const errorLogText = `Error ${import_general.Diagnostic.hex(errorCode)}${e.clusterCode !== void 0 ? `/${import_general.Diagnostic.hex(e.clusterCode)}` : ""} while invoking command: ${e.message}`;
|
|
1245
|
+
if (e instanceof import_types.ValidationError) {
|
|
1246
|
+
logger.info(
|
|
1247
|
+
`Validation-${errorLogText}${e.fieldName !== void 0 ? ` in field ${e.fieldName}` : ""}`
|
|
1248
|
+
);
|
|
1249
|
+
if (errorCode === import_types.StatusCode.InvalidAction) {
|
|
1250
|
+
errorCode = import_types.StatusCode.InvalidCommand;
|
|
1251
|
+
}
|
|
1252
|
+
} else {
|
|
1253
|
+
logger.info(errorLogText);
|
|
1254
|
+
}
|
|
1255
|
+
result = {
|
|
1256
|
+
code: errorCode,
|
|
1257
|
+
clusterCode: e.clusterCode,
|
|
1258
|
+
responseId: command.responseId,
|
|
1259
|
+
response: import_types.TlvNoResponse.encodeTlv()
|
|
1260
|
+
};
|
|
1261
|
+
}
|
|
1262
|
+
const { code, clusterCode, responseId, response } = result;
|
|
1263
|
+
if (response.length === 0) {
|
|
1264
|
+
await processResponseResult({
|
|
1265
|
+
status: { commandPath: path, status: { status: code, clusterStatus: clusterCode }, commandRef }
|
|
1266
|
+
});
|
|
1267
|
+
} else {
|
|
1268
|
+
await processResponseResult({
|
|
1269
|
+
command: {
|
|
1270
|
+
commandPath: { ...path, commandId: responseId },
|
|
1271
|
+
commandFields: response,
|
|
1272
|
+
commandRef
|
|
1273
|
+
}
|
|
1274
|
+
});
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
async invokeCommand(path, command, exchange, commandFields, message, endpoint, timed = false) {
|
|
1280
|
+
const invokeCommand = (context) => {
|
|
1281
|
+
if (context.authorityAt(command.invokeAcl, {
|
|
1282
|
+
endpoint: endpoint.number,
|
|
1283
|
+
cluster: path.clusterId
|
|
1284
|
+
}) !== import_protocol.AccessControl.Authority.Granted) {
|
|
1285
|
+
throw new import_protocol.AccessDeniedError(
|
|
1286
|
+
`Access to ${endpoint.number}/${import_general.Diagnostic.hex(path.clusterId)} denied on ${exchange.session.name}.`
|
|
1287
|
+
);
|
|
1288
|
+
}
|
|
1289
|
+
return command.invoke(exchange.session, commandFields, message, endpoint);
|
|
1290
|
+
};
|
|
1291
|
+
return (0, import_OnlineContext.OnlineContext)({
|
|
1292
|
+
activity: exchange[activityKey],
|
|
1293
|
+
command: true,
|
|
1294
|
+
timed,
|
|
1295
|
+
message,
|
|
1296
|
+
exchange,
|
|
1297
|
+
tracer: this.#tracer,
|
|
1298
|
+
actionType: import_ActionTracer.ActionTracer.ActionType.Invoke,
|
|
1299
|
+
node: this.#node
|
|
1300
|
+
}).act(invokeCommand);
|
|
1301
|
+
}
|
|
1302
|
+
handleTimedRequest(exchange, { timeout, interactionModelRevision }) {
|
|
1303
|
+
logger.debug(`Received timed request (${timeout}ms) from ${exchange.channel.name}`);
|
|
1304
|
+
if (interactionModelRevision > import_model.Specification.INTERACTION_MODEL_REVISION) {
|
|
1305
|
+
logger.debug(
|
|
1306
|
+
`Interaction model revision of sender ${interactionModelRevision} is higher than supported ${import_model.Specification.INTERACTION_MODEL_REVISION}.`
|
|
1307
|
+
);
|
|
1308
|
+
}
|
|
1309
|
+
exchange.startTimedInteraction(timeout);
|
|
1310
|
+
}
|
|
1311
|
+
async close() {
|
|
1312
|
+
this.#isClosing = true;
|
|
1313
|
+
}
|
|
1314
|
+
get #tracer() {
|
|
1315
|
+
if (this.#node.env.has(import_ActionTracer.ActionTracer)) {
|
|
1316
|
+
return this.#node.env.get(import_ActionTracer.ActionTracer);
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
#updateStructure() {
|
|
1320
|
+
if (this.#node.lifecycle.isPartsReady) {
|
|
1321
|
+
const server = import_EndpointServer.EndpointServer.forEndpoint(this.#node);
|
|
1322
|
+
this.#context.structure.initializeFromEndpoint(server);
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
//# sourceMappingURL=InteractionServer.js.map
|