node-opcua-server 2.163.1 → 2.165.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/dist/addressSpace_accessor.js +7 -5
- package/dist/addressSpace_accessor.js.map +1 -1
- package/dist/base_server.d.ts +95 -18
- package/dist/base_server.js +217 -69
- package/dist/base_server.js.map +1 -1
- package/dist/monitored_item.js +32 -32
- package/dist/monitored_item.js.map +1 -1
- package/dist/node_sampler.js +1 -1
- package/dist/node_sampler.js.map +1 -1
- package/dist/opcua_server.d.ts +171 -123
- package/dist/opcua_server.js +466 -223
- package/dist/opcua_server.js.map +1 -1
- package/dist/register_server_manager.d.ts +2 -2
- package/dist/register_server_manager.js +12 -6
- package/dist/register_server_manager.js.map +1 -1
- package/dist/register_server_manager_mdns_only.js +1 -1
- package/dist/register_server_manager_mdns_only.js.map +1 -1
- package/dist/server_end_point.d.ts +108 -12
- package/dist/server_end_point.js +157 -57
- package/dist/server_end_point.js.map +1 -1
- package/dist/server_engine.d.ts +32 -14
- package/dist/server_engine.js +160 -59
- package/dist/server_engine.js.map +1 -1
- package/dist/server_publish_engine.js +5 -5
- package/dist/server_publish_engine.js.map +1 -1
- package/dist/server_session.js +4 -4
- package/dist/server_session.js.map +1 -1
- package/dist/server_subscription.js +18 -18
- package/dist/server_subscription.js.map +1 -1
- package/dist/sessions_compatible_for_transfer.js +1 -1
- package/dist/sessions_compatible_for_transfer.js.map +1 -1
- package/package.json +47 -47
- package/source/addressSpace_accessor.ts +6 -3
- package/source/base_server.ts +252 -90
- package/source/monitored_item.ts +32 -32
- package/source/node_sampler.ts +1 -1
- package/source/opcua_server.ts +674 -489
- package/source/register_server_manager.ts +11 -5
- package/source/register_server_manager_mdns_only.ts +1 -1
- package/source/server_end_point.ts +278 -94
- package/source/server_engine.ts +246 -135
- package/source/server_publish_engine.ts +5 -5
- package/source/server_session.ts +4 -4
- package/source/server_subscription.ts +18 -18
- package/source/sessions_compatible_for_transfer.ts +4 -2
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @module node-opcua-server
|
|
3
3
|
*/
|
|
4
|
-
import { EventEmitter } from "events";
|
|
5
|
-
import { OPCUACertificateManager } from "node-opcua-certificate-manager";
|
|
6
|
-
import { Certificate, PrivateKey } from "node-opcua-crypto/web";
|
|
7
|
-
import { MessageSecurityMode, SecurityPolicy, ServerSecureChannelLayer, ServerSecureChannelParent } from "node-opcua-secure-channel";
|
|
8
|
-
import { UserTokenType } from "node-opcua-service-endpoints";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
4
|
+
import { EventEmitter } from "node:events";
|
|
5
|
+
import type { OPCUACertificateManager } from "node-opcua-certificate-manager";
|
|
6
|
+
import { type Certificate, type PrivateKey } from "node-opcua-crypto/web";
|
|
7
|
+
import { MessageSecurityMode, SecurityPolicy, ServerSecureChannelLayer, type ServerSecureChannelParent } from "node-opcua-secure-channel";
|
|
8
|
+
import { ApplicationDescription, EndpointDescription, UserTokenType } from "node-opcua-service-endpoints";
|
|
9
|
+
import type { IHelloAckLimits } from "node-opcua-transport";
|
|
10
|
+
import type { IChannelData } from "./i_channel_data";
|
|
11
|
+
import type { ISocketData } from "./i_socket_data";
|
|
12
12
|
export interface OPCUAServerEndPointOptions {
|
|
13
13
|
/**
|
|
14
14
|
* the tcp port
|
|
@@ -42,7 +42,7 @@ export interface OPCUAServerEndPointOptions {
|
|
|
42
42
|
*/
|
|
43
43
|
timeout?: number;
|
|
44
44
|
serverInfo: ApplicationDescription;
|
|
45
|
-
objectFactory?:
|
|
45
|
+
objectFactory?: unknown;
|
|
46
46
|
transportSettings?: IServerTransportSettings;
|
|
47
47
|
}
|
|
48
48
|
export interface IServerTransportSettings {
|
|
@@ -54,9 +54,66 @@ export interface EndpointDescriptionParams {
|
|
|
54
54
|
resourcePath?: string;
|
|
55
55
|
alternateHostname?: string[];
|
|
56
56
|
hostname: string;
|
|
57
|
+
/**
|
|
58
|
+
* Override the port used in the endpoint URL.
|
|
59
|
+
* When set, the endpoint URL uses this port instead of the
|
|
60
|
+
* server's listen port. The server does NOT listen on this port.
|
|
61
|
+
* Useful for Docker port-mapping, reverse proxies, and NAT.
|
|
62
|
+
*/
|
|
63
|
+
advertisedPort?: number;
|
|
57
64
|
securityPolicies: SecurityPolicy[];
|
|
58
65
|
userTokenTypes: UserTokenType[];
|
|
59
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* Per-URL security overrides for advertised endpoints.
|
|
69
|
+
*
|
|
70
|
+
* When `advertisedEndpoints` contains a config object, the endpoint
|
|
71
|
+
* descriptions generated for that URL use the overridden security
|
|
72
|
+
* settings instead of inheriting from the main endpoint.
|
|
73
|
+
*
|
|
74
|
+
* Any field that is omitted falls back to the main endpoint's value.
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```ts
|
|
78
|
+
* advertisedEndpoints: [
|
|
79
|
+
* // Public: SignAndEncrypt only, no anonymous
|
|
80
|
+
* {
|
|
81
|
+
* url: "opc.tcp://public.example.com:4840",
|
|
82
|
+
* securityModes: [MessageSecurityMode.SignAndEncrypt],
|
|
83
|
+
* allowAnonymous: false
|
|
84
|
+
* },
|
|
85
|
+
* // Internal: inherits everything from main endpoint
|
|
86
|
+
* "opc.tcp://internal:48480"
|
|
87
|
+
* ]
|
|
88
|
+
* ```
|
|
89
|
+
*/
|
|
90
|
+
export interface AdvertisedEndpointConfig {
|
|
91
|
+
/** The full endpoint URL, e.g. `"opc.tcp://public.example.com:4840"` */
|
|
92
|
+
url: string;
|
|
93
|
+
/** Override security modes (default: inherit from main endpoint) */
|
|
94
|
+
securityModes?: MessageSecurityMode[];
|
|
95
|
+
/** Override security policies (default: inherit from main endpoint) */
|
|
96
|
+
securityPolicies?: SecurityPolicy[];
|
|
97
|
+
/** Override anonymous access (default: inherit from main endpoint) */
|
|
98
|
+
allowAnonymous?: boolean;
|
|
99
|
+
/** Override user token types (default: inherit from main endpoint) */
|
|
100
|
+
userTokenTypes?: UserTokenType[];
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* An advertised endpoint entry — either a plain URL string (inherits
|
|
104
|
+
* all settings from the main endpoint) or a config object with
|
|
105
|
+
* per-URL security overrides.
|
|
106
|
+
*/
|
|
107
|
+
export type AdvertisedEndpoint = string | AdvertisedEndpointConfig;
|
|
108
|
+
/**
|
|
109
|
+
* Normalize any `advertisedEndpoints` input into a uniform
|
|
110
|
+
* `AdvertisedEndpointConfig[]`.
|
|
111
|
+
*
|
|
112
|
+
* This coercion is done early so that all downstream code
|
|
113
|
+
* (endpoint generation, IP/hostname extraction) only deals
|
|
114
|
+
* with one type.
|
|
115
|
+
*/
|
|
116
|
+
export declare function normalizeAdvertisedEndpoints(raw?: AdvertisedEndpoint | AdvertisedEndpoint[]): AdvertisedEndpointConfig[];
|
|
60
117
|
export interface AddStandardEndpointDescriptionsParam {
|
|
61
118
|
allowAnonymous?: boolean;
|
|
62
119
|
disableDiscovery?: boolean;
|
|
@@ -68,7 +125,46 @@ export interface AddStandardEndpointDescriptionsParam {
|
|
|
68
125
|
hostname?: string;
|
|
69
126
|
securityPolicies?: SecurityPolicy[];
|
|
70
127
|
userTokenTypes?: UserTokenType[];
|
|
128
|
+
/**
|
|
129
|
+
* Additional endpoint URL(s) to advertise.
|
|
130
|
+
*
|
|
131
|
+
* Use when the server is behind Docker port-mapping,
|
|
132
|
+
* a reverse proxy, or a NAT gateway.
|
|
133
|
+
*
|
|
134
|
+
* Each entry can be a plain URL string (inherits all security
|
|
135
|
+
* settings from the main endpoint) or an
|
|
136
|
+
* `AdvertisedEndpointConfig` object with per-URL overrides.
|
|
137
|
+
*
|
|
138
|
+
* The server still listens on `port` — these are purely
|
|
139
|
+
* advertised aliases.
|
|
140
|
+
*
|
|
141
|
+
* @example Simple string (inherits main settings)
|
|
142
|
+
* ```ts
|
|
143
|
+
* advertisedEndpoints: "opc.tcp://localhost:48481"
|
|
144
|
+
* ```
|
|
145
|
+
*
|
|
146
|
+
* @example Mixed array with per-URL security overrides
|
|
147
|
+
* ```ts
|
|
148
|
+
* advertisedEndpoints: [
|
|
149
|
+
* "opc.tcp://internal:48480",
|
|
150
|
+
* {
|
|
151
|
+
* url: "opc.tcp://public.example.com:4840",
|
|
152
|
+
* securityModes: [MessageSecurityMode.SignAndEncrypt],
|
|
153
|
+
* allowAnonymous: false
|
|
154
|
+
* }
|
|
155
|
+
* ]
|
|
156
|
+
* ```
|
|
157
|
+
*/
|
|
158
|
+
advertisedEndpoints?: AdvertisedEndpoint | AdvertisedEndpoint[];
|
|
71
159
|
}
|
|
160
|
+
/**
|
|
161
|
+
* Parse an `opc.tcp://hostname:port` URL and extract hostname and port.
|
|
162
|
+
* @internal
|
|
163
|
+
*/
|
|
164
|
+
export declare function parseOpcTcpUrl(url: string): {
|
|
165
|
+
hostname: string;
|
|
166
|
+
port: number;
|
|
167
|
+
};
|
|
72
168
|
/**
|
|
73
169
|
* OPCUAServerEndPoint a Server EndPoint.
|
|
74
170
|
* A sever end point is listening to one port
|
|
@@ -90,11 +186,11 @@ export declare class OPCUAServerEndPoint extends EventEmitter implements ServerS
|
|
|
90
186
|
transactionsCountOldChannels: number;
|
|
91
187
|
securityTokenCountOldChannels: number;
|
|
92
188
|
serverInfo: ApplicationDescription;
|
|
93
|
-
objectFactory:
|
|
189
|
+
objectFactory: unknown;
|
|
94
190
|
_on_new_channel?: (channel: ServerSecureChannelLayer) => void;
|
|
95
191
|
_on_close_channel?: (channel: ServerSecureChannelLayer) => void;
|
|
96
|
-
_on_connectionRefused?: (socketData:
|
|
97
|
-
_on_openSecureChannelFailure?: (socketData:
|
|
192
|
+
_on_connectionRefused?: (socketData: ISocketData) => void;
|
|
193
|
+
_on_openSecureChannelFailure?: (socketData: ISocketData, channelData: IChannelData) => void;
|
|
98
194
|
private _certificateChain;
|
|
99
195
|
private _privateKey;
|
|
100
196
|
private _channels;
|
package/dist/server_end_point.js
CHANGED
|
@@ -1,31 +1,30 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.OPCUAServerEndPoint = void 0;
|
|
7
2
|
/* eslint-disable max-statements */
|
|
8
3
|
/**
|
|
9
4
|
* @module node-opcua-server
|
|
10
5
|
*/
|
|
11
6
|
// tslint:disable:no-console
|
|
12
|
-
|
|
13
|
-
|
|
7
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
8
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
9
|
+
};
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.OPCUAServerEndPoint = void 0;
|
|
12
|
+
exports.normalizeAdvertisedEndpoints = normalizeAdvertisedEndpoints;
|
|
13
|
+
exports.parseOpcTcpUrl = parseOpcTcpUrl;
|
|
14
|
+
const node_events_1 = require("node:events");
|
|
15
|
+
const node_net_1 = __importDefault(require("node:net"));
|
|
14
16
|
const chalk_1 = __importDefault(require("chalk"));
|
|
15
|
-
const async_1 = __importDefault(require("async"));
|
|
16
17
|
const node_opcua_assert_1 = require("node-opcua-assert");
|
|
17
18
|
const web_1 = require("node-opcua-crypto/web");
|
|
18
19
|
const node_opcua_debug_1 = require("node-opcua-debug");
|
|
19
20
|
const node_opcua_hostname_1 = require("node-opcua-hostname");
|
|
20
21
|
const node_opcua_secure_channel_1 = require("node-opcua-secure-channel");
|
|
21
22
|
const node_opcua_service_endpoints_1 = require("node-opcua-service-endpoints");
|
|
22
|
-
const node_opcua_service_endpoints_2 = require("node-opcua-service-endpoints");
|
|
23
|
-
const node_opcua_service_endpoints_3 = require("node-opcua-service-endpoints");
|
|
24
23
|
const debugLog = (0, node_opcua_debug_1.make_debugLog)(__filename);
|
|
25
24
|
const errorLog = (0, node_opcua_debug_1.make_errorLog)(__filename);
|
|
26
25
|
const warningLog = (0, node_opcua_debug_1.make_warningLog)(__filename);
|
|
27
26
|
const doDebug = (0, node_opcua_debug_1.checkDebugFlag)(__filename);
|
|
28
|
-
const
|
|
27
|
+
const UATCP_UASC_UABINARY = "http://opcfoundation.org/UA-Profile/Transport/uatcp-uasc-uabinary";
|
|
29
28
|
function extractSocketData(socket, reason) {
|
|
30
29
|
const { bytesRead, bytesWritten, remoteAddress, remoteFamily, remotePort, localAddress, localPort } = socket;
|
|
31
30
|
const data = {
|
|
@@ -68,6 +67,7 @@ function dumpChannelInfo(channels) {
|
|
|
68
67
|
console.log(" bytesRead = ", channel.bytesRead);
|
|
69
68
|
console.log(" sessions = ", Object.keys(channel.sessionTokens).length);
|
|
70
69
|
console.log(Object.values(channel.sessionTokens).map(d).join("\n"));
|
|
70
|
+
// biome-ignore lint/suspicious/noExplicitAny: accessing internal transport for debug dump
|
|
71
71
|
const socket = channel.transport?._socket;
|
|
72
72
|
if (!socket) {
|
|
73
73
|
console.log(" SOCKET IS CLOSED");
|
|
@@ -79,15 +79,43 @@ function dumpChannelInfo(channels) {
|
|
|
79
79
|
console.log("------------------------------------------------------");
|
|
80
80
|
}
|
|
81
81
|
const emptyCertificate = Buffer.alloc(0);
|
|
82
|
+
// biome-ignore lint/suspicious/noExplicitAny: deliberate null→PrivateKey sentinel
|
|
82
83
|
const emptyPrivateKey = null;
|
|
83
84
|
let OPCUAServerEndPointCounter = 0;
|
|
85
|
+
/**
|
|
86
|
+
* Normalize any `advertisedEndpoints` input into a uniform
|
|
87
|
+
* `AdvertisedEndpointConfig[]`.
|
|
88
|
+
*
|
|
89
|
+
* This coercion is done early so that all downstream code
|
|
90
|
+
* (endpoint generation, IP/hostname extraction) only deals
|
|
91
|
+
* with one type.
|
|
92
|
+
*/
|
|
93
|
+
function normalizeAdvertisedEndpoints(raw) {
|
|
94
|
+
if (!raw)
|
|
95
|
+
return [];
|
|
96
|
+
const arr = Array.isArray(raw) ? raw : [raw];
|
|
97
|
+
return arr.map((entry) => (typeof entry === "string" ? { url: entry } : entry));
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Parse an `opc.tcp://hostname:port` URL and extract hostname and port.
|
|
101
|
+
* @internal
|
|
102
|
+
*/
|
|
103
|
+
function parseOpcTcpUrl(url) {
|
|
104
|
+
// URL class doesn't understand opc.tcp://, so swap to http://
|
|
105
|
+
const httpUrl = url.replace(/^opc\.tcp:\/\//i, "http://");
|
|
106
|
+
const parsed = new URL(httpUrl);
|
|
107
|
+
return {
|
|
108
|
+
hostname: parsed.hostname,
|
|
109
|
+
port: parsed.port ? Number.parseInt(parsed.port, 10) : 4840
|
|
110
|
+
};
|
|
111
|
+
}
|
|
84
112
|
function getUniqueName(name, collection) {
|
|
85
113
|
if (collection[name]) {
|
|
86
114
|
let counter = 0;
|
|
87
|
-
while (collection[name
|
|
115
|
+
while (collection[`${name}_${counter.toString()}`]) {
|
|
88
116
|
counter++;
|
|
89
117
|
}
|
|
90
|
-
name = name
|
|
118
|
+
name = `${name}_${counter.toString()}`;
|
|
91
119
|
collection[name] = 1;
|
|
92
120
|
return name;
|
|
93
121
|
}
|
|
@@ -102,7 +130,7 @@ function getUniqueName(name, collection) {
|
|
|
102
130
|
* note:
|
|
103
131
|
* see OPCUA Release 1.03 part 4 page 108 7.1 ApplicationDescription
|
|
104
132
|
*/
|
|
105
|
-
class OPCUAServerEndPoint extends
|
|
133
|
+
class OPCUAServerEndPoint extends node_events_1.EventEmitter {
|
|
106
134
|
/**
|
|
107
135
|
* the tcp port
|
|
108
136
|
*/
|
|
@@ -165,7 +193,7 @@ class OPCUAServerEndPoint extends events_1.EventEmitter {
|
|
|
165
193
|
this._privateKey = emptyPrivateKey;
|
|
166
194
|
(0, node_opcua_assert_1.assert)(Object.keys(this._channels).length === 0, "OPCUAServerEndPoint channels must have been deleted");
|
|
167
195
|
this._channels = {};
|
|
168
|
-
this.serverInfo = new
|
|
196
|
+
this.serverInfo = new node_opcua_service_endpoints_1.ApplicationDescription({});
|
|
169
197
|
this._endpoints = [];
|
|
170
198
|
(0, node_opcua_assert_1.assert)(this._endpoints.length === 0, "endpoints must have been deleted");
|
|
171
199
|
this._endpoints = [];
|
|
@@ -225,11 +253,11 @@ class OPCUAServerEndPoint extends events_1.EventEmitter {
|
|
|
225
253
|
return arr.length === 0 ? null : arr[0];
|
|
226
254
|
}
|
|
227
255
|
addEndpointDescription(securityMode, securityPolicy, options) {
|
|
228
|
-
//
|
|
256
|
+
// c8 ignore next
|
|
229
257
|
if (securityMode === node_opcua_secure_channel_1.MessageSecurityMode.None && securityPolicy !== node_opcua_secure_channel_1.SecurityPolicy.None) {
|
|
230
258
|
throw new Error(" invalid security ");
|
|
231
259
|
}
|
|
232
|
-
//
|
|
260
|
+
// c8 ignore next
|
|
233
261
|
if (securityMode !== node_opcua_secure_channel_1.MessageSecurityMode.None && securityPolicy === node_opcua_secure_channel_1.SecurityPolicy.None) {
|
|
234
262
|
throw new Error(" invalid security ");
|
|
235
263
|
}
|
|
@@ -238,9 +266,10 @@ class OPCUAServerEndPoint extends events_1.EventEmitter {
|
|
|
238
266
|
const resourcePath = (options.resourcePath || "").replace(/\\/g, "/");
|
|
239
267
|
(0, node_opcua_assert_1.assert)(resourcePath.length === 0 || resourcePath.charAt(0) === "/", "resourcePath should start with /");
|
|
240
268
|
const hostname = options.hostname || (0, node_opcua_hostname_1.getFullyQualifiedDomainName)();
|
|
241
|
-
const
|
|
269
|
+
const effectivePort = options.advertisedPort ?? this.port;
|
|
270
|
+
const endpointUrl = `opc.tcp://${hostname}:${effectivePort}${resourcePath}`;
|
|
242
271
|
const endpoint_desc = this.getEndpointDescription(securityMode, securityPolicy, endpointUrl);
|
|
243
|
-
//
|
|
272
|
+
// c8 ignore next
|
|
244
273
|
if (endpoint_desc) {
|
|
245
274
|
throw new Error(" endpoint already exist");
|
|
246
275
|
}
|
|
@@ -257,13 +286,14 @@ class OPCUAServerEndPoint extends events_1.EventEmitter {
|
|
|
257
286
|
resourcePath: options.resourcePath,
|
|
258
287
|
restricted: !!options.restricted,
|
|
259
288
|
securityPolicies: options.securityPolicies || [],
|
|
289
|
+
advertisedPort: options.advertisedPort,
|
|
260
290
|
userTokenTypes
|
|
261
291
|
}, this));
|
|
262
292
|
}
|
|
263
293
|
addRestrictedEndpointDescription(options) {
|
|
264
294
|
options = { ...options };
|
|
265
295
|
options.restricted = true;
|
|
266
|
-
|
|
296
|
+
this.addEndpointDescription(node_opcua_secure_channel_1.MessageSecurityMode.None, node_opcua_secure_channel_1.SecurityPolicy.None, options);
|
|
267
297
|
}
|
|
268
298
|
addStandardEndpointDescriptions(options) {
|
|
269
299
|
options = options || {};
|
|
@@ -312,6 +342,59 @@ class OPCUAServerEndPoint extends events_1.EventEmitter {
|
|
|
312
342
|
}
|
|
313
343
|
}
|
|
314
344
|
}
|
|
345
|
+
// ── Advertised endpoints (virtual — no TCP listener) ──────
|
|
346
|
+
// Normalize to AdvertisedEndpointConfig[] so downstream code
|
|
347
|
+
// only deals with one type.
|
|
348
|
+
const advertisedList = normalizeAdvertisedEndpoints(options.advertisedEndpoints);
|
|
349
|
+
// Main endpoint defaults (guaranteed non-null — assigned above)
|
|
350
|
+
const mainSecurityModes = options.securityModes || defaultSecurityModes;
|
|
351
|
+
const mainSecurityPolicies = options.securityPolicies || defaultSecurityPolicies;
|
|
352
|
+
const mainUserTokenTypes = options.userTokenTypes || defaultUserTokenTypes;
|
|
353
|
+
for (const config of advertisedList) {
|
|
354
|
+
const { hostname: advHostname, port: advPort } = parseOpcTcpUrl(config.url);
|
|
355
|
+
// Skip if this hostname+port combo was already covered
|
|
356
|
+
// by the regular hostname loop (same hostname, same port)
|
|
357
|
+
if (hostnames.some((h) => h.toLowerCase() === advHostname.toLowerCase()) && advPort === this.port) {
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
// Per-URL security overrides — fall back to main settings
|
|
361
|
+
const entrySecurityModes = config.securityModes ?? mainSecurityModes;
|
|
362
|
+
const entrySecurityPolicies = config.securityPolicies ?? mainSecurityPolicies;
|
|
363
|
+
let entryUserTokenTypes = config.userTokenTypes ?? mainUserTokenTypes;
|
|
364
|
+
// Handle allowAnonymous override: if explicitly false,
|
|
365
|
+
// filter out Anonymous even if the main config allows it
|
|
366
|
+
if (config.allowAnonymous === false) {
|
|
367
|
+
entryUserTokenTypes = entryUserTokenTypes.filter((t) => t !== node_opcua_service_endpoints_1.UserTokenType.Anonymous);
|
|
368
|
+
}
|
|
369
|
+
const optionsE = {
|
|
370
|
+
hostname: advHostname,
|
|
371
|
+
advertisedPort: advPort,
|
|
372
|
+
securityPolicies: entrySecurityPolicies,
|
|
373
|
+
userTokenTypes: entryUserTokenTypes,
|
|
374
|
+
allowUnsecurePassword: options.allowUnsecurePassword,
|
|
375
|
+
alternateHostname: options.alternateHostname,
|
|
376
|
+
resourcePath: options.resourcePath
|
|
377
|
+
};
|
|
378
|
+
if (entrySecurityModes.indexOf(node_opcua_secure_channel_1.MessageSecurityMode.None) >= 0) {
|
|
379
|
+
this.addEndpointDescription(node_opcua_secure_channel_1.MessageSecurityMode.None, node_opcua_secure_channel_1.SecurityPolicy.None, optionsE);
|
|
380
|
+
}
|
|
381
|
+
else {
|
|
382
|
+
if (!options.disableDiscovery) {
|
|
383
|
+
this.addRestrictedEndpointDescription(optionsE);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
for (const securityMode of entrySecurityModes) {
|
|
387
|
+
if (securityMode === node_opcua_secure_channel_1.MessageSecurityMode.None) {
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
for (const securityPolicy of entrySecurityPolicies) {
|
|
391
|
+
if (securityPolicy === node_opcua_secure_channel_1.SecurityPolicy.None) {
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
this.addEndpointDescription(securityMode, securityPolicy, optionsE);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
315
398
|
}
|
|
316
399
|
/**
|
|
317
400
|
* returns the list of end point descriptions.
|
|
@@ -324,9 +407,13 @@ class OPCUAServerEndPoint extends events_1.EventEmitter {
|
|
|
324
407
|
listen(callback) {
|
|
325
408
|
(0, node_opcua_assert_1.assert)(typeof callback === "function");
|
|
326
409
|
(0, node_opcua_assert_1.assert)(!this._started, "OPCUAServerEndPoint is already listening");
|
|
410
|
+
if (!this._server) {
|
|
411
|
+
callback(new Error("Server is not initialized"));
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
327
414
|
this._listen_callback = callback;
|
|
328
415
|
this._server.on("error", (err) => {
|
|
329
|
-
debugLog(chalk_1.default.red.bold(" error")
|
|
416
|
+
debugLog(`${chalk_1.default.red.bold(" error")} port = ${this.port}`, err);
|
|
330
417
|
this._started = false;
|
|
331
418
|
this._end_listen(err);
|
|
332
419
|
});
|
|
@@ -344,8 +431,8 @@ class OPCUAServerEndPoint extends events_1.EventEmitter {
|
|
|
344
431
|
(0, node_opcua_assert_1.assert)(!err, " cannot listen to port ");
|
|
345
432
|
this._started = true;
|
|
346
433
|
if (!this.port) {
|
|
347
|
-
const add = this._server
|
|
348
|
-
this.port = typeof add !== "string" ? add
|
|
434
|
+
const add = this._server?.address();
|
|
435
|
+
this.port = typeof add !== "string" ? add?.port || 0 : this.port;
|
|
349
436
|
}
|
|
350
437
|
this._end_listen();
|
|
351
438
|
});
|
|
@@ -353,7 +440,7 @@ class OPCUAServerEndPoint extends events_1.EventEmitter {
|
|
|
353
440
|
killClientSockets(callback) {
|
|
354
441
|
for (const channel of this.getChannels()) {
|
|
355
442
|
const hacked_channel = channel;
|
|
356
|
-
if (hacked_channel.transport
|
|
443
|
+
if (hacked_channel.transport?._socket) {
|
|
357
444
|
// hacked_channel.transport._socket.close();
|
|
358
445
|
hacked_channel.transport._socket.destroy();
|
|
359
446
|
hacked_channel.transport._socket.emit("error", new Error("EPIPE"));
|
|
@@ -362,8 +449,9 @@ class OPCUAServerEndPoint extends events_1.EventEmitter {
|
|
|
362
449
|
callback();
|
|
363
450
|
}
|
|
364
451
|
suspendConnection(callback) {
|
|
365
|
-
if (!this._started) {
|
|
366
|
-
|
|
452
|
+
if (!this._started || !this._server) {
|
|
453
|
+
callback(new Error("Connection already suspended !!"));
|
|
454
|
+
return;
|
|
367
455
|
}
|
|
368
456
|
// Stops the server from accepting new connections and keeps existing connections.
|
|
369
457
|
// (note from nodejs doc: This function is asynchronous, the server is finally closed
|
|
@@ -373,7 +461,7 @@ class OPCUAServerEndPoint extends events_1.EventEmitter {
|
|
|
373
461
|
// if the server was not open when it was closed.
|
|
374
462
|
this._server.close(() => {
|
|
375
463
|
this._started = false;
|
|
376
|
-
debugLog(
|
|
464
|
+
debugLog(`Connection has been closed !${this.port}`);
|
|
377
465
|
});
|
|
378
466
|
this._started = false;
|
|
379
467
|
callback();
|
|
@@ -395,15 +483,27 @@ class OPCUAServerEndPoint extends events_1.EventEmitter {
|
|
|
395
483
|
this.suspendConnection(() => {
|
|
396
484
|
// shutdown all opened channels ...
|
|
397
485
|
const _channels = Object.values(this._channels);
|
|
398
|
-
|
|
399
|
-
this.shutdown_channel(channel,
|
|
400
|
-
|
|
401
|
-
|
|
486
|
+
const promises = _channels.map((channel) => new Promise((resolve, reject) => {
|
|
487
|
+
this.shutdown_channel(channel, (err) => {
|
|
488
|
+
if (err) {
|
|
489
|
+
reject(err);
|
|
490
|
+
}
|
|
491
|
+
else {
|
|
492
|
+
resolve();
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
}));
|
|
496
|
+
Promise.all(promises)
|
|
497
|
+
.then(() => {
|
|
498
|
+
/* c8 ignore next */
|
|
402
499
|
if (!(Object.keys(this._channels).length === 0)) {
|
|
403
500
|
errorLog(" Bad !");
|
|
404
501
|
}
|
|
405
502
|
(0, node_opcua_assert_1.assert)(Object.keys(this._channels).length === 0, "channel must have unregistered themselves");
|
|
406
|
-
callback(
|
|
503
|
+
callback();
|
|
504
|
+
})
|
|
505
|
+
.catch((err) => {
|
|
506
|
+
callback(err);
|
|
407
507
|
});
|
|
408
508
|
});
|
|
409
509
|
}
|
|
@@ -449,23 +549,23 @@ class OPCUAServerEndPoint extends events_1.EventEmitter {
|
|
|
449
549
|
return Object.keys(this._channels).length;
|
|
450
550
|
}
|
|
451
551
|
_dump_statistics() {
|
|
452
|
-
this._server
|
|
552
|
+
this._server?.getConnections((_err, count) => {
|
|
453
553
|
debugLog(chalk_1.default.cyan("CONCURRENT CONNECTION = "), count);
|
|
454
554
|
});
|
|
455
|
-
debugLog(chalk_1.default.cyan("MAX CONNECTIONS = "), this._server
|
|
555
|
+
debugLog(chalk_1.default.cyan("MAX CONNECTIONS = "), this._server?.maxConnections);
|
|
456
556
|
}
|
|
457
557
|
_setup_server() {
|
|
458
558
|
(0, node_opcua_assert_1.assert)(!this._server);
|
|
459
|
-
this._server =
|
|
559
|
+
this._server = node_net_1.default.createServer({ pauseOnConnect: true }, this._on_client_connection.bind(this));
|
|
460
560
|
// xx console.log(" Server with max connections ", self.maxConnections);
|
|
461
561
|
this._server.maxConnections = this.maxConnections + 1; // plus one extra
|
|
462
562
|
this._listen_callback = undefined;
|
|
463
563
|
this._server
|
|
464
564
|
.on("connection", (socket) => {
|
|
465
|
-
//
|
|
565
|
+
// c8 ignore next
|
|
466
566
|
if (doDebug) {
|
|
467
567
|
this._dump_statistics();
|
|
468
|
-
debugLog(
|
|
568
|
+
debugLog(`server connected with : ${socket.remoteAddress}:${socket.remotePort}`);
|
|
469
569
|
}
|
|
470
570
|
})
|
|
471
571
|
.on("close", () => {
|
|
@@ -489,7 +589,7 @@ class OPCUAServerEndPoint extends events_1.EventEmitter {
|
|
|
489
589
|
const deny_connection = () => {
|
|
490
590
|
console.log(chalk_1.default.bgWhite.cyan("OPCUAServerEndPoint#_on_client_connection " +
|
|
491
591
|
"The maximum number of connection has been reached - Connection is refused"));
|
|
492
|
-
const reason =
|
|
592
|
+
const reason = `maxConnections reached (${this.maxConnections})`;
|
|
493
593
|
const socketData = extractSocketData(socket, reason);
|
|
494
594
|
this.emit("connectionRefused", socketData);
|
|
495
595
|
socket.end();
|
|
@@ -498,7 +598,7 @@ class OPCUAServerEndPoint extends events_1.EventEmitter {
|
|
|
498
598
|
const establish_connection = () => {
|
|
499
599
|
const nbConnections = Object.keys(this._channels).length;
|
|
500
600
|
if (nbConnections >= this.maxConnections) {
|
|
501
|
-
warningLog(" nbConnections ", nbConnections, " self._server.maxConnections", this._server
|
|
601
|
+
warningLog(" nbConnections ", nbConnections, " self._server.maxConnections", this._server?.maxConnections, this.maxConnections);
|
|
502
602
|
deny_connection();
|
|
503
603
|
return;
|
|
504
604
|
}
|
|
@@ -517,7 +617,7 @@ class OPCUAServerEndPoint extends events_1.EventEmitter {
|
|
|
517
617
|
this._un_pre_registerChannel(channel);
|
|
518
618
|
debugLog(chalk_1.default.yellow.bold("Channel#init done"), err);
|
|
519
619
|
if (err) {
|
|
520
|
-
const reason =
|
|
620
|
+
const reason = `openSecureChannel has Failed ${err.message}`;
|
|
521
621
|
const socketData = extractSocketData(socket, reason);
|
|
522
622
|
const channelData = extractChannelData(channel);
|
|
523
623
|
this.emit("openSecureChannelFailure", socketData, channelData);
|
|
@@ -611,7 +711,7 @@ class OPCUAServerEndPoint extends events_1.EventEmitter {
|
|
|
611
711
|
this.bytesReadInOldChannels += channel.bytesRead;
|
|
612
712
|
this.transactionsCountOldChannels += channel.transactionsCount;
|
|
613
713
|
delete this._channels[channel.hashKey];
|
|
614
|
-
//
|
|
714
|
+
// c8 ignore next
|
|
615
715
|
if (doDebug) {
|
|
616
716
|
this._dump_statistics();
|
|
617
717
|
debugLog("un-registering channel - Count = ", this.currentChannelCount);
|
|
@@ -621,7 +721,6 @@ class OPCUAServerEndPoint extends events_1.EventEmitter {
|
|
|
621
721
|
_end_listen(err) {
|
|
622
722
|
if (!this._listen_callback)
|
|
623
723
|
return;
|
|
624
|
-
(0, node_opcua_assert_1.assert)(typeof this._listen_callback === "function");
|
|
625
724
|
this._listen_callback(err);
|
|
626
725
|
this._listen_callback = undefined;
|
|
627
726
|
}
|
|
@@ -646,18 +745,19 @@ class OPCUAServerEndPoint extends events_1.EventEmitter {
|
|
|
646
745
|
_prevent_DDOS_Attack(establish_connection, deny_connection) {
|
|
647
746
|
const nbConnections = this.activeChannelCount;
|
|
648
747
|
if (nbConnections >= this.maxConnections) {
|
|
649
|
-
//
|
|
650
|
-
errorLog(chalk_1.default.bgRed.white(
|
|
748
|
+
// c8 ignore next
|
|
749
|
+
errorLog(chalk_1.default.bgRed.white(`PREVENTING DDOS ATTACK => maxConnection =${this.maxConnections}`));
|
|
651
750
|
const unused_channels = this.getChannels().filter((channel1) => {
|
|
652
751
|
return !channel1.hasSession;
|
|
653
752
|
});
|
|
654
753
|
if (unused_channels.length === 0) {
|
|
655
|
-
doDebug &&
|
|
656
|
-
.
|
|
657
|
-
|
|
754
|
+
doDebug &&
|
|
755
|
+
console.log(this.getChannels()
|
|
756
|
+
.map(({ status, isOpened, hasSession }) => `${status} ${isOpened} ${hasSession}\n`)
|
|
757
|
+
.join(" "));
|
|
658
758
|
// all channels are in used , we cannot get any
|
|
659
759
|
errorLog(`All channels are in used ! we cannot cancel any ${this.getChannels().length}`);
|
|
660
|
-
//
|
|
760
|
+
// c8 ignore next
|
|
661
761
|
if (doDebug) {
|
|
662
762
|
console.log(" - all channels are used !!!!");
|
|
663
763
|
false && dumpChannelInfo(this.getChannels());
|
|
@@ -665,14 +765,14 @@ class OPCUAServerEndPoint extends events_1.EventEmitter {
|
|
|
665
765
|
setTimeout(deny_connection, 1000);
|
|
666
766
|
return;
|
|
667
767
|
}
|
|
668
|
-
//
|
|
768
|
+
// c8 ignore next
|
|
669
769
|
if (doDebug) {
|
|
670
770
|
console.log(" - Unused channels that can be clobbered", unused_channels.map((channel1) => channel1.hashKey).join(" "));
|
|
671
771
|
}
|
|
672
772
|
const channel = unused_channels[0];
|
|
673
773
|
errorLog(`${unused_channels.length} : Forcefully closing oldest channel that have no session: ${channel.hashKey}`);
|
|
674
774
|
channel.close(() => {
|
|
675
|
-
//
|
|
775
|
+
// c8 ignore next
|
|
676
776
|
if (doDebug) {
|
|
677
777
|
console.log(" _ Unused channel has been closed ", channel.hashKey);
|
|
678
778
|
}
|
|
@@ -712,7 +812,6 @@ function estimateSecurityLevel(securityMode, securityPolicy) {
|
|
|
712
812
|
case node_opcua_secure_channel_1.SecurityPolicy.Aes256_Sha256_RsaPss:
|
|
713
813
|
return 7 + offset;
|
|
714
814
|
default:
|
|
715
|
-
case node_opcua_secure_channel_1.SecurityPolicy.None:
|
|
716
815
|
return 1;
|
|
717
816
|
}
|
|
718
817
|
}
|
|
@@ -732,7 +831,7 @@ function _makeEndpointDescription(options, parent) {
|
|
|
732
831
|
options.securityLevel === undefined
|
|
733
832
|
? estimateSecurityLevel(options.securityMode, options.securityPolicy)
|
|
734
833
|
: options.securityLevel;
|
|
735
|
-
(0, node_opcua_assert_1.assert)(isFinite(options.securityLevel), "expecting a valid securityLevel");
|
|
834
|
+
(0, node_opcua_assert_1.assert)(Number.isFinite(options.securityLevel), "expecting a valid securityLevel");
|
|
736
835
|
const securityPolicyUri = (0, node_opcua_secure_channel_1.toURI)(options.securityPolicy);
|
|
737
836
|
const userIdentityTokens = [];
|
|
738
837
|
const registerIdentity2 = (tokenType, securityPolicy, name) => {
|
|
@@ -784,7 +883,7 @@ function _makeEndpointDescription(options, parent) {
|
|
|
784
883
|
// when channel session security is not "None",
|
|
785
884
|
// userIdentityTokens can be left to null.
|
|
786
885
|
// in this case this mean that secure policy will be the same as connection security policy
|
|
787
|
-
//
|
|
886
|
+
// c8 ignore next
|
|
788
887
|
if (process.env.NODEOPCUA_SERVER_EMULATE_SIEMENS) {
|
|
789
888
|
// However, for some reason SIEMENS plc requires that password get encrypted even though
|
|
790
889
|
// the secure channel is also encrypted ....
|
|
@@ -829,7 +928,7 @@ function _makeEndpointDescription(options, parent) {
|
|
|
829
928
|
});
|
|
830
929
|
}
|
|
831
930
|
// return the endpoint object
|
|
832
|
-
const endpoint = new
|
|
931
|
+
const endpoint = new node_opcua_service_endpoints_1.EndpointDescription({
|
|
833
932
|
endpointUrl: "<to be evaluated at run time>", // options.endpointUrl,
|
|
834
933
|
server: undefined, // options.server,
|
|
835
934
|
serverCertificate: options.serverCertificateChain,
|
|
@@ -837,13 +936,14 @@ function _makeEndpointDescription(options, parent) {
|
|
|
837
936
|
securityPolicyUri,
|
|
838
937
|
userIdentityTokens,
|
|
839
938
|
securityLevel: options.securityLevel,
|
|
840
|
-
transportProfileUri:
|
|
939
|
+
transportProfileUri: UATCP_UASC_UABINARY
|
|
841
940
|
});
|
|
842
941
|
endpoint._parent = parent;
|
|
843
942
|
// endpointUrl is dynamic as port number may be adjusted
|
|
844
943
|
// when the tcp socket start listening
|
|
944
|
+
// biome-ignore lint/suspicious/noExplicitAny: __defineGetter__ not in standard typings
|
|
845
945
|
endpoint.__defineGetter__("endpointUrl", () => {
|
|
846
|
-
const port = endpoint._parent.port;
|
|
946
|
+
const port = options.advertisedPort ?? endpoint._parent.port;
|
|
847
947
|
const resourcePath = options.resourcePath || "";
|
|
848
948
|
const hostname = options.hostname;
|
|
849
949
|
const endpointUrl = `opc.tcp://${hostname}:${port}${resourcePath}`;
|
|
@@ -862,7 +962,7 @@ function _makeEndpointDescription(options, parent) {
|
|
|
862
962
|
*
|
|
863
963
|
*/
|
|
864
964
|
function matching_endpoint(securityMode, securityPolicy, endpointUrl, endpoint) {
|
|
865
|
-
(0, node_opcua_assert_1.assert)(endpoint instanceof
|
|
965
|
+
(0, node_opcua_assert_1.assert)(endpoint instanceof node_opcua_service_endpoints_1.EndpointDescription);
|
|
866
966
|
const endpoint_securityPolicy = (0, node_opcua_secure_channel_1.fromURI)(endpoint.securityPolicyUri);
|
|
867
967
|
if (endpointUrl && endpoint.endpointUrl !== endpointUrl) {
|
|
868
968
|
return false;
|