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.
Files changed (100) hide show
  1. package/.mocharc.yml +10 -0
  2. package/LICENSE +20 -0
  3. package/dist/base_server.d.ts +110 -0
  4. package/dist/base_server.js +476 -0
  5. package/dist/base_server.js.map +1 -0
  6. package/dist/factory.d.ts +10 -0
  7. package/dist/factory.js +24 -0
  8. package/dist/factory.js.map +1 -0
  9. package/dist/history_server_capabilities.d.ts +35 -0
  10. package/dist/history_server_capabilities.js +44 -0
  11. package/dist/history_server_capabilities.js.map +1 -0
  12. package/dist/i_channel_data.d.ts +13 -0
  13. package/dist/i_channel_data.js +3 -0
  14. package/dist/i_channel_data.js.map +1 -0
  15. package/dist/i_register_server_manager.d.ts +16 -0
  16. package/dist/i_register_server_manager.js +3 -0
  17. package/dist/i_register_server_manager.js.map +1 -0
  18. package/dist/i_server_side_publish_engine.d.ts +36 -0
  19. package/dist/i_server_side_publish_engine.js +50 -0
  20. package/dist/i_server_side_publish_engine.js.map +1 -0
  21. package/dist/i_socket_data.d.ts +11 -0
  22. package/dist/i_socket_data.js +3 -0
  23. package/dist/i_socket_data.js.map +1 -0
  24. package/dist/index.d.ts +14 -0
  25. package/dist/index.js +27 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/monitored_item.d.ts +173 -0
  28. package/dist/monitored_item.js +1006 -0
  29. package/dist/monitored_item.js.map +1 -0
  30. package/dist/node_sampler.d.ts +3 -0
  31. package/dist/node_sampler.js +76 -0
  32. package/dist/node_sampler.js.map +1 -0
  33. package/dist/opcua_server.d.ts +668 -0
  34. package/dist/opcua_server.js +2407 -0
  35. package/dist/opcua_server.js.map +1 -0
  36. package/dist/queue.d.ts +11 -0
  37. package/dist/queue.js +71 -0
  38. package/dist/queue.js.map +1 -0
  39. package/dist/register_server_manager.d.ts +92 -0
  40. package/dist/register_server_manager.js +574 -0
  41. package/dist/register_server_manager.js.map +1 -0
  42. package/dist/register_server_manager_hidden.d.ts +17 -0
  43. package/dist/register_server_manager_hidden.js +28 -0
  44. package/dist/register_server_manager_hidden.js.map +1 -0
  45. package/dist/register_server_manager_mdns_only.d.ts +19 -0
  46. package/dist/register_server_manager_mdns_only.js +58 -0
  47. package/dist/register_server_manager_mdns_only.js.map +1 -0
  48. package/dist/server_capabilities.d.ts +61 -0
  49. package/dist/server_capabilities.js +109 -0
  50. package/dist/server_capabilities.js.map +1 -0
  51. package/dist/server_end_point.d.ts +180 -0
  52. package/dist/server_end_point.js +825 -0
  53. package/dist/server_end_point.js.map +1 -0
  54. package/dist/server_engine.d.ts +311 -0
  55. package/dist/server_engine.js +1659 -0
  56. package/dist/server_engine.js.map +1 -0
  57. package/dist/server_publish_engine.d.ts +109 -0
  58. package/dist/server_publish_engine.js +531 -0
  59. package/dist/server_publish_engine.js.map +1 -0
  60. package/dist/server_publish_engine_for_orphan_subscriptions.d.ts +16 -0
  61. package/dist/server_publish_engine_for_orphan_subscriptions.js +50 -0
  62. package/dist/server_publish_engine_for_orphan_subscriptions.js.map +1 -0
  63. package/dist/server_session.d.ts +176 -0
  64. package/dist/server_session.js +734 -0
  65. package/dist/server_session.js.map +1 -0
  66. package/dist/server_subscription.d.ts +393 -0
  67. package/dist/server_subscription.js +1313 -0
  68. package/dist/server_subscription.js.map +1 -0
  69. package/dist/sessions_compatible_for_transfer.d.ts +2 -0
  70. package/dist/sessions_compatible_for_transfer.js +36 -0
  71. package/dist/sessions_compatible_for_transfer.js.map +1 -0
  72. package/dist/validate_filter.d.ts +5 -0
  73. package/dist/validate_filter.js +64 -0
  74. package/dist/validate_filter.js.map +1 -0
  75. package/package.json +88 -0
  76. package/source/base_server.ts +617 -0
  77. package/source/factory.ts +25 -0
  78. package/source/history_server_capabilities.ts +75 -0
  79. package/source/i_channel_data.ts +17 -0
  80. package/source/i_register_server_manager.ts +24 -0
  81. package/source/i_server_side_publish_engine.ts +77 -0
  82. package/source/i_socket_data.ts +11 -0
  83. package/source/index.ts +14 -0
  84. package/source/monitored_item.ts +1303 -0
  85. package/source/node_sampler.ts +82 -0
  86. package/source/opcua_server.ts +3742 -0
  87. package/source/queue.ts +73 -0
  88. package/source/register_server_manager.ts +744 -0
  89. package/source/register_server_manager_hidden.ts +33 -0
  90. package/source/register_server_manager_mdns_only.ts +69 -0
  91. package/source/server_capabilities.ts +177 -0
  92. package/source/server_end_point.ts +1182 -0
  93. package/source/server_engine.ts +2167 -0
  94. package/source/server_publish_engine.ts +657 -0
  95. package/source/server_publish_engine_for_orphan_subscriptions.ts +52 -0
  96. package/source/server_session.ts +931 -0
  97. package/source/server_subscription.ts +1792 -0
  98. package/source/sessions_compatible_for_transfer.ts +33 -0
  99. package/source/validate_filter.ts +86 -0
  100. package/test_helpers/create_certificates.js +1 -0
