node-opcua-server 2.51.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 (100) hide show
  1. package/.mocharc.yml +10 -0
  2. package/LICENSE +20 -0
  3. package/dist/base_server.d.ts +110 -0
  4. package/dist/base_server.js +476 -0
  5. package/dist/base_server.js.map +1 -0
  6. package/dist/factory.d.ts +10 -0
  7. package/dist/factory.js +24 -0
  8. package/dist/factory.js.map +1 -0
  9. package/dist/history_server_capabilities.d.ts +35 -0
  10. package/dist/history_server_capabilities.js +44 -0
  11. package/dist/history_server_capabilities.js.map +1 -0
  12. package/dist/i_channel_data.d.ts +13 -0
  13. package/dist/i_channel_data.js +3 -0
  14. package/dist/i_channel_data.js.map +1 -0
  15. package/dist/i_register_server_manager.d.ts +16 -0
  16. package/dist/i_register_server_manager.js +3 -0
  17. package/dist/i_register_server_manager.js.map +1 -0
  18. package/dist/i_server_side_publish_engine.d.ts +36 -0
  19. package/dist/i_server_side_publish_engine.js +50 -0
  20. package/dist/i_server_side_publish_engine.js.map +1 -0
  21. package/dist/i_socket_data.d.ts +11 -0
  22. package/dist/i_socket_data.js +3 -0
  23. package/dist/i_socket_data.js.map +1 -0
  24. package/dist/index.d.ts +14 -0
  25. package/dist/index.js +27 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/monitored_item.d.ts +173 -0
  28. package/dist/monitored_item.js +1006 -0
  29. package/dist/monitored_item.js.map +1 -0
  30. package/dist/node_sampler.d.ts +3 -0
  31. package/dist/node_sampler.js +76 -0
  32. package/dist/node_sampler.js.map +1 -0
  33. package/dist/opcua_server.d.ts +668 -0
  34. package/dist/opcua_server.js +2407 -0
  35. package/dist/opcua_server.js.map +1 -0
  36. package/dist/queue.d.ts +11 -0
  37. package/dist/queue.js +71 -0
  38. package/dist/queue.js.map +1 -0
  39. package/dist/register_server_manager.d.ts +92 -0
  40. package/dist/register_server_manager.js +574 -0
  41. package/dist/register_server_manager.js.map +1 -0
  42. package/dist/register_server_manager_hidden.d.ts +17 -0
  43. package/dist/register_server_manager_hidden.js +28 -0
  44. package/dist/register_server_manager_hidden.js.map +1 -0
  45. package/dist/register_server_manager_mdns_only.d.ts +19 -0
  46. package/dist/register_server_manager_mdns_only.js +58 -0
  47. package/dist/register_server_manager_mdns_only.js.map +1 -0
  48. package/dist/server_capabilities.d.ts +61 -0
  49. package/dist/server_capabilities.js +109 -0
  50. package/dist/server_capabilities.js.map +1 -0
  51. package/dist/server_end_point.d.ts +180 -0
  52. package/dist/server_end_point.js +825 -0
  53. package/dist/server_end_point.js.map +1 -0
  54. package/dist/server_engine.d.ts +311 -0
  55. package/dist/server_engine.js +1659 -0
  56. package/dist/server_engine.js.map +1 -0
  57. package/dist/server_publish_engine.d.ts +109 -0
  58. package/dist/server_publish_engine.js +531 -0
  59. package/dist/server_publish_engine.js.map +1 -0
  60. package/dist/server_publish_engine_for_orphan_subscriptions.d.ts +16 -0
  61. package/dist/server_publish_engine_for_orphan_subscriptions.js +50 -0
  62. package/dist/server_publish_engine_for_orphan_subscriptions.js.map +1 -0
  63. package/dist/server_session.d.ts +176 -0
  64. package/dist/server_session.js +734 -0
  65. package/dist/server_session.js.map +1 -0
  66. package/dist/server_subscription.d.ts +393 -0
  67. package/dist/server_subscription.js +1313 -0
  68. package/dist/server_subscription.js.map +1 -0
  69. package/dist/sessions_compatible_for_transfer.d.ts +2 -0
  70. package/dist/sessions_compatible_for_transfer.js +36 -0
  71. package/dist/sessions_compatible_for_transfer.js.map +1 -0
  72. package/dist/validate_filter.d.ts +5 -0
  73. package/dist/validate_filter.js +64 -0
  74. package/dist/validate_filter.js.map +1 -0
  75. package/package.json +88 -0
  76. package/source/base_server.ts +617 -0
  77. package/source/factory.ts +25 -0
  78. package/source/history_server_capabilities.ts +75 -0
  79. package/source/i_channel_data.ts +17 -0
  80. package/source/i_register_server_manager.ts +24 -0
  81. package/source/i_server_side_publish_engine.ts +77 -0
  82. package/source/i_socket_data.ts +11 -0
  83. package/source/index.ts +14 -0
  84. package/source/monitored_item.ts +1303 -0
  85. package/source/node_sampler.ts +82 -0
  86. package/source/opcua_server.ts +3742 -0
  87. package/source/queue.ts +73 -0
  88. package/source/register_server_manager.ts +744 -0
  89. package/source/register_server_manager_hidden.ts +33 -0
  90. package/source/register_server_manager_mdns_only.ts +69 -0
  91. package/source/server_capabilities.ts +177 -0
  92. package/source/server_end_point.ts +1182 -0
  93. package/source/server_engine.ts +2167 -0
  94. package/source/server_publish_engine.ts +657 -0
  95. package/source/server_publish_engine_for_orphan_subscriptions.ts +52 -0
  96. package/source/server_session.ts +931 -0
  97. package/source/server_subscription.ts +1792 -0
  98. package/source/sessions_compatible_for_transfer.ts +33 -0
  99. package/source/validate_filter.ts +86 -0
  100. package/test_helpers/create_certificates.js +1 -0
