node-opcua-server 2.51.0
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/.mocharc.yml +10 -0
- package/LICENSE +20 -0
- package/dist/base_server.d.ts +110 -0
- package/dist/base_server.js +476 -0
- package/dist/base_server.js.map +1 -0
- package/dist/factory.d.ts +10 -0
- package/dist/factory.js +24 -0
- package/dist/factory.js.map +1 -0
- package/dist/history_server_capabilities.d.ts +35 -0
- package/dist/history_server_capabilities.js +44 -0
- package/dist/history_server_capabilities.js.map +1 -0
- package/dist/i_channel_data.d.ts +13 -0
- package/dist/i_channel_data.js +3 -0
- package/dist/i_channel_data.js.map +1 -0
- package/dist/i_register_server_manager.d.ts +16 -0
- package/dist/i_register_server_manager.js +3 -0
- package/dist/i_register_server_manager.js.map +1 -0
- package/dist/i_server_side_publish_engine.d.ts +36 -0
- package/dist/i_server_side_publish_engine.js +50 -0
- package/dist/i_server_side_publish_engine.js.map +1 -0
- package/dist/i_socket_data.d.ts +11 -0
- package/dist/i_socket_data.js +3 -0
- package/dist/i_socket_data.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/dist/monitored_item.d.ts +173 -0
- package/dist/monitored_item.js +1006 -0
- package/dist/monitored_item.js.map +1 -0
- package/dist/node_sampler.d.ts +3 -0
- package/dist/node_sampler.js +76 -0
- package/dist/node_sampler.js.map +1 -0
- package/dist/opcua_server.d.ts +668 -0
- package/dist/opcua_server.js +2407 -0
- package/dist/opcua_server.js.map +1 -0
- package/dist/queue.d.ts +11 -0
- package/dist/queue.js +71 -0
- package/dist/queue.js.map +1 -0
- package/dist/register_server_manager.d.ts +92 -0
- package/dist/register_server_manager.js +574 -0
- package/dist/register_server_manager.js.map +1 -0
- package/dist/register_server_manager_hidden.d.ts +17 -0
- package/dist/register_server_manager_hidden.js +28 -0
- package/dist/register_server_manager_hidden.js.map +1 -0
- package/dist/register_server_manager_mdns_only.d.ts +19 -0
- package/dist/register_server_manager_mdns_only.js +58 -0
- package/dist/register_server_manager_mdns_only.js.map +1 -0
- package/dist/server_capabilities.d.ts +61 -0
- package/dist/server_capabilities.js +109 -0
- package/dist/server_capabilities.js.map +1 -0
- package/dist/server_end_point.d.ts +180 -0
- package/dist/server_end_point.js +825 -0
- package/dist/server_end_point.js.map +1 -0
- package/dist/server_engine.d.ts +311 -0
- package/dist/server_engine.js +1659 -0
- package/dist/server_engine.js.map +1 -0
- package/dist/server_publish_engine.d.ts +109 -0
- package/dist/server_publish_engine.js +531 -0
- package/dist/server_publish_engine.js.map +1 -0
- package/dist/server_publish_engine_for_orphan_subscriptions.d.ts +16 -0
- package/dist/server_publish_engine_for_orphan_subscriptions.js +50 -0
- package/dist/server_publish_engine_for_orphan_subscriptions.js.map +1 -0
- package/dist/server_session.d.ts +176 -0
- package/dist/server_session.js +734 -0
- package/dist/server_session.js.map +1 -0
- package/dist/server_subscription.d.ts +393 -0
- package/dist/server_subscription.js +1313 -0
- package/dist/server_subscription.js.map +1 -0
- package/dist/sessions_compatible_for_transfer.d.ts +2 -0
- package/dist/sessions_compatible_for_transfer.js +36 -0
- package/dist/sessions_compatible_for_transfer.js.map +1 -0
- package/dist/validate_filter.d.ts +5 -0
- package/dist/validate_filter.js +64 -0
- package/dist/validate_filter.js.map +1 -0
- package/package.json +88 -0
- package/source/base_server.ts +617 -0
- package/source/factory.ts +25 -0
- package/source/history_server_capabilities.ts +75 -0
- package/source/i_channel_data.ts +17 -0
- package/source/i_register_server_manager.ts +24 -0
- package/source/i_server_side_publish_engine.ts +77 -0
- package/source/i_socket_data.ts +11 -0
- package/source/index.ts +14 -0
- package/source/monitored_item.ts +1303 -0
- package/source/node_sampler.ts +82 -0
- package/source/opcua_server.ts +3742 -0
- package/source/queue.ts +73 -0
- package/source/register_server_manager.ts +744 -0
- package/source/register_server_manager_hidden.ts +33 -0
- package/source/register_server_manager_mdns_only.ts +69 -0
- package/source/server_capabilities.ts +177 -0
- package/source/server_end_point.ts +1182 -0
- package/source/server_engine.ts +2167 -0
- package/source/server_publish_engine.ts +657 -0
- package/source/server_publish_engine_for_orphan_subscriptions.ts +52 -0
- package/source/server_session.ts +931 -0
- package/source/server_subscription.ts +1792 -0
- package/source/sessions_compatible_for_transfer.ts +33 -0
- package/source/validate_filter.ts +86 -0
- package/test_helpers/create_certificates.js +1 -0
|
@@ -0,0 +1,744 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module node-opcua-server
|
|
3
|
+
*/
|
|
4
|
+
// tslint:disable:no-console
|
|
5
|
+
import * as async from "async";
|
|
6
|
+
import * as chalk from "chalk";
|
|
7
|
+
import { EventEmitter } from "events";
|
|
8
|
+
|
|
9
|
+
import { assert } from "node-opcua-assert";
|
|
10
|
+
import { ErrorCallback, UAString } from "node-opcua-basic-types";
|
|
11
|
+
import {
|
|
12
|
+
coerceLocalizedText,
|
|
13
|
+
LocalizedTextLike,
|
|
14
|
+
LocalizedTextOptions,
|
|
15
|
+
OPCUAClientBase,
|
|
16
|
+
OPCUAClientBaseOptions,
|
|
17
|
+
ResponseCallback
|
|
18
|
+
} from "node-opcua-client";
|
|
19
|
+
import { make_debugLog, checkDebugFlag, make_warningLog } from "node-opcua-debug";
|
|
20
|
+
import { resolveFullyQualifiedDomainName } from "node-opcua-hostname";
|
|
21
|
+
import { coerceSecurityPolicy, MessageSecurityMode, SecurityPolicy } from "node-opcua-secure-channel";
|
|
22
|
+
import {
|
|
23
|
+
RegisterServer2Request,
|
|
24
|
+
RegisterServer2Response,
|
|
25
|
+
RegisterServerRequest,
|
|
26
|
+
RegisterServerResponse
|
|
27
|
+
} from "node-opcua-service-discovery";
|
|
28
|
+
import { ApplicationType, EndpointDescription, MdnsDiscoveryConfiguration, RegisteredServerOptions } from "node-opcua-types";
|
|
29
|
+
import { IRegisterServerManager } from "./i_register_server_manager";
|
|
30
|
+
import { exploreCertificate } from "node-opcua-crypto";
|
|
31
|
+
import { OPCUACertificateManager } from "node-opcua-certificate-manager";
|
|
32
|
+
|
|
33
|
+
const doDebug = checkDebugFlag(__filename);
|
|
34
|
+
const debugLog = make_debugLog(__filename);
|
|
35
|
+
const warningLog = make_warningLog(__filename);
|
|
36
|
+
|
|
37
|
+
export enum RegisterServerManagerStatus {
|
|
38
|
+
INACTIVE = 1,
|
|
39
|
+
INITIALIZING = 2,
|
|
40
|
+
REGISTERING = 3,
|
|
41
|
+
WAITING = 4,
|
|
42
|
+
UNREGISTERING = 5
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const g_DefaultRegistrationServerTimeout = 8 * 60 * 1000; // 8 minutes
|
|
46
|
+
|
|
47
|
+
function securityPolicyLevel(securityPolicy: UAString): number {
|
|
48
|
+
switch (securityPolicy) {
|
|
49
|
+
case SecurityPolicy.None:
|
|
50
|
+
return 0;
|
|
51
|
+
case SecurityPolicy.Basic128:
|
|
52
|
+
return 1;
|
|
53
|
+
case SecurityPolicy.Basic128Rsa15:
|
|
54
|
+
return 2;
|
|
55
|
+
case SecurityPolicy.Basic192:
|
|
56
|
+
return 3;
|
|
57
|
+
case SecurityPolicy.Basic192Rsa15:
|
|
58
|
+
return 4;
|
|
59
|
+
case SecurityPolicy.Basic256:
|
|
60
|
+
return 5;
|
|
61
|
+
case SecurityPolicy.Basic256Rsa15:
|
|
62
|
+
return 6;
|
|
63
|
+
case SecurityPolicy.Basic256Sha256:
|
|
64
|
+
return 7;
|
|
65
|
+
case SecurityPolicy.Aes128_Sha256_RsaOaep:
|
|
66
|
+
default:
|
|
67
|
+
return 0;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function sortEndpointBySecurityLevel(endpoints: EndpointDescription[]): EndpointDescription[] {
|
|
72
|
+
endpoints.sort((a: EndpointDescription, b: EndpointDescription) => {
|
|
73
|
+
if (a.securityMode === b.securityMode) {
|
|
74
|
+
if (a.securityPolicyUri === b.securityPolicyUri) {
|
|
75
|
+
const sa = a.securityLevel;
|
|
76
|
+
const sb = b.securityLevel;
|
|
77
|
+
return sa < sb ? 1 : sa > sb ? -1 : 0;
|
|
78
|
+
} else {
|
|
79
|
+
const sa = securityPolicyLevel(a.securityPolicyUri);
|
|
80
|
+
const sb = securityPolicyLevel(b.securityPolicyUri);
|
|
81
|
+
return sa < sb ? 1 : sa > sb ? -1 : 0;
|
|
82
|
+
}
|
|
83
|
+
} else {
|
|
84
|
+
return a.securityMode < b.securityMode ? 1 : 0;
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
return endpoints;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function findSecureEndpoint(endpoints: EndpointDescription[]): EndpointDescription | null {
|
|
91
|
+
// we only care about binary tcp transport endpoint
|
|
92
|
+
endpoints = endpoints.filter((e: EndpointDescription) => {
|
|
93
|
+
return e.transportProfileUri === "http://opcfoundation.org/UA-Profile/Transport/uatcp-uasc-uabinary";
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
endpoints = endpoints.filter((e: EndpointDescription) => {
|
|
97
|
+
return e.securityMode === MessageSecurityMode.SignAndEncrypt;
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
if (endpoints.length === 0) {
|
|
101
|
+
endpoints = endpoints.filter((e: EndpointDescription) => {
|
|
102
|
+
return e.securityMode === MessageSecurityMode.Sign;
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
if (endpoints.length === 0) {
|
|
106
|
+
endpoints = endpoints.filter((e: EndpointDescription) => {
|
|
107
|
+
return e.securityMode === MessageSecurityMode.None;
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
endpoints = sortEndpointBySecurityLevel(endpoints);
|
|
111
|
+
return endpoints[0];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function constructRegisteredServer(server: IPartialServer, isOnline: boolean): RegisteredServerOptions {
|
|
115
|
+
const discoveryUrls = server.getDiscoveryUrls();
|
|
116
|
+
assert(!isOnline || discoveryUrls.length >= 1, "expecting some discoveryUrls if we go online ....");
|
|
117
|
+
|
|
118
|
+
const info = exploreCertificate(server.getCertificate());
|
|
119
|
+
const commonName = info.tbsCertificate.subject.commonName!;
|
|
120
|
+
|
|
121
|
+
const serverUri = info.tbsCertificate.extensions?.subjectAltName.uniformResourceIdentifier[0];
|
|
122
|
+
// istanbul ignore next
|
|
123
|
+
if (serverUri !== server.serverInfo.applicationUri) {
|
|
124
|
+
warningLog(
|
|
125
|
+
chalk.yellow("Warning certificate uniformResourceIdentifier doesn't match serverInfo.applicationUri"),
|
|
126
|
+
"\n subjectKeyIdentifier : ",
|
|
127
|
+
info.tbsCertificate.extensions?.subjectKeyIdentifier,
|
|
128
|
+
"\n subjectAltName : ",
|
|
129
|
+
info.tbsCertificate.extensions?.subjectAltName,
|
|
130
|
+
"\n commonName : ",
|
|
131
|
+
info.tbsCertificate.subject.commonName!,
|
|
132
|
+
"\n serverInfo.applicationUri : ",
|
|
133
|
+
server.serverInfo.applicationUri
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// istanbul ignore next
|
|
138
|
+
if (!server.serverInfo.applicationName.text) {
|
|
139
|
+
debugLog("warning: application name is missing");
|
|
140
|
+
}
|
|
141
|
+
// The globally unique identifier for the Server instance. The serverUri matches
|
|
142
|
+
// the applicationUri from the ApplicationDescription defined in 7.1.
|
|
143
|
+
const s = {
|
|
144
|
+
serverUri: server.serverInfo.applicationUri,
|
|
145
|
+
|
|
146
|
+
// The globally unique identifier for the Server product.
|
|
147
|
+
productUri: server.serverInfo.productUri,
|
|
148
|
+
|
|
149
|
+
serverNames: [
|
|
150
|
+
{
|
|
151
|
+
locale: "en-US",
|
|
152
|
+
text: server.serverInfo.applicationName.text
|
|
153
|
+
}
|
|
154
|
+
],
|
|
155
|
+
serverType: server.serverType,
|
|
156
|
+
|
|
157
|
+
discoveryUrls,
|
|
158
|
+
gatewayServerUri: null,
|
|
159
|
+
isOnline,
|
|
160
|
+
semaphoreFilePath: null
|
|
161
|
+
};
|
|
162
|
+
return s;
|
|
163
|
+
}
|
|
164
|
+
function constructRegisterServerRequest(serverB: IPartialServer, isOnline: boolean): RegisterServerRequest {
|
|
165
|
+
const server = constructRegisteredServer(serverB, isOnline);
|
|
166
|
+
return new RegisterServerRequest({
|
|
167
|
+
server
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function constructRegisterServer2Request(serverB: IPartialServer, isOnline: boolean): RegisterServer2Request {
|
|
172
|
+
const server = constructRegisteredServer(serverB, isOnline);
|
|
173
|
+
|
|
174
|
+
return new RegisterServer2Request({
|
|
175
|
+
discoveryConfiguration: [
|
|
176
|
+
new MdnsDiscoveryConfiguration({
|
|
177
|
+
mdnsServerName: serverB.serverInfo.applicationUri,
|
|
178
|
+
serverCapabilities: serverB.capabilitiesForMDNS
|
|
179
|
+
})
|
|
180
|
+
],
|
|
181
|
+
server
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const no_reconnect_connectivity_strategy = {
|
|
186
|
+
initialDelay: 2000,
|
|
187
|
+
maxDelay: 50000,
|
|
188
|
+
maxRetry: 1, // NO RETRY !!!
|
|
189
|
+
randomisationFactor: 0
|
|
190
|
+
};
|
|
191
|
+
const infinite_connectivity_strategy = {
|
|
192
|
+
initialDelay: 2000,
|
|
193
|
+
maxDelay: 50000,
|
|
194
|
+
maxRetry: 10000000,
|
|
195
|
+
randomisationFactor: 0
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
interface ClientBaseEx extends OPCUAClientBase {
|
|
199
|
+
_serverEndpoints: EndpointDescription[];
|
|
200
|
+
|
|
201
|
+
performMessageTransaction(request: RegisterServer2Request, callback: ResponseCallback<RegisterServer2Response>): void;
|
|
202
|
+
performMessageTransaction(request: RegisterServerRequest, callback: ResponseCallback<RegisterServerResponse>): void;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function sendRegisterServerRequest(server: IPartialServer, client: ClientBaseEx, isOnline: boolean, callback: ErrorCallback) {
|
|
206
|
+
// try to send a RegisterServer2Request
|
|
207
|
+
const request = constructRegisterServer2Request(server, isOnline);
|
|
208
|
+
|
|
209
|
+
client.performMessageTransaction(request, (err: Error | null, response?: RegisterServer2Response) => {
|
|
210
|
+
if (!err) {
|
|
211
|
+
// RegisterServerResponse
|
|
212
|
+
debugLog("RegisterServerManager#_registerServer sendRegisterServer2Request has succeeded (isOnline", isOnline, ")");
|
|
213
|
+
assert(response instanceof RegisterServer2Response);
|
|
214
|
+
callback(err!);
|
|
215
|
+
} else {
|
|
216
|
+
debugLog("RegisterServerManager#_registerServer sendRegisterServer2Request has failed " + "(isOnline", isOnline, ")");
|
|
217
|
+
debugLog("RegisterServerManager#_registerServer" + " falling back to using sendRegisterServerRequest instead");
|
|
218
|
+
// fall back to
|
|
219
|
+
const request1 = constructRegisterServerRequest(server, isOnline);
|
|
220
|
+
client.performMessageTransaction(request1, (err1: Error | null, response1?: RegisterServerResponse) => {
|
|
221
|
+
if (!err1) {
|
|
222
|
+
debugLog(
|
|
223
|
+
"RegisterServerManager#_registerServer sendRegisterServerRequest " + "has succeeded (isOnline",
|
|
224
|
+
isOnline,
|
|
225
|
+
")"
|
|
226
|
+
);
|
|
227
|
+
assert(response1 instanceof RegisterServerResponse);
|
|
228
|
+
} else {
|
|
229
|
+
debugLog(
|
|
230
|
+
"RegisterServerManager#_registerServer sendRegisterServerRequest " + "has failed (isOnline",
|
|
231
|
+
isOnline,
|
|
232
|
+
")"
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
callback(err1!);
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export interface IPartialServer {
|
|
242
|
+
serverCertificateManager: OPCUACertificateManager;
|
|
243
|
+
certificateFile: string;
|
|
244
|
+
privateKeyFile: string;
|
|
245
|
+
serverType: ApplicationType;
|
|
246
|
+
serverInfo: {
|
|
247
|
+
applicationUri: UAString;
|
|
248
|
+
applicationName: LocalizedTextOptions;
|
|
249
|
+
productUri: UAString;
|
|
250
|
+
};
|
|
251
|
+
capabilitiesForMDNS: string[];
|
|
252
|
+
getDiscoveryUrls(): string[];
|
|
253
|
+
getCertificate(): Buffer;
|
|
254
|
+
}
|
|
255
|
+
export interface RegisterServerManagerOptions {
|
|
256
|
+
server: IPartialServer;
|
|
257
|
+
discoveryServerEndpointUrl: string;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* RegisterServerManager is responsible to Register an opcua server on a LDS or LDS-ME server
|
|
261
|
+
* This class takes in charge :
|
|
262
|
+
* - the initial registration of a server
|
|
263
|
+
* - the regular registration renewal (every 8 minutes or so ...)
|
|
264
|
+
* - dealing with cases where LDS is not up and running when server starts.
|
|
265
|
+
* ( in this case the connection will be continuously attempted using the infinite
|
|
266
|
+
* back-off strategy
|
|
267
|
+
* - the un-registration of the server ( during shutdown for instance)
|
|
268
|
+
*
|
|
269
|
+
* Events:
|
|
270
|
+
*
|
|
271
|
+
* Emitted when the server is trying to registered the LDS
|
|
272
|
+
* but when the connection to the lds has failed
|
|
273
|
+
* serverRegistrationPending is sent when the backoff signal of the
|
|
274
|
+
* connection process is rained
|
|
275
|
+
* @event serverRegistrationPending
|
|
276
|
+
*
|
|
277
|
+
* emitted when the server is successfully registered to the LDS
|
|
278
|
+
* @event serverRegistered
|
|
279
|
+
*
|
|
280
|
+
* emitted when the server has successfully renewed its registration to the LDS
|
|
281
|
+
* @event serverRegistrationRenewed
|
|
282
|
+
*
|
|
283
|
+
* emitted when the server is successfully unregistered to the LDS
|
|
284
|
+
* ( for instance during shutdown)
|
|
285
|
+
* @event serverUnregistered
|
|
286
|
+
*
|
|
287
|
+
*
|
|
288
|
+
* (LDS => Local Discovery Server)
|
|
289
|
+
* @param options
|
|
290
|
+
* @param options.server {OPCUAServer}
|
|
291
|
+
* @param options.discoveryServerEndpointUrl {String}
|
|
292
|
+
* @constructor
|
|
293
|
+
*/
|
|
294
|
+
export class RegisterServerManager extends EventEmitter implements IRegisterServerManager {
|
|
295
|
+
public discoveryServerEndpointUrl: string;
|
|
296
|
+
public timeout: number;
|
|
297
|
+
|
|
298
|
+
private server: IPartialServer | null;
|
|
299
|
+
private _registrationTimerId: NodeJS.Timer | null;
|
|
300
|
+
private state: RegisterServerManagerStatus = RegisterServerManagerStatus.INACTIVE;
|
|
301
|
+
private _registration_client: OPCUAClientBase | null = null;
|
|
302
|
+
private selectedEndpoint?: EndpointDescription;
|
|
303
|
+
private _serverEndpoints: EndpointDescription[] = [];
|
|
304
|
+
|
|
305
|
+
constructor(options: RegisterServerManagerOptions) {
|
|
306
|
+
super();
|
|
307
|
+
|
|
308
|
+
this.server = options.server;
|
|
309
|
+
this._setState(RegisterServerManagerStatus.INACTIVE);
|
|
310
|
+
this.timeout = g_DefaultRegistrationServerTimeout;
|
|
311
|
+
this.discoveryServerEndpointUrl = options.discoveryServerEndpointUrl || "opc.tcp://localhost:4840";
|
|
312
|
+
|
|
313
|
+
assert(typeof this.discoveryServerEndpointUrl === "string");
|
|
314
|
+
this._registrationTimerId = null;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
public dispose() {
|
|
318
|
+
this.server = null;
|
|
319
|
+
debugLog("RegisterServerManager#dispose", this.state.toString());
|
|
320
|
+
assert(this.state === RegisterServerManagerStatus.INACTIVE);
|
|
321
|
+
assert(this._registrationTimerId === null, "stop has not been called");
|
|
322
|
+
this.removeAllListeners();
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
public _emitEvent(eventName: string) {
|
|
326
|
+
setImmediate(() => {
|
|
327
|
+
this.emit(eventName);
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
public _setState(status: RegisterServerManagerStatus) {
|
|
332
|
+
const previousState = this.state || RegisterServerManagerStatus.INACTIVE;
|
|
333
|
+
debugLog(
|
|
334
|
+
"RegisterServerManager#setState : ",
|
|
335
|
+
RegisterServerManagerStatus[previousState],
|
|
336
|
+
" => ",
|
|
337
|
+
RegisterServerManagerStatus[status]
|
|
338
|
+
);
|
|
339
|
+
this.state = status;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
public start(callback: ErrorCallback) {
|
|
343
|
+
debugLog("RegisterServerManager#start");
|
|
344
|
+
if (this.state !== RegisterServerManagerStatus.INACTIVE) {
|
|
345
|
+
return callback(new Error("RegisterServer process already started")); // already started
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
this.discoveryServerEndpointUrl = resolveFullyQualifiedDomainName(this.discoveryServerEndpointUrl);
|
|
349
|
+
|
|
350
|
+
// perform initial registration + automatic renewal
|
|
351
|
+
this._establish_initial_connection((err?: Error | null) => {
|
|
352
|
+
if (err) {
|
|
353
|
+
debugLog("RegisterServerManager#start => _establish_initial_connection has failed");
|
|
354
|
+
return callback(err);
|
|
355
|
+
}
|
|
356
|
+
if (this.state !== RegisterServerManagerStatus.INITIALIZING) {
|
|
357
|
+
debugLog("RegisterServerManager#start => _establish_initial_connection has failed");
|
|
358
|
+
return callback();
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
this._registerServer(true, (err1?: Error | null) => {
|
|
362
|
+
if (this.state !== RegisterServerManagerStatus.REGISTERING) {
|
|
363
|
+
debugLog("RegisterServerManager#start )=> Registration has been cancelled");
|
|
364
|
+
return callback(new Error("Registration has been cancelled"));
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (err1) {
|
|
368
|
+
warningLog(
|
|
369
|
+
"RegisterServerManager#start - registering server has failed ! \n" +
|
|
370
|
+
"please check that your server certificate is accepted by the LDS"
|
|
371
|
+
);
|
|
372
|
+
this._setState(RegisterServerManagerStatus.INACTIVE);
|
|
373
|
+
this._emitEvent("serverRegistrationFailure");
|
|
374
|
+
} else {
|
|
375
|
+
this._emitEvent("serverRegistered");
|
|
376
|
+
this._setState(RegisterServerManagerStatus.WAITING);
|
|
377
|
+
this._trigger_next();
|
|
378
|
+
}
|
|
379
|
+
callback();
|
|
380
|
+
});
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
private _establish_initial_connection(outer_callback: ErrorCallback) {
|
|
385
|
+
/* istanbul ignore next */
|
|
386
|
+
if (!this.server) {
|
|
387
|
+
throw new Error("Internal Error");
|
|
388
|
+
}
|
|
389
|
+
debugLog("RegisterServerManager#_establish_initial_connection");
|
|
390
|
+
|
|
391
|
+
assert(!this._registration_client);
|
|
392
|
+
assert(typeof this.discoveryServerEndpointUrl === "string");
|
|
393
|
+
assert(this.state === RegisterServerManagerStatus.INACTIVE);
|
|
394
|
+
this._setState(RegisterServerManagerStatus.INITIALIZING);
|
|
395
|
+
this.selectedEndpoint = undefined;
|
|
396
|
+
|
|
397
|
+
const applicationName = coerceLocalizedText(this.server.serverInfo.applicationName!)?.text || undefined;
|
|
398
|
+
|
|
399
|
+
// Retry Strategy must be set
|
|
400
|
+
this.server.serverCertificateManager.referenceCounter++;
|
|
401
|
+
const registrationClient = OPCUAClientBase.create({
|
|
402
|
+
clientName: this.server.serverInfo.applicationUri!,
|
|
403
|
+
|
|
404
|
+
applicationName,
|
|
405
|
+
|
|
406
|
+
applicationUri: this.server.serverInfo.applicationUri!,
|
|
407
|
+
|
|
408
|
+
connectionStrategy: infinite_connectivity_strategy,
|
|
409
|
+
|
|
410
|
+
clientCertificateManager: this.server.serverCertificateManager,
|
|
411
|
+
|
|
412
|
+
certificateFile: this.server.certificateFile,
|
|
413
|
+
privateKeyFile: this.server.privateKeyFile
|
|
414
|
+
}) as ClientBaseEx;
|
|
415
|
+
|
|
416
|
+
this._registration_client = registrationClient;
|
|
417
|
+
|
|
418
|
+
registrationClient.on("backoff", (nbRetry: number, delay: number) => {
|
|
419
|
+
debugLog("RegisterServerManager - received backoff");
|
|
420
|
+
warningLog(
|
|
421
|
+
chalk.bgWhite.cyan("contacting discovery server backoff "),
|
|
422
|
+
this.discoveryServerEndpointUrl,
|
|
423
|
+
" attempt #",
|
|
424
|
+
nbRetry,
|
|
425
|
+
" retrying in ",
|
|
426
|
+
delay / 1000.0,
|
|
427
|
+
" seconds"
|
|
428
|
+
);
|
|
429
|
+
this._emitEvent("serverRegistrationPending");
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
async.series(
|
|
433
|
+
[
|
|
434
|
+
// do_initial_connection_with_discovery_server
|
|
435
|
+
(callback: ErrorCallback) => {
|
|
436
|
+
registrationClient.connect(this.discoveryServerEndpointUrl, (err?: Error) => {
|
|
437
|
+
if (err) {
|
|
438
|
+
debugLog(
|
|
439
|
+
"RegisterServerManager#_establish_initial_connection " + ": initial connection to server has failed"
|
|
440
|
+
);
|
|
441
|
+
// xx debugLog(err);
|
|
442
|
+
}
|
|
443
|
+
return callback(err);
|
|
444
|
+
});
|
|
445
|
+
},
|
|
446
|
+
|
|
447
|
+
// getEndpoints_on_discovery_server
|
|
448
|
+
(callback: ErrorCallback) => {
|
|
449
|
+
registrationClient.getEndpoints((err: Error | null, endpoints?: EndpointDescription[]) => {
|
|
450
|
+
if (!err) {
|
|
451
|
+
const endpoint = findSecureEndpoint(endpoints!);
|
|
452
|
+
|
|
453
|
+
if (!endpoint) {
|
|
454
|
+
throw new Error("Cannot find Secure endpoint");
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
if (endpoint.serverCertificate) {
|
|
458
|
+
assert(endpoint.serverCertificate);
|
|
459
|
+
this.selectedEndpoint = endpoint;
|
|
460
|
+
} else {
|
|
461
|
+
this.selectedEndpoint = undefined;
|
|
462
|
+
}
|
|
463
|
+
} else {
|
|
464
|
+
debugLog("RegisterServerManager#_establish_initial_connection " + ": getEndpointsRequest has failed");
|
|
465
|
+
debugLog(err);
|
|
466
|
+
}
|
|
467
|
+
callback(err!);
|
|
468
|
+
});
|
|
469
|
+
},
|
|
470
|
+
// function closing_discovery_server_connection
|
|
471
|
+
(callback: ErrorCallback) => {
|
|
472
|
+
this._serverEndpoints = registrationClient._serverEndpoints;
|
|
473
|
+
|
|
474
|
+
registrationClient.disconnect((err?: Error) => {
|
|
475
|
+
this._registration_client = null;
|
|
476
|
+
callback(err);
|
|
477
|
+
});
|
|
478
|
+
},
|
|
479
|
+
// function wait_a_little_bit
|
|
480
|
+
(callback: ErrorCallback) => {
|
|
481
|
+
setTimeout(callback, 100);
|
|
482
|
+
}
|
|
483
|
+
],
|
|
484
|
+
(err?: Error | null) => {
|
|
485
|
+
debugLog("-------------------------------", !!err);
|
|
486
|
+
|
|
487
|
+
if (this.state !== RegisterServerManagerStatus.INITIALIZING) {
|
|
488
|
+
debugLog("RegisterServerManager#_establish_initial_connection has been interrupted ", RegisterServerManagerStatus[this.state]);
|
|
489
|
+
this._setState(RegisterServerManagerStatus.INACTIVE);
|
|
490
|
+
if (this._registration_client) {
|
|
491
|
+
this._registration_client.disconnect((err2?: Error) => {
|
|
492
|
+
this._registration_client = null;
|
|
493
|
+
outer_callback(new Error("Initialization has been canceled"));
|
|
494
|
+
});
|
|
495
|
+
} else {
|
|
496
|
+
outer_callback(new Error("Initialization has been canceled"));
|
|
497
|
+
}
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
if (err) {
|
|
501
|
+
this._setState(RegisterServerManagerStatus.INACTIVE);
|
|
502
|
+
if (this._registration_client) {
|
|
503
|
+
this._registration_client.disconnect((err1?: Error) => {
|
|
504
|
+
this._registration_client = null;
|
|
505
|
+
debugLog("#######", !!err1);
|
|
506
|
+
outer_callback(err);
|
|
507
|
+
});
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
outer_callback();
|
|
512
|
+
}
|
|
513
|
+
);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
public _trigger_next() {
|
|
517
|
+
assert(!this._registrationTimerId);
|
|
518
|
+
assert(this.state === RegisterServerManagerStatus.WAITING);
|
|
519
|
+
// from spec 1.04 part 4:
|
|
520
|
+
// The registration process is designed to be platform independent, robust and able to minimize
|
|
521
|
+
// problems created by configuration errors. For that reason, Servers shall register themselves more
|
|
522
|
+
// than once.
|
|
523
|
+
// Under normal conditions, manually launched Servers shall periodically register with the Discovery
|
|
524
|
+
// Server as long as they are able to receive connections from Clients. If a Server goes offline then it
|
|
525
|
+
// shall register itself once more and indicate that it is going offline. The registration frequency
|
|
526
|
+
// should be configurable; however, the maximum is 10 minutes. If an error occurs during registration
|
|
527
|
+
// (e.g. the Discovery Server is not running) then the Server shall periodically re-attempt registration.
|
|
528
|
+
// The frequency of these attempts should start at 1 second but gradually increase until the
|
|
529
|
+
// registration frequency is the same as what it would be if no errors occurred. The recommended
|
|
530
|
+
// approach would be to double the period of each attempt until reaching the maximum.
|
|
531
|
+
// When an automatically launched Server (or its install program) registers with the Discovery Server
|
|
532
|
+
// it shall provide a path to a semaphore file which the Discovery Server can use to determine if the
|
|
533
|
+
// Server has been uninstalled from the machine. The Discovery Server shall have read access to
|
|
534
|
+
// the file system that contains the file
|
|
535
|
+
|
|
536
|
+
// install a registration
|
|
537
|
+
debugLog(
|
|
538
|
+
"RegisterServerManager#_trigger_next " + ": installing timeout to perform registerServer renewal (timeout =",
|
|
539
|
+
this.timeout,
|
|
540
|
+
")"
|
|
541
|
+
);
|
|
542
|
+
|
|
543
|
+
this._registrationTimerId = setTimeout(() => {
|
|
544
|
+
if (!this._registrationTimerId) {
|
|
545
|
+
debugLog("RegisterServerManager => cancelling re registration");
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
this._registrationTimerId = null;
|
|
549
|
+
|
|
550
|
+
debugLog("RegisterServerManager#_trigger_next : renewing RegisterServer");
|
|
551
|
+
this._registerServer(true, (err?: Error | null) => {
|
|
552
|
+
if (
|
|
553
|
+
this.state !== RegisterServerManagerStatus.INACTIVE &&
|
|
554
|
+
this.state !== RegisterServerManagerStatus.UNREGISTERING
|
|
555
|
+
) {
|
|
556
|
+
debugLog("RegisterServerManager#_trigger_next : renewed !", err);
|
|
557
|
+
this._setState(RegisterServerManagerStatus.WAITING);
|
|
558
|
+
this._emitEvent("serverRegistrationRenewed");
|
|
559
|
+
this._trigger_next();
|
|
560
|
+
}
|
|
561
|
+
});
|
|
562
|
+
}, this.timeout);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
public stop(callback: ErrorCallback) {
|
|
566
|
+
debugLog("RegisterServerManager#stop");
|
|
567
|
+
|
|
568
|
+
if (this._registrationTimerId) {
|
|
569
|
+
debugLog("RegisterServerManager#stop :clearing timeout");
|
|
570
|
+
clearTimeout(this._registrationTimerId);
|
|
571
|
+
this._registrationTimerId = null;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
this._cancel_pending_client_if_any(() => {
|
|
575
|
+
debugLog("RegisterServerManager#stop _cancel_pending_client_if_any done ", this.state);
|
|
576
|
+
|
|
577
|
+
if (!this.selectedEndpoint || this.state === RegisterServerManagerStatus.INACTIVE) {
|
|
578
|
+
this.state = RegisterServerManagerStatus.INACTIVE;
|
|
579
|
+
assert(this._registrationTimerId === null);
|
|
580
|
+
return callback();
|
|
581
|
+
}
|
|
582
|
+
this._registerServer(false, () => {
|
|
583
|
+
this._setState(RegisterServerManagerStatus.INACTIVE);
|
|
584
|
+
this._emitEvent("serverUnregistered");
|
|
585
|
+
callback();
|
|
586
|
+
});
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* @param isOnline
|
|
592
|
+
* @param outer_callback
|
|
593
|
+
* @private
|
|
594
|
+
*/
|
|
595
|
+
public _registerServer(isOnline: boolean, outer_callback: ErrorCallback) {
|
|
596
|
+
assert(typeof outer_callback === "function");
|
|
597
|
+
|
|
598
|
+
debugLog(
|
|
599
|
+
"RegisterServerManager#_registerServer isOnline:",
|
|
600
|
+
isOnline,
|
|
601
|
+
"selectedEndpoint: ",
|
|
602
|
+
this.selectedEndpoint?.endpointUrl
|
|
603
|
+
);
|
|
604
|
+
|
|
605
|
+
assert(this.selectedEndpoint, "must have a selected endpoint => please call _establish_initial_connection");
|
|
606
|
+
|
|
607
|
+
assert(this.server!.serverType !== undefined, " must have a valid server Type");
|
|
608
|
+
|
|
609
|
+
// construct connection
|
|
610
|
+
const server = this.server!;
|
|
611
|
+
const selectedEndpoint = this.selectedEndpoint;
|
|
612
|
+
|
|
613
|
+
if (!selectedEndpoint) {
|
|
614
|
+
warningLog("Warning : cannot register server - no endpoint available");
|
|
615
|
+
return outer_callback(new Error("Cannot registerServer"));
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
server.serverCertificateManager.referenceCounter++;
|
|
619
|
+
|
|
620
|
+
const applicationName: string | undefined = coerceLocalizedText(server.serverInfo.applicationName!)?.text || undefined;
|
|
621
|
+
|
|
622
|
+
const theStatus = isOnline ? RegisterServerManagerStatus.REGISTERING : RegisterServerManagerStatus.UNREGISTERING;
|
|
623
|
+
|
|
624
|
+
if (theStatus === this.state) {
|
|
625
|
+
warningLog(`Warning the server is already in the ${RegisterServerManagerStatus[theStatus]} state`);
|
|
626
|
+
return outer_callback();
|
|
627
|
+
}
|
|
628
|
+
assert(this.state === RegisterServerManagerStatus.INITIALIZING || this.state === RegisterServerManagerStatus.WAITING);
|
|
629
|
+
|
|
630
|
+
this._setState(theStatus);
|
|
631
|
+
|
|
632
|
+
if (this._registration_client) {
|
|
633
|
+
warningLog(`Warning there is already a registering/unregistering task taking place: ${RegisterServerManagerStatus[this.state]} state`);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
const options: OPCUAClientBaseOptions = {
|
|
637
|
+
securityMode: selectedEndpoint.securityMode,
|
|
638
|
+
securityPolicy: coerceSecurityPolicy(selectedEndpoint.securityPolicyUri),
|
|
639
|
+
serverCertificate: selectedEndpoint.serverCertificate,
|
|
640
|
+
|
|
641
|
+
clientCertificateManager: server.serverCertificateManager,
|
|
642
|
+
|
|
643
|
+
certificateFile: server.certificateFile,
|
|
644
|
+
privateKeyFile: server.privateKeyFile,
|
|
645
|
+
|
|
646
|
+
// xx clientName: server.serverInfo.applicationUri!,
|
|
647
|
+
|
|
648
|
+
applicationName,
|
|
649
|
+
|
|
650
|
+
applicationUri: server.serverInfo.applicationUri!,
|
|
651
|
+
|
|
652
|
+
connectionStrategy: no_reconnect_connectivity_strategy,
|
|
653
|
+
|
|
654
|
+
clientName: "server client to LDS " + RegisterServerManagerStatus[theStatus],
|
|
655
|
+
};
|
|
656
|
+
|
|
657
|
+
const client = OPCUAClientBase.create(options) as ClientBaseEx;
|
|
658
|
+
|
|
659
|
+
const tmp = this._serverEndpoints;
|
|
660
|
+
client._serverEndpoints = tmp;
|
|
661
|
+
this._registration_client = client;
|
|
662
|
+
|
|
663
|
+
debugLog(" lds endpoint uri : ", selectedEndpoint.endpointUrl);
|
|
664
|
+
debugLog(" securityMode : ", MessageSecurityMode[selectedEndpoint.securityMode]);
|
|
665
|
+
debugLog(" securityPolicy : ", selectedEndpoint.securityPolicyUri);
|
|
666
|
+
|
|
667
|
+
async.series(
|
|
668
|
+
[
|
|
669
|
+
// establish_connection_with_lds
|
|
670
|
+
(callback: ErrorCallback) => {
|
|
671
|
+
client.connect(selectedEndpoint?.endpointUrl!, (err?: Error) => {
|
|
672
|
+
debugLog("establish_connection_with_lds => err = ", err);
|
|
673
|
+
if (err) {
|
|
674
|
+
debugLog("RegisterServerManager#_registerServer connection to client has failed");
|
|
675
|
+
debugLog(
|
|
676
|
+
"RegisterServerManager#_registerServer " +
|
|
677
|
+
"=> please check that you server certificate is trusted by the LDS"
|
|
678
|
+
);
|
|
679
|
+
warningLog(
|
|
680
|
+
"RegisterServer to the LDS has failed during secure connection " +
|
|
681
|
+
"=> please check that you server certificate is trusted by the LDS.",
|
|
682
|
+
"\nerr: " + err.message,
|
|
683
|
+
"\nLDS endpoint :",
|
|
684
|
+
selectedEndpoint?.endpointUrl!,
|
|
685
|
+
"\nsecurity mode :",
|
|
686
|
+
MessageSecurityMode[selectedEndpoint.securityMode],
|
|
687
|
+
"\nsecurity policy :",
|
|
688
|
+
coerceSecurityPolicy(selectedEndpoint.securityPolicyUri)
|
|
689
|
+
);
|
|
690
|
+
// xx debugLog(options);
|
|
691
|
+
client.disconnect(() => {
|
|
692
|
+
this._registration_client = null;
|
|
693
|
+
debugLog("RegisterServerManager#_registerServer client disconnected");
|
|
694
|
+
callback(/* intentionally no error propagation*/);
|
|
695
|
+
});
|
|
696
|
+
} else {
|
|
697
|
+
callback();
|
|
698
|
+
}
|
|
699
|
+
});
|
|
700
|
+
},
|
|
701
|
+
(callback: ErrorCallback) => {
|
|
702
|
+
if (!this._registration_client) { callback(); return; }
|
|
703
|
+
sendRegisterServerRequest(this.server!, client as ClientBaseEx, isOnline, (err?: Error | null) => {
|
|
704
|
+
callback(/* intentionally no error propagation*/);
|
|
705
|
+
});
|
|
706
|
+
},
|
|
707
|
+
// close_connection_with_lds
|
|
708
|
+
(callback: ErrorCallback) => {
|
|
709
|
+
if (!this._registration_client) { callback(); return; }
|
|
710
|
+
client.disconnect(callback);
|
|
711
|
+
}
|
|
712
|
+
],
|
|
713
|
+
(err?: Error | null) => {
|
|
714
|
+
if (!this._registration_client) {
|
|
715
|
+
debugLog("RegisterServerManager#_registerServer end (isOnline", isOnline, ") has been interrupted");
|
|
716
|
+
outer_callback();
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
this._registration_client.disconnect(() => {
|
|
720
|
+
debugLog("RegisterServerManager#_registerServer end (isOnline", isOnline, ")");
|
|
721
|
+
this._registration_client = null;
|
|
722
|
+
outer_callback(err!);
|
|
723
|
+
});
|
|
724
|
+
|
|
725
|
+
}
|
|
726
|
+
);
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
private _cancel_pending_client_if_any(callback: () => void) {
|
|
730
|
+
debugLog("RegisterServerManager#_cancel_pending_client_if_any");
|
|
731
|
+
|
|
732
|
+
if (this._registration_client) {
|
|
733
|
+
debugLog("RegisterServerManager#_cancel_pending_client_if_any " + "=> wee need to disconnect _registration_client");
|
|
734
|
+
|
|
735
|
+
this._registration_client.disconnect(() => {
|
|
736
|
+
this._registration_client = null;
|
|
737
|
+
this._cancel_pending_client_if_any(callback);
|
|
738
|
+
});
|
|
739
|
+
} else {
|
|
740
|
+
debugLog("RegisterServerManager#_cancel_pending_client_if_any : done");
|
|
741
|
+
callback();
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
}
|