node-opcua-server 2.120.0 → 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/dist/addressSpace_accessor.js +1 -3
- package/dist/addressSpace_accessor.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/opcua_server.d.ts +2 -1
- package/dist/opcua_server.js +15 -18
- package/dist/opcua_server.js.map +1 -1
- package/dist/server_end_point.js +11 -7
- package/dist/server_end_point.js.map +1 -1
- package/dist/server_engine.d.ts +3 -3
- package/dist/server_engine.js +1 -1
- 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 +2 -0
- package/dist/server_session.js.map +1 -1
- package/dist/server_subscription.d.ts +17 -9
- package/dist/server_subscription.js +46 -21
- package/dist/server_subscription.js.map +1 -1
- package/package.json +43 -43
- package/source/addressSpace_accessor.ts +2 -3
- package/source/monitored_item.ts +68 -56
- package/source/opcua_server.ts +24 -21
- package/source/server_end_point.ts +13 -7
- 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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-opcua-server",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.121.0",
|
|
4
4
|
"description": "pure nodejs OPCUA SDK - module server",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"build": "tsc -b",
|
|
@@ -17,54 +17,54 @@
|
|
|
17
17
|
"chalk": "4.1.2",
|
|
18
18
|
"dequeue": "^1.0.5",
|
|
19
19
|
"lodash": "4.17.21",
|
|
20
|
-
"node-opcua-address-space": "2.
|
|
21
|
-
"node-opcua-address-space-base": "2.
|
|
20
|
+
"node-opcua-address-space": "2.121.0",
|
|
21
|
+
"node-opcua-address-space-base": "2.121.0",
|
|
22
22
|
"node-opcua-assert": "2.120.0",
|
|
23
|
-
"node-opcua-basic-types": "2.
|
|
23
|
+
"node-opcua-basic-types": "2.121.0",
|
|
24
24
|
"node-opcua-binary-stream": "2.120.0",
|
|
25
|
-
"node-opcua-certificate-manager": "2.
|
|
26
|
-
"node-opcua-client": "2.
|
|
27
|
-
"node-opcua-client-dynamic-extension-object": "2.
|
|
28
|
-
"node-opcua-common": "2.
|
|
29
|
-
"node-opcua-constants": "2.
|
|
30
|
-
"node-opcua-crypto": "4.
|
|
31
|
-
"node-opcua-data-model": "2.
|
|
32
|
-
"node-opcua-data-value": "2.
|
|
33
|
-
"node-opcua-date-time": "2.
|
|
34
|
-
"node-opcua-debug": "2.
|
|
35
|
-
"node-opcua-extension-object": "2.
|
|
36
|
-
"node-opcua-factory": "2.
|
|
25
|
+
"node-opcua-certificate-manager": "2.121.0",
|
|
26
|
+
"node-opcua-client": "2.121.0",
|
|
27
|
+
"node-opcua-client-dynamic-extension-object": "2.121.0",
|
|
28
|
+
"node-opcua-common": "2.121.0",
|
|
29
|
+
"node-opcua-constants": "2.121.0",
|
|
30
|
+
"node-opcua-crypto": "4.8.0",
|
|
31
|
+
"node-opcua-data-model": "2.121.0",
|
|
32
|
+
"node-opcua-data-value": "2.121.0",
|
|
33
|
+
"node-opcua-date-time": "2.121.0",
|
|
34
|
+
"node-opcua-debug": "2.121.0",
|
|
35
|
+
"node-opcua-extension-object": "2.121.0",
|
|
36
|
+
"node-opcua-factory": "2.121.0",
|
|
37
37
|
"node-opcua-hostname": "2.120.0",
|
|
38
|
-
"node-opcua-nodeid": "2.
|
|
39
|
-
"node-opcua-nodesets": "2.
|
|
40
|
-
"node-opcua-numeric-range": "2.
|
|
41
|
-
"node-opcua-object-registry": "2.
|
|
42
|
-
"node-opcua-secure-channel": "2.
|
|
43
|
-
"node-opcua-service-browse": "2.
|
|
44
|
-
"node-opcua-service-call": "2.
|
|
45
|
-
"node-opcua-service-discovery": "2.
|
|
46
|
-
"node-opcua-service-endpoints": "2.
|
|
47
|
-
"node-opcua-service-filter": "2.
|
|
48
|
-
"node-opcua-service-history": "2.
|
|
49
|
-
"node-opcua-service-node-management": "2.
|
|
50
|
-
"node-opcua-service-query": "2.
|
|
51
|
-
"node-opcua-service-read": "2.
|
|
52
|
-
"node-opcua-service-register-node": "2.
|
|
53
|
-
"node-opcua-service-secure-channel": "2.
|
|
54
|
-
"node-opcua-service-session": "2.
|
|
55
|
-
"node-opcua-service-subscription": "2.
|
|
56
|
-
"node-opcua-service-translate-browse-path": "2.
|
|
57
|
-
"node-opcua-service-write": "2.
|
|
58
|
-
"node-opcua-status-code": "2.
|
|
59
|
-
"node-opcua-transport": "2.
|
|
60
|
-
"node-opcua-types": "2.
|
|
38
|
+
"node-opcua-nodeid": "2.121.0",
|
|
39
|
+
"node-opcua-nodesets": "2.121.0",
|
|
40
|
+
"node-opcua-numeric-range": "2.121.0",
|
|
41
|
+
"node-opcua-object-registry": "2.121.0",
|
|
42
|
+
"node-opcua-secure-channel": "2.121.0",
|
|
43
|
+
"node-opcua-service-browse": "2.121.0",
|
|
44
|
+
"node-opcua-service-call": "2.121.0",
|
|
45
|
+
"node-opcua-service-discovery": "2.121.0",
|
|
46
|
+
"node-opcua-service-endpoints": "2.121.0",
|
|
47
|
+
"node-opcua-service-filter": "2.121.0",
|
|
48
|
+
"node-opcua-service-history": "2.121.0",
|
|
49
|
+
"node-opcua-service-node-management": "2.121.0",
|
|
50
|
+
"node-opcua-service-query": "2.121.0",
|
|
51
|
+
"node-opcua-service-read": "2.121.0",
|
|
52
|
+
"node-opcua-service-register-node": "2.121.0",
|
|
53
|
+
"node-opcua-service-secure-channel": "2.121.0",
|
|
54
|
+
"node-opcua-service-session": "2.121.0",
|
|
55
|
+
"node-opcua-service-subscription": "2.121.0",
|
|
56
|
+
"node-opcua-service-translate-browse-path": "2.121.0",
|
|
57
|
+
"node-opcua-service-write": "2.121.0",
|
|
58
|
+
"node-opcua-status-code": "2.121.0",
|
|
59
|
+
"node-opcua-transport": "2.121.0",
|
|
60
|
+
"node-opcua-types": "2.121.0",
|
|
61
61
|
"node-opcua-utils": "2.120.0",
|
|
62
|
-
"node-opcua-variant": "2.
|
|
62
|
+
"node-opcua-variant": "2.121.0",
|
|
63
63
|
"thenify": "^3.3.1"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
|
-
"node-opcua-data-access": "2.
|
|
67
|
-
"node-opcua-leak-detector": "2.
|
|
66
|
+
"node-opcua-data-access": "2.121.0",
|
|
67
|
+
"node-opcua-leak-detector": "2.121.0",
|
|
68
68
|
"node-opcua-test-helpers": "2.120.0",
|
|
69
69
|
"should": "^13.2.3",
|
|
70
70
|
"sinon": "^17.0.1",
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
"internet of things"
|
|
86
86
|
],
|
|
87
87
|
"homepage": "http://node-opcua.github.io/",
|
|
88
|
-
"gitHead": "
|
|
88
|
+
"gitHead": "ba93a78eda343ecc0cc20476837347f6dfa259e0",
|
|
89
89
|
"files": [
|
|
90
90
|
"dist",
|
|
91
91
|
"source"
|
|
@@ -185,7 +185,7 @@ export class AddressSpaceAccessor implements IAddressSpaceAccessor, IAddressSpac
|
|
|
185
185
|
const continuationPoint = m.nodeToRead.continuationPoint;
|
|
186
186
|
return await this.historyReadNode(context, m.nodeToRead, m.processDetail, timestampsToReturn, {
|
|
187
187
|
continuationPoint,
|
|
188
|
-
releaseContinuationPoints
|
|
188
|
+
releaseContinuationPoints
|
|
189
189
|
});
|
|
190
190
|
};
|
|
191
191
|
|
|
@@ -217,7 +217,6 @@ export class AddressSpaceAccessor implements IAddressSpaceAccessor, IAddressSpac
|
|
|
217
217
|
return await this.historyReadNode(context, nodeToRead, historyReadDetails, timestampsToReturn, {
|
|
218
218
|
continuationPoint,
|
|
219
219
|
releaseContinuationPoints,
|
|
220
|
-
index
|
|
221
220
|
});
|
|
222
221
|
};
|
|
223
222
|
const promises: Promise<HistoryReadResult>[] = [];
|
|
@@ -405,7 +404,7 @@ export class AddressSpaceAccessor implements IAddressSpaceAccessor, IAddressSpac
|
|
|
405
404
|
// invalid attributes : BadNodeAttributesInvalid
|
|
406
405
|
// invalid range : BadIndexRangeInvalid
|
|
407
406
|
const result = await obj.historyRead(context, historyReadDetails, indexRange, dataEncoding, continuationData);
|
|
408
|
-
|
|
407
|
+
|
|
409
408
|
assert(result!.isValid());
|
|
410
409
|
return result;
|
|
411
410
|
}
|
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
|
@@ -1479,8 +1479,9 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
1479
1479
|
/**
|
|
1480
1480
|
* retrieve a session by authentication token
|
|
1481
1481
|
* @internal
|
|
1482
|
+
* @private
|
|
1482
1483
|
*/
|
|
1483
|
-
|
|
1484
|
+
public getSession(authenticationToken: NodeId, activeOnly?: boolean): ServerSession | null {
|
|
1484
1485
|
return this.engine ? this.engine.getSession(authenticationToken, activeOnly) : null;
|
|
1485
1486
|
}
|
|
1486
1487
|
|
|
@@ -1621,8 +1622,7 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
1621
1622
|
case StatusCodes.BadCertificateIssuerTimeInvalid:
|
|
1622
1623
|
this.raiseEvent("AuditCertificateExpiredEventType", {
|
|
1623
1624
|
certificate: { dataType: DataType.ByteString, value: certificate },
|
|
1624
|
-
sourceName: { dataType: DataType.String, value: "Security/Certificate" }
|
|
1625
|
-
comment: { dataType: DataType.String, value: certificateStatus.toString() }
|
|
1625
|
+
sourceName: { dataType: DataType.String, value: "Security/Certificate" }
|
|
1626
1626
|
});
|
|
1627
1627
|
break;
|
|
1628
1628
|
case StatusCodes.BadCertificateRevoked:
|
|
@@ -1630,8 +1630,7 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
1630
1630
|
case StatusCodes.BadCertificateIssuerRevocationUnknown:
|
|
1631
1631
|
this.raiseEvent("AuditCertificateRevokedEventType", {
|
|
1632
1632
|
certificate: { dataType: DataType.ByteString, value: certificate },
|
|
1633
|
-
sourceName: { dataType: DataType.String, value: "Security/Certificate" }
|
|
1634
|
-
comment: { dataType: DataType.String, value: certificateStatus.toString() }
|
|
1633
|
+
sourceName: { dataType: DataType.String, value: "Security/Certificate" }
|
|
1635
1634
|
});
|
|
1636
1635
|
break;
|
|
1637
1636
|
case StatusCodes.BadCertificateIssuerUseNotAllowed:
|
|
@@ -1639,8 +1638,7 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
1639
1638
|
case StatusCodes.BadSecurityChecksFailed:
|
|
1640
1639
|
this.raiseEvent("AuditCertificateMismatchEventType", {
|
|
1641
1640
|
certificate: { dataType: DataType.ByteString, value: certificate },
|
|
1642
|
-
sourceName: { dataType: DataType.String, value: "Security/Certificate" }
|
|
1643
|
-
comment: { dataType: DataType.String, value: certificateStatus.toString() }
|
|
1641
|
+
sourceName: { dataType: DataType.String, value: "Security/Certificate" }
|
|
1644
1642
|
});
|
|
1645
1643
|
break;
|
|
1646
1644
|
}
|
|
@@ -2347,8 +2345,8 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
2347
2345
|
let response: any;
|
|
2348
2346
|
/* istanbul ignore next */
|
|
2349
2347
|
if (!message.session || message.session_statusCode !== StatusCodes.Good) {
|
|
2350
|
-
const errMessage = "
|
|
2351
|
-
response = new
|
|
2348
|
+
const errMessage = "=>" + message.session_statusCode?.toString();
|
|
2349
|
+
response = new ServiceFault({ responseHeader: { serviceResult: message.session_statusCode } });
|
|
2352
2350
|
debugLog(chalk.red.bold(errMessage), chalk.yellow(message.session_statusCode!.toString()), response.constructor.name);
|
|
2353
2351
|
return sendResponse(response);
|
|
2354
2352
|
}
|
|
@@ -2451,23 +2449,28 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
2451
2449
|
return sendError(StatusCodes.BadNothingToDo);
|
|
2452
2450
|
}
|
|
2453
2451
|
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
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);
|
|
2461
2461
|
}
|
|
2462
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;
|
|
2463
2469
|
const response = new ResponseClass({
|
|
2464
2470
|
responseHeader: {
|
|
2465
|
-
serviceResult
|
|
2466
|
-
request.subscriptionIds.length > this.engine.serverCapabilities.maxSubscriptionsPerSession
|
|
2467
|
-
? StatusCodes.BadTooManyOperations
|
|
2468
|
-
: StatusCodes.Good
|
|
2471
|
+
serviceResult
|
|
2469
2472
|
},
|
|
2470
|
-
results
|
|
2473
|
+
results: results as any
|
|
2471
2474
|
});
|
|
2472
2475
|
sendResponse(response);
|
|
2473
2476
|
}
|
|
@@ -853,8 +853,9 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
|
|
|
853
853
|
} else {
|
|
854
854
|
debugLog("OPCUAServerEndPoint#_registerChannel called when end point is shutdown !");
|
|
855
855
|
debugLog(" -> channel will be forcefully terminated");
|
|
856
|
-
channel.close()
|
|
857
|
-
|
|
856
|
+
channel.close(() => {
|
|
857
|
+
channel.dispose();
|
|
858
|
+
});
|
|
858
859
|
}
|
|
859
860
|
}
|
|
860
861
|
|
|
@@ -927,17 +928,22 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
|
|
|
927
928
|
errorLog(chalk.bgRed.white("PREVENTING DDOS ATTACK => maxConnection =" + this.maxConnections));
|
|
928
929
|
|
|
929
930
|
const unused_channels: ServerSecureChannelLayer[] = this.getChannels().filter((channel1: ServerSecureChannelLayer) => {
|
|
930
|
-
return !channel1.
|
|
931
|
+
return !channel1.hasSession;
|
|
931
932
|
});
|
|
932
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
|
+
);
|
|
933
939
|
// all channels are in used , we cannot get any
|
|
934
|
-
errorLog(
|
|
940
|
+
errorLog(`All channels are in used ! we cannot cancel any ${this.getChannels().length}`);
|
|
935
941
|
// istanbul ignore next
|
|
936
942
|
if (doDebug) {
|
|
937
943
|
console.log(" - all channels are used !!!!");
|
|
938
|
-
dumpChannelInfo(this.getChannels());
|
|
944
|
+
false && dumpChannelInfo(this.getChannels());
|
|
939
945
|
}
|
|
940
|
-
setTimeout(deny_connection,
|
|
946
|
+
setTimeout(deny_connection, 1000);
|
|
941
947
|
return;
|
|
942
948
|
}
|
|
943
949
|
// istanbul ignore next
|
|
@@ -948,7 +954,7 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
|
|
|
948
954
|
);
|
|
949
955
|
}
|
|
950
956
|
const channel = unused_channels[0];
|
|
951
|
-
errorLog(
|
|
957
|
+
errorLog(`${unused_channels.length} : Forcefully closing oldest channel that have no session: ${channel.hashKey}`);
|
|
952
958
|
channel.close(() => {
|
|
953
959
|
// istanbul ignore next
|
|
954
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);
|