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.
Files changed (51) hide show
  1. package/LICENSE +1 -1
  2. package/dist/addressSpace_accessor.js +1 -3
  3. package/dist/addressSpace_accessor.js.map +1 -1
  4. package/dist/base_server.js.map +1 -1
  5. package/dist/factory.js.map +1 -1
  6. package/dist/helper.js.map +1 -1
  7. package/dist/history_server_capabilities.js.map +1 -1
  8. package/dist/monitored_item.d.ts +6 -6
  9. package/dist/monitored_item.js +55 -38
  10. package/dist/monitored_item.js.map +1 -1
  11. package/dist/node_sampler.js.map +1 -1
  12. package/dist/opcua_server.d.ts +10 -4
  13. package/dist/opcua_server.js +18 -20
  14. package/dist/opcua_server.js.map +1 -1
  15. package/dist/queue.js.map +1 -1
  16. package/dist/register_server_manager.js +1 -1
  17. package/dist/register_server_manager.js.map +1 -1
  18. package/dist/register_server_manager_mdns_only.js +1 -1
  19. package/dist/register_server_manager_mdns_only.js.map +1 -1
  20. package/dist/server_capabilities.js +2 -2
  21. package/dist/server_capabilities.js.map +1 -1
  22. package/dist/server_end_point.d.ts +6 -0
  23. package/dist/server_end_point.js +16 -10
  24. package/dist/server_end_point.js.map +1 -1
  25. package/dist/server_engine.d.ts +3 -3
  26. package/dist/server_engine.js +3 -3
  27. package/dist/server_engine.js.map +1 -1
  28. package/dist/server_publish_engine.d.ts +1 -4
  29. package/dist/server_publish_engine.js +95 -91
  30. package/dist/server_publish_engine.js.map +1 -1
  31. package/dist/server_publish_engine_for_orphan_subscriptions.js +1 -1
  32. package/dist/server_publish_engine_for_orphan_subscriptions.js.map +1 -1
  33. package/dist/server_session.js +3 -1
  34. package/dist/server_session.js.map +1 -1
  35. package/dist/server_subscription.d.ts +22 -14
  36. package/dist/server_subscription.js +46 -21
  37. package/dist/server_subscription.js.map +1 -1
  38. package/dist/sessions_compatible_for_transfer.js.map +1 -1
  39. package/dist/user_manager.js.map +1 -1
  40. package/dist/user_manager_ua.js.map +1 -1
  41. package/dist/validate_filter.js.map +1 -1
  42. package/package.json +48 -47
  43. package/source/addressSpace_accessor.ts +2 -3
  44. package/source/monitored_item.ts +68 -56
  45. package/source/opcua_server.ts +39 -24
  46. package/source/server_end_point.ts +27 -8
  47. package/source/server_engine.ts +6 -5
  48. package/source/server_publish_engine.ts +14 -12
  49. package/source/server_publish_engine_for_orphan_subscriptions.ts +1 -1
  50. package/source/server_session.ts +2 -2
  51. package/source/server_subscription.ts +84 -34
@@ -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 safeGuardRegiter(monitoredItem: MonitoredItem) {
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 altereed !!!");
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 (!this.oldDataValue) {
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?: DataValue;
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
- private queue: QueueItem[];
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 = new DataValue({ statusCode: StatusCodes.BadDataUnavailable }); // unset initially
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 = undefined;
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: boolean, indexRange?: NumericRange): boolean {
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
- assert(dataValue !== this.oldDataValue, "recordValue expects different dataValue to be provided");
592
-
593
- assert(
594
- !dataValue.value || !this.oldDataValue || dataValue.value !== this.oldDataValue!.value,
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("propoped Value =>", dataValue?.toString());
664
- debugLog("propoped Value =>", dataValue == this.oldDataValue, dataValue.value === this.oldDataValue?.value);
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!.toString());
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!.value)) {
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(timestampsToReturn: TimestampsToReturn, monitoringParameters: MonitoringParameters): MonitoredItemModifyResult {
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 resendInitialValues(): Promise<void> {
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
- return this._start_sampling(true);
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
- const sessionContext = this.getSessionContext();
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!, (err: Error | null, newDataValue?: DataValue) => {
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
- // istanbull ignore next
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
- throw new Error("Internal Error");
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
- // aquire initial value from the variable/object not itself or from the last known value if we have
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 (!this.oldDataValue || this.oldDataValue.statusCode.value == StatusCodes.BadDataUnavailable.value) {
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 = new DataValue({ statusCode: StatusCodes.BadDataUnavailable });
1072
+ this.oldDataValue = badDataUnavailable;
1046
1073
  // istanbul ignore next
1047
1074
  if (doDebug) {
1048
- safeGuardRegiter(this);
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
- private _enqueue_value(dataValue: DataValue) {
1271
+ public _enqueue_value(dataValue: DataValue) {
1245
1272
  // preconditions:
1246
- if (doDebug) {
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
- safeGuardRegiter(this);
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
@@ -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
- protected getSession(authenticationToken: NodeId, activeOnly?: boolean): ServerSession | null {
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 = "INVALID SESSION !! ";
2345
- response = new ResponseClass({ responseHeader: { serviceResult: message.session_statusCode } });
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
- const results: any[] = subscriptionIds.map((subscriptionId: number) => actionToPerform(session, subscriptionId));
2449
-
2450
- // resolve potential pending promises ....
2451
- for (let i = 0; i < results.length; i++) {
2452
- if (results[i].then) {
2453
- results[i] = await results[i];
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: { defaultSecureTokenLifetime?: number; timeout?: number; host?:string }
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
- channel.dispose();
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.isOpened && !channel1.hasSession;
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("All channels are in used ! let cancel some");
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, 10);
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("Closing channel ", channel.hashKey);
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) {
@@ -338,7 +338,7 @@ export interface ServerEngineOptions {
338
338
  export interface CreateSessionOption {
339
339
  clientDescription?: ApplicationDescription;
340
340
  sessionTimeout?: number;
341
- server: IServerBase;
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: ServerEngineOptions) {
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: CreateSessionOption): ServerSession {
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);