node-opcua-server 2.166.0 → 2.168.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.d.ts +6 -6
- package/dist/addressSpace_accessor.js +2 -2
- package/dist/addressSpace_accessor.js.map +1 -1
- package/dist/base_server.d.ts +14 -3
- package/dist/base_server.js +64 -44
- package/dist/base_server.js.map +1 -1
- package/dist/extract_password_from_blob.js +1 -3
- package/dist/extract_password_from_blob.js.map +1 -1
- package/dist/factory.d.ts +2 -3
- package/dist/factory.js.map +1 -1
- package/dist/filter/check_where_clause_on_address_space.d.ts +2 -2
- package/dist/filter/check_where_clause_on_address_space.js +1 -2
- package/dist/filter/check_where_clause_on_address_space.js.map +1 -1
- package/dist/filter/extract_event_fields.d.ts +3 -3
- package/dist/filter/extract_event_fields.js +1 -2
- package/dist/filter/extract_event_fields.js.map +1 -1
- package/dist/helper.d.ts +3 -3
- package/dist/helper.js +4 -8
- package/dist/helper.js.map +1 -1
- package/dist/i_address_space_accessor.d.ts +4 -4
- package/dist/i_channel_data.d.ts +1 -1
- package/dist/i_register_server_manager.d.ts +1 -1
- package/dist/i_server_side_publish_engine.d.ts +8 -6
- package/dist/i_server_side_publish_engine.js +7 -2
- package/dist/i_server_side_publish_engine.js.map +1 -1
- package/dist/index.d.ts +8 -7
- package/dist/index.js +8 -7
- package/dist/index.js.map +1 -1
- package/dist/invalidate_server_certificate_cache.d.ts +16 -0
- package/dist/invalidate_server_certificate_cache.js +28 -0
- package/dist/invalidate_server_certificate_cache.js.map +1 -0
- package/dist/monitored_item.d.ts +10 -11
- package/dist/monitored_item.js +38 -39
- package/dist/monitored_item.js.map +1 -1
- package/dist/node_sampler.d.ts +1 -1
- package/dist/node_sampler.js +2 -4
- package/dist/node_sampler.js.map +1 -1
- package/dist/opcua_server.d.ts +57 -62
- package/dist/opcua_server.js +10 -10
- package/dist/opcua_server.js.map +1 -1
- package/dist/register_server_manager.d.ts +8 -8
- package/dist/register_server_manager.js +40 -40
- package/dist/register_server_manager.js.map +1 -1
- package/dist/register_server_manager_hidden.d.ts +1 -1
- package/dist/register_server_manager_hidden.js +2 -4
- package/dist/register_server_manager_hidden.js.map +1 -1
- package/dist/register_server_manager_mdns_only.d.ts +1 -1
- package/dist/register_server_manager_mdns_only.js.map +1 -1
- package/dist/sampling_func.d.ts +2 -2
- package/dist/server_capabilities.d.ts +3 -3
- package/dist/server_capabilities.js.map +1 -1
- package/dist/server_end_point.d.ts +47 -4
- package/dist/server_end_point.js +133 -42
- package/dist/server_end_point.js.map +1 -1
- package/dist/server_engine.js +29 -25
- package/dist/server_engine.js.map +1 -1
- package/dist/server_publish_engine.d.ts +5 -5
- package/dist/server_publish_engine.js +29 -23
- package/dist/server_publish_engine.js.map +1 -1
- package/dist/server_publish_engine_for_orphan_subscriptions.d.ts +2 -2
- package/dist/server_publish_engine_for_orphan_subscriptions.js.map +1 -1
- package/dist/server_session.d.ts +9 -10
- package/dist/server_session.js +11 -12
- package/dist/server_session.js.map +1 -1
- package/dist/server_subscription.d.ts +13 -13
- package/dist/server_subscription.js +100 -79
- package/dist/server_subscription.js.map +1 -1
- package/dist/sessions_compatible_for_transfer.d.ts +1 -1
- package/dist/sessions_compatible_for_transfer.js +1 -1
- package/dist/sessions_compatible_for_transfer.js.map +1 -1
- package/dist/user_manager.d.ts +4 -4
- package/dist/user_manager.js +1 -1
- package/dist/user_manager.js.map +1 -1
- package/dist/user_manager_ua.d.ts +2 -2
- package/dist/user_manager_ua.js +2 -2
- package/dist/user_manager_ua.js.map +1 -1
- package/dist/validate_filter.d.ts +7 -4
- package/dist/validate_filter.js +5 -6
- package/dist/validate_filter.js.map +1 -1
- package/package.json +51 -50
- package/source/addressSpace_accessor.ts +24 -24
- package/source/base_server.ts +75 -63
- package/source/extract_password_from_blob.ts +3 -11
- package/source/factory.ts +2 -4
- package/source/filter/check_where_clause_on_address_space.ts +4 -7
- package/source/filter/extract_event_fields.ts +4 -5
- package/source/helper.ts +9 -13
- package/source/i_address_space_accessor.ts +13 -4
- package/source/i_channel_data.ts +1 -1
- package/source/i_register_server_manager.ts +2 -4
- package/source/i_server_side_publish_engine.ts +16 -9
- package/source/index.ts +10 -9
- package/source/invalidate_server_certificate_cache.ts +26 -0
- package/source/monitored_item.ts +44 -42
- package/source/node_sampler.ts +9 -11
- package/source/opcua_server.ts +86 -99
- package/source/register_server_manager.ts +75 -72
- package/source/register_server_manager_hidden.ts +3 -5
- package/source/register_server_manager_mdns_only.ts +1 -3
- package/source/sampling_func.ts +2 -2
- package/source/server_capabilities.ts +9 -6
- package/source/server_end_point.ts +143 -50
- package/source/server_engine.ts +22 -22
- package/source/server_publish_engine.ts +35 -30
- package/source/server_publish_engine_for_orphan_subscriptions.ts +3 -3
- package/source/server_session.ts +36 -33
- package/source/server_subscription.ts +182 -184
- package/source/sessions_compatible_for_transfer.ts +9 -9
- package/source/user_manager.ts +7 -7
- package/source/user_manager_ua.ts +3 -5
- package/source/validate_filter.ts +9 -11
|
@@ -10,7 +10,8 @@ import chalk from "chalk";
|
|
|
10
10
|
|
|
11
11
|
import { assert } from "node-opcua-assert";
|
|
12
12
|
import type { OPCUACertificateManager } from "node-opcua-certificate-manager";
|
|
13
|
-
import { type
|
|
13
|
+
import { type ICertificateChainProvider, StaticCertificateChainProvider } from "node-opcua-common";
|
|
14
|
+
import { type Certificate, combine_der, makeSHA1Thumbprint, type PrivateKey, split_der } from "node-opcua-crypto/web";
|
|
14
15
|
import { checkDebugFlag, make_debugLog, make_errorLog, make_warningLog } from "node-opcua-debug";
|
|
15
16
|
import { getFullyQualifiedDomainName, resolveFullyQualifiedDomainName } from "node-opcua-hostname";
|
|
16
17
|
import {
|
|
@@ -98,7 +99,8 @@ function dumpChannelInfo(channels: ServerSecureChannelLayer[]): void {
|
|
|
98
99
|
console.log("------------------------------------------------------");
|
|
99
100
|
}
|
|
100
101
|
|
|
101
|
-
const
|
|
102
|
+
const emptyCertificateChain: Certificate[] = [];
|
|
103
|
+
|
|
102
104
|
// biome-ignore lint/suspicious/noExplicitAny: deliberate null→PrivateKey sentinel
|
|
103
105
|
const emptyPrivateKey = null as any as PrivateKey;
|
|
104
106
|
|
|
@@ -116,7 +118,7 @@ export interface OPCUAServerEndPointOptions {
|
|
|
116
118
|
/**
|
|
117
119
|
* the DER certificate chain
|
|
118
120
|
*/
|
|
119
|
-
certificateChain: Certificate;
|
|
121
|
+
certificateChain: Certificate[];
|
|
120
122
|
|
|
121
123
|
/**
|
|
122
124
|
* privateKey
|
|
@@ -302,9 +304,12 @@ function getUniqueName(name: string, collection: { [key: string]: number }) {
|
|
|
302
304
|
}
|
|
303
305
|
}
|
|
304
306
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
307
|
+
/**
|
|
308
|
+
* Stores the abort listener for channels in the pre-registration phase.
|
|
309
|
+
* Using a WeakMap instead of monkey-patching the channel object keeps
|
|
310
|
+
* this internal state invisible to debuggers and external code.
|
|
311
|
+
*/
|
|
312
|
+
const preregisterAbortListeners = new WeakMap<ServerSecureChannelLayer, () => void>();
|
|
308
313
|
/**
|
|
309
314
|
* OPCUAServerEndPoint a Server EndPoint.
|
|
310
315
|
* A sever end point is listening to one port
|
|
@@ -329,12 +334,23 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
|
|
|
329
334
|
public objectFactory: unknown;
|
|
330
335
|
|
|
331
336
|
public _on_new_channel?: (channel: ServerSecureChannelLayer) => void;
|
|
337
|
+
public _on_channel_secured?: (channel: ServerSecureChannelLayer) => void;
|
|
332
338
|
public _on_close_channel?: (channel: ServerSecureChannelLayer) => void;
|
|
333
339
|
public _on_connectionRefused?: (socketData: ISocketData) => void;
|
|
334
340
|
public _on_openSecureChannelFailure?: (socketData: ISocketData, channelData: IChannelData) => void;
|
|
335
341
|
|
|
336
|
-
|
|
337
|
-
|
|
342
|
+
/**
|
|
343
|
+
* Certificate chain provider — delegates all cert/key access.
|
|
344
|
+
* Stored in a `#` private field so secrets are not visible
|
|
345
|
+
* in the debugger's property list or JSON serialization.
|
|
346
|
+
*/
|
|
347
|
+
#certProvider: ICertificateChainProvider;
|
|
348
|
+
/**
|
|
349
|
+
* Combined DER cache — invalidated whenever the cert provider
|
|
350
|
+
* changes so that `EndpointDescription.serverCertificate`
|
|
351
|
+
* getters don't call `combine_der()` on every access.
|
|
352
|
+
*/
|
|
353
|
+
#combinedDerCache: Certificate | undefined;
|
|
338
354
|
private _channels: { [key: string]: ServerSecureChannelLayer };
|
|
339
355
|
private _server?: Server;
|
|
340
356
|
private _endpoints: EndpointDescription[];
|
|
@@ -347,9 +363,9 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
|
|
|
347
363
|
constructor(options: OPCUAServerEndPointOptions) {
|
|
348
364
|
super();
|
|
349
365
|
|
|
350
|
-
assert(!Object.
|
|
351
|
-
assert(Object.
|
|
352
|
-
assert(Object.
|
|
366
|
+
assert(!Object.hasOwn(options, "certificate"), "expecting a certificateChain instead");
|
|
367
|
+
assert(Object.hasOwn(options, "certificateChain"), "expecting a certificateChain");
|
|
368
|
+
assert(Object.hasOwn(options, "privateKey"));
|
|
353
369
|
|
|
354
370
|
this.certificateManager = options.certificateManager;
|
|
355
371
|
|
|
@@ -359,8 +375,7 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
|
|
|
359
375
|
this.host = options.host;
|
|
360
376
|
assert(typeof this.port === "number");
|
|
361
377
|
|
|
362
|
-
this
|
|
363
|
-
this._privateKey = options.privateKey;
|
|
378
|
+
this.#certProvider = new StaticCertificateChainProvider(options.certificateChain, options.privateKey);
|
|
364
379
|
|
|
365
380
|
this._channels = {};
|
|
366
381
|
|
|
@@ -390,8 +405,8 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
|
|
|
390
405
|
}
|
|
391
406
|
|
|
392
407
|
public dispose(): void {
|
|
393
|
-
this
|
|
394
|
-
this
|
|
408
|
+
this.#certProvider = new StaticCertificateChainProvider(emptyCertificateChain, emptyPrivateKey);
|
|
409
|
+
this.#combinedDerCache = undefined;
|
|
395
410
|
|
|
396
411
|
assert(Object.keys(this._channels).length === 0, "OPCUAServerEndPoint channels must have been deleted");
|
|
397
412
|
|
|
@@ -417,7 +432,7 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
|
|
|
417
432
|
" l = " +
|
|
418
433
|
this._endpoints.length +
|
|
419
434
|
" " +
|
|
420
|
-
makeSHA1Thumbprint(this.
|
|
435
|
+
makeSHA1Thumbprint(this.getCertificate()).toString("hex");
|
|
421
436
|
return txt;
|
|
422
437
|
}
|
|
423
438
|
|
|
@@ -429,21 +444,85 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
|
|
|
429
444
|
* Returns the X509 DER form of the server certificate
|
|
430
445
|
*/
|
|
431
446
|
public getCertificate(): Certificate {
|
|
432
|
-
return
|
|
447
|
+
return this.getCertificateChain()[0];
|
|
433
448
|
}
|
|
434
449
|
|
|
435
450
|
/**
|
|
436
451
|
* Returns the X509 DER form of the server certificate
|
|
437
452
|
*/
|
|
438
|
-
public getCertificateChain(): Certificate {
|
|
439
|
-
return this.
|
|
453
|
+
public getCertificateChain(): Certificate[] {
|
|
454
|
+
return this.#certProvider.getCertificateChain();
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Replace the certificate chain provider for this endpoint.
|
|
459
|
+
*
|
|
460
|
+
* Used by push certificate management to switch from the
|
|
461
|
+
* default static provider to a disk-based one that re-reads
|
|
462
|
+
* certificates on demand.
|
|
463
|
+
*
|
|
464
|
+
* Invalidates the cached `combine_der` result so that
|
|
465
|
+
* `EndpointDescription.serverCertificate` getters pick up
|
|
466
|
+
* the new chain immediately.
|
|
467
|
+
*/
|
|
468
|
+
public setCertificateProvider(provider: ICertificateChainProvider): void {
|
|
469
|
+
this.#certProvider = provider;
|
|
470
|
+
this.#combinedDerCache = undefined;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Return the current certificate chain provider.
|
|
475
|
+
* Useful for calling `invalidate()` after certificate rotation.
|
|
476
|
+
*/
|
|
477
|
+
public getCertificateProvider(): ICertificateChainProvider {
|
|
478
|
+
return this.#certProvider;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Invalidate the combined DER cache.
|
|
483
|
+
*
|
|
484
|
+
* Called after the underlying provider's chain changes
|
|
485
|
+
* (e.g. after `provider.invalidate()` or `provider.update()`).
|
|
486
|
+
* The next `EndpointDescription.serverCertificate` access
|
|
487
|
+
* will recompute the combined DER from the provider.
|
|
488
|
+
*/
|
|
489
|
+
public invalidateCombinedDerCache(): void {
|
|
490
|
+
this.#combinedDerCache = undefined;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Convenience method: invalidate both the provider's cache
|
|
495
|
+
* (so it re-reads from disk) and the combined DER cache
|
|
496
|
+
* (so endpoint descriptions recompute `serverCertificate`).
|
|
497
|
+
*
|
|
498
|
+
* Prefer this over calling `getCertificateProvider().invalidate()`
|
|
499
|
+
* and `invalidateCombinedDerCache()` separately.
|
|
500
|
+
*/
|
|
501
|
+
public invalidateCertificates(): void {
|
|
502
|
+
this.#certProvider.invalidate();
|
|
503
|
+
this.#combinedDerCache = undefined;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* Get the combined DER certificate (all certs concatenated)
|
|
508
|
+
* for use in EndpointDescription.serverCertificate.
|
|
509
|
+
* Cached internally; invalidated by provider changes.
|
|
510
|
+
* @internal
|
|
511
|
+
*/
|
|
512
|
+
public getCombinedCertificate(): Certificate | undefined {
|
|
513
|
+
const chain = this.#certProvider.getCertificateChain();
|
|
514
|
+
if (chain.length === 0) return undefined;
|
|
515
|
+
if (!this.#combinedDerCache) {
|
|
516
|
+
this.#combinedDerCache = combine_der(chain);
|
|
517
|
+
}
|
|
518
|
+
return this.#combinedDerCache;
|
|
440
519
|
}
|
|
441
520
|
|
|
442
521
|
/**
|
|
443
522
|
* the private key
|
|
444
523
|
*/
|
|
445
524
|
public getPrivateKey(): PrivateKey {
|
|
446
|
-
return this.
|
|
525
|
+
return this.#certProvider.getPrivateKey();
|
|
447
526
|
}
|
|
448
527
|
|
|
449
528
|
/**
|
|
@@ -512,7 +591,6 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
|
|
|
512
591
|
collection: this._policy_deduplicator,
|
|
513
592
|
hostname,
|
|
514
593
|
server: this.serverInfo,
|
|
515
|
-
serverCertificateChain: this.getCertificateChain(),
|
|
516
594
|
|
|
517
595
|
securityMode,
|
|
518
596
|
securityPolicy,
|
|
@@ -964,16 +1042,16 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
|
|
|
964
1042
|
// as they will need to be interrupted when OPCUAServerEndPoint is closed
|
|
965
1043
|
assert(this._started, "OPCUAServerEndPoint must be started");
|
|
966
1044
|
|
|
967
|
-
assert(!Object.
|
|
1045
|
+
assert(!Object.hasOwn(this._channels, channel.hashKey), " channel already preregistered!");
|
|
968
1046
|
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
channelPriv._unpreregisterChannelEvent = () => {
|
|
1047
|
+
this._channels[channel.hashKey] = channel;
|
|
1048
|
+
const onAbort = () => {
|
|
972
1049
|
debugLog("Channel received an abort event during the preregistration phase");
|
|
973
1050
|
this._un_pre_registerChannel(channel);
|
|
974
1051
|
channel.dispose();
|
|
975
1052
|
};
|
|
976
|
-
|
|
1053
|
+
preregisterAbortListeners.set(channel, onAbort);
|
|
1054
|
+
channel.on("abort", onAbort);
|
|
977
1055
|
}
|
|
978
1056
|
|
|
979
1057
|
private _un_pre_registerChannel(channel: ServerSecureChannelLayer) {
|
|
@@ -982,10 +1060,10 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
|
|
|
982
1060
|
return;
|
|
983
1061
|
}
|
|
984
1062
|
delete this._channels[channel.hashKey];
|
|
985
|
-
const
|
|
986
|
-
if (
|
|
987
|
-
channel.removeListener("abort",
|
|
988
|
-
|
|
1063
|
+
const onAbort = preregisterAbortListeners.get(channel);
|
|
1064
|
+
if (onAbort) {
|
|
1065
|
+
channel.removeListener("abort", onAbort);
|
|
1066
|
+
preregisterAbortListeners.delete(channel);
|
|
989
1067
|
}
|
|
990
1068
|
}
|
|
991
1069
|
|
|
@@ -1000,11 +1078,22 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
|
|
|
1000
1078
|
this._channels[channel.hashKey] = channel;
|
|
1001
1079
|
|
|
1002
1080
|
/**
|
|
1003
|
-
* @event newChannel
|
|
1081
|
+
* @event newChannel — fired after transport init (HEL/ACK).
|
|
1082
|
+
* Note: securityPolicy/securityMode are NOT yet established.
|
|
1004
1083
|
* @param channel
|
|
1005
1084
|
*/
|
|
1006
1085
|
this.emit("newChannel", channel);
|
|
1007
1086
|
|
|
1087
|
+
channel.once("open", () => {
|
|
1088
|
+
/**
|
|
1089
|
+
* @event channelSecured — fired after OpenSecureChannel
|
|
1090
|
+
* handshake succeeds. securityPolicy, securityMode, and
|
|
1091
|
+
* clientCertificate are available at this point.
|
|
1092
|
+
* @param channel
|
|
1093
|
+
*/
|
|
1094
|
+
this.emit("channelSecured", channel);
|
|
1095
|
+
});
|
|
1096
|
+
|
|
1008
1097
|
channel.on("abort", () => {
|
|
1009
1098
|
this._unregisterChannel(channel);
|
|
1010
1099
|
});
|
|
@@ -1021,11 +1110,11 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
|
|
|
1021
1110
|
*/
|
|
1022
1111
|
private _unregisterChannel(channel: ServerSecureChannelLayer): void {
|
|
1023
1112
|
debugLog("_un-registerChannel channel.hashKey", channel.hashKey);
|
|
1024
|
-
if (!Object.
|
|
1113
|
+
if (!Object.hasOwn(this._channels, channel.hashKey)) {
|
|
1025
1114
|
return;
|
|
1026
1115
|
}
|
|
1027
1116
|
|
|
1028
|
-
assert(Object.
|
|
1117
|
+
assert(Object.hasOwn(this._channels, channel.hashKey), "channel is not registered");
|
|
1029
1118
|
|
|
1030
1119
|
/**
|
|
1031
1120
|
* @event closeChannel
|
|
@@ -1130,7 +1219,6 @@ interface MakeEndpointDescriptionOptions {
|
|
|
1130
1219
|
*/
|
|
1131
1220
|
hostname: string;
|
|
1132
1221
|
|
|
1133
|
-
serverCertificateChain: Certificate;
|
|
1134
1222
|
/**
|
|
1135
1223
|
*
|
|
1136
1224
|
*/
|
|
@@ -1224,14 +1312,6 @@ function estimateSecurityLevel(securityMode: MessageSecurityMode, securityPolicy
|
|
|
1224
1312
|
* @private
|
|
1225
1313
|
*/
|
|
1226
1314
|
function _makeEndpointDescription(options: MakeEndpointDescriptionOptions, parent: OPCUAServerEndPoint): EndpointDescriptionEx {
|
|
1227
|
-
assert(Object.prototype.hasOwnProperty.call(options, "serverCertificateChain"));
|
|
1228
|
-
assert(!Object.prototype.hasOwnProperty.call(options, "serverCertificate"));
|
|
1229
|
-
assert(!!options.securityMode); // s.MessageSecurityMode
|
|
1230
|
-
assert(!!options.securityPolicy);
|
|
1231
|
-
assert(options.server !== null && typeof options.server === "object");
|
|
1232
|
-
assert(!!options.hostname && typeof options.hostname === "string");
|
|
1233
|
-
assert(typeof options.restricted === "boolean");
|
|
1234
|
-
|
|
1235
1315
|
const u = (n: string) => getUniqueName(n, options.collection);
|
|
1236
1316
|
options.securityLevel =
|
|
1237
1317
|
options.securityLevel === undefined
|
|
@@ -1353,7 +1433,8 @@ function _makeEndpointDescription(options: MakeEndpointDescriptionOptions, paren
|
|
|
1353
1433
|
endpointUrl: "<to be evaluated at run time>", // options.endpointUrl,
|
|
1354
1434
|
|
|
1355
1435
|
server: undefined, // options.server,
|
|
1356
|
-
serverCertificate
|
|
1436
|
+
// serverCertificate is set as a dynamic getter below
|
|
1437
|
+
serverCertificate: undefined,
|
|
1357
1438
|
|
|
1358
1439
|
securityMode: options.securityMode,
|
|
1359
1440
|
securityPolicyUri,
|
|
@@ -1364,15 +1445,27 @@ function _makeEndpointDescription(options: MakeEndpointDescriptionOptions, paren
|
|
|
1364
1445
|
}) as EndpointDescriptionEx;
|
|
1365
1446
|
endpoint._parent = parent;
|
|
1366
1447
|
|
|
1448
|
+
// serverCertificate — always dynamic.
|
|
1449
|
+
// Delegates to the parent endpoint's combined DER cache
|
|
1450
|
+
// which in turn reads from the current ICertificateChainProvider.
|
|
1451
|
+
// This ensures GetEndpoints always returns the current chain,
|
|
1452
|
+
// whether the provider is static or disk-based.
|
|
1453
|
+
Object.defineProperty(endpoint, "serverCertificate", {
|
|
1454
|
+
get: () => parent.getCombinedCertificate(),
|
|
1455
|
+
configurable: true
|
|
1456
|
+
});
|
|
1457
|
+
|
|
1367
1458
|
// endpointUrl is dynamic as port number may be adjusted
|
|
1368
1459
|
// when the tcp socket start listening
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1460
|
+
Object.defineProperty(endpoint, "endpointUrl", {
|
|
1461
|
+
get: () => {
|
|
1462
|
+
const port = options.advertisedPort ?? endpoint._parent.port;
|
|
1463
|
+
const resourcePath = options.resourcePath || "";
|
|
1464
|
+
const hostname = options.hostname;
|
|
1465
|
+
const endpointUrl = `opc.tcp://${hostname}:${port}${resourcePath}`;
|
|
1466
|
+
return resolveFullyQualifiedDomainName(endpointUrl);
|
|
1467
|
+
},
|
|
1468
|
+
configurable: true
|
|
1376
1469
|
});
|
|
1377
1470
|
|
|
1378
1471
|
endpoint.server = options.server;
|
package/source/server_engine.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { EventEmitter } from "node:events";
|
|
5
5
|
import { types } from "node:util";
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
|
|
8
8
|
import chalk from "chalk";
|
|
9
9
|
import {
|
|
10
10
|
AddressSpace,
|
|
@@ -184,7 +184,7 @@ function requestServerStateChange(
|
|
|
184
184
|
) {
|
|
185
185
|
assert(Array.isArray(inputArguments));
|
|
186
186
|
assert(typeof callback === "function");
|
|
187
|
-
assert(Object.
|
|
187
|
+
assert(Object.hasOwn(context, "session"), " expecting a session id in the context object");
|
|
188
188
|
const session = context.session as ServerSession;
|
|
189
189
|
if (!session) {
|
|
190
190
|
return callback(null, { statusCode: StatusCodes.BadInternalError });
|
|
@@ -199,7 +199,7 @@ function _getSubscription(
|
|
|
199
199
|
context: ISessionContext
|
|
200
200
|
): { subscription: Subscription; statusCode?: never } | { statusCode: StatusCode; subscription?: never } {
|
|
201
201
|
assert(Array.isArray(inputArguments));
|
|
202
|
-
assert(Object.
|
|
202
|
+
assert(Object.hasOwn(context, "session"), " expecting a session id in the context object");
|
|
203
203
|
const session = context.session as ServerSession;
|
|
204
204
|
if (!session) {
|
|
205
205
|
return { statusCode: StatusCodes.BadInternalError };
|
|
@@ -466,11 +466,11 @@ export class ServerEngine extends EventEmitter implements IAddressSpaceAccessor
|
|
|
466
466
|
|
|
467
467
|
// --------------------------------------------------- serverDiagnosticsSummary extension Object
|
|
468
468
|
this.serverDiagnosticsSummary = new ServerDiagnosticsSummaryDataType();
|
|
469
|
-
assert(Object.
|
|
469
|
+
assert(Object.hasOwn(this.serverDiagnosticsSummary, "currentSessionCount"));
|
|
470
470
|
|
|
471
471
|
// note spelling is different for serverDiagnosticsSummary.currentSubscriptionCount
|
|
472
472
|
// and sessionDiagnostics.currentSubscriptionsCount ( with an s)
|
|
473
|
-
assert(Object.
|
|
473
|
+
assert(Object.hasOwn(this.serverDiagnosticsSummary, "currentSubscriptionCount"));
|
|
474
474
|
|
|
475
475
|
Object.defineProperty(this.serverDiagnosticsSummary, "currentSubscriptionCount", {
|
|
476
476
|
get: () => {
|
|
@@ -505,7 +505,7 @@ export class ServerEngine extends EventEmitter implements IAddressSpaceAccessor
|
|
|
505
505
|
this._applicationUri = options.applicationUri || "<unset _applicationUri>";
|
|
506
506
|
}
|
|
507
507
|
|
|
508
|
-
options.serverDiagnosticsEnabled = Object.
|
|
508
|
+
options.serverDiagnosticsEnabled = Object.hasOwn(options, "serverDiagnosticsEnable")
|
|
509
509
|
? options.serverDiagnosticsEnabled
|
|
510
510
|
: true;
|
|
511
511
|
|
|
@@ -1899,23 +1899,23 @@ export class ServerEngine extends EventEmitter implements IAddressSpaceAccessor
|
|
|
1899
1899
|
return;
|
|
1900
1900
|
}
|
|
1901
1901
|
// perform all asyncRefresh in parallel
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
(uaVariable: UAVariable, inner_callback: CallbackT<DataValue>) => {
|
|
1902
|
+
const promises = uaVariableArray.map((uaVariable) => {
|
|
1903
|
+
return new Promise<DataValue>((resolve, reject) => {
|
|
1905
1904
|
try {
|
|
1906
1905
|
uaVariable.asyncRefresh(referenceTime, (err, dataValue) => {
|
|
1907
|
-
|
|
1906
|
+
if (err) return reject(err);
|
|
1907
|
+
resolve(dataValue!);
|
|
1908
1908
|
});
|
|
1909
1909
|
} catch (err) {
|
|
1910
1910
|
const _err = err as Error;
|
|
1911
1911
|
errorLog("asyncRefresh internal error", _err.message);
|
|
1912
|
-
|
|
1912
|
+
reject(_err);
|
|
1913
1913
|
}
|
|
1914
|
-
}
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1914
|
+
});
|
|
1915
|
+
});
|
|
1916
|
+
Promise.all(promises)
|
|
1917
|
+
.then((dataValues) => callback(null, dataValues))
|
|
1918
|
+
.catch((err) => callback(err));
|
|
1919
1919
|
}
|
|
1920
1920
|
|
|
1921
1921
|
private _exposeSubscriptionDiagnostics(subscription: Subscription): void {
|
|
@@ -1954,12 +1954,12 @@ export class ServerEngine extends EventEmitter implements IAddressSpaceAccessor
|
|
|
1954
1954
|
* @return {Subscription}
|
|
1955
1955
|
*/
|
|
1956
1956
|
public _createSubscriptionOnSession(session: ServerSession, request: CreateSubscriptionRequestLike): Subscription {
|
|
1957
|
-
assert(Object.
|
|
1958
|
-
assert(Object.
|
|
1959
|
-
assert(Object.
|
|
1960
|
-
assert(Object.
|
|
1961
|
-
assert(Object.
|
|
1962
|
-
assert(Object.
|
|
1957
|
+
assert(Object.hasOwn(request, "requestedPublishingInterval")); // Duration
|
|
1958
|
+
assert(Object.hasOwn(request, "requestedLifetimeCount")); // Counter
|
|
1959
|
+
assert(Object.hasOwn(request, "requestedMaxKeepAliveCount")); // Counter
|
|
1960
|
+
assert(Object.hasOwn(request, "maxNotificationsPerPublish")); // Counter
|
|
1961
|
+
assert(Object.hasOwn(request, "publishingEnabled")); // Boolean
|
|
1962
|
+
assert(Object.hasOwn(request, "priority")); // Byte
|
|
1963
1963
|
|
|
1964
1964
|
// adjust publishing parameters
|
|
1965
1965
|
const publishingInterval = request.requestedPublishingInterval || 0;
|
|
@@ -2,28 +2,29 @@
|
|
|
2
2
|
* @module node-opcua-server
|
|
3
3
|
*/
|
|
4
4
|
// tslint:disable:no-console
|
|
5
|
-
|
|
5
|
+
|
|
6
|
+
import { EventEmitter } from "node:events";
|
|
6
7
|
import chalk from "chalk";
|
|
7
|
-
import
|
|
8
|
+
import partition from "lodash.partition";
|
|
9
|
+
import sortBy from "lodash.sortby";
|
|
8
10
|
|
|
9
11
|
import { assert } from "node-opcua-assert";
|
|
10
12
|
import { checkDebugFlag, make_debugLog } from "node-opcua-debug";
|
|
11
13
|
import { ObjectRegistry } from "node-opcua-object-registry";
|
|
12
|
-
import { StatusCode, StatusCodes } from "node-opcua-status-code";
|
|
14
|
+
import { type StatusCode, StatusCodes } from "node-opcua-status-code";
|
|
13
15
|
|
|
14
|
-
import { PublishRequest, PublishResponse, ServiceFault, SubscriptionAcknowledgement } from "node-opcua-types";
|
|
15
|
-
import {
|
|
16
|
-
import { SubscriptionState } from "./server_subscription";
|
|
17
|
-
import { IServerSidePublishEngine, INotifMsg, IClosedOrTransferredSubscription } from "./i_server_side_publish_engine";
|
|
16
|
+
import { PublishRequest, PublishResponse, ServiceFault, type SubscriptionAcknowledgement } from "node-opcua-types";
|
|
17
|
+
import type { IClosedOrTransferredSubscription, IServerSidePublishEngine } from "./i_server_side_publish_engine";
|
|
18
|
+
import { Subscription, SubscriptionState } from "./server_subscription";
|
|
18
19
|
|
|
19
20
|
const debugLog = make_debugLog(__filename);
|
|
20
21
|
const doDebug = checkDebugFlag(__filename);
|
|
21
22
|
|
|
22
|
-
function traceLog(...args: [
|
|
23
|
+
function traceLog(...args: [unknown?, ...unknown[]]) {
|
|
23
24
|
if (!doDebug) {
|
|
24
25
|
return;
|
|
25
26
|
}
|
|
26
|
-
const a: string[] = args.map((x?:
|
|
27
|
+
const a: string[] = args.map((x?: unknown) => String(x));
|
|
27
28
|
a.unshift(chalk.yellow(" TRACE "));
|
|
28
29
|
debugLog(...a);
|
|
29
30
|
}
|
|
@@ -56,7 +57,7 @@ function addDate(date: Date, delta: number) {
|
|
|
56
57
|
|
|
57
58
|
function timeout_filter(publishData: PublishData): boolean {
|
|
58
59
|
const request = publishData.request;
|
|
59
|
-
const
|
|
60
|
+
const _results = publishData.results;
|
|
60
61
|
if (!request.requestHeader.timeoutHint) {
|
|
61
62
|
// no limits
|
|
62
63
|
return false;
|
|
@@ -87,7 +88,7 @@ export class ServerSidePublishEngine extends EventEmitter implements IServerSide
|
|
|
87
88
|
);
|
|
88
89
|
|
|
89
90
|
for (const subscription of Object.values(srcPublishEngine._subscriptions)) {
|
|
90
|
-
assert(
|
|
91
|
+
assert(subscription.publishEngine === srcPublishEngine);
|
|
91
92
|
|
|
92
93
|
if (subscription.$session) {
|
|
93
94
|
subscription.$session._unexposeSubscriptionDiagnostics(subscription);
|
|
@@ -117,7 +118,7 @@ export class ServerSidePublishEngine extends EventEmitter implements IServerSide
|
|
|
117
118
|
destPublishEngine: ServerSidePublishEngine,
|
|
118
119
|
sendInitialValues: boolean
|
|
119
120
|
): Promise<Subscription> {
|
|
120
|
-
const srcPublishEngine = subscription.publishEngine as
|
|
121
|
+
const srcPublishEngine = subscription.publishEngine as ServerSidePublishEngine;
|
|
121
122
|
|
|
122
123
|
assert(!destPublishEngine.getSubscriptionById(subscription.id));
|
|
123
124
|
assert(srcPublishEngine.getSubscriptionById(subscription.id));
|
|
@@ -161,8 +162,6 @@ export class ServerSidePublishEngine extends EventEmitter implements IServerSide
|
|
|
161
162
|
assert(destPublishEngine.getSubscriptionById(subscription.id));
|
|
162
163
|
assert(!srcPublishEngine.getSubscriptionById(subscription.id));
|
|
163
164
|
|
|
164
|
-
|
|
165
|
-
|
|
166
165
|
return subscription;
|
|
167
166
|
}
|
|
168
167
|
|
|
@@ -252,9 +251,9 @@ export class ServerSidePublishEngine extends EventEmitter implements IServerSide
|
|
|
252
251
|
*/
|
|
253
252
|
public add_subscription(subscription: Subscription): Subscription {
|
|
254
253
|
assert(subscription instanceof Subscription);
|
|
255
|
-
assert(isFinite(subscription.id));
|
|
256
|
-
subscription.publishEngine =
|
|
257
|
-
assert(
|
|
254
|
+
assert(Number.isFinite(subscription.id));
|
|
255
|
+
subscription.publishEngine = subscription.publishEngine || this;
|
|
256
|
+
assert(subscription.publishEngine === this);
|
|
258
257
|
assert(!this._subscriptions[subscription.id]);
|
|
259
258
|
|
|
260
259
|
debugLog("ServerSidePublishEngine#add_subscription - adding subscription with Id:", subscription.id);
|
|
@@ -265,12 +264,12 @@ export class ServerSidePublishEngine extends EventEmitter implements IServerSide
|
|
|
265
264
|
|
|
266
265
|
public detach_subscription(subscription: Subscription): Subscription {
|
|
267
266
|
assert(subscription instanceof Subscription);
|
|
268
|
-
assert(isFinite(subscription.id));
|
|
269
|
-
assert(
|
|
267
|
+
assert(Number.isFinite(subscription.id));
|
|
268
|
+
assert(subscription.publishEngine === this);
|
|
270
269
|
assert(this._subscriptions[subscription.id] === subscription);
|
|
271
270
|
|
|
272
271
|
delete this._subscriptions[subscription.id];
|
|
273
|
-
subscription.publishEngine = null as
|
|
272
|
+
subscription.publishEngine = null as unknown as ServerSidePublishEngine;
|
|
274
273
|
debugLog("ServerSidePublishEngine#detach_subscription detaching subscription with Id:", subscription.id);
|
|
275
274
|
return subscription;
|
|
276
275
|
}
|
|
@@ -318,7 +317,7 @@ export class ServerSidePublishEngine extends EventEmitter implements IServerSide
|
|
|
318
317
|
const result = subscriptions.reduce((sum: number, subscription: Subscription) => {
|
|
319
318
|
return sum + subscription.monitoredItemCount;
|
|
320
319
|
}, 0);
|
|
321
|
-
assert(isFinite(result));
|
|
320
|
+
assert(Number.isFinite(result));
|
|
322
321
|
return result;
|
|
323
322
|
}
|
|
324
323
|
|
|
@@ -405,7 +404,7 @@ export class ServerSidePublishEngine extends EventEmitter implements IServerSide
|
|
|
405
404
|
*/
|
|
406
405
|
public _on_PublishRequest(
|
|
407
406
|
request: PublishRequest,
|
|
408
|
-
callback?: (request1: PublishRequest, response: PublishResponse| ServiceFault) => void
|
|
407
|
+
callback?: (request1: PublishRequest, response: PublishResponse | ServiceFault) => void
|
|
409
408
|
): void {
|
|
410
409
|
callback = callback || dummy_function;
|
|
411
410
|
assert(typeof callback === "function");
|
|
@@ -430,11 +429,11 @@ export class ServerSidePublishEngine extends EventEmitter implements IServerSide
|
|
|
430
429
|
this._send_error_for_request(publishData, StatusCodes.BadSessionClosed);
|
|
431
430
|
} else if (this.subscriptionCount === 0) {
|
|
432
431
|
if (this._closed_subscriptions.length > 0 && this._closed_subscriptions[0].hasPendingNotifications) {
|
|
433
|
-
const
|
|
432
|
+
const _verif = this._publish_request_queue.length;
|
|
434
433
|
// add the publish request to the queue for later processing
|
|
435
434
|
this._publish_request_queue.push(publishData);
|
|
436
435
|
|
|
437
|
-
const
|
|
436
|
+
const _processed = this.#_feed_closed_subscription();
|
|
438
437
|
//xx ( may be subscription has expired by themselves) assert(verif === this._publish_request_queue.length);
|
|
439
438
|
//xx ( may be subscription has expired by themselves) assert(processed);
|
|
440
439
|
return;
|
|
@@ -529,11 +528,11 @@ export class ServerSidePublishEngine extends EventEmitter implements IServerSide
|
|
|
529
528
|
return false;
|
|
530
529
|
}
|
|
531
530
|
// process closed subscription
|
|
532
|
-
const closed_subscription = this._closed_subscriptions[0]
|
|
531
|
+
const closed_subscription = this._closed_subscriptions[0];
|
|
533
532
|
assert(closed_subscription.hasPendingNotifications);
|
|
534
533
|
debugLog("ServerSidePublishEngine#_feed_closed_subscription for closed_subscription ", closed_subscription.id);
|
|
535
|
-
closed_subscription
|
|
536
|
-
if (!closed_subscription
|
|
534
|
+
closed_subscription._publish_pending_notifications();
|
|
535
|
+
if (!closed_subscription.hasPendingNotifications) {
|
|
537
536
|
closed_subscription.dispose();
|
|
538
537
|
this._closed_subscriptions.shift();
|
|
539
538
|
}
|
|
@@ -579,7 +578,10 @@ export class ServerSidePublishEngine extends EventEmitter implements IServerSide
|
|
|
579
578
|
// request and return a response with the result set to Bad_TooManyPublishRequests.
|
|
580
579
|
|
|
581
580
|
// dequeue oldest request
|
|
582
|
-
const publishData = this._publish_request_queue.shift()
|
|
581
|
+
const publishData = this._publish_request_queue.shift();
|
|
582
|
+
if (!publishData) {
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
583
585
|
this._send_error_for_request(publishData, StatusCodes.BadTooManyPublishRequests);
|
|
584
586
|
}
|
|
585
587
|
}
|
|
@@ -629,10 +631,13 @@ export class ServerSidePublishEngine extends EventEmitter implements IServerSide
|
|
|
629
631
|
);
|
|
630
632
|
return true;
|
|
631
633
|
}
|
|
632
|
-
public _send_response(
|
|
634
|
+
public _send_response(_subscription: Subscription, response: PublishResponse): void {
|
|
633
635
|
assert(this.pendingPublishRequestCount > 0);
|
|
634
636
|
assert(response.subscriptionId !== 0xffffff);
|
|
635
|
-
const publishData = this._publish_request_queue.shift()
|
|
637
|
+
const publishData = this._publish_request_queue.shift();
|
|
638
|
+
if (!publishData) {
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
636
641
|
this._send_valid_response_for_request(publishData, response);
|
|
637
642
|
}
|
|
638
643
|
|
|
@@ -7,8 +7,8 @@ import chalk from "chalk";
|
|
|
7
7
|
import { checkDebugFlag, make_debugLog } from "node-opcua-debug";
|
|
8
8
|
import { NodeId } from "node-opcua-nodeid";
|
|
9
9
|
|
|
10
|
-
import { ServerSidePublishEngine, ServerSidePublishEngineOptions } from "./server_publish_engine";
|
|
11
|
-
import { Subscription } from "./server_subscription";
|
|
10
|
+
import { ServerSidePublishEngine, type ServerSidePublishEngineOptions } from "./server_publish_engine";
|
|
11
|
+
import type { Subscription } from "./server_subscription";
|
|
12
12
|
|
|
13
13
|
const debugLog = make_debugLog(__filename);
|
|
14
14
|
const doDebug = checkDebugFlag(__filename);
|
|
@@ -32,7 +32,7 @@ export class ServerSidePublishEngineForOrphanSubscription extends ServerSidePubl
|
|
|
32
32
|
|
|
33
33
|
// detach subscription from old session
|
|
34
34
|
subscription.$session = undefined;
|
|
35
|
-
|
|
35
|
+
|
|
36
36
|
super.add_subscription(subscription);
|
|
37
37
|
// also add an event handler to detected when the subscription has ended
|
|
38
38
|
// so we can automatically remove it from the orphan table
|