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,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
|
+
}
|