node-opcua-server 2.81.0 → 2.83.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.
@@ -27,6 +27,7 @@ import {
27
27
  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
+ import { UserTokenPolicyOptions } from "node-opcua-types";
30
31
  import { IChannelData } from "./i_channel_data";
31
32
  import { ISocketData } from "./i_socket_data";
32
33
 
@@ -155,26 +156,27 @@ export interface OPCUAServerEndPointOptions {
155
156
  }
156
157
 
157
158
  export interface EndpointDescriptionParams {
158
- allowAnonymous?: boolean;
159
159
  restricted?: boolean;
160
160
  allowUnsecurePassword?: boolean;
161
161
  resourcePath?: string;
162
162
  alternateHostname?: string[];
163
163
  hostname: string;
164
164
  securityPolicies: SecurityPolicy[];
165
+ userTokenTypes: UserTokenType[];
165
166
  }
166
167
 
167
168
  export interface AddStandardEndpointDescriptionsParam {
168
- securityModes?: MessageSecurityMode[];
169
- securityPolicies?: SecurityPolicy[];
170
- disableDiscovery?: boolean;
171
-
172
169
  allowAnonymous?: boolean;
170
+ disableDiscovery?: boolean;
171
+ securityModes?: MessageSecurityMode[];
172
+
173
173
  restricted?: boolean;
174
- hostname?: string;
175
- alternateHostname?: string[];
176
174
  allowUnsecurePassword?: boolean;
177
175
  resourcePath?: string;
176
+ alternateHostname?: string[];
177
+ hostname?: string;
178
+ securityPolicies?: SecurityPolicy[];
179
+ userTokenTypes?: UserTokenType[];
178
180
  }
179
181
 
180
182
  function getUniqueName(name: string, collection: { [key: string]: number }) {
@@ -367,17 +369,8 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
367
369
  public addEndpointDescription(
368
370
  securityMode: MessageSecurityMode,
369
371
  securityPolicy: SecurityPolicy,
370
- options?: EndpointDescriptionParams
372
+ options: EndpointDescriptionParams
371
373
  ): void {
372
- if (!options) {
373
- options = {
374
- hostname: getFullyQualifiedDomainName(),
375
- securityPolicies: [SecurityPolicy.Basic256Sha256]
376
- };
377
- }
378
-
379
- options.allowAnonymous = options.allowAnonymous === undefined ? true : options.allowAnonymous;
380
-
381
374
  // istanbul ignore next
382
375
  if (securityMode === MessageSecurityMode.None && securityPolicy !== SecurityPolicy.None) {
383
376
  throw new Error(" invalid security ");
@@ -405,6 +398,8 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
405
398
  throw new Error(" endpoint already exist");
406
399
  }
407
400
 
401
+ const userTokenTypes = options.userTokenTypes;
402
+
408
403
  // now build endpointUrl
409
404
  this._endpoints.push(
410
405
  _makeEndpointDescription({
@@ -419,12 +414,13 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
419
414
  securityMode,
420
415
  securityPolicy,
421
416
 
422
- allowAnonymous: options.allowAnonymous,
423
417
  allowUnsecurePassword: options.allowUnsecurePassword,
424
418
  resourcePath: options.resourcePath,
425
419
 
426
420
  restricted: !!options.restricted,
427
- securityPolicies: options?.securityPolicies || []
421
+ securityPolicies: options.securityPolicies || [],
422
+
423
+ userTokenTypes
428
424
  })
429
425
  );
430
426
  }
@@ -440,6 +436,14 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
440
436
 
441
437
  options.securityModes = options.securityModes || defaultSecurityModes;
442
438
  options.securityPolicies = options.securityPolicies || defaultSecurityPolicies;
439
+ options.userTokenTypes = options.userTokenTypes || defaultUserTokenTypes;
440
+
441
+ options.allowAnonymous = options.allowAnonymous === undefined ? true : options.allowAnonymous;
442
+ // make sure we do not have anonymous
443
+ if (!options.allowAnonymous) {
444
+ options.userTokenTypes = options.userTokenTypes.filter((r) => r !== UserTokenType.Anonymous);
445
+ }
446
+
443
447
 
444
448
  const defaultHostname = options.hostname || getFullyQualifiedDomainName();
445
449
 
@@ -450,11 +454,17 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
450
454
  options.alternateHostname = [options.alternateHostname];
451
455
  }
452
456
  // remove duplicates if any (uniq)
453
- hostnames = [...new Set(hostnames.concat(options.alternateHostname as string[]))];
457
+ hostnames = [...new Set(hostnames.concat(options.alternateHostname))];
454
458
 
455
459
  for (const alternateHostname of hostnames) {
456
- const optionsE = options as EndpointDescriptionParams;
457
- optionsE.hostname = alternateHostname;
460
+ const optionsE: EndpointDescriptionParams = {
461
+ hostname: alternateHostname,
462
+ securityPolicies: options.securityPolicies,
463
+ userTokenTypes: options.userTokenTypes,
464
+ allowUnsecurePassword: options.allowUnsecurePassword,
465
+ alternateHostname: options.alternateHostname,
466
+ resourcePath: options.resourcePath
467
+ };
458
468
 
459
469
  if (options.securityModes.indexOf(MessageSecurityMode.None) >= 0) {
460
470
  this.addEndpointDescription(MessageSecurityMode.None, SecurityPolicy.None, optionsE);
@@ -966,7 +976,6 @@ interface MakeEndpointDescriptionOptions {
966
976
  };
967
977
  */
968
978
  resourcePath?: string;
969
- allowAnonymous?: boolean; // default true
970
979
 
971
980
  // allow un-encrypted password in userNameIdentity
972
981
  allowUnsecurePassword?: boolean; // default false
@@ -981,6 +990,15 @@ interface MakeEndpointDescriptionOptions {
981
990
  collection: { [key: string]: number };
982
991
 
983
992
  securityPolicies: SecurityPolicy[];
993
+
994
+ userTokenTypes: UserTokenType[];
995
+ /**
996
+ *
997
+ * default value: false;
998
+ *
999
+ * note: setting noUserIdentityTokens=true is useful for pure local discovery servers
1000
+ */
1001
+ noUserIdentityTokens?: boolean;
984
1002
  }
985
1003
 
986
1004
  interface EndpointDescriptionEx extends EndpointDescription {
@@ -1040,72 +1058,85 @@ function _makeEndpointDescription(options: MakeEndpointDescriptionOptions): Endp
1040
1058
 
1041
1059
  const securityPolicyUri = toURI(options.securityPolicy);
1042
1060
 
1043
- const userIdentityTokens = [];
1044
-
1045
- if (options.securityPolicy === SecurityPolicy.None) {
1046
- if (options.allowUnsecurePassword) {
1047
- userIdentityTokens.push({
1048
- policyId: u("username_unsecure"),
1049
- tokenType: UserTokenType.UserName,
1061
+ const userIdentityTokens: UserTokenPolicyOptions[] = [];
1050
1062
 
1051
- issuedTokenType: null,
1052
- issuerEndpointUrl: null,
1053
- securityPolicyUri: null
1054
- });
1063
+ const registerIdentity2 = (tokenType: UserTokenType, securityPolicy: SecurityPolicy, name: string) => {
1064
+ return registerIdentity({
1065
+ policyId: u(name),
1066
+ tokenType,
1067
+ issuedTokenType: null,
1068
+ issuerEndpointUrl: null,
1069
+ securityPolicyUri: securityPolicy
1070
+ });
1071
+ };
1072
+ const registerIdentity = (r: UserTokenPolicyOptions) => {
1073
+ const tokenType = r.tokenType === undefined ? UserTokenType.Invalid : r.tokenType;
1074
+ const securityPolicy = (r.securityPolicyUri || "") as SecurityPolicy;
1075
+ if (!securityPolicy && options.userTokenTypes.indexOf(tokenType) >= 0) {
1076
+ userIdentityTokens.push(r);
1077
+ return;
1055
1078
  }
1079
+ if (options.securityPolicies.indexOf(securityPolicy) >= 0 && options.userTokenTypes.indexOf(tokenType) >= 0) {
1080
+ userIdentityTokens.push(r);
1081
+ }
1082
+ };
1083
+
1084
+ if (!options.noUserIdentityTokens) {
1085
+ if (options.securityPolicy === SecurityPolicy.None) {
1086
+ if (options.allowUnsecurePassword) {
1087
+ registerIdentity({
1088
+ policyId: u("username_unsecure"),
1089
+ tokenType: UserTokenType.UserName,
1056
1090
 
1057
- const a = (tokenType: UserTokenType, securityPolicy: SecurityPolicy, name: string) => {
1058
- if (options.securityPolicies.indexOf(securityPolicy) >= 0) {
1059
- userIdentityTokens.push({
1060
- policyId: u(name),
1061
- tokenType,
1062
1091
  issuedTokenType: null,
1063
1092
  issuerEndpointUrl: null,
1064
- securityPolicyUri: securityPolicy
1093
+ securityPolicyUri: null
1065
1094
  });
1066
1095
  }
1067
- };
1068
- const onlyCertificateLessConnection =
1069
- options.onlyCertificateLessConnection === undefined ? false : options.onlyCertificateLessConnection;
1070
-
1071
- if (!onlyCertificateLessConnection) {
1072
- a(UserTokenType.UserName, SecurityPolicy.Basic256, "username_basic256");
1073
- a(UserTokenType.UserName, SecurityPolicy.Basic128Rsa15, "username_basic128Rsa15");
1074
- a(UserTokenType.UserName, SecurityPolicy.Basic256Sha256, "username_basic256Sha256");
1075
- a(UserTokenType.UserName, SecurityPolicy.Aes128_Sha256_RsaOaep, "username_aes128Sha256RsaOaep");
1076
-
1077
- // X509
1078
- a(UserTokenType.Certificate, SecurityPolicy.Basic256, "certificate_basic256");
1079
- a(UserTokenType.Certificate, SecurityPolicy.Basic128Rsa15, "certificate_basic128Rsa15");
1080
- a(UserTokenType.Certificate, SecurityPolicy.Basic256Sha256, "certificate_basic256Sha256");
1081
- a(UserTokenType.Certificate, SecurityPolicy.Aes128_Sha256_RsaOaep, "certificate_aes128Sha256RsaOaep");
1082
- }
1083
- } else {
1084
- // note:
1085
- // when channel session security is not "None",
1086
- // userIdentityTokens can be left to null.
1087
- // in this case this mean that secure policy will be the same as connection security policy
1088
- userIdentityTokens.push({
1089
- policyId: u("usernamePassword"),
1090
- tokenType: UserTokenType.UserName,
1091
1096
 
1092
- issuedTokenType: null,
1093
- issuerEndpointUrl: null,
1094
- securityPolicyUri: null
1095
- });
1097
+ const onlyCertificateLessConnection =
1098
+ options.onlyCertificateLessConnection === undefined ? false : options.onlyCertificateLessConnection;
1099
+
1100
+ if (!onlyCertificateLessConnection) {
1101
+ registerIdentity2(UserTokenType.UserName, SecurityPolicy.Basic256, "username_basic256");
1102
+ registerIdentity2(UserTokenType.UserName, SecurityPolicy.Basic128Rsa15, "username_basic128Rsa15");
1103
+ registerIdentity2(UserTokenType.UserName, SecurityPolicy.Basic256Sha256, "username_basic256Sha256");
1104
+ registerIdentity2(UserTokenType.UserName, SecurityPolicy.Aes128_Sha256_RsaOaep, "username_aes128Sha256RsaOaep");
1105
+
1106
+ // X509
1107
+ registerIdentity2(UserTokenType.Certificate, SecurityPolicy.Basic256, "certificate_basic256");
1108
+ registerIdentity2(UserTokenType.Certificate, SecurityPolicy.Basic128Rsa15, "certificate_basic128Rsa15");
1109
+ registerIdentity2(UserTokenType.Certificate, SecurityPolicy.Basic256Sha256, "certificate_basic256Sha256");
1110
+ registerIdentity2(
1111
+ UserTokenType.Certificate,
1112
+ SecurityPolicy.Aes128_Sha256_RsaOaep,
1113
+ "certificate_aes128Sha256RsaOaep"
1114
+ );
1115
+ }
1116
+ } else {
1117
+ // note:
1118
+ // when channel session security is not "None",
1119
+ // userIdentityTokens can be left to null.
1120
+ // in this case this mean that secure policy will be the same as connection security policy
1121
+ registerIdentity({
1122
+ policyId: u("usernamePassword"),
1123
+ tokenType: UserTokenType.UserName,
1124
+ issuedTokenType: null,
1125
+ issuerEndpointUrl: null,
1126
+ securityPolicyUri: null
1127
+ });
1096
1128
 
1097
- userIdentityTokens.push({
1098
- policyId: u("certificateX509"),
1099
- tokenType: UserTokenType.Certificate,
1129
+ registerIdentity({
1130
+ policyId: u("certificateX509"),
1131
+ tokenType: UserTokenType.Certificate,
1100
1132
 
1101
- issuedTokenType: null,
1102
- issuerEndpointUrl: null,
1103
- securityPolicyUri: null
1104
- });
1105
- }
1133
+ issuedTokenType: null,
1134
+ issuerEndpointUrl: null,
1135
+ securityPolicyUri: null
1136
+ });
1137
+ }
1106
1138
 
1107
- if (options.allowAnonymous) {
1108
- userIdentityTokens.push({
1139
+ registerIdentity({
1109
1140
  policyId: u("anonymous"),
1110
1141
  tokenType: UserTokenType.Anonymous,
1111
1142
 
@@ -1114,7 +1145,6 @@ function _makeEndpointDescription(options: MakeEndpointDescriptionOptions): Endp
1114
1145
  securityPolicyUri: null
1115
1146
  });
1116
1147
  }
1117
-
1118
1148
  // return the endpoint object
1119
1149
  const endpoint = new EndpointDescription({
1120
1150
  endpointUrl: options.endpointUrl,
@@ -1172,3 +1202,10 @@ const defaultSecurityPolicies = [
1172
1202
  SecurityPolicy.Aes128_Sha256_RsaOaep
1173
1203
  // NO USED YET SecurityPolicy.Aes256_Sha256_RsaPss
1174
1204
  ];
1205
+
1206
+ const defaultUserTokenTypes = [
1207
+ UserTokenType.Anonymous,
1208
+ UserTokenType.UserName,
1209
+ UserTokenType.Certificate
1210
+ // NOT USED YET : UserTokenType.IssuedToken
1211
+ ];
@@ -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;
@@ -7,28 +7,46 @@ import { BaseNode, UAVariable } from "node-opcua-address-space";
7
7
  import { AttributeIds } from "node-opcua-data-model";
8
8
  import { NodeClass } from "node-opcua-data-model";
9
9
  import { ExtensionObject } from "node-opcua-extension-object";
10
- import { NodeId } from "node-opcua-nodeid";
10
+ import { NodeId, NodeIdType } from "node-opcua-nodeid";
11
11
  import { DataChangeFilter, EventFilter } from "node-opcua-service-filter";
12
12
  import { DeadbandType } from "node-opcua-service-subscription";
13
13
  import { StatusCode, StatusCodes } from "node-opcua-status-code";
14
14
  import { ReadValueIdOptions } from "node-opcua-types";
15
+ import { DataType } from "node-opcua-basic-types";
16
+
17
+ function isNumberDataType(node: UAVariable): boolean {
18
+ if (node.dataType.namespace === 0 && node.dataType.identifierType === NodeIdType.NUMERIC && node.dataType.value < 22) {
19
+ switch (node.dataType.identifierType === NodeIdType.NUMERIC && node.dataType.value) {
20
+ case DataType.Float:
21
+ case DataType.Double:
22
+ case DataType.Byte:
23
+ case DataType.SByte:
24
+ case DataType.Int16:
25
+ case DataType.Int32:
26
+ case DataType.Int64:
27
+ case DataType.UInt16:
28
+ case DataType.UInt32:
29
+ case DataType.UInt64:
30
+ return true;
31
+ default:
32
+ return false;
33
+ }
34
+ }
35
+ const dataType = node.addressSpace.findDataType(node.dataType)!;
36
+ const dataTypeNumber = node.addressSpace.findDataType("Number")!;
37
+ return dataType.isSupertypeOf(dataTypeNumber);
38
+ }
15
39
 
16
40
  function __validateDataChangeFilter(filter: DataChangeFilter, itemToMonitor: ReadValueIdOptions, node: UAVariable): StatusCode {
17
41
  assert(itemToMonitor.attributeId === AttributeIds.Value);
18
-
19
42
  if (node.nodeClass !== NodeClass.Variable) {
20
43
  return StatusCodes.BadNodeIdInvalid;
21
44
  }
22
45
 
23
- assert(node.nodeClass === NodeClass.Variable);
24
-
25
- // if node is not Numerical=> DataChangeFilter
26
- assert(node.dataType instanceof NodeId);
27
- const dataType = node.addressSpace.findDataType(node.dataType)!;
28
-
29
- const dataTypeNumber = node.addressSpace.findDataType("Number")!;
30
46
  if (filter.deadbandType !== DeadbandType.None) {
31
- if (!dataType.isSupertypeOf(dataTypeNumber)) {
47
+ // if node is not Numerical=> DataChangeFilter
48
+ assert(node.dataType instanceof NodeId);
49
+ if (!isNumberDataType(node)) {
32
50
  return StatusCodes.BadFilterNotAllowed;
33
51
  }
34
52
  }