node-opcua-server 2.81.0 → 2.82.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-opcua-server",
3
- "version": "2.81.0",
3
+ "version": "2.82.0",
4
4
  "description": "pure nodejs OPCUA SDK - module -server",
5
5
  "scripts": {
6
6
  "build": "tsc -b",
@@ -17,57 +17,57 @@
17
17
  "chalk": "4.1.2",
18
18
  "dequeue": "^1.0.5",
19
19
  "lodash": "4.17.21",
20
- "node-opcua-address-space": "2.81.0",
21
- "node-opcua-address-space-base": "2.81.0",
20
+ "node-opcua-address-space": "2.82.0",
21
+ "node-opcua-address-space-base": "2.82.0",
22
22
  "node-opcua-assert": "2.77.0",
23
- "node-opcua-basic-types": "2.81.0",
24
- "node-opcua-binary-stream": "2.77.0",
25
- "node-opcua-certificate-manager": "2.77.0",
26
- "node-opcua-client": "2.81.0",
27
- "node-opcua-client-dynamic-extension-object": "2.81.0",
28
- "node-opcua-common": "2.81.0",
23
+ "node-opcua-basic-types": "2.82.0",
24
+ "node-opcua-binary-stream": "2.82.0",
25
+ "node-opcua-certificate-manager": "2.82.0",
26
+ "node-opcua-client": "2.82.0",
27
+ "node-opcua-client-dynamic-extension-object": "2.82.0",
28
+ "node-opcua-common": "2.82.0",
29
29
  "node-opcua-constants": "2.77.0",
30
- "node-opcua-crypto": "^1.11.0",
31
- "node-opcua-data-model": "2.81.0",
32
- "node-opcua-data-value": "2.81.0",
33
- "node-opcua-date-time": "2.77.0",
34
- "node-opcua-debug": "2.77.0",
35
- "node-opcua-extension-object": "2.81.0",
36
- "node-opcua-factory": "2.81.0",
30
+ "node-opcua-crypto": "^1.12.0",
31
+ "node-opcua-data-model": "2.82.0",
32
+ "node-opcua-data-value": "2.82.0",
33
+ "node-opcua-date-time": "2.82.0",
34
+ "node-opcua-debug": "2.82.0",
35
+ "node-opcua-extension-object": "2.82.0",
36
+ "node-opcua-factory": "2.82.0",
37
37
  "node-opcua-hostname": "2.77.0",
38
- "node-opcua-nodeid": "2.81.0",
38
+ "node-opcua-nodeid": "2.82.0",
39
39
  "node-opcua-nodesets": "2.77.0",
40
- "node-opcua-numeric-range": "2.81.0",
41
- "node-opcua-object-registry": "2.77.0",
42
- "node-opcua-secure-channel": "2.81.0",
43
- "node-opcua-service-browse": "2.81.0",
44
- "node-opcua-service-call": "2.81.0",
45
- "node-opcua-service-discovery": "2.81.0",
46
- "node-opcua-service-endpoints": "2.81.0",
47
- "node-opcua-service-filter": "2.81.0",
48
- "node-opcua-service-history": "2.81.0",
49
- "node-opcua-service-node-management": "2.81.0",
50
- "node-opcua-service-query": "2.81.0",
51
- "node-opcua-service-read": "2.81.0",
52
- "node-opcua-service-register-node": "2.81.0",
53
- "node-opcua-service-secure-channel": "2.81.0",
54
- "node-opcua-service-session": "2.81.0",
55
- "node-opcua-service-subscription": "2.81.0",
56
- "node-opcua-service-translate-browse-path": "2.81.0",
57
- "node-opcua-service-write": "2.81.0",
58
- "node-opcua-status-code": "2.77.0",
59
- "node-opcua-types": "2.81.0",
60
- "node-opcua-utils": "2.77.0",
61
- "node-opcua-variant": "2.81.0",
40
+ "node-opcua-numeric-range": "2.82.0",
41
+ "node-opcua-object-registry": "2.82.0",
42
+ "node-opcua-secure-channel": "2.82.0",
43
+ "node-opcua-service-browse": "2.82.0",
44
+ "node-opcua-service-call": "2.82.0",
45
+ "node-opcua-service-discovery": "2.82.0",
46
+ "node-opcua-service-endpoints": "2.82.0",
47
+ "node-opcua-service-filter": "2.82.0",
48
+ "node-opcua-service-history": "2.82.0",
49
+ "node-opcua-service-node-management": "2.82.0",
50
+ "node-opcua-service-query": "2.82.0",
51
+ "node-opcua-service-read": "2.82.0",
52
+ "node-opcua-service-register-node": "2.82.0",
53
+ "node-opcua-service-secure-channel": "2.82.0",
54
+ "node-opcua-service-session": "2.82.0",
55
+ "node-opcua-service-subscription": "2.82.0",
56
+ "node-opcua-service-translate-browse-path": "2.82.0",
57
+ "node-opcua-service-write": "2.82.0",
58
+ "node-opcua-status-code": "2.82.0",
59
+ "node-opcua-types": "2.82.0",
60
+ "node-opcua-utils": "2.82.0",
61
+ "node-opcua-variant": "2.82.0",
62
62
  "thenify": "^3.3.1"
63
63
  },
