@project-chip/matter.js 0.11.0-alpha.0-20240926-407400ecb → 0.11.0-alpha.0-20241002-e7b377c34
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/CommissioningController.d.ts +17 -35
- package/dist/cjs/CommissioningController.d.ts.map +1 -1
- package/dist/cjs/CommissioningController.js +37 -5
- package/dist/cjs/CommissioningController.js.map +1 -1
- package/dist/cjs/CommissioningServer.d.ts.map +1 -1
- package/dist/cjs/CommissioningServer.js +28 -13
- package/dist/cjs/CommissioningServer.js.map +1 -1
- package/dist/cjs/MatterController.d.ts +32 -70
- package/dist/cjs/MatterController.d.ts.map +1 -1
- package/dist/cjs/MatterController.js +138 -579
- package/dist/cjs/MatterController.js.map +2 -2
- package/dist/cjs/PaseCommissioner.d.ts.map +1 -1
- package/dist/cjs/PaseCommissioner.js +9 -10
- package/dist/cjs/PaseCommissioner.js.map +2 -2
- package/dist/cjs/cluster/server/AccessControlServer.d.ts.map +1 -1
- package/dist/cjs/cluster/server/AccessControlServer.js +4 -2
- package/dist/cjs/cluster/server/AccessControlServer.js.map +1 -1
- package/dist/cjs/cluster/server/AdministratorCommissioningServer.d.ts.map +1 -1
- package/dist/cjs/cluster/server/AdministratorCommissioningServer.js +1 -1
- package/dist/cjs/cluster/server/AdministratorCommissioningServer.js.map +1 -1
- package/dist/cjs/cluster/server/ClusterServer.d.ts.map +1 -1
- package/dist/cjs/cluster/server/ClusterServer.js +3 -0
- package/dist/cjs/cluster/server/ClusterServer.js.map +1 -1
- package/dist/cjs/cluster/server/ClusterServerTypes.d.ts +5 -5
- package/dist/cjs/cluster/server/ClusterServerTypes.d.ts.map +1 -1
- package/dist/cjs/cluster/server/OperationalCredentialsServer.d.ts.map +1 -1
- package/dist/cjs/cluster/server/OperationalCredentialsServer.js +9 -10
- package/dist/cjs/cluster/server/OperationalCredentialsServer.js.map +1 -1
- package/dist/cjs/compat/interaction.d.ts +3 -2
- package/dist/cjs/compat/interaction.d.ts.map +1 -1
- package/dist/cjs/compat/interaction.js +8 -6
- package/dist/cjs/compat/interaction.js.map +1 -1
- package/dist/cjs/compat/protocol.d.ts +1 -1
- package/dist/cjs/compat/protocol.d.ts.map +1 -1
- package/dist/cjs/compat/protocol.js +1 -2
- package/dist/cjs/compat/protocol.js.map +1 -1
- package/dist/cjs/compat/securechannel.d.ts +2 -1
- package/dist/cjs/compat/securechannel.d.ts.map +1 -1
- package/dist/cjs/compat/securechannel.js +4 -3
- package/dist/cjs/compat/securechannel.js.map +1 -1
- package/dist/cjs/compat/util.d.ts +1 -1
- package/dist/cjs/compat/util.d.ts.map +1 -1
- package/dist/cjs/compat/util.js +1 -1
- package/dist/cjs/device/LegacyInteractionServer.d.ts +2 -2
- package/dist/cjs/device/LegacyInteractionServer.d.ts.map +1 -1
- package/dist/cjs/device/LegacyInteractionServer.js +3 -4
- package/dist/cjs/device/LegacyInteractionServer.js.map +1 -1
- package/dist/cjs/device/PairedNode.d.ts +1 -2
- package/dist/cjs/device/PairedNode.d.ts.map +1 -1
- package/dist/cjs/device/PairedNode.js +2 -3
- package/dist/cjs/device/PairedNode.js.map +1 -1
- package/dist/esm/CommissioningController.d.ts +17 -35
- package/dist/esm/CommissioningController.d.ts.map +1 -1
- package/dist/esm/CommissioningController.js +41 -5
- package/dist/esm/CommissioningController.js.map +1 -1
- package/dist/esm/CommissioningServer.d.ts.map +1 -1
- package/dist/esm/CommissioningServer.js +28 -13
- package/dist/esm/CommissioningServer.js.map +1 -1
- package/dist/esm/MatterController.d.ts +32 -70
- package/dist/esm/MatterController.d.ts.map +1 -1
- package/dist/esm/MatterController.js +144 -601
- package/dist/esm/MatterController.js.map +2 -2
- package/dist/esm/PaseCommissioner.d.ts.map +1 -1
- package/dist/esm/PaseCommissioner.js +12 -11
- package/dist/esm/PaseCommissioner.js.map +1 -1
- package/dist/esm/cluster/server/AccessControlServer.d.ts.map +1 -1
- package/dist/esm/cluster/server/AccessControlServer.js +4 -2
- package/dist/esm/cluster/server/AccessControlServer.js.map +1 -1
- package/dist/esm/cluster/server/AdministratorCommissioningServer.d.ts.map +1 -1
- package/dist/esm/cluster/server/AdministratorCommissioningServer.js +1 -1
- package/dist/esm/cluster/server/AdministratorCommissioningServer.js.map +1 -1
- package/dist/esm/cluster/server/ClusterServer.d.ts.map +1 -1
- package/dist/esm/cluster/server/ClusterServer.js +3 -0
- package/dist/esm/cluster/server/ClusterServer.js.map +1 -1
- package/dist/esm/cluster/server/ClusterServerTypes.d.ts +5 -5
- package/dist/esm/cluster/server/ClusterServerTypes.d.ts.map +1 -1
- package/dist/esm/cluster/server/OperationalCredentialsServer.d.ts.map +1 -1
- package/dist/esm/cluster/server/OperationalCredentialsServer.js +9 -11
- package/dist/esm/cluster/server/OperationalCredentialsServer.js.map +1 -1
- package/dist/esm/compat/interaction.d.ts +3 -2
- package/dist/esm/compat/interaction.d.ts.map +1 -1
- package/dist/esm/compat/interaction.js +14 -15
- package/dist/esm/compat/interaction.js.map +1 -1
- package/dist/esm/compat/protocol.d.ts +1 -1
- package/dist/esm/compat/protocol.d.ts.map +1 -1
- package/dist/esm/compat/protocol.js +2 -4
- package/dist/esm/compat/protocol.js.map +1 -1
- package/dist/esm/compat/securechannel.d.ts +2 -1
- package/dist/esm/compat/securechannel.d.ts.map +1 -1
- package/dist/esm/compat/securechannel.js +1 -3
- package/dist/esm/compat/securechannel.js.map +1 -1
- package/dist/esm/compat/util.d.ts +1 -1
- package/dist/esm/compat/util.d.ts.map +1 -1
- package/dist/esm/compat/util.js +2 -2
- package/dist/esm/compat/util.js.map +1 -1
- package/dist/esm/device/LegacyInteractionServer.d.ts +2 -2
- package/dist/esm/device/LegacyInteractionServer.d.ts.map +1 -1
- package/dist/esm/device/LegacyInteractionServer.js +3 -4
- package/dist/esm/device/LegacyInteractionServer.js.map +1 -1
- package/dist/esm/device/PairedNode.d.ts +1 -2
- package/dist/esm/device/PairedNode.d.ts.map +1 -1
- package/dist/esm/device/PairedNode.js +1 -1
- package/dist/esm/device/PairedNode.js.map +1 -1
- package/package.json +8 -8
- package/src/CommissioningController.ts +56 -47
- package/src/CommissioningServer.ts +27 -12
- package/src/MatterController.ts +177 -816
- package/src/PaseCommissioner.ts +11 -11
- package/src/cluster/server/AccessControlServer.ts +2 -0
- package/src/cluster/server/AdministratorCommissioningServer.ts +1 -1
- package/src/cluster/server/ClusterServer.ts +4 -0
- package/src/cluster/server/ClusterServerTypes.ts +5 -5
- package/src/cluster/server/OperationalCredentialsServer.ts +13 -13
- package/src/compat/interaction.ts +14 -13
- package/src/compat/protocol.ts +2 -3
- package/src/compat/securechannel.ts +2 -3
- package/src/compat/util.ts +1 -1
- package/src/device/LegacyInteractionServer.ts +4 -4
- package/src/device/PairedNode.ts +1 -1
package/src/MatterController.ts
CHANGED
|
@@ -11,65 +11,44 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { GeneralCommissioning } from "#clusters";
|
|
14
|
+
import { NodeCommissioningOptions } from "#CommissioningController.js";
|
|
14
15
|
import {
|
|
15
16
|
CRYPTO_SYMMETRIC_KEY_LENGTH,
|
|
16
|
-
|
|
17
|
+
ChannelType,
|
|
17
18
|
Construction,
|
|
18
19
|
Crypto,
|
|
19
20
|
ImplementationError,
|
|
20
21
|
Logger,
|
|
21
|
-
|
|
22
|
-
NoProviderError,
|
|
23
|
-
NoResponseTimeoutError,
|
|
24
|
-
ServerAddress,
|
|
22
|
+
NetInterfaceSet,
|
|
25
23
|
ServerAddressIp,
|
|
26
24
|
StorageBackendMemory,
|
|
27
25
|
StorageContext,
|
|
28
26
|
StorageManager,
|
|
29
27
|
SupportedStorageTypes,
|
|
30
|
-
Time,
|
|
31
|
-
Timer,
|
|
32
|
-
anyPromise,
|
|
33
|
-
createPromise,
|
|
34
|
-
isIPv6,
|
|
35
28
|
serverAddressToString,
|
|
36
29
|
} from "#general";
|
|
37
|
-
import { Specification } from "#model";
|
|
38
30
|
import {
|
|
39
|
-
Ble,
|
|
40
|
-
CaseClient,
|
|
41
31
|
ChannelManager,
|
|
42
32
|
ClusterClient,
|
|
43
|
-
CommissionableDevice,
|
|
44
33
|
CommissioningError,
|
|
45
|
-
CommissioningSuccessfullyFinished,
|
|
46
34
|
ControllerCommissioner,
|
|
47
|
-
ControllerCommissioningOptions,
|
|
48
|
-
ControllerDiscovery,
|
|
49
35
|
DiscoveryData,
|
|
50
|
-
|
|
36
|
+
DiscoveryOptions,
|
|
51
37
|
ExchangeManager,
|
|
52
|
-
ExchangeProvider,
|
|
53
38
|
Fabric,
|
|
54
39
|
FabricBuilder,
|
|
55
40
|
FabricJsonObject,
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
41
|
+
FabricManager,
|
|
42
|
+
NodeDiscoveryType,
|
|
43
|
+
OperationalPeer,
|
|
44
|
+
PeerCommissioningOptions,
|
|
45
|
+
PeerSet,
|
|
46
|
+
PeerStore,
|
|
62
47
|
ResumptionRecord,
|
|
63
48
|
RetransmissionLimitReachedError,
|
|
64
49
|
RootCertificateManager,
|
|
65
|
-
|
|
66
|
-
SESSION_ACTIVE_INTERVAL_MS,
|
|
67
|
-
SESSION_ACTIVE_THRESHOLD_MS,
|
|
68
|
-
SESSION_IDLE_INTERVAL_MS,
|
|
69
|
-
Scanner,
|
|
70
|
-
SessionContext,
|
|
50
|
+
ScannerSet,
|
|
71
51
|
SessionManager,
|
|
72
|
-
SessionParameters,
|
|
73
52
|
StatusReportOnlySecureChannelProtocol,
|
|
74
53
|
} from "#protocol";
|
|
75
54
|
import {
|
|
@@ -87,7 +66,6 @@ import {
|
|
|
87
66
|
TypeFromSchema,
|
|
88
67
|
VendorId,
|
|
89
68
|
} from "#types";
|
|
90
|
-
import { NodeCommissioningOptions } from "./CommissioningController.js";
|
|
91
69
|
|
|
92
70
|
const TlvCommissioningSuccessFailureResponse = TlvObject({
|
|
93
71
|
/** Contain the result of the operation. */
|
|
@@ -104,47 +82,29 @@ export type CommissionedNodeDetails = {
|
|
|
104
82
|
basicInformationData?: Record<string, SupportedStorageTypes>;
|
|
105
83
|
};
|
|
106
84
|
|
|
107
|
-
|
|
108
|
-
discoveryType?: NodeDiscoveryType;
|
|
109
|
-
timeoutSeconds?: number;
|
|
110
|
-
discoveryData?: DiscoveryData;
|
|
111
|
-
};
|
|
112
|
-
|
|
85
|
+
const DEFAULT_ADMIN_VENDOR_ID = VendorId(0xfff1);
|
|
113
86
|
const DEFAULT_FABRIC_INDEX = FabricIndex(1);
|
|
114
87
|
const DEFAULT_FABRIC_ID = FabricId(1);
|
|
115
|
-
const DEFAULT_ADMIN_VENDOR_ID = VendorId(0xfff1);
|
|
116
|
-
|
|
117
|
-
const RECONNECTION_POLLING_INTERVAL_MS = 600_000; // 10 minutes
|
|
118
|
-
const RETRANSMISSION_DISCOVERY_TIMEOUT_MS = 5_000;
|
|
119
88
|
|
|
120
89
|
const CONTROLLER_CONNECTIONS_PER_FABRIC_AND_NODE = 3;
|
|
121
90
|
const CONTROLLER_MAX_PATHS_PER_INVOKE = 10;
|
|
122
91
|
|
|
123
92
|
const logger = Logger.get("MatterController");
|
|
124
93
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
None = 0,
|
|
128
|
-
|
|
129
|
-
/** Retransmission discovery means that we ignore known addresses and start a query for 5s. */
|
|
130
|
-
RetransmissionDiscovery = 1,
|
|
94
|
+
// Operational peer extended with basic information as required for conversion to CommissionedNodeDetails
|
|
95
|
+
type CommissionedPeer = OperationalPeer & { basicInformationData?: Record<string, SupportedStorageTypes> };
|
|
131
96
|
|
|
132
|
-
|
|
133
|
-
|
|
97
|
+
// Backward-compatible persistence record for nodes
|
|
98
|
+
type StoredOperationalPeer = [NodeId, CommissionedNodeDetails];
|
|
134
99
|
|
|
135
|
-
|
|
136
|
-
FullDiscovery = 3,
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
export class MatterController implements SessionContext {
|
|
100
|
+
export class MatterController {
|
|
140
101
|
public static async create(options: {
|
|
141
102
|
sessionStorage: StorageContext;
|
|
142
103
|
rootCertificateStorage: StorageContext;
|
|
143
104
|
fabricStorage: StorageContext;
|
|
144
105
|
nodesStorage: StorageContext;
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
netInterfaceIpv6: NetInterface;
|
|
106
|
+
scanners: ScannerSet;
|
|
107
|
+
netInterfaces: NetInterfaceSet;
|
|
148
108
|
sessionClosedCallback?: (peerNodeId: NodeId) => void;
|
|
149
109
|
adminVendorId?: VendorId;
|
|
150
110
|
adminFabricId?: FabricId;
|
|
@@ -156,11 +116,10 @@ export class MatterController implements SessionContext {
|
|
|
156
116
|
rootCertificateStorage,
|
|
157
117
|
fabricStorage,
|
|
158
118
|
nodesStorage,
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
netInterfaceIpv6,
|
|
119
|
+
scanners,
|
|
120
|
+
netInterfaces,
|
|
162
121
|
sessionClosedCallback,
|
|
163
|
-
adminVendorId
|
|
122
|
+
adminVendorId,
|
|
164
123
|
adminFabricId = FabricId(DEFAULT_FABRIC_ID),
|
|
165
124
|
adminFabricIndex = FabricIndex(DEFAULT_FABRIC_INDEX),
|
|
166
125
|
caseAuthenticatedTags,
|
|
@@ -176,12 +135,10 @@ export class MatterController implements SessionContext {
|
|
|
176
135
|
sessionStorage,
|
|
177
136
|
fabricStorage,
|
|
178
137
|
nodesStorage,
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
netInterfaceIpv6,
|
|
138
|
+
scanners,
|
|
139
|
+
netInterfaces,
|
|
182
140
|
certificateManager,
|
|
183
141
|
fabric,
|
|
184
|
-
adminVendorId: fabric.rootVendorId,
|
|
185
142
|
sessionClosedCallback,
|
|
186
143
|
});
|
|
187
144
|
} else {
|
|
@@ -191,7 +148,7 @@ export class MatterController implements SessionContext {
|
|
|
191
148
|
.setRootCert(certificateManager.rootCert)
|
|
192
149
|
.setRootNodeId(rootNodeId)
|
|
193
150
|
.setIdentityProtectionKey(ipkValue)
|
|
194
|
-
.setRootVendorId(adminVendorId);
|
|
151
|
+
.setRootVendorId(adminVendorId ?? DEFAULT_ADMIN_VENDOR_ID);
|
|
195
152
|
fabricBuilder.setOperationalCert(
|
|
196
153
|
certificateManager.generateNoc(
|
|
197
154
|
fabricBuilder.publicKey,
|
|
@@ -206,12 +163,10 @@ export class MatterController implements SessionContext {
|
|
|
206
163
|
sessionStorage,
|
|
207
164
|
fabricStorage,
|
|
208
165
|
nodesStorage,
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
netInterfaceIpv6,
|
|
166
|
+
scanners,
|
|
167
|
+
netInterfaces,
|
|
212
168
|
certificateManager,
|
|
213
169
|
fabric,
|
|
214
|
-
adminVendorId,
|
|
215
170
|
sessionClosedCallback,
|
|
216
171
|
});
|
|
217
172
|
}
|
|
@@ -222,27 +177,15 @@ export class MatterController implements SessionContext {
|
|
|
222
177
|
public static async createAsPaseCommissioner(options: {
|
|
223
178
|
rootCertificateData: RootCertificateManager.Data;
|
|
224
179
|
fabricData: FabricJsonObject;
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
netInterfaceIpv6?: NetInterface;
|
|
180
|
+
scanners: ScannerSet;
|
|
181
|
+
netInterfaces: NetInterfaceSet;
|
|
228
182
|
sessionClosedCallback?: (peerNodeId: NodeId) => void;
|
|
229
183
|
}): Promise<MatterController> {
|
|
230
|
-
const {
|
|
231
|
-
rootCertificateData,
|
|
232
|
-
fabricData,
|
|
233
|
-
mdnsScanner,
|
|
234
|
-
netInterfaceIpv4,
|
|
235
|
-
netInterfaceIpv6,
|
|
236
|
-
sessionClosedCallback,
|
|
237
|
-
} = options;
|
|
238
|
-
|
|
239
|
-
// Verify BLE initialization
|
|
240
|
-
try {
|
|
241
|
-
Ble.get();
|
|
242
|
-
} catch (error) {
|
|
243
|
-
NoProviderError.accept(error);
|
|
184
|
+
const { rootCertificateData, fabricData, scanners, netInterfaces, sessionClosedCallback } = options;
|
|
244
185
|
|
|
245
|
-
|
|
186
|
+
// Verify an appropriate network interface is available
|
|
187
|
+
if (!netInterfaces.hasInterfaceFor(ChannelType.BLE)) {
|
|
188
|
+
if (!scanners.hasScannerFor(ChannelType.UDP) || !netInterfaces.hasInterfaceFor(ChannelType.UDP, "::")) {
|
|
246
189
|
throw new ImplementationError(
|
|
247
190
|
"Ble must be initialized to create a Sub Commissioner without an IP network!",
|
|
248
191
|
);
|
|
@@ -263,12 +206,10 @@ export class MatterController implements SessionContext {
|
|
|
263
206
|
const controller = new MatterController({
|
|
264
207
|
sessionStorage,
|
|
265
208
|
nodesStorage,
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
netInterfaceIpv6,
|
|
209
|
+
scanners,
|
|
210
|
+
netInterfaces,
|
|
269
211
|
certificateManager,
|
|
270
212
|
fabric,
|
|
271
|
-
adminVendorId: fabric.rootVendorId,
|
|
272
213
|
sessionClosedCallback,
|
|
273
214
|
});
|
|
274
215
|
await controller.construction;
|
|
@@ -276,33 +217,20 @@ export class MatterController implements SessionContext {
|
|
|
276
217
|
}
|
|
277
218
|
|
|
278
219
|
readonly sessionManager: SessionManager;
|
|
220
|
+
private readonly netInterfaces = new NetInterfaceSet();
|
|
279
221
|
private readonly channelManager = new ChannelManager(CONTROLLER_CONNECTIONS_PER_FABRIC_AND_NODE);
|
|
280
222
|
private readonly exchangeManager: ExchangeManager;
|
|
281
|
-
private readonly
|
|
282
|
-
private readonly
|
|
283
|
-
private netInterfaceBle: NetInterface | undefined;
|
|
284
|
-
private bleScanner: Scanner | undefined;
|
|
285
|
-
private readonly commissionedNodes = new Map<NodeId, CommissionedNodeDetails>();
|
|
223
|
+
private readonly peers: PeerSet;
|
|
224
|
+
private readonly commissioner: ControllerCommissioner;
|
|
286
225
|
#construction: Construction<MatterController>;
|
|
287
226
|
|
|
288
227
|
readonly sessionStorage: StorageContext;
|
|
289
228
|
readonly fabricStorage?: StorageContext;
|
|
290
|
-
readonly
|
|
291
|
-
private readonly
|
|
292
|
-
private readonly netInterfaceIpv4: NetInterface | undefined;
|
|
293
|
-
private readonly netInterfaceIpv6: NetInterface | undefined;
|
|
229
|
+
readonly nodesStore: CommissionedNodeStore;
|
|
230
|
+
private readonly scanners: ScannerSet;
|
|
294
231
|
private readonly certificateManager: RootCertificateManager;
|
|
295
232
|
private readonly fabric: Fabric;
|
|
296
|
-
private readonly adminVendorId: VendorId;
|
|
297
233
|
private readonly sessionClosedCallback?: (peerNodeId: NodeId) => void;
|
|
298
|
-
readonly #runningNodeDiscoveries = new Map<
|
|
299
|
-
NodeId,
|
|
300
|
-
{
|
|
301
|
-
type: NodeDiscoveryType;
|
|
302
|
-
promises?: (() => Promise<MessageChannel>)[];
|
|
303
|
-
timer?: Timer;
|
|
304
|
-
}
|
|
305
|
-
>();
|
|
306
234
|
|
|
307
235
|
get construction() {
|
|
308
236
|
return this.#construction;
|
|
@@ -312,68 +240,75 @@ export class MatterController implements SessionContext {
|
|
|
312
240
|
sessionStorage: StorageContext;
|
|
313
241
|
fabricStorage?: StorageContext;
|
|
314
242
|
nodesStorage: StorageContext;
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
netInterfaceIpv6?: NetInterface;
|
|
243
|
+
scanners: ScannerSet;
|
|
244
|
+
netInterfaces: NetInterfaceSet;
|
|
318
245
|
certificateManager: RootCertificateManager;
|
|
319
246
|
fabric: Fabric;
|
|
320
|
-
adminVendorId: VendorId;
|
|
321
247
|
sessionClosedCallback?: (peerNodeId: NodeId) => void;
|
|
322
248
|
}) {
|
|
323
249
|
const {
|
|
324
250
|
sessionStorage,
|
|
325
251
|
fabricStorage,
|
|
326
252
|
nodesStorage,
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
netInterfaceIpv6,
|
|
253
|
+
scanners,
|
|
254
|
+
netInterfaces,
|
|
330
255
|
certificateManager,
|
|
331
256
|
fabric,
|
|
332
257
|
sessionClosedCallback,
|
|
333
|
-
adminVendorId,
|
|
334
258
|
} = options;
|
|
335
259
|
this.sessionStorage = sessionStorage;
|
|
336
260
|
this.fabricStorage = fabricStorage;
|
|
337
|
-
this.
|
|
338
|
-
this.
|
|
339
|
-
this.netInterfaceIpv4 = netInterfaceIpv4;
|
|
340
|
-
this.netInterfaceIpv6 = netInterfaceIpv6;
|
|
261
|
+
this.scanners = scanners;
|
|
262
|
+
this.netInterfaces = netInterfaces;
|
|
341
263
|
this.certificateManager = certificateManager;
|
|
342
264
|
this.fabric = fabric;
|
|
343
265
|
this.sessionClosedCallback = sessionClosedCallback;
|
|
344
|
-
this.adminVendorId = adminVendorId;
|
|
345
266
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
267
|
+
const fabricManager = new FabricManager();
|
|
268
|
+
fabricManager.addFabric(fabric);
|
|
269
|
+
|
|
270
|
+
this.sessionManager = new SessionManager({
|
|
271
|
+
fabrics: fabricManager,
|
|
272
|
+
storage: sessionStorage,
|
|
273
|
+
parameters: {
|
|
274
|
+
maxPathsPerInvoke: CONTROLLER_MAX_PATHS_PER_INVOKE,
|
|
275
|
+
},
|
|
276
|
+
});
|
|
277
|
+
this.sessionManager.sessions.deleted.on(async session => {
|
|
352
278
|
this.sessionClosedCallback?.(session.peerNodeId);
|
|
353
279
|
});
|
|
354
280
|
|
|
355
|
-
this.exchangeManager = new ExchangeManager(
|
|
281
|
+
this.exchangeManager = new ExchangeManager({
|
|
282
|
+
sessionManager: this.sessionManager,
|
|
283
|
+
channelManager: this.channelManager,
|
|
284
|
+
transportInterfaces: this.netInterfaces,
|
|
285
|
+
});
|
|
356
286
|
this.exchangeManager.addProtocolHandler(new StatusReportOnlySecureChannelProtocol());
|
|
357
287
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
}
|
|
361
|
-
if (netInterfaceIpv6 !== undefined) {
|
|
362
|
-
this.addTransportInterface(netInterfaceIpv6);
|
|
363
|
-
}
|
|
288
|
+
// Adapts the historical storage format for MatterController to OperationalPeer objects
|
|
289
|
+
this.nodesStore = new CommissionedNodeStore(nodesStorage, fabric);
|
|
364
290
|
|
|
365
|
-
this
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
291
|
+
this.nodesStore.peers = this.peers = new PeerSet({
|
|
292
|
+
sessions: this.sessionManager,
|
|
293
|
+
channels: this.channelManager,
|
|
294
|
+
exchanges: this.exchangeManager,
|
|
295
|
+
scanners: this.scanners,
|
|
296
|
+
netInterfaces: this.netInterfaces,
|
|
297
|
+
store: this.nodesStore,
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
this.commissioner = new ControllerCommissioner({
|
|
301
|
+
peers: this.peers,
|
|
302
|
+
scanners: this.scanners,
|
|
303
|
+
netInterfaces: this.netInterfaces,
|
|
304
|
+
exchanges: this.exchangeManager,
|
|
305
|
+
sessions: this.sessionManager,
|
|
306
|
+
certificates: this.certificateManager,
|
|
307
|
+
});
|
|
375
308
|
|
|
376
|
-
|
|
309
|
+
this.#construction = Construction(this, async () => {
|
|
310
|
+
await this.peers.construction.ready;
|
|
311
|
+
await this.sessionManager.construction.ready;
|
|
377
312
|
});
|
|
378
313
|
}
|
|
379
314
|
|
|
@@ -393,53 +328,14 @@ export class MatterController implements SessionContext {
|
|
|
393
328
|
return [this.fabric];
|
|
394
329
|
}
|
|
395
330
|
|
|
396
|
-
/** Our own client/controller session parameters. */
|
|
397
|
-
get sessionParameters(): SessionParameters {
|
|
398
|
-
return {
|
|
399
|
-
idleIntervalMs: SESSION_IDLE_INTERVAL_MS,
|
|
400
|
-
activeIntervalMs: SESSION_ACTIVE_INTERVAL_MS,
|
|
401
|
-
activeThresholdMs: SESSION_ACTIVE_THRESHOLD_MS,
|
|
402
|
-
dataModelRevision: Specification.DATA_MODEL_REVISION,
|
|
403
|
-
interactionModelRevision: Specification.INTERACTION_MODEL_REVISION,
|
|
404
|
-
specificationVersion: Specification.SPECIFICATION_VERSION,
|
|
405
|
-
maxPathsPerInvoke: CONTROLLER_MAX_PATHS_PER_INVOKE,
|
|
406
|
-
};
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
public addTransportInterface(netInterface: NetInterface) {
|
|
410
|
-
this.exchangeManager.addTransportInterface(netInterface);
|
|
411
|
-
}
|
|
412
|
-
|
|
413
331
|
public collectScanners(
|
|
414
332
|
discoveryCapabilities: TypeFromPartialBitSchema<typeof DiscoveryCapabilitiesBitmap> = { onIpNetwork: true },
|
|
415
333
|
) {
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
if (discoveryCapabilities.ble) {
|
|
423
|
-
if (this.bleScanner === undefined) {
|
|
424
|
-
let ble: Ble;
|
|
425
|
-
try {
|
|
426
|
-
ble = Ble.get();
|
|
427
|
-
this.netInterfaceBle = ble.getBleCentralInterface();
|
|
428
|
-
this.addTransportInterface(this.netInterfaceBle);
|
|
429
|
-
|
|
430
|
-
this.bleScanner = ble.getBleScanner();
|
|
431
|
-
} catch (error) {
|
|
432
|
-
NoProviderError.accept(error);
|
|
433
|
-
|
|
434
|
-
logger.warn("BLE is not supported on this platform. The device to commission might not be found!");
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
// If we have an BLE Scanner then we use it
|
|
438
|
-
if (this.bleScanner !== undefined) {
|
|
439
|
-
scannersToUse.push(this.bleScanner);
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
return scannersToUse;
|
|
334
|
+
// Note we always scan via MDNS if available
|
|
335
|
+
return this.scanners.filter(
|
|
336
|
+
scanner =>
|
|
337
|
+
scanner.type === ChannelType.UDP || (discoveryCapabilities.ble && scanner.type === ChannelType.BLE),
|
|
338
|
+
);
|
|
443
339
|
}
|
|
444
340
|
|
|
445
341
|
/**
|
|
@@ -457,259 +353,35 @@ export class MatterController implements SessionContext {
|
|
|
457
353
|
options: NodeCommissioningOptions,
|
|
458
354
|
completeCommissioningCallback?: (peerNodeId: NodeId, discoveryData?: DiscoveryData) => Promise<boolean>,
|
|
459
355
|
): Promise<NodeId> {
|
|
460
|
-
const {
|
|
461
|
-
commissioning
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
passcode,
|
|
467
|
-
} = options;
|
|
468
|
-
const commissionableDevice =
|
|
469
|
-
"commissionableDevice" in options.discovery ? options.discovery.commissionableDevice : undefined;
|
|
470
|
-
let {
|
|
471
|
-
discovery: { discoveryCapabilities = {}, knownAddress },
|
|
472
|
-
} = options;
|
|
473
|
-
let identifierData = "identifierData" in options.discovery ? options.discovery.identifierData : {};
|
|
474
|
-
|
|
475
|
-
if (this.mdnsScanner !== undefined && this.netInterfaceIpv6 !== undefined) {
|
|
476
|
-
discoveryCapabilities.onIpNetwork = true; // We always discover on network as defined by specs
|
|
477
|
-
}
|
|
478
|
-
if (commissionableDevice !== undefined) {
|
|
479
|
-
let { addresses } = commissionableDevice;
|
|
480
|
-
if (discoveryCapabilities.ble === true) {
|
|
481
|
-
discoveryCapabilities = { onIpNetwork: true, ble: addresses.some(address => address.type === "ble") };
|
|
482
|
-
} else if (discoveryCapabilities.onIpNetwork === true) {
|
|
483
|
-
// do not use BLE if not specified, even if existing
|
|
484
|
-
addresses = addresses.filter(address => address.type !== "ble");
|
|
485
|
-
}
|
|
486
|
-
addresses.sort(a => (a.type === "udp" ? -1 : 1)); // Sort addresses to use UDP first
|
|
487
|
-
knownAddress = addresses[0];
|
|
488
|
-
if ("instanceId" in commissionableDevice && commissionableDevice.instanceId !== undefined) {
|
|
489
|
-
// it is an UDP discovery
|
|
490
|
-
identifierData = { instanceId: commissionableDevice.instanceId as string };
|
|
491
|
-
} else {
|
|
492
|
-
identifierData = { longDiscriminator: commissionableDevice.D };
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
const scannersToUse = this.collectScanners(discoveryCapabilities);
|
|
497
|
-
|
|
498
|
-
logger.info(
|
|
499
|
-
`Commissioning device with identifier ${Logger.toJSON(identifierData)} and ${
|
|
500
|
-
scannersToUse.length
|
|
501
|
-
} scanners and knownAddress ${Logger.toJSON(knownAddress)}`,
|
|
502
|
-
);
|
|
503
|
-
|
|
504
|
-
// If we have a known address we try this first before we discover the device
|
|
505
|
-
let paseSecureChannel: MessageChannel | undefined;
|
|
506
|
-
let discoveryData: DiscoveryData | undefined;
|
|
356
|
+
const commissioningOptions: PeerCommissioningOptions = {
|
|
357
|
+
...options.commissioning,
|
|
358
|
+
fabric: this.fabric,
|
|
359
|
+
discovery: options.discovery,
|
|
360
|
+
passcode: options.passcode,
|
|
361
|
+
};
|
|
507
362
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
}
|
|
363
|
+
if (completeCommissioningCallback) {
|
|
364
|
+
commissioningOptions.performCaseCommissioning = async (peerAddress, discoveryData) => {
|
|
365
|
+
const result = await completeCommissioningCallback(peerAddress.nodeId, discoveryData);
|
|
366
|
+
if (!result) {
|
|
367
|
+
throw new RetransmissionLimitReachedError("Device could not be discovered");
|
|
368
|
+
}
|
|
369
|
+
};
|
|
515
370
|
}
|
|
516
|
-
if (paseSecureChannel === undefined) {
|
|
517
|
-
const discoveredDevices = await ControllerDiscovery.discoverDeviceAddressesByIdentifier(
|
|
518
|
-
scannersToUse,
|
|
519
|
-
identifierData,
|
|
520
|
-
timeoutSeconds,
|
|
521
|
-
);
|
|
522
371
|
|
|
523
|
-
|
|
524
|
-
discoveredDevices,
|
|
525
|
-
NoResponseTimeoutError,
|
|
526
|
-
async () =>
|
|
527
|
-
scannersToUse.flatMap(scanner => scanner.getDiscoveredCommissionableDevices(identifierData)),
|
|
528
|
-
async (address, device) => {
|
|
529
|
-
const channel = await this.initializePaseSecureChannel(address, passcode, device);
|
|
530
|
-
discoveryData = device;
|
|
531
|
-
return channel;
|
|
532
|
-
},
|
|
533
|
-
);
|
|
372
|
+
const address = await this.commissioner.commission(commissioningOptions);
|
|
534
373
|
|
|
535
|
-
|
|
536
|
-
paseSecureChannel = result;
|
|
537
|
-
}
|
|
374
|
+
await this.fabricStorage?.set("fabric", this.fabric.toStorageObject());
|
|
538
375
|
|
|
539
|
-
return
|
|
540
|
-
paseSecureChannel,
|
|
541
|
-
commissioningOptions,
|
|
542
|
-
discoveryData,
|
|
543
|
-
completeCommissioningCallback,
|
|
544
|
-
);
|
|
376
|
+
return address.nodeId;
|
|
545
377
|
}
|
|
546
378
|
|
|
547
379
|
async disconnect(nodeId: NodeId) {
|
|
548
|
-
|
|
549
|
-
await this.channelManager.removeAllNodeChannels(this.fabric, nodeId);
|
|
380
|
+
return this.peers.disconnect(this.fabric.addressOf(nodeId));
|
|
550
381
|
}
|
|
551
382
|
|
|
552
383
|
async removeNode(nodeId: NodeId) {
|
|
553
|
-
|
|
554
|
-
await this.sessionManager.removeAllSessionsForNode(nodeId);
|
|
555
|
-
await this.sessionManager.removeResumptionRecord(nodeId);
|
|
556
|
-
await this.channelManager.removeAllNodeChannels(this.fabric, nodeId);
|
|
557
|
-
this.commissionedNodes.delete(nodeId);
|
|
558
|
-
await this.storeCommissionedNodes();
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
/**
|
|
562
|
-
* Method to start commission process with a PASE pairing.
|
|
563
|
-
* If this not successful and throws an RetransmissionLimitReachedError the address is invalid or the passcode
|
|
564
|
-
* is wrong.
|
|
565
|
-
*/
|
|
566
|
-
private async initializePaseSecureChannel(
|
|
567
|
-
address: ServerAddress,
|
|
568
|
-
passcode: number,
|
|
569
|
-
device?: CommissionableDevice,
|
|
570
|
-
): Promise<MessageChannel> {
|
|
571
|
-
let paseChannel: Channel<Uint8Array>;
|
|
572
|
-
if (device !== undefined) {
|
|
573
|
-
logger.info(`Commissioning device`, MdnsScanner.discoveryDataDiagnostics(device));
|
|
574
|
-
}
|
|
575
|
-
if (address.type === "udp") {
|
|
576
|
-
const { ip } = address;
|
|
577
|
-
|
|
578
|
-
const isIpv6Address = isIPv6(ip);
|
|
579
|
-
const paseInterface = isIpv6Address ? this.netInterfaceIpv6 : this.netInterfaceIpv4;
|
|
580
|
-
if (paseInterface === undefined) {
|
|
581
|
-
// mainly IPv6 address when IPv4 is disabled
|
|
582
|
-
throw new PairRetransmissionLimitReachedError(
|
|
583
|
-
`IPv${isIpv6Address ? "6" : "4"} interface not initialized. Cannot use ${ip} for commissioning.`,
|
|
584
|
-
);
|
|
585
|
-
}
|
|
586
|
-
paseChannel = await paseInterface.openChannel(address);
|
|
587
|
-
} else {
|
|
588
|
-
if (this.netInterfaceBle === undefined) {
|
|
589
|
-
throw new PairRetransmissionLimitReachedError(
|
|
590
|
-
`BLE interface not initialized. Cannot use ${address.peripheralAddress} for commissioning.`,
|
|
591
|
-
);
|
|
592
|
-
}
|
|
593
|
-
// TODO Have a Timeout mechanism here for connections
|
|
594
|
-
paseChannel = await this.netInterfaceBle.openChannel(address);
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
// Do PASE paring
|
|
598
|
-
const unsecureSession = this.sessionManager.createUnsecureSession({
|
|
599
|
-
// Use the session parameters from MDNS announcements when available and rest is assumed to be fallbacks
|
|
600
|
-
sessionParameters: {
|
|
601
|
-
idleIntervalMs: device?.SII,
|
|
602
|
-
activeIntervalMs: device?.SAI,
|
|
603
|
-
activeThresholdMs: device?.SAT,
|
|
604
|
-
},
|
|
605
|
-
isInitiator: true,
|
|
606
|
-
});
|
|
607
|
-
const paseUnsecureMessageChannel = new MessageChannel(paseChannel, unsecureSession);
|
|
608
|
-
const paseExchange = this.exchangeManager.initiateExchangeWithChannel(
|
|
609
|
-
paseUnsecureMessageChannel,
|
|
610
|
-
SECURE_CHANNEL_PROTOCOL_ID,
|
|
611
|
-
);
|
|
612
|
-
|
|
613
|
-
let paseSecureSession;
|
|
614
|
-
try {
|
|
615
|
-
paseSecureSession = await this.paseClient.pair(this, paseExchange, passcode);
|
|
616
|
-
} catch (e) {
|
|
617
|
-
// Close the exchange and rethrow
|
|
618
|
-
await paseExchange.close();
|
|
619
|
-
throw e;
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
await unsecureSession.destroy();
|
|
623
|
-
return new MessageChannel(paseChannel, paseSecureSession);
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
/**
|
|
627
|
-
* Method to commission a device with a PASE secure channel. It returns the NodeId of the commissioned device on
|
|
628
|
-
* success.
|
|
629
|
-
*/
|
|
630
|
-
private async commissionDevice(
|
|
631
|
-
paseSecureMessageChannel: MessageChannel,
|
|
632
|
-
commissioningOptions: ControllerCommissioningOptions,
|
|
633
|
-
discoveryData?: DiscoveryData,
|
|
634
|
-
completeCommissioningCallback?: (peerNodeId: NodeId, discoveryData?: DiscoveryData) => Promise<boolean>,
|
|
635
|
-
): Promise<NodeId> {
|
|
636
|
-
// TODO: Create the fabric only when needed before commissioning (to do when refactoring MatterController away)
|
|
637
|
-
// TODO also move certificateManager and other parts into that class to get rid of them here
|
|
638
|
-
// TODO Depending on the Error type during commissioning we can do a retry ...
|
|
639
|
-
/*
|
|
640
|
-
Whenever the Fail-Safe timer is armed, Commissioners and Administrators SHALL NOT consider any cluster
|
|
641
|
-
operation to have timed-out before waiting at least 30 seconds for a valid response from the cluster server.
|
|
642
|
-
Some commands and attributes with complex side-effects MAY require longer and have specific timing requirements
|
|
643
|
-
stated in their respective cluster specification.
|
|
644
|
-
|
|
645
|
-
In concurrent connection commissioning flow, the failure of any of the steps 2 through 10 SHALL result in the
|
|
646
|
-
Commissioner and Commissionee returning to step 2 (device discovery and commissioning channel establishment) and
|
|
647
|
-
repeating each step. The failure of any of the steps 11 through 15 in concurrent connection commissioning flow
|
|
648
|
-
SHALL result in the Commissioner and Commissionee returning to step 11 (configuration of operational network
|
|
649
|
-
information). In the case of failure of any of the steps 11 through 15 in concurrent connection commissioning
|
|
650
|
-
flow, the Commissioner and Commissionee SHALL reuse the existing PASE-derived encryption keys over the
|
|
651
|
-
commissioning channel and all steps up to and including step 10 are considered to have been successfully
|
|
652
|
-
completed.
|
|
653
|
-
In non-concurrent connection commissioning flow, the failure of any of the steps 2 through 15 SHALL result in
|
|
654
|
-
the Commissioner and Commissionee returning to step 2 (device discovery and commissioning channel establishment)
|
|
655
|
-
and repeating each step.
|
|
656
|
-
|
|
657
|
-
Commissioners that need to restart from step 2 MAY immediately expire the fail-safe by invoking the ArmFailSafe
|
|
658
|
-
command with an ExpiryLengthSeconds field set to 0. Otherwise, Commissioners will need to wait until the current
|
|
659
|
-
fail-safe timer has expired for the Commissionee to begin accepting PASE again.
|
|
660
|
-
In both concurrent connection commissioning flow and non-concurrent connection commissioning flow, the
|
|
661
|
-
Commissionee SHALL exit Commissioning Mode after 20 failed attempts.
|
|
662
|
-
*/
|
|
663
|
-
|
|
664
|
-
const peerNodeId = commissioningOptions.nodeId ?? NodeId.randomOperationalNodeId();
|
|
665
|
-
const commissioningManager = new ControllerCommissioner(
|
|
666
|
-
// Use the created secure session to do the commissioning
|
|
667
|
-
new InteractionClient(new ExchangeProvider(this.exchangeManager, paseSecureMessageChannel), peerNodeId),
|
|
668
|
-
this.certificateManager,
|
|
669
|
-
this.fabric,
|
|
670
|
-
commissioningOptions,
|
|
671
|
-
peerNodeId,
|
|
672
|
-
this.adminVendorId,
|
|
673
|
-
async () => {
|
|
674
|
-
// TODO Right now we always close after step 12 because we do not check for commissioning flow requirements
|
|
675
|
-
/*
|
|
676
|
-
In concurrent connection commissioning flow the commissioning channel SHALL terminate after
|
|
677
|
-
successful step 15 (CommissioningComplete command invocation). In non-concurrent connection
|
|
678
|
-
commissioning flow the commissioning channel SHALL terminate after successful step 12 (trigger
|
|
679
|
-
joining of operational network at Commissionee). The PASE-derived encryption keys SHALL be deleted
|
|
680
|
-
when commissioning channel terminates. The PASE session SHALL be terminated by both Commissioner and
|
|
681
|
-
Commissionee once the CommissioningComplete command is received by the Commissionee.
|
|
682
|
-
*/
|
|
683
|
-
await paseSecureMessageChannel.close(); // We reconnect using Case, so close PASE connection
|
|
684
|
-
|
|
685
|
-
if (completeCommissioningCallback !== undefined) {
|
|
686
|
-
if (!(await completeCommissioningCallback(peerNodeId, discoveryData))) {
|
|
687
|
-
throw new RetransmissionLimitReachedError("Device could not be discovered");
|
|
688
|
-
}
|
|
689
|
-
throw new CommissioningSuccessfullyFinished();
|
|
690
|
-
}
|
|
691
|
-
// Look for the device broadcast over MDNS and do CASE pairing
|
|
692
|
-
return await this.connect(peerNodeId, {
|
|
693
|
-
discoveryType: NodeDiscoveryType.TimedDiscovery,
|
|
694
|
-
timeoutSeconds: 120,
|
|
695
|
-
discoveryData,
|
|
696
|
-
}); // Wait maximum 120s to find the operational device for commissioning process
|
|
697
|
-
},
|
|
698
|
-
);
|
|
699
|
-
|
|
700
|
-
try {
|
|
701
|
-
await commissioningManager.executeCommissioning();
|
|
702
|
-
} catch (error) {
|
|
703
|
-
if (this.commissionedNodes.has(peerNodeId)) {
|
|
704
|
-
// We might have added data for an operational address that we need to cleanup
|
|
705
|
-
this.commissionedNodes.delete(peerNodeId);
|
|
706
|
-
}
|
|
707
|
-
throw error;
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
await this.fabricStorage?.set("fabric", this.fabric.toStorageObject());
|
|
711
|
-
|
|
712
|
-
return peerNodeId;
|
|
384
|
+
return this.peers.delete(this.fabric.addressOf(nodeId));
|
|
713
385
|
}
|
|
714
386
|
|
|
715
387
|
/**
|
|
@@ -731,354 +403,45 @@ export class MatterController implements SessionContext {
|
|
|
731
403
|
useExtendedFailSafeMessageResponseTimeout: true,
|
|
732
404
|
});
|
|
733
405
|
if (errorCode !== GeneralCommissioning.CommissioningError.Ok) {
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
this.commissionedNodes.delete(peerNodeId);
|
|
737
|
-
}
|
|
406
|
+
// We might have added data for an operational address that we need to cleanup
|
|
407
|
+
await this.peers.delete(this.fabric.addressOf(peerNodeId));
|
|
738
408
|
throw new CommissioningError(`Commission error on commissioningComplete: ${errorCode}, ${debugText}`);
|
|
739
409
|
}
|
|
740
410
|
await this.fabricStorage?.set("fabric", this.fabric.toStorageObject());
|
|
741
411
|
}
|
|
742
412
|
|
|
743
|
-
handleResubmissionStarted(peerNodeId: NodeId) {
|
|
744
|
-
if (this.#runningNodeDiscoveries.has(peerNodeId)) {
|
|
745
|
-
// We already discover for this node, so we do not need to start a new discovery
|
|
746
|
-
return;
|
|
747
|
-
}
|
|
748
|
-
this.#runningNodeDiscoveries.set(peerNodeId, { type: NodeDiscoveryType.RetransmissionDiscovery });
|
|
749
|
-
this.mdnsScanner
|
|
750
|
-
?.findOperationalDevice(this.fabric, peerNodeId, RETRANSMISSION_DISCOVERY_TIMEOUT_MS, true)
|
|
751
|
-
.catch(error => {
|
|
752
|
-
logger.error(`Failed to discover device ${peerNodeId} after resubmission started.`, error);
|
|
753
|
-
})
|
|
754
|
-
.finally(() => {
|
|
755
|
-
if (this.#runningNodeDiscoveries.get(peerNodeId)?.type === NodeDiscoveryType.RetransmissionDiscovery) {
|
|
756
|
-
this.#runningNodeDiscoveries.delete(peerNodeId);
|
|
757
|
-
}
|
|
758
|
-
});
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
private async reconnectKnownAddress(
|
|
762
|
-
peerNodeId: NodeId,
|
|
763
|
-
operationalAddress: ServerAddressIp,
|
|
764
|
-
discoveryData?: DiscoveryData,
|
|
765
|
-
expectedProcessingTimeMs?: number,
|
|
766
|
-
): Promise<MessageChannel | undefined> {
|
|
767
|
-
const { ip, port } = operationalAddress;
|
|
768
|
-
try {
|
|
769
|
-
logger.debug(
|
|
770
|
-
`Resume device connection to configured server at ${ip}:${port}${expectedProcessingTimeMs !== undefined ? ` with expected processing time of ${expectedProcessingTimeMs}ms` : ""} ...`,
|
|
771
|
-
);
|
|
772
|
-
const channel = await this.pair(peerNodeId, operationalAddress, discoveryData, expectedProcessingTimeMs);
|
|
773
|
-
await this.setOperationalDeviceData(peerNodeId, operationalAddress);
|
|
774
|
-
return channel;
|
|
775
|
-
} catch (error) {
|
|
776
|
-
if (error instanceof NoResponseTimeoutError) {
|
|
777
|
-
logger.debug(
|
|
778
|
-
`Failed to resume connection to node ${peerNodeId} connection with ${ip}:${port}, discover the device ...`,
|
|
779
|
-
error,
|
|
780
|
-
);
|
|
781
|
-
// We remove all sessions, this also informs the PairedNode class
|
|
782
|
-
await this.sessionManager.removeAllSessionsForNode(peerNodeId);
|
|
783
|
-
return undefined;
|
|
784
|
-
} else {
|
|
785
|
-
throw error;
|
|
786
|
-
}
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
private async connectOrDiscoverNode(
|
|
791
|
-
peerNodeId: NodeId,
|
|
792
|
-
operationalAddress?: ServerAddressIp,
|
|
793
|
-
discoveryOptions: DiscoveryOptions = {},
|
|
794
|
-
) {
|
|
795
|
-
const {
|
|
796
|
-
discoveryType: requestedDiscoveryType = NodeDiscoveryType.FullDiscovery,
|
|
797
|
-
timeoutSeconds,
|
|
798
|
-
discoveryData = this.commissionedNodes.get(peerNodeId)?.discoveryData,
|
|
799
|
-
} = discoveryOptions;
|
|
800
|
-
if (timeoutSeconds !== undefined && requestedDiscoveryType !== NodeDiscoveryType.TimedDiscovery) {
|
|
801
|
-
throw new ImplementationError("Cannot set timeout without timed discovery.");
|
|
802
|
-
}
|
|
803
|
-
if (requestedDiscoveryType === NodeDiscoveryType.RetransmissionDiscovery) {
|
|
804
|
-
throw new ImplementationError("Cannot set retransmission discovery type.");
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
if (this.mdnsScanner === undefined) {
|
|
808
|
-
throw new ImplementationError("Cannot discover device without mDNS scanner.");
|
|
809
|
-
}
|
|
810
|
-
const mdnsScanner = this.mdnsScanner;
|
|
811
|
-
|
|
812
|
-
const existingDiscoveryDetails = this.#runningNodeDiscoveries.get(peerNodeId) ?? {
|
|
813
|
-
type: NodeDiscoveryType.None,
|
|
814
|
-
};
|
|
815
|
-
|
|
816
|
-
// If we currently run another "lower" retransmission type we cancel it
|
|
817
|
-
if (
|
|
818
|
-
existingDiscoveryDetails.type !== NodeDiscoveryType.None &&
|
|
819
|
-
existingDiscoveryDetails.type < requestedDiscoveryType
|
|
820
|
-
) {
|
|
821
|
-
mdnsScanner.cancelOperationalDeviceDiscovery(this.fabric, peerNodeId);
|
|
822
|
-
this.#runningNodeDiscoveries.delete(peerNodeId);
|
|
823
|
-
existingDiscoveryDetails.type = NodeDiscoveryType.None;
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
const { type: runningDiscoveryType, promises } = existingDiscoveryDetails;
|
|
827
|
-
|
|
828
|
-
// If we have a last known address try to reach the device directly when we are not already discovering
|
|
829
|
-
// In worst case parallel cases we do this step twice, but that's ok
|
|
830
|
-
if (
|
|
831
|
-
operationalAddress !== undefined &&
|
|
832
|
-
(runningDiscoveryType === NodeDiscoveryType.None || requestedDiscoveryType === NodeDiscoveryType.None)
|
|
833
|
-
) {
|
|
834
|
-
const directReconnection = await this.reconnectKnownAddress(peerNodeId, operationalAddress, discoveryData);
|
|
835
|
-
if (directReconnection !== undefined) {
|
|
836
|
-
return directReconnection;
|
|
837
|
-
}
|
|
838
|
-
if (requestedDiscoveryType === NodeDiscoveryType.None) {
|
|
839
|
-
throw new DiscoveryError(`Node ${peerNodeId} is not reachable right now.`);
|
|
840
|
-
}
|
|
841
|
-
}
|
|
842
|
-
|
|
843
|
-
if (promises !== undefined) {
|
|
844
|
-
if (runningDiscoveryType > requestedDiscoveryType) {
|
|
845
|
-
// We already run a "longer" discovery, so we know it is unreachable for now
|
|
846
|
-
throw new DiscoveryError(
|
|
847
|
-
`Node ${peerNodeId} is not reachable right now and discovery already running.`,
|
|
848
|
-
);
|
|
849
|
-
} else {
|
|
850
|
-
// If we are already discovering this node, so we reuse promises
|
|
851
|
-
return await anyPromise(promises);
|
|
852
|
-
}
|
|
853
|
-
}
|
|
854
|
-
|
|
855
|
-
const discoveryPromises = new Array<() => Promise<MessageChannel>>();
|
|
856
|
-
let reconnectionPollingTimer: Timer | undefined;
|
|
857
|
-
|
|
858
|
-
if (operationalAddress !== undefined) {
|
|
859
|
-
// Additionally to general discovery we also try to poll the formerly known operational address
|
|
860
|
-
if (requestedDiscoveryType === NodeDiscoveryType.FullDiscovery) {
|
|
861
|
-
const { promise, resolver, rejecter } = createPromise<MessageChannel>();
|
|
862
|
-
|
|
863
|
-
reconnectionPollingTimer = Time.getPeriodicTimer(
|
|
864
|
-
"Controller reconnect",
|
|
865
|
-
RECONNECTION_POLLING_INTERVAL_MS,
|
|
866
|
-
async () => {
|
|
867
|
-
try {
|
|
868
|
-
logger.debug(`Polling for device at ${serverAddressToString(operationalAddress)} ...`);
|
|
869
|
-
const result = await this.reconnectKnownAddress(
|
|
870
|
-
peerNodeId,
|
|
871
|
-
operationalAddress,
|
|
872
|
-
discoveryData,
|
|
873
|
-
);
|
|
874
|
-
if (result !== undefined && reconnectionPollingTimer?.isRunning) {
|
|
875
|
-
reconnectionPollingTimer?.stop();
|
|
876
|
-
mdnsScanner.cancelOperationalDeviceDiscovery(this.fabric, peerNodeId);
|
|
877
|
-
this.#runningNodeDiscoveries.delete(peerNodeId);
|
|
878
|
-
resolver(result);
|
|
879
|
-
}
|
|
880
|
-
} catch (error) {
|
|
881
|
-
if (reconnectionPollingTimer?.isRunning) {
|
|
882
|
-
reconnectionPollingTimer?.stop();
|
|
883
|
-
mdnsScanner.cancelOperationalDeviceDiscovery(this.fabric, peerNodeId);
|
|
884
|
-
this.#runningNodeDiscoveries.delete(peerNodeId);
|
|
885
|
-
rejecter(error);
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
|
-
},
|
|
889
|
-
).start();
|
|
890
|
-
|
|
891
|
-
discoveryPromises.push(() => promise);
|
|
892
|
-
}
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
discoveryPromises.push(async () => {
|
|
896
|
-
const scanResult = await ControllerDiscovery.discoverOperationalDevice(
|
|
897
|
-
this.fabric,
|
|
898
|
-
peerNodeId,
|
|
899
|
-
mdnsScanner,
|
|
900
|
-
timeoutSeconds,
|
|
901
|
-
timeoutSeconds === undefined,
|
|
902
|
-
);
|
|
903
|
-
const { timer } = this.#runningNodeDiscoveries.get(peerNodeId) ?? {};
|
|
904
|
-
timer?.stop();
|
|
905
|
-
this.#runningNodeDiscoveries.delete(peerNodeId);
|
|
906
|
-
|
|
907
|
-
const { result } = await ControllerDiscovery.iterateServerAddresses(
|
|
908
|
-
[scanResult],
|
|
909
|
-
NoResponseTimeoutError,
|
|
910
|
-
async () => {
|
|
911
|
-
const device = mdnsScanner.getDiscoveredOperationalDevice(this.fabric, peerNodeId);
|
|
912
|
-
return device !== undefined ? [device] : [];
|
|
913
|
-
},
|
|
914
|
-
async (address, device) => {
|
|
915
|
-
const result = await this.pair(peerNodeId, address, device);
|
|
916
|
-
await this.setOperationalDeviceData(peerNodeId, address, {
|
|
917
|
-
...discoveryData,
|
|
918
|
-
...device,
|
|
919
|
-
});
|
|
920
|
-
return result;
|
|
921
|
-
},
|
|
922
|
-
);
|
|
923
|
-
|
|
924
|
-
return result;
|
|
925
|
-
});
|
|
926
|
-
|
|
927
|
-
this.#runningNodeDiscoveries.set(peerNodeId, {
|
|
928
|
-
type: requestedDiscoveryType,
|
|
929
|
-
promises: discoveryPromises,
|
|
930
|
-
timer: reconnectionPollingTimer,
|
|
931
|
-
});
|
|
932
|
-
|
|
933
|
-
return await anyPromise(discoveryPromises).finally(() => this.#runningNodeDiscoveries.delete(peerNodeId));
|
|
934
|
-
}
|
|
935
|
-
|
|
936
|
-
/**
|
|
937
|
-
* Resume a device connection and establish a CASE session that was previously paired with the controller. This
|
|
938
|
-
* method will try to connect to the device using the previously used server address (if set). If that fails, the
|
|
939
|
-
* device is discovered again using its operational instance details.
|
|
940
|
-
* It returns the operational MessageChannel on success.
|
|
941
|
-
*/
|
|
942
|
-
private async resume(peerNodeId: NodeId, discoveryOptions?: DiscoveryOptions) {
|
|
943
|
-
const operationalAddress = this.getLastOperationalAddress(peerNodeId);
|
|
944
|
-
|
|
945
|
-
try {
|
|
946
|
-
return await this.connectOrDiscoverNode(peerNodeId, operationalAddress, discoveryOptions);
|
|
947
|
-
} catch (error) {
|
|
948
|
-
if (
|
|
949
|
-
(error instanceof DiscoveryError || error instanceof NoResponseTimeoutError) &&
|
|
950
|
-
this.commissionedNodes.has(peerNodeId)
|
|
951
|
-
) {
|
|
952
|
-
logger.info(`Resume failed, remove all sessions for node ${peerNodeId}`);
|
|
953
|
-
// We remove all sessions, this also informs the PairedNode class
|
|
954
|
-
await this.sessionManager.removeAllSessionsForNode(peerNodeId);
|
|
955
|
-
}
|
|
956
|
-
throw error;
|
|
957
|
-
}
|
|
958
|
-
}
|
|
959
|
-
|
|
960
|
-
/** Pair with an operational device (already commissioned) and establish a CASE session. */
|
|
961
|
-
private async pair(
|
|
962
|
-
peerNodeId: NodeId,
|
|
963
|
-
operationalServerAddress: ServerAddressIp,
|
|
964
|
-
discoveryData?: DiscoveryData,
|
|
965
|
-
expectedProcessingTimeMs?: number,
|
|
966
|
-
) {
|
|
967
|
-
const { ip, port } = operationalServerAddress;
|
|
968
|
-
// Do CASE pairing
|
|
969
|
-
const isIpv6Address = isIPv6(ip);
|
|
970
|
-
const operationalInterface = isIpv6Address ? this.netInterfaceIpv6 : this.netInterfaceIpv4;
|
|
971
|
-
|
|
972
|
-
if (operationalInterface === undefined) {
|
|
973
|
-
throw new PairRetransmissionLimitReachedError(
|
|
974
|
-
`IPv${
|
|
975
|
-
isIpv6Address ? "6" : "4"
|
|
976
|
-
} interface not initialized for port ${port}. Cannot use ${ip} for pairing.`,
|
|
977
|
-
);
|
|
978
|
-
}
|
|
979
|
-
|
|
980
|
-
const operationalChannel = await operationalInterface.openChannel(operationalServerAddress);
|
|
981
|
-
const { sessionParameters } = this.findResumptionRecordByNodeId(peerNodeId) ?? {};
|
|
982
|
-
const unsecureSession = this.sessionManager.createUnsecureSession({
|
|
983
|
-
// Use the session parameters from MDNS announcements when available and rest is assumed to be fallbacks
|
|
984
|
-
sessionParameters: {
|
|
985
|
-
idleIntervalMs: discoveryData?.SII ?? sessionParameters?.idleIntervalMs,
|
|
986
|
-
activeIntervalMs: discoveryData?.SAI ?? sessionParameters?.activeIntervalMs,
|
|
987
|
-
activeThresholdMs: discoveryData?.SAT ?? sessionParameters?.activeThresholdMs,
|
|
988
|
-
},
|
|
989
|
-
isInitiator: true,
|
|
990
|
-
});
|
|
991
|
-
const operationalUnsecureMessageExchange = new MessageChannel(operationalChannel, unsecureSession);
|
|
992
|
-
let operationalSecureSession;
|
|
993
|
-
try {
|
|
994
|
-
const exchange = this.exchangeManager.initiateExchangeWithChannel(
|
|
995
|
-
operationalUnsecureMessageExchange,
|
|
996
|
-
SECURE_CHANNEL_PROTOCOL_ID,
|
|
997
|
-
);
|
|
998
|
-
|
|
999
|
-
try {
|
|
1000
|
-
operationalSecureSession = await this.caseClient.pair(
|
|
1001
|
-
this,
|
|
1002
|
-
exchange,
|
|
1003
|
-
this.fabric,
|
|
1004
|
-
peerNodeId,
|
|
1005
|
-
expectedProcessingTimeMs,
|
|
1006
|
-
);
|
|
1007
|
-
} catch (e) {
|
|
1008
|
-
await exchange.close();
|
|
1009
|
-
throw e;
|
|
1010
|
-
}
|
|
1011
|
-
} catch (e) {
|
|
1012
|
-
NoResponseTimeoutError.accept(e);
|
|
1013
|
-
|
|
1014
|
-
// Convert error
|
|
1015
|
-
throw new PairRetransmissionLimitReachedError(e.message);
|
|
1016
|
-
}
|
|
1017
|
-
await unsecureSession.destroy();
|
|
1018
|
-
const channel = new MessageChannel(operationalChannel, operationalSecureSession);
|
|
1019
|
-
await this.channelManager.setChannel(this.fabric, peerNodeId, channel);
|
|
1020
|
-
return channel;
|
|
1021
|
-
}
|
|
1022
|
-
|
|
1023
413
|
isCommissioned() {
|
|
1024
|
-
return this.
|
|
414
|
+
return this.peers.size > 0;
|
|
1025
415
|
}
|
|
1026
416
|
|
|
1027
417
|
getCommissionedNodes() {
|
|
1028
|
-
return
|
|
418
|
+
return this.peers.map(peer => peer.address.nodeId);
|
|
1029
419
|
}
|
|
1030
420
|
|
|
1031
421
|
getCommissionedNodesDetails() {
|
|
1032
|
-
return
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
: undefined,
|
|
422
|
+
return this.peers.map(peer => {
|
|
423
|
+
const { address, operationalAddress, discoveryData, basicInformationData } = peer as CommissionedPeer;
|
|
424
|
+
return {
|
|
425
|
+
nodeId: address.nodeId,
|
|
426
|
+
operationalAddress: operationalAddress ? serverAddressToString(operationalAddress) : undefined,
|
|
1038
427
|
advertisedName: discoveryData?.DN,
|
|
1039
428
|
discoveryData,
|
|
1040
429
|
basicInformationData,
|
|
1041
|
-
}),
|
|
1042
|
-
);
|
|
1043
|
-
}
|
|
1044
|
-
|
|
1045
|
-
private async setOperationalDeviceData(
|
|
1046
|
-
nodeId: NodeId,
|
|
1047
|
-
operationalServerAddress: ServerAddressIp,
|
|
1048
|
-
discoveryData?: DiscoveryData,
|
|
1049
|
-
) {
|
|
1050
|
-
const nodeDetails = this.commissionedNodes.get(nodeId) ?? {};
|
|
1051
|
-
nodeDetails.operationalServerAddress = operationalServerAddress;
|
|
1052
|
-
if (discoveryData !== undefined) {
|
|
1053
|
-
nodeDetails.discoveryData = {
|
|
1054
|
-
...nodeDetails.discoveryData,
|
|
1055
|
-
...discoveryData,
|
|
1056
430
|
};
|
|
1057
|
-
}
|
|
1058
|
-
this.commissionedNodes.set(nodeId, nodeDetails);
|
|
1059
|
-
await this.storeCommissionedNodes();
|
|
431
|
+
});
|
|
1060
432
|
}
|
|
1061
433
|
|
|
1062
434
|
async enhanceCommissionedNodeDetails(
|
|
1063
435
|
nodeId: NodeId,
|
|
1064
436
|
data: { basicInformationData: Record<string, SupportedStorageTypes> },
|
|
1065
437
|
) {
|
|
1066
|
-
const nodeDetails = this.
|
|
438
|
+
const nodeDetails = this.peers.get(this.fabric.addressOf(nodeId)) as CommissionedPeer;
|
|
1067
439
|
if (nodeDetails === undefined) {
|
|
1068
440
|
throw new Error(`Node ${nodeId} is not commissioned.`);
|
|
1069
441
|
}
|
|
1070
442
|
const { basicInformationData } = data;
|
|
1071
443
|
nodeDetails.basicInformationData = basicInformationData;
|
|
1072
|
-
this.
|
|
1073
|
-
await this.storeCommissionedNodes();
|
|
1074
|
-
}
|
|
1075
|
-
|
|
1076
|
-
private getLastOperationalAddress(nodeId: NodeId) {
|
|
1077
|
-
return this.commissionedNodes.get(nodeId)?.operationalServerAddress;
|
|
1078
|
-
}
|
|
1079
|
-
|
|
1080
|
-
private async storeCommissionedNodes() {
|
|
1081
|
-
await this.nodesStorage.set("commissionedNodes", Array.from(this.commissionedNodes.entries()));
|
|
444
|
+
await this.nodesStore.save();
|
|
1082
445
|
}
|
|
1083
446
|
|
|
1084
447
|
/**
|
|
@@ -1086,58 +449,7 @@ export class MatterController implements SessionContext {
|
|
|
1086
449
|
* Returns a InteractionClient on success.
|
|
1087
450
|
*/
|
|
1088
451
|
async connect(peerNodeId: NodeId, discoveryOptions: DiscoveryOptions) {
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
let channel: MessageChannel;
|
|
1092
|
-
try {
|
|
1093
|
-
channel = this.channelManager.getChannel(this.fabric, peerNodeId);
|
|
1094
|
-
} catch (error) {
|
|
1095
|
-
NoChannelError.accept(error);
|
|
1096
|
-
|
|
1097
|
-
channel = await this.resume(peerNodeId, discoveryOptions);
|
|
1098
|
-
}
|
|
1099
|
-
return new InteractionClient(
|
|
1100
|
-
new ExchangeProvider(this.exchangeManager, channel, async () => {
|
|
1101
|
-
if (!this.channelManager.hasChannel(this.fabric, peerNodeId)) {
|
|
1102
|
-
throw new RetransmissionLimitReachedError(`Device ${peerNodeId} is currently not reachable.`);
|
|
1103
|
-
}
|
|
1104
|
-
await this.channelManager.removeAllNodeChannels(this.fabric, peerNodeId);
|
|
1105
|
-
|
|
1106
|
-
const discoveredAddresses = this.mdnsScanner?.getDiscoveredOperationalDevice(this.fabric, peerNodeId);
|
|
1107
|
-
const lastKnownAddress = this.getLastOperationalAddress(peerNodeId);
|
|
1108
|
-
|
|
1109
|
-
if (
|
|
1110
|
-
lastKnownAddress !== undefined &&
|
|
1111
|
-
discoveredAddresses !== undefined &&
|
|
1112
|
-
discoveredAddresses.addresses.some(
|
|
1113
|
-
({ ip, port }) => ip === lastKnownAddress.ip && port === lastKnownAddress.port,
|
|
1114
|
-
)
|
|
1115
|
-
) {
|
|
1116
|
-
// We found the same address, so assume somehow cached response because we just tried to connect,
|
|
1117
|
-
// and it failed, so clear list
|
|
1118
|
-
discoveredAddresses.addresses.length = 0;
|
|
1119
|
-
}
|
|
1120
|
-
|
|
1121
|
-
// Try to use first result for one last try before we need to reconnect
|
|
1122
|
-
const operationalAddress = discoveredAddresses?.addresses[0];
|
|
1123
|
-
if (operationalAddress === undefined) {
|
|
1124
|
-
logger.info(
|
|
1125
|
-
`Re-Discovering device failed (no address found), remove all sessions for node ${peerNodeId}`,
|
|
1126
|
-
);
|
|
1127
|
-
// We remove all sessions, this also informs the PairedNode class
|
|
1128
|
-
await this.sessionManager.removeAllSessionsForNode(peerNodeId);
|
|
1129
|
-
throw new RetransmissionLimitReachedError(`No operational address found for node ${peerNodeId}`);
|
|
1130
|
-
}
|
|
1131
|
-
if (
|
|
1132
|
-
(await this.reconnectKnownAddress(peerNodeId, operationalAddress, discoveryData, 2_000)) ===
|
|
1133
|
-
undefined
|
|
1134
|
-
) {
|
|
1135
|
-
throw new RetransmissionLimitReachedError(`Device ${peerNodeId} is not reachable.`);
|
|
1136
|
-
}
|
|
1137
|
-
return this.channelManager.getChannel(this.fabric, peerNodeId);
|
|
1138
|
-
}),
|
|
1139
|
-
peerNodeId,
|
|
1140
|
-
);
|
|
452
|
+
return this.peers.connect(this.fabric.addressOf(peerNodeId), discoveryOptions);
|
|
1141
453
|
}
|
|
1142
454
|
|
|
1143
455
|
async getNextAvailableSessionId() {
|
|
@@ -1149,7 +461,7 @@ export class MatterController implements SessionContext {
|
|
|
1149
461
|
}
|
|
1150
462
|
|
|
1151
463
|
findResumptionRecordByNodeId(nodeId: NodeId) {
|
|
1152
|
-
return this.sessionManager.
|
|
464
|
+
return this.sessionManager.findResumptionRecordByAddress(this.fabric.addressOf(nodeId));
|
|
1153
465
|
}
|
|
1154
466
|
|
|
1155
467
|
async saveResumptionRecord(resumptionRecord: ResumptionRecord) {
|
|
@@ -1161,19 +473,68 @@ export class MatterController implements SessionContext {
|
|
|
1161
473
|
}
|
|
1162
474
|
|
|
1163
475
|
async close() {
|
|
1164
|
-
|
|
1165
|
-
timer?.stop();
|
|
1166
|
-
this.mdnsScanner?.cancelOperationalDeviceDiscovery(this.fabric, nodeId, false); // This ends discovery without triggering promises
|
|
1167
|
-
}
|
|
476
|
+
await this.peers.close();
|
|
1168
477
|
await this.exchangeManager.close();
|
|
1169
478
|
await this.sessionManager.close();
|
|
1170
479
|
await this.channelManager.close();
|
|
1171
|
-
await this.
|
|
1172
|
-
await this.netInterfaceIpv4?.close();
|
|
1173
|
-
await this.netInterfaceIpv6?.close();
|
|
480
|
+
await this.netInterfaces.close();
|
|
1174
481
|
}
|
|
1175
482
|
|
|
1176
483
|
getActiveSessionInformation() {
|
|
1177
484
|
return this.sessionManager.getActiveSessionInformation();
|
|
1178
485
|
}
|
|
1179
486
|
}
|
|
487
|
+
|
|
488
|
+
class CommissionedNodeStore extends PeerStore {
|
|
489
|
+
declare peers: PeerSet;
|
|
490
|
+
|
|
491
|
+
constructor(
|
|
492
|
+
private nodesStorage: StorageContext,
|
|
493
|
+
private fabric: Fabric,
|
|
494
|
+
) {
|
|
495
|
+
super();
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
async loadPeers() {
|
|
499
|
+
if (!(await this.nodesStorage.has("commissionedNodes"))) {
|
|
500
|
+
return [];
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
const commissionedNodes = await this.nodesStorage.get<StoredOperationalPeer[]>("commissionedNodes");
|
|
504
|
+
return commissionedNodes.map(
|
|
505
|
+
([nodeId, { operationalServerAddress, discoveryData, basicInformationData }]) =>
|
|
506
|
+
({
|
|
507
|
+
address: this.fabric.addressOf(nodeId),
|
|
508
|
+
operationalAddress: operationalServerAddress,
|
|
509
|
+
discoveryData,
|
|
510
|
+
basicInformationData,
|
|
511
|
+
}) satisfies CommissionedPeer,
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
async updatePeer() {
|
|
516
|
+
return this.save();
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
async deletePeer() {
|
|
520
|
+
return this.save();
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
async save() {
|
|
524
|
+
await this.nodesStorage.set(
|
|
525
|
+
"commissionedNodes",
|
|
526
|
+
this.peers.map(peer => {
|
|
527
|
+
const {
|
|
528
|
+
address,
|
|
529
|
+
operationalAddress: operationalServerAddress,
|
|
530
|
+
basicInformationData,
|
|
531
|
+
discoveryData,
|
|
532
|
+
} = peer as CommissionedPeer;
|
|
533
|
+
return [
|
|
534
|
+
address.nodeId,
|
|
535
|
+
{ operationalServerAddress, basicInformationData, discoveryData },
|
|
536
|
+
] satisfies StoredOperationalPeer;
|
|
537
|
+
}),
|
|
538
|
+
);
|
|
539
|
+
}
|
|
540
|
+
}
|