node-opcua-server 2.119.2 → 2.121.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/dist/addressSpace_accessor.js +1 -3
- package/dist/addressSpace_accessor.js.map +1 -1
- package/dist/base_server.js.map +1 -1
- package/dist/factory.js.map +1 -1
- package/dist/helper.js.map +1 -1
- package/dist/history_server_capabilities.js.map +1 -1
- package/dist/monitored_item.d.ts +6 -6
- package/dist/monitored_item.js +55 -38
- package/dist/monitored_item.js.map +1 -1
- package/dist/node_sampler.js.map +1 -1
- package/dist/opcua_server.d.ts +10 -4
- package/dist/opcua_server.js +18 -20
- package/dist/opcua_server.js.map +1 -1
- package/dist/queue.js.map +1 -1
- package/dist/register_server_manager.js +1 -1
- package/dist/register_server_manager.js.map +1 -1
- package/dist/register_server_manager_mdns_only.js +1 -1
- package/dist/register_server_manager_mdns_only.js.map +1 -1
- package/dist/server_capabilities.js +2 -2
- package/dist/server_capabilities.js.map +1 -1
- package/dist/server_end_point.d.ts +6 -0
- package/dist/server_end_point.js +16 -10
- package/dist/server_end_point.js.map +1 -1
- package/dist/server_engine.d.ts +3 -3
- package/dist/server_engine.js +3 -3
- package/dist/server_engine.js.map +1 -1
- package/dist/server_publish_engine.d.ts +1 -4
- package/dist/server_publish_engine.js +95 -91
- package/dist/server_publish_engine.js.map +1 -1
- package/dist/server_publish_engine_for_orphan_subscriptions.js +1 -1
- package/dist/server_publish_engine_for_orphan_subscriptions.js.map +1 -1
- package/dist/server_session.js +3 -1
- package/dist/server_session.js.map +1 -1
- package/dist/server_subscription.d.ts +22 -14
- package/dist/server_subscription.js +46 -21
- package/dist/server_subscription.js.map +1 -1
- package/dist/sessions_compatible_for_transfer.js.map +1 -1
- package/dist/user_manager.js.map +1 -1
- package/dist/user_manager_ua.js.map +1 -1
- package/dist/validate_filter.js.map +1 -1
- package/package.json +48 -47
- package/source/addressSpace_accessor.ts +2 -3
- package/source/monitored_item.ts +68 -56
- package/source/opcua_server.ts +39 -24
- package/source/server_end_point.ts +27 -8
- package/source/server_engine.ts +6 -5
- package/source/server_publish_engine.ts +14 -12
- package/source/server_publish_engine_for_orphan_subscriptions.ts +1 -1
- package/source/server_session.ts +2 -2
- package/source/server_subscription.ts +84 -34
package/source/monitored_item.ts
CHANGED
|
@@ -255,22 +255,21 @@ function apply_dataChange_filter(this: MonitoredItem, newDataValue: DataValue, o
|
|
|
255
255
|
|
|
256
256
|
const s = (a: any) => JSON.stringify(a, null, " ");
|
|
257
257
|
|
|
258
|
-
function
|
|
259
|
-
|
|
258
|
+
function safeGuardRegister(monitoredItem: MonitoredItem) {
|
|
260
259
|
(monitoredItem.oldDataValue as any)._$monitoredItem = monitoredItem.node?.nodeId?.toString();
|
|
261
260
|
(monitoredItem as any)._$safeGuard = s((monitoredItem as any).oldDataValue);
|
|
262
261
|
}
|
|
263
262
|
function safeGuardVerify(monitoredItem: MonitoredItem) {
|
|
264
263
|
if ((monitoredItem as any)._$safeGuard) {
|
|
265
|
-
const verif = s(monitoredItem.oldDataValue|| "");
|
|
264
|
+
const verif = s(monitoredItem.oldDataValue || "");
|
|
266
265
|
if (verif !== (monitoredItem as any)._$safeGuard) {
|
|
267
266
|
errorLog(verif, (monitoredItem as any)._$safeGuard);
|
|
268
|
-
throw new Error("Internal error: DataValue has been
|
|
267
|
+
throw new Error("Internal error: DataValue has been altered !!!");
|
|
269
268
|
}
|
|
270
269
|
}
|
|
271
270
|
}
|
|
272
271
|
function apply_filter(this: MonitoredItem, newDataValue: DataValue) {
|
|
273
|
-
if (
|
|
272
|
+
if (this.oldDataValue === badDataUnavailable) {
|
|
274
273
|
return true; // keep
|
|
275
274
|
}
|
|
276
275
|
|
|
@@ -365,6 +364,7 @@ function isSourceNewerThan(a: DataValue, b?: DataValue): boolean {
|
|
|
365
364
|
return at > bt;
|
|
366
365
|
}
|
|
367
366
|
|
|
367
|
+
const badDataUnavailable = new DataValue({ statusCode: StatusCodes.BadDataUnavailable }); // unset initially
|
|
368
368
|
/**
|
|
369
369
|
* a server side monitored item
|
|
370
370
|
*
|
|
@@ -391,7 +391,7 @@ export class MonitoredItem extends EventEmitter {
|
|
|
391
391
|
public samplingInterval = -1;
|
|
392
392
|
public monitoredItemId: number;
|
|
393
393
|
public overflow: boolean;
|
|
394
|
-
public oldDataValue
|
|
394
|
+
public oldDataValue: DataValue;
|
|
395
395
|
public monitoringMode: MonitoringMode;
|
|
396
396
|
public timestampsToReturn: TimestampsToReturn;
|
|
397
397
|
public itemToMonitor: any;
|
|
@@ -404,7 +404,7 @@ export class MonitoredItem extends EventEmitter {
|
|
|
404
404
|
public samplingFunc: SamplingFunc | null = null;
|
|
405
405
|
|
|
406
406
|
private _node: BaseNode | null;
|
|
407
|
-
|
|
407
|
+
public queue: QueueItem[];
|
|
408
408
|
private _semantic_version: number;
|
|
409
409
|
private _is_sampling = false;
|
|
410
410
|
private _on_opcua_event_received_callback: any;
|
|
@@ -433,7 +433,7 @@ export class MonitoredItem extends EventEmitter {
|
|
|
433
433
|
this.queue = [];
|
|
434
434
|
this.overflow = false;
|
|
435
435
|
|
|
436
|
-
this.oldDataValue =
|
|
436
|
+
this.oldDataValue = badDataUnavailable;
|
|
437
437
|
|
|
438
438
|
// user has to call setMonitoringMode
|
|
439
439
|
this.monitoringMode = MonitoringMode.Invalid;
|
|
@@ -522,7 +522,7 @@ export class MonitoredItem extends EventEmitter {
|
|
|
522
522
|
}
|
|
523
523
|
|
|
524
524
|
// x assert(this._samplingId === null,"Sampling Id must be null");
|
|
525
|
-
this.oldDataValue =
|
|
525
|
+
this.oldDataValue = badDataUnavailable;
|
|
526
526
|
this.queue = [];
|
|
527
527
|
this.itemToMonitor = null;
|
|
528
528
|
this.filter = null;
|
|
@@ -580,7 +580,7 @@ export class MonitoredItem extends EventEmitter {
|
|
|
580
580
|
*
|
|
581
581
|
*/
|
|
582
582
|
// eslint-disable-next-line complexity, max-statements
|
|
583
|
-
public recordValue(dataValue: DataValue, skipChangeTest
|
|
583
|
+
public recordValue(dataValue: DataValue, skipChangeTest?: boolean, indexRange?: NumericRange): boolean {
|
|
584
584
|
if (!this.itemToMonitor) {
|
|
585
585
|
// we must have a valid itemToMonitor(have this monitoredItem been disposed already ?)
|
|
586
586
|
// istanbul ignore next
|
|
@@ -588,18 +588,13 @@ export class MonitoredItem extends EventEmitter {
|
|
|
588
588
|
return false;
|
|
589
589
|
}
|
|
590
590
|
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
"recordValue expects different dataValue.value to be provided"
|
|
596
|
-
);
|
|
597
|
-
|
|
598
|
-
assert(!dataValue.value || dataValue.value.isValid(), "expecting a valid variant value");
|
|
591
|
+
if (dataValue === this.oldDataValue) {
|
|
592
|
+
errorLog("recordValue expects different dataValue to be provided");
|
|
593
|
+
}
|
|
594
|
+
doDebug && assert(!dataValue.value || dataValue.value.isValid(), "expecting a valid variant value");
|
|
599
595
|
|
|
600
596
|
const hasSemanticChanged = this.node && (this.node as any).semantic_version !== this._semantic_version;
|
|
601
597
|
|
|
602
|
-
|
|
603
598
|
if (!hasSemanticChanged && indexRange && this.itemToMonitor.indexRange) {
|
|
604
599
|
// we just ignore changes that do not fall within our range
|
|
605
600
|
// ( unless semantic bit has changed )
|
|
@@ -655,13 +650,13 @@ export class MonitoredItem extends EventEmitter {
|
|
|
655
650
|
}
|
|
656
651
|
}
|
|
657
652
|
|
|
658
|
-
if (!apply_filter.call(this, dataValue)) {
|
|
653
|
+
if (!skipChangeTest && !apply_filter.call(this, dataValue)) {
|
|
659
654
|
// istanbul ignore next
|
|
660
655
|
if (doDebug) {
|
|
661
656
|
debugLog("recordValue => Rejected ", this.node?.browseName.toString(), " because apply_filter");
|
|
662
657
|
debugLog("current Value =>", this.oldDataValue?.toString());
|
|
663
|
-
debugLog("
|
|
664
|
-
debugLog("
|
|
658
|
+
debugLog("proposed Value =>", dataValue?.toString());
|
|
659
|
+
debugLog("proposed Value =>", dataValue == this.oldDataValue, dataValue.value === this.oldDataValue?.value);
|
|
665
660
|
}
|
|
666
661
|
return false;
|
|
667
662
|
}
|
|
@@ -672,12 +667,12 @@ export class MonitoredItem extends EventEmitter {
|
|
|
672
667
|
|
|
673
668
|
// istanbul ignore next
|
|
674
669
|
if (doDebug) {
|
|
675
|
-
debugLog("Current : ", this.oldDataValue
|
|
670
|
+
debugLog("Current : ", this.oldDataValue?.toString());
|
|
676
671
|
debugLog("New : ", dataValue.toString());
|
|
677
672
|
debugLog("indexRange=", indexRange);
|
|
678
673
|
}
|
|
679
674
|
|
|
680
|
-
if (sameVariant(dataValue.value, this.oldDataValue
|
|
675
|
+
if (this.oldDataValue !== badDataUnavailable && sameVariant(dataValue.value, this.oldDataValue.value)) {
|
|
681
676
|
// istanbul ignore next
|
|
682
677
|
doDebug &&
|
|
683
678
|
debugLog("recordValue => Rejected ", this.node?.browseName.toString(), " because useIndexRange && sameVariant");
|
|
@@ -791,13 +786,24 @@ export class MonitoredItem extends EventEmitter {
|
|
|
791
786
|
return notifications;
|
|
792
787
|
}
|
|
793
788
|
|
|
794
|
-
public modify(
|
|
789
|
+
public modify(
|
|
790
|
+
timestampsToReturn: TimestampsToReturn | null,
|
|
791
|
+
monitoringParameters: MonitoringParameters | null
|
|
792
|
+
): MonitoredItemModifyResult {
|
|
795
793
|
assert(monitoringParameters instanceof MonitoringParameters);
|
|
796
794
|
|
|
797
795
|
const old_samplingInterval = this.samplingInterval;
|
|
798
796
|
|
|
799
797
|
this.timestampsToReturn = timestampsToReturn || this.timestampsToReturn;
|
|
800
798
|
|
|
799
|
+
if (!monitoringParameters) {
|
|
800
|
+
return new MonitoredItemModifyResult({
|
|
801
|
+
revisedQueueSize: this.queueSize,
|
|
802
|
+
revisedSamplingInterval: this.samplingInterval,
|
|
803
|
+
filterResult: null,
|
|
804
|
+
statusCode: StatusCodes.Good
|
|
805
|
+
});
|
|
806
|
+
}
|
|
801
807
|
if (old_samplingInterval !== 0 && monitoringParameters.samplingInterval === 0) {
|
|
802
808
|
monitoringParameters.samplingInterval = MonitoredItem.minimumSamplingInterval; // fastest possible
|
|
803
809
|
}
|
|
@@ -831,13 +837,30 @@ export class MonitoredItem extends EventEmitter {
|
|
|
831
837
|
});
|
|
832
838
|
}
|
|
833
839
|
|
|
834
|
-
public async
|
|
840
|
+
public async resendInitialValue(): Promise<void> {
|
|
835
841
|
// the first Publish response(s) after the TransferSubscriptions call shall contain the current values of all
|
|
836
842
|
// Monitored Items in the Subscription where the Monitoring Mode is set to Reporting.
|
|
837
843
|
// the first Publish response after the TransferSubscriptions call shall contain only the value changes since
|
|
838
844
|
// the last Publish response was sent.
|
|
839
845
|
// This parameter only applies to MonitoredItems used for monitoring Attribute changes.
|
|
840
|
-
|
|
846
|
+
|
|
847
|
+
// istanbul ignore next
|
|
848
|
+
if (!this.node) return;
|
|
849
|
+
|
|
850
|
+
const sessionContext = this.getSessionContext() || SessionContext.defaultContext;
|
|
851
|
+
|
|
852
|
+
// istanbul ignore next
|
|
853
|
+
if (!sessionContext) return;
|
|
854
|
+
|
|
855
|
+
// no need to resend if a value is already in the queue
|
|
856
|
+
if (this.queue.length > 0) return;
|
|
857
|
+
|
|
858
|
+
const theValueToResend =
|
|
859
|
+
this.oldDataValue !== badDataUnavailable
|
|
860
|
+
? this.oldDataValue
|
|
861
|
+
: this.node.readAttribute(sessionContext, this.itemToMonitor.attributeId);
|
|
862
|
+
this.oldDataValue = badDataUnavailable;
|
|
863
|
+
this._enqueue_value(theValueToResend);
|
|
841
864
|
}
|
|
842
865
|
|
|
843
866
|
private getSessionContext(): ISessionContext | null {
|
|
@@ -857,8 +880,11 @@ export class MonitoredItem extends EventEmitter {
|
|
|
857
880
|
return;
|
|
858
881
|
}
|
|
859
882
|
|
|
860
|
-
|
|
883
|
+
// Use default context if session is not available
|
|
884
|
+
const sessionContext = this.getSessionContext() || SessionContext.defaultContext;
|
|
885
|
+
|
|
861
886
|
if (!sessionContext) {
|
|
887
|
+
warningLog("MonitoredItem#_on_sampling_timer : ", this.node?.nodeId.toString(), "cannot find session");
|
|
862
888
|
return;
|
|
863
889
|
}
|
|
864
890
|
// istanbul ignore next
|
|
@@ -893,13 +919,13 @@ export class MonitoredItem extends EventEmitter {
|
|
|
893
919
|
|
|
894
920
|
this._is_sampling = true;
|
|
895
921
|
|
|
896
|
-
this.samplingFunc.call(this, sessionContext, this.oldDataValue
|
|
922
|
+
this.samplingFunc.call(this, sessionContext, this.oldDataValue, (err: Error | null, newDataValue?: DataValue) => {
|
|
897
923
|
if (!this._samplingId) {
|
|
898
924
|
// item has been disposed. The monitored item has been disposed while the async sampling func
|
|
899
925
|
// was taking place ... just ignore this
|
|
900
926
|
return;
|
|
901
927
|
}
|
|
902
|
-
//
|
|
928
|
+
// istanbul ignore next
|
|
903
929
|
if (err) {
|
|
904
930
|
errorLog(" SAMPLING ERROR =>", err);
|
|
905
931
|
} else {
|
|
@@ -1022,13 +1048,14 @@ export class MonitoredItem extends EventEmitter {
|
|
|
1022
1048
|
private _start_sampling(recordInitialValue: boolean): void {
|
|
1023
1049
|
// istanbul ignore next
|
|
1024
1050
|
if (!this.node) {
|
|
1025
|
-
|
|
1051
|
+
return; // we just want to ignore here ...
|
|
1026
1052
|
}
|
|
1053
|
+
this.oldDataValue = badDataUnavailable;
|
|
1027
1054
|
setImmediate(() => this.__start_sampling(recordInitialValue));
|
|
1028
1055
|
}
|
|
1029
1056
|
|
|
1030
1057
|
private __acquireInitialValue(sessionContext: ISessionContext, callback: CallbackT<DataValue>): void {
|
|
1031
|
-
//
|
|
1058
|
+
// acquire initial value from the variable/object not itself or from the last known value if we have
|
|
1032
1059
|
// one already
|
|
1033
1060
|
assert(this.itemToMonitor.attributeId === AttributeIds.Value);
|
|
1034
1061
|
assert(this.node);
|
|
@@ -1036,16 +1063,16 @@ export class MonitoredItem extends EventEmitter {
|
|
|
1036
1063
|
return callback(new Error("Invalid "));
|
|
1037
1064
|
}
|
|
1038
1065
|
const variable = this.node as UAVariable;
|
|
1039
|
-
if (
|
|
1066
|
+
if (this.oldDataValue == badDataUnavailable) {
|
|
1040
1067
|
variable.readValueAsync(sessionContext, (err: Error | null, dataValue?: DataValue) => {
|
|
1041
1068
|
callback(err, dataValue);
|
|
1042
1069
|
});
|
|
1043
1070
|
} else {
|
|
1044
1071
|
const o = this.oldDataValue;
|
|
1045
|
-
this.oldDataValue =
|
|
1072
|
+
this.oldDataValue = badDataUnavailable;
|
|
1046
1073
|
// istanbul ignore next
|
|
1047
1074
|
if (doDebug) {
|
|
1048
|
-
|
|
1075
|
+
safeGuardRegister(this);
|
|
1049
1076
|
}
|
|
1050
1077
|
callback(null, o);
|
|
1051
1078
|
}
|
|
@@ -1056,7 +1083,7 @@ export class MonitoredItem extends EventEmitter {
|
|
|
1056
1083
|
return; // we just want to ignore here ...
|
|
1057
1084
|
}
|
|
1058
1085
|
|
|
1059
|
-
const sessionContext = this.getSessionContext();
|
|
1086
|
+
const sessionContext = this.getSessionContext() || SessionContext.defaultContext;
|
|
1060
1087
|
// istanbul ignore next
|
|
1061
1088
|
if (!sessionContext) {
|
|
1062
1089
|
return;
|
|
@@ -1241,34 +1268,20 @@ export class MonitoredItem extends EventEmitter {
|
|
|
1241
1268
|
* @param dataValue {DataValue} the dataValue to enqueue
|
|
1242
1269
|
* @private
|
|
1243
1270
|
*/
|
|
1244
|
-
|
|
1271
|
+
public _enqueue_value(dataValue: DataValue) {
|
|
1245
1272
|
// preconditions:
|
|
1246
|
-
|
|
1247
|
-
debugLog("_enqueue_value = ", dataValue.toString());
|
|
1248
|
-
}
|
|
1273
|
+
doDebug && debugLog("_enqueue_value = ", dataValue.toString());
|
|
1249
1274
|
|
|
1250
|
-
assert(dataValue instanceof DataValue);
|
|
1251
1275
|
// lets verify that, if status code is good then we have a valid Variant in the dataValue
|
|
1252
|
-
assert(!dataValue.statusCode.isGoodish() || dataValue.value instanceof Variant);
|
|
1253
|
-
// xx assert(isGoodish(dataValue.statusCode) || util.isNullOrUndefined(dataValue.value) );
|
|
1276
|
+
doDebug && assert(!dataValue.statusCode.isGoodish() || dataValue.value instanceof Variant);
|
|
1254
1277
|
// let's check that data Value is really a different object
|
|
1255
1278
|
// we may end up with corrupted queue if dataValue are recycled and stored as is in notifications
|
|
1256
|
-
assert(dataValue !== this.oldDataValue, "dataValue cannot be the same object twice!");
|
|
1257
|
-
|
|
1258
|
-
// Xx // todo ERN !!!! PLEASE CHECK this !!!
|
|
1259
|
-
// Xx // let make a clone, so we have a snapshot
|
|
1260
|
-
// Xx dataValue = dataValue.clone();
|
|
1279
|
+
doDebug && assert(dataValue !== this.oldDataValue, "dataValue cannot be the same object twice!");
|
|
1261
1280
|
|
|
1262
1281
|
// let's check that data Value is really a different object
|
|
1263
1282
|
// we may end up with corrupted queue if dataValue are recycled and stored as is in notifications
|
|
1264
|
-
assert(
|
|
1265
|
-
!this.oldDataValue || !dataValue.value || dataValue.value !== this.oldDataValue.value,
|
|
1266
|
-
"dataValue cannot be the same object twice!"
|
|
1267
|
-
);
|
|
1268
|
-
|
|
1269
1283
|
if (
|
|
1270
1284
|
!(
|
|
1271
|
-
!this.oldDataValue ||
|
|
1272
1285
|
!this.oldDataValue.value ||
|
|
1273
1286
|
!dataValue.value ||
|
|
1274
1287
|
!(dataValue.value.value instanceof Object) ||
|
|
@@ -1294,7 +1307,7 @@ export class MonitoredItem extends EventEmitter {
|
|
|
1294
1307
|
this.oldDataValue = dataValue.clone();
|
|
1295
1308
|
// istanbul ignore next
|
|
1296
1309
|
if (doDebug) {
|
|
1297
|
-
|
|
1310
|
+
safeGuardRegister(this);
|
|
1298
1311
|
}
|
|
1299
1312
|
|
|
1300
1313
|
const notification = this._makeDataChangeNotification(this.oldDataValue);
|
|
@@ -1302,7 +1315,6 @@ export class MonitoredItem extends EventEmitter {
|
|
|
1302
1315
|
}
|
|
1303
1316
|
|
|
1304
1317
|
private _makeEventFieldList(eventFields: any[]): EventFieldList {
|
|
1305
|
-
assert(Array.isArray(eventFields));
|
|
1306
1318
|
return new EventFieldList({
|
|
1307
1319
|
clientHandle: this.clientHandle,
|
|
1308
1320
|
eventFields
|
package/source/opcua_server.ts
CHANGED
|
@@ -165,7 +165,7 @@ import { RegisterServerManager } from "./register_server_manager";
|
|
|
165
165
|
import { RegisterServerManagerHidden } from "./register_server_manager_hidden";
|
|
166
166
|
import { RegisterServerManagerMDNSONLY } from "./register_server_manager_mdns_only";
|
|
167
167
|
import { ServerCapabilitiesOptions } from "./server_capabilities";
|
|
168
|
-
import { EndpointDescriptionEx, OPCUAServerEndPoint } from "./server_end_point";
|
|
168
|
+
import { EndpointDescriptionEx, IServerTransportSettings, OPCUAServerEndPoint } from "./server_end_point";
|
|
169
169
|
import { ClosingReason, CreateSessionOption, ServerEngine } from "./server_engine";
|
|
170
170
|
import { ServerSession } from "./server_session";
|
|
171
171
|
import { CreateMonitoredItemHook, DeleteMonitoredItemHook, Subscription } from "./server_subscription";
|
|
@@ -926,6 +926,12 @@ export interface OPCUAServerOptions extends OPCUABaseServerOptions, OPCUAServerE
|
|
|
926
926
|
* @default false
|
|
927
927
|
*/
|
|
928
928
|
skipOwnNamespace?: boolean;
|
|
929
|
+
|
|
930
|
+
/**
|
|
931
|
+
* @private
|
|
932
|
+
* @optional
|
|
933
|
+
*/
|
|
934
|
+
transportSettings?: IServerTransportSettings;
|
|
929
935
|
}
|
|
930
936
|
|
|
931
937
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
|
|
@@ -1473,8 +1479,9 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
1473
1479
|
/**
|
|
1474
1480
|
* retrieve a session by authentication token
|
|
1475
1481
|
* @internal
|
|
1482
|
+
* @private
|
|
1476
1483
|
*/
|
|
1477
|
-
|
|
1484
|
+
public getSession(authenticationToken: NodeId, activeOnly?: boolean): ServerSession | null {
|
|
1478
1485
|
return this.engine ? this.engine.getSession(authenticationToken, activeOnly) : null;
|
|
1479
1486
|
}
|
|
1480
1487
|
|
|
@@ -1615,8 +1622,7 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
1615
1622
|
case StatusCodes.BadCertificateIssuerTimeInvalid:
|
|
1616
1623
|
this.raiseEvent("AuditCertificateExpiredEventType", {
|
|
1617
1624
|
certificate: { dataType: DataType.ByteString, value: certificate },
|
|
1618
|
-
sourceName: { dataType: DataType.String, value: "Security/Certificate" }
|
|
1619
|
-
comment: { dataType: DataType.String, value: certificateStatus.toString() }
|
|
1625
|
+
sourceName: { dataType: DataType.String, value: "Security/Certificate" }
|
|
1620
1626
|
});
|
|
1621
1627
|
break;
|
|
1622
1628
|
case StatusCodes.BadCertificateRevoked:
|
|
@@ -1624,8 +1630,7 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
1624
1630
|
case StatusCodes.BadCertificateIssuerRevocationUnknown:
|
|
1625
1631
|
this.raiseEvent("AuditCertificateRevokedEventType", {
|
|
1626
1632
|
certificate: { dataType: DataType.ByteString, value: certificate },
|
|
1627
|
-
sourceName: { dataType: DataType.String, value: "Security/Certificate" }
|
|
1628
|
-
comment: { dataType: DataType.String, value: certificateStatus.toString() }
|
|
1633
|
+
sourceName: { dataType: DataType.String, value: "Security/Certificate" }
|
|
1629
1634
|
});
|
|
1630
1635
|
break;
|
|
1631
1636
|
case StatusCodes.BadCertificateIssuerUseNotAllowed:
|
|
@@ -1633,8 +1638,7 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
1633
1638
|
case StatusCodes.BadSecurityChecksFailed:
|
|
1634
1639
|
this.raiseEvent("AuditCertificateMismatchEventType", {
|
|
1635
1640
|
certificate: { dataType: DataType.ByteString, value: certificate },
|
|
1636
|
-
sourceName: { dataType: DataType.String, value: "Security/Certificate" }
|
|
1637
|
-
comment: { dataType: DataType.String, value: certificateStatus.toString() }
|
|
1641
|
+
sourceName: { dataType: DataType.String, value: "Security/Certificate" }
|
|
1638
1642
|
});
|
|
1639
1643
|
break;
|
|
1640
1644
|
}
|
|
@@ -2341,8 +2345,8 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
2341
2345
|
let response: any;
|
|
2342
2346
|
/* istanbul ignore next */
|
|
2343
2347
|
if (!message.session || message.session_statusCode !== StatusCodes.Good) {
|
|
2344
|
-
const errMessage = "
|
|
2345
|
-
response = new
|
|
2348
|
+
const errMessage = "=>" + message.session_statusCode?.toString();
|
|
2349
|
+
response = new ServiceFault({ responseHeader: { serviceResult: message.session_statusCode } });
|
|
2346
2350
|
debugLog(chalk.red.bold(errMessage), chalk.yellow(message.session_statusCode!.toString()), response.constructor.name);
|
|
2347
2351
|
return sendResponse(response);
|
|
2348
2352
|
}
|
|
@@ -2445,23 +2449,28 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
2445
2449
|
return sendError(StatusCodes.BadNothingToDo);
|
|
2446
2450
|
}
|
|
2447
2451
|
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2452
|
+
// check minimal
|
|
2453
|
+
if (
|
|
2454
|
+
request.subscriptionIds.length >
|
|
2455
|
+
Math.min(
|
|
2456
|
+
this.engine.serverCapabilities.maxSubscriptionsPerSession,
|
|
2457
|
+
this.engine.serverCapabilities.maxSubscriptions
|
|
2458
|
+
)
|
|
2459
|
+
) {
|
|
2460
|
+
return sendError(StatusCodes.BadTooManyOperations);
|
|
2455
2461
|
}
|
|
2456
2462
|
|
|
2463
|
+
const promises: Promise<T>[] = subscriptionIds.map((subscriptionId: number) =>
|
|
2464
|
+
actionToPerform(session, subscriptionId)
|
|
2465
|
+
);
|
|
2466
|
+
const results: T[] = await Promise.all(promises);
|
|
2467
|
+
|
|
2468
|
+
const serviceResult: StatusCode = StatusCodes.Good;
|
|
2457
2469
|
const response = new ResponseClass({
|
|
2458
2470
|
responseHeader: {
|
|
2459
|
-
serviceResult
|
|
2460
|
-
request.subscriptionIds.length > this.engine.serverCapabilities.maxSubscriptionsPerSession
|
|
2461
|
-
? StatusCodes.BadTooManyOperations
|
|
2462
|
-
: StatusCodes.Good
|
|
2471
|
+
serviceResult
|
|
2463
2472
|
},
|
|
2464
|
-
results
|
|
2473
|
+
results: results as any
|
|
2465
2474
|
});
|
|
2466
2475
|
sendResponse(response);
|
|
2467
2476
|
}
|
|
@@ -3544,7 +3553,12 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
3544
3553
|
|
|
3545
3554
|
private createEndpoint(
|
|
3546
3555
|
port1: number,
|
|
3547
|
-
serverOptions: {
|
|
3556
|
+
serverOptions: {
|
|
3557
|
+
defaultSecureTokenLifetime?: number;
|
|
3558
|
+
timeout?: number;
|
|
3559
|
+
host?: string;
|
|
3560
|
+
transportSettings?: IServerTransportSettings;
|
|
3561
|
+
}
|
|
3548
3562
|
): OPCUAServerEndPoint {
|
|
3549
3563
|
// add the tcp/ip endpoint with no security
|
|
3550
3564
|
const endPoint = new OPCUAServerEndPoint({
|
|
@@ -3560,7 +3574,8 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
3560
3574
|
|
|
3561
3575
|
maxConnections: this.maxConnectionsPerEndpoint,
|
|
3562
3576
|
objectFactory: this.objectFactory,
|
|
3563
|
-
serverInfo: this.serverInfo
|
|
3577
|
+
serverInfo: this.serverInfo,
|
|
3578
|
+
transportSettings: serverOptions.transportSettings
|
|
3564
3579
|
});
|
|
3565
3580
|
return endPoint;
|
|
3566
3581
|
}
|
|
@@ -28,6 +28,8 @@ import { UserTokenType } from "node-opcua-service-endpoints";
|
|
|
28
28
|
import { EndpointDescription } from "node-opcua-service-endpoints";
|
|
29
29
|
import { ApplicationDescription } from "node-opcua-service-endpoints";
|
|
30
30
|
import { UserTokenPolicyOptions } from "node-opcua-types";
|
|
31
|
+
import { IHelloAckLimits } from "node-opcua-transport";
|
|
32
|
+
|
|
31
33
|
import { IChannelData } from "./i_channel_data";
|
|
32
34
|
import { ISocketData } from "./i_socket_data";
|
|
33
35
|
|
|
@@ -157,6 +159,12 @@ export interface OPCUAServerEndPointOptions {
|
|
|
157
159
|
serverInfo: ApplicationDescription;
|
|
158
160
|
|
|
159
161
|
objectFactory?: any;
|
|
162
|
+
|
|
163
|
+
transportSettings?: IServerTransportSettings;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export interface IServerTransportSettings {
|
|
167
|
+
adjustTransportLimits: (hello: IHelloAckLimits) => IHelloAckLimits;
|
|
160
168
|
}
|
|
161
169
|
|
|
162
170
|
export interface EndpointDescriptionParams {
|
|
@@ -238,6 +246,8 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
|
|
|
238
246
|
private _started = false;
|
|
239
247
|
private _counter = OPCUAServerEndPointCounter++;
|
|
240
248
|
private _policy_deduplicator: { [key: string]: number } = {};
|
|
249
|
+
|
|
250
|
+
private transportSettings?: IServerTransportSettings;
|
|
241
251
|
constructor(options: OPCUAServerEndPointOptions) {
|
|
242
252
|
super();
|
|
243
253
|
|
|
@@ -279,6 +289,8 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
|
|
|
279
289
|
|
|
280
290
|
this.serverInfo = options.serverInfo;
|
|
281
291
|
assert(this.serverInfo !== null && typeof this.serverInfo === "object");
|
|
292
|
+
|
|
293
|
+
this.transportSettings = options.transportSettings;
|
|
282
294
|
}
|
|
283
295
|
|
|
284
296
|
public dispose(): void {
|
|
@@ -746,7 +758,8 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
|
|
|
746
758
|
defaultSecureTokenLifetime: this.defaultSecureTokenLifetime,
|
|
747
759
|
// objectFactory: this.objectFactory,
|
|
748
760
|
parent: this,
|
|
749
|
-
timeout: this.timeout
|
|
761
|
+
timeout: this.timeout,
|
|
762
|
+
adjustTransportLimits: this.transportSettings?.adjustTransportLimits
|
|
750
763
|
});
|
|
751
764
|
|
|
752
765
|
debugLog("channel Timeout = >", channel.timeout);
|
|
@@ -840,8 +853,9 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
|
|
|
840
853
|
} else {
|
|
841
854
|
debugLog("OPCUAServerEndPoint#_registerChannel called when end point is shutdown !");
|
|
842
855
|
debugLog(" -> channel will be forcefully terminated");
|
|
843
|
-
channel.close()
|
|
844
|
-
|
|
856
|
+
channel.close(() => {
|
|
857
|
+
channel.dispose();
|
|
858
|
+
});
|
|
845
859
|
}
|
|
846
860
|
}
|
|
847
861
|
|
|
@@ -914,17 +928,22 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
|
|
|
914
928
|
errorLog(chalk.bgRed.white("PREVENTING DDOS ATTACK => maxConnection =" + this.maxConnections));
|
|
915
929
|
|
|
916
930
|
const unused_channels: ServerSecureChannelLayer[] = this.getChannels().filter((channel1: ServerSecureChannelLayer) => {
|
|
917
|
-
return !channel1.
|
|
931
|
+
return !channel1.hasSession;
|
|
918
932
|
});
|
|
919
933
|
if (unused_channels.length === 0) {
|
|
934
|
+
doDebug && console.log(
|
|
935
|
+
this.getChannels()
|
|
936
|
+
.map(({ status, isOpened, hasSession }) => `${status} ${isOpened} ${hasSession}\n`)
|
|
937
|
+
.join(" ")
|
|
938
|
+
);
|
|
920
939
|
// all channels are in used , we cannot get any
|
|
921
|
-
errorLog(
|
|
940
|
+
errorLog(`All channels are in used ! we cannot cancel any ${this.getChannels().length}`);
|
|
922
941
|
// istanbul ignore next
|
|
923
942
|
if (doDebug) {
|
|
924
943
|
console.log(" - all channels are used !!!!");
|
|
925
|
-
dumpChannelInfo(this.getChannels());
|
|
944
|
+
false && dumpChannelInfo(this.getChannels());
|
|
926
945
|
}
|
|
927
|
-
setTimeout(deny_connection,
|
|
946
|
+
setTimeout(deny_connection, 1000);
|
|
928
947
|
return;
|
|
929
948
|
}
|
|
930
949
|
// istanbul ignore next
|
|
@@ -935,7 +954,7 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
|
|
|
935
954
|
);
|
|
936
955
|
}
|
|
937
956
|
const channel = unused_channels[0];
|
|
938
|
-
errorLog(
|
|
957
|
+
errorLog(`${unused_channels.length} : Forcefully closing oldest channel that have no session: ${channel.hashKey}`);
|
|
939
958
|
channel.close(() => {
|
|
940
959
|
// istanbul ignore next
|
|
941
960
|
if (doDebug) {
|
package/source/server_engine.ts
CHANGED
|
@@ -338,7 +338,7 @@ export interface ServerEngineOptions {
|
|
|
338
338
|
export interface CreateSessionOption {
|
|
339
339
|
clientDescription?: ApplicationDescription;
|
|
340
340
|
sessionTimeout?: number;
|
|
341
|
-
server
|
|
341
|
+
server?: IServerBase;
|
|
342
342
|
}
|
|
343
343
|
|
|
344
344
|
export type ClosingReason = "Timeout" | "Terminated" | "CloseSession" | "Forcing";
|
|
@@ -374,7 +374,7 @@ export class ServerEngine extends EventEmitter implements IAddressSpaceAccessor
|
|
|
374
374
|
private _serverStatus: ServerStatusDataType;
|
|
375
375
|
private _globalCounter: { totalMonitoredItemCount: number } = { totalMonitoredItemCount: 0 };
|
|
376
376
|
|
|
377
|
-
constructor(options
|
|
377
|
+
constructor(options?: ServerEngineOptions) {
|
|
378
378
|
super();
|
|
379
379
|
|
|
380
380
|
options = options || ({ applicationUri: "" } as ServerEngineOptions);
|
|
@@ -1423,7 +1423,7 @@ export class ServerEngine extends EventEmitter implements IAddressSpaceAccessor
|
|
|
1423
1423
|
* @param [options.clientDescription] {ApplicationDescription}
|
|
1424
1424
|
* @return {ServerSession}
|
|
1425
1425
|
*/
|
|
1426
|
-
public createSession(options
|
|
1426
|
+
public createSession(options?: CreateSessionOption): ServerSession {
|
|
1427
1427
|
options = options || {};
|
|
1428
1428
|
options.server = options.server || {};
|
|
1429
1429
|
debugLog("createSession : increasing serverDiagnosticsSummary cumulatedSessionCount/currentSessionCount ");
|
|
@@ -1645,9 +1645,10 @@ export class ServerEngine extends EventEmitter implements IAddressSpaceAccessor
|
|
|
1645
1645
|
subscription.$session._unexposeSubscriptionDiagnostics(subscription);
|
|
1646
1646
|
}
|
|
1647
1647
|
|
|
1648
|
-
await ServerSidePublishEngine.transferSubscription(subscription, session.publishEngine, sendInitialValues);
|
|
1649
1648
|
subscription.$session = session;
|
|
1650
|
-
|
|
1649
|
+
|
|
1650
|
+
await ServerSidePublishEngine.transferSubscription(subscription, session.publishEngine, sendInitialValues);
|
|
1651
|
+
|
|
1651
1652
|
session._exposeSubscriptionDiagnostics(subscription);
|
|
1652
1653
|
|
|
1653
1654
|
assert((subscription.publishEngine as any) === session.publishEngine);
|