@matter/protocol 0.12.4-alpha.0-20250224-e0964a795 → 0.12.4-alpha.0-20250224-46934b522
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/interaction/InteractionClient.d.ts +6 -2
- package/dist/cjs/interaction/InteractionClient.d.ts.map +1 -1
- package/dist/cjs/interaction/InteractionClient.js +3 -2
- package/dist/cjs/interaction/InteractionClient.js.map +1 -1
- package/dist/cjs/interaction/InteractionServer.d.ts +18 -1
- package/dist/cjs/interaction/InteractionServer.d.ts.map +1 -1
- package/dist/cjs/interaction/InteractionServer.js +117 -41
- package/dist/cjs/interaction/InteractionServer.js.map +1 -1
- package/dist/cjs/interaction/ServerSubscription.d.ts +8 -16
- package/dist/cjs/interaction/ServerSubscription.d.ts.map +1 -1
- package/dist/cjs/interaction/ServerSubscription.js +78 -55
- package/dist/cjs/interaction/ServerSubscription.js.map +1 -1
- package/dist/cjs/interaction/Subscription.d.ts +7 -1
- package/dist/cjs/interaction/Subscription.d.ts.map +1 -1
- package/dist/cjs/interaction/Subscription.js +25 -2
- package/dist/cjs/interaction/Subscription.js.map +1 -1
- package/dist/cjs/peer/ControllerCommissioner.d.ts.map +1 -1
- package/dist/cjs/peer/ControllerCommissioner.js +4 -5
- package/dist/cjs/peer/ControllerCommissioner.js.map +1 -1
- package/dist/cjs/peer/PeerAddress.d.ts +5 -0
- package/dist/cjs/peer/PeerAddress.d.ts.map +1 -1
- package/dist/cjs/peer/PeerAddress.js +13 -1
- package/dist/cjs/peer/PeerAddress.js.map +1 -1
- package/dist/cjs/peer/PeerSet.d.ts +6 -2
- package/dist/cjs/peer/PeerSet.d.ts.map +1 -1
- package/dist/cjs/peer/PeerSet.js +18 -8
- package/dist/cjs/peer/PeerSet.js.map +1 -1
- package/dist/cjs/securechannel/SecureChannelMessenger.d.ts +1 -0
- package/dist/cjs/securechannel/SecureChannelMessenger.d.ts.map +1 -1
- package/dist/cjs/securechannel/SecureChannelMessenger.js +3 -0
- package/dist/cjs/securechannel/SecureChannelMessenger.js.map +1 -1
- package/dist/cjs/session/SecureSession.d.ts +1 -1
- package/dist/cjs/session/SecureSession.d.ts.map +1 -1
- package/dist/cjs/session/SecureSession.js +3 -2
- package/dist/cjs/session/SecureSession.js.map +1 -1
- package/dist/cjs/session/Session.d.ts +1 -0
- package/dist/cjs/session/Session.d.ts.map +1 -1
- package/dist/cjs/session/Session.js +1 -0
- package/dist/cjs/session/Session.js.map +1 -1
- package/dist/cjs/session/SessionManager.d.ts +2 -2
- package/dist/cjs/session/SessionManager.d.ts.map +1 -1
- package/dist/cjs/session/SessionManager.js +5 -9
- package/dist/cjs/session/SessionManager.js.map +1 -1
- package/dist/esm/interaction/InteractionClient.d.ts +6 -2
- package/dist/esm/interaction/InteractionClient.d.ts.map +1 -1
- package/dist/esm/interaction/InteractionClient.js +3 -2
- package/dist/esm/interaction/InteractionClient.js.map +1 -1
- package/dist/esm/interaction/InteractionServer.d.ts +18 -1
- package/dist/esm/interaction/InteractionServer.d.ts.map +1 -1
- package/dist/esm/interaction/InteractionServer.js +118 -42
- package/dist/esm/interaction/InteractionServer.js.map +1 -1
- package/dist/esm/interaction/ServerSubscription.d.ts +8 -16
- package/dist/esm/interaction/ServerSubscription.d.ts.map +1 -1
- package/dist/esm/interaction/ServerSubscription.js +78 -55
- package/dist/esm/interaction/ServerSubscription.js.map +1 -1
- package/dist/esm/interaction/Subscription.d.ts +7 -1
- package/dist/esm/interaction/Subscription.d.ts.map +1 -1
- package/dist/esm/interaction/Subscription.js +26 -3
- package/dist/esm/interaction/Subscription.js.map +1 -1
- package/dist/esm/peer/ControllerCommissioner.d.ts.map +1 -1
- package/dist/esm/peer/ControllerCommissioner.js +4 -5
- package/dist/esm/peer/ControllerCommissioner.js.map +1 -1
- package/dist/esm/peer/PeerAddress.d.ts +5 -0
- package/dist/esm/peer/PeerAddress.d.ts.map +1 -1
- package/dist/esm/peer/PeerAddress.js +13 -1
- package/dist/esm/peer/PeerAddress.js.map +1 -1
- package/dist/esm/peer/PeerSet.d.ts +6 -2
- package/dist/esm/peer/PeerSet.d.ts.map +1 -1
- package/dist/esm/peer/PeerSet.js +18 -8
- package/dist/esm/peer/PeerSet.js.map +1 -1
- package/dist/esm/securechannel/SecureChannelMessenger.d.ts +1 -0
- package/dist/esm/securechannel/SecureChannelMessenger.d.ts.map +1 -1
- package/dist/esm/securechannel/SecureChannelMessenger.js +3 -0
- package/dist/esm/securechannel/SecureChannelMessenger.js.map +1 -1
- package/dist/esm/session/SecureSession.d.ts +1 -1
- package/dist/esm/session/SecureSession.d.ts.map +1 -1
- package/dist/esm/session/SecureSession.js +3 -2
- package/dist/esm/session/SecureSession.js.map +1 -1
- package/dist/esm/session/Session.d.ts +1 -0
- package/dist/esm/session/Session.d.ts.map +1 -1
- package/dist/esm/session/Session.js +1 -0
- package/dist/esm/session/Session.js.map +1 -1
- package/dist/esm/session/SessionManager.d.ts +2 -2
- package/dist/esm/session/SessionManager.d.ts.map +1 -1
- package/dist/esm/session/SessionManager.js +6 -10
- package/dist/esm/session/SessionManager.js.map +1 -1
- package/package.json +6 -6
- package/src/interaction/InteractionClient.ts +8 -3
- package/src/interaction/InteractionServer.ts +158 -45
- package/src/interaction/ServerSubscription.ts +87 -63
- package/src/interaction/Subscription.ts +34 -6
- package/src/peer/ControllerCommissioner.ts +4 -5
- package/src/peer/PeerAddress.ts +14 -0
- package/src/peer/PeerSet.ts +31 -9
- package/src/securechannel/SecureChannelMessenger.ts +4 -0
- package/src/session/SecureSession.ts +3 -2
- package/src/session/Session.ts +1 -0
- package/src/session/SessionManager.ts +5 -9
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { Crypto, Diagnostic, InternalError, Logger, MatterFlowError } from "#general";
|
|
7
|
+
import { Crypto, Diagnostic, InternalError, Logger, MatterFlowError, Observable, ServerAddressIp } from "#general";
|
|
8
8
|
import { AttributeModel, ClusterModel, CommandModel, GLOBAL_IDS, MatterModel, Specification } from "#model";
|
|
9
9
|
import { PeerAddress } from "#peer/PeerAddress.js";
|
|
10
10
|
import { SessionManager } from "#session/SessionManager.js";
|
|
@@ -67,6 +67,19 @@ import { ServerSubscriptionConfig } from "./SubscriptionOptions.js";
|
|
|
67
67
|
|
|
68
68
|
const logger = Logger.get("InteractionServer");
|
|
69
69
|
|
|
70
|
+
export interface PeerSubscription {
|
|
71
|
+
subscriptionId: number;
|
|
72
|
+
peerAddress: PeerAddress;
|
|
73
|
+
minIntervalFloorSeconds: number;
|
|
74
|
+
maxIntervalCeilingSeconds: number;
|
|
75
|
+
attributeRequests?: TypeFromSchema<typeof TlvAttributePath>[];
|
|
76
|
+
eventRequests?: TypeFromSchema<typeof TlvEventPath>[];
|
|
77
|
+
isFabricFiltered: boolean;
|
|
78
|
+
maxInterval: number;
|
|
79
|
+
sendInterval: number;
|
|
80
|
+
operationalAddress?: ServerAddressIp;
|
|
81
|
+
}
|
|
82
|
+
|
|
70
83
|
export interface CommandPath {
|
|
71
84
|
nodeId?: NodeId;
|
|
72
85
|
endpointId: EndpointNumber;
|
|
@@ -234,6 +247,7 @@ export class InteractionServer implements ProtocolHandler, InteractionRecipient
|
|
|
234
247
|
#clientHandler?: ProtocolHandler;
|
|
235
248
|
readonly #subscriptionConfig: ServerSubscriptionConfig;
|
|
236
249
|
readonly #maxPathsPerInvoke;
|
|
250
|
+
readonly #subscriptionEstablishmentStarted = Observable<[peerAddress: PeerAddress]>();
|
|
237
251
|
|
|
238
252
|
constructor(context: InteractionContext) {
|
|
239
253
|
this.#context = context;
|
|
@@ -254,6 +268,10 @@ export class InteractionServer implements ProtocolHandler, InteractionRecipient
|
|
|
254
268
|
return this.#maxPathsPerInvoke;
|
|
255
269
|
}
|
|
256
270
|
|
|
271
|
+
get subscriptionEstablishmentStarted() {
|
|
272
|
+
return this.#subscriptionEstablishmentStarted;
|
|
273
|
+
}
|
|
274
|
+
|
|
257
275
|
async onNewExchange(exchange: MessageExchange, message: Message) {
|
|
258
276
|
// Note - changes here must be copied to TransactionalInteractionServer as it does not call super() to avoid
|
|
259
277
|
// the stack frame
|
|
@@ -975,7 +993,11 @@ export class InteractionServer implements ProtocolHandler, InteractionRecipient
|
|
|
975
993
|
|
|
976
994
|
async handleSubscribeRequest(
|
|
977
995
|
exchange: MessageExchange,
|
|
978
|
-
|
|
996
|
+
request: SubscribeRequest,
|
|
997
|
+
messenger: InteractionServerMessenger,
|
|
998
|
+
message: Message,
|
|
999
|
+
): Promise<void> {
|
|
1000
|
+
const {
|
|
979
1001
|
minIntervalFloorSeconds,
|
|
980
1002
|
maxIntervalCeilingSeconds,
|
|
981
1003
|
attributeRequests,
|
|
@@ -985,10 +1007,7 @@ export class InteractionServer implements ProtocolHandler, InteractionRecipient
|
|
|
985
1007
|
keepSubscriptions,
|
|
986
1008
|
isFabricFiltered,
|
|
987
1009
|
interactionModelRevision,
|
|
988
|
-
}
|
|
989
|
-
messenger: InteractionServerMessenger,
|
|
990
|
-
message: Message,
|
|
991
|
-
): Promise<void> {
|
|
1010
|
+
} = request;
|
|
992
1011
|
logger.debug(
|
|
993
1012
|
`Received subscribe request from ${exchange.channel.name} (keepSubscriptions=${keepSubscriptions}, isFabricFiltered=${isFabricFiltered})`,
|
|
994
1013
|
);
|
|
@@ -1017,8 +1036,7 @@ export class InteractionServer implements ProtocolHandler, InteractionRecipient
|
|
|
1017
1036
|
|
|
1018
1037
|
if (!keepSubscriptions) {
|
|
1019
1038
|
const clearedCount = await this.#context.sessions.clearSubscriptionsForNode(
|
|
1020
|
-
fabric.
|
|
1021
|
-
session.peerNodeId,
|
|
1039
|
+
fabric.addressOf(session.peerNodeId),
|
|
1022
1040
|
true,
|
|
1023
1041
|
);
|
|
1024
1042
|
if (clearedCount > 0) {
|
|
@@ -1084,6 +1102,71 @@ export class InteractionServer implements ProtocolHandler, InteractionRecipient
|
|
|
1084
1102
|
if (this.#nextSubscriptionId === 0xffffffff) this.#nextSubscriptionId = 0;
|
|
1085
1103
|
const subscriptionId = this.#nextSubscriptionId++;
|
|
1086
1104
|
|
|
1105
|
+
this.#subscriptionEstablishmentStarted.emit(session.peerAddress);
|
|
1106
|
+
let subscription: ServerSubscription;
|
|
1107
|
+
try {
|
|
1108
|
+
subscription = await this.#establishSubscription(
|
|
1109
|
+
subscriptionId,
|
|
1110
|
+
request,
|
|
1111
|
+
messenger,
|
|
1112
|
+
session,
|
|
1113
|
+
exchange,
|
|
1114
|
+
message,
|
|
1115
|
+
);
|
|
1116
|
+
} catch (error: any) {
|
|
1117
|
+
logger.error(
|
|
1118
|
+
`Subscription ${subscriptionId} for Session ${session.id}: Error while sending initial data reports`,
|
|
1119
|
+
error,
|
|
1120
|
+
);
|
|
1121
|
+
if (error instanceof StatusResponseError) {
|
|
1122
|
+
logger.info(`Sending status response ${error.code} for interaction error: ${error.message}`);
|
|
1123
|
+
await messenger.sendStatus(error.code, {
|
|
1124
|
+
logContext: {
|
|
1125
|
+
for: "I/SubscriptionSeed-Status",
|
|
1126
|
+
},
|
|
1127
|
+
});
|
|
1128
|
+
}
|
|
1129
|
+
await messenger.close();
|
|
1130
|
+
return; // Make sure to not bubble up the exception
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
const maxInterval = subscription.maxInterval;
|
|
1134
|
+
// Then send the subscription response
|
|
1135
|
+
await messenger.send(
|
|
1136
|
+
MessageType.SubscribeResponse,
|
|
1137
|
+
TlvSubscribeResponse.encode({
|
|
1138
|
+
subscriptionId,
|
|
1139
|
+
maxInterval,
|
|
1140
|
+
interactionModelRevision: Specification.INTERACTION_MODEL_REVISION,
|
|
1141
|
+
}),
|
|
1142
|
+
{
|
|
1143
|
+
logContext: {
|
|
1144
|
+
subId: subscriptionId,
|
|
1145
|
+
maxInterval,
|
|
1146
|
+
},
|
|
1147
|
+
},
|
|
1148
|
+
);
|
|
1149
|
+
|
|
1150
|
+
// When an error occurs while sending the response, the subscription is not yet active and will be cleaned up by GC
|
|
1151
|
+
subscription.activate();
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
async #establishSubscription(
|
|
1155
|
+
id: number,
|
|
1156
|
+
{
|
|
1157
|
+
minIntervalFloorSeconds,
|
|
1158
|
+
maxIntervalCeilingSeconds,
|
|
1159
|
+
attributeRequests,
|
|
1160
|
+
dataVersionFilters,
|
|
1161
|
+
eventRequests,
|
|
1162
|
+
eventFilters,
|
|
1163
|
+
isFabricFiltered,
|
|
1164
|
+
}: SubscribeRequest,
|
|
1165
|
+
messenger: InteractionServerMessenger,
|
|
1166
|
+
session: SecureSession,
|
|
1167
|
+
exchange: MessageExchange,
|
|
1168
|
+
message: Message,
|
|
1169
|
+
) {
|
|
1087
1170
|
const context: ServerSubscriptionContext = {
|
|
1088
1171
|
session,
|
|
1089
1172
|
structure: this.#endpointStructure,
|
|
@@ -1101,7 +1184,7 @@ export class InteractionServer implements ProtocolHandler, InteractionRecipient
|
|
|
1101
1184
|
};
|
|
1102
1185
|
|
|
1103
1186
|
const subscription = new ServerSubscription({
|
|
1104
|
-
id
|
|
1187
|
+
id,
|
|
1105
1188
|
context,
|
|
1106
1189
|
criteria: {
|
|
1107
1190
|
attributeRequests,
|
|
@@ -1110,56 +1193,86 @@ export class InteractionServer implements ProtocolHandler, InteractionRecipient
|
|
|
1110
1193
|
eventFilters,
|
|
1111
1194
|
isFabricFiltered,
|
|
1112
1195
|
},
|
|
1113
|
-
|
|
1114
|
-
|
|
1196
|
+
minIntervalFloorSeconds,
|
|
1197
|
+
maxIntervalCeilingSeconds,
|
|
1115
1198
|
subscriptionOptions: this.#subscriptionConfig,
|
|
1116
1199
|
});
|
|
1117
1200
|
|
|
1118
1201
|
try {
|
|
1119
1202
|
// Send initial data report to prime the subscription with initial data
|
|
1120
1203
|
await subscription.sendInitialReport(messenger);
|
|
1121
|
-
} catch (error
|
|
1122
|
-
logger.error(
|
|
1123
|
-
`Subscription ${subscriptionId} for Session ${session.id}: Error while sending initial data reports`,
|
|
1124
|
-
error,
|
|
1125
|
-
);
|
|
1204
|
+
} catch (error) {
|
|
1126
1205
|
await subscription.close(); // Cleanup
|
|
1127
|
-
|
|
1128
|
-
logger.info(`Sending status response ${error.code} for interaction error: ${error.message}`);
|
|
1129
|
-
await messenger.sendStatus(error.code, {
|
|
1130
|
-
logContext: {
|
|
1131
|
-
for: "I/SubscriptionSeed-Status",
|
|
1132
|
-
},
|
|
1133
|
-
});
|
|
1134
|
-
}
|
|
1135
|
-
await messenger.close();
|
|
1136
|
-
return; // Make sure to not bubble up the exception
|
|
1206
|
+
throw error;
|
|
1137
1207
|
}
|
|
1138
1208
|
|
|
1139
|
-
const maxInterval = subscription.maxInterval;
|
|
1140
1209
|
logger.info(
|
|
1141
|
-
`Successfully created subscription ${
|
|
1210
|
+
`Successfully created subscription ${id} for Session ${
|
|
1142
1211
|
session.id
|
|
1143
|
-
}. Updates: ${minIntervalFloorSeconds} - ${maxIntervalCeilingSeconds} => ${maxInterval} seconds (sendInterval = ${subscription.sendInterval} seconds)`,
|
|
1212
|
+
}. Updates: ${minIntervalFloorSeconds} - ${maxIntervalCeilingSeconds} => ${subscription.maxInterval} seconds (sendInterval = ${subscription.sendInterval} seconds)`,
|
|
1144
1213
|
);
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1214
|
+
return subscription;
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
async establishFormerSubscription(
|
|
1218
|
+
{
|
|
1219
|
+
subscriptionId,
|
|
1220
|
+
attributeRequests,
|
|
1221
|
+
eventRequests,
|
|
1222
|
+
isFabricFiltered,
|
|
1223
|
+
minIntervalFloorSeconds,
|
|
1224
|
+
maxIntervalCeilingSeconds,
|
|
1225
|
+
maxInterval,
|
|
1226
|
+
sendInterval,
|
|
1227
|
+
}: PeerSubscription,
|
|
1228
|
+
session: SecureSession,
|
|
1229
|
+
) {
|
|
1230
|
+
const exchange = this.#context.initiateExchange(session.peerAddress, INTERACTION_PROTOCOL_ID);
|
|
1231
|
+
const message = {} as Message;
|
|
1232
|
+
logger.debug(
|
|
1233
|
+
`Send DataReports to re-establish subscription ${subscriptionId} to `,
|
|
1234
|
+
Diagnostic.dict({ isFabricFiltered, maxInterval, sendInterval }),
|
|
1159
1235
|
);
|
|
1236
|
+
const context: ServerSubscriptionContext = {
|
|
1237
|
+
session,
|
|
1238
|
+
structure: this.#endpointStructure,
|
|
1160
1239
|
|
|
1161
|
-
|
|
1162
|
-
|
|
1240
|
+
readAttribute: (path, attribute, offline) =>
|
|
1241
|
+
this.readAttribute(path, attribute, exchange, isFabricFiltered, message, offline),
|
|
1242
|
+
|
|
1243
|
+
readEndpointAttributesForSubscription: attributes =>
|
|
1244
|
+
this.readEndpointAttributesForSubscription(attributes, exchange, isFabricFiltered, message),
|
|
1245
|
+
|
|
1246
|
+
readEvent: (path, event, eventFilters) =>
|
|
1247
|
+
this.readEvent(path, eventFilters, event, exchange, isFabricFiltered, message),
|
|
1248
|
+
|
|
1249
|
+
initiateExchange: (address: PeerAddress, protocolId) => this.#context.initiateExchange(address, protocolId),
|
|
1250
|
+
};
|
|
1251
|
+
|
|
1252
|
+
const subscription = new ServerSubscription({
|
|
1253
|
+
id: subscriptionId,
|
|
1254
|
+
context,
|
|
1255
|
+
minIntervalFloorSeconds,
|
|
1256
|
+
maxIntervalCeilingSeconds,
|
|
1257
|
+
criteria: {
|
|
1258
|
+
attributeRequests,
|
|
1259
|
+
eventRequests,
|
|
1260
|
+
isFabricFiltered,
|
|
1261
|
+
},
|
|
1262
|
+
subscriptionOptions: this.#subscriptionConfig,
|
|
1263
|
+
useAsMaxInterval: maxInterval,
|
|
1264
|
+
useAsSendInterval: sendInterval,
|
|
1265
|
+
});
|
|
1266
|
+
|
|
1267
|
+
try {
|
|
1268
|
+
// Send initial data report to prime the subscription with initial data
|
|
1269
|
+
await subscription.sendInitialReport(new InteractionServerMessenger(exchange));
|
|
1270
|
+
subscription.activate();
|
|
1271
|
+
} catch (error) {
|
|
1272
|
+
await subscription.close(); // Cleanup
|
|
1273
|
+
throw error;
|
|
1274
|
+
}
|
|
1275
|
+
return subscription;
|
|
1163
1276
|
}
|
|
1164
1277
|
|
|
1165
1278
|
async handleInvokeRequest(
|
|
@@ -175,41 +175,58 @@ export class ServerSubscription extends Subscription {
|
|
|
175
175
|
}
|
|
176
176
|
>();
|
|
177
177
|
#sendUpdatesActivated = false;
|
|
178
|
-
readonly #maxIntervalMs: number;
|
|
179
178
|
readonly #sendIntervalMs: number;
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
179
|
+
readonly #minIntervalFloorMs: number;
|
|
180
|
+
readonly #maxIntervalCeilingMs: number;
|
|
181
|
+
readonly #peerAddress: PeerAddress;
|
|
183
182
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
183
|
+
#sendNextUpdateImmediately = false;
|
|
184
|
+
#sendUpdateErrorCounter = 0;
|
|
185
|
+
readonly #attributeUpdatePromises = new Set<PromiseLike<void>>();
|
|
186
|
+
#currentUpdatePromise?: Promise<void>;
|
|
188
187
|
|
|
189
188
|
constructor(options: {
|
|
190
189
|
id: number;
|
|
191
190
|
context: ServerSubscriptionContext;
|
|
192
191
|
criteria: SubscriptionCriteria;
|
|
193
|
-
|
|
194
|
-
|
|
192
|
+
minIntervalFloorSeconds: number;
|
|
193
|
+
maxIntervalCeilingSeconds: number;
|
|
195
194
|
subscriptionOptions: ServerSubscriptionConfig;
|
|
195
|
+
useAsMaxInterval?: number;
|
|
196
|
+
useAsSendInterval?: number;
|
|
196
197
|
}) {
|
|
197
|
-
const {
|
|
198
|
+
const {
|
|
199
|
+
id,
|
|
200
|
+
context,
|
|
201
|
+
criteria,
|
|
202
|
+
minIntervalFloorSeconds,
|
|
203
|
+
maxIntervalCeilingSeconds,
|
|
204
|
+
subscriptionOptions,
|
|
205
|
+
useAsMaxInterval,
|
|
206
|
+
useAsSendInterval,
|
|
207
|
+
} = options;
|
|
198
208
|
|
|
199
209
|
super(context.session, id, criteria);
|
|
200
210
|
this.#context = context;
|
|
201
211
|
this.#structure = context.structure;
|
|
202
212
|
|
|
203
|
-
this
|
|
204
|
-
this
|
|
205
|
-
this
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
+
this.#peerAddress = this.session.peerAddress;
|
|
214
|
+
this.#minIntervalFloorMs = minIntervalFloorSeconds * 1000;
|
|
215
|
+
this.#maxIntervalCeilingMs = maxIntervalCeilingSeconds * 1000;
|
|
216
|
+
|
|
217
|
+
let maxInterval: number;
|
|
218
|
+
let sendInterval: number;
|
|
219
|
+
if (useAsMaxInterval !== undefined && useAsSendInterval !== undefined) {
|
|
220
|
+
maxInterval = useAsMaxInterval * 1000;
|
|
221
|
+
sendInterval = useAsSendInterval * 1000;
|
|
222
|
+
} else {
|
|
223
|
+
({ maxInterval, sendInterval } = this.#determineSendingIntervals(
|
|
224
|
+
subscriptionOptions.minIntervalSeconds * 1000,
|
|
225
|
+
subscriptionOptions.maxIntervalSeconds * 1000,
|
|
226
|
+
subscriptionOptions.randomizationWindowSeconds * 1000,
|
|
227
|
+
));
|
|
228
|
+
}
|
|
229
|
+
this.maxIntervalMs = maxInterval;
|
|
213
230
|
this.#sendIntervalMs = sendInterval;
|
|
214
231
|
|
|
215
232
|
this.#updateTimer = Time.getTimer(`Subscription ${this.id} update`, this.#sendIntervalMs, () =>
|
|
@@ -217,7 +234,7 @@ export class ServerSubscription extends Subscription {
|
|
|
217
234
|
); // will be started later
|
|
218
235
|
}
|
|
219
236
|
|
|
220
|
-
|
|
237
|
+
#determineSendingIntervals(
|
|
221
238
|
subscriptionMinIntervalMs: number,
|
|
222
239
|
subscriptionMaxIntervalMs: number,
|
|
223
240
|
subscriptionRandomizationWindowMs: number,
|
|
@@ -230,7 +247,7 @@ export class ServerSubscription extends Subscription {
|
|
|
230
247
|
const maxInterval = Math.min(
|
|
231
248
|
Math.max(
|
|
232
249
|
subscriptionMinIntervalMs,
|
|
233
|
-
Math.max(this
|
|
250
|
+
Math.max(this.#minIntervalFloorMs, Math.min(subscriptionMaxIntervalMs, this.#maxIntervalCeilingMs)),
|
|
234
251
|
) + Math.floor(subscriptionRandomizationWindowMs * Math.random()),
|
|
235
252
|
MAX_INTERVAL_PUBLISHER_LIMIT_S * 1000,
|
|
236
253
|
);
|
|
@@ -239,7 +256,7 @@ export class ServerSubscription extends Subscription {
|
|
|
239
256
|
// But if we have no chance of at least one full resubmission process we do like chip-tool.
|
|
240
257
|
// One full resubmission process takes 33-45 seconds. So 60s means we reach at least first 2 retries of a
|
|
241
258
|
// second subscription report after first failed.
|
|
242
|
-
sendInterval = Math.max(this
|
|
259
|
+
sendInterval = Math.max(this.#minIntervalFloorMs, Math.floor(maxInterval * 0.8));
|
|
243
260
|
}
|
|
244
261
|
if (sendInterval < subscriptionMinIntervalMs) {
|
|
245
262
|
// But not faster than once every 2s
|
|
@@ -251,7 +268,7 @@ export class ServerSubscription extends Subscription {
|
|
|
251
268
|
return { maxInterval, sendInterval };
|
|
252
269
|
}
|
|
253
270
|
|
|
254
|
-
|
|
271
|
+
#registerNewAttributes() {
|
|
255
272
|
const newAttributes = new Array<AttributeWithPath>();
|
|
256
273
|
const attributeErrors = new Array<TypeFromSchema<typeof TlvAttributeStatus>>();
|
|
257
274
|
const formerAttributes = new Set<string>(this.#attributeListeners.keys());
|
|
@@ -426,7 +443,7 @@ export class ServerSubscription extends Subscription {
|
|
|
426
443
|
* controller. The data of newly added events are not sent automatically.
|
|
427
444
|
*/
|
|
428
445
|
async updateSubscription() {
|
|
429
|
-
const { newAttributes } = this
|
|
446
|
+
const { newAttributes } = this.#registerNewAttributes();
|
|
430
447
|
|
|
431
448
|
for (const { path, attribute } of newAttributes) {
|
|
432
449
|
const { version, value } = this.#context.readAttribute(path, attribute);
|
|
@@ -485,15 +502,21 @@ export class ServerSubscription extends Subscription {
|
|
|
485
502
|
this.#prepareDataUpdate();
|
|
486
503
|
}
|
|
487
504
|
|
|
488
|
-
get maxInterval(): number {
|
|
489
|
-
return Math.ceil(this.#maxIntervalMs / 1000);
|
|
490
|
-
}
|
|
491
|
-
|
|
492
505
|
get sendInterval(): number {
|
|
493
506
|
return Math.ceil(this.#sendIntervalMs / 1000);
|
|
494
507
|
}
|
|
495
508
|
|
|
496
|
-
|
|
509
|
+
get minIntervalFloorSeconds(): number {
|
|
510
|
+
return Math.ceil(this.#minIntervalFloorMs / 1000);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
get maxIntervalCeilingSeconds(): number {
|
|
514
|
+
return Math.ceil(this.#maxIntervalCeilingMs / 1000);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
override activate() {
|
|
518
|
+
super.activate();
|
|
519
|
+
|
|
497
520
|
// We do not need these data anymore, so we can free some memory
|
|
498
521
|
if (this.criteria.eventFilters !== undefined) this.criteria.eventFilters.length = 0;
|
|
499
522
|
if (this.criteria.dataVersionFilters !== undefined) this.criteria.dataVersionFilters.length = 0;
|
|
@@ -533,11 +556,11 @@ export class ServerSubscription extends Subscription {
|
|
|
533
556
|
this.#updateTimer.stop();
|
|
534
557
|
const now = Time.nowMs();
|
|
535
558
|
const timeSinceLastUpdateMs = now - this.#lastUpdateTimeMs;
|
|
536
|
-
if (timeSinceLastUpdateMs < this
|
|
559
|
+
if (timeSinceLastUpdateMs < this.#minIntervalFloorMs) {
|
|
537
560
|
// Respect minimum delay time between updates
|
|
538
561
|
this.#updateTimer = Time.getTimer(
|
|
539
562
|
"Subscription update",
|
|
540
|
-
this
|
|
563
|
+
this.#minIntervalFloorMs - timeSinceLastUpdateMs,
|
|
541
564
|
() => this.#prepareDataUpdate(),
|
|
542
565
|
).start();
|
|
543
566
|
return;
|
|
@@ -550,14 +573,14 @@ export class ServerSubscription extends Subscription {
|
|
|
550
573
|
}
|
|
551
574
|
|
|
552
575
|
#triggerSendUpdate() {
|
|
553
|
-
if (this
|
|
576
|
+
if (this.#currentUpdatePromise !== undefined) {
|
|
554
577
|
logger.debug("Sending update already in progress, delaying update ...");
|
|
555
|
-
this
|
|
578
|
+
this.#sendNextUpdateImmediately = true;
|
|
556
579
|
return;
|
|
557
580
|
}
|
|
558
|
-
this
|
|
581
|
+
this.#currentUpdatePromise = this.#sendUpdate()
|
|
559
582
|
.catch(error => logger.warn("Sending subscription update failed:", error))
|
|
560
|
-
.finally(() => (this
|
|
583
|
+
.finally(() => (this.#currentUpdatePromise = undefined));
|
|
561
584
|
}
|
|
562
585
|
|
|
563
586
|
/**
|
|
@@ -593,20 +616,20 @@ export class ServerSubscription extends Subscription {
|
|
|
593
616
|
this.#lastUpdateTimeMs = Time.nowMs();
|
|
594
617
|
|
|
595
618
|
try {
|
|
596
|
-
await this
|
|
597
|
-
this
|
|
619
|
+
await this.#sendUpdateMessage(attributeUpdatesToSend, eventUpdatesToSend);
|
|
620
|
+
this.#sendUpdateErrorCounter = 0;
|
|
598
621
|
} catch (error) {
|
|
599
622
|
if (this.isClosed) {
|
|
600
623
|
// No need to care about resubmissions when the server is closing
|
|
601
624
|
return;
|
|
602
625
|
}
|
|
603
626
|
|
|
604
|
-
this
|
|
627
|
+
this.#sendUpdateErrorCounter++;
|
|
605
628
|
logger.info(
|
|
606
|
-
`Error sending subscription update message (error count=${this
|
|
629
|
+
`Error sending subscription update message (error count=${this.#sendUpdateErrorCounter}):`,
|
|
607
630
|
(error instanceof MatterError && error.message) || error,
|
|
608
631
|
);
|
|
609
|
-
if (this
|
|
632
|
+
if (this.#sendUpdateErrorCounter <= 2) {
|
|
610
633
|
// fill the data back in the queue to resend with next try
|
|
611
634
|
const newAttributeUpdatesToSend = Array.from(this.#outstandingAttributeUpdates.values());
|
|
612
635
|
this.#outstandingAttributeUpdates.clear();
|
|
@@ -622,13 +645,14 @@ export class ServerSubscription extends Subscription {
|
|
|
622
645
|
logger.info(
|
|
623
646
|
`Sending update failed 3 times in a row, canceling subscription ${this.id} and let controller subscribe again.`,
|
|
624
647
|
);
|
|
625
|
-
this
|
|
648
|
+
this.#sendNextUpdateImmediately = false;
|
|
626
649
|
if (
|
|
627
650
|
error instanceof NoResponseTimeoutError ||
|
|
628
651
|
error instanceof NetworkError ||
|
|
629
652
|
error instanceof NoChannelError
|
|
630
653
|
) {
|
|
631
654
|
// Let's consider this subscription as dead and wait for a reconnect
|
|
655
|
+
this.isCanceledByPeer = true; // We handle this case like if the controller canceled the subscription
|
|
632
656
|
await this.destroy();
|
|
633
657
|
return;
|
|
634
658
|
} else {
|
|
@@ -637,9 +661,9 @@ export class ServerSubscription extends Subscription {
|
|
|
637
661
|
}
|
|
638
662
|
}
|
|
639
663
|
|
|
640
|
-
if (this
|
|
664
|
+
if (this.#sendNextUpdateImmediately) {
|
|
641
665
|
logger.debug("Sending delayed update immediately after last one was sent.");
|
|
642
|
-
this
|
|
666
|
+
this.#sendNextUpdateImmediately = false;
|
|
643
667
|
await this.#sendUpdate(true); // Send but only if non-empty
|
|
644
668
|
}
|
|
645
669
|
}
|
|
@@ -799,7 +823,7 @@ export class ServerSubscription extends Subscription {
|
|
|
799
823
|
async sendInitialReport(messenger: InteractionServerMessenger) {
|
|
800
824
|
this.#updateTimer.stop();
|
|
801
825
|
|
|
802
|
-
const { newAttributes, attributeErrors } = this
|
|
826
|
+
const { newAttributes, attributeErrors } = this.#registerNewAttributes();
|
|
803
827
|
const { newEvents, eventErrors } = this.#registerNewEvents();
|
|
804
828
|
const { eventReportsPayload, eventsFiltered } = await this.#collectInitialEventReportPayloads(newEvents);
|
|
805
829
|
|
|
@@ -822,8 +846,8 @@ export class ServerSubscription extends Subscription {
|
|
|
822
846
|
if (MaybePromise.is(changeResult)) {
|
|
823
847
|
const resolver = Promise.resolve(changeResult)
|
|
824
848
|
.catch(error => logger.error(`Error handling attribute change:`, error))
|
|
825
|
-
.finally(() => this
|
|
826
|
-
this
|
|
849
|
+
.finally(() => this.#attributeUpdatePromises.delete(resolver));
|
|
850
|
+
this.#attributeUpdatePromises.add(resolver);
|
|
827
851
|
}
|
|
828
852
|
}
|
|
829
853
|
|
|
@@ -877,15 +901,15 @@ export class ServerSubscription extends Subscription {
|
|
|
877
901
|
}
|
|
878
902
|
}
|
|
879
903
|
|
|
880
|
-
async flush() {
|
|
904
|
+
async #flush() {
|
|
881
905
|
this.#sendDelayTimer.stop();
|
|
882
906
|
if (this.#outstandingAttributeUpdates.size > 0 || this.#outstandingEventUpdates.size > 0) {
|
|
883
907
|
logger.debug(
|
|
884
908
|
`Flushing subscription ${this.id} with ${this.#outstandingAttributeUpdates.size} attributes and ${this.#outstandingEventUpdates.size} events${this.isClosed ? " (for closing)" : ""}`,
|
|
885
909
|
);
|
|
886
910
|
this.#triggerSendUpdate();
|
|
887
|
-
if (this
|
|
888
|
-
await this
|
|
911
|
+
if (this.#currentUpdatePromise) {
|
|
912
|
+
await this.#currentUpdatePromise;
|
|
889
913
|
}
|
|
890
914
|
}
|
|
891
915
|
}
|
|
@@ -894,9 +918,9 @@ export class ServerSubscription extends Subscription {
|
|
|
894
918
|
this.#sendUpdatesActivated = false;
|
|
895
919
|
this.unregisterAttributeListeners(Array.from(this.#attributeListeners.keys()));
|
|
896
920
|
this.unregisterEventListeners(Array.from(this.#eventListeners.keys()));
|
|
897
|
-
if (this
|
|
898
|
-
const resolvers = [...this
|
|
899
|
-
this
|
|
921
|
+
if (this.#attributeUpdatePromises.size) {
|
|
922
|
+
const resolvers = [...this.#attributeUpdatePromises.values()];
|
|
923
|
+
this.#attributeUpdatePromises.clear();
|
|
900
924
|
await MatterAggregateError.allSettled(resolvers, "Error receiving all outstanding attribute values").catch(
|
|
901
925
|
error => logger.error(error),
|
|
902
926
|
);
|
|
@@ -909,16 +933,19 @@ export class ServerSubscription extends Subscription {
|
|
|
909
933
|
/**
|
|
910
934
|
* Closes the subscription and flushes all outstanding data updates if requested.
|
|
911
935
|
*/
|
|
912
|
-
override async close(graceful = false) {
|
|
936
|
+
override async close(graceful = false, cancelledByPeer = false) {
|
|
913
937
|
if (this.isClosed) {
|
|
914
938
|
return;
|
|
915
939
|
}
|
|
940
|
+
if (cancelledByPeer) {
|
|
941
|
+
this.isCanceledByPeer = true;
|
|
942
|
+
}
|
|
916
943
|
await this.destroy();
|
|
917
944
|
if (graceful) {
|
|
918
|
-
await this
|
|
945
|
+
await this.#flush();
|
|
919
946
|
}
|
|
920
|
-
if (this
|
|
921
|
-
await this
|
|
947
|
+
if (this.#currentUpdatePromise) {
|
|
948
|
+
await this.#currentUpdatePromise;
|
|
922
949
|
}
|
|
923
950
|
}
|
|
924
951
|
|
|
@@ -954,11 +981,8 @@ export class ServerSubscription extends Subscription {
|
|
|
954
981
|
}
|
|
955
982
|
}
|
|
956
983
|
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
events: EventPathWithEventData<any>[],
|
|
960
|
-
) {
|
|
961
|
-
const exchange = this.#context.initiateExchange(this.peerAddress, INTERACTION_PROTOCOL_ID);
|
|
984
|
+
async #sendUpdateMessage(attributes: AttributePathWithValueVersion<any>[], events: EventPathWithEventData<any>[]) {
|
|
985
|
+
const exchange = this.#context.initiateExchange(this.#peerAddress, INTERACTION_PROTOCOL_ID);
|
|
962
986
|
if (exchange === undefined) return;
|
|
963
987
|
if (attributes.length) {
|
|
964
988
|
logger.debug(
|