• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

node-opcua / node-opcua / 28389643215

29 Jun 2026 05:10PM UTC coverage: 92.054%. Remained the same
28389643215

push

github

erossignon
fix(filter): return a StatusCode when a selectClause typeDefinitionId does not resolve

checkSelectClause now returns BadNodeIdUnknown when the SimpleAttributeOperand
typeDefinitionId does not reference an existing node, and BadTypeMismatch when it
references a node that is not an ObjectType, instead of dereferencing the result
of findEventType() (which returns null for an unknown NodeId).

Adds a unit test (checkSelectClauses with an unresolved typeDefinitionId) and an
end-to-end test verifying the selectClause result is reported per-clause.

18511 of 21825 branches covered (84.82%)

5 of 6 new or added lines in 1 file covered. (83.33%)

340 existing lines in 8 files now uncovered.

164590 of 178797 relevant lines covered (92.05%)

434180.06 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.49
/packages/node-opcua-server/source/server_subscription.ts
1
/**
337✔
2
 * @module node-opcua-server
2✔
3
 */
2✔
4
// tslint:disable:no-console
2✔
5

2✔
6
import { EventEmitter } from "node:events";
2✔
7
import chalk from "chalk";
2✔
8

2✔
9
import {
2✔
10
    type BaseNode,
2✔
11
    type Duration,
2✔
12
    type IAddressSpace,
2✔
13
    type ISessionContext,
2✔
14
    SessionContext,
2✔
15
    type UAMethod,
2✔
16
    type UAObject,
2✔
17
    type UAObjectType,
2✔
18
    type UAVariable
2✔
19
} from "node-opcua-address-space";
2✔
20
import { assert } from "node-opcua-assert";
2✔
21
import type { Byte, UInt32 } from "node-opcua-basic-types";
2✔
22
import { SubscriptionDiagnosticsDataType } from "node-opcua-common";
2✔
23
import { AttributeIds, isValidDataEncoding, NodeClass, type QualifiedNameLike } from "node-opcua-data-model";
2✔
24
import type { DataValue, TimestampsToReturn } from "node-opcua-data-value";
2✔
25
import { checkDebugFlag, make_debugLog, make_warningLog } from "node-opcua-debug";
2✔
26
import { NodeId } from "node-opcua-nodeid";
2✔
27
import type { NumericRange } from "node-opcua-numeric-range";
2✔
28
import { ObjectRegistry } from "node-opcua-object-registry";
2✔
29
import { SequenceNumberGenerator } from "node-opcua-secure-channel";
2✔
30
import { checkSelectClauses, EventFilter } from "node-opcua-service-filter";
2✔
31
import {
2✔
32
    AggregateFilter,
2✔
33
    DataChangeFilter,
2✔
34
    DataChangeNotification,
2✔
35
    EventNotificationList,
2✔
36
    MonitoredItemCreateRequest,
2✔
37
    MonitoredItemCreateResult,
2✔
38
    MonitoredItemNotification,
2✔
39
    MonitoringMode,
2✔
40
    NotificationMessage,
2✔
41
    PublishResponse,
2✔
42
    StatusChangeNotification
2✔
43
} from "node-opcua-service-subscription";
2✔
44
import { type StatusCode, StatusCodes } from "node-opcua-status-code";
2✔
45
import {
2✔
46
    AggregateFilterResult,
2✔
47
    ContentFilterResult,
2✔
48
    EventFieldList,
2✔
49
    EventFilterResult,
2✔
50
    type MonitoringFilter
2✔
51
} from "node-opcua-types";
2✔
52
import { type IServerSidePublishEngine, TransferredSubscription } from "./i_server_side_publish_engine";
2✔
53

2✔
54
import { MonitoredItem, type MonitoredItemOptions, type QueueItem } from "./monitored_item";
2✔
55
import { Queue } from "./queue";
2✔
56
import type { ServerSession } from "./server_session";
2✔
57
import { validateFilter } from "./validate_filter";
2✔
58

2✔
59
const debugLog = make_debugLog(__filename);
2✔
60
const doDebug = checkDebugFlag(__filename);
2✔
61
const warningLog = make_warningLog(__filename);
2✔
62
const maxNotificationMessagesInQueue = 100;
2✔
63

2✔
64
export interface SubscriptionDiagnosticsDataTypePriv extends SubscriptionDiagnosticsDataType {
2✔
65
    $subscription: Subscription;
2✔
66
}
2✔
67

2✔
68
export enum SubscriptionState {
2✔
69
    CLOSED = 1, // The Subscription has not yet been created or has terminated.
2✔
70
    CREATING = 2, // The Subscription is being created
2✔
71
    NORMAL = 3, // The Subscription is cyclically checking for Notifications from its MonitoredItems.
2✔
72
    // The keep-alive counter is not used in this state.
2✔
73
    LATE = 4, // The publishing timer has expired and there are Notifications available or a keep-alive Message is
2✔
74
    // ready to be sent, but there are no Publish requests queued. When in this state, the next Publish
2✔
75
    // request is processed when it is received. The keep-alive counter is not used in this state.
2✔
76
    KEEPALIVE = 5, // The Subscription is cyclically checking for Notification
2✔
77
    // alive counter to count down to 0 from its maximum.
2✔
78
    TERMINATED = 6
2✔
79
}
2✔
80

2✔
81
function _adjust_publishing_interval(publishingInterval?: number): number {
545✔
82
    publishingInterval =
545✔
83
        publishingInterval === undefined || Number.isNaN(publishingInterval)
545✔
84
            ? Subscription.defaultPublishingInterval
411✔
85
            : publishingInterval;
542✔
86
    publishingInterval = Math.max(publishingInterval, Subscription.minimumPublishingInterval);
545✔
87
    publishingInterval = Math.min(publishingInterval, Subscription.maximumPublishingInterval);
545✔
88
    return publishingInterval;
545✔
89
}
545✔
90

2✔
91
const minimumMaxKeepAliveCount = 2;
2✔
92
const maximumMaxKeepAliveCount = 12000;
2✔
93

2✔
94
function _adjust_maxKeepAliveCount(maxKeepAliveCount?: number /*,publishingInterval*/): number {
545✔
95
    maxKeepAliveCount = maxKeepAliveCount || minimumMaxKeepAliveCount;
545✔
96
    maxKeepAliveCount = Math.max(maxKeepAliveCount, minimumMaxKeepAliveCount);
545✔
97
    maxKeepAliveCount = Math.min(maxKeepAliveCount, maximumMaxKeepAliveCount);
545✔
98
    return maxKeepAliveCount;
545✔
99
}
545✔
100

2✔
101
const MaxUint32 = 0xffffffff;
2✔
102

2✔
103
function _adjust_lifeTimeCount(lifeTimeCount: number, maxKeepAliveCount: number, publishingInterval: number): number {
545✔
104
    lifeTimeCount = lifeTimeCount || 1;
545✔
105

545✔
106
    const minTicks = Math.ceil(Subscription.minimumLifetimeDuration / publishingInterval);
545✔
107
    const maxTicks = Math.floor(Subscription.maximumLifetimeDuration / publishingInterval);
545✔
108

545✔
109
    lifeTimeCount = Math.max(minTicks, lifeTimeCount);
545✔
110
    lifeTimeCount = Math.min(maxTicks, lifeTimeCount);
545✔
111

545✔
112
    // let's make sure that lifeTimeCount is at least three time maxKeepAliveCount
545✔
113
    // Note : the specs say ( part 3  - CreateSubscriptionParameter )
545✔
114
    //        "The lifetime count shall be a minimum of three times the keep keep-alive count."
545✔
115
    lifeTimeCount = Math.max(lifeTimeCount, Math.min(maxKeepAliveCount * 3, MaxUint32));
545✔
116

545✔
117
    return lifeTimeCount;
545✔
118
}
545✔
119

2✔
120
function _adjust_publishingEnable(publishingEnabled?: boolean | null): boolean {
537✔
121
    return publishingEnabled === null || publishingEnabled === undefined ? true : !!publishingEnabled;
537✔
122
}
537✔
123

2✔
124
function _adjust_maxNotificationsPerPublish(maxNotificationsPerPublish?: number): number {
545✔
125
    assert(Subscription.maxNotificationPerPublishHighLimit > 0, "Subscription.maxNotificationPerPublishHighLimit must be positive");
545✔
126

545✔
127
    maxNotificationsPerPublish = maxNotificationsPerPublish || 0;
545✔
128
    assert(typeof maxNotificationsPerPublish === "number");
545✔
129

545✔
130
    // must be strictly positive
545✔
131
    maxNotificationsPerPublish = maxNotificationsPerPublish >= 0 ? maxNotificationsPerPublish : 0;
545!
132

545✔
133
    if (maxNotificationsPerPublish === 0) {
545✔
134
        // if zero then => use our HighLimit
156✔
135
        maxNotificationsPerPublish = Subscription.maxNotificationPerPublishHighLimit;
156✔
136
    } else {
545✔
137
        // if not zero then should be capped by maxNotificationPerPublishHighLimit
389✔
138
        maxNotificationsPerPublish = Math.min(Subscription.maxNotificationPerPublishHighLimit, maxNotificationsPerPublish);
389✔
139
    }
389✔
140

545✔
141
    assert(maxNotificationsPerPublish !== 0 && maxNotificationsPerPublish <= Subscription.maxNotificationPerPublishHighLimit);
545✔
142
    return maxNotificationsPerPublish;
545✔
143
}
545✔
144

2✔
145
function w(s: string | number, length: number): string {
×
146
    return `000${s}`.padStart(length);
×
147
}
×
148

2✔
149
function t(d: Date): string {
×
150
    return `${w(d.getHours(), 2)}:${w(d.getMinutes(), 2)}:${w(d.getSeconds(), 2)}:${w(d.getMilliseconds(), 3)}`;
×
151
}
×
152

2✔
153
function _getSequenceNumbers(arr: NotificationMessage[]): number[] {
1,442✔
154
    return arr.map((notificationMessage) => notificationMessage.sequenceNumber);
1,442✔
155
}
1,442✔
156

2✔
157
function analyzeEventFilterResult(node: BaseNode, eventFilter: EventFilter): EventFilterResult {
22✔
158
    /* c8 ignore next */
2✔
159
    if (!(eventFilter instanceof EventFilter)) {
2✔
160
        throw new Error("Internal Error");
×
161
    }
×
162

22✔
163
    const selectClauseResults = checkSelectClauses(node as UAObjectType, eventFilter.selectClauses || []);
22!
164

22✔
165
    const whereClauseResult = new ContentFilterResult();
22✔
166

22✔
167
    return new EventFilterResult({
22✔
168
        selectClauseDiagnosticInfos: [],
22✔
169
        selectClauseResults,
22✔
170
        whereClauseResult
22✔
171
    });
22✔
172
}
22✔
173

2✔
174
function analyzeDataChangeFilterResult(_node: BaseNode, dataChangeFilter: DataChangeFilter): null {
39✔
175
    assert(dataChangeFilter instanceof DataChangeFilter);
39✔
176
    // the opcua specification doesn't provide dataChangeFilterResult
39✔
177
    return null;
39✔
178
}
39✔
179

2✔
180
function analyzeAggregateFilterResult(_node: BaseNode, aggregateFilter: AggregateFilter): AggregateFilterResult {
×
181
    assert(aggregateFilter instanceof AggregateFilter);
×
182
    return new AggregateFilterResult({});
×
183
}
×
184

2✔
185
function _process_filter(node: BaseNode, filter: MonitoringFilter | null): EventFilterResult | AggregateFilterResult | null {
14,744✔
186
    if (!filter) {
14,744✔
187
        return null;
14,683✔
188
    }
14,683✔
189

125✔
190
    if (filter instanceof EventFilter) {
14,744✔
191
        return analyzeEventFilterResult(node, filter);
22✔
192
    } else if (filter instanceof DataChangeFilter) {
14,744✔
193
        return analyzeDataChangeFilterResult(node, filter);
39✔
194
    } else if (filter instanceof AggregateFilter) {
39!
195
        return analyzeAggregateFilterResult(node, filter);
×
196
    }
×
197
    // c8 ignore next
92✔
198
    throw new Error("invalid filter");
92!
199
}
92✔
200

2✔
201
/**
2✔
202
 * @private
2✔
203
 */