64
64
  "devDependencies": {
65
- "node-opcua-data-access": "2.81.0",
66
- "node-opcua-leak-detector": "2.77.0",
67
- "node-opcua-pki": "^2.17.0",
65
+ "node-opcua-data-access": "2.82.0",
66
+ "node-opcua-leak-detector": "2.82.0",
67
+ "node-opcua-pki": "^2.18.0",
68
68
  "node-opcua-test-helpers": "2.77.0",
69
69
  "should": "^13.2.3",
70
- "sinon": "^14.0.0",
70
+ "sinon": "^14.0.1",
71
71
  "underscore": "^1.13.6"
72
72
  },
73
73
  "author": "Etienne Rossignon",
@@ -85,5 +85,5 @@
85
85
  "internet of things"
86
86
  ],
87
87
  "homepage": "http://node-opcua.github.io/",
88
- "gitHead": "fd89928bc4a4b9f31a6b6e5921f743e8284eba2b"
88
+ "gitHead": "2f66e1fe69c825305f94b15ca17cff066aa37496"
89
89
  }
@@ -20,7 +20,7 @@ import { coerceLocalizedText, LocalizedText } from "node-opcua-data-model";
20
20
  import { installPeriodicClockAdjustment, uninstallPeriodicClockAdjustment } from "node-opcua-date-time";
21
21
  import { checkDebugFlag, make_debugLog, make_errorLog } from "node-opcua-debug";
22
22
  import { displayTraceFromThisProjectOnly } from "node-opcua-debug";
23
- import { extractFullyQualifiedDomainName, getHostname, resolveFullyQualifiedDomainName } from "node-opcua-hostname";
23
+ import { extractFullyQualifiedDomainName, getFullyQualifiedDomainName, getHostname, resolveFullyQualifiedDomainName } from "node-opcua-hostname";
24
24
  import { Message, Response, ServerSecureChannelLayer, ServerSecureChannelParent } from "node-opcua-secure-channel";
25
25
  import { FindServersRequest, FindServersResponse } from "node-opcua-service-discovery";
26
26
  import { ApplicationType, GetEndpointsResponse } from "node-opcua-service-endpoints";
