node-opcua-server 2.51.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/.mocharc.yml +10 -0
  2. package/LICENSE +20 -0
  3. package/dist/base_server.d.ts +110 -0
  4. package/dist/base_server.js +476 -0
  5. package/dist/base_server.js.map +1 -0
  6. package/dist/factory.d.ts +10 -0
  7. package/dist/factory.js +24 -0
  8. package/dist/factory.js.map +1 -0
  9. package/dist/history_server_capabilities.d.ts +35 -0
  10. package/dist/history_server_capabilities.js +44 -0
  11. package/dist/history_server_capabilities.js.map +1 -0
  12. package/dist/i_channel_data.d.ts +13 -0
  13. package/dist/i_channel_data.js +3 -0
  14. package/dist/i_channel_data.js.map +1 -0
  15. package/dist/i_register_server_manager.d.ts +16 -0
  16. package/dist/i_register_server_manager.js +3 -0
  17. package/dist/i_register_server_manager.js.map +1 -0
  18. package/dist/i_server_side_publish_engine.d.ts +36 -0
  19. package/dist/i_server_side_publish_engine.js +50 -0
  20. package/dist/i_server_side_publish_engine.js.map +1 -0
  21. package/dist/i_socket_data.d.ts +11 -0
  22. package/dist/i_socket_data.js +3 -0
  23. package/dist/i_socket_data.js.map +1 -0
  24. package/dist/index.d.ts +14 -0
  25. package/dist/index.js +27 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/monitored_item.d.ts +173 -0
  28. package/dist/monitored_item.js +1006 -0
  29. package/dist/monitored_item.js.map +1 -0
  30. package/dist/node_sampler.d.ts +3 -0
  31. package/dist/node_sampler.js +76 -0
  32. package/dist/node_sampler.js.map +1 -0
  33. package/dist/opcua_server.d.ts +668 -0
  34. package/dist/opcua_server.js +2407 -0
  35. package/dist/opcua_server.js.map +1 -0
  36. package/dist/queue.d.ts +11 -0
  37. package/dist/queue.js +71 -0
  38. package/dist/queue.js.map +1 -0
  39. package/dist/register_server_manager.d.ts +92 -0
  40. package/dist/register_server_manager.js +574 -0
  41. package/dist/register_server_manager.js.map +1 -0
  42. package/dist/register_server_manager_hidden.d.ts +17 -0
  43. package/dist/register_server_manager_hidden.js +28 -0
  44. package/dist/register_server_manager_hidden.js.map +1 -0
  45. package/dist/register_server_manager_mdns_only.d.ts +19 -0
  46. package/dist/register_server_manager_mdns_only.js +58 -0
  47. package/dist/register_server_manager_mdns_only.js.map +1 -0
  48. package/dist/server_capabilities.d.ts +61 -0
  49. package/dist/server_capabilities.js +109 -0
  50. package/dist/server_capabilities.js.map +1 -0
  51. package/dist/server_end_point.d.ts +180 -0
  52. package/dist/server_end_point.js +825 -0
  53. package/dist/server_end_point.js.map +1 -0
  54. package/dist/server_engine.d.ts +311 -0
  55. package/dist/server_engine.js +1659 -0
  56. package/dist/server_engine.js.map +1 -0
  57. package/dist/server_publish_engine.d.ts +109 -0
  58. package/dist/server_publish_engine.js +531 -0
  59. package/dist/server_publish_engine.js.map +1 -0
  60. package/dist/server_publish_engine_for_orphan_subscriptions.d.ts +16 -0
  61. package/dist/server_publish_engine_for_orphan_subscriptions.js +50 -0
  62. package/dist/server_publish_engine_for_orphan_subscriptions.js.map +1 -0
  63. package/dist/server_session.d.ts +176 -0
  64. package/dist/server_session.js +734 -0
  65. package/dist/server_session.js.map +1 -0
  66. package/dist/server_subscription.d.ts +393 -0
  67. package/dist/server_subscription.js +1313 -0
  68. package/dist/server_subscription.js.map +1 -0
  69. package/dist/sessions_compatible_for_transfer.d.ts +2 -0
  70. package/dist/sessions_compatible_for_transfer.js +36 -0
  71. package/dist/sessions_compatible_for_transfer.js.map +1 -0
  72. package/dist/validate_filter.d.ts +5 -0
  73. package/dist/validate_filter.js +64 -0
  74. package/dist/validate_filter.js.map +1 -0
  75. package/package.json +88 -0
  76. package/source/base_server.ts +617 -0
  77. package/source/factory.ts +25 -0
  78. package/source/history_server_capabilities.ts +75 -0
  79. package/source/i_channel_data.ts +17 -0
  80. package/source/i_register_server_manager.ts +24 -0
  81. package/source/i_server_side_publish_engine.ts +77 -0
  82. package/source/i_socket_data.ts +11 -0
  83. package/source/index.ts +14 -0
  84. package/source/monitored_item.ts +1303 -0
  85. package/source/node_sampler.ts +82 -0
  86. package/source/opcua_server.ts +3742 -0
  87. package/source/queue.ts +73 -0
  88. package/source/register_server_manager.ts +744 -0
  89. package/source/register_server_manager_hidden.ts +33 -0
  90. package/source/register_server_manager_mdns_only.ts +69 -0
  91. package/source/server_capabilities.ts +177 -0
  92. package/source/server_end_point.ts +1182 -0
  93. package/source/server_engine.ts +2167 -0
  94. package/source/server_publish_engine.ts +657 -0
  95. package/source/server_publish_engine_for_orphan_subscriptions.ts +52 -0
  96. package/source/server_session.ts +931 -0
  97. package/source/server_subscription.ts +1792 -0
  98. package/source/sessions_compatible_for_transfer.ts +33 -0
  99. package/source/validate_filter.ts +86 -0
  100. package/test_helpers/create_certificates.js +1 -0