2✔
204
function createSubscriptionDiagnostics(subscription: Subscription): SubscriptionDiagnosticsDataTypePriv {
537✔
205
    assert(subscription instanceof Subscription);
537✔
206

537✔
207
    const subscriptionDiagnostics = new SubscriptionDiagnosticsDataType({});
537✔
208

537✔
209
    const sd = subscriptionDiagnostics as SubscriptionDiagnosticsDataTypePriv;
537✔
210
    sd.$subscription = subscription;
537✔
211

537✔
212
    const defineGetter = <T>(name: string, getter: (this: SubscriptionDiagnosticsDataTypePriv) => T) => {
537✔
213
        Object.defineProperty(sd, name, { get: getter, configurable: true });
6,981✔
214
    };
2,169✔
215

537✔
216
    // "sessionId"
537✔
217
    defineGetter("sessionId", function (this: SubscriptionDiagnosticsDataTypePriv): NodeId {
537✔
218
        if (!this.$subscription) {
930!
219
            return NodeId.nullNodeId;
×
220
        }
×
221
        return this.$subscription.getSessionId();
930✔
222
    });
537✔
223
    defineGetter("subscriptionId", function (this: SubscriptionDiagnosticsDataTypePriv): number {
537✔
224
        if (!this.$subscription) {
5,753!
225
            return 0;
×
226
        }
×
227
        return this.$subscription.id;
5,753✔
228
    });
537✔
229
    defineGetter("priority", function (this: SubscriptionDiagnosticsDataTypePriv): number {
537✔
230
        if (!this.$subscription) {
930!
231
            return 0;
×
232
        }
×
233
        return this.$subscription.priority;
930✔
234
    });
537✔
235
    defineGetter("publishingInterval", function (this: SubscriptionDiagnosticsDataTypePriv): number {
537✔
236
        if (!this.$subscription) {
930!
237
            return 0;
×
238
        }
×
239
        return this.$subscription.publishingInterval;
930✔
240
    });
537✔
241
    defineGetter("maxLifetimeCount", function (this: SubscriptionDiagnosticsDataTypePriv) {
537✔
242
        return this.$subscription.lifeTimeCount;
930✔
243
    });
537✔
244
    defineGetter("maxKeepAliveCount", function (this: SubscriptionDiagnosticsDataTypePriv): number {
537✔
245
        if (!this.$subscription) {
930!
246
            return 0;
×
247
        }
×
248
        return this.$subscription.maxKeepAliveCount;
930✔
249
    });
537✔
250
    defineGetter("maxNotificationsPerPublish", function (this: SubscriptionDiagnosticsDataTypePriv): number {
537✔
251
        if (!this.$subscription) {
930!
252
            return 0;
×
253
        }
×
254
        return this.$subscription.maxNotificationsPerPublish;
930✔
255
    });
537✔
256
    defineGetter("publishingEnabled", function (this: SubscriptionDiagnosticsDataTypePriv): boolean {
537✔
257
        if (!this.$subscription) {
930!
258
            return false;
×
259
        }
×
260
        return this.$subscription.publishingEnabled;
930✔
261
    });
537✔
262
    defineGetter("monitoredItemCount", function (this: SubscriptionDiagnosticsDataTypePriv): number {
537✔
263
        if (!this.$subscription) {
938!
264
            return 0;
×
265
        }
×
266
        return this.$subscription.monitoredItemCount;
938✔
267
    });
537✔
268
    defineGetter("nextSequenceNumber", function (this: SubscriptionDiagnosticsDataTypePriv): number {
537✔
269
        if (!this.$subscription) {
930!
270
            return 0;
×
271
        }
×
272
        return this.$subscription.futureSequenceNumber;
930✔
273
    });
537✔
274
    defineGetter("disabledMonitoredItemCount", function (this: SubscriptionDiagnosticsDataTypePriv): number {
537✔
275
        if (!this.$subscription) {
932!
276
            return 0;
×
277
        }
×
278
        return this.$subscription.disabledMonitoredItemCount;
932✔
279
    });
537✔
280

537✔
281
    /* those member of self.subscriptionDiagnostics are handled directly
537✔
282

537✔
283
   modifyCount
537✔
284
   enableCount,
537✔
285
   disableCount,
537✔
286
   republishRequestCount,
537✔
287
   notificationsCount,
537✔
288
   publishRequestCount,
537✔
289
   dataChangeNotificationsCount,
537✔
290
   eventNotificationsCount,
537✔
291
  */
537✔
292

537✔
293
    /*
537✔
294
   those members are not updated yet in the code :
537✔
295
   "republishMessageRequestCount",
537✔
296
   "republishMessageCount",
537✔
297
   "transferRequestCount",
537✔
298
   "transferredToAltClientCount",
537✔
299
   "transferredToSameClientCount",
537✔
300
   "latePublishRequestCount",
537✔
301
   "unacknowledgedMessageCount",
537✔
302
   "discardedMessageCount",
537✔
303
   "monitoringQueueOverflowCount",
537✔
304
   "eventQueueOverflowCount"
537✔
305
   */
537✔
306
    defineGetter("currentKeepAliveCount", function (this: SubscriptionDiagnosticsDataTypePriv): number {
537✔
307
        if (!this.$subscription) {
929!
308
            return 0;
×
309
        }
×
310
        return this.$subscription.currentKeepAliveCount;
929✔
311
    });
537✔
312
    defineGetter("currentLifetimeCount", function (this: SubscriptionDiagnosticsDataTypePriv): number {
537✔
313
        if (!this.$subscription) {
929!
314
            return 0;
×
315
        }
×
316
        return this.$subscription.currentLifetimeCount;
929✔
317
    });
537✔
318
    // add object in Variable SubscriptionDiagnosticArray (i=2290) ( Array of SubscriptionDiagnostics)
537✔
319
    // add properties in Variable to reflect
537✔
320
    return subscriptionDiagnostics as SubscriptionDiagnosticsDataTypePriv;
537✔
321
}
537✔
322

2✔
323
interface IGlobalMonitoredItemCounter {
2✔
324
    totalMonitoredItemCount: number;
2✔
325
}
2✔
326

2✔
327
export interface SubscriptionOptions {
2✔
328
    sessionId?: NodeId;
2✔
329
    /**
2✔
330
     * (default:1000) the publishing interval.
2✔
331
     */
2✔
332
    publishingInterval?: number;
2✔
333
    /**
2✔
334
     * (default:10) the max Life Time Count
2✔
335
     */
2✔
336
    maxKeepAliveCount?: number;
2✔
337

2✔
338
    lifeTimeCount?: number;
2✔
339
    /**
2✔
340
     * (default:true)
2✔
341
     */
2✔
342
    publishingEnabled?: boolean;
2✔
343
    /**
2✔
344
     * (default:0)
2✔
345
     */
2✔
346
    maxNotificationsPerPublish?: number;
2✔
347
    /**
2✔
348
     * subscription priority Byte:(0-255)
2✔
349
     */
2✔
350
    priority?: number;
2✔
351

2✔
352
    publishEngine?: IServerSidePublishEngine;
2✔
353
    /**
2✔
354
     *  a unique identifier
2✔
355
     */
2✔
356
    id?: number;
2✔
357

2✔
358
    serverCapabilities: ServerCapabilitiesPartial;
2✔
359
    globalCounter: IGlobalMonitoredItemCounter;
2✔
360
}
2✔
361

2✔
362
let g_monitoredItemId = Math.ceil(Math.random() * 100000);
2✔
363

2✔
364
function getNextMonitoredItemId() {
14,744✔
365
    return g_monitoredItemId++;
14,744✔
366
}
14,744✔
367

2✔
368
// function myFilter<T>(t1: any, chunk: any[]): T[] {
2✔
369
//     return chunk.filter(filter_instanceof.bind(null, t1));
2✔
370
// }
2✔
371

2✔
372
// function makeNotificationData(notifications_chunk: QueueItem): NotificationData {
2✔
373
//     const dataChangedNotificationData = myFilter<MonitoredItemNotification>(MonitoredItemNotification, notifications_chunk);
2✔
374
//     const eventNotificationListData = myFilter<EventFieldList>(EventFieldList, notifications_chunk);
2✔
375

2✔
376
//     assert(notifications_chunk.length === dataChangedNotificationData.length + eventNotificationListData.length);
2✔
377

2✔
378
//     const notifications: (DataChangeNotification | EventNotificationList)[] = [];
2✔
379

2✔
380
//     // add dataChangeNotification
2✔
381
//     if (dataChangedNotificationData.length) {
2✔
382
//         const dataChangeNotification = new DataChangeNotification({
2✔
383
//             diagnosticInfos: [],
2✔
384
//             monitoredItems: dataChangedNotificationData
2✔
385
//         });
2✔
386
//         notifications.push(dataChangeNotification);
2✔
387
//     }
2✔
388

2✔
389
//     // add dataChangeNotification
2✔
390
//     if (eventNotificationListData.length) {
2✔
391
//         const eventNotificationList = new EventNotificationList({
2✔
392
//             events: eventNotificationListData
2✔
393
//         });
2✔
394
//         notifications.push(eventNotificationList);
2✔
395
//     }
2✔
396
//     return notifications.length === 0 ? null : notifications;
2✔
397
// }
2✔
398
const INVALID_ID = -1;
2✔
399

2✔
400
export type Notification = DataChangeNotification | EventNotificationList | StatusChangeNotification;
2✔
401
export type Counter = number;
2✔
402

2✔
403
export interface ModifySubscriptionParameters {
2✔
404
    /**
2✔
405
     *     requestedPublishingInterval =0 means fastest possible
2✔
406
     */
2✔
407
    requestedPublishingInterval?: Duration;
2✔
408
    /*
2✔
409
     * requestedLifetimeCount=0 means no change
2✔
410
     */
2✔
411
    requestedLifetimeCount?: Counter;
2✔
412
    /**
2✔
413
     * requestedMaxKeepAliveCount  ===0 means no change
2✔
414
     */
2✔
415
    requestedMaxKeepAliveCount?: Counter;
2✔
416
    maxNotificationsPerPublish?: Counter;
2✔
417
    priority?: Byte;
2✔
418
}
2✔
419

2✔
420
export interface GetMonitoredItemsResult {
2✔
421
    /**
2✔
422
     * array of serverHandles for all MonitoredItems of the subscription
2✔
423
     * identified by subscriptionId.
2✔
424
     */
2✔
425
    serverHandles: Uint32Array;
2✔
426
    /**
2✔
427
     *  array of clientHandles for all MonitoredItems of the subscription
2✔
428
     *  identified by subscriptionId.
2✔
429
     */
2✔
430
    clientHandles: Uint32Array;
2✔
431
    statusCode: StatusCode;
2✔
432
}
2✔
433

2✔
434
export interface InternalNotification {
2✔
435
    monitoredItemId?: number;
2✔
436
    notification: QueueItem | StatusChangeNotification;
2✔
437
    publishTime: Date;
2✔
438
    start_tick: number;
2✔
439
}
2✔
440

2✔
441
export interface InternalCreateMonitoredItemResult {
2✔
442
    monitoredItem?: MonitoredItem;
2✔
443
    monitoredItemCreateRequest: MonitoredItemCreateRequest;
2✔
444
    createResult: MonitoredItemCreateResult;
2✔
445
}
2✔
446

2✔
447
export interface MonitoredItemBase {
2✔
448
    node: UAVariable | UAObject | UAMethod | null;
2✔
449
    // from monitoring parameters
2✔
450
    filter: MonitoringFilter | null;
2✔
451
    monitoringMode: MonitoringMode;
2✔
452
    timestampsToReturn: TimestampsToReturn;
2✔
453
    discardOldest: boolean;
2✔
454
    queueSize: number;
2✔
455
    clientHandle: UInt32;
2✔
456
}
2✔
457
export type CreateMonitoredItemHook = (subscription: Subscription, monitoredItem: MonitoredItemBase) => Promise<StatusCode>;
2✔
458
export type DeleteMonitoredItemHook = (subscription: Subscription, monitoredItem: MonitoredItemBase) => Promise<StatusCode>;
2✔
459

2✔
460
export interface ServerCapabilitiesPartial {
2✔
461
    maxMonitoredItems: UInt32;
2✔
462
    maxMonitoredItemsPerSubscription: UInt32;
2✔
463
    maxWhereClauseParameters?: UInt32;
2✔
464
    maxSelectClauseParameters?: UInt32;
2✔
465
}
2✔
466

2✔
467
export interface IReadAttributeCapable {
2✔
468
    readAttribute(
2✔
469
        context: ISessionContext | null,
2✔
470
        attributeId: AttributeIds,
2✔
471
        indexRange?: NumericRange,
2✔
472
        dataEncoding?: QualifiedNameLike | null
2✔
473
    ): DataValue;
2✔
474
}
2✔
475

2✔
476
/**
2✔
477
 * The Subscription class used in the OPCUA server side.
2✔
478
 */