@@ -0,0 +1,1792 @@
1
+ /**
2
+ * @module node-opcua-server
3
+ */
4
+ // tslint:disable:no-console
5
+
6
+ import { Queue } from "./queue";
7
+ import * as chalk from "chalk";
8
+ import { EventEmitter } from "events";
9
+
10
+ import { AddressSpace, BaseNode, Duration, UAObjectType } from "node-opcua-address-space";
11
+ import { checkSelectClauses } from "node-opcua-address-space";
12
+ import { SessionContext } from "node-opcua-address-space";
13
+ import { assert } from "node-opcua-assert";
14
+ import { Byte } from "node-opcua-basic-types";
15
+ import { SubscriptionDiagnosticsDataType } from "node-opcua-common";
16
+ import { NodeClass, AttributeIds, isValidDataEncoding } from "node-opcua-data-model";
17
+ import { TimestampsToReturn } from "node-opcua-data-value";
18
+ import { checkDebugFlag, make_debugLog, make_warningLog } from "node-opcua-debug";
19
+ import { NodeId } from "node-opcua-nodeid";
20
+ import { ObjectRegistry } from "node-opcua-object-registry";
21
+ import { SequenceNumberGenerator } from "node-opcua-secure-channel";
22
+ import { EventFilter } from "node-opcua-service-filter";
23
+ import { AggregateFilter } from "node-opcua-service-subscription";
24
+ import {
25
+ DataChangeNotification,
26
+ EventNotificationList,
27
+ MonitoringMode,
28
+ MonitoredItemCreateResult,
29
+ MonitoredItemNotification,
30
+ PublishResponse,
31
+ NotificationMessage,
32
+ StatusChangeNotification,
33
+ DataChangeFilter,
34
+ MonitoredItemCreateRequest
35
+ } from "node-opcua-service-subscription";
36
+ import { StatusCode, StatusCodes } from "node-opcua-status-code";
37
+ import { AggregateFilterResult, ContentFilterResult, EventFieldList, EventFilterResult, NotificationData } from "node-opcua-types";
38
+
39
+ import { MonitoredItem, MonitoredItemOptions, QueueItem } from "./monitored_item";
40
+ import { ServerSession } from "./server_session";
41
+ import { validateFilter } from "./validate_filter";
42
+ import { IServerSidePublishEngine, TransferredSubscription } from "./i_server_side_publish_engine";
43
+
44
+ const debugLog = make_debugLog(__filename);
45
+ const doDebug = checkDebugFlag(__filename);
46
+ const warningLog = make_warningLog(__filename);
47
+ const maxNotificationMessagesInQueue = 100;
48
+
49
+ export interface SubscriptionDiagnosticsDataTypePriv extends SubscriptionDiagnosticsDataType {
50
+ $subscription: Subscription;
51
+ }
52
+
53
+ export enum SubscriptionState {
54
+ CLOSED = 1, // The Subscription has not yet been created or has terminated.
55
+ CREATING = 2, // The Subscription is being created
56
+ NORMAL = 3, // The Subscription is cyclically checking for Notifications from its MonitoredItems.
57
+ // The keep-alive counter is not used in this state.
58
+ LATE = 4, // The publishing timer has expired and there are Notifications available or a keep-alive Message is
59
+ // ready to be sent, but there are no Publish requests queued. When in this state, the next Publish
60
+ // request is processed when it is received. The keep-alive counter is not used in this state.
61
+ KEEPALIVE = 5, // The Subscription is cyclically checking for Notification
62
+ // alive counter to count down to 0 from its maximum.
63
+ TERMINATED = 6
64
+ }
65
+
66
+ function _adjust_publishing_interval(publishingInterval?: number): number {
67
+ publishingInterval =
68
+ publishingInterval === undefined || Number.isNaN(publishingInterval)
69
+ ? Subscription.defaultPublishingInterval
70
+ : publishingInterval;
71
+ publishingInterval = Math.max(publishingInterval, Subscription.minimumPublishingInterval);
72
+ publishingInterval = Math.min(publishingInterval, Subscription.maximumPublishingInterval);
73
+ return publishingInterval;
74
+ }
75
+
76
+ const minimumMaxKeepAliveCount = 2;
77
+ const maximumMaxKeepAliveCount = 12000;
78
+
79
+ function _adjust_maxKeepAliveCount(maxKeepAliveCount?: number /*,publishingInterval*/): number {
80
+ maxKeepAliveCount = maxKeepAliveCount || minimumMaxKeepAliveCount;
81
+ maxKeepAliveCount = Math.max(maxKeepAliveCount, minimumMaxKeepAliveCount);
82
+ maxKeepAliveCount = Math.min(maxKeepAliveCount, maximumMaxKeepAliveCount);
83
+ return maxKeepAliveCount;
84
+ }
85
+
86
+ function _adjust_lifeTimeCount(lifeTimeCount: number, maxKeepAliveCount: number, publishingInterval: number): number {
87
+ lifeTimeCount = lifeTimeCount || 1;
88
+
89
+ // let's make sure that lifeTimeCount is at least three time maxKeepAliveCount
90
+ // Note : the specs say ( part 3 - CreateSubscriptionParameter )
91
+ // "The lifetime count shall be a minimum of three times the keep keep-alive count."
92
+ lifeTimeCount = Math.max(lifeTimeCount, maxKeepAliveCount * 3);
93
+
94
+ const minTicks = Math.ceil((5 * 1000) / publishingInterval); // we want 5 seconds min
95
+
96
+ lifeTimeCount = Math.max(minTicks, lifeTimeCount);
97
+ return lifeTimeCount;
98
+ }
99
+
100
+ function _adjust_publishingEnable(publishingEnabled?: boolean | null): boolean {
101
+ return publishingEnabled === null || publishingEnabled === undefined ? true : !!publishingEnabled;
102
+ }
103
+
104
+ function _adjust_maxNotificationsPerPublish(maxNotificationsPerPublish?: number): number {
105
+ assert(Subscription.maxNotificationPerPublishHighLimit > 0, "Subscription.maxNotificationPerPublishHighLimit must be positive");
106
+
107
+ maxNotificationsPerPublish = maxNotificationsPerPublish || 0;
108
+ assert(typeof maxNotificationsPerPublish === "number");
109
+
110
+ // must be strictly positive
111
+ maxNotificationsPerPublish = maxNotificationsPerPublish >= 0 ? maxNotificationsPerPublish : 0;
112
+
113
+ if (maxNotificationsPerPublish === 0) {
114
+ // if zero then => use our HighLimit
115
+ maxNotificationsPerPublish = Subscription.maxNotificationPerPublishHighLimit;
116
+ } else {
117
+ // if not zero then should be capped by maxNotificationPerPublishHighLimit
118
+ maxNotificationsPerPublish = Math.min(Subscription.maxNotificationPerPublishHighLimit, maxNotificationsPerPublish);
119
+ }
120
+
121
+ assert(maxNotificationsPerPublish !== 0 && maxNotificationsPerPublish <= Subscription.maxNotificationPerPublishHighLimit);
122
+ return maxNotificationsPerPublish;
123
+ }
124
+
125
+ function w(s: string | number, length: number): string {
126
+ return ("000" + s).substr(-length);
127
+ }
128
+
129
+ function t(d: Date): string {
130
+ return w(d.getHours(), 2) + ":" + w(d.getMinutes(), 2) + ":" + w(d.getSeconds(), 2) + ":" + w(d.getMilliseconds(), 3);
131
+ }
132
+
133
+ function _getSequenceNumbers(arr: NotificationMessage[]): number[] {
134
+ return arr.map((notificationMessage) => notificationMessage.sequenceNumber);
135
+ }
136
+
137
+ function analyseEventFilterResult(node: BaseNode, eventFilter: EventFilter): EventFilterResult {
138
+ /* istanbul ignore next */
139
+ if (!(eventFilter instanceof EventFilter)) {
140
+ throw new Error("Internal Error");
141
+ }
142
+
143
+ const selectClauseResults = checkSelectClauses(node as UAObjectType, eventFilter.selectClauses || []);
144
+
145
+ const whereClauseResult = new ContentFilterResult();
146
+
147
+ return new EventFilterResult({
148
+ selectClauseDiagnosticInfos: [],
149
+ selectClauseResults,
150
+ whereClauseResult
151
+ });
152
+ }
153
+
154
+ function analyseDataChangeFilterResult(node: BaseNode, dataChangeFilter: DataChangeFilter): null {
155
+ assert(dataChangeFilter instanceof DataChangeFilter);
156
+ // the opcua specification doesn't provide dataChangeFilterResult
157
+ return null;
158
+ }
159
+
160
+ function analyseAggregateFilterResult(node: BaseNode, aggregateFilter: AggregateFilter): AggregateFilterResult {
161
+ assert(aggregateFilter instanceof AggregateFilter);
162
+ return new AggregateFilterResult({});
163
+ }
164
+
165
+ function _process_filter(node: BaseNode, filter: any): EventFilterResult | AggregateFilterResult | null {
166
+ if (!filter) {
167
+ return null;
168
+ }
169
+
170
+ if (filter instanceof EventFilter) {
171
+ return analyseEventFilterResult(node, filter);
172
+ } else if (filter instanceof DataChangeFilter) {
173
+ return analyseDataChangeFilterResult(node, filter);
174
+ } else if (filter instanceof AggregateFilter) {
175
+ return analyseAggregateFilterResult(node, filter);
176
+ }
177
+ // istanbul ignore next
178
+ throw new Error("invalid filter");
179
+ }
180
+
181
+ /**
182
+ * @private
183
+ */
184
+ function createSubscriptionDiagnostics(subscription: Subscription): SubscriptionDiagnosticsDataTypePriv {
185
+ assert(subscription instanceof Subscription);
186
+
187
+ const subscriptionDiagnostics = new SubscriptionDiagnosticsDataType({});
188
+
189
+ const subscription_subscriptionDiagnostics = subscriptionDiagnostics as any;
190
+ subscription_subscriptionDiagnostics.$subscription = subscription;
191
+ // "sessionId"
192
+ subscription_subscriptionDiagnostics.__defineGetter__(
193
+ "sessionId",
194
+ function (this: SubscriptionDiagnosticsDataTypePriv): NodeId {
195
+ if (!this.$subscription) {
196
+ return NodeId.nullNodeId;
197
+ }
198
+ return this.$subscription.getSessionId();
199
+ }
200
+ );
201
+ subscription_subscriptionDiagnostics.__defineGetter__(
202
+ "subscriptionId",
203
+ function (this: SubscriptionDiagnosticsDataTypePriv): number {
204
+ if (!this.$subscription) {
205
+ return 0;
206
+ }
207
+ return this.$subscription.id;
208
+ }
209
+ );
210
+ subscription_subscriptionDiagnostics.__defineGetter__("priority", function (this: SubscriptionDiagnosticsDataTypePriv): number {
211
+ if (!this.$subscription) {
212
+ return 0;
213
+ }
214
+ return this.$subscription.priority;
215
+ });
216
+ subscription_subscriptionDiagnostics.__defineGetter__(
217
+ "publishingInterval",
218
+ function (this: SubscriptionDiagnosticsDataTypePriv): number {
219
+ if (!this.$subscription) {
220
+ return 0;
221
+ }
222
+ return this.$subscription.publishingInterval;
223
+ }
224
+ );
225
+ subscription_subscriptionDiagnostics.__defineGetter__("maxLifetimeCount", function (this: SubscriptionDiagnosticsDataTypePriv) {
226
+ return this.$subscription.lifeTimeCount;
227
+ });
228
+ subscription_subscriptionDiagnostics.__defineGetter__(
229
+ "maxKeepAliveCount",
230
+ function (this: SubscriptionDiagnosticsDataTypePriv): number {
231
+ if (!this.$subscription) {
232
+ return 0;
233
+ }
234
+ return this.$subscription.maxKeepAliveCount;
235
+ }
236
+ );
237
+ subscription_subscriptionDiagnostics.__defineGetter__(
238
+ "maxNotificationsPerPublish",
239
+ function (this: SubscriptionDiagnosticsDataTypePriv): number {
240
+ if (!this.$subscription) {
241
+ return 0;
242
+ }
243
+ return this.$subscription.maxNotificationsPerPublish;
244
+ }
245
+ );
246
+ subscription_subscriptionDiagnostics.__defineGetter__(
247
+ "publishingEnabled",
248
+ function (this: SubscriptionDiagnosticsDataTypePriv): boolean {
249
+ if (!this.$subscription) {
250
+ return false;
251
+ }
252
+ return this.$subscription.publishingEnabled;
253
+ }
254
+ );
255
+ subscription_subscriptionDiagnostics.__defineGetter__(
256
+ "monitoredItemCount",
257
+ function (this: SubscriptionDiagnosticsDataTypePriv): number {
258
+ if (!this.$subscription) {
259
+ return 0;
260
+ }
261
+ return this.$subscription.monitoredItemCount;
262
+ }
263
+ );
264
+ subscription_subscriptionDiagnostics.__defineGetter__(
265
+ "nextSequenceNumber",
266
+ function (this: SubscriptionDiagnosticsDataTypePriv): number {
267
+ if (!this.$subscription) {
268
+ return 0;
269
+ }
270
+ return this.$subscription._get_future_sequence_number();
271
+ }
272
+ );
273
+ subscription_subscriptionDiagnostics.__defineGetter__(
274
+ "disabledMonitoredItemCount",
275
+ function (this: SubscriptionDiagnosticsDataTypePriv): number {
276
+ if (!this.$subscription) {
277
+ return 0;
278
+ }
279
+ return this.$subscription.disabledMonitoredItemCount;
280
+ }
281
+ );
282
+
283
+ /* those member of self.subscriptionDiagnostics are handled directly
284
+
285
+ modifyCount
286
+ enableCount,
287
+ disableCount,
288
+ republishRequestCount,
289
+ notificationsCount,
290
+ publishRequestCount,
291
+ dataChangeNotificationsCount,
292
+ eventNotificationsCount,
293
+ */
294
+
295
+ /*
296
+ those members are not updated yet in the code :
297
+ "republishMessageRequestCount",
298
+ "republishMessageCount",
299
+ "transferRequestCount",
300
+ "transferredToAltClientCount",
301
+ "transferredToSameClientCount",
302
+ "latePublishRequestCount",
303
+ "currentKeepAliveCount",
304
+ "currentLifetimeCount",
305
+ "unacknowledgedMessageCount",
306
+ "discardedMessageCount",
307
+ "monitoringQueueOverflowCount",
308
+ "eventQueueOverFlowCount"
309
+ */
310
+ // add object in Variable SubscriptionDiagnosticArray (i=2290) ( Array of SubscriptionDiagnostics)
311
+ // add properties in Variable to reflect
312
+ return subscriptionDiagnostics as SubscriptionDiagnosticsDataTypePriv;
313
+ }
314
+
315
+ export interface SubscriptionOptions {
316
+ sessionId?: NodeId;
317
+ /**
318
+ * (default:1000) the publishing interval.
319
+ */
320
+ publishingInterval?: number;
321
+ /**
322
+ * (default:10) the max Life Time Count
323
+ */
324
+ maxKeepAliveCount?: number;
325
+
326
+ lifeTimeCount?: number;
327
+ /**
328
+ * (default:true)
329
+ */
330
+ publishingEnabled?: boolean;
331
+ /**
332
+ * (default:0)
333
+ */
334
+ maxNotificationsPerPublish?: number;
335
+ /**
336
+ * subscription priority Byte:(0-255)
337
+ */
338
+ priority?: number;
339
+
340
+ publishEngine?: IServerSidePublishEngine;
341
+ /**
342
+ * a unique identifier
343
+ */
344
+ id?: number;
345
+ }
346
+
347
+ let g_monitoredItemId = Math.ceil(Math.random() * 100000);
348
+
349
+ function getNextMonitoredItemId() {
350
+ return g_monitoredItemId++;
351
+ }
352
+
353
+ // function myFilter<T>(t1: any, chunk: any[]): T[] {
354
+ // return chunk.filter(filter_instanceof.bind(null, t1));
355
+ // }
356
+
357
+ // function makeNotificationData(notifications_chunk: QueueItem): NotificationData {
358
+ // const dataChangedNotificationData = myFilter<MonitoredItemNotification>(MonitoredItemNotification, notifications_chunk);
359
+ // const eventNotificationListData = myFilter<EventFieldList>(EventFieldList, notifications_chunk);
360
+
361
+ // assert(notifications_chunk.length === dataChangedNotificationData.length + eventNotificationListData.length);
362
+
363
+ // const notifications: (DataChangeNotification | EventNotificationList)[] = [];
364
+
365
+ // // add dataChangeNotification
366
+ // if (dataChangedNotificationData.length) {
367
+ // const dataChangeNotification = new DataChangeNotification({
368
+ // diagnosticInfos: [],
369
+ // monitoredItems: dataChangedNotificationData
370
+ // });
371
+ // notifications.push(dataChangeNotification);
372
+ // }
373
+
374
+ // // add dataChangeNotification
375
+ // if (eventNotificationListData.length) {
376
+ // const eventNotificationList = new EventNotificationList({
377
+ // events: eventNotificationListData
378
+ // });
379
+ // notifications.push(eventNotificationList);
380
+ // }
381
+ // return notifications.length === 0 ? null : notifications;
382
+ // }
383
+ const INVALID_ID = -1;
384
+
385
+ export type Notification = DataChangeNotification | EventNotificationList | StatusChangeNotification;
386
+ export type Counter = number;
387
+
388
+ export interface ModifySubscriptionParameters {
389
+ /**
390
+ * requestedPublishingInterval =0 means fastest possible
391
+ */
392
+ requestedPublishingInterval?: Duration;
393
+ /*
394
+ * requestedLifetimeCount=0 means no change
395
+ */
396
+ requestedLifetimeCount?: Counter;
397
+ /**
398
+ * requestedMaxKeepAliveCount ===0 means no change
399
+ */
400
+ requestedMaxKeepAliveCount?: Counter;
401
+ maxNotificationsPerPublish?: Counter;
402
+ priority?: Byte;
403
+ }
404
+
405
+ export interface GetMonitoredItemsResult {
406
+ /**
407
+ * array of serverHandles for all MonitoredItems of the subscription
408
+ * identified by subscriptionId.
409
+ */
410
+ serverHandles: number[];
411
+ /**
412
+ * array of clientHandles for all MonitoredItems of the subscription
413
+ * identified by subscriptionId.
414
+ */
415
+ clientHandles: number[];
416
+ statusCode: StatusCode;
417
+ }
418
+
419
+ export interface InternalNotification {
420
+ monitoredItemId?: number;
421
+ notification: QueueItem | StatusChangeNotification;
422
+ publishTime: Date;
423
+ start_tick: number;
424
+ }
425
+
426
+ export interface InternalCreateMonitoredItemResult {
427
+ monitoredItem?: MonitoredItem;
428
+ monitoredItemCreateRequest: MonitoredItemCreateRequest;
429
+ createResult: MonitoredItemCreateResult;
430
+ }
431
+
432
+ export interface MonitoredItemBase {
433
+ node: any | null;
434
+ }
435
+ export type CreateMonitoredItemHook = (subscription: Subscription, monitoredItem: MonitoredItemBase) => Promise<StatusCode>;
436
+ export type DeleteMonitoredItemHook = (subscription: Subscription, monitoredItem: MonitoredItemBase) => Promise<StatusCode>;
437
+
438
+ /**
439
+ * The Subscription class used in the OPCUA server side.
440
+ */
441
+ export class Subscription extends EventEmitter {
442
+ public static minimumPublishingInterval: number = 50; // fastest possible
443
+ public static defaultPublishingInterval: number = 1000; // one second
444
+ public static maximumPublishingInterval: number = 1000 * 60 * 60 * 24 * 15; // 15 days
445
+ public static maxNotificationPerPublishHighLimit: number = 1000;
446
+ public static maxMonitoredItemCount: number = 20000;
447
+
448
+ public static registry = new ObjectRegistry();
449
+
450
+ public sessionId: NodeId;
451
+ public publishEngine?: IServerSidePublishEngine;
452
+ public id: number;
453
+ public priority: number;
454
+ /**
455
+ * the Subscription publishing interval
456
+ * @default 1000
457
+ */
458
+ public publishingInterval: number;
459
+ /**
460
+ * The keep alive count defines how many times the publish interval need to
461
+ * expires without having notifications available before the server send an
462
+ * empty message.
463
+ * OPCUA Spec says: a value of 0 is invalid.
464
+ * @default 10
465
+ *
466
+ */
467
+ public maxKeepAliveCount: number;
468
+ /**
469
+ * The life time count defines how many times the publish interval expires without
470
+ * having a connection to the client to deliver data.
471
+ * If the life time count reaches maxKeepAliveCount, the subscription will
472
+ * automatically terminate.
473
+ * OPCUA Spec: The life-time count shall be a minimum of three times the keep keep-alive count.
474
+ *
475
+ * Note: this has to be interpreted as without having a PublishRequest available
476
+ * @default 1
477
+ */
478
+ public lifeTimeCount: number;
479
+ /**
480
+ * The maximum number of notifications that the Client wishes to receive in a
481
+ * single Publish response. A value of zero indicates that there is no limit.
482
+ * The number of notifications per Publish is the sum of monitoredItems in the
483
+ * DataChangeNotification and events in the EventNotificationList.
484
+ *
485
+ * @property maxNotificationsPerPublish
486
+ * @default 0
487
+ */
488
+ public maxNotificationsPerPublish: number;
489
+ public publishingEnabled: boolean;
490
+ public subscriptionDiagnostics: SubscriptionDiagnosticsDataTypePriv;
491
+ public publishIntervalCount: number;
492
+ /**
493
+ * number of monitored Item
494
+ */
495
+ public monitoredItemIdCounter: number;
496
+
497
+ public state: SubscriptionState;
498
+ public messageSent: boolean;
499
+ public $session?: ServerSession;
500
+
501
+ private _life_time_counter: number;
502
+ private _keep_alive_counter: number = 0;
503
+ private _pending_notifications: Queue<InternalNotification>;
504
+ private _sent_notification_messages: NotificationMessage[];
505
+ private readonly _sequence_number_generator: SequenceNumberGenerator;
506
+ private readonly monitoredItems: { [key: number]: MonitoredItem };
507
+ private timerId: any;
508
+ private _hasUncollectedMonitoredItemNotifications: boolean = false;
509
+
510
+ constructor(options: SubscriptionOptions) {
511
+ super();
512
+
513
+ options = options || {};
514
+
515
+ Subscription.registry.register(this);
516
+
517
+ this.sessionId = options.sessionId || NodeId.nullNodeId;
518
+ assert(this.sessionId instanceof NodeId, "expecting a sessionId NodeId");
519
+
520
+ this.publishEngine = options.publishEngine!;
521
+
522
+ this.id = options.id || INVALID_ID;
523
+
524
+ this.priority = options.priority || 0;
525
+
526
+ this.publishingInterval = _adjust_publishing_interval(options.publishingInterval);
527
+
528
+ this.maxKeepAliveCount = _adjust_maxKeepAliveCount(options.maxKeepAliveCount); // , this.publishingInterval);
529
+
530
+ this.resetKeepAliveCounter();
531
+
532
+ this.lifeTimeCount = _adjust_lifeTimeCount(options.lifeTimeCount || 0, this.maxKeepAliveCount, this.publishingInterval);
533
+
534
+ this.maxNotificationsPerPublish = _adjust_maxNotificationsPerPublish(options.maxNotificationsPerPublish);
535
+
536
+ this._life_time_counter = 0;
537
+ this.resetLifeTimeCounter();
538
+
539
+ // notification message that are ready to be sent to the client
540
+ this._pending_notifications = new Queue<InternalNotification>();
541
+
542
+ this._sent_notification_messages = [];
543
+
544
+ this._sequence_number_generator = new SequenceNumberGenerator();
545
+
546
+ // initial state of the subscription
547
+ this.state = SubscriptionState.CREATING;
548
+
549
+ this.publishIntervalCount = 0;
550
+
551
+ this.monitoredItems = {}; // monitored item map
552
+
553
+ this.monitoredItemIdCounter = 0;
554
+
555
+ this.publishingEnabled = _adjust_publishingEnable(options.publishingEnabled);
556
+
557
+ this.subscriptionDiagnostics = createSubscriptionDiagnostics(this);
558
+
559
+ // A boolean value that is set to TRUE to mean that either a NotificationMessage or a keep-alive
560
+ // Message has been sent on the Subscription. It is a flag that is used to ensure that either a
561
+ // NotificationMessage or a keep-alive Message is sent out the first time the publishing
562
+ // timer expires.
563
+ this.messageSent = false;
564
+
565
+ this.timerId = null;
566
+ this._start_timer();
567
+
568
+ debugLog(chalk.green(`creating subscription ${this.id}`));
569
+ }
570
+
571
+ public getSessionId(): NodeId {
572
+ return this.sessionId;
573
+ }
574
+
575
+ public toString(): string {
576
+ let str = "Subscription:\n";
577
+ str += " subscriptionId " + this.id + "\n";
578
+ str += " sessionId " + this.getSessionId().toString() + "\n";
579
+
580
+ str += " publishingEnabled " + this.publishingEnabled + "\n";
581
+ str += " maxKeepAliveCount " + this.maxKeepAliveCount + "\n";
582
+ str += " publishingInterval " + this.publishingInterval + "\n";
583
+ str += " lifeTimeCount " + this.lifeTimeCount + "\n";
584
+ str += " maxKeepAliveCount " + this.maxKeepAliveCount + "\n";
585
+ return str;
586
+ }
587
+
588
+ /**
589
+ * modify subscription parameters
590
+ * @param param
591
+ */
592
+ public modify(param: ModifySubscriptionParameters): void {
593
+ // update diagnostic counter
594
+ this.subscriptionDiagnostics.modifyCount += 1;
595
+
596
+ const publishingInterval_old = this.publishingInterval;
597
+
598
+ param.requestedPublishingInterval = param.requestedPublishingInterval || 0;
599
+ param.requestedMaxKeepAliveCount = param.requestedMaxKeepAliveCount || this.maxKeepAliveCount;
600
+ param.requestedLifetimeCount = param.requestedLifetimeCount || this.lifeTimeCount;
601
+
602
+ this.publishingInterval = _adjust_publishing_interval(param.requestedPublishingInterval);
603
+ this.maxKeepAliveCount = _adjust_maxKeepAliveCount(param.requestedMaxKeepAliveCount);
604
+
605
+ this.lifeTimeCount = _adjust_lifeTimeCount(param.requestedLifetimeCount, this.maxKeepAliveCount, this.publishingInterval);
606
+
607
+ this.maxNotificationsPerPublish = _adjust_maxNotificationsPerPublish(param.maxNotificationsPerPublish || 0);
608
+ this.priority = param.priority || 0;
609
+
610
+ this.resetLifeTimeAndKeepAliveCounters();
611
+
612
+ if (publishingInterval_old !== this.publishingInterval) {
613
+ // todo
614
+ }
615
+ this._stop_timer();
616
+ this._start_timer();
617
+ }
618
+
619
+ /**
620
+ * set publishing mode
621
+ * @param publishingEnabled
622
+ */
623
+ public setPublishingMode(publishingEnabled: boolean): StatusCode {
624
+ this.publishingEnabled = !!publishingEnabled;
625
+ // update diagnostics
626
+ if (this.publishingEnabled) {
627
+ this.subscriptionDiagnostics.enableCount += 1;
628
+ } else {
629
+ this.subscriptionDiagnostics.disableCount += 1;
630
+ }
631
+
632
+ this.resetLifeTimeCounter();
633
+
634
+ if (!publishingEnabled && this.state !== SubscriptionState.CLOSED) {
635
+ this.state = SubscriptionState.NORMAL;
636
+ }
637
+ return StatusCodes.Good;
638
+ }
639
+
640
+ /**
641
+ * @private
642
+ */
643
+ public get keepAliveCounterHasExpired(): boolean {
644
+ return this._keep_alive_counter >= this.maxKeepAliveCount;
645
+ }
646
+
647
+ /**
648
+ * Reset the Lifetime Counter Variable to the value specified for the lifetime of a Subscription in
649
+ * the CreateSubscription Service( 5.13.2).
650
+ * @private
651
+ */
652
+ public resetLifeTimeCounter() {
653
+ this._life_time_counter = 0;
654
+ }
655
+
656
+ /**
657
+ * @private
658
+ */
659
+ public increaseLifeTimeCounter() {
660
+ this._life_time_counter += 1;
661
+ }
662
+
663
+ /**
664
+ * True if the subscription life time has expired.
665
+ *
666
+ */
667
+ public get lifeTimeHasExpired(): boolean {
668
+ assert(this.lifeTimeCount > 0);
669
+ return this._life_time_counter >= this.lifeTimeCount;
670
+ }
671
+
672
+ /**
673
+ * number of milliseconds before this subscription times out (lifeTimeHasExpired === true);
674
+ */
675
+ public get timeToExpiration(): number {
676
+ return (this.lifeTimeCount - this._life_time_counter) * this.publishingInterval;
677
+ }
678
+
679
+ public get timeToKeepAlive(): number {
680
+ return (this.maxKeepAliveCount - this._keep_alive_counter) * this.publishingInterval;
681
+ }
682
+
683
+ /**
684
+ * Terminates the subscription.
685
+ * Calling this method will also remove any monitored items.
686
+ *
687
+ */
688
+ public terminate() {
689
+ assert(arguments.length === 0);
690
+ debugLog("Subscription#terminate status", SubscriptionState[this.state]);
691
+
692
+ if (this.state === SubscriptionState.CLOSED) {
693
+ // todo verify if asserting is required here
694
+ return;
695
+ }
696
+
697
+ // stop timer
698
+ this._stop_timer();
699
+
700
+ debugLog("terminating Subscription ", this.id, " with ", this.monitoredItemCount, " monitored items");
701
+
702
+ // dispose all monitoredItem
703
+ const keys = Object.keys(this.monitoredItems);
704
+
705
+ for (const key of keys) {
706
+ const status = this.removeMonitoredItem(parseInt(key, 10));
707
+ assert(status === StatusCodes.Good);
708
+ }
709
+ assert(this.monitoredItemCount === 0);
710
+
711
+ if (this.$session) {
712
+ this.$session._unexposeSubscriptionDiagnostics(this);
713
+ }
714
+ this.state = SubscriptionState.CLOSED;
715
+
716
+ /**
717
+ * notify the subscription owner that the subscription has been terminated.
718
+ * @event "terminated"
719
+ */
720
+ this.emit("terminated");
721
+ if (this.publishEngine) {
722
+ this.publishEngine.on_close_subscription(this);
723
+ }
724
+ }
725
+
726
+ public setTriggering(
727
+ triggeringItemId: number,
728
+ linksToAdd: number[] | null,
729
+ linksToRemove: number[] | null
730
+ ): { statusCode: StatusCode; addResults: StatusCode[]; removeResults: StatusCode[] } {
731
+ /** Bad_NothingToDo, Bad_TooManyOperations,Bad_SubscriptionIdInvalid, Bad_MonitoredItemIdInvalid */
732
+ linksToAdd = linksToAdd || [];
733
+ linksToRemove = linksToRemove || [];
734
+
735
+ if (linksToAdd.length === 0 && linksToRemove.length === 0) {
736
+ return { statusCode: StatusCodes.BadNothingToDo, addResults: [], removeResults: [] };
737
+ }
738
+ const triggeringItem = this.getMonitoredItem(triggeringItemId);
739
+
740
+ const monitoredItemsToAdd = linksToAdd.map((id) => this.getMonitoredItem(id));
741
+ const monitoredItemsToRemove = linksToRemove.map((id) => this.getMonitoredItem(id));
742
+
743
+ if (!triggeringItem) {
744
+ const removeResults1: StatusCode[] = monitoredItemsToRemove.map((m) =>
745
+ m ? StatusCodes.Good : StatusCodes.BadMonitoredItemIdInvalid
746
+ );
747
+ const addResults1: StatusCode[] = monitoredItemsToAdd.map((m) =>
748
+ m ? StatusCodes.Good : StatusCodes.BadMonitoredItemIdInvalid
749
+ );
750
+ return {
751
+ statusCode: StatusCodes.BadMonitoredItemIdInvalid,
752
+
753
+ addResults: addResults1,
754
+ removeResults: removeResults1
755
+ };
756
+ }
757
+ //
758
+ // note: it seems that CTT imposed that we do remove before add
759
+ const removeResults = monitoredItemsToRemove.map((m) =>
760
+ !m ? StatusCodes.BadMonitoredItemIdInvalid : triggeringItem.removeLinkItem(m.monitoredItemId)
761
+ );
762
+ const addResults = monitoredItemsToAdd.map((m) =>
763
+ !m ? StatusCodes.BadMonitoredItemIdInvalid : triggeringItem.addLinkItem(m.monitoredItemId)
764
+ );
765
+
766
+ const statusCode: StatusCode = StatusCodes.Good;
767
+
768
+ // do binding
769
+
770
+ return {
771
+ statusCode,
772
+
773
+ addResults,
774
+ removeResults
775
+ };
776
+ }
777
+ public dispose() {
778
+ if (doDebug) {
779
+ debugLog("Subscription#dispose", this.id, this.monitoredItemCount);
780
+ }
781
+
782
+ assert(this.monitoredItemCount === 0, "MonitoredItems haven't been deleted first !!!");
783
+ assert(this.timerId === null, "Subscription timer haven't been terminated");
784
+
785
+ if (this.subscriptionDiagnostics) {
786
+ (this.subscriptionDiagnostics as SubscriptionDiagnosticsDataTypePriv).$subscription = null as any as Subscription;
787
+ }
788
+
789
+ this.publishEngine = undefined;
790
+ this._pending_notifications.clear();
791
+ this._sent_notification_messages = [];
792
+
793
+ this.sessionId = NodeId.nullNodeId;
794
+
795
+ this.$session = undefined;
796
+ this.removeAllListeners();
797
+
798
+ Subscription.registry.unregister(this);
799
+ }
800
+
801
+ public get aborted(): boolean {
802
+ const session = this.$session;
803
+ if (!session) {
804
+ return true;
805
+ }
806
+ return session.aborted;
807
+ }
808
+
809
+ /**
810
+ * number of pending notifications
811
+ */
812
+ public get pendingNotificationsCount(): number {
813
+ return this._pending_notifications ? this._pending_notifications.size : 0;
814
+ }
815
+
816
+ /**
817
+ * is 'true' if there are pending notifications for this subscription. (i.e moreNotifications)
818
+ */
819
+ public get hasPendingNotifications(): boolean {
820
+ return this.pendingNotificationsCount > 0;
821
+ }
822
+
823
+ /**
824
+ * number of sent notifications
825
+ */
826
+ public get sentNotificationMessageCount(): number {
827
+ return this._sent_notification_messages.length;
828
+ }
829
+
830
+ /**
831
+ * @internal
832
+ */
833
+ public _flushSentNotifications() {
834
+ const tmp = this._sent_notification_messages;
835
+ this._sent_notification_messages = [];
836
+ return tmp;
837
+ }
838
+ /**
839
+ * number of monitored items handled by this subscription
840
+ */
841
+ public get monitoredItemCount(): number {
842
+ return Object.keys(this.monitoredItems).length;
843
+ }
844
+
845
+ /**
846
+ * number of disabled monitored items.
847
+ */
848
+ public get disabledMonitoredItemCount(): number {
849
+ return Object.values(this.monitoredItems).reduce((cumul: any, monitoredItem: MonitoredItem) => {
850
+ return cumul + (monitoredItem.monitoringMode === MonitoringMode.Disabled ? 1 : 0);
851
+ }, 0);
852
+ }
853
+
854
+ /**
855
+ * The number of unacknowledged messages saved in the republish queue.
856
+ */
857
+ public get unacknowledgedMessageCount(): number {
858
+ return this.subscriptionDiagnostics.unacknowledgedMessageCount;
859
+ }
860
+
861
+ /**
862
+ * adjust monitored item sampling interval
863
+ * - an samplingInterval ===0 means that we use a event-base model ( no sampling)
864
+ * - otherwise the sampling is adjusted
865
+ * @private
866
+ */
867
+ public adjustSamplingInterval(samplingInterval: number, node: BaseNode) {
868
+ if (samplingInterval < 0) {
869
+ // - The value -1 indicates that the default sampling interval defined by the publishing
870
+ // interval of the Subscription is requested.
871
+ // - Any negative number is interpreted as -1.
872
+ samplingInterval = this.publishingInterval;
873
+ } else if (samplingInterval === 0) {
874
+ // OPCUA 1.0.3 Part 4 - 5.12.1.2
875
+ // The value 0 indicates that the Server should use the fastest practical rate.
876
+
877
+ // The fastest supported sampling interval may be equal to 0, which indicates
878
+ // that the data item is exception-based rather than being sampled at some period.
879
+ // An exception-based model means that the underlying system does not require
880
+ // sampling and reports data changes.
881
+
882
+ const dataValueSamplingInterval = node.readAttribute(
883
+ SessionContext.defaultContext,
884
+ AttributeIds.MinimumSamplingInterval
885
+ );
886
+
887
+ // TODO if attributeId === AttributeIds.Value : sampling interval required here
888
+ if (dataValueSamplingInterval.statusCode === StatusCodes.Good) {
889
+ // node provides a Minimum sampling interval ...
890
+ samplingInterval = dataValueSamplingInterval.value.value;
891
+ assert(samplingInterval >= 0 && samplingInterval <= MonitoredItem.maximumSamplingInterval);
892
+
893
+ // note : at this stage, a samplingInterval===0 means that the data item is really exception-based
894
+ }
895
+ } else if (samplingInterval < MonitoredItem.minimumSamplingInterval) {
896
+ samplingInterval = MonitoredItem.minimumSamplingInterval;
897
+ } else if (samplingInterval > MonitoredItem.maximumSamplingInterval) {
898
+ // If the requested samplingInterval is higher than the
899
+ // maximum sampling interval supported by the Server, the maximum sampling
900
+ // interval is returned.
901
+ samplingInterval = MonitoredItem.maximumSamplingInterval;
902
+ }
903
+
904
+ const node_minimumSamplingInterval =
905
+ node && (node as any).minimumSamplingInterval ? (node as any).minimumSamplingInterval : 0;
906
+
907
+ samplingInterval = Math.max(samplingInterval, node_minimumSamplingInterval);
908
+
909
+ return samplingInterval;
910
+ }
911
+
912
+ /**
913
+ * create a monitored item
914
+ * @param addressSpace - address space
915
+ * @param timestampsToReturn - the timestamp to return
916
+ * @param monitoredItemCreateRequest - the parameters describing the monitored Item to create
917
+ */
918
+ public preCreateMonitoredItem(
919
+ addressSpace: AddressSpace,
920
+ timestampsToReturn: TimestampsToReturn,
921
+ monitoredItemCreateRequest: MonitoredItemCreateRequest
922
+ ): InternalCreateMonitoredItemResult {
923
+ assert(monitoredItemCreateRequest instanceof MonitoredItemCreateRequest);
924
+
925
+ function handle_error(statusCode: StatusCode): InternalCreateMonitoredItemResult {
926
+ return {
927
+ createResult: new MonitoredItemCreateResult({ statusCode }),
928
+ monitoredItemCreateRequest
929
+ };
930
+ }
931
+
932
+ const itemToMonitor = monitoredItemCreateRequest.itemToMonitor;
933
+
934
+ const node = addressSpace.findNode(itemToMonitor.nodeId);
935
+ if (!node) {
936
+ return handle_error(StatusCodes.BadNodeIdUnknown);
937
+ }
938
+
939
+ if (itemToMonitor.attributeId === AttributeIds.Value && !(node.nodeClass === NodeClass.Variable)) {
940
+ // AttributeIds.Value is only valid for monitoring value of UAVariables.
941
+ return handle_error(StatusCodes.BadAttributeIdInvalid);
942
+ }
943
+
944
+ if (itemToMonitor.attributeId === AttributeIds.INVALID) {
945
+ return handle_error(StatusCodes.BadAttributeIdInvalid);
946
+ }
947
+
948
+ if (!itemToMonitor.indexRange.isValid()) {
949
+ return handle_error(StatusCodes.BadIndexRangeInvalid);
950
+ }
951
+
952
+ // check dataEncoding applies only on Values
953
+ if (itemToMonitor.dataEncoding.name && itemToMonitor.attributeId !== AttributeIds.Value) {
954
+ return handle_error(StatusCodes.BadDataEncodingInvalid);
955
+ }
956
+
957
+ // check dataEncoding
958
+ if (!isValidDataEncoding(itemToMonitor.dataEncoding)) {
959
+ return handle_error(StatusCodes.BadDataEncodingUnsupported);
960
+ }
961
+
962
+ // check that item can be read by current user session
963
+
964
+ // filter
965
+ const requestedParameters = monitoredItemCreateRequest.requestedParameters;
966
+ const filter = requestedParameters.filter;
967
+ const statusCodeFilter = validateFilter(filter, itemToMonitor, node);
968
+ if (statusCodeFilter !== StatusCodes.Good) {
969
+ return handle_error(statusCodeFilter);
970
+ }
971
+ // xx var monitoringMode = monitoredItemCreateRequest.monitoringMode; // Disabled, Sampling, Reporting
972
+ // xx var requestedParameters = monitoredItemCreateRequest.requestedParameters;
973
+ // do we have enough room for new monitored items ?
974
+ if (this.monitoredItemCount >= Subscription.maxMonitoredItemCount) {
975
+ return handle_error(StatusCodes.BadTooManyMonitoredItems);
976
+ }
977
+ const createResult = this._createMonitoredItemStep2(timestampsToReturn, monitoredItemCreateRequest, node);
978
+
979
+ assert(createResult.statusCode === StatusCodes.Good);
980
+
981
+ const monitoredItem = this.getMonitoredItem(createResult.monitoredItemId);
982
+ // istanbul ignore next
983
+ if (!monitoredItem) {
984
+ throw new Error("internal error");
985
+ }
986
+
987
+ // TODO: fix old way to set node. !!!!
988
+ monitoredItem.setNode(node);
989
+
990
+ this.emit("monitoredItem", monitoredItem, itemToMonitor);
991
+
992
+ return { monitoredItem, monitoredItemCreateRequest, createResult };
993
+ }
994
+
995
+ public async applyOnMonitoredItem(functor: (monitoredItem: MonitoredItem) => Promise<void>): Promise<void> {
996
+ for (const m of Object.values(this.monitoredItems)) {
997
+ await functor(m);
998
+ }
999
+ }
1000
+
1001
+ public postCreateMonitoredItem(
1002
+ monitoredItem: MonitoredItem,
1003
+ monitoredItemCreateRequest: MonitoredItemCreateRequest,
1004
+ createResult: MonitoredItemCreateResult
1005
+ ): void {
1006
+ this._createMonitoredItemStep3(monitoredItem, monitoredItemCreateRequest);
1007
+ }
1008
+
1009
+ public createMonitoredItem(
1010
+ addressSpace: AddressSpace,
1011
+ timestampsToReturn: TimestampsToReturn,
1012
+ monitoredItemCreateRequest: MonitoredItemCreateRequest
1013
+ ): MonitoredItemCreateResult {
1014
+ const { monitoredItem, createResult } = this.preCreateMonitoredItem(
1015
+ addressSpace,
1016
+ timestampsToReturn,
1017
+ monitoredItemCreateRequest
1018
+ );
1019
+ this.postCreateMonitoredItem(monitoredItem!, monitoredItemCreateRequest, createResult);
1020
+ return createResult;
1021
+ }
1022
+ /**
1023
+ * get a monitoredItem by Id.
1024
+ * @param monitoredItemId : the id of the monitored item to get.
1025
+ * @return the monitored item matching monitoredItemId
1026
+ */
1027
+ public getMonitoredItem(monitoredItemId: number): MonitoredItem | null {
1028
+ return this.monitoredItems[monitoredItemId] || null;
1029
+ }
1030
+
1031
+ /**
1032
+ * remove a monitored Item from the subscription.
1033
+ * @param monitoredItemId : the id of the monitored item to get.
1034
+ */
1035
+ public removeMonitoredItem(monitoredItemId: number): StatusCode {
1036
+ debugLog("Removing monitoredIem ", monitoredItemId);
1037
+ if (!this.monitoredItems.hasOwnProperty(monitoredItemId.toString())) {
1038
+ return StatusCodes.BadMonitoredItemIdInvalid;
1039
+ }
1040
+
1041
+ const monitoredItem = this.monitoredItems[monitoredItemId];
1042
+
1043
+ monitoredItem.terminate();
1044
+
1045
+ monitoredItem.dispose();
1046
+
1047
+ /**
1048
+ *
1049
+ * notify that a monitored item has been removed from the subscription
1050
+ * @param monitoredItem {MonitoredItem}
1051
+ */
1052
+ this.emit("removeMonitoredItem", monitoredItem);
1053
+
1054
+ delete this.monitoredItems[monitoredItemId];
1055
+
1056
+ this._removePendingNotificationsFor(monitoredItemId);
1057
+ // flush pending notifications
1058
+ // assert(this._pending_notifications.size === 0);
1059
+ return StatusCodes.Good;
1060
+ }
1061
+
1062
+ /**
1063
+ * rue if monitored Item have uncollected Notifications
1064
+ */
1065
+ public get hasUncollectedMonitoredItemNotifications(): boolean {
1066
+ if (this._hasUncollectedMonitoredItemNotifications) {
1067
+ return true;
1068
+ }
1069
+ const keys = Object.keys(this.monitoredItems);
1070
+ const n = keys.length;
1071
+ for (let i = 0; i < n; i++) {
1072
+ const key = parseInt(keys[i], 10);
1073
+ const monitoredItem = this.monitoredItems[key];
1074
+ if (monitoredItem.hasMonitoredItemNotifications) {
1075
+ this._hasUncollectedMonitoredItemNotifications = true;
1076
+ return true;
1077
+ }
1078
+ }
1079
+ return false;
1080
+ }
1081
+
1082
+ public get subscriptionId() {
1083
+ return this.id;
1084
+ }
1085
+
1086
+ public getMessageForSequenceNumber(sequenceNumber: number): NotificationMessage | null {
1087
+ const notification_message = this._sent_notification_messages.find((e) => e.sequenceNumber === sequenceNumber);
1088
+ return notification_message || null;
1089
+ }
1090
+
1091
+ /**
1092
+ * returns true if the notification has expired
1093
+ * @param notification
1094
+ */
1095
+ public notificationHasExpired(notification: any): boolean {
1096
+ assert(notification.hasOwnProperty("start_tick"));
1097
+ assert(isFinite(notification.start_tick + this.maxKeepAliveCount));
1098
+ return notification.start_tick + this.maxKeepAliveCount < this.publishIntervalCount;
1099
+ }
1100
+
1101
+ /**
1102
+ * returns in an array the sequence numbers of the notifications that have been sent
1103
+ * and that haven't been acknowledged yet.
1104
+ */
1105
+ public getAvailableSequenceNumbers(): number[] {
1106
+ const availableSequenceNumbers = _getSequenceNumbers(this._sent_notification_messages);
1107
+ return availableSequenceNumbers;
1108
+ }
1109
+
1110
+ /**
1111
+ * acknowledges a notification identified by its sequence number
1112
+ */
1113
+ public acknowledgeNotification(sequenceNumber: number): StatusCode {
1114
+ debugLog("acknowledgeNotification ", sequenceNumber);
1115
+ let foundIndex = -1;
1116
+ this._sent_notification_messages.forEach((e: NotificationMessage, index: number) => {
1117
+ if (e.sequenceNumber === sequenceNumber) {
1118
+ foundIndex = index;
1119
+ }
1120
+ });
1121
+
1122
+ if (foundIndex === -1) {
1123
+ if (doDebug) {
1124
+ debugLog(chalk.red("acknowledging sequence FAILED !!! "), chalk.cyan(sequenceNumber.toString()));
1125
+ }
1126
+ return StatusCodes.BadSequenceNumberUnknown;
1127
+ } else {
1128
+ if (doDebug) {
1129
+ debugLog(chalk.yellow("acknowledging sequence "), chalk.cyan(sequenceNumber.toString()));
1130
+ }
1131
+ this._sent_notification_messages.splice(foundIndex, 1);
1132
+ this.subscriptionDiagnostics.unacknowledgedMessageCount--;
1133
+ return StatusCodes.Good;
1134
+ }
1135
+ }
1136
+
1137
+ /**
1138
+ * getMonitoredItems is used to get information about monitored items of a subscription.Its intended
1139
+ * use is defined in Part 4. This method is the implementation of the Standard OPCUA GetMonitoredItems Method.
1140
+ * from spec:
1141
+ * This method can be used to get the list of monitored items in a subscription if CreateMonitoredItems
1142
+ * failed due to a network interruption and the client does not know if the creation succeeded in the server.
1143
+ *
1144
+ */
1145
+ public getMonitoredItems(): GetMonitoredItemsResult {
1146
+ const result: GetMonitoredItemsResult = {
1147
+ clientHandles: [] as number[],
1148
+ serverHandles: [] as number[],
1149
+ statusCode: StatusCodes.Good
1150
+ };
1151
+ for (const monitoredItemId of Object.keys(this.monitoredItems)) {
1152
+ const monitoredItem = this.getMonitoredItem(parseInt(monitoredItemId, 10))!;
1153
+ result.clientHandles.push(monitoredItem.clientHandle!);
1154
+ // TODO: serverHandle is defined anywhere in the OPCUA Specification 1.02
1155
+ // I am not sure what shall be reported for serverHandle...
1156
+ // using monitoredItem.monitoredItemId instead...
1157
+ // May be a clarification in the OPCUA Spec is required.
1158
+ result.serverHandles.push(parseInt(monitoredItemId, 10));
1159
+ }
1160
+ return result;
1161
+ }
1162
+
1163
+ /**
1164
+ * @private
1165
+ */
1166
+ public async resendInitialValues(): Promise<void> {
1167
+ const promises: Promise<void>[] = [];
1168
+ for (const monitoredItem of Object.values(this.monitoredItems)) {
1169
+ assert(monitoredItem.clientHandle !== 4294967295);
1170
+ promises.push(monitoredItem.resendInitialValues());
1171
+ }
1172
+ await Promise.all(promises);
1173
+ this._harvestMonitoredItems();
1174
+ }
1175
+
1176
+ /**
1177
+ * @private
1178
+ */
1179
+ public notifyTransfer(): void {
1180
+ // OPCUA UA Spec 1.0.3 : part 3 - page 82 - 5.13.7 TransferSubscriptions:
1181
+ // If the Server transfers the Subscription to the new Session, the Server shall issue
1182
+ // a StatusChangeNotification notificationMessage with the status code
1183
+ // Good_SubscriptionTransferred to the old Session.
1184
+ debugLog(chalk.red(" Subscription => Notifying Transfer "));
1185
+
1186
+ const notificationData = new StatusChangeNotification({
1187
+ status: StatusCodes.GoodSubscriptionTransferred
1188
+ });
1189
+
1190
+ if (this.publishEngine!.pendingPublishRequestCount) {
1191
+ // the GoodSubscriptionTransferred can be processed immediately
1192
+ this._addNotificationMessage(notificationData);
1193
+ debugLog(chalk.red("pendingPublishRequestCount"), this.publishEngine?.pendingPublishRequestCount);
1194
+ this._publish_pending_notifications();
1195
+ } else {
1196
+ debugLog(chalk.red("Cannot send GoodSubscriptionTransferred => lets create a TransferredSubscription "));
1197
+ const ts = new TransferredSubscription({
1198
+ generator: this._sequence_number_generator,
1199
+ id: this.id,
1200
+ publishEngine: this.publishEngine
1201
+ });
1202
+
1203
+ ts._pending_notification = notificationData;
1204
+ (this.publishEngine as any)._closed_subscriptions.push(ts);
1205
+ }
1206
+ }
1207
+
1208
+ /**
1209
+ *
1210
+ * the server invokes the resetLifeTimeAndKeepAliveCounters method of the subscription
1211
+ * when the server has send a Publish Response, so that the subscription
1212
+ * can reset its life time counter.
1213
+ *
1214
+ * @private
1215
+ */
1216
+ public resetLifeTimeAndKeepAliveCounters() {
1217
+ this.resetLifeTimeCounter();
1218
+ this.resetKeepAliveCounter();
1219
+ }
1220
+
1221
+ private _updateCounters(notificationMessage: NotificationMessage) {
1222
+ for (const notificationData of notificationMessage.notificationData || []) {
1223
+ // update diagnostics
1224
+ if (notificationData instanceof DataChangeNotification) {
1225
+ const nbNotifs = notificationData.monitoredItems!.length;
1226
+ this.subscriptionDiagnostics.dataChangeNotificationsCount += nbNotifs;
1227
+ this.subscriptionDiagnostics.notificationsCount += nbNotifs;
1228
+ } else if (notificationData instanceof EventNotificationList) {
1229
+ const nbNotifs = notificationData.events!.length;
1230
+ this.subscriptionDiagnostics.eventNotificationsCount += nbNotifs;
1231
+ this.subscriptionDiagnostics.notificationsCount += nbNotifs;
1232
+ } else {
1233
+ assert(notificationData instanceof StatusChangeNotification);
1234
+ // TODO
1235
+ // note: :there is no way to count StatusChangeNotifications in opcua yet.
1236
+ }
1237
+ }
1238
+ }
1239
+ /**
1240
+ * _publish_pending_notifications send a "notification" event:
1241
+ *
1242
+ * @private
1243
+ * @precondition
1244
+ * - pendingPublishRequestCount > 0
1245
+ */
1246
+ public _publish_pending_notifications(): void {
1247
+ const publishEngine = this.publishEngine!;
1248
+ const subscriptionId = this.id;
1249
+ // preconditions
1250
+ assert(publishEngine!.pendingPublishRequestCount > 0);
1251
+ assert(this.hasPendingNotifications);
1252
+
1253
+ const notificationMessage = this._popNotificationToSend();
1254
+ if (notificationMessage.notificationData!.length === 0) {
1255
+ return; // nothing to do
1256
+ }
1257
+ const moreNotifications = this.hasPendingNotifications;
1258
+
1259
+ this.emit("notification", notificationMessage);
1260
+ // Update counters ....
1261
+ this._updateCounters(notificationMessage);
1262
+
1263
+ assert(notificationMessage.hasOwnProperty("sequenceNumber"));
1264
+ assert(notificationMessage.hasOwnProperty("notificationData"));
1265
+ // update diagnostics
1266
+ this.subscriptionDiagnostics.publishRequestCount += 1;
1267
+
1268
+ const response = new PublishResponse({
1269
+ moreNotifications,
1270
+ notificationMessage: {
1271
+ notificationData: notificationMessage.notificationData,
1272
+ sequenceNumber: this._get_next_sequence_number()
1273
+ },
1274
+ subscriptionId
1275
+ });
1276
+
1277
+ this._sent_notification_messages.push(response.notificationMessage);
1278
+
1279
+ // get available sequence number;
1280
+ const availableSequenceNumbers = this.getAvailableSequenceNumbers();
1281
+ assert(
1282
+ !response.notificationMessage ||
1283
+ availableSequenceNumbers[availableSequenceNumbers.length - 1] === response.notificationMessage.sequenceNumber
1284
+ );
1285
+ response.availableSequenceNumbers = availableSequenceNumbers;
1286
+
1287
+ publishEngine._send_response(this, response);
1288
+
1289
+ this.messageSent = true;
1290
+
1291
+ this.subscriptionDiagnostics.unacknowledgedMessageCount++;
1292
+
1293
+ this.resetLifeTimeAndKeepAliveCounters();
1294
+
1295
+ if (doDebug) {
1296
+ debugLog(
1297
+ "Subscription sending a notificationMessage subscriptionId=",
1298
+ subscriptionId,
1299
+ "sequenceNumber = ",
1300
+ notificationMessage.sequenceNumber.toString(),
1301
+ notificationMessage.notificationData?.map((x) => x?.constructor.name).join(" ")
1302
+ );
1303
+ // debugLog(notificationMessage.toString());
1304
+ }
1305
+
1306
+ if (this.state !== SubscriptionState.CLOSED) {
1307
+ assert(notificationMessage.notificationData!.length > 0, "We are not expecting a keep-alive message here");
1308
+ this.state = SubscriptionState.NORMAL;
1309
+ debugLog("subscription " + this.id + chalk.bgYellow(" set to NORMAL"));
1310
+ }
1311
+ }
1312
+
1313
+ public process_subscription() {
1314
+ assert(this.publishEngine!.pendingPublishRequestCount > 0);
1315
+
1316
+ if (!this.publishingEnabled) {
1317
+ // no publish to do, except keep alive
1318
+ debugLog(" -> no publish to do, except keep alive");
1319
+ this._process_keepAlive();
1320
+ return;
1321
+ }
1322
+
1323
+ if (!this.hasPendingNotifications && this.hasUncollectedMonitoredItemNotifications) {
1324
+ // collect notification from monitored items
1325
+ this._harvestMonitoredItems();
1326
+ }
1327
+
1328
+ // let process them first
1329
+ if (this.hasPendingNotifications) {
1330
+ this._publish_pending_notifications();
1331
+
1332
+ if (this.state === SubscriptionState.NORMAL && this.hasPendingNotifications) {
1333
+ // istanbul ignore next
1334
+ if (doDebug) {
1335
+ debugLog(" -> pendingPublishRequestCount > 0 " + "&& normal state => re-trigger tick event immediately ");
1336
+ }
1337
+
1338
+ // let process an new publish request
1339
+ setImmediate(this._tick.bind(this));
1340
+ }
1341
+ } else {
1342
+ this._process_keepAlive();
1343
+ }
1344
+ }
1345
+
1346
+ public _get_future_sequence_number(): number {
1347
+ return this._sequence_number_generator ? this._sequence_number_generator.future() : 0;
1348
+ }
1349
+
1350
+ private _process_keepAlive() {
1351
+ this.increaseKeepAliveCounter();
1352
+
1353
+ if (this.keepAliveCounterHasExpired) {
1354
+ debugLog(` -> _process_keepAlive => keepAliveCounterHasExpired`);
1355
+ if (this._sendKeepAliveResponse()) {
1356
+ this.resetLifeTimeAndKeepAliveCounters();
1357
+ } else {
1358
+ debugLog(
1359
+ " -> subscription.state === LATE , " +
1360
+ "because keepAlive Response cannot be send due to lack of PublishRequest"
1361
+ );
1362
+ this.state = SubscriptionState.LATE;
1363
+ }
1364
+ }
1365
+ }
1366
+
1367
+ private _stop_timer() {
1368
+ if (this.timerId) {
1369
+ debugLog(chalk.bgWhite.blue("Subscription#_stop_timer subscriptionId="), this.id);
1370
+ clearInterval(this.timerId);
1371
+ this.timerId = null;
1372
+ }
1373
+ }
1374
+
1375
+ private _start_timer() {
1376
+ debugLog(
1377
+ chalk.bgWhite.blue("Subscription#_start_timer subscriptionId="),
1378
+ this.id,
1379
+ " publishingInterval = ",
1380
+ this.publishingInterval
1381
+ );
1382
+
1383
+ assert(this.timerId === null);
1384
+ // from the spec:
1385
+ // When a Subscription is created, the first Message is sent at the end of the first publishing cycle to
1386
+ // inform the Client that the Subscription is operational. A NotificationMessage is sent if there are
1387
+ // Notifications ready to be reported. If there are none, a keep-alive Message is sent instead that
1388
+ // contains a sequence number of 1, indicating that the first NotificationMessage has not yet been sent.
1389
+ // This is the only time a keep-alive Message is sent without waiting for the maximum keep-alive count
1390
+ // to be reached, as specified in (f) above.
1391
+
1392
+ // make sure that a keep-alive Message will be send at the end of the first publishing cycle
1393
+ // if there are no Notifications ready.
1394
+ this._keep_alive_counter = this.maxKeepAliveCount;
1395
+
1396
+ assert(this.publishingInterval >= Subscription.minimumPublishingInterval);
1397
+ this.timerId = setInterval(this._tick.bind(this), this.publishingInterval);
1398
+ }
1399
+
1400
+ // counter
1401
+ private _get_next_sequence_number(): number {
1402
+ return this._sequence_number_generator ? this._sequence_number_generator.next() : 0;
1403
+ }
1404
+
1405
+ /**
1406
+ * @private
1407
+ */
1408
+ private _tick() {
1409
+ // istanbul ignore next
1410
+ if (doDebug) {
1411
+ debugLog(`Subscription#_tick id ${this.id} aborted=${this.aborted} state=${SubscriptionState[this.state]}`);
1412
+ }
1413
+
1414
+ if (this.aborted) {
1415
+ // xx console.log(" Log aborted")
1416
+ // xx // underlying channel has been aborted ...
1417
+ // xx self.publishEngine.cancelPendingPublishRequestBeforeChannelChange();
1418
+ // xx // let's still increase lifetime counter to detect timeout
1419
+ }
1420
+
1421
+ if (this.state === SubscriptionState.CLOSED) {
1422
+ warningLog(`Warning: Subscription#_tick id ${this.id} called while subscription is CLOSED`);
1423
+ return;
1424
+ }
1425
+
1426
+ this.discardOldSentNotifications();
1427
+
1428
+ // istanbul ignore next
1429
+ if (doDebug) {
1430
+ debugLog(
1431
+ t(new Date()) + " " + this._life_time_counter + "/" + this.lifeTimeCount + chalk.cyan(" Subscription#_tick"),
1432
+ " processing subscriptionId=",
1433
+ this.id,
1434
+ "hasUncollectedMonitoredItemNotifications = ",
1435
+ this.hasUncollectedMonitoredItemNotifications,
1436
+ " publishingIntervalCount =",
1437
+ this.publishIntervalCount
1438
+ );
1439
+ }
1440
+
1441
+ this.publishEngine!._on_tick();
1442
+
1443
+ this.publishIntervalCount += 1;
1444
+
1445
+ this.increaseLifeTimeCounter();
1446
+
1447
+ if (this.lifeTimeHasExpired) {
1448
+ /* istanbul ignore next */
1449
+ if (doDebug) {
1450
+ debugLog(chalk.red.bold(`Subscription ${this.id} has expired !!!!! => Terminating`));
1451
+ }
1452
+ /**
1453
+ * notify the subscription owner that the subscription has expired by exceeding its life time.
1454
+ * @event expired
1455
+ *
1456
+ */
1457
+ this.emit("expired");
1458
+
1459
+ // notify new terminated status only when subscription has timeout.
1460
+ debugLog("adding StatusChangeNotification notification message for BadTimeout subscription = ", this.id);
1461
+ this._addNotificationMessage(new StatusChangeNotification({ status: StatusCodes.BadTimeout }));
1462
+
1463
+ // kill timer and delete monitored items and transfer pending notification messages
1464
+ this.terminate();
1465
+
1466
+ return;
1467
+ }
1468
+
1469
+ const publishEngine = this.publishEngine!;
1470
+
1471
+ // istanbul ignore next
1472
+ if (doDebug) {
1473
+ debugLog("Subscription#_tick self._pending_notifications= ", this._pending_notifications.size);
1474
+ }
1475
+
1476
+ if (
1477
+ publishEngine.pendingPublishRequestCount === 0 &&
1478
+ (this.hasPendingNotifications || this.hasUncollectedMonitoredItemNotifications)
1479
+ ) {
1480
+ // istanbul ignore next
1481
+ if (doDebug) {
1482
+ debugLog(
1483
+ "subscription set to LATE hasPendingNotifications = ",
1484
+ this.hasPendingNotifications,
1485
+ " hasUncollectedMonitoredItemNotifications =",
1486
+ this.hasUncollectedMonitoredItemNotifications
1487
+ );
1488
+ }
1489
+ this.state = SubscriptionState.LATE;
1490
+ return;
1491
+ }
1492
+
1493
+ if (publishEngine.pendingPublishRequestCount > 0) {
1494
+ if (this.hasPendingNotifications) {
1495
+ // simply pop pending notification and send it
1496
+ this.process_subscription();
1497
+ } else if (this.hasUncollectedMonitoredItemNotifications) {
1498
+ this.process_subscription();
1499
+ } else {
1500
+ this._process_keepAlive();
1501
+ }
1502
+ } else {
1503
+ this._process_keepAlive();
1504
+ }
1505
+ }
1506
+
1507
+ /**
1508
+ * @private
1509
+ */
1510
+ private _sendKeepAliveResponse(): boolean {
1511
+ const future_sequence_number = this._get_future_sequence_number();
1512
+
1513
+ if (this.publishEngine!.send_keep_alive_response(this.id, future_sequence_number)) {
1514
+ this.messageSent = true;
1515
+ // istanbul ignore next
1516
+ if (doDebug) {
1517
+ debugLog(
1518
+ ` -> Subscription#_sendKeepAliveResponse subscriptionId ${this.id} future_sequence_number ${future_sequence_number}`
1519
+ );
1520
+ }
1521
+ /**
1522
+ * notify the subscription owner that a keepalive message has to be sent.
1523
+ * @event keepalive
1524
+ *
1525
+ */
1526
+ this.emit("keepalive", future_sequence_number);
1527
+ this.state = SubscriptionState.KEEPALIVE;
1528
+
1529
+ return true;
1530
+ }
1531
+ return false;
1532
+ }
1533
+
1534
+ /**
1535
+ * Reset the Lifetime Counter Variable to the value specified for the lifetime of a Subscription in
1536
+ * the CreateSubscription Service( 5.13.2).
1537
+ * @private
1538
+ */
1539
+ private resetKeepAliveCounter(): void {
1540
+ this._keep_alive_counter = 0;
1541
+
1542
+ // istanbul ignore next
1543
+ if (doDebug) {
1544
+ debugLog(
1545
+ " -> subscriptionId",
1546
+ this.id,
1547
+ " Resetting keepAliveCounter = ",
1548
+ this._keep_alive_counter,
1549
+ this.maxKeepAliveCount
1550
+ );
1551
+ }
1552
+ }
1553
+
1554
+ /**
1555
+ * @private
1556
+ */
1557
+ private increaseKeepAliveCounter() {
1558
+ this._keep_alive_counter += 1;
1559
+
1560
+ // istanbul ignore next
1561
+ if (doDebug) {
1562
+ debugLog(
1563
+ " -> subscriptionId",
1564
+ this.id,
1565
+ " Increasing keepAliveCounter = ",
1566
+ this._keep_alive_counter,
1567
+ this.maxKeepAliveCount
1568
+ );
1569
+ }
1570
+ }
1571
+
1572
+ /**
1573
+ * @private
1574
+ */
1575
+ private _addNotificationMessage(notificationData: QueueItem | StatusChangeNotification, monitoredItemId?: number) {
1576
+ // istanbul ignore next
1577
+ if (doDebug) {
1578
+ debugLog(chalk.yellow("Subscription#_addNotificationMessage"), notificationData.toString());
1579
+ }
1580
+ this._pending_notifications.push({
1581
+ monitoredItemId,
1582
+ notification: notificationData,
1583
+ publishTime: new Date(),
1584
+ start_tick: this.publishIntervalCount
1585
+ });
1586
+ }
1587
+
1588
+ /**
1589
+ * @internal
1590
+ * @param monitoredItemId
1591
+ */
1592
+ private _removePendingNotificationsFor(monitoredItemId: number) {
1593
+ const nbRemovedNotification = this._pending_notifications.filterOut((e) => e.monitoredItemId === monitoredItemId);
1594
+ if (doDebug) {
1595
+ debugLog(`Removed ${nbRemovedNotification} notifications`);
1596
+ }
1597
+ }
1598
+ /**
1599
+ * Extract the next Notification that is ready to be sent to the client.
1600
+ * @return the Notification to send._pending_notifications
1601
+ */
1602
+ private _popNotificationToSend(): NotificationMessage {
1603
+ assert(this._pending_notifications.size > 0);
1604
+
1605
+ const notificationMessage = new NotificationMessage({
1606
+ sequenceNumber: 0xffffffff,
1607
+ notificationData: [],
1608
+ publishTime: new Date()
1609
+ }); //
1610
+
1611
+ const dataChangeNotifications: DataChangeNotification = new DataChangeNotification({
1612
+ monitoredItems: []
1613
+ });
1614
+ const eventNotificationList: EventNotificationList = new EventNotificationList({
1615
+ events: []
1616
+ });
1617
+
1618
+ let statusChangeNotification: StatusChangeNotification | undefined;
1619
+
1620
+ let i = 0;
1621
+ let hasEventFieldList = 0;
1622
+ let hasMonitoredItemNotification = 0;
1623
+ const m = this.maxNotificationsPerPublish;
1624
+ while (i < m && this._pending_notifications.size > 0) {
1625
+ if (hasEventFieldList || hasMonitoredItemNotification) {
1626
+ const notification1 = this._pending_notifications.first()!.notification;
1627
+ if (notification1 instanceof StatusChangeNotification) {
1628
+ break;
1629
+ }
1630
+ }
1631
+ const notification = this._pending_notifications.shift()!.notification;
1632
+ if (notification instanceof MonitoredItemNotification) {
1633
+ assert(notification.clientHandle !== 4294967295);
1634
+ dataChangeNotifications.monitoredItems!.push(notification);
1635
+ hasMonitoredItemNotification = 1;
1636
+ } else if (notification instanceof EventFieldList) {
1637
+ eventNotificationList.events!.push(notification);
1638
+ hasEventFieldList = 1;
1639
+ } else if (notification instanceof StatusChangeNotification) {
1640
+ // to do
1641
+ statusChangeNotification = notification;
1642
+ break;
1643
+ }
1644
+ i += 1;
1645
+ }
1646
+
1647
+ if (dataChangeNotifications.monitoredItems!.length) {
1648
+ notificationMessage.notificationData!.push(dataChangeNotifications);
1649
+ }
1650
+ if (eventNotificationList.events!.length) {
1651
+ notificationMessage.notificationData!.push(eventNotificationList);
1652
+ }
1653
+ if (statusChangeNotification) {
1654
+ notificationMessage.notificationData!.push(statusChangeNotification);
1655
+ }
1656
+ return notificationMessage;
1657
+ }
1658
+
1659
+ /**
1660
+ * discardOldSentNotification find all sent notification message that have expired keep-alive
1661
+ * and destroy them.
1662
+ * @private
1663
+ *
1664
+ * Subscriptions maintain a retransmission queue of sent NotificationMessages.
1665
+ * NotificationMessages are retained in this queue until they are acknowledged or until they have
1666
+ * been in the queue for a minimum of one keep-alive interval.
1667
+ *
1668
+ */
1669
+ private discardOldSentNotifications() {
1670
+ // Sessions maintain a retransmission queue of sent NotificationMessages. NotificationMessages
1671
+ // are retained in this queue until they are acknowledged. The Session shall maintain a
1672
+ // retransmission queue size of at least two times the number of Publish requests per Session the
1673
+ // Server supports. Clients are required to acknowledge NotificationMessages as they are received. In the
1674
+ // case of a retransmission queue overflow, the oldest sent NotificationMessage gets deleted. If a
1675
+ // Subscription is transferred to another Session, the queued NotificationMessages for this
1676
+ // Subscription are moved from the old to the new Session.
1677
+ if (maxNotificationMessagesInQueue <= this._sent_notification_messages.length) {
1678
+ debugLog("discardOldSentNotifications = ", this._sent_notification_messages.length);
1679
+ this._sent_notification_messages.splice(this._sent_notification_messages.length - maxNotificationMessagesInQueue);
1680
+ }
1681
+ }
1682
+
1683
+ /**
1684
+ * @param timestampsToReturn
1685
+ * @param monitoredItemCreateRequest
1686
+ * @param node
1687
+ * @private
1688
+ */
1689
+ private _createMonitoredItemStep2(
1690
+ timestampsToReturn: TimestampsToReturn,
1691
+ monitoredItemCreateRequest: MonitoredItemCreateRequest,
1692
+ node: BaseNode
1693
+ ): MonitoredItemCreateResult {
1694
+ // note : most of the parameter inconsistencies shall have been handled by the caller
1695
+ // any error here will raise an assert here
1696
+
1697
+ assert(monitoredItemCreateRequest instanceof MonitoredItemCreateRequest);
1698
+ const itemToMonitor = monitoredItemCreateRequest.itemToMonitor;
1699
+
1700
+ // xx check if attribute Id invalid (we only support Value or EventNotifier )
1701
+ // xx assert(itemToMonitor.attributeId !== AttributeIds.INVALID);
1702
+
1703
+ this.monitoredItemIdCounter += 1;
1704
+
1705
+ const monitoredItemId = getNextMonitoredItemId();
1706
+
1707
+ const requestedParameters = monitoredItemCreateRequest.requestedParameters;
1708
+
1709
+ // adjust requestedParameters.samplingInterval
1710
+ requestedParameters.samplingInterval = this.adjustSamplingInterval(requestedParameters.samplingInterval, node);
1711
+
1712
+ // reincorporate monitoredItemId and itemToMonitor into the requestedParameters
1713
+ const options = requestedParameters as any as MonitoredItemOptions;
1714
+
1715
+ options.monitoredItemId = monitoredItemId;
1716
+ options.itemToMonitor = itemToMonitor;
1717
+
1718
+ const monitoredItem = new MonitoredItem(options);
1719
+ monitoredItem.timestampsToReturn = timestampsToReturn;
1720
+ monitoredItem.$subscription = this;
1721
+
1722
+ assert(monitoredItem.monitoredItemId === monitoredItemId);
1723
+ this.monitoredItems[monitoredItemId] = monitoredItem;
1724
+ assert(monitoredItem.clientHandle !== 4294967295);
1725
+
1726
+ const filterResult = _process_filter(node, requestedParameters.filter);
1727
+
1728
+ const monitoredItemCreateResult = new MonitoredItemCreateResult({
1729
+ filterResult,
1730
+ monitoredItemId,
1731
+ revisedQueueSize: monitoredItem.queueSize,
1732
+ revisedSamplingInterval: monitoredItem.samplingInterval,
1733
+ statusCode: StatusCodes.Good
1734
+ });
1735
+
1736
+ // this.emit("monitoredItem", monitoredItem, itemToMonitor);
1737
+ return monitoredItemCreateResult;
1738
+ }
1739
+
1740
+ /**
1741
+ *
1742
+ * @param monitoredItem
1743
+ * @param monitoredItemCreateRequest
1744
+ * @private
1745
+ */
1746
+ public _createMonitoredItemStep3(
1747
+ monitoredItem: MonitoredItem | null,
1748
+ monitoredItemCreateRequest: MonitoredItemCreateRequest
1749
+ ): void {
1750
+ if (!monitoredItem) {
1751
+ return;
1752
+ }
1753
+ assert(monitoredItem.monitoringMode === MonitoringMode.Invalid);
1754
+ assert(typeof monitoredItem.samplingFunc === "function", " expecting a sampling function here");
1755
+ const monitoringMode = monitoredItemCreateRequest.monitoringMode; // Disabled, Sampling, Reporting
1756
+ monitoredItem.setMonitoringMode(monitoringMode);
1757
+ }
1758
+
1759
+ private _harvestMonitoredItems() {
1760
+ for (const monitoredItem of Object.values(this.monitoredItems)) {
1761
+ const notifications_chunks = monitoredItem.extractMonitoredItemNotifications();
1762
+ for (const chunk of notifications_chunks) {
1763
+ this._addNotificationMessage(chunk, monitoredItem.monitoredItemId);
1764
+ }
1765
+ }
1766
+ this._hasUncollectedMonitoredItemNotifications = false;
1767
+ }
1768
+ }
1769
+
1770
+ /**
1771
+ * extract up to maxNotificationsPerPublish notifications
1772
+ * @param the full array of monitored items
1773
+ * @param maxNotificationsPerPublish the maximum number of notification to extract
1774
+ * @return an extract of array of monitored item matching at most maxNotificationsPerPublish
1775
+ * @private
1776
+ */
1777
+ function extract_notifications_chunk<T>(monitoredItems: Queue<T>, maxNotificationsPerPublish: number): T[] {
1778
+ let n = maxNotificationsPerPublish === 0 ? monitoredItems.size : Math.min(monitoredItems.size, maxNotificationsPerPublish);
1779
+
1780
+ const chunk_monitoredItems: T[] = [];
1781
+ while (n) {
1782
+ chunk_monitoredItems.push(monitoredItems.shift()!);
1783
+ n--;
1784
+ }
1785
+ return chunk_monitoredItems;
1786
+ }
1787
+
1788
+ function filter_instanceof(Class: any, e: any): boolean {
1789
+ return e instanceof Class;
1790
+ }
1791
+
1792
+ assert(Subscription.maximumPublishingInterval < 2147483647, "maximumPublishingInterval cannot exceed (2**31-1) ms ");