• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

node-opcua / node-opcua / 23974043205

04 Apr 2026 07:17AM UTC coverage: 92.589% (+0.01%) from 92.576%
23974043205

push

github

erossignon
chore: fix Mocha.Suite.settimeout misused

18408 of 21832 branches covered (84.32%)

161708 of 174651 relevant lines covered (92.59%)

461089.77 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.64
/packages/node-opcua-server/source/server_end_point.ts
1
/* eslint-disable max-statements */
2✔
2
/**
2✔
3
 * @module node-opcua-server
2✔
4
 */
2✔
5
// tslint:disable:no-console
2✔
6

2✔
7
import { EventEmitter } from "node:events";
2✔
8
import net, { type Server, type Socket } from "node:net";
2✔
9
import chalk from "chalk";
2✔
10

2✔
11
import { assert } from "node-opcua-assert";
2✔
12
import type { OPCUACertificateManager } from "node-opcua-certificate-manager";
2✔
13
import { type ICertificateChainProvider, StaticCertificateChainProvider } from "node-opcua-common";
2✔
14
import { type Certificate, combine_der, makeSHA1Thumbprint, type PrivateKey, split_der } from "node-opcua-crypto/web";
2✔
15
import { checkDebugFlag, make_debugLog, make_errorLog, make_warningLog } from "node-opcua-debug";
2✔
16
import { getFullyQualifiedDomainName, resolveFullyQualifiedDomainName } from "node-opcua-hostname";
2✔
17
import {
2✔
18
    fromURI,
2✔
19
    type IServerSessionBase,
2✔
20
    type Message,
2✔
21
    MessageSecurityMode,
2✔
22
    SecurityPolicy,
2✔
23
    ServerSecureChannelLayer,
2✔
24
    type ServerSecureChannelParent,
2✔
25
    toURI
2✔
26
} from "node-opcua-secure-channel";
2✔
27
import { ApplicationDescription, EndpointDescription, UserTokenType } from "node-opcua-service-endpoints";
2✔
28
import type { IHelloAckLimits } from "node-opcua-transport";
2✔
29
import type { UserTokenPolicyOptions } from "node-opcua-types";
2✔
30

2✔
31
import type { IChannelData } from "./i_channel_data";
2✔
32
import type { ISocketData } from "./i_socket_data";
2✔
33

2✔
34
const debugLog = make_debugLog(__filename);
2✔
35
const errorLog = make_errorLog(__filename);
2✔
36
const warningLog = make_warningLog(__filename);
2✔
37
const doDebug = checkDebugFlag(__filename);
2✔
38

2✔
39
const UATCP_UASC_UABINARY = "http://opcfoundation.org/UA-Profile/Transport/uatcp-uasc-uabinary";
2✔
40

2✔
41
function extractSocketData(socket: net.Socket, reason: string): ISocketData {
22✔
42
    const { bytesRead, bytesWritten, remoteAddress, remoteFamily, remotePort, localAddress, localPort } = socket;
22✔
43
    const data: ISocketData = {
22✔
44
        bytesRead,
22✔
45
        bytesWritten,
22✔
46
        localAddress,
22✔
47
        localPort,
22✔
48
        remoteAddress,
22✔
49
        remoteFamily,
22✔
50
        remotePort,
22✔
51
        timestamp: new Date(),
22✔
52
        reason
22✔
53
    };
22✔
54
    return data;
22✔
55
}
22✔
56

2✔
57
function extractChannelData(channel: ServerSecureChannelLayer): IChannelData {
3✔
58
    const { channelId, clientCertificate, securityMode, securityPolicy, timeout, transactionsCount } = channel;
3✔
59

3✔
60
    const channelData: IChannelData = {
3✔
61
        channelId,
3✔
62
        clientCertificate,
3✔
63
        securityMode,
3✔
64
        securityPolicy,
3✔
65
        timeout,
3✔
66
        transactionsCount
3✔
67
    };
3✔
68
    return channelData;
3✔
69
}
3✔
70

2✔
71
function dumpChannelInfo(channels: ServerSecureChannelLayer[]): void {
1✔
72
    function d(s: IServerSessionBase) {
1✔
73
        return `[ status=${s.status} lastSeen=${s.clientLastContactTime.toFixed(0)}ms sessionName=${s.sessionName} timeout=${
1✔
74
            s.sessionTimeout
1✔
75
        } ]`;
1✔
76
    }
1✔
77
    function dumpChannel(channel: ServerSecureChannelLayer): void {
1✔
78
        console.log("------------------------------------------------------");
1✔
79
        console.log("            channelId = ", channel.channelId);
1✔
80
        console.log("             timeout  = ", channel.timeout);
1✔
81
        console.log("        remoteAddress = ", channel.remoteAddress);
1✔
82
        console.log("        remotePort    = ", channel.remotePort);
1✔
83
        console.log("");
1✔
84
        console.log("        bytesWritten  = ", channel.bytesWritten);
1✔
85
        console.log("        bytesRead     = ", channel.bytesRead);
1✔
86
        console.log("        sessions      = ", Object.keys(channel.sessionTokens).length);
1✔
87
        console.log(Object.values(channel.sessionTokens).map(d).join("\n"));
1✔
88

1✔
89
        // biome-ignore lint/suspicious/noExplicitAny: accessing internal transport for debug dump
1✔
90
        const socket = (channel as any).transport?._socket;
1✔
91
        if (!socket) {
1✔
92
            console.log(" SOCKET IS CLOSED");
1✔
93
        }
1✔
94
    }
1✔
95

1✔
96
    for (const channel of channels) {
1✔
97
        dumpChannel(channel);
1✔
98
    }
1✔
99
    console.log("------------------------------------------------------");
1✔
100
}
1✔
101

2✔
102
const emptyCertificateChain: Certificate[] = [];
2✔
103

2✔
104
// biome-ignore lint/suspicious/noExplicitAny: deliberate null→PrivateKey sentinel
2✔
105
const emptyPrivateKey = null as any as PrivateKey;
2✔
106

2✔
107
let OPCUAServerEndPointCounter = 0;
2✔
108

2✔
109
export interface OPCUAServerEndPointOptions {
2✔
110
    /**
2✔
111
     * the tcp port
2✔
112
     */
2✔
113
    port: number;
2✔
114
    /**
2✔
115
     * the tcp host
2✔
116
     */
2✔
117
    host?: string;
2✔
118
    /**
2✔
119
     * the DER certificate chain
2✔
120
     */
2✔
121
    certificateChain: Certificate[];
2✔
122

2✔
123
    /**
2✔
124
     * privateKey
2✔
125
     */
2✔
126
    privateKey: PrivateKey;
2✔
127

2✔
128
    certificateManager: OPCUACertificateManager;
2✔
129

2✔
130
    /**
2✔
131
     *  the default secureToken lifetime @default=60000
2✔
132
     */
2✔
133
    defaultSecureTokenLifetime?: number;
2✔
134

2✔
135
    /**
2✔
136
     * the maximum number of connection allowed on the TCP server socket
2✔
137
     * @default 20
2✔
138
     */
2✔
139
    maxConnections?: number;
2✔
140

2✔
141
    /**
2✔
142
     *  the  timeout for the TCP HEL/ACK transaction (in ms)
2✔
143
     *  @default 30000
2✔
144
     */
2✔
145
    timeout?: number;
2✔
146

2✔
147
    serverInfo: ApplicationDescription;
2✔
148

2✔
149
    objectFactory?: unknown;
2✔
150

2✔
151
    transportSettings?: IServerTransportSettings;
2✔
152
}
2✔
153

2✔
154
export interface IServerTransportSettings {
2✔
155
    adjustTransportLimits: (hello: IHelloAckLimits) => IHelloAckLimits;
2✔
156
}
2✔
157

2✔
158
export interface EndpointDescriptionParams {
2✔
159
    restricted?: boolean;
2✔
160
    allowUnsecurePassword?: boolean;
2✔
161
    resourcePath?: string;
2✔
162
    alternateHostname?: string[];
2✔
163
    hostname: string;
2✔
164
    /**
2✔
165
     * Override the port used in the endpoint URL.
2✔
166
     * When set, the endpoint URL uses this port instead of the
2✔
167
     * server's listen port. The server does NOT listen on this port.
2✔
168
     * Useful for Docker port-mapping, reverse proxies, and NAT.
2✔
169
     */
2✔
170
    advertisedPort?: number;
2✔
171
    securityPolicies: SecurityPolicy[];
2✔
172
    userTokenTypes: UserTokenType[];
2✔
173
}
2✔
174

2✔
175
/**
2✔
176
 * Per-URL security overrides for advertised endpoints.
2✔
177
 *
2✔
178
 * When `advertisedEndpoints` contains a config object, the endpoint
2✔
179
 * descriptions generated for that URL use the overridden security
2✔
180
 * settings instead of inheriting from the main endpoint.
2✔
181
 *
2✔
182
 * Any field that is omitted falls back to the main endpoint's value.
2✔
183
 *
2✔
184
 * @example
2✔
185
 * ```ts
2✔
186
 * advertisedEndpoints: [
2✔
187
 *     // Public: SignAndEncrypt only, no anonymous
2✔
188
 *     {
2✔
189
 *         url: "opc.tcp://public.example.com:4840",
2✔
190
 *         securityModes: [MessageSecurityMode.SignAndEncrypt],
2✔
191
 *         allowAnonymous: false
2✔
192
 *     },
2✔
193
 *     // Internal: inherits everything from main endpoint
2✔
194
 *     "opc.tcp://internal:48480"
2✔
195
 * ]
2✔
196
 * ```
2✔
197
 */