@@ -0,0 +1,657 @@
1
+ /**
2
+ * @module node-opcua-server
3
+ */
4
+ // tslint:disable:no-console
5
+ import * as chalk from "chalk";
6
+ import { EventEmitter } from "events";
7
+ import { partition, sortBy } from "lodash";
8
+
9
+ import { assert } from "node-opcua-assert";
10
+ import { checkDebugFlag, make_debugLog } from "node-opcua-debug";
11
+ import { ObjectRegistry } from "node-opcua-object-registry";
12
+ import { StatusCode, StatusCodes } from "node-opcua-status-code";
13
+
14
+ import { PublishRequest, PublishResponse, SubscriptionAcknowledgement } from "node-opcua-types";
15
+ import { Subscription } from "./server_subscription";
16
+ import { SubscriptionState } from "./server_subscription";
17
+ import { IServerSidePublishEngine, INotifMsg, IClosedOrTransferredSubscription } from "./i_server_side_publish_engine";
18
+
19
+ const debugLog = make_debugLog(__filename);
20
+ const doDebug = checkDebugFlag(__filename);
21
+
22
+ function traceLog(...args: [any?, ...any[]]) {
23
+ if (!doDebug) {
24
+ return;
25
+ }
26
+ const a: string[] = args.map((x?: any) => x!);
27
+ a.unshift(chalk.yellow(" TRACE "));
28
+ debugLog.apply(null, a as [any?, ...any[]]);
29
+ }
30
+
31
+ export interface ServerSidePublishEngineOptions {
32
+ maxPublishRequestInQueue?: number;
33
+ }
34
+
35
+ interface PublishData {
36
+ request: PublishRequest;
37
+ serverTimeWhenReceived: number;
38
+ results: StatusCode[];
39
+ callback: (request: PublishRequest, response: PublishResponse) => void;
40
+ }
41
+
42
+ function _assertValidPublishData(publishData: PublishData) {
43
+ assert(publishData.request instanceof PublishRequest);
44
+ assert(typeof publishData.serverTimeWhenReceived === "number");
45
+ assert(Array.isArray(publishData.results));
46
+ assert(typeof publishData.callback === "function");
47
+ }
48
+
49
+ function dummy_function() {
50
+ /* empty */
51
+ }
52
+
53
+ function addDate(date: Date, delta: number) {
54
+ return new Date(date.getTime() + delta);
55
+ }
56
+
57
+ function timeout_filter(publishData: PublishData): boolean {
58
+ const request = publishData.request;
59
+ const results = publishData.results;
60
+ if (!request.requestHeader.timeoutHint) {
61
+ // no limits
62
+ return false;
63
+ }
64
+ const serverTimeWhenReceived = publishData.serverTimeWhenReceived;
65
+ // remark : do not use request.requestHeader.timestamp! here as this is a client date and server and client clocks might differ
66
+ const expected_timeout_time = addDate(new Date(serverTimeWhenReceived), request.requestHeader.timeoutHint);
67
+ return expected_timeout_time.getTime() < Date.now();
68
+ }
69
+
70
+ /***
71
+ * a Publish Engine for a given session
72
+ */
73
+ export class ServerSidePublishEngine extends EventEmitter implements IServerSidePublishEngine {
74
+ public static registry = new ObjectRegistry();
75
+
76
+ /**
77
+ * @private
78
+ */
79
+ public static transferSubscriptionsToOrphan(
80
+ srcPublishEngine: ServerSidePublishEngine,
81
+ destPublishEngine: ServerSidePublishEngine
82
+ ) {
83
+ debugLog(
84
+ chalk.yellow(
85
+ "ServerSidePublishEngine#transferSubscriptionsToOrphan! " + "start transferring long live subscriptions to orphan"
86
+ )
87
+ );
88
+
89
+ for (const subscription of Object.values(srcPublishEngine._subscriptions)) {
90
+ assert((subscription.publishEngine as any) === srcPublishEngine);
91
+
92
+ if (subscription.$session) {
93
+ subscription.$session._unexposeSubscriptionDiagnostics(subscription);
94
+ } else {
95
+ console.warn("Warning: subscription", subscription.id, " has no session attached!!!");
96
+ }
97
+
98
+ ServerSidePublishEngine.transferSubscription(subscription, destPublishEngine, false);
99
+ }
100
+ assert(srcPublishEngine.subscriptionCount === 0);
101
+
102
+ debugLog(
103
+ chalk.yellow(
104
+ "ServerSidePublishEngine#transferSubscriptionsToOrphan! " + "end transferring long lived subscriptions to orphan"
105
+ )
106
+ );
107
+ }
108
+
109
+ /**
110
+ * @param subscription
111
+ * @param destPublishEngine
112
+ * @param sendInitialValues true if initial values should be sent
113
+ * @private
114
+ */
115
+ public static async transferSubscription(
116
+ subscription: Subscription,
117
+ destPublishEngine: ServerSidePublishEngine,
118
+ sendInitialValues: boolean
119
+ ): Promise<void> {
120
+ const srcPublishEngine = (subscription.publishEngine as any) as ServerSidePublishEngine;
121
+
122
+ assert(!destPublishEngine.getSubscriptionById(subscription.id));
123
+ assert(srcPublishEngine.getSubscriptionById(subscription.id));
124
+
125
+ // remove pending StatusChangeNotification on the same session that may exist already
126
+ destPublishEngine._purge_dangling_subscription(subscription.id);
127
+
128
+ debugLog(chalk.cyan("ServerSidePublishEngine.transferSubscription live subscriptionId ="), subscription.subscriptionId);
129
+
130
+ // xx const internalNotification = subscription._flushSentNotifications();
131
+ debugLog(chalk.cyan("ServerSidePublishEngine.transferSubscription with = "), subscription.getAvailableSequenceNumbers());
132
+
133
+ // If the Server transfers the Subscription to the new Session, the Server shall issue a
134
+ // StatusChangeNotification notificationMessage with the status code Good_SubscriptionTransferred
135
+ // to the old Session.
136
+ subscription.notifyTransfer();
137
+
138
+ destPublishEngine.add_subscription(srcPublishEngine.detach_subscription(subscription));
139
+ subscription.resetLifeTimeCounter();
140
+ if (sendInitialValues) {
141
+ /* A Boolean parameter with the following values:
142
+ TRUE the first Publish response(s) after the TransferSubscriptions call
143
+ shall contain the current values of all Monitored Items in the
144
+ Subscription where the Monitoring Mode is set to Reporting.
145
+ If a value is queued for a data MonitoredItem, the next value in
146
+ the queue is sent in the Publish response. If no value is queued
147
+ for a data MonitoredItem, the last value sent is repeated in the
148
+ Publish response.
149
+ FALSE the first Publish response after the TransferSubscriptions call
150
+ shall contain only the value changes since the last Publish
151
+ response was sent.
152
+ This parameter only applies to MonitoredItems used for monitoring Attribute
153
+ changes
154
+ */
155
+ debugLog("Resending initial values");
156
+ await subscription.resendInitialValues();
157
+ }
158
+
159
+ assert(destPublishEngine.getSubscriptionById(subscription.id));
160
+ assert(!srcPublishEngine.getSubscriptionById(subscription.id));
161
+ }
162
+
163
+ public maxPublishRequestInQueue: number = 0;
164
+ public isSessionClosed: boolean = false;
165
+
166
+ private _publish_request_queue: PublishData[] = [];
167
+ private _subscriptions: { [key: string]: Subscription };
168
+ private _closed_subscriptions: IClosedOrTransferredSubscription[] = [];
169
+
170
+ constructor(options?: ServerSidePublishEngineOptions) {
171
+ super();
172
+
173
+ options = options || {};
174
+
175
+ ServerSidePublishEngine.registry.register(this);
176
+
177
+ // a queue of pending publish request send by the client
178
+ // waiting to be used by the server to send notification
179
+ this._publish_request_queue = []; // { request :/*PublishRequest*/{},
180
+
181
+ this._subscriptions = {};
182
+
183
+ // _closed_subscriptions contains a collection of Subscription that
184
+ // have expired but that still need to send some pending notification
185
+ // to the client.
186
+ // Once publish requests will be received from the client
187
+ // the notifications of those subscriptions will be processed so that
188
+ // they can be properly disposed.
189
+ this._closed_subscriptions = [];
190
+
191
+ this.maxPublishRequestInQueue = options.maxPublishRequestInQueue || 100;
192
+
193
+ this.isSessionClosed = false;
194
+ }
195
+
196
+ public toString() {
197
+ let str = "";
198
+ str += `maxPublishRequestInQueue ${this.maxPublishRequestInQueue}\n`;
199
+ str += `subscriptions ${Object.keys(this._subscriptions).join()}\n`;
200
+ str += `closed subscriptions ${this._closed_subscriptions.map((s)=> s.id).join()}\n`;
201
+ return str;
202
+ }
203
+ public dispose() {
204
+ debugLog("ServerSidePublishEngine#dispose");
205
+
206
+ assert(Object.keys(this._subscriptions).length === 0, "self._subscriptions count!=0");
207
+ this._subscriptions = {};
208
+
209
+ assert(this._closed_subscriptions.length === 0, "self._closed_subscriptions count!=0");
210
+ this._closed_subscriptions = [];
211
+
212
+ ServerSidePublishEngine.registry.unregister(this);
213
+ }
214
+
215
+ public process_subscriptionAcknowledgements(subscriptionAcknowledgements: SubscriptionAcknowledgement[]): StatusCode[] {
216
+ // process acknowledgements
217
+ subscriptionAcknowledgements = subscriptionAcknowledgements || [];
218
+ debugLog("process_subscriptionAcknowledgements = ", subscriptionAcknowledgements);
219
+ const results = subscriptionAcknowledgements.map((subscriptionAcknowledgement: SubscriptionAcknowledgement) => {
220
+ const subscription = this.getSubscriptionById(subscriptionAcknowledgement.subscriptionId);
221
+ if (!subscription) {
222
+ // // try to find the session
223
+ // const transferredSubscription = this._transferred_subscriptions.find(
224
+ // (s) => s.subscriptionId === subscriptionAcknowledgement.subscriptionId
225
+ // );
226
+ // if (transferredSubscription) {
227
+ // debugLog("Subscription acknowledgeNotification done in transferred subscription ");
228
+ // return transferredSubscription.acknowledgeNotification(subscriptionAcknowledgement.sequenceNumber);
229
+ // }
230
+ return StatusCodes.BadSubscriptionIdInvalid;
231
+ }
232
+ return subscription.acknowledgeNotification(subscriptionAcknowledgement.sequenceNumber);
233
+ });
234
+
235
+ return results;
236
+ }
237
+
238
+ /**
239
+ * get a array of subscription handled by the publish engine.
240
+ */
241
+ public get subscriptions(): Subscription[] {
242
+ return Object.values(this._subscriptions);
243
+ }
244
+
245
+ /**
246
+ */
247
+ public add_subscription(subscription: Subscription): Subscription {
248
+ assert(subscription instanceof Subscription);
249
+ assert(isFinite(subscription.id));
250
+ subscription.publishEngine = (subscription.publishEngine || this) as any;
251
+ assert((subscription.publishEngine as any) === this);
252
+ assert(!this._subscriptions[subscription.id]);
253
+
254
+ debugLog("ServerSidePublishEngine#add_subscription - adding subscription with Id:", subscription.id);
255
+ this._subscriptions[subscription.id] = subscription;
256
+ // xx subscription._flushSentNotifications();
257
+ return subscription;
258
+ }
259
+
260
+ public detach_subscription(subscription: Subscription): Subscription {
261
+ assert(subscription instanceof Subscription);
262
+ assert(isFinite(subscription.id));
263
+ assert((subscription.publishEngine as any) === this);
264
+ assert(this._subscriptions[subscription.id] === subscription);
265
+
266
+ delete this._subscriptions[subscription.id];
267
+ subscription.publishEngine = null as any;
268
+ debugLog("ServerSidePublishEngine#detach_subscription detaching subscription with Id:", subscription.id);
269
+ return subscription;
270
+ }
271
+
272
+ /**
273
+ */
274
+ public shutdown() {
275
+ if (this.subscriptionCount !== 0) {
276
+ debugLog(chalk.red("Shutting down pending subscription"));
277
+ this.subscriptions.map((subscription: Subscription) => subscription.terminate());
278
+ }
279
+
280
+ assert(this.subscriptionCount === 0, "subscription shall be removed first before you can shutdown a publish engine");
281
+
282
+ debugLog("ServerSidePublishEngine#shutdown");
283
+
284
+ // purge _publish_request_queue
285
+ this._publish_request_queue = [];
286
+
287
+ // purge self._closed_subscriptions
288
+ this._closed_subscriptions.map((subscription) => subscription.dispose());
289
+ this._closed_subscriptions = [];
290
+ }
291
+
292
+ /**
293
+ * number of pending PublishRequest available in queue
294
+ */
295
+ public get pendingPublishRequestCount(): number {
296
+ return this._publish_request_queue.length;
297
+ }
298
+
299
+ /**
300
+ * number of subscriptions
301
+ */
302
+ public get subscriptionCount(): number {
303
+ return Object.keys(this._subscriptions).length;
304
+ }
305
+
306
+ public get pendingClosedSubscriptionCount(): number {
307
+ return this._closed_subscriptions.length;
308
+ }
309
+
310
+ public get currentMonitoredItemCount(): number {
311
+ const subscriptions = Object.values(this._subscriptions);
312
+ const result = subscriptions.reduce((cumul: number, subscription: Subscription) => {
313
+ return cumul + subscription.monitoredItemCount;
314
+ }, 0);
315
+ assert(isFinite(result));
316
+ return result;
317
+ }
318
+
319
+ public _purge_dangling_subscription(subscriptionId: number) {
320
+ this._closed_subscriptions = this._closed_subscriptions.filter((s) => s.id !== subscriptionId);
321
+ }
322
+
323
+ public on_close_subscription(subscription: IClosedOrTransferredSubscription): void {
324
+ debugLog("ServerSidePublishEngine#on_close_subscription", subscription.id);
325
+ if (subscription.hasPendingNotifications) {
326
+ debugLog(
327
+ "ServerSidePublishEngine#on_close_subscription storing subscription",
328
+ subscription.id,
329
+ " to _closed_subscriptions because it has pending notification"
330
+ );
331
+ this._closed_subscriptions.push(subscription);
332
+ } else {
333
+ debugLog("ServerSidePublishEngine#on_close_subscription disposing subscription", subscription.id);
334
+ // subscription is no longer needed
335
+ subscription.dispose();
336
+ }
337
+
338
+ delete this._subscriptions[subscription.id];
339
+
340
+ while (this._feed_closed_subscription()) {
341
+ /* keep looping */
342
+ }
343
+ if (this.subscriptionCount === 0 && this._closed_subscriptions.length === 0) {
344
+ this.cancelPendingPublishRequest();
345
+ }
346
+ }
347
+
348
+ /**
349
+ * retrieve a subscription by id.
350
+ * @param subscriptionId
351
+ * @return Subscription
352
+ */
353
+ public getSubscriptionById(subscriptionId: number | string): Subscription {
354
+ return this._subscriptions[subscriptionId.toString()];
355
+ }
356
+
357
+ public findLateSubscriptions(): Subscription[] {
358
+ const subscriptions = Object.values(this._subscriptions);
359
+ return subscriptions.filter((subscription: Subscription) => {
360
+ return subscription.state === SubscriptionState.LATE && subscription.publishingEnabled;
361
+ });
362
+ }
363
+
364
+ public get hasLateSubscriptions(): boolean {
365
+ return this.findLateSubscriptions().length > 0;
366
+ }
367
+
368
+ public findLateSubscriptionsSortedByAge() {
369
+ let late_subscriptions = this.findLateSubscriptions();
370
+ late_subscriptions = sortBy(late_subscriptions, "timeToExpiration");
371
+
372
+ return late_subscriptions;
373
+ }
374
+
375
+ public cancelPendingPublishRequestBeforeChannelChange() {
376
+ this._cancelPendingPublishRequest(StatusCodes.BadSecureChannelClosed);
377
+ }
378
+
379
+ public onSessionClose() {
380
+ this.isSessionClosed = true;
381
+ this._cancelPendingPublishRequest(StatusCodes.BadSessionClosed);
382
+ }
383
+
384
+ /**
385
+ * @private
386
+ */
387
+ public cancelPendingPublishRequest() {
388
+ assert(this.subscriptionCount === 0);
389
+ this._cancelPendingPublishRequest(StatusCodes.BadNoSubscription);
390
+ }
391
+
392
+ /**
393
+ *
394
+ * @param request
395
+ * @param callback
396
+ * @private
397
+ * @internal
398
+ */
399
+ public _on_PublishRequest(request: PublishRequest, callback?: any) {
400
+ callback = callback || dummy_function;
401
+ assert(typeof callback === "function");
402
+
403
+ // istanbul ignore next
404
+ if (!(request instanceof PublishRequest)) {
405
+ throw new Error("Internal error : expecting a Publish Request here");
406
+ }
407
+
408
+ const subscriptionAckResults = this.process_subscriptionAcknowledgements(request.subscriptionAcknowledgements || []);
409
+
410
+ const currentTime = Date.now();
411
+ const publishData: PublishData = {
412
+ callback,
413
+ request,
414
+ results: subscriptionAckResults,
415
+ serverTimeWhenReceived: currentTime
416
+ };
417
+
418
+ if (this.isSessionClosed) {
419
+ traceLog("server has received a PublishRequest but session is Closed");
420
+ this._send_error_for_request(publishData, StatusCodes.BadSessionClosed);
421
+ } else if (this.subscriptionCount === 0) {
422
+ if (this._closed_subscriptions.length > 0 && this._closed_subscriptions[0].hasPendingNotifications) {
423
+ const verif = this._publish_request_queue.length;
424
+ // add the publish request to the queue for later processing
425
+ this._publish_request_queue.push(publishData);
426
+
427
+ const processed = this._feed_closed_subscription();
428
+ //xx ( may be subscription has expired by themselve) assert(verif === this._publish_request_queue.length);
429
+ //xx ( may be subscription has expired by themselve) assert(processed);
430
+ return;
431
+ }
432
+ traceLog("server has received a PublishRequest but has no subscription opened");
433
+ this._send_error_for_request(publishData, StatusCodes.BadNoSubscription);
434
+ } else {
435
+ // add the publish request to the queue for later processing
436
+ this._publish_request_queue.push(publishData);
437
+ assert(this.pendingPublishRequestCount > 0);
438
+
439
+ debugLog(chalk.bgWhite.red("Adding a PublishRequest to the queue "), this._publish_request_queue.length);
440
+
441
+ this._feed_closed_subscription();
442
+
443
+ this._feed_late_subscription();
444
+
445
+ this._handle_too_many_requests();
446
+ }
447
+ }
448
+
449
+ private _find_starving_subscription(): Subscription | null {
450
+ const late_subscriptions = this.findLateSubscriptions();
451
+ function compare_subscriptions(s1: Subscription, s2: Subscription): number {
452
+ if (s1.priority === s2.priority) {
453
+ return s1.timeToExpiration < s2.timeToExpiration ? 1 : 0;
454
+ }
455
+ return s1.priority > s2.priority ? 1 : 0;
456
+ }
457
+ function findLateSubscriptionSortedByPriority() {
458
+ if (late_subscriptions.length === 0) {
459
+ return null;
460
+ }
461
+ late_subscriptions.sort(compare_subscriptions);
462
+
463
+ // istanbul ignore next
464
+ if (doDebug) {
465
+ debugLog(
466
+ late_subscriptions
467
+ .map(
468
+ (s: Subscription) =>
469
+ "[ id = " +
470
+ s.id +
471
+ " prio=" +
472
+ s.priority +
473
+ " t=" +
474
+ s.timeToExpiration +
475
+ " ka=" +
476
+ s.timeToKeepAlive +
477
+ " m?=" +
478
+ s.hasUncollectedMonitoredItemNotifications +
479
+ "]"
480
+ )
481
+ .join(" \n")
482
+ );
483
+ }
484
+ return late_subscriptions[late_subscriptions.length - 1];
485
+ }
486
+
487
+ if (this._closed_subscriptions) {
488
+ /** */
489
+ }
490
+ const starving_subscription = /* this.findSubscriptionWaitingForFirstPublish() || */ findLateSubscriptionSortedByPriority();
491
+ return starving_subscription;
492
+ }
493
+ private _feed_late_subscription() {
494
+ setImmediate(() => {
495
+ if (!this.pendingPublishRequestCount) {
496
+ return;
497
+ }
498
+ const starving_subscription = this._find_starving_subscription();
499
+ if (starving_subscription) {
500
+ debugLog(chalk.bgWhite.red("feeding most late subscription subscriptionId = "), starving_subscription.id);
501
+ starving_subscription.process_subscription();
502
+ }
503
+ });
504
+ }
505
+
506
+ private _feed_closed_subscription() {
507
+ if (!this.pendingPublishRequestCount) {
508
+ return false;
509
+ }
510
+
511
+ if (this._closed_subscriptions.length === 0) {
512
+ debugLog("ServerSidePublishEngine#_feed_closed_subscription -> nothing to do");
513
+ return false;
514
+ }
515
+ // process closed subscription
516
+ const closed_subscription = this._closed_subscriptions[0]!;
517
+ assert(closed_subscription.hasPendingNotifications);
518
+ debugLog("ServerSidePublishEngine#_feed_closed_subscription for closed_subscription ", closed_subscription.id);
519
+ closed_subscription?._publish_pending_notifications();
520
+ if (!closed_subscription?.hasPendingNotifications) {
521
+ closed_subscription.dispose();
522
+ this._closed_subscriptions.shift();
523
+ }
524
+ return true;
525
+ }
526
+
527
+ private _send_error_for_request(publishData: PublishData, statusCode: StatusCode): void {
528
+ _assertValidPublishData(publishData);
529
+ const publishResponse = new PublishResponse({
530
+ responseHeader: { serviceResult: statusCode }
531
+ });
532
+ this._send_response_for_request(publishData, publishResponse);
533
+ }
534
+
535
+ private _cancelPendingPublishRequest(statusCode: StatusCode): void {
536
+ if (this._publish_request_queue) {
537
+ debugLog(
538
+ chalk.red("Cancelling pending PublishRequest with statusCode "),
539
+ statusCode.toString(),
540
+ " length =",
541
+ this._publish_request_queue.length
542
+ );
543
+ } else {
544
+ debugLog(chalk.red("No pending PublishRequest to cancel"));
545
+ }
546
+
547
+ for (const publishData of this._publish_request_queue) {
548
+ this._send_error_for_request(publishData, statusCode);
549
+ }
550
+ this._publish_request_queue = [];
551
+ }
552
+
553
+ private _handle_too_many_requests() {
554
+ if (this.pendingPublishRequestCount > this.maxPublishRequestInQueue) {
555
+ traceLog(
556
+ "server has received too many PublishRequest",
557
+ this.pendingPublishRequestCount,
558
+ "/",
559
+ this.maxPublishRequestInQueue
560
+ );
561
+ assert(this.pendingPublishRequestCount === this.maxPublishRequestInQueue + 1);
562
+ // When a Server receives a new Publish request that exceeds its limit it shall de-queue the oldest Publish
563
+ // request and return a response with the result set to Bad_TooManyPublishRequests.
564
+
565
+ // dequeue oldest request
566
+ const publishData = this._publish_request_queue.shift()!;
567
+ this._send_error_for_request(publishData, StatusCodes.BadTooManyPublishRequests);
568
+ }
569
+ }
570
+
571
+ /**
572
+ * call by a subscription when no notification message is available after the keep alive delay has
573
+ * expired.
574
+ *
575
+ * @method send_keep_alive_response
576
+ * @param subscriptionId
577
+ * @param future_sequence_number
578
+ * @return true if a publish response has been sent
579
+ */
580
+ public send_keep_alive_response(subscriptionId: number, future_sequence_number: number): boolean {
581
+ // this keep-alive Message informs the Client that the Subscription is still active.
582
+ // Each keep-alive Message is a response to a Publish request in which the notification Message
583
+ // parameter does not contain any Notifications and that contains the sequence number of the next
584
+ // Notification Message that is to be sent.
585
+
586
+ const subscription = this.getSubscriptionById(subscriptionId);
587
+ /* istanbul ignore next */
588
+ if (!subscription) {
589
+ traceLog("send_keep_alive_response => invalid subscriptionId = ", subscriptionId);
590
+ return false;
591
+ }
592
+ // let check if we have available PublishRequest to send the keep alive
593
+ if (this.pendingPublishRequestCount === 0 || subscription.hasPendingNotifications) {
594
+ // we cannot send the keep alive PublishResponse
595
+ traceLog(
596
+ "send_keep_alive_response => cannot send keep-aliave (no PublishRequest left) subscriptionId = ",
597
+ subscriptionId
598
+ );
599
+ return false;
600
+ }
601
+ debugLog(
602
+ `Sending keep alive response for subscription id ${subscription.id} ${subscription.publishingInterval} ${subscription.maxKeepAliveCount}`
603
+ );
604
+ this._send_response(
605
+ subscription,
606
+ new PublishResponse({
607
+ availableSequenceNumbers: subscription.getAvailableSequenceNumbers(),
608
+ moreNotifications: false,
609
+ notificationMessage: {
610
+ sequenceNumber: future_sequence_number
611
+ },
612
+ subscriptionId
613
+ })
614
+ );
615
+ return true;
616
+ }
617
+ public _send_response(subscription: Subscription, response: PublishResponse) {
618
+ assert(this.pendingPublishRequestCount > 0);
619
+ assert(response.subscriptionId !== 0xffffff);
620
+ const publishData = this._publish_request_queue.shift()!;
621
+ this._send_response_for_request(publishData, response);
622
+ }
623
+
624
+ public _on_tick(): void {
625
+ this._cancelTimeoutRequests();
626
+ }
627
+
628
+ private _cancelTimeoutRequests(): void {
629
+ if (this._publish_request_queue.length === 0) {
630
+ return;
631
+ }
632
+
633
+ // filter out timeout requests
634
+ const parts = partition(this._publish_request_queue, timeout_filter);
635
+
636
+ this._publish_request_queue = parts[1]; // still valid
637
+
638
+ const invalid_published_request = parts[0];
639
+ for (const publishData of invalid_published_request) {
640
+ if (doDebug) {
641
+ debugLog(chalk.cyan(" CANCELING TIMEOUT PUBLISH REQUEST "));
642
+ }
643
+ this._send_error_for_request(publishData, StatusCodes.BadTimeout);
644
+ }
645
+ }
646
+
647
+ public _send_response_for_request(publishData: PublishData, response: PublishResponse) {
648
+ if (doDebug) {
649
+ debugLog("_send_response_for_request ", response.toString());
650
+ }
651
+ _assertValidPublishData(publishData);
652
+ // xx assert(response.responseHeader.requestHandle !== 0,"expecting a valid requestHandle");
653
+ response.results = publishData.results;
654
+ response.responseHeader.requestHandle = publishData.request.requestHeader.requestHandle;
655
+ publishData.callback(publishData.request, response);
656
+ }
657
+ }