@@ -179,14 +179,26 @@ export class OPCUABaseServer extends OPCUASecureObject {
179
179
  if (fs.existsSync(this.certificateFile)) {
180
180
  return;
181
181
  }
182
+
183
+ // collect all hostnames
184
+ const hostnames = [];
185
+ for (const e of this.endpoints) {
186
+ for( const ee of e.endpointDescriptions()) {
187
+ /* to do */
188
+ }
189
+ }
190
+
182
191
  const lockfile = path.join(this.certificateFile + ".lock");
183
192
  await withLock({ lockfile }, async () => {
184
193
  if (!fs.existsSync(this.certificateFile)) {
185
194
  const applicationUri = this.serverInfo.applicationUri!;
195
+ const fqdn = getFullyQualifiedDomainName();
186
196
  const hostname = getHostname();
197
+ const dns = [... new Set([fqdn, hostname])];
198
+
187
199
  await this.serverCertificateManager.createSelfSignedCertificate({
188
200
  applicationUri,
189
- dns: [hostname],
201
+ dns,
190
202
  // ip: await getIpAddresses(),
191
203
  outputFile: this.certificateFile,
192
204
 
@@ -198,7 +210,7 @@ export class OPCUABaseServer extends OPCUASecureObject {
198
210
  }
199
211
  });
200
212
  }
201
-
213
+
202
214
  public async initializeCM(): Promise<void> {
203
215
  await this.serverCertificateManager.initialize();
204
216
  await this.createDefaultCertificate();
@@ -40,7 +40,7 @@ import {
40
40
  isOutsideDeadbandPercent,
41
41
  PseudoRange
42
42
  } from "node-opcua-service-subscription";
43
- import { StatusCode, StatusCodes } from "node-opcua-status-code";
43
+ import { CallbackT, StatusCode, StatusCodes } from "node-opcua-status-code";
44
44
  import {
45
45
  DataChangeNotification,
46
46
  EventFieldList,
@@ -56,7 +56,6 @@ import { appendToTimer, removeFromTimer } from "./node_sampler";
56
56
  import { validateFilter } from "./validate_filter";
57
57
  import { checkWhereClauseOnAdressSpace } from "./filter/check_where_clause_on_address_space";
58
58
  import { SamplingFunc } from "./sampling_func";
59
- import { registerNodePromoter } from "node-opcua-address-space/source/loader/register_node_promoter";
60
59
 
61
60
  export type QueueItem = MonitoredItemNotification | EventFieldList;
62
61
 
@@ -473,7 +472,7 @@ export class MonitoredItem extends EventEmitter {
473
472
  // sampling interval.
474
473
  const recordInitialValue =
475
474
  old_monitoringMode === MonitoringMode.Invalid || old_monitoringMode === MonitoringMode.Disabled;
476
-
475
+ const installEventHandler = old_monitoringMode === MonitoringMode.Invalid;
477
476
  this._start_sampling(recordInitialValue);
478
477
  }
479
478
  }
@@ -555,11 +554,16 @@ export class MonitoredItem extends EventEmitter {
555
554
  *
556
555
  */
557
556
  public recordValue(dataValue: DataValue, skipChangeTest: boolean, indexRange?: NumericRange): void {
558
- assert(dataValue instanceof DataValue);
557
+
558
+ if (!this.itemToMonitor) {
559
+ // we must have a valid itemToMonitor(have this monitoredItem been disposed already ?)
560
+ return;
561
+ }
562
+
559
563
  assert(dataValue !== this.oldDataValue, "recordValue expects different dataValue to be provided");
560
564
 
561
565
  assert(
562
- !dataValue.value || dataValue.value !== this.oldDataValue!.value,
566
+ !dataValue.value || !this.oldDataValue || dataValue.value !== this.oldDataValue!.value,
563
567
  "recordValue expects different dataValue.value to be provided"
564
568
  );
565
569
 
@@ -581,7 +585,6 @@ export class MonitoredItem extends EventEmitter {
581
585
  }
582
586
  }
583
587
 
584
- assert(this.itemToMonitor, "must have a valid itemToMonitor(have this monitoredItem been disposed already ?");
585
588
  // extract the range that we are interested with
586
589
  dataValue = extractRange(dataValue, this.itemToMonitor.indexRange);
587
590
 
@@ -760,7 +763,7 @@ export class MonitoredItem extends EventEmitter {
760
763
 
761
764
  this._adjust_queue_to_match_new_queue_size();
762
765
 
763
- this._adjust_sampling(old_samplingInterval);
766
+ this._adjustSampling(old_samplingInterval);
764
767
 
765
768
  if (monitoringParameters.filter) {
766
769
  const statusCodeFilter = validateFilter(monitoringParameters.filter, this.itemToMonitor, this.node!);
@@ -784,12 +787,11 @@ export class MonitoredItem extends EventEmitter {
784
787
  }
785
788
 
786
789
  public async resendInitialValues(): Promise<void> {
787
- // tte first Publish response(s) after the TransferSubscriptions call shall contain the current values of all
790
+ // the first Publish response(s) after the TransferSubscriptions call shall contain the current values of all
788
791
  // Monitored Items in the Subscription where the Monitoring Mode is set to Reporting.
789
792
  // the first Publish response after the TransferSubscriptions call shall contain only the value changes since
790
793
  // the last Publish response was sent.
791
794
  // This parameter only applies to MonitoredItems used for monitoring Attribute changes.
792
- this._stop_sampling();
793
795
  return this._start_sampling(true);
794
796
  }
795
797
 
@@ -804,10 +806,13 @@ export class MonitoredItem extends EventEmitter {
804
806
  /**
805
807
  * @method _on_sampling_timer
806
808
  * @private
807
- * request
808
- *
809
809
  */
810
810
  private _on_sampling_timer() {
811
+
812
+ if (this.monitoringMode === MonitoringMode.Disabled) {
813
+ return;
814
+ }
815
+
811
816
  const sessionContext = this.getSessionContext();
812
817
  if (!sessionContext) {
813
818
  return;
@@ -971,13 +976,34 @@ export class MonitoredItem extends EventEmitter {
971
976
  return this.$subscription.$session;
972
977
  }
973
978
 
974
- private _start_sampling(recordInitialValue?: boolean): void {
979
+ private _start_sampling(recordInitialValue: boolean): void {
975
980
  // istanbul ignore next
976
981
  if (!this.node) {
977
982
  throw new Error("Internal Error");
978
983
  }
979
984
  setImmediate(() => this.__start_sampling(recordInitialValue));
980
985
  }
986
+
987
+ private __acquireInitialValue(sessionContext: ISessionContext, callback: CallbackT<DataValue>): void {
988
+ // aquire initial value from the variable/object not itself or from the last known value if we have
989
+ // one already
990
+ assert(this.itemToMonitor.attributeId === AttributeIds.Value);
991
+ assert(this.node);
992
+ if (this.node?.nodeClass !== NodeClass.Variable) {
993
+ return callback(new Error("Invalid "));
994
+ }
995
+ const variable = (this.node as UAVariable);
996
+ if (!this.oldDataValue || this.oldDataValue.statusCode == StatusCodes.BadDataUnavailable) {
997
+ variable.readValueAsync(sessionContext, (err: Error | null, dataValue?: DataValue) => {
998
+ callback(err, dataValue);
999
+ });
1000
+ } else {
1001
+ const o = this.oldDataValue;
1002
+ this.oldDataValue = new DataValue({ statusCode: StatusCodes.BadDataUnavailable });
1003
+ callback(null, o);
1004
+ }
1005
+
1006
+ }
981
1007
  private __start_sampling(recordInitialValue?: boolean): void {
982
1008
  // istanbul ignore next
983
1009
  if (!this.node) {
@@ -989,8 +1015,6 @@ export class MonitoredItem extends EventEmitter {
989
1015
  if (!sessionContext) {
990
1016
  return;
991
1017
  }
992
- // make sure oldDataValue is scrapped so first data recording can happen
993
- this.oldDataValue = new DataValue({ statusCode: StatusCodes.BadDataUnavailable }); // unset initially
994
1018
 
995
1019
  this._stop_sampling();
996
1020
 
@@ -999,10 +1023,11 @@ export class MonitoredItem extends EventEmitter {
999
1023
  if (doDebug) {
1000
1024
  debugLog("xxxxxx monitoring EventNotifier on", this.node.nodeId.toString(), this.node.browseName.toString());
1001
1025
  }
1002
- // we are monitoring OPCUA Event
1003
- this._on_opcua_event_received_callback = this._on_opcua_event.bind(this);
1004
- this.node.on("event", this._on_opcua_event_received_callback);
1005
-
1026
+ if (!this._on_opcua_event_received_callback) {
1027
+ // we are monitoring OPCUA Event
1028
+ this._on_opcua_event_received_callback = this._on_opcua_event.bind(this);
1029
+ this.node.on("event", this._on_opcua_event_received_callback);
1030
+ }
1006
1031
  return;
1007
1032
  }
1008
1033
  if (this.itemToMonitor.attributeId !== AttributeIds.Value) {
@@ -1010,10 +1035,11 @@ export class MonitoredItem extends EventEmitter {
1010
1035
  this.samplingInterval = 0; // turned to exception-based regardless of requested sampling interval
1011
1036
 
1012
1037
  // non value attribute only react on value change
1013
- this._attribute_changed_callback = this._on_value_changed.bind(this);
1014
- const event_name = makeAttributeEventName(this.itemToMonitor.attributeId);
1015
-
1016
- this.node.on(event_name, this._attribute_changed_callback);
1038
+ if (!this._attribute_changed_callback) {
1039
+ this._attribute_changed_callback = this._on_value_changed.bind(this);
1040
+ const event_name = makeAttributeEventName(this.itemToMonitor.attributeId);
1041
+ this.node.on(event_name, this._attribute_changed_callback);
1042
+ }
1017
1043
 
1018
1044
  if (recordInitialValue) {
1019
1045
  // read initial value
@@ -1026,29 +1052,32 @@ export class MonitoredItem extends EventEmitter {
1026
1052
  if (this.samplingInterval === 0) {
1027
1053
  // we have a exception-based dataItem : event based model, so we do not need a timer
1028
1054
  // rather , we setup the "value_changed_event";
1029
- this._value_changed_callback = this._on_value_changed.bind(this);
1030
- this._semantic_changed_callback = this._on_semantic_changed.bind(this);
1031
-
1032
- this.node.on("value_changed", this._value_changed_callback);
1033
- this.node.on("semantic_changed", this._semantic_changed_callback);
1055
+ if (!this._value_changed_callback) {
1056
+ assert(!this._semantic_changed_callback);
1057
+ this._value_changed_callback = this._on_value_changed.bind(this);
1058
+ this._semantic_changed_callback = this._on_semantic_changed.bind(this);
1059
+ this.node.on("value_changed", this._value_changed_callback);
1060
+ this.node.on("semantic_changed", this._semantic_changed_callback);
1061
+ }
1034
1062
 
1035
1063
  // initiate first read
1036
1064
  if (recordInitialValue) {
1037
- /* await */ new Promise<void>((resolve: () => void) => {
1038
- (this.node as UAVariable).readValueAsync(sessionContext, (err: Error | null, dataValue?: DataValue) => {
1039
- if (!err && dataValue) {
1040
- this.recordValue(dataValue, true);
1041
- }
1042
- resolve();
1043
- });
1065
+ this.__acquireInitialValue(sessionContext, (err: Error | null, dataValue?: DataValue) => {
1066
+ if (!err && dataValue) {
1067
+ this.recordValue(dataValue, true);
1068
+ }
1044
1069
  });
1045
1070
  }
1046
1071
  } else {
1047
- this._set_timer();
1048
1072
  if (recordInitialValue) {
1049
- setImmediate(() => {
1050
- this._on_sampling_timer();
1073
+ this.__acquireInitialValue(sessionContext, (err: Error | null, dataValue?: DataValue) => {
1074
+ if (!err && dataValue) {
1075
+ this.recordValue(dataValue, true);
1076
+ }
1077
+ this._set_timer();
1051
1078
  });
1079
+ } else {
1080
+ this._set_timer();
1052
1081
  }
1053
1082
  }
1054
1083
  }
@@ -1065,9 +1094,10 @@ export class MonitoredItem extends EventEmitter {
1065
1094
  // that the data item is exception-based rather than being sampled at some period. An exception-based
1066
1095
  // model means that the underlying system does not require sampling and reports data changes.
1067
1096
  if (this.node && this.node.nodeClass === NodeClass.Variable) {
1097
+ const variable = (this.node as UAVariable);
1068
1098
  this.samplingInterval = _adjust_sampling_interval(
1069
1099
  monitoredParameters.samplingInterval,
1070
- this.node ? (this.node as UAVariable).minimumSamplingInterval : 0
1100
+ variable.minimumSamplingInterval || 0
1071
1101
  );
1072
1102
  } else {
1073
1103
  this.samplingInterval = _adjust_sampling_interval(monitoredParameters.samplingInterval, 0);
@@ -1197,11 +1227,11 @@ export class MonitoredItem extends EventEmitter {
1197
1227
  ) {
1198
1228
  throw new Error(
1199
1229
  "dataValue.value.value cannot be the same object twice! " +
1200
- this.node!.browseName.toString() +
1201
- " " +
1202
- dataValue.toString() +
1203
- " " +
1204
- chalk.cyan(this.oldDataValue.toString())
1230
+ this.node!.browseName.toString() +
1231
+ " " +
1232
+ dataValue.toString() +
1233
+ " " +
1234
+ chalk.cyan(this.oldDataValue.toString())
1205
1235
  );
1206
1236
  }
1207
1237
 
@@ -1248,6 +1278,13 @@ export class MonitoredItem extends EventEmitter {
1248
1278
  }
1249
1279
 
1250
1280
  private _set_timer() {
1281
+
1282
+ if(!this.itemToMonitor) {
1283
+ // item has already been deleted
1284
+ // so do not create the timer !
1285
+ return;
1286
+ }
1287
+
1251
1288
  assert(this.samplingInterval >= MonitoredItem.minimumSamplingInterval);
1252
1289
  assert(!this._samplingId);
1253
1290
 
@@ -1289,7 +1326,7 @@ export class MonitoredItem extends EventEmitter {
1289
1326
  assert(this.queue.length <= this.queueSize);
1290
1327
  }
1291
1328
 
1292
- private _adjust_sampling(old_samplingInterval: number) {
1329
+ private _adjustSampling(old_samplingInterval: number) {
1293
1330
  if (old_samplingInterval !== this.samplingInterval) {
1294
1331
  this._start_sampling(false);
1295
1332
  }
@@ -3,6 +3,7 @@
3
3
  */
4
4
  import { assert } from "node-opcua-assert";
5
5
  import { checkDebugFlag, make_debugLog } from "node-opcua-debug";
6
+ import { MonitoringMode } from "node-opcua-types";
6
7
  import { hrtime } from "node-opcua-utils";
7
8
 
8
9
  const debugLog = make_debugLog(__filename);
@@ -18,6 +19,11 @@ interface MonitoredItemPriv {
18
19
  }
19
20
  function sampleMonitoredItem(monitoredItem: MonitoredItem) {
20
21
  const _monitoredItem = monitoredItem as unknown as MonitoredItemPriv;
22
+
23
+ if (monitoredItem.monitoringMode === MonitoringMode.Disabled) {
24
+ return;
25
+ }
26
+
21
27
  setImmediate(() => {
22
28
  _monitoredItem._on_sampling_timer();
23
29
  });
@@ -1154,7 +1154,6 @@ export class OPCUAServer extends OPCUABaseServer {
1154
1154
  securityModes: options.securityModes,
1155
1155
  securityPolicies: options.securityPolicies
1156
1156
  });
1157
-
1158
1157
  // todo should self.serverInfo.productUri match self.engine.buildInfo.productUri ?
1159
1158
  for (const endpointOptions of endpointDefinitions) {
1160
1159
  const endPoint = this.createEndpointDescriptions(options!, endpointOptions);
@@ -96,7 +96,6 @@ import { ServerSession } from "./server_session";
96
96
  import { Subscription } from "./server_subscription";
97
97
  import { sessionsCompatibleForTransfer } from "./sessions_compatible_for_transfer";
98
98
  import { OPCUAServerOptions } from "./opcua_server";
99
- import { IUserManager } from "node-opcua-address-space/source";
100
99
 
101
100
  const debugLog = make_debugLog(__filename);
102
101
  const errorLog = make_errorLog(__filename);
@@ -123,25 +122,13 @@ function setSubscriptionDurable(
123
122
  ) {
124
123
  // see https://reference.opcfoundation.org/v104/Core/docs/Part5/9.3/
125
124
  // https://reference.opcfoundation.org/v104/Core/docs/Part4/6.8/
126
- assert(Array.isArray(inputArguments));
127
125
  assert(typeof callback === "function");
128
- assert(Object.prototype.hasOwnProperty.call(context, "session"), " expecting a session id in the context object");
129
- const session = context.session as ServerSession;
130
- if (!session) {
131
- return callback(null, { statusCode: StatusCodes.BadInternalError });
132
- }
133
- const subscriptionId = inputArguments[0].value as UInt32;
134
- const lifetimeInHours = inputArguments[1].value as UInt32;
135
126
 
136
- const subscription = session.getSubscription(subscriptionId);
137
- if (!subscription) {
138
- // subscription may belongs to a different session that ours
139
- if (this.findSubscription(subscriptionId)) {
140
- // if yes, then access to Subscription data should be denied
141
- return callback(null, { statusCode: StatusCodes.BadUserAccessDenied });
142
- }
143
- return callback(null, { statusCode: StatusCodes.BadSubscriptionIdInvalid });
144
- }
127
+ const data = _getSubscription.call(this, inputArguments, context);
128
+ if (data.statusCode) return callback(null, { statusCode: data.statusCode });
129
+ const { subscription } = data;
130
+
131
+ const lifetimeInHours = inputArguments[1].value as UInt32;
145
132
  if (subscription.monitoredItemCount > 0) {
146
133
  // This is returned when a Subscription already contains MonitoredItems.
147
134
  return callback(null, { statusCode: StatusCodes.BadInvalidState });
@@ -199,8 +186,7 @@ function setSubscriptionDurable(
199
186
  callback(null, callMethodResult);
200
187
  }
201
188
 
202
- // binding methods
203
- function getMonitoredItemsId(
189
+ function requestServerStateChange(
204
190
  this: ServerEngine,
205
191
  inputArguments: Variant[],
206
192
  context: ISessionContext,
@@ -209,23 +195,70 @@ function getMonitoredItemsId(
209
195
  assert(Array.isArray(inputArguments));
210
196
  assert(typeof callback === "function");
211
197
  assert(Object.prototype.hasOwnProperty.call(context, "session"), " expecting a session id in the context object");
212
-
213
198
  const session = context.session as ServerSession;
214
199
  if (!session) {
215
200
  return callback(null, { statusCode: StatusCodes.BadInternalError });
216
201
  }
217
202
 
203
+ return callback(null, { statusCode: StatusCodes.BadNotImplemented });
204
+ }
205
+
206
+ function _getSubscription(
207
+ this: ServerEngine,
208
+ inputArguments: Variant[],
209
+ context: ISessionContext
210
+ ): { subscription: Subscription, statusCode?: never } | { statusCode: StatusCode, subscription?: never } {
211
+ assert(Array.isArray(inputArguments));
212
+ assert(Object.prototype.hasOwnProperty.call(context, "session"), " expecting a session id in the context object");
213
+ const session = context.session as ServerSession;
214
+ if (!session) {
215
+ return { statusCode: StatusCodes.BadInternalError };
216
+ }
218
217
  const subscriptionId = inputArguments[0].value;
219
218
  const subscription = session.getSubscription(subscriptionId);
220
219
  if (!subscription) {
221
220
  // subscription may belongs to a different session that ours
222
221
  if (this.findSubscription(subscriptionId)) {
223
222
  // if yes, then access to Subscription data should be denied
224
- return callback(null, { statusCode: StatusCodes.BadUserAccessDenied });
223
+ return { statusCode: StatusCodes.BadUserAccessDenied };
225
224
  }
226
-
227
- return callback(null, { statusCode: StatusCodes.BadSubscriptionIdInvalid });
225
+ return { statusCode: StatusCodes.BadSubscriptionIdInvalid };
228
226
  }
227
+ return { subscription };
228
+
229
+ }
230
+ function resendData(
231
+ this: ServerEngine,
232
+ inputArguments: Variant[],
233
+ context: ISessionContext,
234
+ callback: CallbackT<CallMethodResultOptions>
235
+ ): void {
236
+ assert(typeof callback === "function");
237
+
238
+ const data = _getSubscription.call(this, inputArguments, context);
239
+ if (data.statusCode) return callback(null, { statusCode: data.statusCode });
240
+ const { subscription } = data;
241
+
242
+ subscription.resendInitialValues().then(() => {
243
+ callback(null, { statusCode: StatusCodes.Good });
244
+ }).catch((err) => callback(err));
245
+
246
+ }
247
+
248
+
249
+ // binding methods
250
+ function getMonitoredItemsId(
251
+ this: ServerEngine,
252
+ inputArguments: Variant[],
253
+ context: ISessionContext,
254
+ callback: CallbackT<CallMethodResultOptions>
255
+ ) {
256
+ assert(typeof callback === "function");
257
+
258
+ const data = _getSubscription.call(this, inputArguments, context);
259
+ if (data.statusCode) return callback(null, { statusCode: data.statusCode });
260
+ const { subscription } = data;
261
+
229
262
  const result = subscription.getMonitoredItems();
230
263
  assert(result.statusCode);
231
264
  assert(result.serverHandles.length === result.clientHandles.length);
@@ -1202,6 +1235,8 @@ export class ServerEngine extends EventEmitter {
1202
1235
 
1203
1236
  this.__internal_bindMethod(makeNodeId(MethodIds.Server_GetMonitoredItems), getMonitoredItemsId.bind(this));
1204
1237
  this.__internal_bindMethod(makeNodeId(MethodIds.Server_SetSubscriptionDurable), setSubscriptionDurable.bind(this));
1238
+ this.__internal_bindMethod(makeNodeId(MethodIds.Server_ResendData), resendData.bind(this));
1239
+ this.__internal_bindMethod(makeNodeId(MethodIds.Server_RequestServerStateChange), requestServerStateChange.bind(this));
1205
1240
 
1206
1241
  // fix getMonitoredItems.outputArguments arrayDimensions
1207
1242
  const fixGetMonitoredItemArgs = () => {
@@ -1415,7 +1450,7 @@ export class ServerEngine extends EventEmitter {
1415
1450
  (!dataValue.serverTimestamp || dataValue.serverTimestamp.getTime() === minOPCUADate.getTime())
1416
1451
  ) {
1417
1452
  dataValue.serverTimestamp = context.currentTime.timestamp;
1418
- dataValue.sourcePicoseconds = 0; // context.currentTime.picosecond // do we really need picosecond here ? this would inflate binary data
1453
+ dataValue.serverPicoseconds = 0; // context.currentTime.picoseconds;
1419
1454
  }
1420
1455
  dataValues.push(dataValue);
1421
1456
  }
@@ -2246,8 +2281,8 @@ export class ServerEngine extends EventEmitter {
2246
2281
  } else {
2247
2282
  warningLog(
2248
2283
  chalk.yellow("WARNING: cannot bind a method with id ") +
2249
- chalk.cyan(nodeId.toString()) +
2250
- chalk.yellow(". please check your nodeset.xml file or add this node programmatically")
2284
+ chalk.cyan(nodeId.toString()) +
2285
+ chalk.yellow(". please check your nodeset.xml file or add this node programmatically")
2251
2286
  );
2252
2287
  warningLog(traceFromThisProjectOnly());
2253
2288
  }
@@ -6,7 +6,7 @@
6
6
  import { EventEmitter } from "events";
7
7
  import * as chalk from "chalk";
8
8
 
9
- import { SessionContext,AddressSpace, BaseNode, Duration, UAObjectType } from "node-opcua-address-space";
9
+ import { SessionContext, AddressSpace, BaseNode, Duration, UAObjectType } from "node-opcua-address-space";
10
10
  import { assert } from "node-opcua-assert";
11
11
  import { Byte, UInt32 } from "node-opcua-basic-types";
12
12
  import { SubscriptionDiagnosticsDataType } from "node-opcua-common";
@@ -81,19 +81,22 @@ function _adjust_maxKeepAliveCount(maxKeepAliveCount?: number /*,publishingInter
81
81
  return maxKeepAliveCount;
82
82
  }
83
83
 
84
+ const MaxUint32 = 0xFFFFFFFF;
85
+
84
86
  function _adjust_lifeTimeCount(lifeTimeCount: number, maxKeepAliveCount: number, publishingInterval: number): number {
85
87
  lifeTimeCount = lifeTimeCount || 1;
86
88
 
87
- // let's make sure that lifeTimeCount is at least three time maxKeepAliveCount
88
- // Note : the specs say ( part 3 - CreateSubscriptionParameter )
89
- // "The lifetime count shall be a minimum of three times the keep keep-alive count."
90
- lifeTimeCount = Math.max(lifeTimeCount, maxKeepAliveCount * 3);
91
-
92
89
  const minTicks = Math.ceil(Subscription.minimumLifetimeDuration / publishingInterval);
93
90
  const maxTicks = Math.floor(Subscription.maximumLifetimeDuration / publishingInterval);
94
91
 
95
92
  lifeTimeCount = Math.max(minTicks, lifeTimeCount);
96
93
  lifeTimeCount = Math.min(maxTicks, lifeTimeCount);
94
+
95
+ // let's make sure that lifeTimeCount is at least three time maxKeepAliveCount
96
+ // Note : the specs say ( part 3 - CreateSubscriptionParameter )
97
+ // "The lifetime count shall be a minimum of three times the keep keep-alive count."
98
+ lifeTimeCount = Math.max(lifeTimeCount, Math.min(maxKeepAliveCount * 3, MaxUint32));
99
+
97
100
  return lifeTimeCount;
98
101
  }
99
102
 
@@ -1362,7 +1365,7 @@ export class Subscription extends EventEmitter {
1362
1365
  const availableSequenceNumbers = this.getAvailableSequenceNumbers();
1363
1366
  assert(
1364
1367
  !response.notificationMessage ||
1365
- availableSequenceNumbers[availableSequenceNumbers.length - 1] === response.notificationMessage.sequenceNumber
1368
+ availableSequenceNumbers[availableSequenceNumbers.length - 1] === response.notificationMessage.sequenceNumber
1366
1369
  );
1367
1370
  response.availableSequenceNumbers = availableSequenceNumbers;
1368
1371
 
@@ -1439,7 +1442,7 @@ export class Subscription extends EventEmitter {
1439
1442
  } else {
1440
1443
  debugLog(
1441
1444
  " -> subscription.state === LATE , " +
1442
- "because keepAlive Response cannot be send due to lack of PublishRequest"
1445
+ "because keepAlive Response cannot be send due to lack of PublishRequest"
1443
1446
  );
1444
1447
  if (this.messageSent || this.keepAliveCounterHasExpired) {
1445
1448
  this.state = SubscriptionState.LATE;