node-opcua-server-discovery 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/LICENSE +20 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/mdns_responder.d.ts +13 -0
- package/dist/mdns_responder.js +106 -0
- package/dist/mdns_responder.js.map +1 -0
- package/dist/opcua_discovery_server.d.ts +29 -0
- package/dist/opcua_discovery_server.js +439 -0
- package/dist/opcua_discovery_server.js.map +1 -0
- package/package.json +45 -0
- package/source/index.ts +4 -0
- package/source/mdns_responder.ts +136 -0
- package/source/opcua_discovery_server.ts +564 -0
- package/test_helpers/create_certificates.js +1 -0
|
@@ -0,0 +1,564 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module node-opcua-server-discovery
|
|
3
|
+
*/
|
|
4
|
+
import * as chalk from "chalk";
|
|
5
|
+
import * as os from "os";
|
|
6
|
+
import * as path from "path";
|
|
7
|
+
import * as url from "url";
|
|
8
|
+
import { callbackify } from "util";
|
|
9
|
+
import envPaths = require("env-paths");
|
|
10
|
+
|
|
11
|
+
import { assert } from "node-opcua-assert";
|
|
12
|
+
import { UAString } from "node-opcua-basic-types";
|
|
13
|
+
import { makeApplicationUrn } from "node-opcua-common";
|
|
14
|
+
import { checkDebugFlag, make_debugLog } from "node-opcua-debug";
|
|
15
|
+
import { extractFullyQualifiedDomainName, resolveFullyQualifiedDomainName } from "node-opcua-hostname";
|
|
16
|
+
import { Message, Response, ServerSecureChannelLayer } from "node-opcua-secure-channel";
|
|
17
|
+
import { OPCUABaseServer, OPCUABaseServerOptions, OPCUAServerEndPoint } from "node-opcua-server";
|
|
18
|
+
|
|
19
|
+
import {
|
|
20
|
+
Announcement,
|
|
21
|
+
BonjourHolder,
|
|
22
|
+
FindServersOnNetworkRequest,
|
|
23
|
+
FindServersOnNetworkResponse,
|
|
24
|
+
MdnsDiscoveryConfiguration,
|
|
25
|
+
RegisteredServer,
|
|
26
|
+
RegisterServer2Request,
|
|
27
|
+
RegisterServer2Response,
|
|
28
|
+
RegisterServerRequest,
|
|
29
|
+
RegisterServerResponse,
|
|
30
|
+
sameAnnouncement,
|
|
31
|
+
ServerOnNetwork
|
|
32
|
+
} from "node-opcua-service-discovery";
|
|
33
|
+
import { ApplicationDescription } from "node-opcua-service-endpoints";
|
|
34
|
+
import { ApplicationDescriptionOptions, ApplicationType } from "node-opcua-service-endpoints";
|
|
35
|
+
import { ErrorCallback, StatusCode, StatusCodes } from "node-opcua-status-code";
|
|
36
|
+
|
|
37
|
+
import { MDNSResponder } from "./mdns_responder";
|
|
38
|
+
import { OPCUACertificateManager } from "node-opcua-certificate-manager";
|
|
39
|
+
|
|
40
|
+
const debugLog = make_debugLog(__filename);
|
|
41
|
+
const doDebug = checkDebugFlag(__filename);
|
|
42
|
+
|
|
43
|
+
function constructFilename(p: string): string {
|
|
44
|
+
const filename = path.join(__dirname, "..", p);
|
|
45
|
+
return filename;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function hasCapabilities(serverCapabilities: UAString[] | null, serverCapabilityFilter: string): boolean {
|
|
49
|
+
if (serverCapabilities == null) {
|
|
50
|
+
return true; // filter is empty => no filtering should take place
|
|
51
|
+
}
|
|
52
|
+
if (serverCapabilityFilter.length === 0) {
|
|
53
|
+
return true; // filter is empty => no filtering should take place
|
|
54
|
+
}
|
|
55
|
+
return !!serverCapabilities.join(" ").match(serverCapabilityFilter);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface OPCUADiscoveryServerOptions extends OPCUABaseServerOptions {
|
|
59
|
+
certificateFile?: string;
|
|
60
|
+
port?: number;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
interface RegisteredServerExtended extends RegisteredServer {
|
|
64
|
+
bonjourHolder: BonjourHolder;
|
|
65
|
+
serverInfo: ApplicationDescriptionOptions;
|
|
66
|
+
discoveryConfiguration?: MdnsDiscoveryConfiguration[];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
interface RegisterServerMap {
|
|
70
|
+
[key: string]: RegisteredServerExtended;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const defaultProductUri = "NodeOPCUA-LocalDiscoveryServer";
|
|
74
|
+
const defaultApplicationUri = makeApplicationUrn(os.hostname(), defaultProductUri);
|
|
75
|
+
|
|
76
|
+
function getDefaultCertificateManager(): OPCUACertificateManager {
|
|
77
|
+
const config = envPaths(defaultProductUri).config;
|
|
78
|
+
return new OPCUACertificateManager({
|
|
79
|
+
name: "PKI",
|
|
80
|
+
rootFolder: path.join(config, "PKI"),
|
|
81
|
+
|
|
82
|
+
automaticallyAcceptUnknownCertificate: true
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export class OPCUADiscoveryServer extends OPCUABaseServer {
|
|
87
|
+
private mDnsResponder?: MDNSResponder;
|
|
88
|
+
private readonly registeredServers: RegisterServerMap;
|
|
89
|
+
private bonjourHolder: BonjourHolder;
|
|
90
|
+
private _delayInit?: () => void;
|
|
91
|
+
|
|
92
|
+
constructor(options: OPCUADiscoveryServerOptions) {
|
|
93
|
+
options.serverInfo = options.serverInfo || {};
|
|
94
|
+
const serverInfo = options.serverInfo;
|
|
95
|
+
|
|
96
|
+
serverInfo.applicationType = ApplicationType.DiscoveryServer;
|
|
97
|
+
|
|
98
|
+
serverInfo.applicationUri = serverInfo.applicationUri || defaultApplicationUri;
|
|
99
|
+
|
|
100
|
+
serverInfo.productUri = serverInfo.productUri || defaultProductUri;
|
|
101
|
+
|
|
102
|
+
serverInfo.applicationName = serverInfo.applicationName || {
|
|
103
|
+
text: defaultProductUri,
|
|
104
|
+
|
|
105
|
+
locale: null
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
serverInfo.gatewayServerUri = serverInfo.gatewayServerUri || "";
|
|
109
|
+
serverInfo.discoveryProfileUri = serverInfo.discoveryProfileUri || "";
|
|
110
|
+
serverInfo.discoveryUrls = serverInfo.discoveryUrls || [];
|
|
111
|
+
|
|
112
|
+
options.serverCertificateManager = options.serverCertificateManager || getDefaultCertificateManager();
|
|
113
|
+
|
|
114
|
+
super(options);
|
|
115
|
+
|
|
116
|
+
this.bonjourHolder = new BonjourHolder();
|
|
117
|
+
|
|
118
|
+
// see OPC UA Spec 1.2 part 6 : 7.4 Well Known Addresses
|
|
119
|
+
// opc.tcp://localhost:4840/UADiscovery
|
|
120
|
+
const port = options.port || 4840;
|
|
121
|
+
|
|
122
|
+
this.capabilitiesForMDNS = ["LDS"];
|
|
123
|
+
this.registeredServers = {};
|
|
124
|
+
|
|
125
|
+
this.mDnsResponder = undefined;
|
|
126
|
+
|
|
127
|
+
this._delayInit = async (): Promise<void> => {
|
|
128
|
+
const endPoint = new OPCUAServerEndPoint({
|
|
129
|
+
port,
|
|
130
|
+
|
|
131
|
+
certificateChain: this.getCertificateChain(),
|
|
132
|
+
|
|
133
|
+
certificateManager: this.serverCertificateManager,
|
|
134
|
+
|
|
135
|
+
privateKey: this.getPrivateKey(),
|
|
136
|
+
serverInfo: this.serverInfo
|
|
137
|
+
});
|
|
138
|
+
endPoint.addStandardEndpointDescriptions();
|
|
139
|
+
|
|
140
|
+
this.endpoints.push(endPoint);
|
|
141
|
+
|
|
142
|
+
endPoint.on("message", (message: Message, channel: ServerSecureChannelLayer) => {
|
|
143
|
+
this.on_request(message, channel);
|
|
144
|
+
});
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
public async start(): Promise<void>;
|
|
149
|
+
public start(done: ErrorCallback): void;
|
|
150
|
+
public start(done?: ErrorCallback): any {
|
|
151
|
+
assert(!this.mDnsResponder);
|
|
152
|
+
assert(Array.isArray(this.capabilitiesForMDNS));
|
|
153
|
+
|
|
154
|
+
this._preInitTask.push(async () => {
|
|
155
|
+
await this._delayInit!();
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
super.start((err?: Error | null) => {
|
|
159
|
+
if (err) {
|
|
160
|
+
return done!(err);
|
|
161
|
+
}
|
|
162
|
+
this.mDnsResponder = new MDNSResponder();
|
|
163
|
+
// declare discovery server in bonjour
|
|
164
|
+
this.bonjourHolder._announcedOnMulticastSubnetWithCallback(
|
|
165
|
+
{
|
|
166
|
+
capabilities: this.capabilitiesForMDNS,
|
|
167
|
+
name: this.serverInfo.applicationUri!,
|
|
168
|
+
path: "/DiscoveryServer",
|
|
169
|
+
port: this.endpoints[0].port
|
|
170
|
+
},
|
|
171
|
+
(err2: Error | null) => {
|
|
172
|
+
done!(err2!);
|
|
173
|
+
}
|
|
174
|
+
);
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
public async shutdown(): Promise<void>;
|
|
179
|
+
public shutdown(done: ErrorCallback): void;
|
|
180
|
+
public shutdown(done?: ErrorCallback): any {
|
|
181
|
+
debugLog("stopping announcement of LDS on mDNS");
|
|
182
|
+
this.bonjourHolder._stop_announcedOnMulticastSubnetWithCallback((err?: Error | null) => {
|
|
183
|
+
if (err) {
|
|
184
|
+
console.log("Error ", err.message);
|
|
185
|
+
}
|
|
186
|
+
debugLog("stopping announcement of LDS on mDNS - DONE");
|
|
187
|
+
debugLog("Shutting down Discovery Server");
|
|
188
|
+
super.shutdown(() => {
|
|
189
|
+
if (this.mDnsResponder) {
|
|
190
|
+
this.mDnsResponder.dispose();
|
|
191
|
+
this.mDnsResponder = undefined;
|
|
192
|
+
}
|
|
193
|
+
setTimeout(() => done!(), 200);
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* returns the number of registered servers
|
|
200
|
+
*/
|
|
201
|
+
public get registeredServerCount(): number {
|
|
202
|
+
return Object.keys(this.registeredServers).length;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
public getServers(channel: ServerSecureChannelLayer): ApplicationDescription[] {
|
|
206
|
+
this.serverInfo.discoveryUrls = this.getDiscoveryUrls();
|
|
207
|
+
|
|
208
|
+
const servers: ApplicationDescription[] = [this.serverInfo];
|
|
209
|
+
|
|
210
|
+
for (const registered_server of Object.values(this.registeredServers)) {
|
|
211
|
+
const serverInfo: ApplicationDescription = new ApplicationDescription(registered_server.serverInfo);
|
|
212
|
+
servers.push(serverInfo);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return servers;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
protected _on_RegisterServer2Request(message: Message, channel: ServerSecureChannelLayer) {
|
|
219
|
+
assert(message.request instanceof RegisterServer2Request);
|
|
220
|
+
const request = message.request as RegisterServer2Request;
|
|
221
|
+
|
|
222
|
+
assert(request.schema.name === "RegisterServer2Request");
|
|
223
|
+
|
|
224
|
+
request.discoveryConfiguration = request.discoveryConfiguration || [];
|
|
225
|
+
this.__internalRegisterServerWithCallback(
|
|
226
|
+
RegisterServer2Response,
|
|
227
|
+
request.server,
|
|
228
|
+
request.discoveryConfiguration as MdnsDiscoveryConfiguration[],
|
|
229
|
+
(err: Error | null, response?: Response) => {
|
|
230
|
+
// istanbul ignore next
|
|
231
|
+
if (err) {
|
|
232
|
+
// tslint:disable-next-line: no-console
|
|
233
|
+
console.log("What shall I do ?", err.message);
|
|
234
|
+
// tslint:disable-next-line: no-console
|
|
235
|
+
console.log(err);
|
|
236
|
+
let additional_messages = [];
|
|
237
|
+
additional_messages.push("EXCEPTION CAUGHT WHILE PROCESSING REQUEST !!! " + request.schema.name);
|
|
238
|
+
additional_messages.push(err.message);
|
|
239
|
+
if (err.stack) {
|
|
240
|
+
additional_messages = additional_messages.concat(err.stack.split("\n"));
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
response = OPCUADiscoveryServer.makeServiceFault(StatusCodes.BadInternalError, additional_messages);
|
|
244
|
+
channel.send_response("MSG", response, message);
|
|
245
|
+
} else {
|
|
246
|
+
channel.send_response("MSG", response!, message);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
protected _on_RegisterServerRequest(message: Message, channel: ServerSecureChannelLayer) {
|
|
253
|
+
assert(message.request instanceof RegisterServerRequest);
|
|
254
|
+
const request = message.request as RegisterServerRequest;
|
|
255
|
+
assert(request.schema.name === "RegisterServerRequest");
|
|
256
|
+
this.__internalRegisterServerWithCallback(
|
|
257
|
+
RegisterServerResponse,
|
|
258
|
+
request.server,
|
|
259
|
+
undefined,
|
|
260
|
+
(err: Error | null, response?: Response) => {
|
|
261
|
+
channel.send_response("MSG", response!, message);
|
|
262
|
+
}
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
protected _on_FindServersOnNetworkRequest(message: Message, channel: ServerSecureChannelLayer) {
|
|
267
|
+
// from OPCUA 1.04 part 4
|
|
268
|
+
// This Service returns the Servers known to a Discovery Server. Unlike FindServer, this Service is
|
|
269
|
+
// only implemented by Discovery Servers.
|
|
270
|
+
// The Client may reduce the number of results returned by specifying filter criteria. An empty list is
|
|
271
|
+
// returned if no Server matches the criteria specified by the Client.
|
|
272
|
+
// This Service shall not require message security but it may require transport layer security.
|
|
273
|
+
// Each time the Discovery Server creates or updates a record in its cache it shall assign a
|
|
274
|
+
// monotonically increasing identifier to the record. This allows Clients to request records in batches
|
|
275
|
+
// by specifying the identifier for the last record received in the last call to FindServersOnNetwork.
|
|
276
|
+
// To support this the Discovery Server shall return records in numerical order starting from the
|
|
277
|
+
// lowest record identifier. The Discovery Server shall also return the last time the counter was reset
|
|
278
|
+
// for example due to a restart of the Discovery Server. If a Client detects that this time is more
|
|
279
|
+
// recent than the last time the Client called the Service it shall call the Service again with a
|
|
280
|
+
// startingRecordId of 0.
|
|
281
|
+
// This Service can be used without security and it is therefore vulnerable to Denial Of Service
|
|
282
|
+
// (DOS) attacks. A Server should minimize the amount of processing required to send the response
|
|
283
|
+
// for this Service. This can be achieved by preparing the result in advance
|
|
284
|
+
|
|
285
|
+
assert(message.request instanceof FindServersOnNetworkRequest);
|
|
286
|
+
const request = message.request as FindServersOnNetworkRequest;
|
|
287
|
+
|
|
288
|
+
assert(request.schema.name === "FindServersOnNetworkRequest");
|
|
289
|
+
|
|
290
|
+
function sendError(statusCode: StatusCode) {
|
|
291
|
+
const response1 = new FindServersOnNetworkResponse({ responseHeader: { serviceResult: statusCode } });
|
|
292
|
+
return channel.send_response("MSG", response1, message);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// startingRecordId Counter Only records with an identifier greater than this number will be
|
|
296
|
+
// returned.
|
|
297
|
+
// Specify 0 to start with the first record in the cache.
|
|
298
|
+
// maxRecordsToReturn UInt32 The maximum number of records to return in the response.
|
|
299
|
+
// 0 indicates that there is no limit.
|
|
300
|
+
// serverCapabilityFilter[] String List of Server capability filters. The set of allowed server capabilities
|
|
301
|
+
// are defined in Part 12.
|
|
302
|
+
// Only records with all of the specified server capabilities are
|
|
303
|
+
// returned.
|
|
304
|
+
// The comparison is case insensitive.
|
|
305
|
+
// If this list is empty then no filtering is performed
|
|
306
|
+
|
|
307
|
+
// ------------------------
|
|
308
|
+
|
|
309
|
+
// The last time the counters were reset.
|
|
310
|
+
const lastCounterResetTime = new Date();
|
|
311
|
+
|
|
312
|
+
// servers[] ServerOnNetwork List of DNS service records that meet criteria specified in the
|
|
313
|
+
// request. This list is empty if no Servers meet the criteria
|
|
314
|
+
const servers = [];
|
|
315
|
+
|
|
316
|
+
request.serverCapabilityFilter = request.serverCapabilityFilter || [];
|
|
317
|
+
const serverCapabilityFilter: string = request.serverCapabilityFilter
|
|
318
|
+
.map((x: UAString) => x!.toUpperCase())
|
|
319
|
+
.sort()
|
|
320
|
+
.join(" ");
|
|
321
|
+
|
|
322
|
+
debugLog(" startingRecordId = ", request.startingRecordId);
|
|
323
|
+
|
|
324
|
+
if (this.mDnsResponder) {
|
|
325
|
+
for (const server of this.mDnsResponder.registeredServers) {
|
|
326
|
+
if (server.recordId <= request.startingRecordId) {
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
if (!hasCapabilities(server.serverCapabilities, serverCapabilityFilter)) {
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
servers.push(server);
|
|
333
|
+
if (servers.length === request.maxRecordsToReturn) {
|
|
334
|
+
break;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
const response = new FindServersOnNetworkResponse({
|
|
339
|
+
lastCounterResetTime, // UtcTime The last time the counters were reset
|
|
340
|
+
servers
|
|
341
|
+
});
|
|
342
|
+
channel.send_response("MSG", response, message);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
private async __internalRegisterServerWithCallback(
|
|
346
|
+
RegisterServerXResponse: any /* RegisterServer2Response | RegisterServerResponse */,
|
|
347
|
+
rawServer: RegisteredServer,
|
|
348
|
+
discoveryConfigurations: MdnsDiscoveryConfiguration[] | undefined,
|
|
349
|
+
callback: (err: Error | null, response?: Response) => void
|
|
350
|
+
) {
|
|
351
|
+
// istanbul ignore next
|
|
352
|
+
callback(new Error("internal Error"));
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
private async __internalRegisterServer(
|
|
356
|
+
RegisterServerXResponse: any /* RegisterServer2Response | RegisterServerResponse */,
|
|
357
|
+
rawServer: RegisteredServer,
|
|
358
|
+
discoveryConfigurations?: MdnsDiscoveryConfiguration[]
|
|
359
|
+
): Promise<Response> {
|
|
360
|
+
const server = rawServer as any as RegisteredServerExtended;
|
|
361
|
+
|
|
362
|
+
if (!discoveryConfigurations) {
|
|
363
|
+
discoveryConfigurations = [
|
|
364
|
+
new MdnsDiscoveryConfiguration({
|
|
365
|
+
mdnsServerName: undefined,
|
|
366
|
+
serverCapabilities: ["NA"]
|
|
367
|
+
})
|
|
368
|
+
];
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function sendError(statusCode: StatusCode): Response {
|
|
372
|
+
debugLog(chalk.red("_on_RegisterServer(2)Request error"), statusCode.toString());
|
|
373
|
+
const response1 = new RegisterServerXResponse({
|
|
374
|
+
responseHeader: { serviceResult: statusCode }
|
|
375
|
+
});
|
|
376
|
+
return response1;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
async function _stop_announcedOnMulticastSubnet(conf: MdnsDiscoveryConfiguration): Promise<void> {
|
|
380
|
+
const b = (conf as any).bonjourHolder as BonjourHolder;
|
|
381
|
+
await b._stop_announcedOnMulticastSubnet();
|
|
382
|
+
(conf as any).bonjourHolder = undefined;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
async function _announcedOnMulticastSubnet(conf: MdnsDiscoveryConfiguration, announcement: Announcement): Promise<void> {
|
|
386
|
+
let b = (conf as any).bonjourHolder as BonjourHolder;
|
|
387
|
+
if (b && b.announcement) {
|
|
388
|
+
if (sameAnnouncement(b.announcement, announcement)) {
|
|
389
|
+
debugLog("Configuration ", conf.mdnsServerName, " has not changed !");
|
|
390
|
+
// nothing to do
|
|
391
|
+
return;
|
|
392
|
+
} else {
|
|
393
|
+
debugLog("Configuration ", conf.mdnsServerName, " HAS changed !");
|
|
394
|
+
debugLog(" Was ", b.announcement);
|
|
395
|
+
debugLog(" is ", announcement);
|
|
396
|
+
}
|
|
397
|
+
await _stop_announcedOnMulticastSubnet(conf);
|
|
398
|
+
}
|
|
399
|
+
b = new BonjourHolder();
|
|
400
|
+
(conf as any).bonjourHolder = b;
|
|
401
|
+
await b._announcedOnMulticastSubnet(announcement);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
async function dealWithDiscoveryConfiguration(
|
|
405
|
+
previousConfMap: any,
|
|
406
|
+
server1: RegisteredServer,
|
|
407
|
+
serverInfo: ApplicationDescriptionOptions,
|
|
408
|
+
discoveryConfiguration: MdnsDiscoveryConfiguration
|
|
409
|
+
): Promise<StatusCode> {
|
|
410
|
+
// mdnsServerName String The name of the Server when it is announced via mDNS.
|
|
411
|
+
// See Part 12 for the details about mDNS. This string shall be less than 64 bytes.
|
|
412
|
+
// If not specified the first element of the serverNames array is used
|
|
413
|
+
// (truncated to 63 bytes if necessary).
|
|
414
|
+
// serverCapabilities [] String The set of Server capabilities supported by the Server.
|
|
415
|
+
// A Server capability is a short identifier for a feature
|
|
416
|
+
// The set of allowed Server capabilities are defined in Part 12.
|
|
417
|
+
discoveryConfiguration.mdnsServerName = discoveryConfiguration.mdnsServerName || server1.serverNames![0].text;
|
|
418
|
+
|
|
419
|
+
serverInfo.discoveryUrls = serverInfo.discoveryUrls || [];
|
|
420
|
+
|
|
421
|
+
const endpointUrl = serverInfo.discoveryUrls[0]!;
|
|
422
|
+
const parsedUrl = url.parse(endpointUrl);
|
|
423
|
+
|
|
424
|
+
discoveryConfiguration.serverCapabilities = discoveryConfiguration.serverCapabilities || [];
|
|
425
|
+
const announcement = {
|
|
426
|
+
capabilities: discoveryConfiguration.serverCapabilities.map((x: UAString) => x!) || ["DA"],
|
|
427
|
+
name: discoveryConfiguration.mdnsServerName!,
|
|
428
|
+
path: parsedUrl.pathname || "/",
|
|
429
|
+
port: parseInt(parsedUrl.port!, 10)
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
if (previousConfMap[discoveryConfiguration.mdnsServerName!]) {
|
|
433
|
+
// configuration already exists
|
|
434
|
+
debugLog("Configuration ", discoveryConfiguration.mdnsServerName, " already exists !");
|
|
435
|
+
const prevConf = previousConfMap[discoveryConfiguration.mdnsServerName!];
|
|
436
|
+
delete previousConfMap[discoveryConfiguration.mdnsServerName!];
|
|
437
|
+
(discoveryConfiguration as any).bonjourHolder = prevConf.bonjourHolder;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// let's announce the server on the multicast DNS
|
|
441
|
+
await _announcedOnMulticastSubnet(discoveryConfiguration, announcement);
|
|
442
|
+
return StatusCodes.Good;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// check serverType is valid
|
|
446
|
+
if (!_isValidServerType(server.serverType)) {
|
|
447
|
+
debugLog("Invalid server Type", ApplicationType[server.serverType]);
|
|
448
|
+
return sendError(StatusCodes.BadInvalidArgument);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
if (!server.serverUri) {
|
|
452
|
+
debugLog("Missing serverURI");
|
|
453
|
+
return sendError(StatusCodes.BadInvalidArgument);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// BadServerUriInvalid
|
|
457
|
+
// TODO
|
|
458
|
+
server.serverNames = server.serverNames || [];
|
|
459
|
+
// BadServerNameMissing
|
|
460
|
+
if (server.serverNames.length === 0 || !server.serverNames[0].text) {
|
|
461
|
+
return sendError(StatusCodes.BadServerNameMissing);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// BadDiscoveryUrlMissing
|
|
465
|
+
server.discoveryUrls = server.discoveryUrls || [];
|
|
466
|
+
if (server.discoveryUrls.length === 0 || !server.discoveryUrls[0]) {
|
|
467
|
+
return sendError(StatusCodes.BadDiscoveryUrlMissing);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
const key = server.serverUri;
|
|
471
|
+
let configurationResults: StatusCode[] | null = null;
|
|
472
|
+
|
|
473
|
+
if (server.isOnline) {
|
|
474
|
+
debugLog(chalk.cyan(" registering server : "), chalk.yellow(server.serverUri));
|
|
475
|
+
|
|
476
|
+
// prepare serverInfo which will be used by FindServers
|
|
477
|
+
const serverInfo: ApplicationDescriptionOptions = {
|
|
478
|
+
applicationName: server.serverNames[0], // which one shall we use ?
|
|
479
|
+
applicationType: server.serverType,
|
|
480
|
+
applicationUri: server.serverUri,
|
|
481
|
+
discoveryUrls: server.discoveryUrls,
|
|
482
|
+
gatewayServerUri: server.gatewayServerUri,
|
|
483
|
+
productUri: server.productUri
|
|
484
|
+
// XXX ?????? serverInfo.discoveryProfileUri = serverInfo.discoveryProfileUri;
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
const previousConfMap: any = [];
|
|
488
|
+
if (this.registeredServers[key]) {
|
|
489
|
+
// server already exists and must only be updated
|
|
490
|
+
const previousServer = this.registeredServers[key];
|
|
491
|
+
|
|
492
|
+
for (const conf of previousServer.discoveryConfiguration!) {
|
|
493
|
+
previousConfMap[conf.mdnsServerName!] = conf;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
this.registeredServers[key] = server;
|
|
497
|
+
|
|
498
|
+
// xx server.semaphoreFilePath = server.semaphoreFilePath;
|
|
499
|
+
// xx server.serverNames = server.serverNames;
|
|
500
|
+
server.serverInfo = serverInfo;
|
|
501
|
+
server.discoveryConfiguration = discoveryConfigurations;
|
|
502
|
+
|
|
503
|
+
assert(discoveryConfigurations);
|
|
504
|
+
|
|
505
|
+
configurationResults = [];
|
|
506
|
+
for (const conf of discoveryConfigurations) {
|
|
507
|
+
const statusCode = await dealWithDiscoveryConfiguration(previousConfMap, server, serverInfo, conf);
|
|
508
|
+
configurationResults.push(statusCode);
|
|
509
|
+
}
|
|
510
|
+
// now also unregister unprocessed
|
|
511
|
+
if (Object.keys(previousConfMap).length !== 0) {
|
|
512
|
+
debugLog(" Warning some conf need to be removed !");
|
|
513
|
+
}
|
|
514
|
+
} else {
|
|
515
|
+
// server is announced offline
|
|
516
|
+
if (key in this.registeredServers) {
|
|
517
|
+
const server1 = this.registeredServers[key];
|
|
518
|
+
debugLog(chalk.cyan("unregistering server : "), chalk.yellow(server1.serverUri!));
|
|
519
|
+
configurationResults = [];
|
|
520
|
+
|
|
521
|
+
discoveryConfigurations = server1.discoveryConfiguration || [];
|
|
522
|
+
|
|
523
|
+
for (const conf of discoveryConfigurations) {
|
|
524
|
+
await _stop_announcedOnMulticastSubnet(conf);
|
|
525
|
+
configurationResults.push(StatusCodes.Good);
|
|
526
|
+
}
|
|
527
|
+
delete this.registeredServers[key];
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
const response = new RegisterServerXResponse({
|
|
532
|
+
configurationResults
|
|
533
|
+
});
|
|
534
|
+
return response;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/*== private
|
|
539
|
+
* returns true if the serverType can be added to a discovery server.
|
|
540
|
+
* @param serverType
|
|
541
|
+
* @return {boolean}
|
|
542
|
+
* @private
|
|
543
|
+
*/
|
|
544
|
+
function _isValidServerType(serverType: ApplicationType): boolean {
|
|
545
|
+
switch (serverType) {
|
|
546
|
+
case ApplicationType.Client:
|
|
547
|
+
return false;
|
|
548
|
+
case ApplicationType.Server:
|
|
549
|
+
case ApplicationType.ClientAndServer:
|
|
550
|
+
case ApplicationType.DiscoveryServer:
|
|
551
|
+
return true;
|
|
552
|
+
}
|
|
553
|
+
return false;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
(OPCUADiscoveryServer as any).prototype.__internalRegisterServerWithCallback = callbackify(
|
|
557
|
+
(OPCUADiscoveryServer as any).prototype.__internalRegisterServer
|
|
558
|
+
);
|
|
559
|
+
|
|
560
|
+
// tslint:disable-next-line: no-var-requires
|
|
561
|
+
const thenify = require("thenify");
|
|
562
|
+
const opts = { multiArgs: false };
|
|
563
|
+
OPCUADiscoveryServer.prototype.start = thenify.withCallback(OPCUADiscoveryServer.prototype.start, opts);
|
|
564
|
+
OPCUADiscoveryServer.prototype.shutdown = thenify.withCallback(OPCUADiscoveryServer.prototype.shutdown, opts);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require("node-opcua-pki/bin/crypto_create_CA");
|