2✔
479
export class Subscription extends EventEmitter {
2✔
480
    public static minimumPublishingInterval = 50; // fastest possible
2✔
481
    public static defaultPublishingInterval = 1000; // one second
2✔
482
    public static maximumPublishingInterval: number = 1000 * 60; // one minute
2✔
483
    public static maxNotificationPerPublishHighLimit = 1000;
2✔
484
    public static minimumLifetimeDuration = 5 * 1000; //  // we want 2 seconds minimum lifetime for any subscription
2✔
485
    public static maximumLifetimeDuration = 60 * 60 * 1000; // 1 hour
2✔
486

2✔
487
    /**
2✔
488
     * maximum number of monitored item in a subscription to be used
2✔
489
     * when serverCapacity.maxMonitoredItems and serverCapacity.maxMonitoredItemsPerSubscription are not set.
2✔
490
     */
2✔
491
    public static defaultMaxMonitoredItemCount = 20000;
2✔
492

2✔
493
    /**
2✔
494
     * @deprecated use serverCapacity.maxMonitoredItems and serverCapacity.maxMonitoredItemsPerSubscription instead
2✔
495
     */
2✔
496
    protected static get maxMonitoredItemCount() {
2✔
UNCOV
497
        return Subscription.defaultMaxMonitoredItemCount;
×
UNCOV
498
    }
×
499

2✔
500
    public static registry = new ObjectRegistry();
2✔
501

2✔
502
    public publishEngine?: IServerSidePublishEngine;
2✔
503
    public id: number;
2✔
504
    public priority: number;
2✔
505
    /**
2✔
506
     * the Subscription publishing interval
2✔
507
     * @default 1000
2✔
508
     */
2✔
509
    public publishingInterval: number;
2✔
510
    /**
2✔
511
     * The keep alive count defines how many times the publish interval need to
2✔
512
     * expires without having notifications available before the server send an
2✔
513
     * empty message.
2✔
514
     * OPCUA Spec says: a value of 0 is invalid.
2✔
515
     * @default 10
2✔
516
     *
2✔
517
     */
2✔
518
    public maxKeepAliveCount: number;
2✔
519
    /**
2✔
520
     * The life time count defines how many times the publish interval expires without
2✔
521
     * having a connection to the client to deliver data.
2✔
522
     * If the life time count reaches maxKeepAliveCount, the subscription will
2✔
523
     * automatically terminate.
2✔
524
     * OPCUA Spec: The life-time count shall be a minimum of three times the keep keep-alive count.
2✔
525
     *
2✔
526
     * Note: this has to be interpreted as without having a PublishRequest available
2✔
527
     * @default 1
2✔
528
     */
2✔
529
    public lifeTimeCount: number;
2✔
530
    /**
2✔
531
     * The maximum number of notifications that the Client wishes to receive in a
2✔
532
     * single Publish response. A value of zero indicates that there is no limit.
2✔
533
     * The number of notifications per Publish is the sum of monitoredItems in the
2✔
534
     * DataChangeNotification and events in the EventNotificationList.
2✔
535
     *
2✔
536
     * @property maxNotificationsPerPublish
2✔
537
     * @default 0
2✔
538
     */
2✔
539
    public maxNotificationsPerPublish: number;
2✔
540
    public publishingEnabled: boolean;
2✔
541
    public subscriptionDiagnostics: SubscriptionDiagnosticsDataTypePriv;
2✔
542
    public publishIntervalCount: number;
2✔
543
    /**
2✔
544
     *  number of monitored Item
2✔
545
     */
2✔
546
    public monitoredItemIdCounter: number;
2✔
547

2✔
548
    private _state: SubscriptionState = -1 as SubscriptionState;
2✔
549
    public set state(value: SubscriptionState) {
2✔
550
        if (this._state !== value) {
3,124✔
551
            this._state = value;
2,031✔
552
            this.emit("stateChanged", value);
2,031✔
553
        }
2,031✔
554
    }
3,124✔
555
    public get state(): SubscriptionState {
2✔
556
        return this._state;
29,369✔
557
    }
29,369✔
558

2✔
559
    public messageSent: boolean;
2✔
560
    public $session?: ServerSession;
2✔
561

2✔
562
    public get sessionId(): NodeId {
2✔
563
        return this.$session ? this.$session.nodeId : NodeId.nullNodeId;
2,740✔
564
    }
2,740✔
565

2✔
566
    // ServerCapabilities filter limits (OPC UA Part 5), exposed for EventFilter validation (e.g. on modify)
2✔
567
    public get maxWhereClauseParameters(): UInt32 | undefined {
2✔
568
        return this.serverCapabilities.maxWhereClauseParameters;
2✔
569
    }
2✔
570
    public get maxSelectClauseParameters(): UInt32 | undefined {
2✔
571
        return this.serverCapabilities.maxSelectClauseParameters;
2✔
572
    }
2✔
573

2✔
574
    public get currentLifetimeCount(): number {
2✔
575
        return this._life_time_counter;
931✔
576
    }
931✔
577
    public get currentKeepAliveCount(): number {
2✔
578
        return this._keep_alive_counter;
934✔
579
    }
934✔
580

2✔
581
    private _life_time_counter: number;
2✔
582
    protected _keep_alive_counter = 0;
2✔
583
    public _pending_notifications: Queue<InternalNotification>;
2✔
584
    private _sent_notification_messages: NotificationMessage[];
2✔
585
    private readonly _sequence_number_generator: SequenceNumberGenerator;
2✔
586
    private readonly monitoredItems: { [key: number]: MonitoredItem };
2✔
587
    private timerId: ReturnType<typeof setTimeout> | null;
2✔
588
    private _hasUncollectedMonitoredItemNotifications = false;
2✔
589

2✔
590
    private globalCounter: IGlobalMonitoredItemCounter;
2✔
591
    private serverCapabilities: ServerCapabilitiesPartial;
2✔
592

2✔
593
    constructor(options: SubscriptionOptions) {
2✔
594
        super();
537✔
595

537✔
596
        options = options || {};
537!
597

537✔
598
        Subscription.registry.register(this);
537✔
599

537✔
600
        assert(this.sessionId instanceof NodeId, "expecting a sessionId NodeId");
537✔
601

537✔
602
        this.publishEngine = options.publishEngine;
537✔
603

537✔
604
        this.id = options.id || INVALID_ID;
537✔
605

537✔
606
        this.priority = options.priority || 0;
537✔
607

537✔
608
        this.publishingInterval = _adjust_publishing_interval(options.publishingInterval);
537✔
609

537✔
610
        this.maxKeepAliveCount = _adjust_maxKeepAliveCount(options.maxKeepAliveCount); // , this.publishingInterval);
537✔
611

537✔
612
        this.resetKeepAliveCounter();
537✔
613

537✔
614
        this.lifeTimeCount = _adjust_lifeTimeCount(options.lifeTimeCount || 0, this.maxKeepAliveCount, this.publishingInterval);
537✔
615

537✔
616
        this.maxNotificationsPerPublish = _adjust_maxNotificationsPerPublish(options.maxNotificationsPerPublish);
537✔
617

537✔
618
        this._life_time_counter = 0;
537✔
619
        this.resetLifeTimeCounter();
537✔
620

537✔
621
        // notification message that are ready to be sent to the client
537✔
622
        this._pending_notifications = new Queue<InternalNotification>();
537✔
623

537✔
624
        this._sent_notification_messages = [];
537✔
625

537✔
626
        this._sequence_number_generator = new SequenceNumberGenerator();
537✔
627

537✔
628
        // initial state of the subscription
537✔
629
        this.state = SubscriptionState.CREATING;
537✔
630

537✔
631
        this.publishIntervalCount = 0;
537✔
632

537✔
633
        this.monitoredItems = {}; // monitored item map
537✔
634

537✔
635
        this.monitoredItemIdCounter = 0;
537✔
636

537✔
637
        this.publishingEnabled = _adjust_publishingEnable(options.publishingEnabled);
537✔
638

537✔
639
        this.subscriptionDiagnostics = createSubscriptionDiagnostics(this);
537✔
640

537✔
641
        // A boolean value that is set to TRUE to mean that either a NotificationMessage or a keep-alive
537✔
642
        // Message has been sent on the Subscription. It is a flag that is used to ensure that either a
537✔
643
        // NotificationMessage or a keep-alive Message is sent out the first time the publishing
537✔
644
        // timer expires.
537✔
645
        this.messageSent = false;
537✔
646

537✔
647
        this.timerId = null;
537✔
648
        this._start_timer({ firstTime: true });
537✔
649

537✔
650
        debugLog(chalk.green(`creating subscription ${this.id}`));
537✔
651

537✔
652
        this.serverCapabilities = options.serverCapabilities;
537✔
653
        this.serverCapabilities.maxMonitoredItems =
537✔
654
            this.serverCapabilities.maxMonitoredItems || Subscription.defaultMaxMonitoredItemCount;
537!
655
        this.serverCapabilities.maxMonitoredItemsPerSubscription =
537✔
656
            this.serverCapabilities.maxMonitoredItemsPerSubscription || Subscription.defaultMaxMonitoredItemCount;
537!
657
        this.globalCounter = options.globalCounter;
537✔
658
    }
537✔
659

2✔
660
    public getSessionId(): NodeId {
2✔
661
        return this.sessionId;
931✔
662
    }
931✔
663

2✔
664
    public toString(): string {
2✔
665
        let str = "Subscription:\n";
×
UNCOV
666
        str += `  subscriptionId          ${this.id}\n`;
×
UNCOV
667
        str += `  sessionId          ${this.getSessionId()?.toString()}\n`;
×
668

×
669
        str += `  publishingEnabled  ${this.publishingEnabled}\n`;
×
670
        str += `  maxKeepAliveCount  ${this.maxKeepAliveCount}\n`;
×
671
        str += `  publishingInterval ${this.publishingInterval}\n`;
×
672
        str += `  lifeTimeCount      ${this.lifeTimeCount}\n`;
×
673
        str += `  maxKeepAliveCount  ${this.maxKeepAliveCount}\n`;
×
674
        return str;
×
675
    }
×
676

2✔
677
    public toJSON(): Record<string, string | number | boolean> {
2✔
678
        return {
×
679
            id: this.id,
×
UNCOV
680
            sessionId: this.getSessionId() ? this.getSessionId().toString() : NodeId.nullNodeId.toString(),
×
UNCOV
681
            publishingInterval: this.publishingInterval,
×
682
            maxKeepAliveCount: this.maxKeepAliveCount,
×
683
            lifeTimeCount: this.lifeTimeCount,
×
UNCOV
684
            publishingEnabled: this.publishingEnabled,
×
UNCOV
685
            state: this.state !== undefined ? SubscriptionState[this.state] : "unknown",
×
UNCOV
686
            monitoredItemCount: this.monitoredItemCount,
×
UNCOV
687
            pendingNotificationsCount: this.pendingNotificationsCount
×
UNCOV
688
        };
×
UNCOV
689
    }
×
690

2✔
691
    public [Symbol.for("nodejs.util.inspect.custom")](): string {
2✔
UNCOV
692
        return this.toString();
×
UNCOV
693
    }
×
694

2✔
695
    /**
2✔
696
     * modify subscription parameters
2✔
697
     * @param param
2✔
698
     */
2✔
699
    public modify(param: ModifySubscriptionParameters): void {
2✔
700
        // update diagnostic counter
8✔
701
        this.subscriptionDiagnostics.modifyCount += 1;
8✔
702

8✔
703
        const publishingInterval_old = this.publishingInterval;
8✔
704

8✔
705
        param.requestedPublishingInterval = param.requestedPublishingInterval || 0;
8✔
706
        param.requestedMaxKeepAliveCount = param.requestedMaxKeepAliveCount || this.maxKeepAliveCount;
8✔
707
        param.requestedLifetimeCount = param.requestedLifetimeCount || this.lifeTimeCount;
8✔
708

8✔
709
        this.publishingInterval = _adjust_publishing_interval(param.requestedPublishingInterval);
8✔
710
        this.maxKeepAliveCount = _adjust_maxKeepAliveCount(param.requestedMaxKeepAliveCount);
8✔
711

8✔
712
        this.lifeTimeCount = _adjust_lifeTimeCount(param.requestedLifetimeCount, this.maxKeepAliveCount, this.publishingInterval);
8✔
713

8✔
714
        this.maxNotificationsPerPublish = _adjust_maxNotificationsPerPublish(param.maxNotificationsPerPublish || 0);
8✔
715
        this.priority = param.priority || 0;
8✔
716

8✔
717
        this.resetLifeTimeAndKeepAliveCounters();
8✔
718

8✔
719
        if (publishingInterval_old !== this.publishingInterval) {
8✔
720
            // todo
6✔
721
        }
6✔
722
        this._stop_timer();
8✔
723

8✔
724
        this._start_timer({ firstTime: false });
8✔
725
    }
8✔
726

2✔
727
    /**
2✔
728
     * set publishing mode
2✔
729
     * @param publishingEnabled
2✔
730
     */
2✔
731
    public setPublishingMode(publishingEnabled: boolean): StatusCode {
2✔
732
        this.publishingEnabled = !!publishingEnabled;
6✔
733
        // update diagnostics
6✔
734
        if (this.publishingEnabled) {
6✔
735
            this.subscriptionDiagnostics.enableCount += 1;
3✔
736
        } else {
3✔
737
            this.subscriptionDiagnostics.disableCount += 1;
3✔
738
        }
3✔
739

6✔
740
        this.resetLifeTimeCounter();
6✔
741

6✔
742
        if (!publishingEnabled && this.state !== SubscriptionState.CLOSED) {
6✔
743
            this.state = SubscriptionState.NORMAL;
3✔
744
        }
3✔
745
        return StatusCodes.Good;
6✔
746
    }
6✔
747

2✔
748
    /**
2✔
749
     * @private
2✔
750
     */
2✔
751
    public get keepAliveCounterHasExpired(): boolean {
2✔
752
        return this._keep_alive_counter >= this.maxKeepAliveCount || this.state === SubscriptionState.LATE;
4,746✔
753
    }
4,746✔
754

2✔
755
    /**
2✔
756
     * Reset the Lifetime Counter Variable to the value specified for the lifetime of a Subscription in
2✔
757
     * the CreateSubscription Service( 5.13.2).
2✔
758
     * @private
2✔
759
     */
2✔
760
    public resetLifeTimeCounter(): void {
2✔
761
        this._life_time_counter = 0;
2,839✔
762
    }
2,839✔
763

2✔
764
    /**
2✔
765
     * @private
2✔
766
     */
2✔
767
    public increaseLifeTimeCounter(): void {
2✔
768
        this._life_time_counter += 1;
1,663✔
769
        if (this._life_time_counter >= this.lifeTimeCount) {
1,663✔
770
            this.emit("lifeTimeExpired");
15✔
771
        }
15✔
772
        this.emit("lifeTimeCounterChanged", this._life_time_counter);
1,663✔
773
    }
1,663✔
774

2✔
775
    /**
2✔
776
     *  True if the subscription life time has expired.
2✔
777
     *
2✔
778
     */
2✔
779
    public get lifeTimeHasExpired(): boolean {
2✔
780
        assert(this.lifeTimeCount > 0);
6,792✔
781
        return this._life_time_counter >= this.lifeTimeCount;
6,792✔
782
    }
6,792✔
783

2✔
784
    /**
2✔
785
     * number of milliseconds before this subscription times out (lifeTimeHasExpired === true);
2✔
786
     */
2✔
787
    public get timeToExpiration(): number {
2✔
788
        return (this.lifeTimeCount - this._life_time_counter) * this.publishingInterval;
22✔
789
    }
22✔
790

2✔
791
    public get timeToKeepAlive(): number {
2✔
UNCOV
792
        return (this.maxKeepAliveCount - this._keep_alive_counter) * this.publishingInterval;
×
UNCOV
793
    }
×
794

2✔
795
    /**
2✔
796
     * Terminates the subscription.
2✔
797
     * Calling this method will also remove any monitored items.
2✔
798
     *
2✔
799
     */
2✔
800
    public terminate(): void {
2✔
801
        debugLog("Subscription#terminate status", SubscriptionState[this.state]);
543✔
802

543✔
803
        if (this.state === SubscriptionState.CLOSED) {
543✔
804
            // todo verify if asserting is required here
6✔
805
            return;
6✔
806
        }
6✔
807

543✔
808
        // stop timer
543✔
809
        this._stop_timer();
543✔
810

537✔
811
        debugLog("terminating Subscription  ", this.id, " with ", this.monitoredItemCount, " monitored items");
537✔
812

537✔
813
        // dispose all monitoredItem
537✔
814
        const keys = Object.keys(this.monitoredItems);
537✔
815

537✔
816
        for (const key of keys) {
543✔
817
            const status = this.removeMonitoredItem(parseInt(key, 10));
8,066✔
818
            assert(status === StatusCodes.Good);
8,066✔
819
        }
8,066✔
820
        assert(this.monitoredItemCount === 0);
537✔
821

537✔
822
        if (this.$session?._unexposeSubscriptionDiagnostics) {
543✔
823
            this.$session._unexposeSubscriptionDiagnostics(this);
421✔
824
        }
421✔
825
        this.state = SubscriptionState.CLOSED;
543✔
826

537✔
827
        /**
537✔
828
         * notify the subscription owner that the subscription has been terminated.
537✔
829
         * @event "terminated"
537✔
830
         */
537✔
831
        this.emit("terminated");
537✔
832
        if (this.publishEngine) {
537✔
833
            this.publishEngine.on_close_subscription(this);
537✔
834
        }
537✔
835
    }
543✔
836

2✔
837
    public setTriggering(
2✔
838
        triggeringItemId: number,
24✔
839
        linksToAdd: number[] | null,
24✔
840
        linksToRemove: number[] | null
24✔
841
    ): { statusCode: StatusCode; addResults: StatusCode[]; removeResults: StatusCode[] } {
24✔
842
        /** Bad_NothingToDo, Bad_TooManyOperations,Bad_SubscriptionIdInvalid, Bad_MonitoredItemIdInvalid */
24✔
843
        linksToAdd = linksToAdd || [];
24!
844
        linksToRemove = linksToRemove || [];
24!
845

24✔
846
        if (linksToAdd.length === 0 && linksToRemove.length === 0) {
24✔
847
            return { statusCode: StatusCodes.BadNothingToDo, addResults: [], removeResults: [] };
3✔
848
        }
3✔
849
        const triggeringItem = this.getMonitoredItem(triggeringItemId);
23✔
850

21✔
851
        const monitoredItemsToAdd = linksToAdd.map((id) => this.getMonitoredItem(id));
21✔
852
        const monitoredItemsToRemove = linksToRemove.map((id) => this.getMonitoredItem(id));
21✔
853

21✔
854
        if (!triggeringItem) {
24✔
855
            const removeResults1: StatusCode[] = monitoredItemsToRemove.map((m) =>
1✔
UNCOV
856
                m ? StatusCodes.Good : StatusCodes.BadMonitoredItemIdInvalid
×
857
            );
1✔
858
            const addResults1: StatusCode[] = monitoredItemsToAdd.map((m) =>
1✔
859
                m ? StatusCodes.Good : StatusCodes.BadMonitoredItemIdInvalid
1!
860
            );
1✔
861
            return {
1✔
862
                statusCode: StatusCodes.BadMonitoredItemIdInvalid,
1✔
863

1✔
864
                addResults: addResults1,
1✔
865
                removeResults: removeResults1
1✔
866
            };
1✔
867
        }
1✔
868
        //
23✔
869
        // note: it seems that CTT imposed that we do remove before add
23✔
870
        const removeResults = monitoredItemsToRemove.map((m) =>
23✔
871
            !m ? StatusCodes.BadMonitoredItemIdInvalid : triggeringItem.removeLinkItem(m.monitoredItemId)
10✔
872
        );
20✔
873
        const addResults = monitoredItemsToAdd.map((m) =>
20✔
874
            !m ? StatusCodes.BadMonitoredItemIdInvalid : triggeringItem.addLinkItem(m.monitoredItemId)
26✔
875
        );
20✔
876

20✔
877
        const statusCode: StatusCode = StatusCodes.Good;
20✔
878

20✔
879
        // do binding
20✔
880

20✔
881
        return {
20✔
882
            statusCode,
20✔
883

20✔
884
            addResults,
20✔
885
            removeResults
20✔
886
        };
20✔
887
    }
23✔
888
    public dispose(): void {
2✔
889
        // c8 ignore next
581✔
890
        if (doDebug) {
581!
UNCOV
891
            debugLog("Subscription#dispose", this.id, this.monitoredItemCount);
×
UNCOV
892
        }
×
893

581✔
894
        assert(this.monitoredItemCount === 0, "MonitoredItems haven't been  deleted first !!!");
581✔
895
        assert(this.timerId === null, "Subscription timer haven't been terminated");
581✔
896

581✔
897
        if (this.subscriptionDiagnostics) {
581✔
898
            (this.subscriptionDiagnostics as SubscriptionDiagnosticsDataTypePriv).$subscription = null as unknown as Subscription;
581✔
899
        }
581✔
900

581✔
901
        this.publishEngine = undefined;
581✔
902
        this._pending_notifications.clear();
581✔
903
        this._sent_notification_messages = [];
581✔
904

581✔
905
        this.$session = undefined;
581✔
906
        this.removeAllListeners();
581✔
907

581✔
908
        Subscription.registry.unregister(this);
581✔
909
    }
581✔
910

2✔
911
    public get aborted(): boolean {
2✔
UNCOV
912
        const session = this.$session;
×
UNCOV
913
        if (!session) {
×
UNCOV
914
            return true;
×
UNCOV
915
        }
×
UNCOV
916
        return session.aborted;
×
UNCOV
917
    }
×
918

2✔
919
    /**
2✔
920
     * number of pending notifications
2✔
921
     */
2✔
922
    public get pendingNotificationsCount(): number {
2✔
923
        return this._pending_notifications ? this._pending_notifications.size : 0;
12,139!
924
    }
12,139✔
925

2✔
926
    /**
2✔
927
     * is 'true' if there are pending notifications for this subscription. (i.e moreNotifications)
2✔
928
     */
2✔
929
    public get hasPendingNotifications(): boolean {
2✔
930
        return this.pendingNotificationsCount > 0;
12,132✔
931
    }
12,132✔
932

2✔
933
    /**
2✔
934
     * number of sent notifications
2✔
935
     */
2✔
936
    public get sentNotificationMessageCount(): number {
2✔
937
        return this._sent_notification_messages.length;
11✔
938
    }
11✔
939

2✔
940
    /**
2✔
941
     * @internal
2✔
942
     */
2✔
943
    public _flushSentNotifications(): NotificationMessage[] {
2✔
UNCOV
944
        const tmp = this._sent_notification_messages;
×
UNCOV
945
        this._sent_notification_messages = [];
×
UNCOV
946
        return tmp;
×
UNCOV
947
    }
×
948
    /**
2✔
949
     * number of monitored items handled by this subscription
2✔
950
     */
2✔
951
    public get monitoredItemCount(): number {
2✔
952
        return Object.keys(this.monitoredItems).length;
30,700✔
953
    }
30,700✔
954

2✔
955
    /**
2✔
956
     * number of disabled monitored items.
2✔
957
     */
2✔
958
    public get disabledMonitoredItemCount(): number {
2✔
959
        return Object.values(this.monitoredItems).reduce((sum: number, monitoredItem: MonitoredItem) => {
932✔
960
            return sum + (monitoredItem.monitoringMode === MonitoringMode.Disabled ? 1 : 0);
2,808✔
961
        }, 0);
932✔
962
    }
932✔
963

2✔
964
    /**
2✔
965
     * The number of unacknowledged messages saved in the republish queue.
2✔
966
     */
2✔
967
    public get unacknowledgedMessageCount(): number {
2✔
UNCOV
968
        return this.subscriptionDiagnostics.unacknowledgedMessageCount;
×
UNCOV
969
    }
×
970

2✔
971
    /**
2✔
972
     * adjust monitored item sampling interval
2✔
973
     *  - an samplingInterval ===0 means that we use a event-base model ( no sampling)
2✔
974
     *  - otherwise the sampling is adjusted
2✔
975
     * @private
2✔
976
     */
2✔
977
    public adjustSamplingInterval(samplingInterval: number, node?: IReadAttributeCapable): number {
2✔
978
        if (samplingInterval < 0) {
14,752✔
979
            // - The value -1 indicates that the default sampling interval defined by the publishing
5✔
980
            //   interval of the Subscription is requested.
5✔
981
            // - Any negative number is interpreted as -1.
5✔
982
            samplingInterval = this.publishingInterval;
5✔
983
        } else if (samplingInterval === 0) {
14,752✔
984
            // c8 ignore next
436✔
985
            if (!node) throw new Error("Internal Error");
436!
986

436✔
987
            // OPCUA 1.0.3 Part 4 - 5.12.1.2
436✔
988
            // The value 0 indicates that the Server should use the fastest practical rate.
436✔
989

436✔
990
            // The fastest supported sampling interval may be equal to 0, which indicates
436✔
991
            // that the data item is exception-based rather than being sampled at some period.
436✔
992
            // An exception-based model means that the underlying system does not require
436✔
993
            // sampling and reports data changes.
436✔
994

436✔
995
            const dataValueSamplingInterval = node.readAttribute(
436✔
996
                SessionContext.defaultContext,
436✔
997
                AttributeIds.MinimumSamplingInterval
436✔
998
            );
436✔
999

436✔
1000
            // TODO if attributeId === AttributeIds.Value : sampling interval required here
436✔
1001
            if (dataValueSamplingInterval.statusCode.isGood()) {
436✔
1002
                // node provides a Minimum sampling interval ...
329✔
1003
                samplingInterval = dataValueSamplingInterval.value.value;
329✔
1004
                assert(samplingInterval >= 0 && samplingInterval <= MonitoredItem.maximumSamplingInterval);
329✔
1005

329✔
1006
                // note : at this stage, a samplingInterval===0 means that the data item is really exception-based
329✔
1007
            }
329✔
1008
        } else if (samplingInterval < MonitoredItem.minimumSamplingInterval) {
14,747✔
1009
            samplingInterval = MonitoredItem.minimumSamplingInterval;
6,768✔
1010
        } else if (samplingInterval > MonitoredItem.maximumSamplingInterval) {
14,311✔
1011
            // If the requested samplingInterval is higher than the
1✔
1012
            // maximum sampling interval supported by the Server, the maximum sampling
1✔
1013
            // interval is returned.
1✔
1014
            samplingInterval = MonitoredItem.maximumSamplingInterval;
1✔
1015
        }
1✔
1016

14,752✔
1017
        const node_minimumSamplingInterval =
14,752✔
1018
            node && (node as UAVariable).minimumSamplingInterval ? (node as UAVariable).minimumSamplingInterval : 0;
14,752✔
1019

14,752✔
1020
        samplingInterval = Math.max(samplingInterval, node_minimumSamplingInterval);
14,752✔
1021

14,752✔
1022
        return samplingInterval;
14,752✔
1023
    }
14,752✔
1024

2✔
1025
    /**
2✔
1026
     * create a monitored item
2✔
1027
     * @param addressSpace - address space
2✔
1028
     * @param timestampsToReturn  - the timestamp to return
2✔
1029
     * @param monitoredItemCreateRequest - the parameters describing the monitored Item to create
2✔
1030
     */
2✔
1031
    public preCreateMonitoredItem(
2✔
1032
        addressSpace: IAddressSpace,
14,774✔
1033
        timestampsToReturn: TimestampsToReturn,
14,774✔
1034
        monitoredItemCreateRequest: MonitoredItemCreateRequest
14,774✔
1035
    ): InternalCreateMonitoredItemResult {
14,774✔
1036
        assert(monitoredItemCreateRequest instanceof MonitoredItemCreateRequest);
14,774✔
1037

14,774✔
1038
        function handle_error(statusCode: StatusCode): InternalCreateMonitoredItemResult {
14,774✔
1039
            return {
30✔
1040
                createResult: new MonitoredItemCreateResult({ statusCode }),
30✔
1041
                monitoredItemCreateRequest
30✔
1042
            };
30✔
1043
        }
30✔
1044

14,774✔
1045
        const itemToMonitor = monitoredItemCreateRequest.itemToMonitor;
14,774✔
1046

14,774✔
1047
        const node = addressSpace.findNode(itemToMonitor.nodeId) as UAObject | UAVariable | UAMethod;
14,774✔
1048
        if (
14,774✔
1049
            !node ||
14,774✔
1050
            (node.nodeClass !== NodeClass.Variable && node.nodeClass !== NodeClass.Object && node.nodeClass !== NodeClass.Method)
14,770✔
1051
        ) {
14,774✔
1052
            return handle_error(StatusCodes.BadNodeIdUnknown);
4✔
1053
        }
4✔
1054

14,771✔
1055
        if (itemToMonitor.attributeId === AttributeIds.Value && !(node.nodeClass === NodeClass.Variable)) {
14,774✔
1056
            // AttributeIds.Value is only valid for monitoring value of UAVariables.
2✔
1057
            return handle_error(StatusCodes.BadAttributeIdInvalid);
2✔
1058
        }
2✔
1059

14,769✔
1060
        if (itemToMonitor.attributeId === AttributeIds.INVALID) {
14,774✔
1061
            return handle_error(StatusCodes.BadAttributeIdInvalid);
1✔
1062
        }
1✔
1063

14,768✔
1064
        if (!itemToMonitor.indexRange.isValid()) {
14,774✔
1065
            return handle_error(StatusCodes.BadIndexRangeInvalid);
1✔
1066
        }
1✔
1067

14,767✔
1068
        // check dataEncoding applies only on Values
14,767✔
1069
        if (itemToMonitor.dataEncoding.name && itemToMonitor.attributeId !== AttributeIds.Value) {
14,774!
UNCOV
1070
            return handle_error(StatusCodes.BadDataEncodingInvalid);
×
UNCOV
1071
        }
×
1072

14,767✔
1073
        // check dataEncoding
14,767✔
1074
        if (!isValidDataEncoding(itemToMonitor.dataEncoding)) {
14,774!
UNCOV
1075
            return handle_error(StatusCodes.BadDataEncodingUnsupported);
×
UNCOV
1076
        }
×
1077

14,767✔
1078
        // check that item can be read by current user session
14,767✔
1079

14,767✔
1080
        // filter
14,767✔
1081
        const requestedParameters = monitoredItemCreateRequest.requestedParameters;
14,767✔
1082
        const filter = requestedParameters.filter;
14,766✔
1083
        const statusCodeFilter = validateFilter(filter, itemToMonitor, node, {
14,766✔
1084
            maxWhereClauseParameters: this.serverCapabilities.maxWhereClauseParameters,
14,766✔
1085
            maxSelectClauseParameters: this.serverCapabilities.maxSelectClauseParameters
14,766✔
1086
        });
14,766✔
1087
        if (statusCodeFilter !== StatusCodes.Good) {
14,774✔
1088
            return handle_error(statusCodeFilter);
19✔
1089
        }
19✔
1090

14,760✔
1091
        // do we have enough room for new monitored items ?
14,760✔
1092
        if (this.monitoredItemCount >= this.serverCapabilities.maxMonitoredItemsPerSubscription) {
14,774✔
1093
            return handle_error(StatusCodes.BadTooManyMonitoredItems);
1✔
1094
        }
1✔
1095

14,760✔
1096
        if (this.globalCounter.totalMonitoredItemCount >= this.serverCapabilities.maxMonitoredItems) {
14,774✔
1097
            return handle_error(StatusCodes.BadTooManyMonitoredItems);
2✔
1098
        }
2✔
1099

14,760✔
1100
        const createResult = this._createMonitoredItemStep2(timestampsToReturn, monitoredItemCreateRequest, node);
14,760✔
1101

14,744✔
1102
        assert(createResult.statusCode.isGood());
14,744✔
1103

14,744✔
1104
        const monitoredItem = this.getMonitoredItem(createResult.monitoredItemId);
14,744✔
1105
        // c8 ignore next
14,744✔
1106
        if (!monitoredItem) {
14,774!
UNCOV
1107
            throw new Error("internal error");
×
UNCOV
1108
        }
×
1109

14,760✔
1110
        // TODO: fix old way to set node. !!!!
14,760✔
1111
        monitoredItem.setNode(node);
14,760✔
1112

14,744✔
1113
        this.emit("monitoredItem", monitoredItem, itemToMonitor);
14,744✔
1114

14,744✔
1115
        return { monitoredItem, monitoredItemCreateRequest, createResult };
14,744✔
1116
    }
14,760✔
1117

2✔
1118
    public async applyOnMonitoredItem(functor: (monitoredItem: MonitoredItem) => Promise<void>): Promise<void> {
2✔
1119
        for (const m of Object.values(this.monitoredItems)) {
4✔
1120
            await functor(m);
5✔
1121
        }
5✔
1122
    }
4✔
1123

2✔
1124
    public postCreateMonitoredItem(
2✔
1125
        monitoredItem: MonitoredItem,
14,744✔
1126
        monitoredItemCreateRequest: MonitoredItemCreateRequest,
14,744✔
1127
        _createResult: MonitoredItemCreateResult
14,744✔
1128
    ): void {
14,744✔
1129
        this._createMonitoredItemStep3(monitoredItem, monitoredItemCreateRequest);
14,744✔
1130
    }
14,744✔
1131

2✔
1132
    public async createMonitoredItem(
2✔
1133
        addressSpace: IAddressSpace,
116✔
1134
        timestampsToReturn: TimestampsToReturn,
116✔
1135
        monitoredItemCreateRequest: MonitoredItemCreateRequest
116✔
1136
    ): Promise<MonitoredItemCreateResult> {
116✔
1137
        const { monitoredItem, createResult } = this.preCreateMonitoredItem(
116✔
1138
            addressSpace,
116✔
1139
            timestampsToReturn,
116✔
1140
            monitoredItemCreateRequest
116✔
1141
        );
116✔
1142
        if (!monitoredItem) {
116✔
1143
            return createResult;
20✔
1144
        }
20✔
1145
        this.postCreateMonitoredItem(monitoredItem, monitoredItemCreateRequest, createResult);
112✔
1146
        return createResult;
96✔
1147
    }
112✔
1148
    /**
2✔
1149
     * get a monitoredItem by Id.
2✔
1150
     * @param monitoredItemId : the id of the monitored item to get.
2✔
1151
     * @return the monitored item matching monitoredItemId
2✔
1152
     */
2✔
1153
    public getMonitoredItem(monitoredItemId: number): MonitoredItem | null {
2✔
1154
        return this.monitoredItems[monitoredItemId] || null;
21,879✔
1155
    }
21,879✔
1156

2✔
1157
    /**
2✔
1158
     * remove a monitored Item from the subscription.
2✔
1159
     * @param monitoredItemId : the id of the monitored item to get.
2✔
1160
     */
2✔
1161
    public removeMonitoredItem(monitoredItemId: number): StatusCode {
2✔
1162
        debugLog("Removing monitoredIem ", monitoredItemId);
14,779✔
1163
        if (!Object.hasOwn(this.monitoredItems, monitoredItemId.toString())) {
14,779✔
1164
            return StatusCodes.BadMonitoredItemIdInvalid;
4✔
1165
        }
4✔
1166

14,776✔
1167
        const monitoredItem = this.monitoredItems[monitoredItemId];
14,776✔
1168

14,775✔
1169
        monitoredItem.terminate();
14,775✔
1170

14,775✔
1171
        /**
14,775✔
1172
         *
14,775✔
1173
         * notify that a monitored item has been removed from the subscription
14,775✔
1174
         * @param monitoredItem {MonitoredItem}
14,775✔
1175
         */
14,775✔
1176
        this.emit("removeMonitoredItem", monitoredItem);
14,775✔
1177

14,775✔
1178
        monitoredItem.dispose();
14,775✔
1179

14,775✔
1180
        delete this.monitoredItems[monitoredItemId];
14,775✔
1181
        this.globalCounter.totalMonitoredItemCount -= 1;
14,775✔
1182

14,775✔
1183
        this._removePendingNotificationsFor(monitoredItemId);
14,775✔
1184
        // flush pending notifications
14,775✔
1185
        // assert(this._pending_notifications.size === 0);
14,775✔
1186
        return StatusCodes.Good;
14,775✔
1187
    }
14,776✔
1188

2✔
1189
    /**
2✔
1190
     * rue if monitored Item have uncollected Notifications
2✔
1191
     */
2✔
1192
    public get hasUncollectedMonitoredItemNotifications(): boolean {
2✔
1193
        if (this._hasUncollectedMonitoredItemNotifications) {
7,911✔
1194
            return true;
1,301✔
1195
        }
1,301✔
1196
        const keys = Object.keys(this.monitoredItems);
7,075✔
1197
        const n = keys.length;
6,610✔
1198
        for (let i = 0; i < n; i++) {
7,911✔
1199
            const key = parseInt(keys[i], 10);
44,108✔
1200
            const monitoredItem = this.monitoredItems[key];
44,108✔
1201
            if (monitoredItem.hasMonitoredItemNotifications) {
44,108✔
1202
                this._hasUncollectedMonitoredItemNotifications = true;
860✔
1203
                return true;
860✔
1204
            }
860✔
1205
        }
44,108✔
1206
        return false;
6,421✔
1207
    }
6,421✔
1208

2✔
1209
    public get subscriptionId(): number {
2✔
1210
        return this.id;
885✔
1211
    }
885✔
1212

2✔
1213
    public getMessageForSequenceNumber(sequenceNumber: number): NotificationMessage | null {
2✔
1214
        const notification_message = this._sent_notification_messages.find((e) => e.sequenceNumber === sequenceNumber);
28✔
1215
        return notification_message || null;
28✔
1216
    }
28✔
1217

2✔
1218
    /**
2✔
1219
     * returns true if the notification has expired
2✔
1220
     * @param notification
2✔
1221
     */
2✔
1222
    public notificationHasExpired(notification: { start_tick: number }): boolean {
2✔
UNCOV
1223
        assert(Object.hasOwn(notification, "start_tick"));
×
UNCOV
1224
        assert(Number.isFinite(notification.start_tick + this.maxKeepAliveCount));
×
UNCOV
1225
        return notification.start_tick + this.maxKeepAliveCount < this.publishIntervalCount;
×
UNCOV
1226
    }
×
1227

2✔
1228
    /**
2✔
1229
     *  returns in an array the sequence numbers of the notifications that have been sent
2✔
1230
     *  and that haven't been acknowledged yet.
2✔
1231
     */
2✔
1232
    public getAvailableSequenceNumbers(): number[] {
2✔
1233
        const availableSequenceNumbers = _getSequenceNumbers(this._sent_notification_messages);
1,442✔
1234
        return availableSequenceNumbers;
1,442✔
1235
    }
1,442✔
1236

2✔
1237
    /**
2✔
1238
     * acknowledges a notification identified by its sequence number
2✔
1239
     */
2✔
1240
    public acknowledgeNotification(sequenceNumber: number): StatusCode {
2✔
1241
        debugLog("acknowledgeNotification ", sequenceNumber);
614✔
1242
        let foundIndex = -1;
614✔
1243
        this._sent_notification_messages.forEach((e: NotificationMessage, index: number) => {
614✔
1244
            if (e.sequenceNumber === sequenceNumber) {
726✔
1245
                foundIndex = index;
613✔
1246
            }
613✔
1247
        });
614✔
1248

614✔
1249
        if (foundIndex === -1) {
614✔
1250
            // c8 ignore next
1✔
1251
            if (doDebug) {
1!
UNCOV
1252
                debugLog(chalk.red("acknowledging sequence FAILED !!! "), chalk.cyan(sequenceNumber.toString()));
×
UNCOV
1253
            }
×
1254
            return StatusCodes.BadSequenceNumberUnknown;
1✔
1255
        } else {
614✔
1256
            // c8 ignore next
613✔
1257
            if (doDebug) {
613!
UNCOV
1258
                debugLog(chalk.yellow("acknowledging sequence "), chalk.cyan(sequenceNumber.toString()));
×
UNCOV
1259
            }
×
1260
            this._sent_notification_messages.splice(foundIndex, 1);
613✔
1261
            this.subscriptionDiagnostics.unacknowledgedMessageCount--;
613✔
1262
            return StatusCodes.Good;
613✔
1263
        }
613✔
1264
    }
614✔
1265

2✔
1266
    /**
2✔
1267
     * getMonitoredItems is used to get information about monitored items of a subscription.Its intended
2✔
1268
     * use is defined in Part 4. This method is the implementation of the Standard OPCUA GetMonitoredItems Method.
2✔
1269
     * from spec:
2✔
1270
     * This method can be used to get the  list of monitored items in a subscription if CreateMonitoredItems
2✔
1271
     * failed due to a network interruption and the client does not know if the creation succeeded in the server.
2✔
1272
     *
2✔
1273
     */
2✔
1274
    public getMonitoredItems(): GetMonitoredItemsResult {
2✔
1275
        const monitoredItems = Object.keys(this.monitoredItems);
8✔
1276
        const monitoredItemCount = monitoredItems.length;
8✔
1277
        const result: GetMonitoredItemsResult = {
8✔
1278
            clientHandles: new Uint32Array(monitoredItemCount),
8✔
1279
            serverHandles: new Uint32Array(monitoredItemCount),
8✔
1280
            statusCode: StatusCodes.Good
8✔
1281
        };
8✔
1282
        for (let index = 0; index < monitoredItemCount; index++) {
8✔
1283
            const monitoredItemId = monitoredItems[index];
331✔
1284
            const serverHandle = parseInt(monitoredItemId, 10);
331✔
1285
            const monitoredItem = this.getMonitoredItem(serverHandle);
331✔
1286
            // c8 ignore next
331✔
1287
            if (!monitoredItem) {
331!
UNCOV
1288
                throw new Error("monitoredItem is null");
×
UNCOV
1289
            }
×
1290
            result.clientHandles[index] = monitoredItem.clientHandle;
331✔
1291
            // TODO:  serverHandle is defined anywhere in the OPCUA Specification 1.02
331✔
1292
            //        I am not sure what shall be reported for serverHandle...
331✔
1293
            //        using monitoredItem.monitoredItemId instead...
331✔
1294
            //        May be a clarification in the OPCUA Spec is required.
331✔
1295
            result.serverHandles[index] = serverHandle;
331✔
1296
        }
331✔
1297
        return result;
8✔
1298
    }
8✔
1299

2✔
1300
    /**
2✔
1301
     * @private
2✔
1302
     */
2✔
1303
    public async resendInitialValues(): Promise<void> {
2✔
1304
        this._keep_alive_counter = 0;
26✔
1305

26✔
1306
        try {
26✔
1307
            const promises: Promise<void>[] = [];
26✔
1308
            for (const monitoredItem of Object.values(this.monitoredItems)) {
26✔
1309
                promises.push(
19✔
1310
                    (async () => {
19✔
1311
                        try {
19✔
1312
                            monitoredItem.resendInitialValue();
19✔
1313
                        } catch (err) {
19!
1314
                            warningLog(
×
UNCOV
1315
                                "resendInitialValues:",
×
UNCOV
1316
                                monitoredItem.node?.nodeId.toString(),
×
UNCOV
1317
                                "error:",
×
UNCOV
1318
                                (err as Error).message
×
UNCOV
1319
                            );
×
UNCOV
1320
                        }
×
1321
                    })()
19✔
1322
                );
19✔
1323
            }
19✔
1324
            await Promise.all(promises);
26✔
1325
        } catch (err) {
26!
UNCOV
1326
            warningLog("resendInitialValues: error:", (err as Error).message);
×
UNCOV
1327
        }
×
1328
        // make sure data will be sent immediately
26✔
1329
        this._keep_alive_counter = this.maxKeepAliveCount - 1;
26✔
1330
        this.state = SubscriptionState.NORMAL;
26✔
1331
        this._harvestMonitoredItems();
26✔
1332
    }
26✔
1333

2✔
1334
    /**
2✔
1335
     * @private
2✔
1336
     */
2✔
1337
    public notifyTransfer(): void {
2✔
1338
        // OPCUA UA Spec 1.0.3 : part 3 - page 82 - 5.13.7 TransferSubscriptions:
27✔
1339
        // If the Server transfers the Subscription to the new Session, the Server shall issue
27✔
1340
        // a StatusChangeNotification notificationMessage with the status code
27✔
1341
        // Good_SubscriptionTransferred to the old Session.
27✔
1342
        debugLog(chalk.red(" Subscription => Notifying Transfer                                  "));
27✔
1343

27✔
1344
        const notificationData = new StatusChangeNotification({
27✔
1345
            status: StatusCodes.GoodSubscriptionTransferred
27✔
1346
        });
27✔
1347

27✔
1348
        if (this.publishEngine?.pendingPublishRequestCount) {
27✔
1349
            // the GoodSubscriptionTransferred can be processed immediately
12✔
1350
            this._addNotificationMessage(notificationData);
12✔
1351
            debugLog(chalk.red("pendingPublishRequestCount"), this.publishEngine?.pendingPublishRequestCount);
12✔
1352
            this._publish_pending_notifications();
12✔
1353
        } else {
27✔
1354
            debugLog(chalk.red("Cannot  send GoodSubscriptionTransferred => lets create a TransferredSubscription "));
15✔
1355
            // c8 ignore next
15✔
1356
            if (!this.publishEngine) {
15!
UNCOV
1357
                warningLog("notifyTransfer: publishEngine is not available");
×
UNCOV
1358
                return;
×
UNCOV
1359
            }
×
1360
            const ts = new TransferredSubscription({
15✔
1361
                generator: this._sequence_number_generator,
15✔
1362
                id: this.id,
15✔
1363
                publishEngine: this.publishEngine
15✔
1364
            });
15✔
1365

15✔
1366
            ts._pending_notification = notificationData;
15✔
1367
            (this.publishEngine as unknown as { _closed_subscriptions: TransferredSubscription[] })._closed_subscriptions.push(ts);
15✔
1368
        }
15✔
1369
    }
27✔
1370

2✔
1371
    /**
2✔
1372
     *
2✔
1373
     *  the server invokes the resetLifeTimeAndKeepAliveCounters method of the subscription
2✔
1374
     *  when the server  has send a Publish Response, so that the subscription
2✔
1375
     *  can reset its life time counter.
2✔
1376
     *
2✔
1377
     * @private
2✔
1378
     */
2✔
1379
    public resetLifeTimeAndKeepAliveCounters(): void {
2✔
1380
        this.resetLifeTimeCounter();
2,269✔
1381
        this.resetKeepAliveCounter();
2,269✔
1382
    }
2,269✔
1383

2✔
1384
    private _updateCounters(notificationMessage: NotificationMessage) {
2✔
1385
        for (const notificationData of notificationMessage.notificationData || []) {
880!
1386
            // update diagnostics
881✔
1387
            if (notificationData instanceof DataChangeNotification) {
881✔
1388
                const nbNotifs = notificationData.monitoredItems?.length || 0;
813!
1389
                this.subscriptionDiagnostics.dataChangeNotificationsCount += nbNotifs;
813✔
1390
                this.subscriptionDiagnostics.notificationsCount += nbNotifs;
813✔
1391
            } else if (notificationData instanceof EventNotificationList) {
881✔
1392
                const nbNotifs = notificationData.events?.length || 0;
50!
1393
                this.subscriptionDiagnostics.eventNotificationsCount += nbNotifs;
50✔
1394
                this.subscriptionDiagnostics.notificationsCount += nbNotifs;
50✔
1395
            } else {
68✔
1396
                assert(notificationData instanceof StatusChangeNotification);
18✔
1397
                // TODO
18✔
1398
                // note: :there is no way to count StatusChangeNotifications in opcua yet.
18✔
1399
            }
18✔
1400
        }
881✔
1401
    }
880✔
1402
    /**
2✔
1403
     *  _publish_pending_notifications send a "notification" event:
2✔
1404
     *
2✔
1405
     * @private
2✔
1406
     *
2✔
1407
     * precondition
2✔
1408
     *     - pendingPublishRequestCount > 0
2✔
1409
     */
2✔
1410
    public _publish_pending_notifications(): void {
2✔
1411
        const publishEngine = this.publishEngine;
880✔
1412
        // c8 ignore next
880✔
1413
        if (!publishEngine) {
880!
UNCOV
1414
            throw new Error("publishEngine is null");
×
UNCOV
1415
        }
×
1416
        const subscriptionId = this.id;
880✔
1417
        // preconditions
880✔
1418
        assert(publishEngine.pendingPublishRequestCount > 0);
880✔
1419
        assert(this.hasPendingNotifications);
880✔
1420

880✔
1421
        const notificationMessage = this._popNotificationToSend();
880✔
1422
        if (notificationMessage.notificationData?.length === 0) {
880!
UNCOV
1423
            return; // nothing to do
×
UNCOV
1424
        }
×
1425
        const moreNotifications = this.hasPendingNotifications;
880✔
1426

880✔
1427
        this.emit("notification", notificationMessage);
880✔
1428
        // Update counters ....
880✔
1429
        this._updateCounters(notificationMessage);
880✔
1430

880✔
1431
        assert(Object.hasOwn(notificationMessage, "sequenceNumber"));
880✔
1432
        assert(Object.hasOwn(notificationMessage, "notificationData"));
880✔
1433
        // update diagnostics
880✔
1434
        this.subscriptionDiagnostics.publishRequestCount += 1;
880✔
1435

880✔
1436
        const response = new PublishResponse({
880✔
1437
            moreNotifications,
880✔
1438
            notificationMessage: {
880✔
1439
                notificationData: notificationMessage.notificationData,
880✔
1440
                sequenceNumber: this._get_next_sequence_number()
880✔
1441
            },
880✔
1442
            subscriptionId
880✔
1443
        });
880✔
1444

880✔
1445
        this._sent_notification_messages.push(response.notificationMessage);
880✔
1446

880✔
1447
        // get available sequence number;
880✔
1448
        const availableSequenceNumbers = this.getAvailableSequenceNumbers();
880✔
1449
        assert(
880✔
1450
            !response.notificationMessage ||
880✔
1451
            availableSequenceNumbers[availableSequenceNumbers.length - 1] === response.notificationMessage.sequenceNumber
880✔
1452
        );
880✔
1453
        response.availableSequenceNumbers = availableSequenceNumbers;
880✔
1454

880✔
1455
        publishEngine._send_response(this, response);
880✔
1456

880✔
1457
        this.messageSent = true;
880✔
1458

880✔
1459
        this.subscriptionDiagnostics.unacknowledgedMessageCount++;
880✔
1460

880✔
1461
        this.resetLifeTimeAndKeepAliveCounters();
880✔
1462

880✔
1463
        // c8 ignore next
880✔
1464
        if (doDebug) {
880!
UNCOV
1465
            debugLog(
×
UNCOV
1466
                "Subscription sending a notificationMessage subscriptionId=",
×
UNCOV
1467
                subscriptionId,
×
UNCOV
1468
                "sequenceNumber = ",
×
UNCOV
1469
                notificationMessage.sequenceNumber.toString(),
×
UNCOV
1470
                notificationMessage.notificationData?.map((x) => x?.constructor.name).join(" ")
×
UNCOV
1471
            );
×
UNCOV
1472
            // debugLog(notificationMessage.toString());
×
UNCOV
1473
        }
×
1474

880✔
1475
        if (this.state !== SubscriptionState.CLOSED) {
880✔
1476
            assert((notificationMessage.notificationData?.length || 0) > 0, "We are not expecting a keep-alive message here");
874!
1477
            this.state = SubscriptionState.NORMAL;
874✔
1478
            debugLog(`subscription ${this.id}${chalk.bgYellow(" set to NORMAL")}`);
874✔
1479
        }
874✔
1480
    }
880✔
1481

2✔
1482
    public process_subscription(): void {
2✔
1483
        assert((this.publishEngine?.pendingPublishRequestCount || 0) > 0);
1,402!
1484

1,402✔
1485
        if (!this.publishingEnabled) {
1,402✔
1486
            // no publish to do, except keep alive
101✔
1487
            debugLog("    -> no publish to do, except keep alive");
101✔
1488
            this._process_keepAlive();
101✔
1489
            return;
101✔
1490
        }
101✔
1491

1,388✔
1492
        if (!this.hasPendingNotifications && this.hasUncollectedMonitoredItemNotifications) {
1,402✔
1493
            // collect notification from monitored items
845✔
1494
            this._harvestMonitoredItems();
845✔
1495
        }
845✔
1496

1,388✔
1497
        // let process them first
1,388✔
1498
        if (this.hasPendingNotifications) {
1,402✔
1499
            this._publish_pending_notifications();
862✔
1500

862✔
1501
            if (
862✔
1502
                this.state === SubscriptionState.NORMAL ||
862!
UNCOV
1503
                (this.state === SubscriptionState.LATE && this.hasPendingNotifications)
×
1504
            ) {
862✔
1505
                // c8 ignore next
862✔
1506
                if (doDebug) {
862!
UNCOV
1507
                    debugLog("    -> pendingPublishRequestCount > 0 " + "&& normal state => re-trigger tick event immediately ");
×
UNCOV
1508
                }
×
1509

862✔
1510
                // let process an new publish request
862✔
1511
                setImmediate(this._tick.bind(this));
862✔
1512
            }
862✔
1513
        } else {
1,402✔
1514
            this._process_keepAlive();
439✔
1515
        }
439✔
1516
    }
1,402✔
1517

2✔
1518
    private _process_keepAlive() {
2✔
1519
        this.increaseKeepAliveCounter();
4,710✔
1520

4,710✔
1521
        if (this.keepAliveCounterHasExpired) {
4,710✔
1522
            debugLog(`     ->  _process_keepAlive => keepAliveCounterHasExpired`);
613✔
1523
            if (this._sendKeepAliveResponse()) {
613✔
1524
                this.resetLifeTimeAndKeepAliveCounters();
563✔
1525
            } else {
613✔
1526
                debugLog(
50✔
1527
                    "     -> subscription.state === LATE , " +
50✔
1528
                    "because keepAlive Response cannot be send due to lack of PublishRequest"
50✔
1529
                );
50✔
1530
                if (this.messageSent || this.keepAliveCounterHasExpired) {
50✔
1531
                    this.state = SubscriptionState.LATE;
50✔
1532
                }
50✔
1533
            }
50✔
1534
        }
613✔
1535
    }
4,710✔
1536

2✔
1537
    private _stop_timer() {
2✔
1538
        if (this.timerId) {
545✔
1539
            debugLog(chalk.bgWhite.blue("Subscription#_stop_timer subscriptionId="), this.id);
545✔
1540
            clearInterval(this.timerId);
545✔
1541
            this.timerId = null;
545✔
1542
        }
545✔
1543
    }
545✔
1544

2✔
1545
    private _start_timer({ firstTime }: { firstTime: boolean }) {
2✔
1546
        debugLog(
545✔
1547
            chalk.bgWhite.blue("Subscription#_start_timer  subscriptionId="),
545✔
1548
            this.id,
545✔
1549
            " publishingInterval = ",
545✔
1550
            this.publishingInterval
545✔
1551
        );
545✔
1552

545✔
1553
        assert(this.timerId === null);
545✔
1554
        // from the spec:
545✔
1555
        // When a Subscription is created, the first Message is sent at the end of the first publishing cycle to
545✔
1556
        // inform the Client that the Subscription is operational. A NotificationMessage is sent if there are
545✔
1557
        // Notifications ready to be reported. If there are none, a keep-alive Message is sent instead that
545✔
1558
        // contains a sequence number of 1, indicating that the first NotificationMessage has not yet been sent.
545✔
1559
        // This is the only time a keep-alive Message is sent without waiting for the maximum keep-alive count
545✔
1560
        // to be reached, as specified in (f) above.
545✔
1561

545✔
1562
        // make sure that a keep-alive Message will be send at the end of the first publishing cycle
545✔
1563
        // if there are no Notifications ready.
545✔
1564
        this._keep_alive_counter = this.maxKeepAliveCount - 1;
545✔
1565

545✔
1566
        if (firstTime) {
545✔
1567
            assert(this.messageSent === false);
537✔
1568
            assert(this.state === SubscriptionState.CREATING);
537✔
1569
        }
537✔
1570

545✔
1571
        assert(this.publishingInterval >= Subscription.minimumPublishingInterval);
545✔
1572
        this.timerId = setInterval(this._tick.bind(this), this.publishingInterval);
545✔
1573
    }
545✔
1574

2✔
1575
    private _get_future_sequence_number(): number {
2✔
1576
        return this._sequence_number_generator ? this._sequence_number_generator.future() : 0;
1,547!
1577
    }
1,547✔
1578
    public get futureSequenceNumber(): number {
2✔
1579
        return this._get_future_sequence_number();
934✔
1580
    }
934✔
1581
    // counter
2✔
1582
    private _get_next_sequence_number(): number {
2✔
1583
        return this._sequence_number_generator ? this._sequence_number_generator.next() : 0;
883!
1584
    }
883✔
1585
    public get nextSequenceNumber(): number {
2✔
1586
        return this._get_next_sequence_number();
3✔
1587
    }
3✔
1588

2✔
1589
    /**
2✔
1590
     * @private
2✔
1591
     */
2✔
1592
    private _tick() {
2✔
1593
        // c8 ignore next
6,798✔
1594
        if (doDebug) {
6,798!
1595
            debugLog(`Subscription#_tick id ${this.id} aborted=${this.aborted} state=${SubscriptionState[this.state]}`);
×
1596
        }
×
1597
        if (this.state === SubscriptionState.CLOSED) {
6,798✔
1598
            warningLog(`Warning: Subscription#_tick id ${this.id}  called while subscription is CLOSED`);
6✔
1599
            return;
6✔
1600
        }
6✔
1601

6,792✔
1602
        this.discardOldSentNotifications();
6,792✔
1603

6,792✔
1604
        // c8 ignore next
6,792✔
1605
        if (doDebug) {
6,798!
UNCOV
1606
            debugLog(
×
UNCOV
1607
                `${t(new Date())}  ${this._life_time_counter}/${this.lifeTimeCount}${chalk.cyan("   Subscription#_tick")}`,
×
UNCOV
1608
                "  processing subscriptionId=",
×
UNCOV
1609
                this.id,
×
UNCOV
1610
                "hasUncollectedMonitoredItemNotifications = ",
×
UNCOV
1611
                this.hasUncollectedMonitoredItemNotifications,
×
UNCOV
1612
                " publishingIntervalCount =",
×
UNCOV
1613
                this.publishIntervalCount
×
UNCOV
1614
            );
×
UNCOV
1615
        }
×
1616

6,792✔
1617
        // give a chance to the publish engine to cancel timed out publish requests
6,792✔
1618
        this.publishEngine?._on_tick();
6,798✔
1619

6,798✔
1620
        this.publishIntervalCount += 1;
6,798✔
1621

6,798✔
1622
        if (this.state === SubscriptionState.LATE) {
6,798✔
1623
            this.increaseLifeTimeCounter();
1,663✔
1624
        }
1,663✔
1625

6,792✔
1626
        if (this.lifeTimeHasExpired) {
6,798✔
1627
            /* c8 ignore next */
2✔
1628
            doDebug && debugLog(chalk.red.bold(`Subscription ${this.id} has expired !!!!! => Terminating`));
2✔
1629

15✔
1630
            /**
15✔
1631
             * notify the subscription owner that the subscription has expired by exceeding its life time.
15✔
1632
             * @event expired
15✔
1633
             *
15✔
1634
             */
15✔
1635
            this.emit("expired");
15✔
1636

15✔
1637
            // notify new terminated status only when subscription has timeout.
15✔
1638
            doDebug && debugLog("adding StatusChangeNotification notification message for BadTimeout subscription = ", this.id);
15!
1639
            this._addNotificationMessage(new StatusChangeNotification({ status: StatusCodes.BadTimeout }));
15✔
1640

15✔
1641
            // kill timer and delete monitored items and transfer pending notification messages
15✔
1642
            this.terminate();
15✔
1643

15✔
1644
            return;
15✔
1645
        }
15✔
1646

6,787✔
1647
        const publishEngine = this.publishEngine;
6,787✔
1648
        if (!publishEngine) {
6,798!
1649
            throw new Error("publishEngine is null");
×
1650
        }
×
1651

6,787✔
1652
        // c8 ignore next
6,787✔
1653
        doDebug && debugLog("Subscription#_tick  self._pending_notifications= ", this._pending_notifications.size);
6,798!
1654

6,798✔
1655
        if (
6,798✔
1656
            publishEngine.pendingPublishRequestCount === 0 &&
6,798✔
1657
            (this.hasPendingNotifications || this.hasUncollectedMonitoredItemNotifications)
2,509✔
1658
        ) {
6,798✔
1659
            // c8 ignore next
534✔
1660
            doDebug &&
534!
UNCOV
1661
                debugLog(
×
UNCOV
1662
                    "subscription set to LATE  hasPendingNotifications = ",
×
UNCOV
1663
                    this.hasPendingNotifications,
×
UNCOV
1664
                    " hasUncollectedMonitoredItemNotifications =",
×
UNCOV
1665
                    this.hasUncollectedMonitoredItemNotifications
×
1666
                );
299✔
1667

534✔
1668
            this.state = SubscriptionState.LATE;
534✔
1669
            return;
534✔
1670
        }
534✔
1671

6,488✔
1672
        if (publishEngine.pendingPublishRequestCount > 0) {
6,798✔
1673
            if (this.hasPendingNotifications) {
4,268✔
1674
                // simply pop pending notification and send it
35✔
1675
                this.process_subscription();
35✔
1676
            } else if (this.hasUncollectedMonitoredItemNotifications) {
4,268✔
1677
                this.process_subscription();
869✔
1678
            } else {
4,233✔
1679
                this._process_keepAlive();
3,364✔
1680
            }
3,364✔
1681
        } else {
6,798✔
1682
            if (this.state !== SubscriptionState.LATE) {
1,975✔
1683
                this._process_keepAlive();
806✔
1684
            } else {
1,975✔
1685
                this.resetKeepAliveCounter();
1,169✔
1686
            }
1,169✔
1687
        }
1,975✔
1688
    }
6,798✔
1689

2✔
1690
    /**
2✔
1691
     * @private
2✔
1692
     */
2✔
1693
    private _sendKeepAliveResponse(): boolean {
2✔
1694
        const future_sequence_number = this._get_future_sequence_number();
613✔
1695

613✔
1696
        if (this.publishEngine?.send_keep_alive_response(this.id, future_sequence_number)) {
613✔
1697
            this.messageSent = true;
563✔
1698
            // c8 ignore next
563✔
1699
            doDebug &&
563!
UNCOV
1700
                debugLog(
×
UNCOV
1701
                    `    -> Subscription#_sendKeepAliveResponse subscriptionId ${this.id} future_sequence_number ${future_sequence_number}`
×
1702
                );
481✔
1703
            /**
563✔
1704
             * notify the subscription owner that a keepalive message has to be sent.
563✔
1705
             * @event keepalive
563✔
1706
             *
563✔
1707
             */
563✔
1708
            this.emit("keepalive", future_sequence_number);
563✔
1709
            this.state = SubscriptionState.KEEPALIVE;
563✔
1710

563✔
1711
            return true;
563✔
1712
        }
563✔
1713
        return false;
132✔
1714
    }
132✔
1715

2✔
1716
    /**
2✔
1717
     * Reset the Lifetime Counter Variable to the value specified for the lifetime of a Subscription in
2✔
1718
     * the CreateSubscription Service( 5.13.2).
2✔
1719
     * @private
2✔
1720
     */
2✔
1721
    private resetKeepAliveCounter(): void {
2✔
1722
        this._keep_alive_counter = 0;
3,975✔
1723

3,975✔
1724
        // c8 ignore next
3,975✔
1725
        doDebug &&
3,975!
UNCOV
1726
            debugLog(
×
UNCOV
1727
                "     -> subscriptionId",
×
UNCOV
1728
                this.id,
×
UNCOV
1729
                " Resetting keepAliveCounter = ",
×
1730
                this._keep_alive_counter,
×
1731
                this.maxKeepAliveCount
×
1732
            );
2,670✔
1733
    }
3,975✔
1734

2✔
1735
    /**
2✔
1736
     * @private
2✔
1737
     */
2✔
1738
    private increaseKeepAliveCounter() {
2✔
1739
        this._keep_alive_counter += 1;
4,710✔
1740

4,710✔
1741
        // c8 ignore next
4,710✔
1742
        doDebug &&
4,710!
UNCOV
1743
            debugLog(
×
UNCOV
1744
                "     -> subscriptionId",
×
UNCOV
1745
                this.id,
×
UNCOV
1746
                " Increasing keepAliveCounter = ",
×
UNCOV
1747
                this._keep_alive_counter,
×
UNCOV
1748
                this.maxKeepAliveCount
×
1749
            );
2,873✔
1750
    }
4,710✔
1751

2✔
1752
    /**
2✔
1753
     * @private
2✔
1754
     */
2✔
1755
    private _addNotificationMessage(notificationData: QueueItem | StatusChangeNotification, monitoredItemId?: number) {
2✔
1756
        // c8 ignore next
20,234✔
1757
        doDebug && debugLog(chalk.yellow("Subscription#_addNotificationMessage"), notificationData.toString());
20,234!
1758

20,234✔
1759
        this._pending_notifications.push({
20,234✔
1760
            monitoredItemId,
20,234✔
1761
            notification: notificationData,
20,234✔
1762
            publishTime: new Date(),
20,234✔
1763
            start_tick: this.publishIntervalCount
20,234✔
1764
        });
20,234✔
1765
    }
20,234✔
1766

2✔
1767
    /**
2✔
1768
     * @internal
2✔
1769
     * @param monitoredItemId
2✔
1770
     */
2✔
1771
    private _removePendingNotificationsFor(monitoredItemId: number) {
2✔
1772
        const nbRemovedNotification = this._pending_notifications.filterOut((e) => e.monitoredItemId === monitoredItemId);
14,775✔
1773
        doDebug && debugLog(`Removed ${nbRemovedNotification} notifications`);
14,775!
1774
    }
14,775✔
1775
    /**
2✔
1776
     * Extract the next Notification that is ready to be sent to the client.
2✔
1777
     * @return the Notification to send._pending_notifications
2✔
1778
     */
2✔
1779
    private _popNotificationToSend(): NotificationMessage {
2✔
1780
        assert(this._pending_notifications.size > 0);
880✔
1781

880✔
1782
        const notificationMessage = new NotificationMessage({
880✔
1783
            sequenceNumber: 0xffffffff,
880✔
1784
            notificationData: [],
880✔
1785
            publishTime: new Date()
880✔
1786
        }); //
880✔
1787

880✔
1788
        const dataChangeNotifications: DataChangeNotification = new DataChangeNotification({
880✔
1789
            monitoredItems: []
880✔
1790
        });
880✔
1791
        const eventNotificationList: EventNotificationList = new EventNotificationList({
880✔
1792
            events: []
880✔
1793
        });
880✔
1794

880✔
1795
        let statusChangeNotification: StatusChangeNotification | undefined;
880✔
1796

880✔
1797
        let i = 0;
880✔
1798
        let hasEventFieldList = 0;
880✔
1799
        let hasMonitoredItemNotification = 0;
880✔
1800
        const m = this.maxNotificationsPerPublish;
880✔
1801
        while (i < m && this._pending_notifications.size > 0) {
880✔
1802
            if (hasEventFieldList || hasMonitoredItemNotification) {
17,540✔
1803
                const notification1 = this._pending_notifications.first()?.notification;
16,660✔
1804
                if (notification1 instanceof StatusChangeNotification) {
16,660!
UNCOV
1805
                    break;
×
UNCOV
1806
                }
×
1807
            }
16,660✔
1808
            const notification = this._pending_notifications.shift()?.notification;
17,540✔
1809
            if (notification instanceof MonitoredItemNotification) {
17,540✔
1810
                assert(notification.clientHandle !== 4294967295);
17,428✔
1811
                dataChangeNotifications.monitoredItems?.push(notification);
17,428✔
1812
                hasMonitoredItemNotification = 1;
17,428✔
1813
            } else if (notification instanceof EventFieldList) {
17,540✔
1814
                eventNotificationList.events?.push(notification);
94✔
1815
                hasEventFieldList = 1;
94✔
1816
            } else if (notification instanceof StatusChangeNotification) {
112✔
1817
                // to do
18✔
1818
                statusChangeNotification = notification;
18✔
1819
                break;
18✔
1820
            }
18✔
1821
            i += 1;
17,527✔
1822
        }
17,522✔
1823

880✔
1824
        if (dataChangeNotifications.monitoredItems?.length) {
880✔
1825
            notificationMessage.notificationData?.push(dataChangeNotifications);
813✔
1826
        }
813✔
1827
        if (eventNotificationList.events?.length) {
880✔
1828
            notificationMessage.notificationData?.push(eventNotificationList);
50✔
1829
        }
50✔
1830
        if (statusChangeNotification) {
880✔
1831
            notificationMessage.notificationData?.push(statusChangeNotification);
18✔
1832
        }
18✔
1833
        return notificationMessage;
880✔
1834
    }
880✔
1835

2✔
1836
    /**
2✔
1837
     * discardOldSentNotification find all sent notification message that have expired keep-alive
2✔
1838
     * and destroy them.
2✔
1839
     * @private
2✔
1840
     *
2✔
1841
     * Subscriptions maintain a retransmission queue of sent  NotificationMessages.
2✔
1842
     * NotificationMessages are retained in this queue until they are acknowledged or until they have
2✔
1843
     * been in the queue for a minimum of one keep-alive interval.
2✔
1844
     *
2✔
1845
     */
2✔
1846
    private discardOldSentNotifications() {
2✔
1847
        // Sessions maintain a retransmission queue of sent NotificationMessages. NotificationMessages
6,792✔
1848
        // are retained in this queue until they are acknowledged. The Session shall maintain a
6,792✔
1849
        // retransmission queue size of at least two times the number of Publish requests per Session the
6,792✔
1850
        // Server supports.  Clients are required to acknowledge NotificationMessages as they are received. In the
6,792✔
1851
        // case of a retransmission queue overflow, the oldest sent NotificationMessage gets deleted. If a
6,792✔
1852
        // Subscription is transferred to another Session, the queued NotificationMessages for this
6,792✔
1853
        // Subscription are moved from the old to the new Session.
6,792✔
1854
        if (maxNotificationMessagesInQueue <= this._sent_notification_messages.length) {
6,792!
UNCOV
1855
            doDebug && debugLog("discardOldSentNotifications = ", this._sent_notification_messages.length);
×
UNCOV
1856
            this._sent_notification_messages.splice(this._sent_notification_messages.length - maxNotificationMessagesInQueue);
×
UNCOV
1857
        }
×
1858
    }
6,792✔
1859

2✔
1860
    /**
2✔
1861
     * @param timestampsToReturn
2✔
1862
     * @param monitoredItemCreateRequest
2✔
1863
     * @param node
2✔
1864
     * @private
2✔
1865
     */
2✔
1866
    private _createMonitoredItemStep2(
2✔
1867
        timestampsToReturn: TimestampsToReturn,
14,744✔
1868
        monitoredItemCreateRequest: MonitoredItemCreateRequest,
14,744✔
1869
        node: BaseNode
14,744✔
1870
    ): MonitoredItemCreateResult {
14,744✔
1871
        // note : most of the parameter inconsistencies shall have been handled by the caller
14,744✔
1872
        // any error here will raise an assert here
14,744✔
1873

14,744✔
1874
        assert(monitoredItemCreateRequest instanceof MonitoredItemCreateRequest);
14,744✔
1875
        const itemToMonitor = monitoredItemCreateRequest.itemToMonitor;
14,744✔
1876

14,744✔
1877
        // xx check if attribute Id invalid (we only support Value or EventNotifier )
14,744✔
1878
        // xx assert(itemToMonitor.attributeId !== AttributeIds.INVALID);
14,744✔
1879

14,744✔
1880
        this.monitoredItemIdCounter += 1;
14,744✔
1881

14,744✔
1882
        const monitoredItemId = getNextMonitoredItemId();
14,744✔
1883

14,744✔
1884
        const requestedParameters = monitoredItemCreateRequest.requestedParameters;
14,744✔
1885

14,744✔
1886
        // adjust requestedParameters.samplingInterval
14,744✔
1887
        requestedParameters.samplingInterval = this.adjustSamplingInterval(requestedParameters.samplingInterval, node);
14,744✔
1888

14,744✔
1889
        // reincorporate monitoredItemId and itemToMonitor into the requestedParameters
14,744✔
1890
        const options = requestedParameters as unknown as MonitoredItemOptions;
14,744✔
1891

14,744✔
1892
        options.monitoredItemId = monitoredItemId;
14,744✔
1893
        options.itemToMonitor = itemToMonitor;
14,744✔
1894

14,744✔
1895
        const monitoredItem = new MonitoredItem(options);
14,744✔
1896
        monitoredItem.timestampsToReturn = timestampsToReturn;
14,744✔
1897
        monitoredItem.$subscription = this;
14,744✔
1898

14,744✔
1899
        assert(monitoredItem.monitoredItemId === monitoredItemId);
14,744✔
1900

14,744✔
1901
        this.monitoredItems[monitoredItemId] = monitoredItem;
14,744✔
1902
        this.globalCounter.totalMonitoredItemCount += 1;
14,744✔
1903

14,744✔
1904
        assert(monitoredItem.clientHandle !== 4294967295);
14,744✔
1905

14,744✔
1906
        const filterResult = _process_filter(node, requestedParameters.filter);
14,744✔
1907

14,744✔
1908
        const monitoredItemCreateResult = new MonitoredItemCreateResult({
14,744✔
1909
            filterResult,
14,744✔
1910
            monitoredItemId,
14,744✔
1911
            revisedQueueSize: monitoredItem.queueSize,
14,744✔
1912
            revisedSamplingInterval: monitoredItem.samplingInterval,
14,744✔
1913
            statusCode: StatusCodes.Good
14,744✔
1914
        });
14,744✔
1915

14,744✔
1916
        // this.emit("monitoredItem", monitoredItem, itemToMonitor);
14,744✔
1917
        return monitoredItemCreateResult;
14,744✔
1918
    }
14,744✔
1919

2✔
1920
    /**
2✔
1921
     *
2✔
1922
     * @param monitoredItem
2✔
1923
     * @param monitoredItemCreateRequest
2✔
1924
     * @private
2✔
1925
     */
2✔
1926
    public _createMonitoredItemStep3(
2✔
1927
        monitoredItem: MonitoredItem | null,
14,744✔
1928
        monitoredItemCreateRequest: MonitoredItemCreateRequest
14,744✔
1929
    ): void {
14,744✔
1930
        if (!monitoredItem) {
14,744!
UNCOV
1931
            return;
×
UNCOV
1932
        }
×
1933
        assert(monitoredItem.monitoringMode === MonitoringMode.Invalid);
14,744✔
1934
        assert(typeof monitoredItem.samplingFunc === "function", " expecting a sampling function here");
14,744✔
1935
        const monitoringMode = monitoredItemCreateRequest.monitoringMode; // Disabled, Sampling, Reporting
14,744✔
1936
        monitoredItem.setMonitoringMode(monitoringMode);
14,744✔
1937
    }
14,744✔
1938

2✔
1939
    public _harvestMonitoredItems() {
2✔
1940
        for (const monitoredItem of Object.values(this.monitoredItems)) {
873✔
1941
            const notifications_chunks = monitoredItem.extractMonitoredItemNotifications();
21,373✔
1942
            for (const chunk of notifications_chunks) {
21,373✔
1943
                this._addNotificationMessage(chunk, monitoredItem.monitoredItemId);
20,207✔
1944
            }
20,207✔
1945
        }
21,373✔
1946
        this._hasUncollectedMonitoredItemNotifications = false;
873✔
1947
    }
873✔
1948
}
2✔
1949

2✔
1950
assert(Subscription.maximumPublishingInterval < 2147483647, "maximumPublishingInterval cannot exceed (2**31-1) ms ");
2!
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc