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

node-opcua / node-opcua / 22636309940

03 Mar 2026 06:03PM UTC coverage: 92.756% (+1.9%) from 90.81%
22636309940

push

github

erossignon
test: disable ENOENT on overlapping trustCertificate invocations in test environments with concurrency tests

18684 of 22141 branches covered (84.39%)

23 of 37 new or added lines in 1 file covered. (62.16%)

5775 existing lines in 228 files now uncovered.

160668 of 173216 relevant lines covered (92.76%)

875869.8 hits per line

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

91.2
/packages/node-opcua-server/source/server_engine.ts
1
/**
2✔
2
 * @module node-opcua-server
4✔
3
 */
4✔
4
import { EventEmitter } from "events";
4✔
5
import { types } from "util";
4✔
6
import async from "async";
4✔
7
import chalk from "chalk";
4✔
8
import { assert } from "node-opcua-assert";
4✔
9
import { BinaryStream } from "node-opcua-binary-stream";
4✔
10
import {
4✔
11
    addElement,
4✔
12
    AddressSpace,
4✔
13
    bindExtObjArrayNode,
4✔
14
    ensureObjectIsSecure,
4✔
15
    MethodFunctor,
4✔
16
    removeElement,
4✔
17
    SessionContext,
4✔
18
    UADynamicVariableArray,
4✔
19
    UAMethod,
4✔
20
    UAObject,
4✔
21
    UAServerDiagnosticsSummary,
4✔
22
    UAServerStatus,
4✔
23
    UAVariable,
4✔
24
    UAServerDiagnostics,
4✔
25
    BindVariableOptions,
4✔
26
    ISessionContext,
4✔
27
    DTServerStatus,
4✔
28
    IServerBase
4✔
29
} from "node-opcua-address-space";
4✔
30
import { generateAddressSpace } from "node-opcua-address-space/nodeJS";
4✔
31
import { DataValue } from "node-opcua-data-value";
4✔
32
import {
4✔
33
    ServerDiagnosticsSummaryDataType,
4✔
34
    ServerState,
4✔
35
    ServerStatusDataType,
4✔
36
    SubscriptionDiagnosticsDataType
4✔
37
} from "node-opcua-common";
4✔
38
import { AttributeIds, coerceLocalizedText, LocalizedTextLike, makeAccessLevelFlag, NodeClass } from "node-opcua-data-model";
4✔
39
import { coerceNodeId, makeNodeId, NodeId, NodeIdLike, NodeIdType, resolveNodeId } from "node-opcua-nodeid";
4✔
40
import { BrowseResult } from "node-opcua-service-browse";
4✔
41
import { UInt32 } from "node-opcua-basic-types";
4✔
42
import { CreateSubscriptionRequestLike } from "node-opcua-client";
4✔
43
import { DataTypeIds, MethodIds, ObjectIds, VariableIds } from "node-opcua-constants";
4✔
44
import { getCurrentClock, getMinOPCUADate } from "node-opcua-date-time";
4✔
45
import { checkDebugFlag, make_debugLog, make_errorLog, make_warningLog, traceFromThisProjectOnly } from "node-opcua-debug";
4✔
46
import { nodesets } from "node-opcua-nodesets";
4✔
47
import { ObjectRegistry } from "node-opcua-object-registry";
4✔
48
import { CallMethodResult } from "node-opcua-service-call";
4✔
49
import { TransferResult } from "node-opcua-service-subscription";
4✔
50
import { ApplicationDescription } from "node-opcua-service-endpoints";
4✔
51
import { HistoryReadRequest, HistoryReadResult, HistoryReadValueId } from "node-opcua-service-history";
4✔
52
import { StatusCode, StatusCodes, CallbackT } from "node-opcua-status-code";
4✔
53
import {
4✔
54
    BrowseDescription,
4✔
55
    BrowsePath,
4✔
56
    BrowsePathResult,
4✔
57
    BuildInfo,
4✔
58
    BuildInfoOptions,
4✔
59
    SessionDiagnosticsDataType,
4✔
60
    SessionSecurityDiagnosticsDataType,
4✔
61
    WriteValue,
4✔
62
    ReadValueId,
4✔
63
    TimeZoneDataType,
4✔
64
    ProgramDiagnosticDataType,
4✔
65
    CallMethodResultOptions,
4✔
66
    ReadRequestOptions,
4✔
67
    BrowseDescriptionOptions,
4✔
68
    CallMethodRequest,
4✔
69
    ApplicationType
4✔
70
} from "node-opcua-types";
4✔
71
import { DataType, isValidVariant, Variant, VariantArrayType } from "node-opcua-variant";
4✔
72

4✔
73
import { HistoryServerCapabilities, HistoryServerCapabilitiesOptions } from "./history_server_capabilities";
4✔
74
import { MonitoredItem } from "./monitored_item";
4✔
75
import { ServerCapabilities, ServerCapabilitiesOptions, ServerOperationLimits, defaultServerCapabilities } from "./server_capabilities";
4✔
76
import { ServerSidePublishEngine } from "./server_publish_engine";
4✔
77
import { ServerSidePublishEngineForOrphanSubscription } from "./server_publish_engine_for_orphan_subscriptions";
4✔
78
import { ServerSession } from "./server_session";
4✔
79
import { Subscription } from "./server_subscription";
4✔
80
import { sessionsCompatibleForTransfer } from "./sessions_compatible_for_transfer";
4✔
81
import { OPCUAServerOptions } from "./opcua_server";
4✔
82
import { IAddressSpaceAccessor } from "./i_address_space_accessor";
4✔
83
import { AddressSpaceAccessor } from "./addressSpace_accessor";
4✔
84

4✔
85
const debugLog = make_debugLog(__filename);
4✔
86
const errorLog = make_errorLog(__filename);
4✔
87
const warningLog = make_warningLog(__filename);
4✔
88
const doDebug = checkDebugFlag(__filename);
4✔
89

4✔
90
function upperCaseFirst(str: string) {
7,344✔
91
    return str.slice(0, 1).toUpperCase() + str.slice(1);
7,344✔
92
}
7,344✔
93

4✔
94
async function shutdownAndDisposeAddressSpace(this: ServerEngine) {
618✔
95
    if (this.addressSpace) {
618✔
96
        await this.addressSpace.shutdown();
612✔
97
        this.addressSpace.dispose();
612✔
98
        delete (this as any).addressSpace;
612✔
99
    }
612✔
100
}
618✔
101

4✔
UNCOV
102
function setSubscriptionDurable(
×
UNCOV
103
    this: ServerEngine,
×
UNCOV
104
    inputArguments: Variant[],
×
UNCOV
105
    context: ISessionContext,
×
UNCOV
106
    callback: CallbackT<CallMethodResultOptions>
×
UNCOV
107
) {
×
UNCOV
108
    // see https://reference.opcfoundation.org/v104/Core/docs/Part5/9.3/
×
UNCOV
109
    // https://reference.opcfoundation.org/v104/Core/docs/Part4/6.8/
×
110
    assert(typeof callback === "function");
×
UNCOV
111

×
112
    const data = _getSubscription.call(this, inputArguments, context);
×
113
    if (data.statusCode) return callback(null, { statusCode: data.statusCode });
×
114
    const { subscription } = data;
×
UNCOV
115

×
116
    const lifetimeInHours = inputArguments[1].value as UInt32;
×
117
    if (subscription.monitoredItemCount > 0) {
×
UNCOV
118
        // This is returned when a Subscription already contains MonitoredItems.
×
119
        return callback(null, { statusCode: StatusCodes.BadInvalidState });
×
UNCOV
120
    }
×
UNCOV
121

×
UNCOV
122
    /**
×
UNCOV
123
     * MonitoredItems are used to monitor Variable Values for data changes and event notifier
×
UNCOV
124
     * Objects for new Events. Subscriptions are used to combine data changes and events of
×
UNCOV
125
     * the assigned MonitoredItems to an optimized stream of network messages. A reliable
×
UNCOV
126
     * delivery is ensured as long as the lifetime of the Subscription and the queues in the
×
UNCOV
127
     * MonitoredItems are long enough for a network interruption between OPC UA Client and
×
UNCOV
128
     * Server. All queues that ensure reliable delivery are normally kept in memory and a
×
UNCOV
129
     * Server restart would delete them.
×
UNCOV
130
     * There are use cases where OPC UA Clients have no permanent network connection to the
×
UNCOV
131
     * OPC UA Server or where reliable delivery of data changes and events is necessary
×
UNCOV
132
     * even if the OPC UA Server is restarted or the network connection is interrupted
×
UNCOV
133
     * for a longer time.
×
UNCOV
134
     * To ensure this reliable delivery, the OPC UA Server must store collected data and
×
UNCOV
135
     * events in non-volatile memory until the OPC UA Client has confirmed reception.
×
UNCOV
136
     * It is possible that there will be data lost if the Server is not shut down gracefully
×
UNCOV
137
     * or in case of power failure. But the OPC UA Server should store the queues frequently
×
UNCOV
138
     * even if the Server is not shut down.
×
UNCOV
139
     * The Method SetSubscriptionDurable defined in OPC 10000-5 is used to set a Subscription
×
UNCOV
140
     * into this durable mode and to allow much longer lifetimes and queue sizes than for normal
×
UNCOV
141
     * Subscriptions. The Method shall be called before the MonitoredItems are created in the
×
UNCOV
142
     * durable Subscription. The Server shall verify that the Method is called within the
×
UNCOV
143
     * Session context of the Session that owns the Subscription.
×
UNCOV
144
     *
×
UNCOV
145
     * A value of 0 for the parameter lifetimeInHours requests the highest lifetime supported by the Server.
×
UNCOV
146
     */
×
UNCOV
147

×
148
    const highestLifetimeInHours = 24 * 100;
×
UNCOV
149

×
UNCOV
150
    const revisedLifetimeInHours =
×
151
        lifetimeInHours === 0 ? highestLifetimeInHours : Math.max(1, Math.min(lifetimeInHours, highestLifetimeInHours));
×
UNCOV
152

×
UNCOV
153
    // also adjust subscription life time
×
154
    const currentLifeTimeInHours = (subscription.lifeTimeCount * subscription.publishingInterval) / (1000 * 60 * 60);
×
155
    if (currentLifeTimeInHours < revisedLifetimeInHours) {
×
156
        const requestedLifetimeCount = Math.ceil((revisedLifetimeInHours * (1000 * 60 * 60)) / subscription.publishingInterval);
×
UNCOV
157

×
158
        subscription.modify({
×
UNCOV
159
            requestedMaxKeepAliveCount: subscription.maxKeepAliveCount,
×
UNCOV
160
            requestedPublishingInterval: subscription.publishingInterval,
×
UNCOV
161
            maxNotificationsPerPublish: subscription.maxNotificationsPerPublish,
×
UNCOV
162
            priority: subscription.priority,
×
UNCOV
163
            requestedLifetimeCount
×
UNCOV
164
        });
×
UNCOV
165
    }
×
UNCOV
166

×
167
    const callMethodResult = new CallMethodResult({
×
UNCOV
168
        statusCode: StatusCodes.Good,
×
UNCOV
169
        outputArguments: [{ dataType: DataType.UInt32, arrayType: VariantArrayType.Scalar, value: revisedLifetimeInHours }]
×
UNCOV
170
    });
×
171
    callback(null, callMethodResult);
×
UNCOV
172
}
×
173

4✔
174
function requestServerStateChange(
2✔
175
    this: ServerEngine,
2✔
176
    inputArguments: Variant[],
2✔
177
    context: ISessionContext,
2✔
178
    callback: CallbackT<CallMethodResultOptions>
2✔
179
) {
2✔
180
    assert(Array.isArray(inputArguments));
2✔
181
    assert(typeof callback === "function");
2✔
182
    assert(Object.prototype.hasOwnProperty.call(context, "session"), " expecting a session id in the context object");
2✔
183
    const session = context.session as ServerSession;
2✔
184
    if (!session) {
2!
185
        return callback(null, { statusCode: StatusCodes.BadInternalError });
×
UNCOV
186
    }
×
187

2✔
188
    return callback(null, { statusCode: StatusCodes.BadNotImplemented });
2✔
189
}
2✔
190

4✔
191
function _getSubscription(
126✔
192
    this: ServerEngine,
126✔
193
    inputArguments: Variant[],
126✔
194
    context: ISessionContext
126✔
195
): { subscription: Subscription; statusCode?: never } | { statusCode: StatusCode; subscription?: never } {
126✔
196
    assert(Array.isArray(inputArguments));
126✔
197
    assert(Object.prototype.hasOwnProperty.call(context, "session"), " expecting a session id in the context object");
126✔
198
    const session = context.session as ServerSession;
126✔
199
    if (!session) {
126!
200
        return { statusCode: StatusCodes.BadInternalError };
×
UNCOV
201
    }
×
202
    const subscriptionId = inputArguments[0].value;
126✔
203
    const subscription = session.getSubscription(subscriptionId);
126✔
204
    if (!subscription) {
126✔
205
        // subscription may belongs to a different session  that ours
106✔
206
        if (this.findSubscription(subscriptionId)) {
106!
UNCOV
207
            // if yes, then access to  Subscription data should be denied
×
208
            return { statusCode: StatusCodes.BadUserAccessDenied };
×
UNCOV
209
        }
×
210
        return { statusCode: StatusCodes.BadSubscriptionIdInvalid };
106✔
211
    }
106✔
212
    return { subscription };
20✔
213
}
20✔
214
function resendData(
6✔
215
    this: ServerEngine,
6✔
216
    inputArguments: Variant[],
6✔
217
    context: ISessionContext,
6✔
218
    callback: CallbackT<CallMethodResultOptions>
6✔
219
): void {
6✔
220
    assert(typeof callback === "function");
6✔
221

6✔
222
    const data = _getSubscription.call(this, inputArguments, context);
6✔
223
    if (data.statusCode) return callback(null, { statusCode: data.statusCode });
6!
224
    const { subscription } = data;
6✔
225

6✔
226
    subscription
6✔
227
        .resendInitialValues()
6✔
228
        .then(() => {
6✔
229
            callback(null, { statusCode: StatusCodes.Good });
6✔
230
        })
6✔
231
        .catch((err) => callback(err));
6✔
232
}
6✔
233

4✔
234
// binding methods
4✔
235
function getMonitoredItemsId(
120✔
236
    this: ServerEngine,
120✔
237
    inputArguments: Variant[],
120✔
238
    context: ISessionContext,
120✔
239
    callback: CallbackT<CallMethodResultOptions>
120✔
240
) {
120✔
241
    assert(typeof callback === "function");
120✔
242

120✔
243
    const data = _getSubscription.call(this, inputArguments, context);
120✔
244
    if (data.statusCode) return callback(null, { statusCode: data.statusCode });
120✔
245
    const { subscription } = data;
14✔
246

14✔
247
    const result = subscription.getMonitoredItems();
14✔
248
    assert(result.statusCode);
14✔
249
    assert(result.serverHandles.length === result.clientHandles.length);
14✔
250
    const callMethodResult = new CallMethodResult({
14✔
251
        statusCode: result.statusCode,
14✔
252
        outputArguments: [
14✔
253
            { dataType: DataType.UInt32, arrayType: VariantArrayType.Array, value: result.serverHandles },
14✔
254
            { dataType: DataType.UInt32, arrayType: VariantArrayType.Array, value: result.clientHandles }
14✔
255
        ]
14✔
256
    });
14✔
257
    callback(null, callMethodResult);
14✔
258
}
14✔
259

4✔
260
function __bindVariable(self: ServerEngine, nodeId: NodeIdLike, options?: BindVariableOptions) {
30,228✔
261
    options = options || {};
30,228!
262

30,228✔
263
    const variable = self.addressSpace!.findNode(nodeId) as UAVariable;
30,228✔
264
    if (variable && variable.bindVariable) {
30,228✔
265
        variable.bindVariable(options, true);
30,228✔
266
        assert(typeof variable.asyncRefresh === "function");
30,228✔
267
        assert(typeof (variable as any).refreshFunc === "function");
30,228✔
268
    } else {
30,228!
269
        warningLog(
×
UNCOV
270
            "Warning: cannot bind object with id ",
×
UNCOV
271
            nodeId.toString(),
×
UNCOV
272
            " please check your nodeset.xml file or add this node programmatically"
×
UNCOV
273
        );
×
UNCOV
274
    }
×
275
}
30,228✔
276

4✔
277
// note OPCUA 1.03 part 4 page 76
4✔
278
// The Server-assigned identifier for the Subscription (see 7.14 for IntegerId definition). This identifier shall
4✔
279
// be unique for the entire Server, not just for the Session, in order to allow the Subscription to be transferred
4✔
280
// to another Session using the TransferSubscriptions service.
4✔
281
// After Server start-up the generation of subscriptionIds should start from a random IntegerId or continue from
4✔
282
// the point before the restart.
4✔
283
let next_subscriptionId = Math.ceil(Math.random() * 1000000);
4✔
284
export function setNextSubscriptionId(n: number) {
2✔
285
    next_subscriptionId = Math.max(n, 1);
×
UNCOV
286
}
×
287
function _get_next_subscriptionId() {
914✔
288
    debugLog(" next_subscriptionId = ", next_subscriptionId);
914✔
289
    return next_subscriptionId++;
914✔
290
}
914✔
291

4✔
292
export type StringGetter = () => string;
4✔
293
export type StringArrayGetter = () => string[];
4✔
294
export type ApplicationTypeGetter = () => ApplicationType;
4✔
295
export type BooleanGetter = () => boolean;
4✔
296

4✔
297
export interface ServerConfigurationOptions {
4✔
298
    applicationUri?: string | StringGetter;
4✔
299
    applicationType?: ApplicationType | ApplicationTypeGetter; // default "Server"
4✔
300

4✔
301
    hasSecureElement?: boolean | BooleanGetter; // default true
4✔
302

4✔
303
    multicastDnsEnabled?: boolean | BooleanGetter; // default true
4✔
304

4✔
305
    productUri?: string | StringGetter;
4✔
306

4✔
307
    // /** @restricted only in professional version */
4✔
308
    // resetToServerDefaults: () => Promise<void>;
4✔
309
    // /** @restricted only in professional version */
4✔
310
    // setAdminPassword?: (password: string) => Promise<void>;
4✔
311

4✔
312
    /**
4✔
313
     * The SupportedPrivateKeyFormats specifies the PrivateKey formats supported by the Server.
4✔
314
     * Possible values include “PEM” (see RFC 5958) or “PFX” (see PKCS #12).
4✔
315
     * @default ["PEM"]
4✔
316
     */
4✔
317
    supportedPrivateKeyFormat: string[] | StringArrayGetter;
4✔
318

4✔
319
    /**
4✔
320
     * The ServerCapabilities Property specifies the capabilities from Annex D
4✔
321
     * ( see https://reference.opcfoundation.org/GDS/v104/docs/D)  which the Server supports. The value is
4✔
322
     * the same as the value reported to the LocalDiscoveryServer when the Server calls the RegisterServer2 Service.
4✔
323
     */
4✔
324
    serverCapabilities?: string[] | StringArrayGetter; // default|"N/A"]
4✔
325
}
4✔
326
export interface ServerEngineOptions {
4✔
327
    applicationUri: string | StringGetter;
4✔
328

4✔
329
    buildInfo?: BuildInfoOptions;
4✔
330
    isAuditing?: boolean;
4✔
331
    /**
4✔
332
     * set to true to enable serverDiagnostics
4✔
333
     */
4✔
334
    serverDiagnosticsEnabled?: boolean;
4✔
335
    serverCapabilities?: ServerCapabilitiesOptions;
4✔
336
    historyServerCapabilities?: HistoryServerCapabilitiesOptions;
4✔
337
    serverConfiguration?: ServerConfigurationOptions;
4✔
338
}
4✔
339

4✔
340
export interface CreateSessionOption {
4✔
341
    clientDescription?: ApplicationDescription;
4✔
342
    sessionTimeout?: number;
4✔
343
    server?: IServerBase;
4✔
344
}
4✔
345

4✔
346
export type ClosingReason = "Timeout" | "Terminated" | "CloseSession" | "Forcing";
4✔
347

4✔
348
export type ServerEngineShutdownTask = (this: ServerEngine) => void | Promise<void>;
4✔
349

4✔
350
/**
4✔
351
 *
4✔
352
 */
4✔
353
export class ServerEngine extends EventEmitter implements IAddressSpaceAccessor {
4✔
354
    public static readonly registry = new ObjectRegistry();
102✔
355

102✔
356
    public isAuditing: boolean;
102✔
357
    public serverDiagnosticsSummary: ServerDiagnosticsSummaryDataType;
102✔
358
    public serverDiagnosticsEnabled: boolean;
102✔
359
    public serverCapabilities: ServerCapabilities;
102✔
360
    public historyServerCapabilities: HistoryServerCapabilities;
102✔
361
    public serverConfiguration: ServerConfigurationOptions;
102✔
362
    public clientDescription?: ApplicationDescription;
102✔
363

102✔
364
    public addressSpace: AddressSpace | null;
102✔
365
    public addressSpaceAccessor: IAddressSpaceAccessor | null = null;
102✔
366

102✔
367
    // pseudo private
102✔
368
    public _internalState: "creating" | "initializing" | "initialized" | "shutdown" | "disposed";
102✔
369

102✔
370
    private _sessions: { [key: string]: ServerSession };
102✔
371
    private _closedSessions: { [key: string]: ServerSession };
102✔
372
    private _orphanPublishEngine?: ServerSidePublishEngineForOrphanSubscription;
102✔
373
    private _shutdownTasks: ServerEngineShutdownTask[];
102✔
374
    private _applicationUri: string;
102✔
375
    private _expectedShutdownTime!: Date;
102✔
376
    private _serverStatus: ServerStatusDataType;
102✔
377
    private _globalCounter: { totalMonitoredItemCount: number } = { totalMonitoredItemCount: 0 };
102✔
378

102✔
379
    constructor(options?: ServerEngineOptions) {
102✔
380
        super();
616✔
381

616✔
382
        options = options || ({ applicationUri: "" } as ServerEngineOptions);
616✔
383
        options.buildInfo = options.buildInfo || {};
616✔
384

616✔
385
        ServerEngine.registry.register(this);
616✔
386

616✔
387
        this._sessions = {};
616✔
388
        this._closedSessions = {};
616✔
389
        this._orphanPublishEngine = undefined; // will be constructed on demand
616✔
390

616✔
391
        this.isAuditing = typeof options.isAuditing === "boolean" ? options.isAuditing : false;
616✔
392

616✔
393
        options.buildInfo.buildDate = options.buildInfo.buildDate || new Date();
616✔
394
        // ---------------------------------------------------- ServerStatusDataType
616✔
395
        this._serverStatus = new ServerStatusDataType({
616✔
396
            buildInfo: options.buildInfo,
616✔
397
            currentTime: new Date(),
616✔
398
            secondsTillShutdown: 0,
616✔
399
            shutdownReason: { text: "" },
616✔
400
            startTime: new Date(),
616✔
401
            state: ServerState.NoConfiguration
616✔
402
        });
616✔
403

616✔
404
        // --------------------------------------------------- ServerCapabilities
616✔
405
        options.serverCapabilities = options.serverCapabilities || {};
616✔
406

616✔
407
        options.serverConfiguration = options.serverConfiguration || {
616✔
408
            supportedPrivateKeyFormat: ["PEM"]
68✔
409
        };
68✔
410

616✔
411
        // https://profiles.opcfoundation.org/profile
616✔
412
        options.serverCapabilities.serverProfileArray = options.serverCapabilities.serverProfileArray || [
616✔
413
            "http://opcfoundation.org/UA-Profile/Server/Standard", // Standard UA Server Profile",
616✔
414
            "http://opcfoundation.org/UA-Profile/Server/DataAccess",
616✔
415
            "http://opcfoundation.org/UA-Profile/Server/ComplexTypes2017",
616✔
416
            "http://opcfoundation.org/UA-Profile/Server/Events",
616✔
417
            "http://opcfoundation.org/UA-Profile/Client/HistoricalAccess",
616✔
418
            "http://opcfoundation.org/UA-Profile/Server/Methods",
616✔
419
            "http://opcfoundation.org/UA-Profile/Server/StandardEventSubscription",
616✔
420
            "http://opcfoundation.org/UA-Profile/Transport/uatcp-uasc-uabinary",
616✔
421
            "http://opcfoundation.org/UA-Profile/Server/FileAccess",
616✔
422
            "http://opcfoundation.org/UA-Profile/Server/StateMachine",
616✔
423

616✔
424

616✔
425
            // "http://opcfoundation.org/UA-Profile/Transport/wss-uajson",
616✔
426
            // "http://opcfoundation.org/UA-Profile/Transport/wss-uasc-uabinary"
616✔
427
            // "http://opcfoundation.org/UA-Profile/Server/DurableSubscription"
616✔
428

616✔
429
            // "http://opcfoundation.org/UA-Profile/Server/ReverseConnect",
616✔
430
            // "http://opcfoundation.org/UAProfile/Server/NodeManagement",
616✔
431

616✔
432
            //  "Embedded UA Server Profile",
616✔
433
            // "Micro Embedded Device Server Profile",
616✔
434
            // "Nano Embedded Device Server Profile"
616✔
435
        ];
616✔
436
        options.serverCapabilities.localeIdArray = options.serverCapabilities.localeIdArray || ["en-EN", "fr-FR"];
616✔
437

616✔
438
        this.serverCapabilities = new ServerCapabilities(options.serverCapabilities);
616✔
439

616✔
440
        // to do when spec is clear about what goes here!
616✔
441
        // spec 1.04 says (in Part 4 7.33 SignedSoftwareCertificate
616✔
442
        // Note: Details on SoftwareCertificates need to be defined in a future version.
616✔
443
        this.serverCapabilities.softwareCertificates = [
616✔
444
            // new SignedSoftwareCertificate({})
616✔
445
        ];
616✔
446

616✔
447
        // make sure minSupportedSampleRate matches MonitoredItem.minimumSamplingInterval
616✔
448
        (this.serverCapabilities as any).__defineGetter__("minSupportedSampleRate", () => {
616✔
449
            return options!.serverCapabilities?.minSupportedSampleRate || MonitoredItem.minimumSamplingInterval;
1,134✔
450
        });
616✔
451

616✔
452
        this.serverConfiguration = options.serverConfiguration;
616✔
453

616✔
454
        this.historyServerCapabilities = new HistoryServerCapabilities(options.historyServerCapabilities);
616✔
455

616✔
456
        // --------------------------------------------------- serverDiagnosticsSummary extension Object
616✔
457
        this.serverDiagnosticsSummary = new ServerDiagnosticsSummaryDataType();
616✔
458
        assert(Object.prototype.hasOwnProperty.call(this.serverDiagnosticsSummary, "currentSessionCount"));
616✔
459

616✔
460
        // note spelling is different for serverDiagnosticsSummary.currentSubscriptionCount
616✔
461
        //      and sessionDiagnostics.currentSubscriptionsCount ( with an s)
616✔
462
        assert(Object.prototype.hasOwnProperty.call(this.serverDiagnosticsSummary, "currentSubscriptionCount"));
616✔
463

616✔
464
        (this.serverDiagnosticsSummary as any).__defineGetter__("currentSubscriptionCount", () => {
616✔
465
            // currentSubscriptionCount returns the total number of subscriptions
8,212✔
466
            // that are currently active on all sessions
8,212✔
467
            let counter = 0;
8,212✔
468
            Object.values(this._sessions).forEach((session: ServerSession) => {
8,212✔
469
                counter += session.currentSubscriptionCount;
8,102✔
470
            });
8,212✔
471
            // we also need to add the orphan subscriptions
8,212✔
472
            counter += this._orphanPublishEngine ? this._orphanPublishEngine.subscriptions.length : 0;
8,212✔
473
            return counter;
8,212✔
474
        });
616✔
475

616✔
476
        this._internalState = "creating";
616✔
477

616✔
478
        this.setServerState(ServerState.NoConfiguration);
616✔
479

616✔
480
        this.addressSpace = null;
616✔
481

616✔
482
        this._shutdownTasks = [];
616✔
483

616✔
484
        this._applicationUri = "";
616✔
485
        if (typeof options.applicationUri === "function") {
616✔
486
            (this as any).__defineGetter__("_applicationUri", options.applicationUri);
548✔
487
        } else {
616✔
488
            this._applicationUri = options.applicationUri || "<unset _applicationUri>";
68✔
489
        }
68✔
490

616✔
491
        options.serverDiagnosticsEnabled = Object.prototype.hasOwnProperty.call(options, "serverDiagnosticsEnable")
616✔
492
            ? options.serverDiagnosticsEnabled
516!
493
            : true;
616✔
494

616✔
495
        this.serverDiagnosticsEnabled = options.serverDiagnosticsEnabled!;
616✔
496
    }
616✔
497
    public isStarted(): boolean {
102✔
498
        return !!this._serverStatus!;
548✔
499
    }
548✔
500

102✔
501
    public dispose(): void {
102✔
502
        this.addressSpace = null;
1,202✔
503

1,202✔
504
        assert(Object.keys(this._sessions).length === 0, "ServerEngine#_sessions not empty");
1,202✔
505
        this._sessions = {};
1,202✔
506

1,202✔
507
        // todo fix me
1,202✔
508
        this._closedSessions = {};
1,202✔
509
        assert(Object.keys(this._closedSessions).length === 0, "ServerEngine#_closedSessions not empty");
1,202✔
510
        this._closedSessions = {};
1,202✔
511

1,202✔
512
        if (this._orphanPublishEngine) {
1,202✔
513
            this._orphanPublishEngine.dispose();
18✔
514
            this._orphanPublishEngine = undefined;
18✔
515
        }
18✔
516

1,202✔
517
        this._shutdownTasks = [];
1,202✔
518
        this._serverStatus = null as any as ServerStatusDataType;
1,202✔
519
        this._internalState = "disposed";
1,202✔
520
        this.removeAllListeners();
1,202✔
521

1,202✔
522
        ServerEngine.registry.unregister(this);
1,202✔
523
    }
1,202✔
524

102✔
525
    public get startTime(): Date {
102✔
526
        return this._serverStatus.startTime!;
×
UNCOV
527
    }
×
528

102✔
529
    public get currentTime(): Date {
102✔
530
        return this._serverStatus.currentTime!;
×
UNCOV
531
    }
×
532

102✔
533
    public get buildInfo(): BuildInfo {
102✔
534
        return this._serverStatus.buildInfo;
552✔
535
    }
552✔
536

102✔
537
    /**
102✔
538
     * register a function that will be called when the server will perform its shut down.
102✔
539
     */
102✔
540
    public registerShutdownTask(task: ServerEngineShutdownTask): void {
102✔
541
        assert(typeof task === "function");
2✔
542
        this._shutdownTasks.push(task);
2✔
543
    }
2✔
544

102✔
545
    /**
102✔
546
     */
102✔
547
    public async shutdown(): Promise<void> {
102✔
548
        debugLog("ServerEngine#shutdown");
618✔
549

618✔
550
        this._internalState = "shutdown";
618✔
551
        this.setServerState(ServerState.Shutdown);
618✔
552

618✔
553
        // delete any existing sessions
618✔
554
        const tokens = Object.keys(this._sessions).map((key: string) => {
618✔
555
            const session = this._sessions[key];
92✔
556
            return session.authenticationToken;
92✔
557
        });
618✔
558

618✔
559
        // delete and close any orphan subscriptions
618✔
560
        if (this._orphanPublishEngine) {
618✔
561
            this._orphanPublishEngine.shutdown();
18✔
562
        }
18✔
563

618✔
564
        for (const token of tokens) {
618✔
565
            this.closeSession(token, true, "Terminated");
92✔
566
        }
92✔
567

618✔
568
        // all sessions must have been terminated
618✔
569
        assert(this.currentSessionCount === 0);
618✔
570

618✔
571
        // all subscriptions must have been terminated
618✔
572
        assert(this.currentSubscriptionCount === 0, "all subscriptions must have been terminated");
618✔
573

618✔
574
        this._shutdownTasks.push(shutdownAndDisposeAddressSpace);
618✔
575

618✔
576
        // perform registerShutdownTask
618✔
577
        for (const task of this._shutdownTasks) {
618✔
578
            await task.call(this);
620✔
579
        }
620✔
580
        this.setServerState(ServerState.Invalid);
618✔
581

618✔
582
        this.dispose();
618✔
583
    }
618✔
584

102✔
585
    /**
102✔
586
     * the number of active sessions
102✔
587
     */
102✔
588
    public get currentSessionCount(): number {
102✔
589
        return this.serverDiagnosticsSummary.currentSessionCount;
7,552✔
590
    }
7,552✔
591

102✔
592
    /**
102✔
593
     * the cumulated number of sessions that have been opened since this object exists
102✔
594
     */
102✔
595
    public get cumulatedSessionCount(): number {
102✔
596
        return this.serverDiagnosticsSummary.cumulatedSessionCount;
10✔
597
    }
10✔
598

102✔
599
    /**
102✔
600
     * the number of active subscriptions.
102✔
601
     */
102✔
602
    public get currentSubscriptionCount(): number {
102✔
603
        return this.serverDiagnosticsSummary.currentSubscriptionCount;
4,060✔
604
    }
4,060✔
605

102✔
606
    /**
102✔
607
     * the cumulated number of subscriptions that have been created since this object exists
102✔
608
     */
102✔
609
    public get cumulatedSubscriptionCount(): number {
102✔
610
        return this.serverDiagnosticsSummary.cumulatedSubscriptionCount;
16✔
611
    }
16✔
612

102✔
613
    public get rejectedSessionCount(): number {
102✔
614
        return this.serverDiagnosticsSummary.rejectedSessionCount;
4✔
615
    }
4✔
616

102✔
617
    public get rejectedRequestsCount(): number {
102✔
618
        return this.serverDiagnosticsSummary.rejectedRequestsCount;
4✔
619
    }
4✔
620

102✔
621
    public get sessionAbortCount(): number {
102✔
622
        return this.serverDiagnosticsSummary.sessionAbortCount;
4✔
623
    }
4✔
624

102✔
625
    public get sessionTimeoutCount(): number {
102✔
626
        return this.serverDiagnosticsSummary.sessionTimeoutCount;
×
UNCOV
627
    }
×
628

102✔
629
    public get publishingIntervalCount(): number {
102✔
630
        return this.serverDiagnosticsSummary.publishingIntervalCount;
4✔
631
    }
4✔
632

102✔
633
    public incrementSessionTimeoutCount(): void {
102✔
634
        if (this.serverDiagnosticsSummary && this.serverDiagnosticsEnabled) {
22✔
635
            // The requests include all Services defined in Part 4 of the OPC UA Specification, also requests to create sessions. This number includes the securityRejectedRequestsCount.
22✔
636
            this.serverDiagnosticsSummary.sessionTimeoutCount += 1;
22✔
637
        }
22✔
638
    }
22✔
639
    public incrementSessionAbortCount(): void {
102✔
640
        if (this.serverDiagnosticsSummary && this.serverDiagnosticsEnabled) {
×
UNCOV
641
            // The requests include all Services defined in Part 4 of the OPC UA Specification, also requests to create sessions. This number includes the securityRejectedRequestsCount.
×
642
            this.serverDiagnosticsSummary.sessionAbortCount += 1;
×
UNCOV
643
        }
×
UNCOV
644
    }
×
645
    public incrementRejectedRequestsCount(): void {
102✔
646
        if (this.serverDiagnosticsSummary && this.serverDiagnosticsEnabled) {
362✔
647
            // The requests include all Services defined in Part 4 of the OPC UA Specification, also requests to create sessions. This number includes the securityRejectedRequestsCount.
362✔
648
            this.serverDiagnosticsSummary.rejectedRequestsCount += 1;
362✔
649
        }
362✔
650
    }
362✔
651

102✔
652
    /**
102✔
653
     * increment rejected session count (also increment rejected requests count)
102✔
654
     */
102✔
655
    public incrementRejectedSessionCount(): void {
102✔
656
        if (this.serverDiagnosticsSummary && this.serverDiagnosticsEnabled) {
134✔
657
            // The requests include all Services defined in Part 4 of the OPC UA Specification, also requests to create sessions. This number includes the securityRejectedRequestsCount.
134✔
658
            this.serverDiagnosticsSummary.rejectedSessionCount += 1;
134✔
659
        }
134✔
660
        this.incrementRejectedRequestsCount();
134✔
661
    }
134✔
662

102✔
663
    public incrementSecurityRejectedRequestsCount(): void {
102✔
664
        if (this.serverDiagnosticsSummary && this.serverDiagnosticsEnabled) {
114✔
665
            // The requests include all Services defined in Part 4 of the OPC UA Specification, also requests to create sessions. This number includes the securityRejectedRequestsCount.
114✔
666
            this.serverDiagnosticsSummary.securityRejectedRequestsCount += 1;
114✔
667
        }
114✔
668
        this.incrementRejectedRequestsCount();
114✔
669
    }
114✔
670

102✔
671
    /**
102✔
672
     * increment rejected session count (also increment rejected requests count)
102✔
673
     */
102✔
674
    public incrementSecurityRejectedSessionCount(): void {
102✔
675
        if (this.serverDiagnosticsSummary && this.serverDiagnosticsEnabled) {
114✔
676
            // The requests include all Services defined in Part 4 of the OPC UA Specification, also requests to create sessions. This number includes the securityRejectedRequestsCount.
114✔
677
            this.serverDiagnosticsSummary.securityRejectedSessionCount += 1;
114✔
678
        }
114✔
679
        this.incrementSecurityRejectedRequestsCount();
114✔
680
    }
114✔
681

102✔
682
    public setShutdownTime(date: Date): void {
102✔
683
        this._expectedShutdownTime = date;
548✔
684
    }
548✔
685
    public setShutdownReason(reason: LocalizedTextLike): void {
102✔
686
        this.addressSpace?.rootFolder.objects.server.serverStatus.shutdownReason.setValueFromSource({
2✔
687
            dataType: DataType.LocalizedText,
2✔
688
            value: coerceLocalizedText(reason)!
2✔
689
        });
2✔
690
    }
2✔
691
    /**
102✔
692
     * @return the approximate number of seconds until the server will be shut down. The
102✔
693
     * value is only relevant once the state changes into SHUTDOWN.
102✔
694
     */
102✔
695
    public secondsTillShutdown(): number {
102✔
696
        if (!this._expectedShutdownTime) {
50✔
697
            return 0;
48✔
698
        }
48✔
699
        // ToDo: implement a correct solution here
4✔
700
        const now = Date.now();
4✔
701
        return Math.max(0, Math.ceil((this._expectedShutdownTime.getTime() - now) / 1000));
2✔
702
    }
4✔
703

102✔
704
    /**
102✔
705
     * the name of the server
102✔
706
     */
102✔
707
    public get serverName(): string {
102✔
708
        return this._serverStatus.buildInfo!.productName!;
×
UNCOV
709
    }
×
710

102✔
711
    /**
102✔
712
     * the server urn
102✔
713
     */
102✔
714
    public get serverNameUrn(): string {
102✔
715
        return this._applicationUri;
612✔
716
    }
612✔
717

102✔
718
    /**
102✔
719
     * the urn of the server namespace
102✔
720
     */
102✔
721
    public get serverNamespaceUrn(): string {
102✔
722
        return this._applicationUri; // "urn:" + engine.serverName;
612✔
723
    }
612✔
724
    public get serverStatus(): ServerStatusDataType {
102✔
725
        return this._serverStatus;
6✔
726
    }
6✔
727

102✔
728
    public setServerState(serverState: ServerState): void {
102✔
729
        assert(serverState !== null && serverState !== undefined);
3,012✔
730
        this.addressSpace?.rootFolder?.objects?.server?.serverStatus?.state?.setValueFromSource({
3,012✔
731
            dataType: DataType.Int32,
3,012✔
732
            value: serverState
3,012✔
733
        });
3,012✔
734
    }
3,012✔
735

102✔
736
    public getServerDiagnosticsEnabledFlag(): boolean {
102✔
737
        const server = this.addressSpace!.rootFolder.objects.server;
×
738
        const serverDiagnostics = server.getComponentByName("ServerDiagnostics") as UAVariable;
×
739
        if (!serverDiagnostics) {
×
740
            return false;
×
UNCOV
741
        }
×
742
        return serverDiagnostics.readValue().value.value;
×
UNCOV
743
    }
×
744

102✔
745
    /**
102✔
746
     *
102✔
747
     */
102✔
748
    public initialize(options: OPCUAServerOptions, callback: (err?: Error | null) => void): void {
102✔
749
        assert(!this.addressSpace); // check that 'initialize' has not been already called
612✔
750

612✔
751
        this._internalState = "initializing";
612✔
752

612✔
753
        options = options || {};
612!
754
        assert(typeof callback === "function");
612✔
755

612✔
756
        options.nodeset_filename = options.nodeset_filename || nodesets.standard;
612✔
757

612✔
758
        const startTime = new Date();
612✔
759

612✔
760
        debugLog("Loading ", options.nodeset_filename, "...");
612✔
761

612✔
762
        this.addressSpace = AddressSpace.create();
612✔
763

612✔
764
        this.addressSpaceAccessor = new AddressSpaceAccessor(this.addressSpace);
612✔
765

612✔
766
        if (!options.skipOwnNamespace) {
612✔
767
            // register namespace 1 (our namespace);
612✔
768
            const serverNamespace = this.addressSpace.registerNamespace(this.serverNamespaceUrn);
612✔
769
            assert(serverNamespace.index === 1);
612✔
770
        }
612✔
771
        // eslint-disable-next-line max-statements
612✔
772
        generateAddressSpace(this.addressSpace, options.nodeset_filename)
612✔
773
            .catch((err) => {
612✔
774
                console.log(err.message);
×
775
                callback(err);
×
776
            })
612✔
777
            .then(() => {
612✔
778
                /* c8 ignore next */
4✔
779
                if (!this.addressSpace) {
4✔
UNCOV
780
                    throw new Error("Internal error");
×
UNCOV
781
                }
×
782
                const addressSpace = this.addressSpace;
612✔
783

612✔
784
                const endTime = new Date();
612✔
785
                debugLog("Loading ", options.nodeset_filename, " done : ", endTime.getTime() - startTime.getTime(), " ms");
612✔
786

612✔
787
                const bindVariableIfPresent = (nodeId: NodeId, opts: any) => {
612✔
788
                    assert(!nodeId.isEmpty());
35,496✔
789
                    const obj = addressSpace.findNode(nodeId);
35,496✔
790
                    if (obj) {
35,496✔
791
                        __bindVariable(this, nodeId, opts);
30,228✔
792
                    }
30,228✔
793
                    return obj;
35,496✔
794
                };
6,084✔
795

612✔
796
                // -------------------------------------------- install default get/put handler
612✔
797
                const server_NamespaceArray_Id = makeNodeId(VariableIds.Server_NamespaceArray); // ns=0;i=2255
612✔
798
                bindVariableIfPresent(server_NamespaceArray_Id, {
612✔
799
                    get() {
612✔
800
                        return new Variant({
2,450✔
801
                            arrayType: VariantArrayType.Array,
2,450✔
802
                            dataType: DataType.String,
2,450✔
803
                            value: addressSpace.getNamespaceArray().map((x) => x.namespaceUri)
2,450✔
804
                        });
2,450✔
805
                    },
612✔
806
                    set: null // read only
612✔
807
                });
612✔
808

612✔
809
                const server_NameUrn_var = new Variant({
612✔
810
                    arrayType: VariantArrayType.Array,
612✔
811
                    dataType: DataType.String,
612✔
812
                    value: [
612✔
813
                        this.serverNameUrn // this is us !
612✔
814
                    ]
612✔
815
                });
612✔
816
                const server_ServerArray_Id = makeNodeId(VariableIds.Server_ServerArray); // ns=0;i=2254
612✔
817

612✔
818
                bindVariableIfPresent(server_ServerArray_Id, {
612✔
819
                    get() {
612✔
820
                        return server_NameUrn_var;
648✔
821
                    },
608✔
822
                    set: null // read only
612✔
823
                });
612✔
824

612✔
825
                // fix DefaultUserRolePermissions and DefaultUserRolePermissions
612✔
826
                // of namespaces
612✔
827
                const namespaces = makeNodeId(ObjectIds.Server_Namespaces);
612✔
828
                const namespacesNode = addressSpace.findNode(namespaces) as UAObject;
612✔
829
                if (namespacesNode) {
612✔
830
                    for (const ns of namespacesNode.getComponents()) {
586✔
831
                        const defaultUserRolePermissions = ns.getChildByName("DefaultUserRolePermissions") as UAVariable | null;
576✔
832
                        if (defaultUserRolePermissions) {
576✔
833
                            defaultUserRolePermissions.setValueFromSource({ dataType: DataType.Null });
528✔
834
                        }
528✔
835
                        const defaultRolePermissions = ns.getChildByName("DefaultRolePermissions") as UAVariable | null;
576✔
836
                        if (defaultRolePermissions) {
576✔
837
                            defaultRolePermissions.setValueFromSource({ dataType: DataType.Null });
528✔
838
                        }
528✔
839
                    }
576✔
840
                }
586✔
841

612✔
842
                const bindStandardScalar = (
612✔
843
                    id: number,
31,212✔
844
                    dataType: DataType,
31,212✔
845
                    func: () => any,
31,212✔
846
                    setter_func?: (value: any) => void
31,212✔
847
                ) => {
31,212✔
848
                    assert(typeof id === "number", "expecting id to be a number");
31,212✔
849
                    assert(typeof func === "function");
31,212✔
850
                    assert(typeof setter_func === "function" || !setter_func);
31,212✔
851
                    assert(dataType !== null); // check invalid dataType
31,212✔
852

31,212✔
853
                    let setter_func2 = null;
31,212✔
854
                    if (setter_func) {
31,212✔
855
                        setter_func2 = (variant: Variant) => {
612✔
856
                            const variable2 = !!variant.value;
×
857
                            setter_func(variable2);
×
858
                            return StatusCodes.Good;
×
859
                        };
516✔
860
                    }
612✔
861

31,212✔
862
                    const nodeId = makeNodeId(id);
31,212✔
863

31,212✔
864
                    // make sur the provided function returns a valid value for the variant type
31,212✔
865
                    // This test may not be exhaustive but it will detect obvious mistakes.
31,212✔
866

31,212✔
867
                    /* c8 ignore next */
4✔
868
                    if (!isValidVariant(VariantArrayType.Scalar, dataType, func())) {
4✔
UNCOV
869
                        errorLog("func", func());
×
UNCOV
870
                        throw new Error("bindStandardScalar : func doesn't provide an value of type " + DataType[dataType]);
×
UNCOV
871
                    }
×
872

31,212✔
873
                    return bindVariableIfPresent(nodeId, {
31,212✔
874
                        get() {
31,212✔
875
                            return new Variant({
99,874✔
876
                                arrayType: VariantArrayType.Scalar,
99,874✔
877
                                dataType,
99,874✔
878
                                value: func()
99,874✔
879
                            });
99,874✔
880
                        },
28,712✔
881
                        set: setter_func2
31,212✔
882
                    });
31,212✔
883
                };
5,412✔
884

612✔
885
                const bindStandardArray = (id: number, variantDataType: DataType, dataType: any, func: () => any[]) => {
612✔
886
                    assert(typeof func === "function");
3,060✔
887
                    assert(variantDataType !== null); // check invalid dataType
3,060✔
888

3,060✔
889
                    const nodeId = makeNodeId(id);
3,060✔
890

3,060✔
891
                    // make sur the provided function returns a valid value for the variant type
3,060✔
892
                    // This test may not be exhaustive but it will detect obvious mistakes.
3,060✔
893
                    assert(isValidVariant(VariantArrayType.Array, variantDataType, func()));
3,060✔
894

3,060✔
895
                    bindVariableIfPresent(nodeId, {
3,060✔
896
                        get() {
3,060✔
897
                            const value = func();
2,772✔
898
                            assert(Array.isArray(value));
2,772✔
899
                            return new Variant({
2,772✔
900
                                arrayType: VariantArrayType.Array,
2,772✔
901
                                dataType: variantDataType,
2,772✔
902
                                value
2,772✔
903
                            });
2,772✔
904
                        },
2,786✔
905
                        set: null // read only
3,060✔
906
                    });
3,060✔
907
                };
996✔
908

612✔
909
                bindStandardScalar(VariableIds.Server_EstimatedReturnTime, DataType.DateTime, () => getMinOPCUADate());
612✔
910

612✔
911
                // TimeZoneDataType
612✔
912
                const timeZoneDataType = addressSpace.findDataType(resolveNodeId(DataTypeIds.TimeZoneDataType))!;
612✔
913

612✔
914
                const timeZone = new TimeZoneDataType({
612✔
915
                    daylightSavingInOffset: /* boolean*/ false,
612✔
916
                    offset: /* int16 */ 0
612✔
917
                });
612✔
918
                bindStandardScalar(VariableIds.Server_LocalTime, DataType.ExtensionObject, () => {
612✔
919
                    return timeZone;
1,128✔
920
                });
612✔
921

612✔
922
                bindStandardScalar(VariableIds.Server_ServiceLevel, DataType.Byte, () => {
612✔
923
                    return 255;
1,222✔
924
                });
612✔
925

612✔
926
                bindStandardScalar(VariableIds.Server_Auditing, DataType.Boolean, () => {
612✔
927
                    return this.isAuditing;
1,226✔
928
                });
612✔
929

612✔
930
                // eslint-disable-next-line @typescript-eslint/no-this-alias
612✔
931
                const engine = this;
612✔
932
                const makeNotReadableIfEnabledFlagIsFalse = (variable: UAVariable) => {
612✔
933
                    const originalIsReadable = variable.isReadable;
8,204✔
934
                    variable.isUserReadable = checkReadableFlag;
8,204✔
935
                    function checkReadableFlag(this: UAVariable, context: SessionContext): boolean {
8,204✔
936
                        const isEnabled = engine.serverDiagnosticsEnabled;
360✔
937
                        return originalIsReadable.call(this, context) && isEnabled;
360✔
938
                    }
360✔
939
                    for (const c of variable.getAggregates()) {
8,204✔
940
                        if (c.nodeClass === NodeClass.Variable) {
7,032✔
941
                            makeNotReadableIfEnabledFlagIsFalse(c as UAVariable);
7,032✔
942
                        }
7,032✔
943
                    }
7,032✔
944
                };
1,804✔
945

612✔
946
                const bindServerDiagnostics = () => {
612✔
947
                    bindStandardScalar(
612✔
948
                        VariableIds.Server_ServerDiagnostics_EnabledFlag,
612✔
949
                        DataType.Boolean,
612✔
950
                        () => {
612✔
951
                            return this.serverDiagnosticsEnabled;
1,222✔
952
                        },
704✔
953
                        (newFlag: boolean) => {
612✔
954
                            this.serverDiagnosticsEnabled = newFlag;
×
UNCOV
955
                        }
×
956
                    );
612✔
957
                    const nodeId = makeNodeId(VariableIds.Server_ServerDiagnostics_ServerDiagnosticsSummary);
612✔
958
                    const serverDiagnosticsSummaryNode = addressSpace.findNode(
612✔
959
                        nodeId
612✔
960
                    ) as UAServerDiagnosticsSummary<ServerDiagnosticsSummaryDataType>;
612✔
961

612✔
962
                    if (serverDiagnosticsSummaryNode) {
612✔
963
                        serverDiagnosticsSummaryNode.bindExtensionObject(this.serverDiagnosticsSummary);
586✔
964
                        this.serverDiagnosticsSummary = serverDiagnosticsSummaryNode.$extensionObject;
586✔
965
                        makeNotReadableIfEnabledFlagIsFalse(serverDiagnosticsSummaryNode);
586✔
966
                    }
586✔
967
                };
612✔
968

612✔
969
                const bindServerStatus = () => {
612✔
970
                    const serverStatusNode = addressSpace.findNode(
612✔
971
                        makeNodeId(VariableIds.Server_ServerStatus)
612✔
972
                    ) as UAServerStatus<DTServerStatus>;
612✔
973

612✔
974
                    if (!serverStatusNode) {
612✔
975
                        return;
26✔
976
                    }
26✔
977
                    if (serverStatusNode) {
590✔
978
                        serverStatusNode.bindExtensionObject(this._serverStatus);
586✔
979
                        serverStatusNode.minimumSamplingInterval = 1000;
586✔
980
                    }
586✔
981

586✔
982
                    const currentTimeNode = addressSpace.findNode(
586✔
983
                        makeNodeId(VariableIds.Server_ServerStatus_CurrentTime)
586✔
984
                    ) as UAVariable;
586✔
985

586✔
986
                    if (currentTimeNode) {
586✔
987
                        currentTimeNode.minimumSamplingInterval = 1000;
586✔
988
                    }
586✔
989
                    const secondsTillShutdown = addressSpace.findNode(
586✔
990
                        makeNodeId(VariableIds.Server_ServerStatus_SecondsTillShutdown)
586✔
991
                    ) as UAVariable;
586✔
992

586✔
993
                    if (secondsTillShutdown) {
586✔
994
                        secondsTillShutdown.minimumSamplingInterval = 1000;
586✔
995
                    }
586✔
996

586✔
997
                    assert(serverStatusNode.$extensionObject);
586✔
998

586✔
999
                    serverStatusNode.$extensionObject = new Proxy(serverStatusNode.$extensionObject, {
586✔
1000
                        get(target, prop) {
586✔
1001
                            if (prop === "currentTime") {
5,988✔
1002
                                serverStatusNode.currentTime.touchValue();
1,019✔
1003
                                return new Date();
1,019✔
1004
                            } else if (prop === "secondsTillShutdown") {
5,988✔
1005
                                serverStatusNode.secondsTillShutdown.touchValue();
50✔
1006
                                return engine.secondsTillShutdown();
50✔
1007
                            }
50✔
1008
                            return (target as any)[prop];
4,939✔
1009
                        }
4,939✔
1010
                    });
586✔
1011
                    this._serverStatus = serverStatusNode.$extensionObject;
586✔
1012
                };
612✔
1013

612✔
1014
                const bindServerCapabilities = () => {
612✔
1015
                    bindStandardArray(
612✔
1016
                        VariableIds.Server_ServerCapabilities_ServerProfileArray,
612✔
1017
                        DataType.String,
612✔
1018
                        DataType.String,
612✔
1019
                        () => {
612✔
1020
                            return this.serverCapabilities.serverProfileArray;
1,216✔
1021
                        }
1,216✔
1022
                    );
612✔
1023

612✔
1024
                    bindStandardArray(VariableIds.Server_ServerCapabilities_LocaleIdArray, DataType.String, "LocaleId", () => {
612✔
1025
                        return this.serverCapabilities.localeIdArray;
1,226✔
1026
                    });
612✔
1027

612✔
1028
                    bindStandardScalar(VariableIds.Server_ServerCapabilities_MinSupportedSampleRate, DataType.Double, () => {
612✔
1029
                        return Math.max(
1,132✔
1030
                            this.serverCapabilities.minSupportedSampleRate,
1,132✔
1031
                            defaultServerCapabilities.minSupportedSampleRate
1,132✔
1032
                        );
1,132✔
1033
                    });
612✔
1034

612✔
1035
                    bindStandardScalar(VariableIds.Server_ServerCapabilities_MaxBrowseContinuationPoints, DataType.UInt16, () => {
612✔
1036
                        return this.serverCapabilities.maxBrowseContinuationPoints;
36,928✔
1037
                    });
612✔
1038

612✔
1039
                    bindStandardScalar(VariableIds.Server_ServerCapabilities_MaxQueryContinuationPoints, DataType.UInt16, () => {
612✔
1040
                        return this.serverCapabilities.maxQueryContinuationPoints;
1,132✔
1041
                    });
612✔
1042

612✔
1043
                    bindStandardScalar(VariableIds.Server_ServerCapabilities_MaxHistoryContinuationPoints, DataType.UInt16, () => {
612✔
1044
                        return this.serverCapabilities.maxHistoryContinuationPoints;
1,136✔
1045
                    });
612✔
1046

612✔
1047
                    // new in 1.05
612✔
1048
                    bindStandardScalar(VariableIds.Server_ServerCapabilities_MaxSessions, DataType.UInt32, () => {
612✔
1049
                        return this.serverCapabilities.maxSessions;
1,132✔
1050
                    });
612✔
1051
                    bindStandardScalar(VariableIds.Server_ServerCapabilities_MaxSubscriptions, DataType.UInt32, () => {
612✔
1052
                        return this.serverCapabilities.maxSubscriptions;
1,132✔
1053
                    });
612✔
1054
                    bindStandardScalar(VariableIds.Server_ServerCapabilities_MaxMonitoredItems, DataType.UInt32, () => {
612✔
1055
                        return this.serverCapabilities.maxMonitoredItems;
1,132✔
1056
                    });
612✔
1057
                    bindStandardScalar(VariableIds.Server_ServerCapabilities_MaxSubscriptionsPerSession, DataType.UInt32, () => {
612✔
1058
                        return this.serverCapabilities.maxSubscriptionsPerSession;
1,132✔
1059
                    });
612✔
1060
                    bindStandardScalar(VariableIds.Server_ServerCapabilities_MaxSelectClauseParameters, DataType.UInt32, () => {
612✔
1061
                        return this.serverCapabilities.maxSelectClauseParameters;
1,132✔
1062
                    });
612✔
1063
                    bindStandardScalar(VariableIds.Server_ServerCapabilities_MaxWhereClauseParameters, DataType.UInt32, () => {
612✔
1064
                        return this.serverCapabilities.maxWhereClauseParameters;
1,132✔
1065
                    });
612✔
1066
                    //bindStandardArray(VariableIds.Server_ServerCapabilities_ConformanceUnits, DataType.QualifiedName, () => {
612✔
1067
                    //    return this.serverCapabilities.conformanceUnits;
612✔
1068
                    //});
612✔
1069
                    bindStandardScalar(
612✔
1070
                        VariableIds.Server_ServerCapabilities_MaxMonitoredItemsPerSubscription,
612✔
1071
                        DataType.UInt32,
612✔
1072
                        () => {
612✔
1073
                            return this.serverCapabilities.maxMonitoredItemsPerSubscription;
1,132✔
1074
                        }
1,132✔
1075
                    );
612✔
1076

612✔
1077
                    // added by DI : Server-specific period of time in milliseconds until the Server will revoke a lock.
612✔
1078
                    // TODO bindStandardScalar(VariableIds.Server_ServerCapabilities_MaxInactiveLockTime,
612✔
1079
                    // TODO     DataType.UInt16, function () {
612✔
1080
                    // TODO         return self.serverCapabilities.maxInactiveLockTime;
612✔
1081
                    // TODO });
612✔
1082

612✔
1083
                    bindStandardArray(
612✔
1084
                        VariableIds.Server_ServerCapabilities_SoftwareCertificates,
612✔
1085
                        DataType.ExtensionObject,
612✔
1086
                        "SoftwareCertificates",
612✔
1087
                        () => {
612✔
1088
                            return this.serverCapabilities.softwareCertificates;
1,132✔
1089
                        }
1,132✔
1090
                    );
612✔
1091

612✔
1092
                    bindStandardScalar(VariableIds.Server_ServerCapabilities_MaxArrayLength, DataType.UInt32, () => {
612✔
1093
                        return Math.min(this.serverCapabilities.maxArrayLength, Variant.maxArrayLength);
1,132✔
1094
                    });
612✔
1095

612✔
1096
                    bindStandardScalar(VariableIds.Server_ServerCapabilities_MaxStringLength, DataType.UInt32, () => {
612✔
1097
                        return Math.min(this.serverCapabilities.maxStringLength, BinaryStream.maxStringLength);
1,132✔
1098
                    });
612✔
1099

612✔
1100
                    bindStandardScalar(VariableIds.Server_ServerCapabilities_MaxByteStringLength, DataType.UInt32, () => {
612✔
1101
                        return Math.min(this.serverCapabilities.maxByteStringLength, BinaryStream.maxByteStringLength);
1,136✔
1102
                    });
612✔
1103

612✔
1104
                    bindStandardScalar(VariableIds.Server_ServerCapabilities_MaxMonitoredItemsQueueSize, DataType.UInt32, () => {
612✔
1105
                        return Math.max(1, this.serverCapabilities.maxMonitoredItemsQueueSize);
1,132✔
1106
                    });
612✔
1107

612✔
1108
                    const bindOperationLimits = (operationLimits: ServerOperationLimits) => {
612✔
1109
                        assert(operationLimits !== null && typeof operationLimits === "object");
612✔
1110

612✔
1111
                        const keys = Object.keys(operationLimits);
612✔
1112

612✔
1113
                        keys.forEach((key: string) => {
612✔
1114
                            const uid = "Server_ServerCapabilities_OperationLimits_" + upperCaseFirst(key);
7,344✔
1115
                            const nodeId = makeNodeId((VariableIds as any)[uid]);
7,344✔
1116
                            assert(!nodeId.isEmpty());
7,344✔
1117

7,344✔
1118
                            bindStandardScalar((VariableIds as any)[uid], DataType.UInt32, () => {
7,344✔
1119
                                return (operationLimits as any)[key];
50,880✔
1120
                            });
7,344✔
1121
                        });
612✔
1122
                    };
612✔
1123

612✔
1124
                    bindOperationLimits(this.serverCapabilities.operationLimits);
612✔
1125

612✔
1126
                    // i=2399 [ProgramStateMachineType_ProgramDiagnostics];
612✔
1127
                    function fix_ProgramStateMachineType_ProgramDiagnostics() {
612✔
1128
                        const nodeId = coerceNodeId("i=2399"); // ProgramStateMachineType_ProgramDiagnostics
612✔
1129
                        const variable = addressSpace.findNode(nodeId) as UAVariable;
612✔
1130
                        if (variable) {
612✔
1131
                            (variable as any).$extensionObject = new ProgramDiagnosticDataType({});
492✔
1132
                            //  variable.setValueFromSource({
492✔
1133
                            //     dataType: DataType.ExtensionObject,
492✔
1134
                            //     //     value: new ProgramDiagnostic2DataType()
492✔
1135
                            //     value: new ProgramDiagnosticDataType({})
492✔
1136
                            // });
492✔
1137
                        }
492✔
1138
                    }
612✔
1139
                    fix_ProgramStateMachineType_ProgramDiagnostics();
612✔
1140
                };
612✔
1141

612✔
1142
                const bindHistoryServerCapabilities = () => {
612✔
1143
                    bindStandardScalar(VariableIds.HistoryServerCapabilities_MaxReturnDataValues, DataType.UInt32, () => {
612✔
1144
                        return this.historyServerCapabilities.maxReturnDataValues;
1,132✔
1145
                    });
612✔
1146

612✔
1147
                    bindStandardScalar(VariableIds.HistoryServerCapabilities_MaxReturnEventValues, DataType.UInt32, () => {
612✔
1148
                        return this.historyServerCapabilities.maxReturnEventValues;
1,132✔
1149
                    });
612✔
1150

612✔
1151
                    bindStandardScalar(VariableIds.HistoryServerCapabilities_AccessHistoryDataCapability, DataType.Boolean, () => {
612✔
1152
                        return this.historyServerCapabilities.accessHistoryDataCapability;
1,132✔
1153
                    });
612✔
1154
                    bindStandardScalar(
612✔
1155
                        VariableIds.HistoryServerCapabilities_AccessHistoryEventsCapability,
612✔
1156
                        DataType.Boolean,
612✔
1157
                        () => {
612✔
1158
                            return this.historyServerCapabilities.accessHistoryEventsCapability;
1,132✔
1159
                        }
1,132✔
1160
                    );
612✔
1161
                    bindStandardScalar(VariableIds.HistoryServerCapabilities_InsertDataCapability, DataType.Boolean, () => {
612✔
1162
                        return this.historyServerCapabilities.insertDataCapability;
1,132✔
1163
                    });
612✔
1164
                    bindStandardScalar(VariableIds.HistoryServerCapabilities_ReplaceDataCapability, DataType.Boolean, () => {
612✔
1165
                        return this.historyServerCapabilities.replaceDataCapability;
1,132✔
1166
                    });
612✔
1167
                    bindStandardScalar(VariableIds.HistoryServerCapabilities_UpdateDataCapability, DataType.Boolean, () => {
612✔
1168
                        return this.historyServerCapabilities.updateDataCapability;
1,132✔
1169
                    });
612✔
1170

612✔
1171
                    bindStandardScalar(VariableIds.HistoryServerCapabilities_InsertEventCapability, DataType.Boolean, () => {
612✔
1172
                        return this.historyServerCapabilities.insertEventCapability;
1,132✔
1173
                    });
612✔
1174

612✔
1175
                    bindStandardScalar(VariableIds.HistoryServerCapabilities_ReplaceEventCapability, DataType.Boolean, () => {
612✔
1176
                        return this.historyServerCapabilities.replaceEventCapability;
1,132✔
1177
                    });
612✔
1178

612✔
1179
                    bindStandardScalar(VariableIds.HistoryServerCapabilities_UpdateEventCapability, DataType.Boolean, () => {
612✔
1180
                        return this.historyServerCapabilities.updateEventCapability;
1,132✔
1181
                    });
612✔
1182

612✔
1183
                    bindStandardScalar(VariableIds.HistoryServerCapabilities_DeleteEventCapability, DataType.Boolean, () => {
612✔
1184
                        return this.historyServerCapabilities.deleteEventCapability;
1,132✔
1185
                    });
612✔
1186

612✔
1187
                    bindStandardScalar(VariableIds.HistoryServerCapabilities_DeleteRawCapability, DataType.Boolean, () => {
612✔
1188
                        return this.historyServerCapabilities.deleteRawCapability;
1,132✔
1189
                    });
612✔
1190

612✔
1191
                    bindStandardScalar(VariableIds.HistoryServerCapabilities_DeleteAtTimeCapability, DataType.Boolean, () => {
612✔
1192
                        return this.historyServerCapabilities.deleteAtTimeCapability;
1,132✔
1193
                    });
612✔
1194

612✔
1195
                    bindStandardScalar(VariableIds.HistoryServerCapabilities_InsertAnnotationCapability, DataType.Boolean, () => {
612✔
1196
                        return this.historyServerCapabilities.insertAnnotationCapability;
1,132✔
1197
                    });
612✔
1198
                };
612✔
1199

612✔
1200
                type Getter<T> = () => T;
612✔
1201
                function r<T>(a: undefined | T | Getter<T>, defaultValue: T): T {
612✔
1202
                    if (a === undefined) return defaultValue;
7,906✔
1203
                    if (typeof a === "function") {
7,906✔
1204
                        return (a as any)();
5,300✔
1205
                    }
5,300✔
1206
                    return a;
1,882✔
1207
                }
1,882✔
1208
                const bindServerConfigurationBasic = () => {
612✔
1209
                    bindStandardArray(VariableIds.ServerConfiguration_ServerCapabilities, DataType.String, DataType.String, () =>
612✔
1210
                        r(this.serverConfiguration.serverCapabilities, ["NA"])
1,130✔
1211
                    );
612✔
1212
                    bindStandardScalar(VariableIds.ServerConfiguration_ApplicationType, DataType.Int32, () =>
612✔
1213
                        r(this.serverConfiguration.applicationType, ApplicationType.Server)
1,130✔
1214
                    );
612✔
1215
                    bindStandardScalar(VariableIds.ServerConfiguration_ApplicationUri, DataType.String, () =>
612✔
1216
                        r(this.serverConfiguration.applicationUri, "")
1,130✔
1217
                    );
612✔
1218
                    bindStandardScalar(VariableIds.ServerConfiguration_ProductUri, DataType.String, () =>
612✔
1219
                        r(this.serverConfiguration.productUri, "")
1,130✔
1220
                    );
612✔
1221
                    bindStandardScalar(VariableIds.ServerConfiguration_HasSecureElement, DataType.Boolean, () =>
612✔
1222
                        r(this.serverConfiguration.hasSecureElement, false)
1,128✔
1223
                    );
612✔
1224
                    bindStandardScalar(VariableIds.ServerConfiguration_MulticastDnsEnabled, DataType.Boolean, () =>
612✔
1225
                        r(this.serverConfiguration.multicastDnsEnabled, false)
1,130✔
1226
                    );
612✔
1227
                    bindStandardArray(
612✔
1228
                        VariableIds.ServerConfiguration_SupportedPrivateKeyFormats,
612✔
1229
                        DataType.String,
612✔
1230
                        DataType.String,
612✔
1231
                        () => r(this.serverConfiguration.supportedPrivateKeyFormat, ["PEM"])
612✔
1232
                    );
612✔
1233
                };
612✔
1234

612✔
1235
                bindServerDiagnostics();
612✔
1236

612✔
1237
                bindServerStatus();
612✔
1238

612✔
1239
                bindServerCapabilities();
612✔
1240

612✔
1241
                bindServerConfigurationBasic();
612✔
1242

612✔
1243
                bindHistoryServerCapabilities();
612✔
1244

612✔
1245
                const bindExtraStuff = () => {
612✔
1246
                    // mainly for compliance
612✔
1247
                    /*
612✔
1248
                // The version number for the data type description. i=104
612✔
1249
                bindStandardScalar(VariableIds.DataTypeDescriptionType_DataTypeVersion, DataType.String, () => {
612✔
1250
                    return "0";
612✔
1251
                });
612✔
1252

612✔
1253
                const namingRuleDataTypeNode = addressSpace.findDataType(resolveNodeId(DataTypeIds.NamingRuleType))! as UADataType;
612✔
1254

612✔
1255
                if (namingRuleDataTypeNode) {
612✔
1256
                    const namingRuleType = (namingRuleDataTypeNode as any)._getEnumerationInfo().nameIndex; // getEnumeration("NamingRuleType");
612✔
1257
                    if (!namingRuleType) {
612✔
1258
                        throw new Error("Cannot find Enumeration definition for NamingRuleType");
612✔
1259
                    }
612✔
1260
                    // i=111
612✔
1261
                    bindStandardScalar(VariableIds.ModellingRuleType_NamingRule, DataType.Int32, () => {
612✔
1262
                        return 0;
612✔
1263
                    });
612✔
1264

612✔
1265
                    // i=112
612✔
1266
                    bindStandardScalar(VariableIds.ModellingRule_Mandatory_NamingRule, DataType.Int32, () => {
612✔
1267
                        return namingRuleType.Mandatory ? namingRuleType.Mandatory.value : 0;
612✔
1268
                    });
612✔
1269

612✔
1270
                    // i=113
612✔
1271
                    bindStandardScalar(VariableIds.ModellingRule_Optional_NamingRule, DataType.Int32, () => {
612✔
1272
                        return namingRuleType.Optional ? namingRuleType.Optional.value : 0;
612✔
1273
                    });
612✔
1274
                    // i=114
612✔
1275
                    bindStandardScalar(VariableIds.ModellingRule_ExposesItsArray_NamingRule, DataType.Int32, () => {
612✔
1276
                        return namingRuleType.ExposesItsArray ? namingRuleType.ExposesItsArray.value : 0;
612✔
1277
                    });
612✔
1278
                    bindStandardScalar(VariableIds.ModellingRule_MandatoryPlaceholder_NamingRule, DataType.Int32, () => {
612✔
1279
                        return namingRuleType.MandatoryPlaceholder ? namingRuleType.MandatoryPlaceholder.value : 0;
612✔
1280
                    });
612✔
1281
                }
612✔
1282
*/
612✔
1283
                };
612✔
1284

612✔
1285
                bindExtraStuff();
612✔
1286

612✔
1287
                this.__internal_bindMethod(makeNodeId(MethodIds.Server_GetMonitoredItems), getMonitoredItemsId.bind(this));
612✔
1288
                this.__internal_bindMethod(makeNodeId(MethodIds.Server_SetSubscriptionDurable), setSubscriptionDurable.bind(this));
612✔
1289
                this.__internal_bindMethod(makeNodeId(MethodIds.Server_ResendData), resendData.bind(this));
612✔
1290
                this.__internal_bindMethod(
612✔
1291
                    makeNodeId(MethodIds.Server_RequestServerStateChange),
612✔
1292
                    requestServerStateChange.bind(this)
612✔
1293
                );
612✔
1294

612✔
1295
                // fix getMonitoredItems.outputArguments arrayDimensions
612✔
1296
                const fixGetMonitoredItemArgs = () => {
612✔
1297
                    const objects = this.addressSpace!.rootFolder?.objects;
612✔
1298
                    if (!objects || !objects.server) {
612✔
1299
                        return;
24✔
1300
                    }
24✔
1301
                    const getMonitoredItemsMethod = objects.server.getMethodByName("GetMonitoredItems")!;
592✔
1302
                    if (!getMonitoredItemsMethod) {
612✔
1303
                        return;
2✔
1304
                    }
2✔
1305
                    const outputArguments = getMonitoredItemsMethod.outputArguments!;
590✔
1306
                    const dataValue = outputArguments.readValue();
586✔
1307
                    if (!dataValue.value?.value) {
612!
UNCOV
1308
                        // value is null or undefined , meaning no arguments necessary
×
1309
                        return;
×
UNCOV
1310
                    }
✔
1311
                    assert(
586✔
1312
                        dataValue.value.value[0].arrayDimensions.length === 1 && dataValue.value.value[0].arrayDimensions[0] === 0
586✔
1313
                    );
612✔
1314
                    assert(
612✔
1315
                        dataValue.value.value[1].arrayDimensions.length === 1 && dataValue.value.value[1].arrayDimensions[0] === 0
612✔
1316
                    );
612✔
1317
                };
612✔
1318
                fixGetMonitoredItemArgs();
612✔
1319

612✔
1320
                const prepareServerDiagnostics = () => {
612✔
1321
                    const addressSpace1 = this.addressSpace!;
612✔
1322

612✔
1323
                    if (!addressSpace1.rootFolder.objects) {
612✔
1324
                        return;
24✔
1325
                    }
24✔
1326
                    const server = addressSpace1.rootFolder.objects.server;
592✔
1327

588✔
1328
                    if (!server) {
612!
1329
                        return;
×
UNCOV
1330
                    }
×
1331

592✔
1332
                    // create SessionsDiagnosticsSummary
592✔
1333
                    const serverDiagnosticsNode = server.getComponentByName("ServerDiagnostics") as UAServerDiagnostics;
592✔
1334
                    if (!serverDiagnosticsNode) {
612✔
1335
                        return;
2✔
1336
                    }
2✔
1337
                    if (true) {
590✔
1338
                        // set serverDiagnosticsNode enabledFlag writeable for admin user only
586✔
1339
                        // TO DO ...
586✔
1340
                        serverDiagnosticsNode.enabledFlag.userAccessLevel = makeAccessLevelFlag("CurrentRead");
586✔
1341
                        serverDiagnosticsNode.enabledFlag.accessLevel = makeAccessLevelFlag("CurrentRead");
586✔
1342
                    }
586✔
1343

586✔
1344
                    // A Server may not expose the SamplingIntervalDiagnosticsArray if it does not use fixed sampling rates.
586✔
1345
                    // because we are not using fixed sampling rate, we need to remove the optional SamplingIntervalDiagnosticsArray
586✔
1346
                    // component
586✔
1347
                    const samplingIntervalDiagnosticsArray = serverDiagnosticsNode.getComponentByName(
586✔
1348
                        "SamplingIntervalDiagnosticsArray"
586✔
1349
                    );
586✔
1350
                    if (samplingIntervalDiagnosticsArray) {
586✔
1351
                        addressSpace.deleteNode(samplingIntervalDiagnosticsArray);
586✔
1352
                        const s = serverDiagnosticsNode.getComponents();
586✔
1353
                    }
586✔
1354

586✔
1355
                    const subscriptionDiagnosticsArrayNode = serverDiagnosticsNode.getComponentByName(
586✔
1356
                        "SubscriptionDiagnosticsArray"
586✔
1357
                    )! as UADynamicVariableArray<SessionDiagnosticsDataType>;
586✔
1358
                    assert(subscriptionDiagnosticsArrayNode.nodeClass === NodeClass.Variable);
586✔
1359
                    bindExtObjArrayNode(subscriptionDiagnosticsArrayNode, "SubscriptionDiagnosticsType", "subscriptionId");
586✔
1360

586✔
1361
                    makeNotReadableIfEnabledFlagIsFalse(subscriptionDiagnosticsArrayNode);
586✔
1362

586✔
1363
                    const sessionsDiagnosticsSummary = serverDiagnosticsNode.getComponentByName("SessionsDiagnosticsSummary")!;
586✔
1364

586✔
1365
                    const sessionDiagnosticsArray = sessionsDiagnosticsSummary.getComponentByName(
586✔
1366
                        "SessionDiagnosticsArray"
586✔
1367
                    )! as UADynamicVariableArray<SessionDiagnosticsDataType>;
586✔
1368
                    assert(sessionDiagnosticsArray.nodeClass === NodeClass.Variable);
586✔
1369

586✔
1370
                    bindExtObjArrayNode(sessionDiagnosticsArray, "SessionDiagnosticsVariableType", "sessionId");
586✔
1371

586✔
1372
                    const varType = addressSpace.findVariableType("SessionSecurityDiagnosticsType");
586✔
1373
                    if (!varType) {
612✔
1374
                        debugLog("Warning cannot find SessionSecurityDiagnosticsType variable Type");
94✔
1375
                    } else {
612✔
1376
                        const sessionSecurityDiagnosticsArray = sessionsDiagnosticsSummary.getComponentByName(
492✔
1377
                            "SessionSecurityDiagnosticsArray"
492✔
1378
                        )! as UADynamicVariableArray<SessionSecurityDiagnosticsDataType>;
492✔
1379
                        assert(sessionSecurityDiagnosticsArray.nodeClass === NodeClass.Variable);
492✔
1380
                        bindExtObjArrayNode(sessionSecurityDiagnosticsArray, "SessionSecurityDiagnosticsType", "sessionId");
492✔
1381
                        ensureObjectIsSecure(sessionSecurityDiagnosticsArray);
492✔
1382
                    }
492✔
1383
                };
612✔
1384

612✔
1385
                prepareServerDiagnostics();
612✔
1386

612✔
1387
                this._internalState = "initialized";
612✔
1388
                this.setServerState(ServerState.Running);
612✔
1389
                setImmediate(() => callback());
612✔
1390
            });
612✔
1391
    }
612✔
1392

102✔
1393
    public async browseWithAutomaticExpansion(
102✔
1394
        nodesToBrowse: BrowseDescription[],
25,312✔
1395
        context: ISessionContext
25,312✔
1396
    ): Promise<BrowseResult[]> {
25,312✔
1397
        // do expansion first
25,312✔
1398
        for (const browseDescription of nodesToBrowse) {
25,312✔
1399
            const nodeId = resolveNodeId(browseDescription.nodeId);
95,142✔
1400
            const node = this.addressSpace!.findNode(nodeId);
95,142✔
1401
            if (node) {
95,142✔
1402
                if (node.onFirstBrowseAction) {
95,142✔
1403
                    try {
2✔
1404
                        await node.onFirstBrowseAction();
2✔
1405
                        node.onFirstBrowseAction = undefined;
2✔
1406
                    } catch (err) {
2!
1407
                        if (types.isNativeError(err)) {
×
1408
                            errorLog("onFirstBrowseAction method has failed", err.message);
×
UNCOV
1409
                        }
×
1410
                        errorLog(err);
×
UNCOV
1411
                    }
×
1412
                    assert(node.onFirstBrowseAction === undefined, "expansion can only be made once");
2✔
1413
                }
2✔
1414
            }
95,142✔
1415
        }
95,142✔
1416
        return await this.browse(context, nodesToBrowse);
25,312✔
1417
    }
25,312✔
1418
    public async browse(context: ISessionContext, nodesToBrowse: BrowseDescriptionOptions[]): Promise<BrowseResult[]> {
102✔
1419
        return this.addressSpaceAccessor!.browse(context, nodesToBrowse);
25,342✔
1420
    }
25,342✔
1421
    public async read(context: ISessionContext, readRequest: ReadRequestOptions): Promise<DataValue[]> {
102✔
1422
        return this.addressSpaceAccessor!.read(context, readRequest);
47,393✔
1423
    }
47,393✔
1424
    public async write(context: ISessionContext, nodesToWrite: WriteValue[]): Promise<StatusCode[]> {
102✔
1425
        return await this.addressSpaceAccessor!.write(context, nodesToWrite);
320✔
1426
    }
320✔
1427
    public async call(context: ISessionContext, methodsToCall: CallMethodRequest[]): Promise<CallMethodResultOptions[]> {
102✔
1428
        return await this.addressSpaceAccessor!.call(context, methodsToCall);
172✔
1429
    }
172✔
1430
    public async historyRead(context: ISessionContext, historyReadRequest: HistoryReadRequest): Promise<HistoryReadResult[]> {
102✔
1431
        return this.addressSpaceAccessor!.historyRead(context, historyReadRequest);
52✔
1432
    }
52✔
1433

102✔
1434
    public getOldestInactiveSession(): ServerSession | null {
102✔
1435
        // search screwed or closed session first
52✔
1436
        let tmp = Object.values(this._sessions).filter(
52✔
1437
            (session1: ServerSession) =>
52✔
1438
                session1.status === "screwed" || session1.status === "disposed" || session1.status === "closed"
618✔
1439
        );
52✔
1440
        if (tmp.length === 0) {
52✔
1441
            // if none available, tap into the session that are not yet activated
52✔
1442
            tmp = Object.values(this._sessions).filter((session1: ServerSession) => session1.status === "new");
52✔
1443
        }
52✔
1444
        if (tmp.length === 0) return null;
52✔
1445
        let session = tmp[0];
28✔
1446
        for (let i = 1; i < tmp.length; i++) {
52✔
1447
            const c = tmp[i];
526✔
1448
            if (session.creationDate.getTime() < c.creationDate.getTime()) {
526✔
1449
                session = c;
351✔
1450
            }
351✔
1451
        }
526✔
1452
        return session;
28✔
1453
    }
28✔
1454

102✔
1455
    /**
102✔
1456
     * create a new server session object.
102✔
1457
     */
102✔
1458
    public createSession(options?: CreateSessionOption): ServerSession {
102✔
1459
        options = options || {};
2,140✔
1460
        options.server = options.server || {};
2,140✔
1461
        debugLog("createSession : increasing serverDiagnosticsSummary cumulatedSessionCount/currentSessionCount ");
2,140✔
1462
        this.serverDiagnosticsSummary.cumulatedSessionCount += 1;
2,140✔
1463
        this.serverDiagnosticsSummary.currentSessionCount += 1;
2,140✔
1464

2,140✔
1465
        this.clientDescription = options.clientDescription || new ApplicationDescription({});
2,140✔
1466

2,140✔
1467
        const sessionTimeout = options.sessionTimeout || 1000;
2,140✔
1468
        assert(typeof sessionTimeout === "number");
2,140✔
1469

2,140✔
1470
        const session = new ServerSession(this, options.server.userManager!, sessionTimeout);
2,140✔
1471

2,140✔
1472
        debugLog("createSession :sessionTimeout = ", session.sessionTimeout);
2,140✔
1473

2,140✔
1474
        const key = session.authenticationToken.toString();
2,140✔
1475

2,140✔
1476
        this._sessions[key] = session;
2,140✔
1477

2,140✔
1478
        // see spec OPC Unified Architecture,  Part 2 page 26 Release 1.02
2,140✔
1479
        // TODO : When a Session is created, the Server adds an entry for the Client
2,140✔
1480
        //        in its SessionDiagnosticsArray Variable
2,140✔
1481

2,140✔
1482
        session.on("new_subscription", (subscription: Subscription) => {
2,140✔
1483
            this.serverDiagnosticsSummary.cumulatedSubscriptionCount += 1;
914✔
1484
            // add the subscription diagnostics in our subscriptions diagnostics array
914✔
1485
            // note currentSubscriptionCount is handled directly with a special getter
914✔
1486
        });
2,140✔
1487

2,140✔
1488
        session.on("subscription_terminated", (subscription: Subscription) => {
2,140✔
1489
            // remove the subscription diagnostics in our subscriptions diagnostics array
890✔
1490
            // note currentSubscriptionCount is handled directly with a special getter
890✔
1491
        });
2,140✔
1492

2,140✔
1493
        // OPC Unified Architecture, Part 4 23 Release 1.03
2,140✔
1494
        // Sessions are terminated by the Server automatically if the Client fails to issue a Service request on the
2,140✔
1495
        // Session within the timeout period negotiated by the Server in the CreateSession Service response.
2,140✔
1496
        // This protects the Server against Client failures and against situations where a failed underlying
2,140✔
1497
        // connection cannot be re-established. Clients shall be prepared to submit requests in a timely manner
2,140✔
1498
        // prevent the Session from closing automatically. Clients may explicitly terminate sessions using the
2,140✔
1499
        // CloseSession Service.
2,140✔
1500
        session.on("timeout", () => {
2,140✔
1501
            // the session hasn't been active for a while , probably because the client has disconnected abruptly
22✔
1502
            // it is now time to close the session completely
22✔
1503
            this.serverDiagnosticsSummary.sessionTimeoutCount += 1;
22✔
1504
            session.sessionName = session.sessionName || "";
22✔
1505

22✔
1506
            const channel = session.channel;
22✔
1507
            errorLog(
22✔
1508
                chalk.cyan("Server: closing SESSION "),
22✔
1509
                session.status,
22✔
1510
                chalk.yellow(session.sessionName),
22✔
1511
                chalk.yellow(session.nodeId.toString()),
22✔
1512
                chalk.cyan(" because of timeout = "),
22✔
1513
                session.sessionTimeout,
22✔
1514
                chalk.cyan(" has expired without a keep alive"),
22✔
1515
                chalk.bgCyan("channel = "),
22✔
1516
                channel?.remoteAddress,
22✔
1517
                " port = ",
22✔
1518
                channel?.remotePort
22✔
1519
            );
22✔
1520

22✔
1521
            // If a Server terminates a Session for any other reason, Subscriptions  associated with the Session,
22✔
1522
            // are not deleted. => deleteSubscription= false
22✔
1523
            this.closeSession(session.authenticationToken, /*deleteSubscription=*/ false, /* reason =*/ "Timeout");
22✔
1524

22✔
1525
            this.incrementSessionTimeoutCount();
22✔
1526
        });
2,140✔
1527

2,140✔
1528
        return session;
2,140✔
1529
    }
2,140✔
1530

102✔
1531
    /**
102✔
1532
     * @param authenticationToken
102✔
1533
     * @param deleteSubscriptions {Boolean} : true if session's subscription shall be deleted
102✔
1534
     * @param {String} [reason = "CloseSession"] the reason for closing the session (
102✔
1535
     *                 shall be "Timeout", "Terminated" or "CloseSession")
102✔
1536
     *
102✔
1537
     *
102✔
1538
     * what the specs say:
102✔
1539
     * -------------------
102✔
1540
     *
102✔
1541
     * If a Client invokes the CloseSession Service then all Subscriptions associated with the Session are also deleted
102✔
1542
     * if the deleteSubscriptions flag is set to TRUE. If a Server terminates a Session for any other reason,
102✔
1543
     * Subscriptions associated with the Session, are not deleted. Each Subscription has its own lifetime to protect
102✔
1544
     * against data loss in the case of a Session termination. In these cases, the Subscription can be reassigned to
102✔
1545
     * another Client before its lifetime expires.
102✔
1546
     */
102✔
1547
    public closeSession(authenticationToken: NodeId, deleteSubscriptions: boolean, reason: ClosingReason): void {
102✔
1548
        reason = reason || "CloseSession";
2,140!
1549
        assert(typeof reason === "string");
2,140✔
1550
        assert(reason === "Timeout" || reason === "Terminated" || reason === "CloseSession" || reason === "Forcing");
2,140✔
1551

2,140✔
1552
        debugLog("ServerEngine.closeSession ", authenticationToken.toString(), deleteSubscriptions);
2,140✔
1553

2,140✔
1554
        const session = this.getSession(authenticationToken);
2,140✔
1555

2,140✔
1556
        // c8 ignore next
2,140✔
1557
        if (!session) {
2,140!
UNCOV
1558
            throw new Error("cannot find session with this authenticationToken " + authenticationToken.toString());
×
UNCOV
1559
        }
×
1560

2,140✔
1561
        if (!deleteSubscriptions) {
2,140✔
1562
            // Live Subscriptions will not be deleted, but transferred to the orphanPublishEngine
64✔
1563
            // until they time out or until a other session transfer them back to it.
64✔
1564
            if (!this._orphanPublishEngine) {
64✔
1565
                this._orphanPublishEngine = new ServerSidePublishEngineForOrphanSubscription({ maxPublishRequestInQueue: 0 });
18✔
1566
            }
18✔
1567

64✔
1568
            debugLog("transferring remaining live subscription to orphanPublishEngine !");
64✔
1569
            ServerSidePublishEngine.transferSubscriptionsToOrphan(session.publishEngine, this._orphanPublishEngine);
64✔
1570
        }
64✔
1571

2,140✔
1572
        session.close(deleteSubscriptions, reason);
2,140✔
1573

2,140✔
1574
        assert(session.status === "closed");
2,140✔
1575

2,140✔
1576
        debugLog(" engine.serverDiagnosticsSummary.currentSessionCount -= 1;");
2,140✔
1577
        this.serverDiagnosticsSummary.currentSessionCount -= 1;
2,140✔
1578

2,140✔
1579
        // xx //TODO make sure _closedSessions gets cleaned at some point
2,140✔
1580
        // xx self._closedSessions[key] = session;
2,140✔
1581

2,140✔
1582
        // remove sessionDiagnostics from server.ServerDiagnostics.SessionsDiagnosticsSummary.SessionDiagnosticsSummary
2,140✔
1583
        delete this._sessions[authenticationToken.toString()];
2,140✔
1584
        session.dispose();
2,140✔
1585
    }
2,140✔
1586

102✔
1587
    public findSubscription(subscriptionId: number): Subscription | null {
102✔
1588
        const subscriptions: Subscription[] = [];
156✔
1589
        Object.values(this._sessions).map((session) => {
156✔
1590
            if (subscriptions.length) {
188✔
1591
                return;
30✔
1592
            }
30✔
1593
            const subscription = session.publishEngine.getSubscriptionById(subscriptionId);
176✔
1594
            if (subscription) {
188✔
1595
                subscriptions.push(subscription);
32✔
1596
            }
32✔
1597
        });
156✔
1598
        if (subscriptions.length) {
156✔
1599
            assert(subscriptions.length === 1);
32✔
1600
            return subscriptions[0];
32✔
1601
        }
32✔
1602
        return this.findOrphanSubscription(subscriptionId);
144✔
1603
    }
144✔
1604

102✔
1605
    public findOrphanSubscription(subscriptionId: number): Subscription | null {
102✔
1606
        if (!this._orphanPublishEngine) {
724✔
1607
            return null;
638✔
1608
        }
638✔
1609
        return this._orphanPublishEngine.getSubscriptionById(subscriptionId);
86✔
1610
    }
86✔
1611

102✔
1612
    public deleteOrphanSubscription(subscription: Subscription): StatusCode {
102✔
1613
        if (!this._orphanPublishEngine) {
×
1614
            return StatusCodes.BadInternalError;
×
UNCOV
1615
        }
×
1616
        assert(this.findSubscription(subscription.id));
×
UNCOV
1617

×
1618
        const c = this._orphanPublishEngine.subscriptionCount;
×
1619
        subscription.terminate();
×
1620
        subscription.dispose();
×
1621
        assert(this._orphanPublishEngine.subscriptionCount === c - 1);
×
1622
        return StatusCodes.Good;
×
UNCOV
1623
    }
×
1624

102✔
1625
    /**
102✔
1626
     * @param session           {ServerSession}  - the new session that will own the subscription
102✔
1627
     * @param subscriptionId    {IntegerId}      - the subscription Id to transfer
102✔
1628
     * @param sendInitialValues {Boolean}        - true if initial values will be resent.
102✔
1629
     * @return                  {TransferResult}
102✔
1630
     */
102✔
1631
    public async transferSubscription(
102✔
1632
        session: ServerSession,
50✔
1633
        subscriptionId: number,
50✔
1634
        sendInitialValues: boolean
50✔
1635
    ): Promise<TransferResult> {
50✔
1636
        if (subscriptionId <= 0) {
50!
1637
            return new TransferResult({ statusCode: StatusCodes.BadSubscriptionIdInvalid });
×
UNCOV
1638
        }
×
1639

50✔
1640
        const subscription = this.findSubscription(subscriptionId);
50✔
1641
        if (!subscription) {
50✔
1642
            return new TransferResult({ statusCode: StatusCodes.BadSubscriptionIdInvalid });
10✔
1643
        }
10✔
1644

40✔
1645
        // check that session have same userIdentity
40✔
1646
        if (!sessionsCompatibleForTransfer(subscription.$session, session)) {
50!
1647
            return new TransferResult({ statusCode: StatusCodes.BadUserAccessDenied });
×
UNCOV
1648
        }
×
1649

40✔
1650
        // update diagnostics
40✔
1651
        subscription.subscriptionDiagnostics.transferRequestCount++;
40✔
1652

40✔
1653
        // now check that new session has sufficient right
40✔
1654
        // if (session.authenticationToken.toString() !== subscription.authenticationToken.toString()) {
40✔
1655
        //     warningLog("ServerEngine#transferSubscription => BadUserAccessDenied");
40✔
1656
        //     return new TransferResult({ statusCode: StatusCodes.BadUserAccessDenied });
40✔
1657
        // }
40✔
1658
        if ((session.publishEngine as any) === subscription.publishEngine) {
50!
UNCOV
1659
            // subscription is already in this session !!
×
1660
            return new TransferResult({ statusCode: StatusCodes.BadNothingToDo });
×
UNCOV
1661
        }
×
1662
        if (session === subscription.$session) {
50!
UNCOV
1663
            // subscription is already in this session !!
×
1664
            return new TransferResult({ statusCode: StatusCodes.BadNothingToDo });
×
UNCOV
1665
        }
×
1666

40✔
1667
        // The number of times the subscription has been transferred to an alternate client.
40✔
1668
        subscription.subscriptionDiagnostics.transferredToAltClientCount++;
40✔
1669
        // The number of times the subscription has been transferred to an alternate session for the same client.
40✔
1670
        subscription.subscriptionDiagnostics.transferredToSameClientCount++;
40✔
1671

40✔
1672
        const nbSubscriptionBefore = session.publishEngine.subscriptionCount;
40✔
1673

40✔
1674
        if (subscription.$session) {
50✔
1675
            subscription.$session._unexposeSubscriptionDiagnostics(subscription);
32✔
1676
        }
32✔
1677

40✔
1678
        subscription.$session = session;
40✔
1679

40✔
1680
        await ServerSidePublishEngine.transferSubscription(subscription, session.publishEngine, sendInitialValues);
40✔
1681

40✔
1682
        session._exposeSubscriptionDiagnostics(subscription);
40✔
1683

40✔
1684
        assert((subscription.publishEngine as any) === session.publishEngine);
40✔
1685
        // assert(session.publishEngine.subscriptionCount === nbSubscriptionBefore + 1);
40✔
1686

40✔
1687
        const result = new TransferResult({
40✔
1688
            availableSequenceNumbers: subscription.getAvailableSequenceNumbers(),
40✔
1689
            statusCode: StatusCodes.Good
40✔
1690
        });
40✔
1691

40✔
1692
        // c8 ignore next
40✔
1693
        if (doDebug) {
50!
UNCOV
1694
            debugLog("TransferResult", result.toString());
×
UNCOV
1695
        }
×
1696

40✔
1697
        return result;
40✔
1698
    }
40✔
1699

102✔
1700
    /**
102✔
1701
     * retrieve a session by its authenticationToken.
102✔
1702
     *
102✔
1703
     * @param authenticationToken
102✔
1704
     * @param activeOnly
102✔
1705
     * @return {ServerSession}
102✔
1706
     */
102✔
1707
    public getSession(authenticationToken: NodeId, activeOnly?: boolean): ServerSession | null {
102✔
1708
        if (
95,678✔
1709
            !authenticationToken ||
95,678✔
1710
            (authenticationToken.identifierType && authenticationToken.identifierType !== NodeIdType.BYTESTRING)
95,678✔
1711
        ) {
95,678✔
1712
            return null; // wrong type !
4,262✔
1713
        }
4,262✔
1714
        const key = authenticationToken.toString();
91,420✔
1715
        let session = this._sessions[key];
91,416✔
1716
        if (!activeOnly && !session) {
95,678✔
1717
            session = this._closedSessions[key];
56✔
1718
        }
56✔
1719
        return session;
91,420✔
1720
    }
91,420✔
1721

102✔
1722
    public async translateBrowsePaths(browsePaths: BrowsePath[]): Promise<BrowsePathResult[]> {
102✔
1723
        const browsePathResults: BrowsePathResult[] = [];
122✔
1724
        for (const browsePath of browsePaths) {
122✔
1725
            const result = await this.translateBrowsePath(browsePath);
230✔
1726
            browsePathResults.push(result);
230✔
1727
        }
230✔
1728
        return browsePathResults;
122✔
1729
    }
122✔
1730
    public async translateBrowsePath(browsePath: BrowsePath): Promise<BrowsePathResult> {
102✔
1731
        return this.addressSpace!.browsePath(browsePath);
244✔
1732
    }
244✔
1733

102✔
1734
    /**
102✔
1735
     *
102✔
1736
     * performs a call to ```asyncRefresh``` on all variable nodes that provide an async refresh func.
102✔
1737
     *
102✔
1738
     * @param nodesToRefresh {Array<ReadValueId|HistoryReadValueId>}  an array containing the node to consider
102✔
1739
     * Each element of the array shall be of the form { nodeId: <xxx>, attributeIds: <value> }.
102✔
1740
     * @param maxAge {number}  the maximum age of the value to be read, in milliseconds.
102✔
1741
     * @param callback
102✔
1742
     *
102✔
1743
     */
102✔
1744
    public refreshValues(
102✔
1745
        nodesToRefresh: ReadValueId[] | HistoryReadValueId[],
47,353✔
1746
        maxAge: number,
47,353✔
1747
        /**
47,353✔
1748
         * @param err
47,353✔
1749
         * @param dataValues an array containing value read
47,353✔
1750
         * The array length matches the number of  nodeIds that are candidate for an
47,353✔
1751
         * async refresh (i.e: nodes that are of type Variable with asyncRefresh func }
47,353✔
1752
         */
47,353✔
1753
        callback: (err: Error | null, dataValues?: DataValue[]) => void
47,353✔
1754
    ): void {
47,353✔
1755
        const referenceTime = getCurrentClock();
47,353✔
1756
        maxAge && referenceTime.timestamp.setTime(referenceTime.timestamp.getTime() - maxAge);
47,353✔
1757

47,353✔
1758
        assert(typeof callback === "function");
47,353✔
1759

47,353✔
1760
        const nodeMap: Record<string, UAVariable> = {};
47,353✔
1761
        for (const nodeToRefresh of nodesToRefresh) {
47,353✔
1762
            // only consider node  for which the caller wants to read the Value attribute
494,291✔
1763
            // assuming that Value is requested if attributeId is missing,
494,291✔
1764
            if (nodeToRefresh instanceof ReadValueId && nodeToRefresh.attributeId !== AttributeIds.Value) {
494,291✔
1765
                continue;
398,656✔
1766
            }
398,656✔
1767
            // ... and that are valid object and instances of Variables ...
95,673✔
1768
            const uaNode = this.addressSpace!.findNode(nodeToRefresh.nodeId);
95,673✔
1769
            if (!uaNode || !(uaNode.nodeClass === NodeClass.Variable)) {
494,291✔
1770
                continue;
1,058✔
1771
            }
1,058✔
1772
            // ... and that have been declared as asynchronously updating
94,615✔
1773
            if (typeof (uaNode as any).refreshFunc !== "function") {
494,291✔
1774
                continue;
56,561✔
1775
            }
56,561✔
1776
            const key = uaNode.nodeId.toString();
38,078✔
1777
            if (nodeMap[key]) {
494,291✔
1778
                continue;
2✔
1779
            }
2✔
1780
            nodeMap[key] = uaNode as UAVariable;
38,078✔
1781
        }
38,014✔
1782

47,353✔
1783
        const uaVariableArray = Object.values(nodeMap);
47,353✔
1784
        if (uaVariableArray.length === 0) {
47,353✔
1785
            // nothing to do
28,037✔
1786
            return callback(null, []);
28,037✔
1787
        }
28,037✔
1788
        // perform all asyncRefresh in parallel
19,342✔
1789
        async.map(
19,342✔
1790
            uaVariableArray,
19,316✔
1791
            (uaVariable: UAVariable, inner_callback: CallbackT<DataValue>) => {
19,316✔
1792
                try {
38,014✔
1793
                    uaVariable.asyncRefresh(referenceTime, (err, dataValue) => {
38,014✔
1794
                        inner_callback(err, dataValue);
38,014✔
1795
                    });
38,014✔
1796
                } catch (err) {
38,014!
1797
                    const _err = err as Error;
×
1798
                    errorLog("asyncRefresh internal error", _err.message);
×
1799
                    inner_callback(_err);
×
UNCOV
1800
                }
×
1801
            },
19,318✔
1802
            (err?: Error | null, arrResult?: (DataValue | undefined)[]) => {
19,316✔
1803
                callback(err || null, arrResult as DataValue[]);
19,316✔
1804
            }
19,316✔
1805
        );
19,316✔
1806
    }
19,342✔
1807

102✔
1808
    private _exposeSubscriptionDiagnostics(subscription: Subscription): void {
102✔
1809
        try {
914✔
1810
            debugLog("ServerEngine#_exposeSubscriptionDiagnostics", subscription.subscriptionId);
914✔
1811
            const subscriptionDiagnosticsArray = this._getServerSubscriptionDiagnosticsArrayNode();
914✔
1812
            const subscriptionDiagnostics = subscription.subscriptionDiagnostics;
914✔
1813
            assert((subscriptionDiagnostics as any).$subscription === subscription);
914✔
1814
            assert(subscriptionDiagnostics instanceof SubscriptionDiagnosticsDataType);
914✔
1815

914✔
1816
            if (subscriptionDiagnostics && subscriptionDiagnosticsArray) {
914✔
1817
                addElement(subscriptionDiagnostics, subscriptionDiagnosticsArray);
910✔
1818
            }
910✔
1819
        } catch (err) {
914!
1820
            errorLog("_exposeSubscriptionDiagnostics err", err);
×
UNCOV
1821
        }
×
1822
    }
914✔
1823

102✔
1824
    protected _unexposeSubscriptionDiagnostics(subscription: Subscription): void {
102✔
1825
        const serverSubscriptionDiagnosticsArray = this._getServerSubscriptionDiagnosticsArrayNode();
914✔
1826
        const subscriptionDiagnostics = subscription.subscriptionDiagnostics;
914✔
1827
        assert(subscriptionDiagnostics instanceof SubscriptionDiagnosticsDataType);
914✔
1828
        if (subscriptionDiagnostics && serverSubscriptionDiagnosticsArray) {
914✔
1829
            const node = (serverSubscriptionDiagnosticsArray as any)[subscription.id];
910✔
1830
            removeElement(serverSubscriptionDiagnosticsArray, (a) => a.subscriptionId === subscription.id);
910✔
1831
            /*assert(
910✔
1832
                !(subscriptionDiagnosticsArray as any)[subscription.id],
910✔
1833
                " subscription node must have been removed from subscriptionDiagnosticsArray"
910✔
1834
            );
910✔
1835
            */
910✔
1836
        }
910✔
1837
        debugLog("ServerEngine#_unexposeSubscriptionDiagnostics", subscription.subscriptionId);
914✔
1838
    }
914✔
1839

102✔
1840
    /**
102✔
1841
     * create a new subscription
102✔
1842
     * @return {Subscription}
102✔
1843
     */
102✔
1844
    public _createSubscriptionOnSession(session: ServerSession, request: CreateSubscriptionRequestLike): Subscription {
102✔
1845
        assert(Object.prototype.hasOwnProperty.call(request, "requestedPublishingInterval")); // Duration
914✔
1846
        assert(Object.prototype.hasOwnProperty.call(request, "requestedLifetimeCount")); // Counter
914✔
1847
        assert(Object.prototype.hasOwnProperty.call(request, "requestedMaxKeepAliveCount")); // Counter
914✔
1848
        assert(Object.prototype.hasOwnProperty.call(request, "maxNotificationsPerPublish")); // Counter
914✔
1849
        assert(Object.prototype.hasOwnProperty.call(request, "publishingEnabled")); // Boolean
914✔
1850
        assert(Object.prototype.hasOwnProperty.call(request, "priority")); // Byte
914✔
1851

914✔
1852
        // adjust publishing parameters
914✔
1853
        const publishingInterval = request.requestedPublishingInterval || 0;
914✔
1854
        const maxKeepAliveCount = request.requestedMaxKeepAliveCount || 0;
914✔
1855
        const lifeTimeCount = request.requestedLifetimeCount || 0;
914✔
1856

914✔
1857
        const subscription = new Subscription({
914✔
1858
            id: _get_next_subscriptionId(),
914✔
1859
            lifeTimeCount,
914✔
1860
            maxKeepAliveCount,
914✔
1861
            maxNotificationsPerPublish: request.maxNotificationsPerPublish,
914✔
1862
            priority: request.priority || 0,
914✔
1863
            publishEngine: session.publishEngine as any, //
914✔
1864
            publishingEnabled: request.publishingEnabled,
914✔
1865
            publishingInterval,
914✔
1866
            // -------------------
914✔
1867
            sessionId: NodeId.nullNodeId,
914✔
1868
            globalCounter: this._globalCounter,
914✔
1869
            serverCapabilities: this.serverCapabilities // shared
914✔
1870
        });
914✔
1871

914✔
1872
        // add subscriptionDiagnostics
914✔
1873
        this._exposeSubscriptionDiagnostics(subscription);
914✔
1874

914✔
1875
        assert((subscription.publishEngine as any) === session.publishEngine);
914✔
1876
        session.publishEngine.add_subscription(subscription);
914✔
1877

914✔
1878
        // eslint-disable-next-line @typescript-eslint/no-this-alias
914✔
1879
        const engine = this;
914✔
1880
        subscription.once("terminated", function (this: Subscription) {
914✔
1881
            engine._unexposeSubscriptionDiagnostics(this);
914✔
1882
        });
914✔
1883

914✔
1884
        return subscription;
914✔
1885
    }
914✔
1886

102✔
1887
    /**
102✔
1888
     */
102✔
1889
    private __internal_bindMethod(nodeId: NodeId, func: MethodFunctor) {
102✔
1890
        assert(typeof func === "function");
2,448✔
1891
        assert(nodeId instanceof NodeId);
2,448✔
1892

2,448✔
1893
        const methodNode = this.addressSpace!.findNode(nodeId)! as UAMethod;
2,448✔
1894
        if (!methodNode) {
2,448✔
1895
            return;
386✔
1896
        }
386✔
1897
        if (methodNode && methodNode.bindMethod) {
2,448✔
1898
            methodNode.bindMethod(func);
2,062✔
1899
        }
2,062✔
1900
        /* c8 ignore next */
4✔
1901
        else {
4✔
UNCOV
1902
            warningLog(
×
UNCOV
1903
                chalk.yellow("WARNING:  cannot bind a method with id ") +
×
UNCOV
1904
                chalk.cyan(nodeId.toString()) +
×
UNCOV
1905
                chalk.yellow(". please check your nodeset.xml file or add this node programmatically")
×
UNCOV
1906
            );
×
UNCOV
1907
            warningLog(traceFromThisProjectOnly());
×
UNCOV
1908
        }
×
1909
    }
2,448✔
1910

102✔
1911
    private _getServerSubscriptionDiagnosticsArrayNode(): UADynamicVariableArray<SubscriptionDiagnosticsDataType> | null {
102✔
1912
        // c8 ignore next
1,828✔
1913
        if (!this.addressSpace) {
1,828!
UNCOV
1914
            doDebug && debugLog("ServerEngine#_getServerSubscriptionDiagnosticsArray : no addressSpace");
×
UNCOV
1915

×
UNCOV
1916
            return null; // no addressSpace
×
UNCOV
1917
        }
×
1918
        const subscriptionDiagnosticsType = this.addressSpace.findVariableType("SubscriptionDiagnosticsType");
1,828✔
1919
        if (!subscriptionDiagnosticsType) {
1,828✔
1920
            doDebug &&
8!
1921
                debugLog("ServerEngine#_getServerSubscriptionDiagnosticsArray " + ": cannot find SubscriptionDiagnosticsType");
8✔
1922
        }
8✔
1923

1,828✔
1924
        // SubscriptionDiagnosticsArray = i=2290
1,828✔
1925
        const subscriptionDiagnosticsArrayNode = this.addressSpace.findNode(
1,828✔
1926
            makeNodeId(VariableIds.Server_ServerDiagnostics_SubscriptionDiagnosticsArray)
1,828✔
1927
        )!;
1,828✔
1928

1,828✔
1929
        return subscriptionDiagnosticsArrayNode as UADynamicVariableArray<SubscriptionDiagnosticsDataType>;
1,828✔
1930
    }
1,828✔
1931
}
102!
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc