node-opcua-server 2.99.0 → 2.101.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.
@@ -168,7 +168,7 @@ import { RegisterServerManager } from "./register_server_manager";
168
168
  import { RegisterServerManagerHidden } from "./register_server_manager_hidden";
169
169
  import { RegisterServerManagerMDNSONLY } from "./register_server_manager_mdns_only";
170
170
  import { ServerCapabilitiesOptions } from "./server_capabilities";
171
- import { OPCUAServerEndPoint } from "./server_end_point";
171
+ import { EndpointDescriptionEx, OPCUAServerEndPoint } from "./server_end_point";
172
172
  import { ClosingReason, CreateSessionOption, ServerEngine } from "./server_engine";
173
173
  import { ServerSession } from "./server_session";
174
174
  import { CreateMonitoredItemHook, DeleteMonitoredItemHook, Subscription } from "./server_subscription";
@@ -642,10 +642,10 @@ function validate_security_endpoint(
642
642
  if (endpoints.length === 0) {
643
643
  // we have a UrlMismatch here
644
644
  const ua_server = server.engine.addressSpace!.rootFolder.objects.server;
645
+ errorLog("Cannot find suitable endpoints in available endpoints. endpointUri =", request.endpointUrl);
645
646
  ua_server.raiseEvent("AuditUrlMismatchEventType", {
646
647
  endpointUrl: { dataType: DataType.String, value: request.endpointUrl }
647
648
  });
648
- debugLog("Cannot find endpoint in available endpoints with endpointUri", request.endpointUrl);
649
649
  if (OPCUAServer.requestExactEndpointUrl) {
650
650
  return { errCode: StatusCodes.BadServiceUnsupported };
651
651
  } else {
@@ -653,7 +653,7 @@ function validate_security_endpoint(
653
653
  }
654
654
  }
655
655
  // ignore restricted endpoints
656
- endpoints = endpoints.filter((e: EndpointDescription) => !(e as any).restricted);
656
+ endpoints = endpoints.filter((e: EndpointDescription) => !(e as EndpointDescriptionEx).restricted);
657
657
 
658
658
  const endpoints_matching_security_mode = endpoints.filter((e: EndpointDescription) => {
659
659
  return e.securityMode === channel.securityMode;
@@ -1148,7 +1148,7 @@ export class OPCUAServer extends OPCUABaseServer {
1148
1148
  const hostname = getFullyQualifiedDomainName();
1149
1149
 
1150
1150
  endpointDefinitions.push({
1151
- port: options.port || 26543,
1151
+ port: options.port === undefined ? 26543 : options.port,
1152
1152
 
1153
1153
  allowAnonymous: options.allowAnonymous,
1154
1154
  alternateHostname: options.alternateHostname,
@@ -3526,7 +3526,7 @@ export class OPCUAServer extends OPCUABaseServer {
3526
3526
  }
3527
3527
  const hostname = getFullyQualifiedDomainName();
3528
3528
  endpointOptions.hostname = endpointOptions.hostname || hostname;
3529
- endpointOptions.port = endpointOptions.port || 26543;
3529
+ endpointOptions.port = endpointOptions.port === undefined ? 26543 : endpointOptions.port ;
3530
3530
 
3531
3531
  /* istanbul ignore next */
3532
3532
  if (
@@ -43,10 +43,12 @@ export class RegisterServerManagerMDNSONLY extends EventEmitter implements IRegi
43
43
  }
44
44
  assert(this.server instanceof OPCUABaseServer);
45
45
 
46
+ const host = "TODO-find how to extract hostname";
46
47
  this.bonjour.announcedOnMulticastSubnetWithCallback({
47
48
  capabilities: this.server.capabilitiesForMDNS,
48
49
  name: this.server.serverInfo.applicationUri!,
49
50
  path: "/", // <- to do
51
+ host,
50
52
  port: this.server.endpoints[0].port
51
53
  }, ()=>{
52
54
  this.emit("serverRegistered");
@@ -9,8 +9,8 @@ import * as chalk from "chalk";
9
9
  import * as async from "async";
10
10
 
11
11
  import { assert } from "node-opcua-assert";
12
- import { ICertificateManager, OPCUACertificateManager } from "node-opcua-certificate-manager";
13
- import { Certificate, convertPEMtoDER, makeSHA1Thumbprint, PrivateKey, PrivateKeyPEM, split_der } from "node-opcua-crypto";
12
+ import { OPCUACertificateManager } from "node-opcua-certificate-manager";
13
+ import { Certificate, makeSHA1Thumbprint, PrivateKey, split_der } from "node-opcua-crypto";
14
14
  import { checkDebugFlag, make_debugLog, make_errorLog, make_warningLog } from "node-opcua-debug";
15
15
  import { getFullyQualifiedDomainName, resolveFullyQualifiedDomainName } from "node-opcua-hostname";
16
16
  import {
@@ -20,7 +20,6 @@ import {
20
20
  ServerSecureChannelLayer,
21
21
  ServerSecureChannelParent,
22
22
  toURI,
23
- AsymmetricAlgorithmSecurityHeader,
24
23
  IServerSessionBase,
25
24
  Message
26
25
  } from "node-opcua-secure-channel";
@@ -83,9 +82,8 @@ function extractChannelData(channel: ServerSecureChannelLayer): IChannelData {
83
82
 
84
83
  function dumpChannelInfo(channels: ServerSecureChannelLayer[]): void {
85
84
  function d(s: IServerSessionBase) {
86
- return `[ status=${s.status} lastSeen=${s.clientLastContactTime.toFixed(0)}ms sessionName=${s.sessionName} timeout=${
87
- s.sessionTimeout
88
- } ]`;
85
+ return `[ status=${s.status} lastSeen=${s.clientLastContactTime.toFixed(0)}ms sessionName=${s.sessionName} timeout=${s.sessionTimeout
86
+ } ]`;
89
87
  }
90
88
  function dumpChannel(channel: ServerSecureChannelLayer): void {
91
89
  console.log("------------------------------------------------------");
@@ -381,7 +379,6 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
381
379
  }
382
380
  //
383
381
 
384
- const port = this.port;
385
382
 
386
383
  // resource Path is a string added at the end of the url such as "/UA/Server"
387
384
  const resourcePath = (options.resourcePath || "").replace(/\\/g, "/");
@@ -389,7 +386,7 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
389
386
  assert(resourcePath.length === 0 || resourcePath.charAt(0) === "/", "resourcePath should start with /");
390
387
 
391
388
  const hostname = options.hostname || getFullyQualifiedDomainName();
392
- const endpointUrl = `opc.tcp://${hostname}:${port}${resourcePath}`;
389
+ const endpointUrl = `opc.tcp://${hostname}:${this.port}${resourcePath}`;
393
390
 
394
391
  const endpoint_desc = this.getEndpointDescription(securityMode, securityPolicy, endpointUrl);
395
392
 
@@ -404,10 +401,7 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
404
401
  this._endpoints.push(
405
402
  _makeEndpointDescription({
406
403
  collection: this._policy_deduplicator,
407
- endpointUrl,
408
404
  hostname,
409
- port,
410
-
411
405
  server: this.serverInfo,
412
406
  serverCertificateChain: this.getCertificateChain(),
413
407
 
@@ -421,7 +415,7 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
421
415
  securityPolicies: options.securityPolicies || [],
422
416
 
423
417
  userTokenTypes
424
- })
418
+ }, this)
425
419
  );
426
420
  }
427
421
 
@@ -511,16 +505,23 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
511
505
  this._server!.on("listening", () => {
512
506
  debugLog("server is listening");
513
507
  });
508
+
514
509
  this._server!.listen(
515
510
  this.port,
516
- /*"::",*/ (err?: Error) => {
511
+ /*"::",*/(err?: Error) => {
517
512
  // 'listening' listener
518
513
  debugLog(chalk.green.bold("LISTENING TO PORT "), this.port, "err ", err);
519
514
  assert(!err, " cannot listen to port ");
520
515
  this._started = true;
516
+ if (!this.port) {
517
+ const add = this._server!.address()!;
518
+ this.port = typeof add !== "string" ? add.port : this.port;
519
+
520
+ }
521
521
  this._end_listen();
522
522
  }
523
523
  );
524
+
524
525
  }
525
526
 
526
527
  public killClientSockets(callback: (err?: Error) => void): void {
@@ -691,7 +692,7 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
691
692
  debugLog(
692
693
  chalk.bgWhite.cyan(
693
694
  "OPCUAServerEndPoint#_on_client_connection " +
694
- "SERVER END POINT IS PROBABLY SHUTTING DOWN !!! - Connection is refused"
695
+ "SERVER END POINT IS PROBABLY SHUTTING DOWN !!! - Connection is refused"
695
696
  )
696
697
  );
697
698
  socket.end();
@@ -701,7 +702,7 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
701
702
  console.log(
702
703
  chalk.bgWhite.cyan(
703
704
  "OPCUAServerEndPoint#_on_client_connection " +
704
- "The maximum number of connection has been reached - Connection is refused"
705
+ "The maximum number of connection has been reached - Connection is refused"
705
706
  )
706
707
  );
707
708
  const reason = "maxConnections reached (" + this.maxConnections + ")";
@@ -937,19 +938,11 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
937
938
  }
938
939
 
939
940
  interface MakeEndpointDescriptionOptions {
940
- /**
941
- * port number s
942
- */
943
- port: number;
944
941
 
945
942
  /**
946
943
  * @default default hostname (default value will be full qualified domain name)
947
944
  */
948
945
  hostname: string;
949
- /**
950
- *
951
- */
952
- endpointUrl: string;
953
946
 
954
947
  serverCertificateChain: Certificate;
955
948
  /**
@@ -1000,7 +993,8 @@ interface MakeEndpointDescriptionOptions {
1000
993
  noUserIdentityTokens?: boolean;
1001
994
  }
1002
995
 
1003
- interface EndpointDescriptionEx extends EndpointDescription {
996
+ export interface EndpointDescriptionEx extends EndpointDescription {
997
+ _parent: OPCUAServerEndPoint;
1004
998
  restricted: boolean;
1005
999
  }
1006
1000
 
@@ -1038,8 +1032,7 @@ function estimateSecurityLevel(securityMode: MessageSecurityMode, securityPolicy
1038
1032
  /**
1039
1033
  * @private
1040
1034
  */
1041
- function _makeEndpointDescription(options: MakeEndpointDescriptionOptions): EndpointDescriptionEx {
1042
- assert(isFinite(options.port), "expecting a valid port number");
1035
+ function _makeEndpointDescription(options: MakeEndpointDescriptionOptions, parent: OPCUAServerEndPoint): EndpointDescriptionEx {
1043
1036
  assert(Object.prototype.hasOwnProperty.call(options, "serverCertificateChain"));
1044
1037
  assert(!Object.prototype.hasOwnProperty.call(options, "serverCertificate"));
1045
1038
  assert(!!options.securityMode); // s.MessageSecurityMode
@@ -1146,7 +1139,7 @@ function _makeEndpointDescription(options: MakeEndpointDescriptionOptions): Endp
1146
1139
  }
1147
1140
  // return the endpoint object
1148
1141
  const endpoint = new EndpointDescription({
1149
- endpointUrl: options.endpointUrl,
1142
+ endpointUrl: '<to be evaluated at run time>',// options.endpointUrl,
1150
1143
 
1151
1144
  server: undefined, // options.server,
1152
1145
  serverCertificate: options.serverCertificateChain,
@@ -1158,9 +1151,16 @@ function _makeEndpointDescription(options: MakeEndpointDescriptionOptions): Endp
1158
1151
  securityLevel: options.securityLevel,
1159
1152
  transportProfileUri: default_transportProfileUri
1160
1153
  }) as EndpointDescriptionEx;
1161
-
1154
+ endpoint._parent = parent;
1155
+
1156
+ // endpointUrl is dynamic as port number may be adjusted
1157
+ // when the tcp socker start listening
1162
1158
  (endpoint as any).__defineGetter__("endpointUrl", () => {
1163
- return resolveFullyQualifiedDomainName(options.endpointUrl);
1159
+ const port = endpoint._parent.port;
1160
+ const resourcePath = options.resourcePath || "";
1161
+ const hostname = options.hostname;
1162
+ const endpointUrl = `opc.tcp://${hostname}:${port}${resourcePath}`;
1163
+ return resolveFullyQualifiedDomainName(endpointUrl);
1164
1164
  });
1165
1165
 
1166
1166
  endpoint.server = options.server;
@@ -207,7 +207,7 @@ function _getSubscription(
207
207
  this: ServerEngine,
208
208
  inputArguments: Variant[],
209
209
  context: ISessionContext
210
- ): { subscription: Subscription, statusCode?: never } | { statusCode: StatusCode, subscription?: never } {
210
+ ): { subscription: Subscription; statusCode?: never } | { statusCode: StatusCode; subscription?: never } {
211
211
  assert(Array.isArray(inputArguments));
212
212
  assert(Object.prototype.hasOwnProperty.call(context, "session"), " expecting a session id in the context object");
213
213
  const session = context.session as ServerSession;
@@ -225,7 +225,6 @@ function _getSubscription(
225
225
  return { statusCode: StatusCodes.BadSubscriptionIdInvalid };
226
226
  }
227
227
  return { subscription };
228
-
229
228
  }
230
229
  function resendData(
231
230
  this: ServerEngine,
@@ -239,13 +238,14 @@ function resendData(
239
238
  if (data.statusCode) return callback(null, { statusCode: data.statusCode });
240
239
  const { subscription } = data;
241
240
 
242
- subscription.resendInitialValues().then(() => {
243
- callback(null, { statusCode: StatusCodes.Good });
244
- }).catch((err) => callback(err));
245
-
241
+ subscription
242
+ .resendInitialValues()
243
+ .then(() => {
244
+ callback(null, { statusCode: StatusCodes.Good });
245
+ })
246
+ .catch((err) => callback(err));
246
247
  }
247
248
 
248
-
249
249
  // binding methods
250
250
  function getMonitoredItemsId(
251
251
  this: ServerEngine,
@@ -1450,7 +1450,7 @@ export class ServerEngine extends EventEmitter {
1450
1450
  (!dataValue.serverTimestamp || dataValue.serverTimestamp.getTime() === minOPCUADate.getTime())
1451
1451
  ) {
1452
1452
  dataValue.serverTimestamp = context.currentTime.timestamp;
1453
- dataValue.serverPicoseconds = 0; // context.currentTime.picoseconds;
1453
+ dataValue.serverPicoseconds = 0; // context.currentTime.picoseconds;
1454
1454
  }
1455
1455
  dataValues.push(dataValue);
1456
1456
  }
@@ -1990,11 +1990,12 @@ export class ServerEngine extends EventEmitter {
1990
1990
  * performs a call to ```asyncRefresh``` on all variable nodes that provide an async refresh func.
1991
1991
  *
1992
1992
  * @method refreshValues
1993
- * @param nodesToRefresh {Array<Object>} an array containing the node to consider
1993
+ * @param nodesToRefresh {Array<ReadValueId|HistoryReadValueId>} an array containing the node to consider
1994
1994
  * Each element of the array shall be of the form { nodeId: <xxx>, attributeIds: <value> }.
1995
+ * @param maxAge {number} the maximum age of the value to be read, in milliseconds.
1995
1996
  * @param callback
1996
- * @param callback.err
1997
- * @param callback.data an array containing value read
1997
+ * @param callback.err {Error}
1998
+ * @param callback.dataValue {DataValue} an array containing value read
1998
1999
  * The array length matches the number of nodeIds that are candidate for an async refresh (i.e: nodes that
1999
2000
  * are of type Variable with asyncRefresh func }
2000
2001
  *
@@ -2005,10 +2006,12 @@ export class ServerEngine extends EventEmitter {
2005
2006
  maxAge: number,
2006
2007
  callback: (err: Error | null, dataValues?: DataValue[]) => void
2007
2008
  ): void {
2008
- const referenceTime = new Date(Date.now() - maxAge);
2009
+ const referenceTime = getCurrentClock();
2010
+ maxAge && referenceTime.timestamp.setTime(referenceTime.timestamp.getTime() - maxAge);
2009
2011
 
2010
2012
  assert(typeof callback === "function");
2011
- const objectMap: Record<string, BaseNode> = {};
2013
+
2014
+ const nodeMap: Record<string, UAVariable> = {};
2012
2015
  for (const nodeToRefresh of nodesToRefresh) {
2013
2016
  // only consider node for which the caller wants to read the Value attribute
2014
2017
  // assuming that Value is requested if attributeId is missing,
@@ -2016,51 +2019,38 @@ export class ServerEngine extends EventEmitter {
2016
2019
  continue;
2017
2020
  }
2018
2021
  // ... and that are valid object and instances of Variables ...
2019
- const obj = this.addressSpace!.findNode(nodeToRefresh.nodeId);
2020
- if (!obj || !(obj.nodeClass === NodeClass.Variable)) {
2022
+ const uaNode = this.addressSpace!.findNode(nodeToRefresh.nodeId);
2023
+ if (!uaNode || !(uaNode.nodeClass === NodeClass.Variable)) {
2021
2024
  continue;
2022
2025
  }
2023
2026
  // ... and that have been declared as asynchronously updating
2024
- if (typeof (obj as any).refreshFunc !== "function") {
2027
+ if (typeof (uaNode as any).refreshFunc !== "function") {
2025
2028
  continue;
2026
2029
  }
2027
- const key = obj.nodeId.toString();
2028
- if (objectMap[key]) {
2030
+ const key = uaNode.nodeId.toString();
2031
+ if (nodeMap[key]) {
2029
2032
  continue;
2030
2033
  }
2031
-
2032
- objectMap[key] = obj;
2034
+ nodeMap[key] = uaNode as UAVariable;
2033
2035
  }
2034
2036
 
2035
- const objectArray = Object.values(objectMap);
2036
- if (objectArray.length === 0) {
2037
+ const uaVariableArray = Object.values(nodeMap);
2038
+ if (uaVariableArray.length === 0) {
2037
2039
  // nothing to do
2038
2040
  return callback(null, []);
2039
2041
  }
2040
2042
  // perform all asyncRefresh in parallel
2041
2043
  async.map(
2042
- objectArray,
2043
- (obj: BaseNode, inner_callback: CallbackT<DataValue>) => {
2044
- if (obj.nodeClass !== NodeClass.Variable) {
2045
- inner_callback(
2046
- null,
2047
- new DataValue({
2048
- statusCode: StatusCodes.BadNodeClassInvalid
2049
- })
2050
- );
2051
- return;
2052
- }
2044
+ uaVariableArray,
2045
+ (uaVariable: UAVariable, inner_callback: CallbackT<DataValue>) => {
2053
2046
  try {
2054
- (obj as UAVariable).asyncRefresh(referenceTime, (err, dataValue) => {
2047
+ uaVariable.asyncRefresh(referenceTime, (err, dataValue) => {
2055
2048
  inner_callback(err, dataValue);
2056
2049
  });
2057
2050
  } catch (err) {
2058
- // istanbul ignore next
2059
- if (!(err instanceof Error)) {
2060
- throw new Error("internal error");
2061
- }
2062
- errorLog("asyncRefresh internal error", err);
2063
- inner_callback(err);
2051
+ const _err = err as Error;
2052
+ errorLog("asyncRefresh internal error", _err.message);
2053
+ inner_callback(_err);
2064
2054
  }
2065
2055
  },
2066
2056
  (err?: Error | null, arrResult?: (DataValue | undefined)[]) => {
@@ -2091,7 +2081,7 @@ export class ServerEngine extends EventEmitter {
2091
2081
  assert(subscriptionDiagnostics instanceof SubscriptionDiagnosticsDataType);
2092
2082
  if (subscriptionDiagnostics && serverSubscriptionDiagnosticsArray) {
2093
2083
  const node = (serverSubscriptionDiagnosticsArray as any)[subscription.id];
2094
- removeElement(serverSubscriptionDiagnosticsArray, (a)=> a.subscriptionId === subscription.id);
2084
+ removeElement(serverSubscriptionDiagnosticsArray, (a) => a.subscriptionId === subscription.id);
2095
2085
  /*assert(
2096
2086
  !(subscriptionDiagnosticsArray as any)[subscription.id],
2097
2087
  " subscription node must have been removed from subscriptionDiagnosticsArray"
@@ -2281,8 +2271,8 @@ export class ServerEngine extends EventEmitter {
2281
2271
  } else {
2282
2272
  warningLog(
2283
2273
  chalk.yellow("WARNING: cannot bind a method with id ") +
2284
- chalk.cyan(nodeId.toString()) +
2285
- chalk.yellow(". please check your nodeset.xml file or add this node programmatically")
2274
+ chalk.cyan(nodeId.toString()) +
2275
+ chalk.yellow(". please check your nodeset.xml file or add this node programmatically")
2286
2276
  );
2287
2277
  warningLog(traceFromThisProjectOnly());
2288
2278
  }