2✔
198
export interface AdvertisedEndpointConfig {
2✔
199
    /** The full endpoint URL, e.g. `"opc.tcp://public.example.com:4840"` */
2✔
200
    url: string;
2✔
201
    /** Override security modes (default: inherit from main endpoint) */
2✔
202
    securityModes?: MessageSecurityMode[];
2✔
203
    /** Override security policies (default: inherit from main endpoint) */
2✔
204
    securityPolicies?: SecurityPolicy[];
2✔
205
    /** Override anonymous access (default: inherit from main endpoint) */
2✔
206
    allowAnonymous?: boolean;
2✔
207
    /** Override user token types (default: inherit from main endpoint) */
2✔
208
    userTokenTypes?: UserTokenType[];
2✔
209
}
2✔
210

2✔
211
/**
2✔
212
 * An advertised endpoint entry — either a plain URL string (inherits
2✔
213
 * all settings from the main endpoint) or a config object with
2✔
214
 * per-URL security overrides.
2✔
215
 */
2✔
216
export type AdvertisedEndpoint = string | AdvertisedEndpointConfig;
2✔
217

2✔
218
/**
2✔
219
 * Normalize any `advertisedEndpoints` input into a uniform
2✔
220
 * `AdvertisedEndpointConfig[]`.
2✔
221
 *
2✔
222
 * This coercion is done early so that all downstream code
2✔
223
 * (endpoint generation, IP/hostname extraction) only deals
2✔
224
 * with one type.
2✔
225
 */
2✔
226
export function normalizeAdvertisedEndpoints(raw?: AdvertisedEndpoint | AdvertisedEndpoint[]): AdvertisedEndpointConfig[] {
913✔
227
    if (!raw) return [];
913✔
228
    const arr = Array.isArray(raw) ? raw : [raw];
913✔
229
    return arr.map((entry) => (typeof entry === "string" ? { url: entry } : entry));
913✔
230
}
913✔
231

2✔
232
export interface AddStandardEndpointDescriptionsParam {
2✔
233
    allowAnonymous?: boolean;
2✔
234
    disableDiscovery?: boolean;
2✔
235
    securityModes?: MessageSecurityMode[];
2✔
236

2✔
237
    restricted?: boolean;
2✔
238
    allowUnsecurePassword?: boolean;
2✔
239
    resourcePath?: string;
2✔
240
    alternateHostname?: string[];
2✔
241
    hostname?: string;
2✔
242
    securityPolicies?: SecurityPolicy[];
2✔
243
    userTokenTypes?: UserTokenType[];
2✔
244

2✔
245
    /**
2✔
246
     * Additional endpoint URL(s) to advertise.
2✔
247
     *
2✔
248
     * Use when the server is behind Docker port-mapping,
2✔
249
     * a reverse proxy, or a NAT gateway.
2✔
250
     *
2✔
251
     * Each entry can be a plain URL string (inherits all security
2✔
252
     * settings from the main endpoint) or an
2✔
253
     * `AdvertisedEndpointConfig` object with per-URL overrides.
2✔
254
     *
2✔
255
     * The server still listens on `port` — these are purely
2✔
256
     * advertised aliases.
2✔
257
     *
2✔
258
     * @example Simple string (inherits main settings)
2✔
259
     * ```ts
2✔
260
     * advertisedEndpoints: "opc.tcp://localhost:48481"
2✔
261
     * ```
2✔
262
     *
2✔
263
     * @example Mixed array with per-URL security overrides
2✔
264
     * ```ts
2✔
265
     * advertisedEndpoints: [
2✔
266
     *     "opc.tcp://internal:48480",
2✔
267
     *     {
2✔
268
     *         url: "opc.tcp://public.example.com:4840",
2✔
269
     *         securityModes: [MessageSecurityMode.SignAndEncrypt],
2✔
270
     *         allowAnonymous: false
2✔
271
     *     }
2✔
272
     * ]
2✔
273
     * ```
2✔
274
     */
2✔
275
    advertisedEndpoints?: AdvertisedEndpoint | AdvertisedEndpoint[];
2✔
276
}
2✔
277

2✔
278
/**
2✔
279
 * Parse an `opc.tcp://hostname:port` URL and extract hostname and port.
2✔
280
 * @internal
2✔
281
 */