@@ -0,0 +1,617 @@
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 * as fs from "fs";
8
+ import * as path from "path";
9
+ import * as os from "os";
10
+ import { withLock } from "@ster5/global-mutex";
11
+ import { assert } from "node-opcua-assert";
12
+ import {
13
+ getDefaultCertificateManager,
14
+ ICertificateManager,
15
+ makeSubject,
16
+ OPCUACertificateManager
17
+ } from "node-opcua-certificate-manager";
18
+ import { IOPCUASecureObjectOptions, makeApplicationUrn, OPCUASecureObject } from "node-opcua-common";
19
+ import { coerceLocalizedText, LocalizedText } from "node-opcua-data-model";
20
+ import { installPeriodicClockAdjustment, uninstallPeriodicClockAdjustment } from "node-opcua-date-time";
21
+ import { checkDebugFlag, make_debugLog, make_errorLog } from "node-opcua-debug";
22
+ import { displayTraceFromThisProjectOnly } from "node-opcua-debug";
23
+ import { extractFullyQualifiedDomainName, getHostname, resolveFullyQualifiedDomainName } from "node-opcua-hostname";
24
+ import { Message, Response, ServerSecureChannelLayer, ServerSecureChannelParent } from "node-opcua-secure-channel";
25
+ import { FindServersRequest, FindServersResponse } from "node-opcua-service-discovery";
26
+ import { ApplicationType, GetEndpointsResponse } from "node-opcua-service-endpoints";
27
+ import { ApplicationDescription } from "node-opcua-service-endpoints";
28
+ import { ServiceFault } from "node-opcua-service-secure-channel";
29
+ import { StatusCode, StatusCodes } from "node-opcua-status-code";
30
+ import { ApplicationDescriptionOptions } from "node-opcua-types";
31
+ import { EndpointDescription, GetEndpointsRequest } from "node-opcua-types";
32
+ import { matchUri } from "node-opcua-utils";
33
+
34
+ import { OPCUAServerEndPoint } from "./server_end_point";
35
+ import { IChannelData } from "./i_channel_data";
36
+ import { ISocketData } from "./i_socket_data";
37
+ import { performCertificateSanityCheck } from "node-opcua-client";
38
+
39
+ const doDebug = checkDebugFlag(__filename);
40
+ const debugLog = make_debugLog(__filename);
41
+ const errorLog = make_errorLog(__filename);
42
+ const warningLog = errorLog;
43
+
44
+ function constructFilename(p: string): string {
45
+ let filename = path.join(__dirname, "..", p);
46
+ if (!fs.existsSync(filename)) {
47
+ // try one level up
48
+ filename = path.join(__dirname, p);
49
+ if (!fs.existsSync(filename)) {
50
+ throw new Error("Cannot find filename " + filename + " ( __dirname = " + __dirname);
51
+ }
52
+ }
53
+ return filename;
54
+ }
55
+
56
+ const default_server_info = {
57
+ // The globally unique identifier for the application instance. This URI is used as
58
+ // ServerUri in Services if the application is a Server.
59
+ applicationUri: makeApplicationUrn(os.hostname(), "NodeOPCUA-Server"),
60
+
61
+ // The globally unique identifier for the product.
62
+ productUri: "NodeOPCUA-Server",
63
+
64
+ // A localized descriptive name for the application.
65
+ applicationName: { text: "NodeOPCUA", locale: "en" },
66
+ applicationType: ApplicationType.Server,
67
+ gatewayServerUri: "",
68
+
69
+ discoveryProfileUri: "",
70
+
71
+ discoveryUrls: []
72
+ };
73
+
74
+ function cleanupEndpoint(endpoint: OPCUAServerEndPoint) {
75
+ if (endpoint._on_new_channel) {
76
+ assert(typeof endpoint._on_new_channel === "function");
77
+ endpoint.removeListener("newChannel", endpoint._on_new_channel);
78
+ endpoint._on_new_channel = undefined;
79
+ }
80
+
81
+ if (endpoint._on_close_channel) {
82
+ assert(typeof endpoint._on_close_channel === "function");
83
+ endpoint.removeListener("closeChannel", endpoint._on_close_channel);
84
+ endpoint._on_close_channel = undefined;
85
+ }
86
+ if (endpoint._on_connectionRefused) {
87
+ assert(typeof endpoint._on_connectionRefused === "function");
88
+ endpoint.removeListener("connectionRefused", endpoint._on_connectionRefused);
89
+ endpoint._on_connectionRefused = undefined;
90
+ }
91
+ if (endpoint._on_openSecureChannelFailure) {
92
+ assert(typeof endpoint._on_openSecureChannelFailure === "function");
93
+ endpoint.removeListener("openSecureChannelFailure", endpoint._on_openSecureChannelFailure);
94
+ endpoint._on_openSecureChannelFailure = undefined;
95
+ }
96
+ }
97
+
98
+ /**
99
+ *
100
+ */
101
+ export interface OPCUABaseServerOptions extends IOPCUASecureObjectOptions {
102
+ /**
103
+ * the information used in the end point description
104
+ */
105
+ serverInfo?: ApplicationDescriptionOptions;
106
+ /**
107
+ * the server Certificate Manager
108
+ */
109
+ serverCertificateManager?: OPCUACertificateManager;
110
+ }
111
+
112
+ const emptyCallback = () => {
113
+ /* empty */
114
+ };
115
+
116
+ /**
117
+ * @class OPCUABaseServer
118
+ * @constructor
119
+ */
120
+ export class OPCUABaseServer extends OPCUASecureObject {
121
+ public static makeServiceFault = makeServiceFault;
122
+
123
+ /**
124
+ * The type of server
125
+ */
126
+ get serverType(): ApplicationType {
127
+ return this.serverInfo.applicationType;
128
+ }
129
+
130
+ public serverInfo: ApplicationDescription;
131
+ public endpoints: OPCUAServerEndPoint[];
132
+ public readonly serverCertificateManager: OPCUACertificateManager;
133
+ public capabilitiesForMDNS: string[];
134
+ protected _preInitTask: any[];
135
+
136
+ protected options: OPCUABaseServerOptions;
137
+
138
+ constructor(options?: OPCUABaseServerOptions) {
139
+ options = options || ({} as OPCUABaseServerOptions);
140
+
141
+ if (!options.serverCertificateManager) {
142
+ options.serverCertificateManager = getDefaultCertificateManager("PKI");
143
+ }
144
+ options.privateKeyFile = options.privateKeyFile || options.serverCertificateManager.privateKey;
145
+ options.certificateFile =
146
+ options.certificateFile || path.join(options.serverCertificateManager.rootDir, "own/certs/certificate.pem");
147
+
148
+ super(options);
149
+
150
+ this.serverCertificateManager = options.serverCertificateManager;
151
+ this.capabilitiesForMDNS = [];
152
+ this.endpoints = [];
153
+ this.options = options;
154
+ this._preInitTask = [];
155
+
156
+ const serverInfo: ApplicationDescriptionOptions = {
157
+ ...default_server_info,
158
+ ...options.serverInfo
159
+ };
160
+ serverInfo.applicationName = coerceLocalizedText(serverInfo.applicationName);
161
+ this.serverInfo = new ApplicationDescription(serverInfo);
162
+
163
+ if (this.serverInfo.applicationName.toString().match(/urn:/)) {
164
+ errorLog("[NODE-OPCUA-E06] application name cannot be a urn", this.serverInfo.applicationName.toString());
165
+ }
166
+
167
+ this.serverInfo.applicationName!.locale = this.serverInfo.applicationName?.locale || "en";
168
+
169
+ if (!this.serverInfo.applicationName?.locale) {
170
+ warningLog(
171
+ "[NODE-OPCUA-W24] the server applicationName must have a valid locale : ",
172
+ this.serverInfo.applicationName.toString()
173
+ );
174
+ }
175
+
176
+ const __applicationUri = serverInfo.applicationUri || "";
177
+
178
+ (this.serverInfo as any).__defineGetter__("applicationUri", function (this: any) {
179
+ return resolveFullyQualifiedDomainName(__applicationUri);
180
+ });
181
+
182
+ this._preInitTask.push(async () => {
183
+ const fqdn = await extractFullyQualifiedDomainName();
184
+ });
185
+
186
+ this._preInitTask.push(async () => {
187
+ await this.initializeCM();
188
+ });
189
+ }
190
+
191
+ protected async createDefaultCertificate() {
192
+ if (fs.existsSync(this.certificateFile)) {
193
+ return;
194
+ }
195
+ const lockfile = path.join(this.certificateFile + ".lock");
196
+ await withLock({ lockfile }, async () => {
197
+ if (!fs.existsSync(this.certificateFile)) {
198
+ const applicationUri = this.serverInfo.applicationUri!;
199
+ const hostname = getHostname();
200
+ await this.serverCertificateManager.createSelfSignedCertificate({
201
+ applicationUri,
202
+ dns: [hostname],
203
+ // ip: await getIpAddresses(),
204
+ outputFile: this.certificateFile,
205
+
206
+ subject: makeSubject(this.serverInfo.applicationName.text!, hostname),
207
+
208
+ startDate: new Date(),
209
+ validity: 365 * 10 // 10 years
210
+ });
211
+ }
212
+ });
213
+ }
214
+ protected async initializeCM(): Promise<void> {
215
+ await this.serverCertificateManager.initialize();
216
+ await this.createDefaultCertificate();
217
+ debugLog("privateKey = ", this.privateKeyFile, this.serverCertificateManager.privateKey);
218
+ debugLog("certificateFile = ", this.certificateFile);
219
+ await performCertificateSanityCheck.call(this, "server", this.serverCertificateManager, this.serverInfo.applicationUri!);
220
+ }
221
+
222
+ /**
223
+ * start all registered endPoint, in parallel, and call done when all endPoints are listening.
224
+ * @method start
225
+ * @async
226
+ * @param {callback} done
227
+ */
228
+ public start(done: (err?: Error | null) => void) {
229
+ assert(typeof done === "function");
230
+ this.startAsync()
231
+ .then(() => done(null))
232
+ .catch((err) => done(err));
233
+ }
234
+
235
+ protected async performPreInitialization(): Promise<void> {
236
+ const tasks = this._preInitTask;
237
+ this._preInitTask = [];
238
+ for (const task of tasks) {
239
+ await task();
240
+ }
241
+ }
242
+
243
+ protected async startAsync(): Promise<void> {
244
+ await this.performPreInitialization();
245
+
246
+ const self = this;
247
+ assert(Array.isArray(this.endpoints));
248
+ assert(this.endpoints.length > 0, "We need at least one end point");
249
+
250
+ installPeriodicClockAdjustment();
251
+
252
+ const _on_new_channel = function (this: OPCUAServerEndPoint, channel: ServerSecureChannelLayer) {
253
+ self.emit("newChannel", channel, this);
254
+ };
255
+
256
+ const _on_close_channel = function (this: OPCUAServerEndPoint, channel: ServerSecureChannelLayer) {
257
+ self.emit("closeChannel", channel, this);
258
+ };
259
+
260
+ const _on_connectionRefused = function (this: OPCUAServerEndPoint, socketData: ISocketData) {
261
+ self.emit("connectionRefused", socketData, this);
262
+ };
263
+
264
+ const _on_openSecureChannelFailure = function (
265
+ this: OPCUAServerEndPoint,
266
+ socketData: ISocketData,
267
+ channelData: IChannelData
268
+ ) {
269
+ self.emit("openSecureChannelFailure", socketData, channelData, this);
270
+ };
271
+
272
+ const promises: Promise<void>[] = [];
273
+
274
+ for (const endpoint of this.endpoints) {
275
+ assert(!endpoint._on_close_channel);
276
+
277
+ endpoint._on_new_channel = _on_new_channel;
278
+ endpoint.on("newChannel", endpoint._on_new_channel);
279
+
280
+ endpoint._on_close_channel = _on_close_channel;
281
+ endpoint.on("closeChannel", endpoint._on_close_channel);
282
+
283
+ endpoint._on_connectionRefused = _on_connectionRefused;
284
+ endpoint.on("connectionRefused", endpoint._on_connectionRefused);
285
+
286
+ endpoint._on_openSecureChannelFailure = _on_openSecureChannelFailure;
287
+ endpoint.on("openSecureChannelFailure", endpoint._on_openSecureChannelFailure);
288
+
289
+ promises.push(new Promise<void>((resolve, reject) => endpoint.start((err) => (err ? reject(err) : resolve()))));
290
+ }
291
+ await Promise.all(promises);
292
+ }
293
+
294
+ /**
295
+ * shutdown all server endPoints
296
+ * @async
297
+ */
298
+ public shutdown(done: (err?: Error) => void) {
299
+ assert(typeof done === "function");
300
+ uninstallPeriodicClockAdjustment();
301
+ this.serverCertificateManager.dispose().then(() => {
302
+ debugLog("OPCUABaseServer#shutdown starting");
303
+ async.forEach(
304
+ this.endpoints,
305
+ (endpoint: OPCUAServerEndPoint, callback: (err?: Error) => void) => {
306
+ cleanupEndpoint(endpoint);
307
+ endpoint.shutdown(callback);
308
+ },
309
+ (err?: Error | null) => {
310
+ debugLog("shutdown completed");
311
+ done(err!);
312
+ }
313
+ );
314
+ });
315
+ }
316
+
317
+ public async shutdownChannels(): Promise<void>;
318
+ public shutdownChannels(callback: (err?: Error | null) => void): void;
319
+ public shutdownChannels(callback?: (err?: Error | null) => void): Promise<void> | void {
320
+ assert(typeof callback === "function");
321
+ debugLog("OPCUABaseServer#shutdownChannels");
322
+ async.forEach(
323
+ this.endpoints,
324
+ (endpoint: OPCUAServerEndPoint, inner_callback: (err?: Error | null) => void) => {
325
+ debugLog(" shutting down endpoint ", endpoint.endpointDescriptions()[0].endpointUrl);
326
+ async.series(
327
+ [
328
+ // xx (callback2: (err?: Error| null) => void) => {
329
+ // xx endpoint.suspendConnection(callback2);
330
+ // xx },
331
+ (callback2: (err?: Error | null) => void) => {
332
+ endpoint.abruptlyInterruptChannels();
333
+ endpoint.shutdown(callback2);
334
+ }
335
+ // xx (callback2: (err?: Error| null) => void) => {
336
+ // xx endpoint.restoreConnection(callback2);
337
+ // xx }
338
+ ],
339
+ inner_callback
340
+ );
341
+ },
342
+ callback!
343
+ );
344
+ }
345
+
346
+ /**
347
+ * @private
348
+ */
349
+ public on_request(message: Message, channel: ServerSecureChannelLayer) {
350
+ assert(message.request);
351
+ assert(message.requestId !== 0);
352
+ const request = message.request;
353
+
354
+ // install channel._on_response so we can intercept its call and emit the "response" event.
355
+ if (!channel._on_response) {
356
+ channel._on_response = (msg: string, response1: Response /*, inner_message: Message*/) => {
357
+ this.emit("response", response1, channel);
358
+ };
359
+ }
360
+
361
+ // prepare request
362
+ this.prepare(message, channel);
363
+
364
+ if (doDebug) {
365
+ debugLog(
366
+ chalk.green.bold("--------------------------------------------------------"),
367
+ channel.channelId,
368
+ request.schema.name
369
+ );
370
+ }
371
+
372
+ let errMessage: string;
373
+ let response: Response;
374
+
375
+ this.emit("request", request, channel);
376
+
377
+ try {
378
+ // handler must be named _on_ActionRequest()
379
+ const handler = (this as any)["_on_" + request.schema.name];
380
+ if (typeof handler === "function") {
381
+ handler.apply(this, arguments);
382
+ } else {
383
+ errMessage = "[NODE-OPCUA-W07] Unsupported Service : " + request.schema.name;
384
+ warningLog(errMessage);
385
+ debugLog(chalk.red.bold(errMessage));
386
+ response = makeServiceFault(StatusCodes.BadServiceUnsupported, [errMessage]);
387
+ channel.send_response("MSG", response, message, emptyCallback);
388
+ }
389
+ } catch (err) {
390
+ /* istanbul ignore if */
391
+ const errMessage1 = "[NODE-OPCUA-W08] EXCEPTION CAUGHT WHILE PROCESSING REQUEST !! " + request.schema.name;
392
+ warningLog(chalk.red.bold(errMessage1));
393
+ warningLog(request.toString());
394
+ displayTraceFromThisProjectOnly(err as Error);
395
+
396
+ let additional_messages = [];
397
+ additional_messages.push("EXCEPTION CAUGHT WHILE PROCESSING REQUEST !!! " + request.schema.name);
398
+ if (err instanceof Error) {
399
+ additional_messages.push(err.message);
400
+ if (err.stack) {
401
+ additional_messages = additional_messages.concat(err.stack.split("\n"));
402
+ }
403
+ }
404
+ response = makeServiceFault(StatusCodes.BadInternalError, additional_messages);
405
+
406
+ channel.send_response("MSG", response, message, emptyCallback);
407
+ }
408
+ }
409
+
410
+ /**
411
+ * @private
412
+ */
413
+ public _get_endpoints(endpointUrl?: string | null): EndpointDescription[] {
414
+ let endpoints: EndpointDescription[] = [];
415
+ for (const endPoint of this.endpoints) {
416
+ const ep = endPoint.endpointDescriptions();
417
+ const epFiltered = endpointUrl ? ep.filter((e) => matchUri(e.endpointUrl, endpointUrl)) : ep;
418
+ endpoints = endpoints.concat(epFiltered);
419
+ }
420
+ return endpoints;
421
+ }
422
+ /**
423
+ * get one of the possible endpointUrl
424
+ */
425
+ public getEndpointUrl(): string {
426
+ return this._get_endpoints()[0].endpointUrl!;
427
+ }
428
+
429
+ public getDiscoveryUrls(): string[] {
430
+ const discoveryUrls = this.endpoints.map((e: OPCUAServerEndPoint) => {
431
+ return e.endpointDescriptions()[0].endpointUrl!;
432
+ });
433
+ return discoveryUrls;
434
+ }
435
+
436
+ public getServers(channel: ServerSecureChannelLayer): ApplicationDescription[] {
437
+ this.serverInfo.discoveryUrls = this.getDiscoveryUrls();
438
+ const servers = [this.serverInfo];
439
+ return servers;
440
+ }
441
+
442
+ /**
443
+ * set all the end point into a state where they do not accept further connections
444
+ *
445
+ * note:
446
+ * this method is useful for testing purpose
447
+ *
448
+ */
449
+ public async suspendEndPoints(): Promise<void>;
450
+ public suspendEndPoints(callback: (err?: Error) => void): void;
451
+ public suspendEndPoints(callback?: (err?: Error) => void): void | Promise<void> {
452
+ /* istanbul ignore next */
453
+ if (!callback) {
454
+ throw new Error("Internal Error");
455
+ }
456
+ async.forEach(
457
+ this.endpoints,
458
+ (ep: OPCUAServerEndPoint, _inner_callback) => {
459
+ /* istanbul ignore next */
460
+ if (doDebug) {
461
+ debugLog("Suspending ", ep.endpointDescriptions()[0].endpointUrl);
462
+ }
463
+
464
+ ep.suspendConnection((err?: Error | null) => {
465
+ /* istanbul ignore next */
466
+ if (doDebug) {
467
+ debugLog("Suspended ", ep.endpointDescriptions()[0].endpointUrl);
468
+ }
469
+ _inner_callback(err);
470
+ });
471
+ },
472
+ (err?: Error | null) => callback(err!)
473
+ );
474
+ }
475
+
476
+ /**
477
+ * set all the end point into a state where they do accept connections
478
+ * note:
479
+ * this method is useful for testing purpose
480
+ */
481
+ public async resumeEndPoints(): Promise<void>;
482
+ public resumeEndPoints(callback: (err?: Error) => void): void;
483
+ public resumeEndPoints(callback?: (err?: Error) => void): void | Promise<void> {
484
+ async.forEach(
485
+ this.endpoints,
486
+ (ep: OPCUAServerEndPoint, _inner_callback) => {
487
+ ep.restoreConnection(_inner_callback);
488
+ },
489
+ (err?: Error | null) => callback!(err!)
490
+ );
491
+ }
492
+
493
+ protected prepare(message: Message, channel: ServerSecureChannelLayer): void {
494
+ /* empty */
495
+ }
496
+
497
+ /**
498
+ * @private
499
+ */
500
+ protected _on_GetEndpointsRequest(message: Message, channel: ServerSecureChannelLayer) {
501
+ const server = this;
502
+ const request = message.request as GetEndpointsRequest;
503
+
504
+ assert(request.schema.name === "GetEndpointsRequest");
505
+
506
+ const response = new GetEndpointsResponse({});
507
+
508
+ response.endpoints = server._get_endpoints(null);
509
+
510
+ response.endpoints = response.endpoints.filter((endpoint: EndpointDescription) => !(endpoint as any).restricted);
511
+
512
+ // apply filters
513
+ if (request.profileUris && request.profileUris.length > 0) {
514
+ response.endpoints = response.endpoints.filter((endpoint: any) => {
515
+ return request.profileUris!.indexOf(endpoint.transportProfileUri) >= 0;
516
+ });
517
+ }
518
+
519
+ // adjust locale on ApplicationName to match requested local or provide
520
+ // a string with neutral locale (locale === null)
521
+ // TODO: find a better way to handle this
522
+ response.endpoints.forEach((endpoint: EndpointDescription) => {
523
+ endpoint.server.applicationName.locale = "en-US";
524
+ });
525
+
526
+ channel.send_response("MSG", response, message, emptyCallback);
527
+ }
528
+
529
+ /**
530
+ * @private
531
+ */
532
+ protected _on_FindServersRequest(message: Message, channel: ServerSecureChannelLayer) {
533
+ const server = this;
534
+ // Release 1.02 13 OPC Unified Architecture, Part 4 :
535
+ // This Service can be used without security and it is therefore vulnerable to Denial Of Service (DOS)
536
+ // attacks. A Server should minimize the amount of processing required to send the response for this
537
+ // Service. This can be achieved by preparing the result in advance. The Server should also add a
538
+ // short delay before starting processing of a request during high traffic conditions.
539
+
540
+ const shortDelay = 100; // milliseconds
541
+ setTimeout(() => {
542
+ const request = message.request;
543
+ assert(request.schema.name === "FindServersRequest");
544
+ if (!(request instanceof FindServersRequest)) {
545
+ throw new Error("Invalid request type");
546
+ }
547
+
548
+ let servers = server.getServers(channel);
549
+ // apply filters
550
+ // TODO /
551
+ if (request.serverUris && request.serverUris.length > 0) {
552
+ // A serverUri matches the applicationUri from the ApplicationDescription define
553
+ servers = servers.filter((inner_Server: ApplicationDescription) => {
554
+ return request.serverUris!.indexOf(inner_Server.applicationUri) >= 0;
555
+ });
556
+ }
557
+
558
+ function adapt(applicationDescription: ApplicationDescription): ApplicationDescription {
559
+ return new ApplicationDescription({
560
+ applicationName: applicationDescription.applicationName,
561
+ applicationType: applicationDescription.applicationType,
562
+ applicationUri: applicationDescription.applicationUri,
563
+ discoveryProfileUri: applicationDescription.discoveryProfileUri,
564
+ discoveryUrls: applicationDescription.discoveryUrls,
565
+ gatewayServerUri: applicationDescription.gatewayServerUri,
566
+ productUri: applicationDescription.productUri
567
+ });
568
+ }
569
+
570
+ const response = new FindServersResponse({
571
+ servers: servers.map(adapt)
572
+ });
573
+
574
+ channel.send_response("MSG", response, message, emptyCallback);
575
+ }, shortDelay);
576
+ }
577
+
578
+ /**
579
+ * returns a array of currently active channels
580
+ */
581
+ protected getChannels(): ServerSecureChannelLayer[] {
582
+ let channels: ServerSecureChannelLayer[] = [];
583
+
584
+ for (const endpoint of this.endpoints) {
585
+ const c = endpoint.getChannels();
586
+ channels = channels.concat(c);
587
+ }
588
+ return channels;
589
+ }
590
+ }
591
+
592
+ /**
593
+ * construct a service Fault response
594
+ * @method makeServiceFault
595
+ * @param statusCode
596
+ * @param messages
597
+ */
598
+ function makeServiceFault(statusCode: StatusCode, messages: string[]): ServiceFault {
599
+ const response = new ServiceFault();
600
+ response.responseHeader.serviceResult = statusCode;
601
+ // xx response.serviceDiagnostics.push( new DiagnosticInfo({ additionalInfo: messages.join("\n")}));
602
+
603
+ assert(Array.isArray(messages));
604
+ assert(typeof messages[0] === "string");
605
+
606
+ response.responseHeader.stringTable = messages;
607
+ // tslint:disable:no-console
608
+ warningLog(chalk.cyan(" messages "), messages.join("\n"));
609
+ return response;
610
+ }
611
+
612
+ // tslint:disable:no-var-requires
613
+ const thenify = require("thenify");
614
+ const opts = { multiArgs: false };
615
+ OPCUABaseServer.prototype.resumeEndPoints = thenify.withCallback(OPCUABaseServer.prototype.resumeEndPoints, opts);
616
+ OPCUABaseServer.prototype.suspendEndPoints = thenify.withCallback(OPCUABaseServer.prototype.suspendEndPoints, opts);
617
+ OPCUABaseServer.prototype.shutdownChannels = thenify.withCallback(OPCUABaseServer.prototype.shutdownChannels, opts);
@@ -0,0 +1,25 @@
1
+ /**
2
+ * @module node-opcua-server
3
+ */
4
+
5
+ import { assert } from "node-opcua-assert";
6
+ import { ExtensionObject } from "node-opcua-extension-object";
7
+ import { constructObject } from "node-opcua-factory";
8
+ import { ExpandedNodeId } from "node-opcua-nodeid";
9
+
10
+ export class Factory {
11
+ public engine: any;
12
+
13
+ constructor(engine: any) {
14
+ assert(engine !== null && typeof engine === "object");
15
+ this.engine = engine;
16
+ }
17
+
18
+ public constructObject(id: ExpandedNodeId): ExtensionObject {
19
+ const obj = constructObject(id);
20
+ if (!(obj instanceof ExtensionObject)) {
21
+ throw new Error("Internal Error constructObject");
22
+ }
23
+ return obj as ExtensionObject;
24
+ }
25
+ }