2✔
282
export function parseOpcTcpUrl(url: string): { hostname: string; port: number } {
1✔
283
    // URL class doesn't understand opc.tcp://, so swap to http://
1✔
284
    const httpUrl = url.replace(/^opc\.tcp:\/\//i, "http://");
1✔
285
    const parsed = new URL(httpUrl);
1✔
286
    return {
1✔
287
        hostname: parsed.hostname,
1✔
288
        port: parsed.port ? Number.parseInt(parsed.port, 10) : 4840
1✔
289
    };
1✔
290
}
1✔
291

2✔
292
function getUniqueName(name: string, collection: { [key: string]: number }) {
7,543✔
293
    if (collection[name]) {
7,543✔
294
        let counter = 0;
4,443✔
295
        while (collection[`${name}_${counter.toString()}`]) {
4,443✔
296
            counter++;
10,443✔
297
        }
10,443✔
298
        name = `${name}_${counter.toString()}`;
4,443✔
299
        collection[name] = 1;
4,443✔
300
        return name;
4,443✔
301
    } else {
7,543✔
302
        collection[name] = 1;
3,101✔
303
        return name;
3,101✔
304
    }
3,101✔
305
}
7,543✔
306

2✔
307
/**
2✔
308
 * Stores the abort listener for channels in the pre-registration phase.
2✔
309
 * Using a WeakMap instead of monkey-patching the channel object keeps
2✔
310
 * this internal state invisible to debuggers and external code.
2✔
311
 */
2✔
312
const preregisterAbortListeners = new WeakMap<ServerSecureChannelLayer, () => void>();
2✔
313
/**
2✔
314
 * OPCUAServerEndPoint a Server EndPoint.
2✔
315
 * A sever end point is listening to one port
2✔
316
 * note:
2✔
317
 *   see OPCUA Release 1.03 part 4 page 108 7.1 ApplicationDescription
2✔
318
 */
2✔
319
export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureChannelParent {
285✔
320
    /**
336✔
321
     * the tcp port
336✔
322
     */
336✔
323
    public port: number;
336✔
324
    public host: string | undefined;
336✔
325
    public certificateManager: OPCUACertificateManager;
336✔
326
    public defaultSecureTokenLifetime: number;
336✔
327
    public maxConnections: number;
336✔
328
    public timeout: number;
336✔
329
    public bytesWrittenInOldChannels: number;
336✔
330
    public bytesReadInOldChannels: number;
336✔
331
    public transactionsCountOldChannels: number;
336✔
332
    public securityTokenCountOldChannels: number;
336✔
333
    public serverInfo: ApplicationDescription;
336✔
334
    public objectFactory: unknown;
336✔
335

336✔
336
    public _on_new_channel?: (channel: ServerSecureChannelLayer) => void;
336✔
337
    public _on_channel_secured?: (channel: ServerSecureChannelLayer) => void;
336✔
338
    public _on_close_channel?: (channel: ServerSecureChannelLayer) => void;
336✔
339
    public _on_connectionRefused?: (socketData: ISocketData) => void;
336✔
340
    public _on_openSecureChannelFailure?: (socketData: ISocketData, channelData: IChannelData) => void;
336✔
341

336✔
342
    /**
336✔
343
     * Certificate chain provider — delegates all cert/key access.
336✔
344
     * Stored in a `#` private field so secrets are not visible
336✔
345
     * in the debugger's property list or JSON serialization.
336✔
346
     */
336✔
347
    #certProvider: ICertificateChainProvider;
336✔
348
    /**
336✔
349
     * Combined DER cache — invalidated whenever the cert provider
336✔
350
     * changes so that `EndpointDescription.serverCertificate`
336✔
351
     * getters don't call `combine_der()` on every access.
336✔
352
     */
336✔
353
    #combinedDerCache: Certificate | undefined;
336✔
354
    private _channels: { [key: string]: ServerSecureChannelLayer };
336✔
355
    private _server?: Server;
336✔
356
    private _endpoints: EndpointDescription[];
336✔
357
    private _listen_callback?: (err?: Error) => void;
336✔
358
    private _started = false;
336✔
359
    private _counter = OPCUAServerEndPointCounter++;
336✔
360
    private _policy_deduplicator: { [key: string]: number } = {};
336✔
361

336✔
362
    private transportSettings?: IServerTransportSettings;
336✔
363
    constructor(options: OPCUAServerEndPointOptions) {
336✔
364
        super();
336✔
365

336✔
366
        assert(!Object.hasOwn(options, "certificate"), "expecting a certificateChain instead");
336✔
367
        assert(Object.hasOwn(options, "certificateChain"), "expecting a certificateChain");
336✔
368
        assert(Object.hasOwn(options, "privateKey"));
336✔
369

336✔
370
        this.certificateManager = options.certificateManager;
336✔
371

336✔
372
        options.port = options.port || 0;
336✔
373

336✔
374
        this.port = parseInt(options.port.toString(), 10);
336✔
375
        this.host = options.host;
336✔
376
        assert(typeof this.port === "number");
336✔
377

336✔
378
        this.#certProvider = new StaticCertificateChainProvider(options.certificateChain, options.privateKey);
336✔
379

336✔
380
        this._channels = {};
336✔
381

336✔
382
        this.defaultSecureTokenLifetime = options.defaultSecureTokenLifetime || 600000;
336✔
383

336✔
384
        this.maxConnections = options.maxConnections || 20;
336✔
385

336✔
386
        this.timeout = options.timeout || 30000;
336✔
387

336✔
388
        this._server = undefined;
336✔
389

336✔
390
        this._setup_server();
336✔
391

336✔
392
        this._endpoints = [];
336✔
393

336✔
394
        this.objectFactory = options.objectFactory;
336✔
395

336✔
396
        this.bytesWrittenInOldChannels = 0;
336✔
397
        this.bytesReadInOldChannels = 0;
336✔
398
        this.transactionsCountOldChannels = 0;
336✔
399
        this.securityTokenCountOldChannels = 0;
336✔
400

336✔
401
        this.serverInfo = options.serverInfo;
336✔
402
        assert(this.serverInfo !== null && typeof this.serverInfo === "object");
336✔
403

336✔
404
        this.transportSettings = options.transportSettings;
336✔
405
    }
336✔
406

336✔
407
    public dispose(): void {
336✔
408
        this.#certProvider = new StaticCertificateChainProvider(emptyCertificateChain, emptyPrivateKey);
300✔
409
        this.#combinedDerCache = undefined;
300✔
410

300✔
411
        assert(Object.keys(this._channels).length === 0, "OPCUAServerEndPoint channels must have been deleted");
300✔
412

300✔
413
        this._channels = {};
300✔
414
        this.serverInfo = new ApplicationDescription({});
300✔
415

300✔
416
        this._endpoints = [];
300✔
417
        assert(this._endpoints.length === 0, "endpoints must have been deleted");
300✔
418
        this._endpoints = [];
300✔
419

300✔
420
        this._server = undefined;
300✔
421
        this._listen_callback = undefined;
300✔
422

300✔
423
        this.removeAllListeners();
300✔
424
    }
300✔
425

336✔
426
    public toString(): string {
336✔
427
        const txt =
12✔
428
            " end point" +
12✔
429
            this._counter +
12✔
430
            " port = " +
12✔
431
            this.port +
12✔
432
            " l = " +
12✔
433
            this._endpoints.length +
12✔
434
            " " +
12✔
435
            makeSHA1Thumbprint(this.getCertificate()).toString("hex");
12✔
436
        return txt;
12✔
437
    }
12✔
438

336✔
439
    public getChannels(): ServerSecureChannelLayer[] {
336✔
440
        return Object.values(this._channels);
49✔
441
    }
49✔
442

336✔
443
    /**
336✔
444
     * Returns the X509 DER form of the server certificate
336✔
445
     */
336✔
446
    public getCertificate(): Certificate {
336✔
447
        return this.getCertificateChain()[0];
642✔
448
    }
642✔
449

336✔
450
    /**
336✔
451
     * Returns the X509 DER form of the server certificate
336✔
452
     */
336✔
453
    public getCertificateChain(): Certificate[] {
336✔
454
        return this.#certProvider.getCertificateChain();
1,273✔
455
    }
1,273✔
456

336✔
457
    /**
336✔
458
     * Replace the certificate chain provider for this endpoint.
336✔
459
     *
336✔
460
     * Used by push certificate management to switch from the
336✔
461
     * default static provider to a disk-based one that re-reads
336✔
462
     * certificates on demand.
336✔
463
     *
336✔
464
     * Invalidates the cached `combine_der` result so that
336✔
465
     * `EndpointDescription.serverCertificate` getters pick up
336✔
466
     * the new chain immediately.
336✔
467
     */
336✔
468
    public setCertificateProvider(provider: ICertificateChainProvider): void {
336✔
469
        this.#certProvider = provider;
301✔
470
        this.#combinedDerCache = undefined;
301✔
471
    }
301✔
472

336✔
473
    /**
336✔
474
     * Return the current certificate chain provider.
336✔
475
     * Useful for calling `invalidate()` after certificate rotation.
336✔
476
     */
336✔
477
    public getCertificateProvider(): ICertificateChainProvider {
336✔
478
        return this.#certProvider;
3✔
479
    }
3✔
480

336✔
481
    /**
336✔
482
     * Invalidate the combined DER cache.
336✔
483
     *
336✔
484
     * Called after the underlying provider's chain changes
336✔
485
     * (e.g. after `provider.invalidate()` or `provider.update()`).
336✔
486
     * The next `EndpointDescription.serverCertificate` access
336✔
487
     * will recompute the combined DER from the provider.
336✔
488
     */
336✔
489
    public invalidateCombinedDerCache(): void {
336✔
490
        this.#combinedDerCache = undefined;
2✔
491
    }
2✔
492

336✔
493
    /**
336✔
494
     * Convenience method: invalidate both the provider's cache
336✔
495
     * (so it re-reads from disk) and the combined DER cache
336✔
496
     * (so endpoint descriptions recompute `serverCertificate`).
336✔
497
     *
336✔
498
     * Prefer this over calling `getCertificateProvider().invalidate()`
336✔
499
     * and `invalidateCombinedDerCache()` separately.
336✔
500
     */
336✔
501
    public invalidateCertificates(): void {
336✔
502
        this.#certProvider.invalidate();
11✔
503
        this.#combinedDerCache = undefined;
11✔
504
    }
11✔
505

336✔
506
    /**
336✔
507
     * Get the combined DER certificate (all certs concatenated)
336✔
508
     * for use in EndpointDescription.serverCertificate.
336✔
509
     * Cached internally; invalidated by provider changes.
336✔
510
     * @internal
336✔
511
     */
336✔
512
    public getCombinedCertificate(): Certificate | undefined {
336✔
513
        const chain = this.#certProvider.getCertificateChain();
23,898✔
514
        if (chain.length === 0) return undefined;
23,898!
515
        if (!this.#combinedDerCache) {
23,898✔
516
            this.#combinedDerCache = combine_der(chain);
149✔
517
        }
149✔
518
        return this.#combinedDerCache;
23,898✔
519
    }
23,898✔
520

336✔
521
    /**
336✔
522
     * the private key
336✔
523
     */
336✔
524
    public getPrivateKey(): PrivateKey {
336✔
525
        return this.#certProvider.getPrivateKey();
2,122✔
526
    }
2,122✔
527

336✔
528
    /**
336✔
529
     * The number of active channel on this end point.
336✔
530
     */
336✔
531
    public get currentChannelCount(): number {
336✔
532
        return Object.keys(this._channels).length;
69✔
533
    }
69✔
534

336✔
535
    /**
336✔
536
     */
336✔
537
    public getEndpointDescription(
336✔
538
        securityMode: MessageSecurityMode,
3,641✔
539
        securityPolicy: SecurityPolicy,
3,641✔
540
        endpointUrl: string | null
3,641✔
541
    ): EndpointDescription | null {
3,641✔
542
        const endpoints = this.endpointDescriptions();
3,641✔
543
        const arr = endpoints.filter(matching_endpoint.bind(this, securityMode, securityPolicy, endpointUrl));
3,641✔
544

3,641✔
545
        if (endpointUrl && endpointUrl.length > 0 && !(arr.length === 0 || arr.length === 1)) {
3,641!
546
            errorLog("Several matching endpoints have been found : ");
×
547
            for (const a of arr) {
×
548
                errorLog("   ", a.endpointUrl, MessageSecurityMode[securityMode], securityPolicy);
×
549
            }
×
550
        }
×
551
        return arr.length === 0 ? null : arr[0];
3,641✔
552
    }
3,641✔
553

336✔
554
    public addEndpointDescription(
336✔
555
        securityMode: MessageSecurityMode,
2,166✔
556
        securityPolicy: SecurityPolicy,
2,166✔
557
        options: EndpointDescriptionParams
2,166✔
558
    ): void {
2,166✔
559
        // c8 ignore next
2,166✔
560
        if (securityMode === MessageSecurityMode.None && securityPolicy !== SecurityPolicy.None) {
2,166✔
561
            throw new Error(" invalid security ");
1✔
562
        }
1✔
563
        // c8 ignore next
2,166✔
564
        if (securityMode !== MessageSecurityMode.None && securityPolicy === SecurityPolicy.None) {
2,166✔
565
            throw new Error(" invalid security ");
1✔
566
        }
1✔
567
        //
2,166✔
568

2,166✔
569
        // resource Path is a string added at the end of the url such as "/UA/Server"
2,166✔
570
        const resourcePath = (options.resourcePath || "").replace(/\\/g, "/");
2,166✔
571

2,166✔
572
        assert(resourcePath.length === 0 || resourcePath.charAt(0) === "/", "resourcePath should start with /");
2,166✔
573

2,166✔
574
        const hostname = options.hostname || getFullyQualifiedDomainName();
2,166!
575
        const effectivePort = options.advertisedPort ?? this.port;
2,166✔
576
        const endpointUrl = `opc.tcp://${hostname}:${effectivePort}${resourcePath}`;
2,166✔
577

2,166✔
578
        const endpoint_desc = this.getEndpointDescription(securityMode, securityPolicy, endpointUrl);
2,166✔
579

2,166✔
580
        // c8 ignore next
2,166✔
581
        if (endpoint_desc) {
2,166✔
582
            throw new Error(" endpoint already exist");
1✔
583
        }
1✔
584

2,166✔
585
        const userTokenTypes = options.userTokenTypes;
2,166✔
586

2,163✔
587
        // now build endpointUrl
2,163✔
588
        this._endpoints.push(
2,163✔
589
            _makeEndpointDescription(
2,163✔
590
                {
2,163✔
591
                    collection: this._policy_deduplicator,
2,163✔
592
                    hostname,
2,163✔
593
                    server: this.serverInfo,
2,163✔
594

2,163✔
595
                    securityMode,
2,163✔
596
                    securityPolicy,
2,163✔
597

2,163✔
598
                    allowUnsecurePassword: options.allowUnsecurePassword,
2,163✔
599
                    resourcePath: options.resourcePath,
2,163✔
600

2,163✔
601
                    restricted: !!options.restricted,
2,163✔
602
                    securityPolicies: options.securityPolicies || [],
2,166!
603

2,166✔
604
                    advertisedPort: options.advertisedPort,
2,166✔
605
                    userTokenTypes
2,166✔
606
                },
2,166✔
607
                this
2,166✔
608
            )
2,166✔
609
        );
2,166✔
610
    }
2,166✔
611

336✔
612
    public addRestrictedEndpointDescription(options: EndpointDescriptionParams): void {
336✔
613
        options = { ...options };
11✔
614
        options.restricted = true;
11✔
615
        this.addEndpointDescription(MessageSecurityMode.None, SecurityPolicy.None, options);
11✔
616
    }
11✔
617

336✔
618
    public addStandardEndpointDescriptions(options?: AddStandardEndpointDescriptionsParam): void {
336✔
619
        options = options || {};
324✔
620

324✔
621
        options.securityModes = options.securityModes || defaultSecurityModes;
324✔
622
        options.securityPolicies = options.securityPolicies || defaultSecurityPolicies;
324✔
623
        options.userTokenTypes = options.userTokenTypes || defaultUserTokenTypes;
324✔
624

324✔
625
        options.allowAnonymous = options.allowAnonymous === undefined ? true : options.allowAnonymous;
324✔
626
        // make sure we do not have anonymous
324✔
627
        if (!options.allowAnonymous) {
324✔
628
            options.userTokenTypes = options.userTokenTypes.filter((r) => r !== UserTokenType.Anonymous);
7✔
629
        }
7✔
630

324✔
631
        const defaultHostname = options.hostname || getFullyQualifiedDomainName();
324✔
632

324✔
633
        let hostnames: string[] = [defaultHostname];
324✔
634

324✔
635
        options.alternateHostname = options.alternateHostname || [];
324✔
636
        if (typeof options.alternateHostname === "string") {
324!
637
            options.alternateHostname = [options.alternateHostname];
×
638
        }
×
639
        // remove duplicates if any (uniq)
324✔
640
        hostnames = [...new Set(hostnames.concat(options.alternateHostname))];
324✔
641

324✔
642
        for (const alternateHostname of hostnames) {
324✔
643
            const optionsE: EndpointDescriptionParams = {
341✔
644
                hostname: alternateHostname,
341✔
645
                securityPolicies: options.securityPolicies,
341✔
646
                userTokenTypes: options.userTokenTypes,
341✔
647
                allowUnsecurePassword: options.allowUnsecurePassword,
341✔
648
                alternateHostname: options.alternateHostname,
341✔
649
                resourcePath: options.resourcePath
341✔
650
            };
341✔
651

341✔
652
            if (options.securityModes.indexOf(MessageSecurityMode.None) >= 0) {
341✔
653
                this.addEndpointDescription(MessageSecurityMode.None, SecurityPolicy.None, optionsE);
326✔
654
            } else {
341✔
655
                if (!options.disableDiscovery) {
15✔
656
                    this.addRestrictedEndpointDescription(optionsE);
10✔
657
                }
10✔
658
            }
15✔
659
            for (const securityMode of options.securityModes) {
341✔
660
                if (securityMode === MessageSecurityMode.None) {
972✔
661
                    continue;
326✔
662
                }
326✔
663
                for (const securityPolicy of options.securityPolicies) {
972✔
664
                    if (securityPolicy === SecurityPolicy.None) {
1,853✔
665
                        continue;
64✔
666
                    }
64✔
667
                    this.addEndpointDescription(securityMode, securityPolicy, optionsE);
1,851✔
668
                }
1,789✔
669
            }
694✔
670
        }
341✔
671

324✔
672
        // ── Advertised endpoints (virtual — no TCP listener) ──────
324✔
673
        // Normalize to AdvertisedEndpointConfig[] so downstream code
324✔
674
        // only deals with one type.
324✔
675
        const advertisedList = normalizeAdvertisedEndpoints(options.advertisedEndpoints);
324✔
676

324✔
677
        // Main endpoint defaults (guaranteed non-null — assigned above)
324✔
678
        const mainSecurityModes = options.securityModes || defaultSecurityModes;
324!
679
        const mainSecurityPolicies = options.securityPolicies || defaultSecurityPolicies;
324!
680
        const mainUserTokenTypes = options.userTokenTypes || defaultUserTokenTypes;
324!
681

324✔
682
        for (const config of advertisedList) {
324✔
683
            const { hostname: advHostname, port: advPort } = parseOpcTcpUrl(config.url);
14✔
684
            // Skip if this hostname+port combo was already covered
14✔
685
            // by the regular hostname loop (same hostname, same port)
14✔
686
            if (hostnames.some((h) => h.toLowerCase() === advHostname.toLowerCase()) && advPort === this.port) {
14✔
687
                continue;
1✔
688
            }
1✔
689

14✔
690
            // Per-URL security overrides — fall back to main settings
14✔
691
            const entrySecurityModes = config.securityModes ?? mainSecurityModes;
14✔
692
            const entrySecurityPolicies = config.securityPolicies ?? mainSecurityPolicies;
14✔
693
            let entryUserTokenTypes = config.userTokenTypes ?? mainUserTokenTypes;
14✔
694

14✔
695
            // Handle allowAnonymous override: if explicitly false,
14✔
696
            // filter out Anonymous even if the main config allows it
14✔
697
            if (config.allowAnonymous === false) {
14✔
698
                entryUserTokenTypes = entryUserTokenTypes.filter((t) => t !== UserTokenType.Anonymous);
1✔
699
            }
1✔
700

14✔
701
            const optionsE: EndpointDescriptionParams = {
14✔
702
                hostname: advHostname,
13✔
703
                advertisedPort: advPort,
13✔
704
                securityPolicies: entrySecurityPolicies,
13✔
705
                userTokenTypes: entryUserTokenTypes,
13✔
706
                allowUnsecurePassword: options.allowUnsecurePassword,
13✔
707
                alternateHostname: options.alternateHostname,
13✔
708
                resourcePath: options.resourcePath
13✔
709
            };
13✔
710

13✔
711
            if (entrySecurityModes.indexOf(MessageSecurityMode.None) >= 0) {
14✔
712
                this.addEndpointDescription(MessageSecurityMode.None, SecurityPolicy.None, optionsE);
12✔
713
            } else {
14✔
714
                if (!options.disableDiscovery) {
1✔
715
                    this.addRestrictedEndpointDescription(optionsE);
1✔
716
                }
1✔
717
            }
1✔
718
            for (const securityMode of entrySecurityModes) {
14✔
719
                if (securityMode === MessageSecurityMode.None) {
36✔
720
                    continue;
12✔
721
                }
12✔
722
                for (const securityPolicy of entrySecurityPolicies) {
36✔
723
                    if (securityPolicy === SecurityPolicy.None) {
40✔
724
                        continue;
20✔
725
                    }
20✔
726
                    this.addEndpointDescription(securityMode, securityPolicy, optionsE);
20✔
727
                }
20✔
728
            }
36✔
729
        }
14✔
730
    }
324✔
731

336✔
732
    /**
336✔
733
     * returns the list of end point descriptions.
336✔
734
     */
336✔
735
    public endpointDescriptions(): EndpointDescription[] {
336✔
736
        return this._endpoints;
8,244✔
737
    }
8,244✔
738

336✔
739
    /**
336✔
740
     */
336✔
741
    public listen(callback: (err?: Error) => void): void {
336✔
742
        assert(typeof callback === "function");
297✔
743
        assert(!this._started, "OPCUAServerEndPoint is already listening");
297✔
744

297✔
745
        if (!this._server) {
297!
746
            callback(new Error("Server is not initialized"));
×
747
            return;
×
748
        }
×
749

297✔
750
        this._listen_callback = callback;
297✔
751

297✔
752
        this._server.on("error", (err: Error) => {
297✔
753
            debugLog(`${chalk.red.bold(" error")} port = ${this.port}`, err);
2✔
754
            this._started = false;
2✔
755
            this._end_listen(err);
2✔
756
        });
297✔
757
        this._server.on("listening", () => {
297✔
758
            debugLog("server is listening");
299✔
759
        });
297✔
760

297✔
761
        const listenOptions: net.ListenOptions = {
297✔
762
            port: this.port,
297✔
763
            host: this.host
297✔
764
        };
297✔
765

297✔
766
        this._server.listen(
297✔
767
            listenOptions,
297✔
768
            /*"::",*/ (err?: Error) => {
297✔
769
                // 'listening' listener
295✔
770
                debugLog(chalk.green.bold("LISTENING TO PORT "), this.port, "err  ", err);
295✔
771
                assert(!err, " cannot listen to port ");
295✔
772
                this._started = true;
295✔
773
                if (!this.port) {
295✔
774
                    const add = this._server?.address();
3✔
775
                    this.port = typeof add !== "string" ? add?.port || 0 : this.port;
3!
776
                }
3✔
777
                this._end_listen();
295✔
778
            }
295✔
779
        );
297✔
780
    }
297✔
781

336✔
782
    public killClientSockets(callback: (err?: Error) => void): void {
336✔
783
        for (const channel of this.getChannels()) {
×
784
            const hacked_channel = channel as unknown as {
×
785
                transport: { _socket: { destroy: () => void; emit: (event: string, err: Error) => void } };
×
786
            };
×
787
            if (hacked_channel.transport?._socket) {
×
788
                // hacked_channel.transport._socket.close();
×
789
                hacked_channel.transport._socket.destroy();
×
790
                hacked_channel.transport._socket.emit("error", new Error("EPIPE"));
×
791
            }
×
792
        }
×
793
        callback();
×
794
    }
×
795

336✔
796
    public suspendConnection(callback: (err?: Error) => void): void {
336✔
797
        if (!this._started || !this._server) {
295!
798
            callback(new Error("Connection already suspended !!"));
×
799
            return;
×
800
        }
×
801

295✔
802
        // Stops the server from accepting new connections and keeps existing connections.
295✔
803
        // (note from nodejs doc: This function is asynchronous, the server is finally closed
295✔
804
        // when all connections are ended and the server emits a 'close' event.
295✔
805
        // The optional callback will be called once the 'close' event occurs.
295✔
806
        // Unlike that event, it will be called with an Error as its only argument
295✔
807
        // if the server was not open when it was closed.
295✔
808
        this._server.close(() => {
295✔
809
            this._started = false;
295✔
810
            debugLog(`Connection has been closed !${this.port}`);
295✔
811
        });
295✔
812
        this._started = false;
295✔
813
        callback();
295✔
814
    }
295✔
815

336✔
816
    public restoreConnection(callback: (err?: Error) => void): void {
336✔
817
        this.listen(callback);
4✔
818
    }
4✔
819

336✔
820
    public abruptlyInterruptChannels(): void {
336✔
821
        for (const channel of Object.values(this._channels)) {
4✔
822
            channel.abruptlyInterrupt();
7✔
823
        }
7✔
824
    }
4✔
825

336✔
826
    /**
336✔
827
     */
336✔
828
    public shutdown(callback: (err?: Error) => void): void {
336✔
829
        debugLog("OPCUAServerEndPoint#shutdown ");
321✔
830

321✔
831
        if (this._started) {
321✔
832
            // make sure we don't accept new connection any more ...
291✔
833
            this.suspendConnection(() => {
291✔
834
                // shutdown all opened channels ...
291✔
835
                const _channels = Object.values(this._channels);
291✔
836
                const promises = _channels.map(
291✔
837
                    (channel) =>
291✔
838
                        new Promise<void>((resolve, reject) => {
18✔
839
                            this.shutdown_channel(channel, (err?: Error) => {
18✔
840
                                if (err) {
18✔
841
                                    reject(err);
×
842
                                } else {
18✔
843
                                    resolve();
18✔
844
                                }
18✔
845
                            });
18✔
846
                        })
18✔
847
                );
291✔
848
                Promise.all(promises)
291✔
849
                    .then(() => {
291✔
850
                        /* c8 ignore next */
2✔
851
                        if (!(Object.keys(this._channels).length === 0)) {
2✔
852
                            errorLog(" Bad !");
×
853
                        }
×
854
                        assert(Object.keys(this._channels).length === 0, "channel must have unregistered themselves");
291✔
855
                        callback();
291✔
856
                    })
291✔
857
                    .catch((err) => {
291✔
858
                        callback(err);
×
859
                    });
291✔
860
            });
291✔
861
        } else {
321✔
862
            callback();
30✔
863
        }
30✔
864
    }
321✔
865

336✔
866
    /**
336✔
867
     */
336✔
868
    public start(callback: (err?: Error) => void): void {
336✔
869
        assert(typeof callback === "function");
293✔
870
        this.listen(callback);
293✔
871
    }
293✔
872

336✔
873
    public get bytesWritten(): number {
336✔
874
        const channels = Object.values(this._channels);
1✔
875
        return (
1✔
876
            this.bytesWrittenInOldChannels +
1✔
877
            channels.reduce((accumulated: number, channel: ServerSecureChannelLayer) => {
1✔
878
                return accumulated + channel.bytesWritten;
1✔
879
            }, 0)
1✔
880
        );
1✔
881
    }
1✔
882

336✔
883
    public get bytesRead(): number {
336✔
884
        const channels = Object.values(this._channels);
1✔
885
        return (
1✔
886
            this.bytesReadInOldChannels +
1✔
887
            channels.reduce((accumulated: number, channel: ServerSecureChannelLayer) => {
1✔
888
                return accumulated + channel.bytesRead;
1✔
889
            }, 0)
1✔
890
        );
1✔
891
    }
1✔
892

336✔
893
    public get transactionsCount(): number {
336✔
894
        const channels = Object.values(this._channels);
1✔
895
        return (
1✔
896
            this.transactionsCountOldChannels +
1✔
897
            channels.reduce((accumulated: number, channel: ServerSecureChannelLayer) => {
1✔
898
                return accumulated + channel.transactionsCount;
1✔
899
            }, 0)
1✔
900
        );
1✔
901
    }
1✔
902

336✔
903
    public get securityTokenCount(): number {
336✔
904
        const channels = Object.values(this._channels);
110✔
905
        return (
110✔
906
            this.securityTokenCountOldChannels +
110✔
907
            channels.reduce((accumulated: number, channel: ServerSecureChannelLayer) => {
110✔
908
                return accumulated + channel.securityTokenCount;
110✔
909
            }, 0)
110✔
910
        );
110✔
911
    }
110✔
912

336✔
913
    public get activeChannelCount(): number {
336✔
914
        return Object.keys(this._channels).length;
1,523✔
915
    }
1,523✔
916

336✔
917
    private _dump_statistics() {
336✔
918
        this._server?.getConnections((_err: Error | null, count: number) => {
×
919
            debugLog(chalk.cyan("CONCURRENT CONNECTION = "), count);
×
920
        });
×
921
        debugLog(chalk.cyan("MAX CONNECTIONS = "), this._server?.maxConnections);
×
922
    }
×
923

336✔
924
    private _setup_server() {
336✔
925
        assert(!this._server);
336✔
926
        this._server = net.createServer({ pauseOnConnect: true }, this._on_client_connection.bind(this));
336✔
927

336✔
928
        // xx console.log(" Server with max connections ", self.maxConnections);
336✔
929
        this._server.maxConnections = this.maxConnections + 1; // plus one extra
336✔
930

336✔
931
        this._listen_callback = undefined;
336✔
932
        this._server
336✔
933
            .on("connection", (socket: Socket) => {
336✔
934
                // c8 ignore next
1,523✔
935
                if (doDebug) {
1,523!
936
                    this._dump_statistics();
×
937
                    debugLog(`server connected  with : ${socket.remoteAddress}:${socket.remotePort}`);
×
938
                }
×
939
            })
336✔
940
            .on("close", () => {
336✔
941
                debugLog("server closed : all connections have ended");
295✔
942
            })
336✔
943
            .on("error", (err: Error) => {
336✔
944
                // this could be because the port is already in use
2✔
945
                debugLog(chalk.red.bold("server error: "), err.message);
2✔
946
            });
336✔
947
    }
336✔
948

336✔
949
    private _on_client_connection(socket: Socket) {
336✔
950
        // a client is attempting a connection on the socket
1,523✔
951
        socket.setNoDelay(true);
1,523✔
952

1,523✔
953
        debugLog("OPCUAServerEndPoint#_on_client_connection", this._started);
1,523✔
954
        if (!this._started) {
1,523!
955
            debugLog(
×
956
                chalk.bgWhite.cyan(
×
957
                    "OPCUAServerEndPoint#_on_client_connection " +
×
958
                        "SERVER END POINT IS PROBABLY SHUTTING DOWN !!! - Connection is refused"
×
959
                )
×
960
            );
×
961
            socket.end();
×
962
            return;
×
963
        }
×
964
        const deny_connection = () => {
1,523✔
965
            console.log(
19✔
966
                chalk.bgWhite.cyan(
19✔
967
                    "OPCUAServerEndPoint#_on_client_connection " +
19✔
968
                        "The maximum number of connection has been reached - Connection is refused"
19✔
969
                )
19✔
970
            );
19✔
971
            const reason = `maxConnections reached (${this.maxConnections})`;
19✔
972
            const socketData = extractSocketData(socket, reason);
19✔
973
            this.emit("connectionRefused", socketData);
19✔
974

19✔
975
            socket.end();
19✔
976
            socket.destroy();
19✔
977
        };
1,515✔
978

1,523✔
979
        const establish_connection = () => {
1,523✔
980
            const nbConnections = Object.keys(this._channels).length;
1,507✔
981
            if (nbConnections >= this.maxConnections) {
1,507✔
982
                warningLog(
3✔
983
                    " nbConnections ",
3✔
984
                    nbConnections,
3✔
985
                    " self._server.maxConnections",
3✔
986
                    this._server?.maxConnections,
3✔
987
                    this.maxConnections
3✔
988
                );
3✔
989
                deny_connection();
3✔
990
                return;
3✔
991
            }
3✔
992

1,504✔
993
            debugLog("OPCUAServerEndPoint._on_client_connection successful => New Channel");
1,504✔
994

1,504✔
995
            const channel = new ServerSecureChannelLayer({
1,504✔
996
                defaultSecureTokenLifetime: this.defaultSecureTokenLifetime,
1,504✔
997
                // objectFactory: this.objectFactory,
1,504✔
998
                parent: this,
1,504✔
999
                timeout: this.timeout,
1,504✔
1000
                adjustTransportLimits: this.transportSettings?.adjustTransportLimits
1,507✔
1001
            });
1,507✔
1002

1,507✔
1003
            debugLog("channel Timeout = >", channel.timeout);
1,507✔
1004

1,507✔
1005
            socket.resume();
1,507✔
1006

1,507✔
1007
            this._preregisterChannel(channel);
1,507✔
1008

1,507✔
1009
            channel.init(socket, (err?: Error) => {
1,507✔
1010
                this._un_pre_registerChannel(channel);
1,504✔
1011
                debugLog(chalk.yellow.bold("Channel#init done"), err);
1,504✔
1012
                if (err) {
1,504✔
1013
                    const reason = `openSecureChannel has Failed ${err.message}`;
8✔
1014
                    const socketData = extractSocketData(socket, reason);
8✔
1015
                    const channelData = extractChannelData(channel);
8✔
1016
                    this.emit("openSecureChannelFailure", socketData, channelData);
8✔
1017

8✔
1018
                    socket.end();
8✔
1019
                    socket.destroy();
8✔
1020
                } else {
1,504✔
1021
                    debugLog("server receiving a client connection");
1,496✔
1022
                    this._registerChannel(channel);
1,496✔
1023
                }
1,496✔
1024
            });
1,507✔
1025

1,507✔
1026
            channel.on("message", (message: Message) => {
1,507✔
1027
                // forward
46,463✔
1028
                this.emit("message", message, channel, this);
46,463✔
1029
            });
1,507✔
1030
        };
1,523✔
1031

1,523✔
1032
        // Each SecureChannel exists until it is explicitly closed or until the last token has expired and the overlap
1,523✔
1033
        // period has elapsed. A Server application should limit the number of SecureChannels.
1,523✔
1034
        // To protect against misbehaving Clients and denial of service attacks, the Server shall close the oldest
1,523✔
1035
        // SecureChannel that has no Session assigned before reaching the maximum number of supported SecureChannels.
1,523✔
1036
        this._prevent_DDOS_Attack(establish_connection, deny_connection);
1,523✔
1037
    }
1,523✔
1038
    private _preregisterChannel(channel: ServerSecureChannelLayer) {
336✔
1039
        // _preregisterChannel is used to keep track of channel for which
1,504✔
1040
        // that are in early stage of the hand shaking process.
1,504✔
1041
        // e.g HEL/ACK and OpenSecureChannel may not have been received yet
1,504✔
1042
        // as they will need to be interrupted when OPCUAServerEndPoint is closed
1,504✔
1043
        assert(this._started, "OPCUAServerEndPoint must be started");
1,504✔
1044

1,504✔
1045
        assert(!Object.hasOwn(this._channels, channel.hashKey), " channel already preregistered!");
1,504✔
1046

1,504✔
1047
        this._channels[channel.hashKey] = channel;
1,504✔
1048
        const onAbort = () => {
1,504✔
1049
            debugLog("Channel received an abort event during the preregistration phase");
2✔
1050
            this._un_pre_registerChannel(channel);
2✔
1051
            channel.dispose();
2✔
1052
        };
1,496✔
1053
        preregisterAbortListeners.set(channel, onAbort);
1,504✔
1054
        channel.on("abort", onAbort);
1,504✔
1055
    }
1,504✔
1056

336✔
1057
    private _un_pre_registerChannel(channel: ServerSecureChannelLayer) {
336✔
1058
        if (!this._channels[channel.hashKey]) {
1,506✔
1059
            debugLog("Already un preregistered ?", channel.hashKey);
2✔
1060
            return;
2✔
1061
        }
2✔
1062
        delete this._channels[channel.hashKey];
1,504✔
1063
        const onAbort = preregisterAbortListeners.get(channel);
1,504✔
1064
        if (onAbort) {
1,504✔
1065
            channel.removeListener("abort", onAbort);
1,504✔
1066
            preregisterAbortListeners.delete(channel);
1,504✔
1067
        }
1,504✔
1068
    }
1,506✔
1069

336✔
1070
    /**
336✔
1071
     * @private
336✔
1072
     */
336✔
1073
    private _registerChannel(channel: ServerSecureChannelLayer) {
336✔
1074
        if (this._started) {
1,496✔
1075
            debugLog(chalk.red("_registerChannel = "), "channel.hashKey = ", channel.hashKey);
1,496✔
1076

1,496✔
1077
            assert(!this._channels[channel.hashKey]);
1,496✔
1078
            this._channels[channel.hashKey] = channel;
1,496✔
1079

1,496✔
1080
            /**
1,496✔
1081
             * @event newChannel — fired after transport init (HEL/ACK).
1,496✔
1082
             * Note: securityPolicy/securityMode are NOT yet established.
1,496✔
1083
             * @param channel
1,496✔
1084
             */
1,496✔
1085
            this.emit("newChannel", channel);
1,496✔
1086

1,496✔
1087
            channel.once("open", () => {
1,496✔
1088
                /**
1,448✔
1089
                 * @event channelSecured — fired after OpenSecureChannel
1,448✔
1090
                 * handshake succeeds. securityPolicy, securityMode, and
1,448✔
1091
                 * clientCertificate are available at this point.
1,448✔
1092
                 * @param channel
1,448✔
1093
                 */
1,448✔
1094
                this.emit("channelSecured", channel);
1,448✔
1095
            });
1,496✔
1096

1,496✔
1097
            channel.on("abort", () => {
1,496✔
1098
                this._unregisterChannel(channel);
1,527✔
1099
            });
1,496✔
1100
        } else {
1,496!
1101
            debugLog("OPCUAServerEndPoint#_registerChannel called when end point is shutdown !");
×
1102
            debugLog("  -> channel will be forcefully terminated");
×
1103
            channel.close(() => {
×
1104
                channel.dispose();
×
1105
            });
×
1106
        }
×
1107
    }
1,496✔
1108

336✔
1109
    /**
336✔
1110
     */
336✔
1111
    private _unregisterChannel(channel: ServerSecureChannelLayer): void {
336✔
1112
        debugLog("_un-registerChannel channel.hashKey", channel.hashKey);
1,555✔
1113
        if (!Object.hasOwn(this._channels, channel.hashKey)) {
1,555✔
1114
            return;
59✔
1115
        }
59✔
1116

1,496✔
1117
        assert(Object.hasOwn(this._channels, channel.hashKey), "channel is not registered");
1,496✔
1118

1,496✔
1119
        /**
1,496✔
1120
         * @event closeChannel
1,496✔
1121
         * @param channel
1,496✔
1122
         */
1,496✔
1123
        this.emit("closeChannel", channel);
1,496✔
1124

1,496✔
1125
        // keep trace of statistics data from old channel for our own accumulated stats.
1,496✔
1126
        this.bytesWrittenInOldChannels += channel.bytesWritten;
1,496✔
1127
        this.bytesReadInOldChannels += channel.bytesRead;
1,496✔
1128
        this.transactionsCountOldChannels += channel.transactionsCount;
1,496✔
1129
        delete this._channels[channel.hashKey];
1,496✔
1130

1,496✔
1131
        // c8 ignore next
1,496✔
1132
        if (doDebug) {
1,555!
1133
            this._dump_statistics();
×
1134
            debugLog("un-registering channel  - Count = ", this.currentChannelCount);
×
1135
        }
×
1136

1,555✔
1137
        /// channel.dispose();
1,555✔
1138
    }
1,555✔
1139

336✔
1140
    private _end_listen(err?: Error) {
336✔
1141
        if (!this._listen_callback) return;
297!
1142
        this._listen_callback(err);
297✔
1143
        this._listen_callback = undefined;
297✔
1144
    }
297✔
1145

336✔
1146
    /**
336✔
1147
     *  shutdown_channel
336✔
1148
     * @param channel
336✔
1149
     * @param inner_callback
336✔
1150
     */
336✔
1151
    private shutdown_channel(channel: ServerSecureChannelLayer, inner_callback: (err?: Error) => void) {
336✔
1152
        assert(typeof inner_callback === "function");
18✔
1153
        channel.once("close", () => {
18✔
1154
            // xx console.log(" ON CLOSED !!!!");
×
1155
        });
18✔
1156

18✔
1157
        channel.close(() => {
18✔
1158
            this._unregisterChannel(channel);
18✔
1159
            setImmediate(inner_callback);
18✔
1160
        });
18✔
1161
    }
18✔
1162

336✔
1163
    /**
336✔
1164
     * @private
336✔
1165
     */
336✔
1166
    private _prevent_DDOS_Attack(establish_connection: () => void, deny_connection: () => void) {
336✔
1167
        const nbConnections = this.activeChannelCount;
1,523✔
1168

1,523✔
1169
        if (nbConnections >= this.maxConnections) {
1,523✔
1170
            // c8 ignore next
26✔
1171
            errorLog(chalk.bgRed.white(`PREVENTING DDOS ATTACK => maxConnection =${this.maxConnections}`));
26✔
1172

26✔
1173
            const unused_channels: ServerSecureChannelLayer[] = this.getChannels().filter((channel1: ServerSecureChannelLayer) => {
26✔
1174
                return !channel1.hasSession;
78✔
1175
            });
26✔
1176
            if (unused_channels.length === 0) {
26✔
1177
                doDebug &&
16!
1178
                    console.log(
×
1179
                        this.getChannels()
×
1180
                            .map(({ status, isOpened, hasSession }) => `${status} ${isOpened} ${hasSession}\n`)
×
1181
                            .join(" ")
×
1182
                    );
16✔
1183
                // all channels are in used , we cannot get any
16✔
1184
                errorLog(`All channels are in used ! we cannot cancel any ${this.getChannels().length}`);
16✔
1185
                // c8 ignore next
16✔
1186
                if (doDebug) {
16!
1187
                    console.log("  - all channels are used !!!!");
×
1188
                    false && dumpChannelInfo(this.getChannels());
×
1189
                }
×
1190
                setTimeout(deny_connection, 1000);
16✔
1191
                return;
16✔
1192
            }
16✔
1193
            // c8 ignore next
10✔
1194
            if (doDebug) {
26!
1195
                console.log(
×
1196
                    "   - Unused channels that can be clobbered",
×
1197
                    unused_channels.map((channel1: ServerSecureChannelLayer) => channel1.hashKey).join(" ")
×
1198
                );
×
1199
            }
×
1200
            const channel = unused_channels[0];
10✔
1201
            errorLog(`${unused_channels.length} : Forcefully closing oldest channel that have no session: ${channel.hashKey}`);
10✔
1202
            channel.close(() => {
10✔
1203
                // c8 ignore next
10✔
1204
                if (doDebug) {
10!
1205
                    console.log("   _ Unused channel has been closed ", channel.hashKey);
×
1206
                }
×
1207
                this._unregisterChannel(channel);
10✔
1208
                establish_connection();
10✔
1209
            });
10✔
1210
        } else {
1,523✔
1211
            setImmediate(establish_connection);
1,497✔
1212
        }
1,497✔
1213
    }
1,523✔
1214
}
336✔
1215

2✔
1216
interface MakeEndpointDescriptionOptions {
2✔
1217
    /**
2✔
1218
     * @default  default hostname (default value will be full qualified domain name)
2✔
1219
     */
2✔
1220
    hostname: string;
2✔
1221

2✔
1222
    /**
2✔
1223
     *
2✔
1224
     */
2✔
1225
    securityMode: MessageSecurityMode;
2✔
1226
    /**
2✔
1227
     *
2✔
1228
     */
2✔
1229
    securityPolicy: SecurityPolicy;
2✔
1230

2✔
1231
    securityLevel?: number;
2✔
1232

2✔
1233
    server: ApplicationDescription;
2✔
1234
    /*
2✔
1235
        {
2✔
1236
            applicationUri: string;
2✔
1237
            applicationName: LocalizedTextOptions;
2✔
1238
            applicationType: ApplicationType;
2✔
1239
            gatewayServerUri: string;
2✔
1240
            discoveryProfileUri: string;
2✔
1241
            discoveryUrls: string[];
2✔
1242
        };
2✔
1243
     */
2✔
1244
    resourcePath?: string;
2✔
1245

2✔
1246
    // allow un-encrypted password in userNameIdentity
2✔
1247
    allowUnsecurePassword?: boolean; // default false
2✔
1248

2✔
1249
    /**
2✔
1250
     * onlyCertificateLessConnection
2✔
1251
     */
2✔
1252
    onlyCertificateLessConnection?: boolean;
2✔
1253

2✔
1254
    restricted: boolean;
2✔
1255

2✔
1256
    collection: { [key: string]: number };
2✔
1257

2✔
1258
    securityPolicies: SecurityPolicy[];
2✔
1259

2✔
1260
    userTokenTypes: UserTokenType[];
2✔
1261
    /**
2✔
1262
     * Override the port used in the dynamic endpointUrl getter.
2✔
1263
     * When set, the endpoint URL advertises this port instead of
2✔
1264
     * the parent's listen port.
2✔
1265
     */
2✔
1266
    advertisedPort?: number;
2✔
1267
    /**
2✔
1268
     *
2✔
1269
     * default value: false;
2✔
1270
     *
2✔
1271
     * note: setting noUserIdentityTokens=true is useful for pure local discovery servers
2✔
1272
     */
2✔
1273
    noUserIdentityTokens?: boolean;
2✔
1274
}
2✔
1275

2✔
1276
export interface EndpointDescriptionEx extends EndpointDescription {
2✔
1277
    _parent: OPCUAServerEndPoint;
2✔
1278
    restricted: boolean;
2✔
1279
}
2✔
1280

2✔
1281
function estimateSecurityLevel(securityMode: MessageSecurityMode, securityPolicy: SecurityPolicy): number {
2,163✔
1282
    if (securityMode === MessageSecurityMode.None) {
2,163✔
1283
        return 1;
352✔
1284
    }
352✔
1285
    let offset = 100;
1,875✔
1286
    if (securityMode === MessageSecurityMode.SignAndEncrypt) {
2,163✔
1287
        offset = 200;
920✔
1288
    }
920✔
1289
    switch (securityPolicy) {
1,875✔
1290
        case SecurityPolicy.Basic128:
2,163✔
1291
        case SecurityPolicy.Basic128Rsa15:
2,163✔
1292
        case SecurityPolicy.Basic192:
1,945✔
1293
            return 2; // deprecated => low
1,945✔
1294
        case SecurityPolicy.Basic192Rsa15:
2,163!
1295
            return 3; // deprecated => low
1,938✔
1296
        case SecurityPolicy.Basic256:
2,163✔
1297
            return 4; // deprecated => low
1,947✔
1298
        case SecurityPolicy.Basic256Rsa15:
2,163!
1299
            return 4 + offset;
×
1300
        case SecurityPolicy.Aes128_Sha256_RsaOaep:
2,163✔
1301
            return 5 + offset;
577✔
1302
        case SecurityPolicy.Basic256Sha256:
2,163✔
1303
            return 6 + offset;
607✔
1304
        case SecurityPolicy.Aes256_Sha256_RsaPss:
2,163✔
1305
            return 7 + offset;
581✔
1306

2,163✔
1307
        default:
2,163!
1308
            return 1;
×
1309
    }
2,163✔
1310
}
2,163✔
1311
/**
2✔
1312
 * @private
2✔
1313
 */
2✔
1314
function _makeEndpointDescription(options: MakeEndpointDescriptionOptions, parent: OPCUAServerEndPoint): EndpointDescriptionEx {
2,163✔
1315
    const u = (n: string) => getUniqueName(n, options.collection);
2,163✔
1316
    options.securityLevel =
2,163✔
1317
        options.securityLevel === undefined
2,163✔
1318
            ? estimateSecurityLevel(options.securityMode, options.securityPolicy)
2,163✔
1319
            : options.securityLevel;
1,938!
1320
    assert(Number.isFinite(options.securityLevel), "expecting a valid securityLevel");
2,163✔
1321

2,163✔
1322
    const securityPolicyUri = toURI(options.securityPolicy);
2,163✔
1323

2,163✔
1324
    const userIdentityTokens: UserTokenPolicyOptions[] = [];
2,163✔
1325

2,163✔
1326
    const registerIdentity2 = (tokenType: UserTokenType, securityPolicy: SecurityPolicy, name: string) => {
2,163✔
1327
        return registerIdentity({
2,816✔
1328
            policyId: u(name),
2,816✔
1329
            tokenType,
2,816✔
1330
            issuedTokenType: null,
2,816✔
1331
            issuerEndpointUrl: null,
2,816✔
1332
            securityPolicyUri: securityPolicy
2,816✔
1333
        });
2,816✔
1334
    };
2,450✔
1335
    const registerIdentity = (r: UserTokenPolicyOptions) => {
2,163✔
1336
        const tokenType = r.tokenType === undefined ? UserTokenType.Invalid : r.tokenType;
8,601!
1337
        const securityPolicy = (r.securityPolicyUri || "") as SecurityPolicy;
8,601✔
1338
        if (!securityPolicy && options.userTokenTypes.indexOf(tokenType) >= 0) {
8,601✔
1339
            userIdentityTokens.push(r);
5,417✔
1340
            return;
5,417✔
1341
        }
5,417✔
1342
        if (options.securityPolicies.indexOf(securityPolicy) >= 0 && options.userTokenTypes.indexOf(tokenType) >= 0) {
8,601✔
1343
            userIdentityTokens.push(r);
1,171✔
1344
        }
1,171✔
1345
    };
2,997✔
1346

2,163✔
1347
    if (!options.noUserIdentityTokens) {
2,163✔
1348
        if (options.securityPolicy === SecurityPolicy.None) {
2,163✔
1349
            if (options.allowUnsecurePassword) {
352!
1350
                registerIdentity({
×
1351
                    policyId: u("username_unsecure"),
×
1352
                    tokenType: UserTokenType.UserName,
×
1353

×
1354
                    issuedTokenType: null,
×
1355
                    issuerEndpointUrl: null,
×
1356
                    securityPolicyUri: null
×
1357
                });
×
1358
            }
×
1359

352✔
1360
            const onlyCertificateLessConnection =
352✔
1361
                options.onlyCertificateLessConnection === undefined ? false : options.onlyCertificateLessConnection;
352!
1362

352✔
1363
            if (!onlyCertificateLessConnection) {
352✔
1364
                registerIdentity2(UserTokenType.UserName, SecurityPolicy.Basic256, "username_basic256");
352✔
1365
                registerIdentity2(UserTokenType.UserName, SecurityPolicy.Basic128Rsa15, "username_basic128Rsa15");
352✔
1366
                registerIdentity2(UserTokenType.UserName, SecurityPolicy.Basic256Sha256, "username_basic256Sha256");
352✔
1367
                registerIdentity2(UserTokenType.UserName, SecurityPolicy.Aes128_Sha256_RsaOaep, "username_aes128Sha256RsaOaep");
352✔
1368

352✔
1369
                // X509
352✔
1370
                registerIdentity2(UserTokenType.Certificate, SecurityPolicy.Basic256, "certificate_basic256");
352✔
1371
                registerIdentity2(UserTokenType.Certificate, SecurityPolicy.Basic128Rsa15, "certificate_basic128Rsa15");
352✔
1372
                registerIdentity2(UserTokenType.Certificate, SecurityPolicy.Basic256Sha256, "certificate_basic256Sha256");
352✔
1373
                registerIdentity2(
352✔
1374
                    UserTokenType.Certificate,
352✔
1375
                    SecurityPolicy.Aes128_Sha256_RsaOaep,
352✔
1376
                    "certificate_aes128Sha256RsaOaep"
352✔
1377
                );
352✔
1378
            }
352✔
1379
        } else {
2,163✔
1380
            // note:
1,811✔
1381
            //  when channel session security is not "None",
1,811✔
1382
            //  userIdentityTokens can be left to null.
1,811✔
1383
            //  in this case this mean that secure policy will be the same as connection security policy
1,811✔
1384
            // c8 ignore next
1,811✔
1385
            if (process.env.NODEOPCUA_SERVER_EMULATE_SIEMENS) {
1,811!
1386
                // However, for some reason SIEMENS plc requires that password get encrypted even though
×
1387
                // the secure channel is also encrypted ....
×
1388
                // you can set the NODEOPCUA_SERVER_EMULATE_SIEMENS env variable to simulate this behavior
×
1389
                const registerIdentity3 = (tokenType: UserTokenType, securityPolicy: SecurityPolicy, name: string) => {
×
1390
                    const identity = {
×
1391
                        policyId: u(name),
×
1392
                        tokenType,
×
1393
                        issuedTokenType: null,
×
1394
                        issuerEndpointUrl: null,
×
1395
                        securityPolicyUri: securityPolicy
×
1396
                    };
×
1397
                    userIdentityTokens.push(identity);
×
1398
                };
×
1399
                registerIdentity3(UserTokenType.UserName, SecurityPolicy.Basic256, "username_basic256");
×
1400
                registerIdentity3(UserTokenType.UserName, SecurityPolicy.Basic128Rsa15, "username_basic128Rsa15");
×
1401
                registerIdentity3(UserTokenType.UserName, SecurityPolicy.Basic256Sha256, "username_basic256Sha256");
×
1402
            } else {
1,811✔
1403
                registerIdentity({
1,811✔
1404
                    policyId: u("usernamePassword"),
1,811✔
1405
                    tokenType: UserTokenType.UserName,
1,811✔
1406
                    issuedTokenType: null,
1,811✔
1407
                    issuerEndpointUrl: null,
1,811✔
1408
                    securityPolicyUri: null
1,811✔
1409
                });
1,811✔
1410

1,811✔
1411
                registerIdentity({
1,811✔
1412
                    policyId: u("certificateX509"),
1,811✔
1413
                    tokenType: UserTokenType.Certificate,
1,811✔
1414

1,811✔
1415
                    issuedTokenType: null,
1,811✔
1416
                    issuerEndpointUrl: null,
1,811✔
1417
                    securityPolicyUri: null
1,811✔
1418
                });
1,811✔
1419
            }
1,811✔
1420
        }
1,811✔
1421

2,163✔
1422
        registerIdentity({
2,163✔
1423
            policyId: u("anonymous"),
2,163✔
1424
            tokenType: UserTokenType.Anonymous,
2,163✔
1425

2,163✔
1426
            issuedTokenType: null,
2,163✔
1427
            issuerEndpointUrl: null,
2,163✔
1428
            securityPolicyUri: null
2,163✔
1429
        });
2,163✔
1430
    }
2,163✔
1431
    // return the endpoint object
2,163✔
1432
    const endpoint = new EndpointDescription({
2,163✔
1433
        endpointUrl: "<to be evaluated at run time>", // options.endpointUrl,
2,163✔
1434

2,163✔
1435
        server: undefined, // options.server,
2,163✔
1436
        // serverCertificate is set as a dynamic getter below
2,163✔
1437
        serverCertificate: undefined,
2,163✔
1438

2,163✔
1439
        securityMode: options.securityMode,
2,163✔
1440
        securityPolicyUri,
2,163✔
1441
        userIdentityTokens,
2,163✔
1442

2,163✔
1443
        securityLevel: options.securityLevel,
2,163✔
1444
        transportProfileUri: UATCP_UASC_UABINARY
2,163✔
1445
    }) as EndpointDescriptionEx;
2,163✔
1446
    endpoint._parent = parent;
2,163✔
1447

2,163✔
1448
    // serverCertificate — always dynamic.
2,163✔
1449
    // Delegates to the parent endpoint's combined DER cache
2,163✔
1450
    // which in turn reads from the current ICertificateChainProvider.
2,163✔
1451
    // This ensures GetEndpoints always returns the current chain,
2,163✔
1452
    // whether the provider is static or disk-based.
2,163✔
1453
    Object.defineProperty(endpoint, "serverCertificate", {
2,163✔
1454
        get: () => parent.getCombinedCertificate(),
2,163✔
1455
        configurable: true
2,163✔
1456
    });
2,163✔
1457

2,163✔
1458
    // endpointUrl is dynamic as port number may be adjusted
2,163✔
1459
    // when the tcp socket start listening
2,163✔
1460
    Object.defineProperty(endpoint, "endpointUrl", {
2,163✔
1461
        get: () => {
2,163✔
1462
            const port = options.advertisedPort ?? endpoint._parent.port;
87,246✔
1463
            const resourcePath = options.resourcePath || "";
87,246✔
1464
            const hostname = options.hostname;
87,246✔
1465
            const endpointUrl = `opc.tcp://${hostname}:${port}${resourcePath}`;
87,246✔
1466
            return resolveFullyQualifiedDomainName(endpointUrl);
87,246✔
1467
        },
3,035✔
1468
        configurable: true
2,163✔
1469
    });
2,163✔
1470

2,163✔
1471
    endpoint.server = options.server;
2,163✔
1472

2,163✔
1473
    endpoint.restricted = options.restricted;
2,163✔
1474

2,163✔
1475
    return endpoint;
2,163✔
1476
}
2,163✔
1477

2✔
1478
/**
2✔
1479
 * return true if the end point matches security mode and policy
2✔
1480
 * @param endpoint
2✔
1481
 * @param securityMode
2✔
1482
 * @param securityPolicy
2✔
1483
 * @internal
2✔
1484
 *
2✔
1485
 */
2✔
1486
function matching_endpoint(
17,977✔
1487
    securityMode: MessageSecurityMode,
17,977✔
1488
    securityPolicy: SecurityPolicy,
17,977✔
1489
    endpointUrl: string | null,
17,977✔
1490
    endpoint: EndpointDescription
17,977✔
1491
): boolean {
17,977✔
1492
    assert(endpoint instanceof EndpointDescription);
17,977✔
1493
    const endpoint_securityPolicy = fromURI(endpoint.securityPolicyUri);
17,977✔
1494
    if (endpointUrl && endpoint.endpointUrl !== endpointUrl) {
17,977✔
1495
        return false;
604✔
1496
    }
604✔
1497
    return endpoint.securityMode === securityMode && endpoint_securityPolicy === securityPolicy;
17,977✔
1498
}
17,977✔
1499

2✔
1500
const defaultSecurityModes = [MessageSecurityMode.None, MessageSecurityMode.Sign, MessageSecurityMode.SignAndEncrypt];
2✔
1501

2✔
1502
const defaultSecurityPolicies = [
2✔
1503
    // now deprecated  Basic128Rs15 shall be disabled by default
2✔
1504
    // see https://profiles.opcfoundation.org/profile/1532
2✔
1505
    // SecurityPolicy.Basic128Rsa15,
2✔
1506

2✔
1507
    // now deprecated Basic256 shall be disabled by default
2✔
1508
    // see https://profiles.opcfoundation.org/profile/2062
2✔
1509
    // SecurityPolicy.Basic256,
2✔
1510

2✔
1511
    // xx UNUSED!!  SecurityPolicy.Basic192Rsa15,
2✔
1512
    // xx UNUSED!!  SecurityPolicy.Basic256Rsa15,
2✔
1513

2✔
1514
    SecurityPolicy.Basic256Sha256,
2✔
1515
    SecurityPolicy.Aes128_Sha256_RsaOaep,
2✔
1516
    SecurityPolicy.Aes256_Sha256_RsaPss
2✔
1517
];
2✔
1518

2✔
1519
const defaultUserTokenTypes = [
2✔
1520
    UserTokenType.Anonymous,
2✔
1521
    UserTokenType.UserName,
2✔
1522
    UserTokenType.Certificate
2✔
1523
    // NOT USED YET : UserTokenType.IssuedToken
2✔
1524
];
1!
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc