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

node-opcua / node-opcua / 23088660581

14 Mar 2026 01:13PM UTC coverage: 92.7% (-0.06%) from 92.756%
23088660581

push

github

erossignon
chore: resolve biome lint issues with proper types

ua_data_type_impl.ts:
- Remove dead $isUnion field (set but never read)
- Move _extensionObjectConstructor into class
  (eliminates declaration merging)
- Type enumStrings/enumValues as UAVariable
- Type enum callbacks with LocalizedText/EnumValueType
- Replace `as any` with `as unknown` in subtypeOfObj
- Use type guard `obj is BaseNode` in filter chain
- Remove non-null assertions with null guards

load_nodeset2.ts:
- Type pendingSimpleTypeToRegister with proper
  interface { name, dataTypeNodeId }
- Type capturedValue as VariantOptions | undefined
- Refactor capturedVariable and capturedDataTypeNode
  to T | undefined with null guards for GC release
- Replace (v as any).value.value with as unknown cast
- Convert .forEach to for..of (useIterableCallbackReturn)
- Add biome-ignore only for xml2json this: any callbacks
  (inherent to the library's dynamic binding pattern)

test_datatype_definition.ts:
- Replace non-null assertion with null guard

18192 of 21552 branches covered (84.41%)

79 of 84 new or added lines in 2 files covered. (94.05%)

826 existing lines in 17 files now uncovered.

160976 of 173652 relevant lines covered (92.7%)

443633.19 hits per line

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

90.05
/packages/node-opcua-server/source/server_engine.ts
1
/**
1✔
2
 * @module node-opcua-server
2✔
3
 */
2✔
4
import { EventEmitter } from "node:events";
2✔
5
import { types } from "node:util";
2✔
6

2✔
7
import async from "async";
2✔
8
import chalk from "chalk";
2✔
9
import {
2✔
10
    AddressSpace,
2✔
11
    addElement,
2✔
12
    type BindVariableOptions,
2✔
13
    bindExtObjArrayNode,
2✔
14
    type DTServerStatus,
2✔
15
    ensureObjectIsSecure,
2✔
16
    type IServerBase,
2✔
17
    type ISessionContext,
2✔
18
    type MethodFunctor,
2✔
19
    removeElement,
2✔
20
    type SessionContext,
2✔
21
    type UADynamicVariableArray,
2✔
22
    type UAMethod,
2✔
23
    type UAObject,
2✔
24
    type UAServerDiagnostics,
2✔
25
    type UAServerDiagnosticsSummary,
2✔
26
    type UAServerStatus,
2✔
27
    type UAVariable
2✔
28
} from "node-opcua-address-space";
2✔
29
import { generateAddressSpace } from "node-opcua-address-space/nodeJS";
2✔
30
import { assert } from "node-opcua-assert";
2✔
31
import type { UInt32 } from "node-opcua-basic-types";
2✔
32
import { BinaryStream } from "node-opcua-binary-stream";
2✔
33
import type { CreateSubscriptionRequestLike } from "node-opcua-client";
2✔
34
import {
2✔
35
    ServerDiagnosticsSummaryDataType,
2✔
36
    ServerState,
2✔
37
    ServerStatusDataType,
2✔
38
    SubscriptionDiagnosticsDataType
2✔
39
} from "node-opcua-common";
2✔
40
import { DataTypeIds, MethodIds, ObjectIds, VariableIds } from "node-opcua-constants";
2✔
41
import { AttributeIds, coerceLocalizedText, type LocalizedTextLike, makeAccessLevelFlag, NodeClass } from "node-opcua-data-model";
2✔
42
import type { DataValue } from "node-opcua-data-value";
2✔
43
import { getCurrentClock, getMinOPCUADate } from "node-opcua-date-time";
2✔
44
import { checkDebugFlag, make_debugLog, make_errorLog, make_warningLog, traceFromThisProjectOnly } from "node-opcua-debug";
2✔
45
import { coerceNodeId, makeNodeId, NodeId, type NodeIdLike, NodeIdType, resolveNodeId } from "node-opcua-nodeid";
2✔
46
import { nodesets } from "node-opcua-nodesets";
2✔
47
import { ObjectRegistry } from "node-opcua-object-registry";
2✔
48
import type { BrowseResult } from "node-opcua-service-browse";
2✔
49
import { CallMethodResult } from "node-opcua-service-call";
2✔
50
import { ApplicationDescription } from "node-opcua-service-endpoints";
2✔
51
import type { HistoryReadRequest, HistoryReadResult, HistoryReadValueId } from "node-opcua-service-history";
2✔
52
import { TransferResult } from "node-opcua-service-subscription";
2✔
53
import { type CallbackT, type StatusCode, StatusCodes } from "node-opcua-status-code";
2✔
54
import {
2✔
55
    ApplicationType,
2✔
56
    type BrowseDescription,
2✔
57
    type BrowseDescriptionOptions,
2✔
58
    type BrowsePath,
2✔
59
    type BrowsePathResult,
2✔
60
    type BuildInfo,
2✔
61
    type BuildInfoOptions,
2✔
62
    type CallMethodRequest,
2✔
63
    type CallMethodResultOptions,
2✔
64
    ProgramDiagnosticDataType,
2✔
65
    type ReadRequestOptions,
2✔
66
    ReadValueId,
2✔
67
    type SessionDiagnosticsDataType,
2✔
68
    type SessionSecurityDiagnosticsDataType,
2✔
69
    TimeZoneDataType,
2✔
70
    type WriteValue
2✔
71
} from "node-opcua-types";
2✔
72
import { DataType, isValidVariant, Variant, VariantArrayType } from "node-opcua-variant";
2✔
73
import { AddressSpaceAccessor } from "./addressSpace_accessor";
2✔
74
import { HistoryServerCapabilities, type HistoryServerCapabilitiesOptions } from "./history_server_capabilities";
2✔
75
import type { IAddressSpaceAccessor } from "./i_address_space_accessor";
2✔
76
import { MonitoredItem } from "./monitored_item";
2✔
77
import type { OPCUAServerOptions } from "./opcua_server";
2✔
78
import {
2✔
79
    defaultServerCapabilities,
2✔
80
    ServerCapabilities,
2✔
81
    type ServerCapabilitiesOptions,
2✔
82
    type ServerOperationLimits
2✔
83
} from "./server_capabilities";
2✔
84
import { ServerSidePublishEngine } from "./server_publish_engine";
2✔
85
import { ServerSidePublishEngineForOrphanSubscription } from "./server_publish_engine_for_orphan_subscriptions";
2✔
86
import { ServerSession } from "./server_session";
2✔
87
import { Subscription } from "./server_subscription";
2✔
88
import { sessionsCompatibleForTransfer } from "./sessions_compatible_for_transfer";
2✔
89

2✔
90
const debugLog = make_debugLog(__filename);
2✔
91
const errorLog = make_errorLog(__filename);
2✔
92
const warningLog = make_warningLog(__filename);
2✔
93
const doDebug = checkDebugFlag(__filename);
2✔
94

2✔
95
function upperCaseFirst(str: string) {
3,780✔
96
    return str.slice(0, 1).toUpperCase() + str.slice(1);
3,780✔
97
}
3,780✔
98

2✔
99
async function shutdownAndDisposeAddressSpace(this: ServerEngine) {
318✔
100
    if (this.addressSpace) {
318✔
101
        await this.addressSpace.shutdown();
315✔
102
        this.addressSpace.dispose();
315✔
103
        this.addressSpace = null;
315✔
104
    }
315✔
105
}
318✔
106

2✔
107
function setSubscriptionDurable(
×
108
    this: ServerEngine,
×
109
    inputArguments: Variant[],
×
110
    context: ISessionContext,
×
111
    callback: CallbackT<CallMethodResultOptions>
×
112
) {
×
113
    // see https://reference.opcfoundation.org/v104/Core/docs/Part5/9.3/
×
114
    // https://reference.opcfoundation.org/v104/Core/docs/Part4/6.8/
×
115
    assert(typeof callback === "function");
×
116

×
117
    const data = _getSubscription.call(this, inputArguments, context);
×
118
    if (data.statusCode) return callback(null, { statusCode: data.statusCode });
×
119
    const { subscription } = data;
×
120

×
121
    const lifetimeInHours = inputArguments[1].value as UInt32;
×
122
    if (subscription.monitoredItemCount > 0) {
×
123
        // This is returned when a Subscription already contains MonitoredItems.
×
124
        return callback(null, { statusCode: StatusCodes.BadInvalidState });
×
125
    }
×
126

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

×
153
    const highestLifetimeInHours = 24 * 100;
×
154

×
155
    const revisedLifetimeInHours =
×
156
        lifetimeInHours === 0 ? highestLifetimeInHours : Math.max(1, Math.min(lifetimeInHours, highestLifetimeInHours));
×
157

×
158
    // also adjust subscription life time
×
159
    const currentLifeTimeInHours = (subscription.lifeTimeCount * subscription.publishingInterval) / (1000 * 60 * 60);
×
160
    if (currentLifeTimeInHours < revisedLifetimeInHours) {
×
161
        const requestedLifetimeCount = Math.ceil((revisedLifetimeInHours * (1000 * 60 * 60)) / subscription.publishingInterval);
×
162

×
163
        subscription.modify({
×
164
            requestedMaxKeepAliveCount: subscription.maxKeepAliveCount,
×
165
            requestedPublishingInterval: subscription.publishingInterval,
×
166
            maxNotificationsPerPublish: subscription.maxNotificationsPerPublish,
×
167
            priority: subscription.priority,
×
168
            requestedLifetimeCount
×
169
        });
×
170
    }
×
171

×
172
    const callMethodResult = new CallMethodResult({
×
UNCOV
173
        statusCode: StatusCodes.Good,
×
UNCOV
174
        outputArguments: [{ dataType: DataType.UInt32, arrayType: VariantArrayType.Scalar, value: revisedLifetimeInHours }]
×
UNCOV
175
    });
×
UNCOV
176
    callback(null, callMethodResult);
×
UNCOV
177
}
×
178

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

1✔
193
    return callback(null, { statusCode: StatusCodes.BadNotImplemented });
1✔
194
}
1✔
195

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

3✔
227
    const data = _getSubscription.call(this, inputArguments, context);
3✔
228
    if (data.statusCode) {
3!
UNCOV
229
        callback(null, { statusCode: data.statusCode });
×
UNCOV
230
        return;
×
UNCOV
231
    }
×
232
    const { subscription } = data;
3✔
233

3✔
234
    subscription
3✔
235
        .resendInitialValues()
3✔
236
        .then(() => {
3✔
237
            callback(null, { statusCode: StatusCodes.Good });
3✔
238
        })
3✔
239
        .catch((err) => callback(err));
3✔
240
}
3✔
241

2✔
242
// binding methods
2✔
243
function getMonitoredItemsId(
60✔
244
    this: ServerEngine,
60✔
245
    inputArguments: Variant[],
60✔
246
    context: ISessionContext,
60✔
247
    callback: CallbackT<CallMethodResultOptions>
60✔
248
) {
60✔
249
    assert(typeof callback === "function");
60✔
250

60✔
251
    const data = _getSubscription.call(this, inputArguments, context);
60✔
252
    if (data.statusCode) return callback(null, { statusCode: data.statusCode });
60✔
253
    const { subscription } = data;
7✔
254

7✔
255
    const result = subscription.getMonitoredItems();
7✔
256
    assert(result.statusCode);
7✔
257
    assert(result.serverHandles.length === result.clientHandles.length);
7✔
258
    const callMethodResult = new CallMethodResult({
7✔
259
        statusCode: result.statusCode,
7✔
260
        outputArguments: [
7✔
261
            { dataType: DataType.UInt32, arrayType: VariantArrayType.Array, value: result.serverHandles },
7✔
262
            { dataType: DataType.UInt32, arrayType: VariantArrayType.Array, value: result.clientHandles }
7✔
263
        ]
7✔
264
    });
7✔
265
    callback(null, callMethodResult);
7✔
266
}
7✔
267

2✔
268
function __bindVariable(self: ServerEngine, nodeId: NodeIdLike, options?: BindVariableOptions) {
15,596✔
269
    options = options || {};
15,596!
270

15,596✔
271
    if (!self.addressSpace) {
15,596!
272
        return;
×
273
    }
×
274
    const variable = self.addressSpace.findNode(nodeId) as UAVariable;
15,596✔
275
    if (variable?.bindVariable) {
15,596✔
276
        variable.bindVariable(options, true);
15,596✔
277
        assert(typeof variable.asyncRefresh === "function");
15,596✔
278
        assert(typeof (variable as unknown as Record<string, unknown>).refreshFunc === "function");
15,596✔
279
    } else {
15,596!
UNCOV
280
        warningLog(
×
UNCOV
281
            "Warning: cannot bind object with id ",
×
UNCOV
282
            nodeId.toString(),
×
UNCOV
283
            " please check your nodeset.xml file or add this node programmatically"
×
UNCOV
284
        );
×
285
    }
×
286
}
15,596✔
287

2✔
288
// note OPCUA 1.03 part 4 page 76
2✔
289
// The Server-assigned identifier for the Subscription (see 7.14 for IntegerId definition). This identifier shall
2✔
290
// be unique for the entire Server, not just for the Session, in order to allow the Subscription to be transferred
2✔
291
// to another Session using the TransferSubscriptions service.
2✔
292
// After Server start-up the generation of subscriptionIds should start from a random IntegerId or continue from
2✔
293
// the point before the restart.
2✔
294
let next_subscriptionId = Math.ceil(Math.random() * 1000000);
2✔
295
export function setNextSubscriptionId(n: number) {
1✔
UNCOV
296
    next_subscriptionId = Math.max(n, 1);
×
UNCOV
297
}
×
298
function _get_next_subscriptionId() {
457✔
299
    debugLog(" next_subscriptionId = ", next_subscriptionId);
457✔
300
    return next_subscriptionId++;
457✔
301
}
457✔
302

2✔
303
export type StringGetter = () => string;
2✔
304
export type StringArrayGetter = () => string[];
2✔
305
export type ApplicationTypeGetter = () => ApplicationType;
2✔
306
export type BooleanGetter = () => boolean;
2✔
307

2✔
308
export interface ServerConfigurationOptions {
2✔
309
    applicationUri?: string | StringGetter;
2✔
310
    applicationType?: ApplicationType | ApplicationTypeGetter; // default "Server"
2✔
311

2✔
312
    hasSecureElement?: boolean | BooleanGetter; // default true
2✔
313

2✔
314
    multicastDnsEnabled?: boolean | BooleanGetter; // default true
2✔
315

2✔
316
    productUri?: string | StringGetter;
2✔
317

2✔
318
    // /** @restricted only in professional version */
2✔
319
    // resetToServerDefaults: () => Promise<void>;
2✔
320
    // /** @restricted only in professional version */
2✔
321
    // setAdminPassword?: (password: string) => Promise<void>;
2✔
322

2✔
323
    /**
2✔
324
     * The SupportedPrivateKeyFormats specifies the PrivateKey formats supported by the Server.
2✔
325
     * Possible values include “PEM” (see RFC 5958) or “PFX” (see PKCS #12).
2✔
326
     * @default ["PEM"]
2✔
327
     */
2✔
328
    supportedPrivateKeyFormat: string[] | StringArrayGetter;
2✔
329

2✔
330
    /**
2✔
331
     * The ServerCapabilities Property specifies the capabilities from Annex D
2✔
332
     * ( see https://reference.opcfoundation.org/GDS/v104/docs/D)  which the Server supports. The value is
2✔
333
     * the same as the value reported to the LocalDiscoveryServer when the Server calls the RegisterServer2 Service.
2✔
334
     */
2✔
335
    serverCapabilities?: string[] | StringArrayGetter; // default|"N/A"]
2✔
336
}
2✔
337
export interface ServerEngineOptions {
2✔
338
    applicationUri: string | StringGetter;
2✔
339

2✔
340
    buildInfo?: BuildInfoOptions;
2✔
341
    isAuditing?: boolean;
2✔
342
    /**
2✔
343
     * set to true to enable serverDiagnostics
2✔
344
     */
2✔
345
    serverDiagnosticsEnabled?: boolean;
2✔
346
    serverCapabilities?: ServerCapabilitiesOptions;
2✔
347
    historyServerCapabilities?: HistoryServerCapabilitiesOptions;
2✔
348
    serverConfiguration?: ServerConfigurationOptions;
2✔
349
}
2✔
350

2✔
351
export interface CreateSessionOption {
2✔
352
    clientDescription?: ApplicationDescription;
2✔
353
    sessionTimeout?: number;
2✔
354
    server?: IServerBase;
2✔
355
}
2✔
356

2✔
357
export type ClosingReason = "Timeout" | "Terminated" | "CloseSession" | "Forcing";
2✔
358

2✔
359
export type ServerEngineShutdownTask = (this: ServerEngine) => void | Promise<void>;
2✔
360

2✔
361
/**
2✔
362
 *
2✔
363
 */
2✔
364
export class ServerEngine extends EventEmitter implements IAddressSpaceAccessor {
2✔
365
    public static readonly registry = new ObjectRegistry();
53✔
366

53✔
367
    public isAuditing: boolean;
53✔
368
    public serverDiagnosticsSummary: ServerDiagnosticsSummaryDataType;
53✔
369
    public serverDiagnosticsEnabled: boolean;
53✔
370
    public serverCapabilities: ServerCapabilities;
53✔
371
    public historyServerCapabilities: HistoryServerCapabilities;
53✔
372
    public serverConfiguration: ServerConfigurationOptions;
53✔
373
    public clientDescription?: ApplicationDescription;
53✔
374

53✔
375
    public addressSpace: AddressSpace | null;
53✔
376
    public addressSpaceAccessor: IAddressSpaceAccessor | null = null;
53✔
377

53✔
378
    // pseudo private
53✔
379
    public _internalState: "creating" | "initializing" | "initialized" | "shutdown" | "disposed";
53✔
380

53✔
381
    private _sessions: { [key: string]: ServerSession };
53✔
382
    private _closedSessions: { [key: string]: ServerSession };
53✔
383
    private _orphanPublishEngine?: ServerSidePublishEngineForOrphanSubscription;
53✔
384
    private _shutdownTasks: ServerEngineShutdownTask[];
53✔
385
    private _applicationUri: string;
53✔
386
    private _expectedShutdownTime!: Date;
53✔
387
    private _serverStatus: ServerStatusDataType;
53✔
388
    private _globalCounter: { totalMonitoredItemCount: number } = { totalMonitoredItemCount: 0 };
53✔
389

53✔
390
    constructor(options?: ServerEngineOptions) {
53✔
391
        super();
317✔
392

317✔
393
        options = options || ({ applicationUri: "" } as ServerEngineOptions);
317✔
394
        options.buildInfo = options.buildInfo || {};
317✔
395

317✔
396
        ServerEngine.registry.register(this);
317✔
397

317✔
398
        this._sessions = {};
317✔
399
        this._closedSessions = {};
317✔
400
        this._orphanPublishEngine = undefined; // will be constructed on demand
317✔
401

317✔
402
        this.isAuditing = typeof options.isAuditing === "boolean" ? options.isAuditing : false;
317✔
403

317✔
404
        options.buildInfo.buildDate = options.buildInfo.buildDate || new Date();
317✔
405
        // ---------------------------------------------------- ServerStatusDataType
317✔
406
        this._serverStatus = new ServerStatusDataType({
317✔
407
            buildInfo: options.buildInfo,
317✔
408
            currentTime: new Date(),
317✔
409
            secondsTillShutdown: 0,
317✔
410
            shutdownReason: { text: "" },
317✔
411
            startTime: new Date(),
317✔
412
            state: ServerState.NoConfiguration
317✔
413
        });
317✔
414

317✔
415
        // --------------------------------------------------- ServerCapabilities
317✔
416
        options.serverCapabilities = options.serverCapabilities || {};
317✔
417

317✔
418
        options.serverConfiguration = options.serverConfiguration || {
317✔
419
            supportedPrivateKeyFormat: ["PEM"]
36✔
420
        };
36✔
421

317✔
422
        // https://profiles.opcfoundation.org/profile
317✔
423
        options.serverCapabilities.serverProfileArray = options.serverCapabilities.serverProfileArray || [
317✔
424
            "http://opcfoundation.org/UA-Profile/Server/Standard", // Standard UA Server Profile",
317✔
425
            "http://opcfoundation.org/UA-Profile/Server/DataAccess",
317✔
426
            "http://opcfoundation.org/UA-Profile/Server/ComplexTypes2017",
317✔
427
            "http://opcfoundation.org/UA-Profile/Server/Events",
317✔
428
            "http://opcfoundation.org/UA-Profile/Client/HistoricalAccess",
317✔
429
            "http://opcfoundation.org/UA-Profile/Server/Methods",
317✔
430
            "http://opcfoundation.org/UA-Profile/Server/StandardEventSubscription",
317✔
431
            "http://opcfoundation.org/UA-Profile/Transport/uatcp-uasc-uabinary",
317✔
432
            "http://opcfoundation.org/UA-Profile/Server/FileAccess",
317✔
433
            "http://opcfoundation.org/UA-Profile/Server/StateMachine"
317✔
434

317✔
435
            // "http://opcfoundation.org/UA-Profile/Transport/wss-uajson",
317✔
436
            // "http://opcfoundation.org/UA-Profile/Transport/wss-uasc-uabinary"
317✔
437
            // "http://opcfoundation.org/UA-Profile/Server/DurableSubscription"
317✔
438

317✔
439
            // "http://opcfoundation.org/UA-Profile/Server/ReverseConnect",
317✔
440
            // "http://opcfoundation.org/UAProfile/Server/NodeManagement",
317✔
441

317✔
442
            //  "Embedded UA Server Profile",
317✔
443
            // "Micro Embedded Device Server Profile",
317✔
444
            // "Nano Embedded Device Server Profile"
317✔
445
        ];
317✔
446
        options.serverCapabilities.localeIdArray = options.serverCapabilities.localeIdArray || ["en-EN", "fr-FR"];
317✔
447

317✔
448
        this.serverCapabilities = new ServerCapabilities(options.serverCapabilities);
317✔
449

317✔
450
        // to do when spec is clear about what goes here!
317✔
451
        // spec 1.04 says (in Part 4 7.33 SignedSoftwareCertificate
317✔
452
        // Note: Details on SoftwareCertificates need to be defined in a future version.
317✔
453
        this.serverCapabilities.softwareCertificates = [
317✔
454
            // new SignedSoftwareCertificate({})
317✔
455
        ];
317✔
456

317✔
457
        // make sure minSupportedSampleRate matches MonitoredItem.minimumSamplingInterval
317✔
458
        Object.defineProperty(this.serverCapabilities, "minSupportedSampleRate", {
317✔
459
            get: () => options.serverCapabilities?.minSupportedSampleRate || MonitoredItem.minimumSamplingInterval,
317✔
460
            configurable: true
317✔
461
        });
317✔
462

317✔
463
        this.serverConfiguration = options.serverConfiguration;
317✔
464

317✔
465
        this.historyServerCapabilities = new HistoryServerCapabilities(options.historyServerCapabilities);
317✔
466

317✔
467
        // --------------------------------------------------- serverDiagnosticsSummary extension Object
317✔
468
        this.serverDiagnosticsSummary = new ServerDiagnosticsSummaryDataType();
317✔
469
        assert(Object.prototype.hasOwnProperty.call(this.serverDiagnosticsSummary, "currentSessionCount"));
317✔
470

317✔
471
        // note spelling is different for serverDiagnosticsSummary.currentSubscriptionCount
317✔
472
        //      and sessionDiagnostics.currentSubscriptionsCount ( with an s)
317✔
473
        assert(Object.prototype.hasOwnProperty.call(this.serverDiagnosticsSummary, "currentSubscriptionCount"));
317✔
474

317✔
475
        Object.defineProperty(this.serverDiagnosticsSummary, "currentSubscriptionCount", {
317✔
476
            get: () => {
317✔
477
                // currentSubscriptionCount returns the total number of subscriptions
4,124✔
478
                // that are currently active on all sessions
4,124✔
479
                let counter = 0;
4,124✔
480
                Object.values(this._sessions).forEach((session: ServerSession) => {
4,124✔
481
                    counter += session.currentSubscriptionCount;
5,984✔
482
                });
4,124✔
483
                // we also need to add the orphan subscriptions
4,124✔
484
                counter += this._orphanPublishEngine ? this._orphanPublishEngine.subscriptions.length : 0;
4,124✔
485
                return counter;
4,124✔
486
            },
385✔
487
            configurable: true
317✔
488
        });
317✔
489

317✔
490
        this._internalState = "creating";
317✔
491

317✔
492
        this.setServerState(ServerState.NoConfiguration);
317✔
493

317✔
494
        this.addressSpace = null;
317✔
495

317✔
496
        this._shutdownTasks = [];
317✔
497

317✔
498
        this._applicationUri = "";
317✔
499
        if (typeof options.applicationUri === "function") {
317✔
500
            Object.defineProperty(this, "_applicationUri", {
281✔
501
                get: options.applicationUri,
281✔
502
                configurable: true
281✔
503
            });
281✔
504
        } else {
317✔
505
            this._applicationUri = options.applicationUri || "<unset _applicationUri>";
36✔
506
        }
36✔
507

317✔
508
        options.serverDiagnosticsEnabled = Object.prototype.hasOwnProperty.call(options, "serverDiagnosticsEnable")
317✔
509
            ? options.serverDiagnosticsEnabled
265!
510
            : true;
317✔
511

317✔
512
        this.serverDiagnosticsEnabled = options.serverDiagnosticsEnabled || false;
317!
513
    }
317✔
514
    public isStarted(): boolean {
53✔
515
        return !!this._serverStatus;
281✔
516
    }
281✔
517

53✔
518
    public dispose(): void {
53✔
519
        this.addressSpace = null;
617✔
520

617✔
521
        assert(Object.keys(this._sessions).length === 0, "ServerEngine#_sessions not empty");
617✔
522
        this._sessions = {};
617✔
523

617✔
524
        // todo fix me
617✔
525
        this._closedSessions = {};
617✔
526
        assert(Object.keys(this._closedSessions).length === 0, "ServerEngine#_closedSessions not empty");
617✔
527
        this._closedSessions = {};
617✔
528

617✔
529
        if (this._orphanPublishEngine) {
617✔
530
            this._orphanPublishEngine.dispose();
9✔
531
            this._orphanPublishEngine = undefined;
9✔
532
        }
9✔
533

617✔
534
        this._shutdownTasks = [];
617✔
535
        this._serverStatus = null as unknown as ServerStatusDataType;
617✔
536
        this._internalState = "disposed";
617✔
537
        this.removeAllListeners();
617✔
538

617✔
539
        ServerEngine.registry.unregister(this);
617✔
540
    }
617✔
541

53✔
542
    public get startTime(): Date {
53✔
UNCOV
543
        return this._serverStatus.startTime || new Date();
×
UNCOV
544
    }
×
545

53✔
546
    public get currentTime(): Date {
53✔
UNCOV
547
        return this._serverStatus.currentTime || new Date();
×
UNCOV
548
    }
×
549

53✔
550
    public get buildInfo(): BuildInfo {
53✔
551
        return this._serverStatus.buildInfo;
283✔
552
    }
283✔
553

53✔
554
    /**
53✔
555
     * register a function that will be called when the server will perform its shut down.
53✔
556
     */
53✔
557
    public registerShutdownTask(task: ServerEngineShutdownTask): void {
53✔
558
        assert(typeof task === "function");
1✔
559
        this._shutdownTasks.push(task);
1✔
560
    }
1✔
561

53✔
562
    /**
53✔
563
     */
53✔
564
    public async shutdown(): Promise<void> {
53✔
565
        debugLog("ServerEngine#shutdown");
318✔
566

318✔
567
        this._internalState = "shutdown";
318✔
568
        this.setServerState(ServerState.Shutdown);
318✔
569

318✔
570
        // delete any existing sessions
318✔
571
        const tokens = Object.keys(this._sessions).map((key: string) => {
318✔
572
            const session = this._sessions[key];
46✔
573
            return session.authenticationToken;
46✔
574
        });
318✔
575

318✔
576
        // delete and close any orphan subscriptions
318✔
577
        if (this._orphanPublishEngine) {
318✔
578
            this._orphanPublishEngine.shutdown();
9✔
579
        }
9✔
580

318✔
581
        for (const token of tokens) {
318✔
582
            this.closeSession(token, true, "Terminated");
46✔
583
        }
46✔
584

318✔
585
        // all sessions must have been terminated
318✔
586
        assert(this.currentSessionCount === 0);
318✔
587

318✔
588
        // all subscriptions must have been terminated
318✔
589
        assert(this.currentSubscriptionCount === 0, "all subscriptions must have been terminated");
318✔
590

318✔
591
        this._shutdownTasks.push(shutdownAndDisposeAddressSpace);
318✔
592

318✔
593
        // perform registerShutdownTask
318✔
594
        for (const task of this._shutdownTasks) {
318✔
595
            await task.call(this);
319✔
596
        }
319✔
597
        this.setServerState(ServerState.Invalid);
318✔
598

318✔
599
        this.dispose();
318✔
600
    }
318✔
601

53✔
602
    /**
53✔
603
     * the number of active sessions
53✔
604
     */
53✔
605
    public get currentSessionCount(): number {
53✔
606
        return this.serverDiagnosticsSummary.currentSessionCount;
3,785✔
607
    }
3,785✔
608

53✔
609
    /**
53✔
610
     * the cumulated number of sessions that have been opened since this object exists
53✔
611
     */
53✔
612
    public get cumulatedSessionCount(): number {
53✔
613
        return this.serverDiagnosticsSummary.cumulatedSessionCount;
5✔
614
    }
5✔
615

53✔
616
    /**
53✔
617
     * the number of active subscriptions.
53✔
618
     */
53✔
619
    public get currentSubscriptionCount(): number {
53✔
620
        return this.serverDiagnosticsSummary.currentSubscriptionCount;
2,039✔
621
    }
2,039✔
622

53✔
623
    /**
53✔
624
     * the cumulated number of subscriptions that have been created since this object exists
53✔
625
     */
53✔
626
    public get cumulatedSubscriptionCount(): number {
53✔
627
        return this.serverDiagnosticsSummary.cumulatedSubscriptionCount;
8✔
628
    }
8✔
629

53✔
630
    public get rejectedSessionCount(): number {
53✔
631
        return this.serverDiagnosticsSummary.rejectedSessionCount;
2✔
632
    }
2✔
633

53✔
634
    public get rejectedRequestsCount(): number {
53✔
635
        return this.serverDiagnosticsSummary.rejectedRequestsCount;
2✔
636
    }
2✔
637

53✔
638
    public get sessionAbortCount(): number {
53✔
639
        return this.serverDiagnosticsSummary.sessionAbortCount;
2✔
640
    }
2✔
641

53✔
642
    public get sessionTimeoutCount(): number {
53✔
643
        return this.serverDiagnosticsSummary.sessionTimeoutCount;
×
644
    }
×
645

53✔
646
    public get publishingIntervalCount(): number {
53✔
647
        return this.serverDiagnosticsSummary.publishingIntervalCount;
2✔
648
    }
2✔
649

53✔
650
    public incrementSessionTimeoutCount(): void {
53✔
651
        if (this.serverDiagnosticsSummary && this.serverDiagnosticsEnabled) {
11✔
652
            // The requests include all Services defined in Part 4 of the OPC UA Specification, also requests to create sessions. This number includes the securityRejectedRequestsCount.
11✔
653
            this.serverDiagnosticsSummary.sessionTimeoutCount += 1;
11✔
654
        }
11✔
655
    }
11✔
656
    public incrementSessionAbortCount(): void {
53✔
UNCOV
657
        if (this.serverDiagnosticsSummary && this.serverDiagnosticsEnabled) {
×
UNCOV
658
            // The requests include all Services defined in Part 4 of the OPC UA Specification, also requests to create sessions. This number includes the securityRejectedRequestsCount.
×
UNCOV
659
            this.serverDiagnosticsSummary.sessionAbortCount += 1;
×
UNCOV
660
        }
×
UNCOV
661
    }
×
662
    public incrementRejectedRequestsCount(): void {
53✔
663
        if (this.serverDiagnosticsSummary && this.serverDiagnosticsEnabled) {
181✔
664
            // The requests include all Services defined in Part 4 of the OPC UA Specification, also requests to create sessions. This number includes the securityRejectedRequestsCount.
181✔
665
            this.serverDiagnosticsSummary.rejectedRequestsCount += 1;
181✔
666
        }
181✔
667
    }
181✔
668

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

53✔
680
    public incrementSecurityRejectedRequestsCount(): void {
53✔
681
        if (this.serverDiagnosticsSummary && this.serverDiagnosticsEnabled) {
57✔
682
            // The requests include all Services defined in Part 4 of the OPC UA Specification, also requests to create sessions. This number includes the securityRejectedRequestsCount.
57✔
683
            this.serverDiagnosticsSummary.securityRejectedRequestsCount += 1;
57✔
684
        }
57✔
685
        this.incrementRejectedRequestsCount();
57✔
686
    }
57✔
687

53✔
688
    /**
53✔
689
     * increment rejected session count (also increment rejected requests count)
53✔
690
     */
53✔
691
    public incrementSecurityRejectedSessionCount(): void {
53✔
692
        if (this.serverDiagnosticsSummary && this.serverDiagnosticsEnabled) {
57✔
693
            // The requests include all Services defined in Part 4 of the OPC UA Specification, also requests to create sessions. This number includes the securityRejectedRequestsCount.
57✔
694
            this.serverDiagnosticsSummary.securityRejectedSessionCount += 1;
57✔
695
        }
57✔
696
        this.incrementSecurityRejectedRequestsCount();
57✔
697
    }
57✔
698

53✔
699
    public setShutdownTime(date: Date): void {
53✔
700
        this._expectedShutdownTime = date;
281✔
701
    }
281✔
702
    public setShutdownReason(reason: LocalizedTextLike): void {
53✔
703
        const localizedReason = coerceLocalizedText(reason);
1✔
704
        if (!localizedReason) return;
1✔
705
        this.addressSpace?.rootFolder.objects.server.serverStatus.shutdownReason.setValueFromSource({
1✔
706
            dataType: DataType.LocalizedText,
1✔
707
            value: localizedReason
1✔
708
        });
1✔
709
    }
1✔
710
    /**
53✔
711
     * @return the approximate number of seconds until the server will be shut down. The
53✔
712
     * value is only relevant once the state changes into SHUTDOWN.
53✔
713
     */
53✔
714
    public secondsTillShutdown(): number {
53✔
715
        if (!this._expectedShutdownTime) {
25✔
716
            return 0;
24✔
717
        }
24✔
718
        // ToDo: implement a correct solution here
2✔
719
        const now = Date.now();
2✔
720
        return Math.max(0, Math.ceil((this._expectedShutdownTime.getTime() - now) / 1000));
1✔
721
    }
2✔
722

53✔
723
    /**
53✔
724
     * the name of the server
53✔
725
     */
53✔
726
    public get serverName(): string {
53✔
UNCOV
727
        return this._serverStatus.buildInfo?.productName || "";
×
UNCOV
728
    }
×
729

53✔
730
    /**
53✔
731
     * the server urn
53✔
732
     */
53✔
733
    public get serverNameUrn(): string {
53✔
734
        return this._applicationUri;
315✔
735
    }
315✔
736

53✔
737
    /**
53✔
738
     * the urn of the server namespace
53✔
739
     */
53✔
740
    public get serverNamespaceUrn(): string {
53✔
741
        return this._applicationUri; // "urn:" + engine.serverName;
315✔
742
    }
315✔
743
    public get serverStatus(): ServerStatusDataType {
53✔
744
        return this._serverStatus;
3✔
745
    }
3✔
746

53✔
747
    public setServerState(serverState: ServerState): void {
53✔
748
        assert(serverState !== null && serverState !== undefined);
1,552✔
749
        this.addressSpace?.rootFolder?.objects?.server?.serverStatus?.state?.setValueFromSource({
1,552✔
750
            dataType: DataType.Int32,
1,552✔
751
            value: serverState
1,552✔
752
        });
1,552✔
753
    }
1,552✔
754

53✔
755
    public getServerState(): ServerState {
53✔
UNCOV
756
        return this._serverStatus.state;
×
UNCOV
757
    }
×
758

53✔
759
    /**
53✔
760
     * Set the `ServerConfiguration.InApplicationSetup` property
53✔
761
     * in the address space.
53✔
762
     *
53✔
763
     * This flag indicates whether the server is currently in
53✔
764
     * its initial application setup phase (e.g. waiting for
53✔
765
     * GDS provisioning).
53✔
766
     */
53✔
767
    public setInApplicationSetup(value: boolean): void {
53✔
768
        const addressSpace = this.addressSpace;
2✔
769
        if (!addressSpace) {
2!
UNCOV
770
            return;
×
UNCOV
771
        }
×
772
        const serverConfiguration = addressSpace.rootFolder?.objects?.server?.getChildByName(
2✔
773
            "ServerConfiguration"
2✔
774
        ) as UAObject | null;
2✔
775
        if (!serverConfiguration) {
2!
UNCOV
776
            return;
×
UNCOV
777
        }
×
778
        let prop = serverConfiguration.getPropertyByName("InApplicationSetup") as UAVariable | null;
2✔
779
        if (!prop) {
2✔
780
            // InApplicationSetup is ModellingRule_Optional on
1✔
781
            // ServerConfigurationType (i=19308) — instantiate
1✔
782
            // it on the instance on first use.
1✔
783
            const ns = addressSpace.getOwnNamespace();
1✔
784
            prop = ns.addVariable({
1✔
785
                browseName: { name: "InApplicationSetup", namespaceIndex: 0 },
1✔
786
                propertyOf: serverConfiguration,
1✔
787
                typeDefinition: "PropertyType",
1✔
788
                dataType: DataType.Boolean,
1✔
789
                minimumSamplingInterval: -1,
1✔
790
                value: { dataType: DataType.Boolean, value }
1✔
791
            });
1✔
792
            return;
1✔
793
        }
1✔
794
        prop.setValueFromSource({
1✔
795
            dataType: DataType.Boolean,
1✔
796
            value
1✔
797
        });
1✔
798
    }
2✔
799

53✔
800
    /**
53✔
801
     * Read the current value of
53✔
802
     * `ServerConfiguration.InApplicationSetup`.
53✔
803
     *
53✔
804
     * Returns `false` if the property does not exist in
53✔
805
     * the address space.
53✔
806
     */
53✔
807
    public getInApplicationSetup(): boolean {
53✔
808
        const serverConfiguration = this.addressSpace?.rootFolder?.objects?.server?.getChildByName(
3✔
809
            "ServerConfiguration"
3✔
810
        ) as UAObject | null;
3✔
811
        if (!serverConfiguration) {
3!
UNCOV
812
            return false;
×
UNCOV
813
        }
×
814
        const prop = serverConfiguration.getPropertyByName("InApplicationSetup") as UAVariable | null;
3✔
815
        if (!prop) {
3✔
816
            return false;
1✔
817
        }
1✔
818
        return prop.readValue().value.value ?? false;
3!
819
    }
3✔
820

53✔
821
    public getServerDiagnosticsEnabledFlag(): boolean {
53✔
UNCOV
822
        if (!this.addressSpace) {
×
UNCOV
823
            return false;
×
UNCOV
824
        }
×
UNCOV
825
        const server = this.addressSpace.rootFolder.objects.server;
×
UNCOV
826
        const serverDiagnostics = server.getComponentByName("ServerDiagnostics") as UAVariable;
×
UNCOV
827
        if (!serverDiagnostics) {
×
UNCOV
828
            return false;
×
UNCOV
829
        }
×
UNCOV
830
        return serverDiagnostics.readValue().value.value;
×
UNCOV
831
    }
×
832

53✔
833
    /**
53✔
834
     *
53✔
835
     */
53✔
836
    public initialize(options: OPCUAServerOptions, callback: (err?: Error | null) => void): void {
53✔
837
        assert(!this.addressSpace); // check that 'initialize' has not been already called
315✔
838

315✔
839
        this._internalState = "initializing";
315✔
840

315✔
841
        options = options || {};
315!
842
        assert(typeof callback === "function");
315✔
843

315✔
844
        options.nodeset_filename = options.nodeset_filename || nodesets.standard;
315✔
845

315✔
846
        const startTime = new Date();
315✔
847

315✔
848
        debugLog("Loading ", options.nodeset_filename, "...");
315✔
849

315✔
850
        this.addressSpace = AddressSpace.create();
315✔
851

315✔
852
        this.addressSpaceAccessor = new AddressSpaceAccessor(this.addressSpace);
315✔
853

315✔
854
        if (!options.skipOwnNamespace) {
315✔
855
            // register namespace 1 (our namespace);
315✔
856
            const serverNamespace = this.addressSpace.registerNamespace(this.serverNamespaceUrn);
315✔
857
            assert(serverNamespace.index === 1);
315✔
858
        }
315✔
859
        // eslint-disable-next-line max-statements
315✔
860
        generateAddressSpace(this.addressSpace, options.nodeset_filename)
315✔
861
            .catch((err) => {
315✔
UNCOV
862
                console.log(err.message);
×
UNCOV
863
                callback(err);
×
864
            })
315✔
865
            .then(() => {
315✔
866
                /* c8 ignore next */
2✔
867
                if (!this.addressSpace) {
2✔
UNCOV
868
                    throw new Error("Internal error");
×
869
                }
×
870
                const addressSpace = this.addressSpace;
315✔
871

315✔
872
                const endTime = new Date();
315✔
873
                debugLog("Loading ", options.nodeset_filename, " done : ", endTime.getTime() - startTime.getTime(), " ms");
315✔
874

315✔
875
                const bindVariableIfPresent = (nodeId: NodeId, opts?: BindVariableOptions) => {
315✔
876
                    assert(!nodeId.isEmpty());
18,270✔
877
                    const obj = addressSpace.findNode(nodeId);
18,270✔
878
                    if (obj) {
18,270✔
879
                        __bindVariable(this, nodeId, opts);
15,596✔
880
                    }
15,596✔
881
                    return obj;
18,270✔
882
                };
3,165✔
883

315✔
884
                // -------------------------------------------- install default get/put handler
315✔
885
                const server_NamespaceArray_Id = makeNodeId(VariableIds.Server_NamespaceArray); // ns=0;i=2255
315✔
886
                bindVariableIfPresent(server_NamespaceArray_Id, {
315✔
887
                    get() {
315✔
888
                        return new Variant({
1,234✔
889
                            arrayType: VariantArrayType.Array,
1,234✔
890
                            dataType: DataType.String,
1,234✔
891
                            value: addressSpace.getNamespaceArray().map((x) => x.namespaceUri)
1,234✔
892
                        });
1,234✔
893
                    },
315✔
894
                    set: null // read only
315✔
895
                });
315✔
896

315✔
897
                const server_NameUrn_var = new Variant({
315✔
898
                    arrayType: VariantArrayType.Array,
315✔
899
                    dataType: DataType.String,
315✔
900
                    value: [
315✔
901
                        this.serverNameUrn // this is us !
315✔
902
                    ]
315✔
903
                });
315✔
904
                const server_ServerArray_Id = makeNodeId(VariableIds.Server_ServerArray); // ns=0;i=2254
315✔
905

315✔
906
                bindVariableIfPresent(server_ServerArray_Id, {
315✔
907
                    get() {
315✔
908
                        return server_NameUrn_var;
333✔
909
                    },
313✔
910
                    set: null // read only
315✔
911
                });
315✔
912

315✔
913
                // fix DefaultUserRolePermissions and DefaultUserRolePermissions
315✔
914
                // of namespaces
315✔
915
                const namespaces = makeNodeId(ObjectIds.Server_Namespaces);
315✔
916
                const namespacesNode = addressSpace.findNode(namespaces) as UAObject;
315✔
917
                if (namespacesNode) {
315✔
918
                    for (const ns of namespacesNode.getComponents()) {
302✔
919
                        const defaultUserRolePermissions = ns.getChildByName("DefaultUserRolePermissions") as UAVariable | null;
296✔
920
                        if (defaultUserRolePermissions) {
296✔
921
                            defaultUserRolePermissions.setValueFromSource({ dataType: DataType.Null });
272✔
922
                        }
272✔
923
                        const defaultRolePermissions = ns.getChildByName("DefaultRolePermissions") as UAVariable | null;
296✔
924
                        if (defaultRolePermissions) {
296✔
925
                            defaultRolePermissions.setValueFromSource({ dataType: DataType.Null });
272✔
926
                        }
272✔
927
                    }
296✔
928
                }
302✔
929

315✔
930
                const bindStandardScalar = (
315✔
931
                    id: number,
16,065✔
932
                    dataType: DataType,
16,065✔
933
                    func: () => unknown,
16,065✔
934
                    setter_func?: (value: boolean) => void
16,065✔
935
                ) => {
16,065✔
936
                    assert(typeof id === "number", "expecting id to be a number");
16,065✔
937
                    assert(typeof func === "function");
16,065✔
938
                    assert(typeof setter_func === "function" || !setter_func);
16,065✔
939
                    assert(dataType !== null); // check invalid dataType
16,065✔
940

16,065✔
941
                    let setter_func2 = null;
16,065✔
942
                    if (setter_func) {
16,065✔
943
                        setter_func2 = (variant: Variant) => {
315✔
UNCOV
944
                            const variable2 = !!variant.value;
×
UNCOV
945
                            setter_func(variable2);
×
UNCOV
946
                            return StatusCodes.Good;
×
947
                        };
265✔
948
                    }
315✔
949

16,065✔
950
                    const nodeId = makeNodeId(id);
16,065✔
951

16,065✔
952
                    // make sur the provided function returns a valid value for the variant type
16,065✔
953
                    // This test may not be exhaustive but it will detect obvious mistakes.
16,065✔
954

16,065✔
955
                    /* c8 ignore next */
2✔
956
                    if (!isValidVariant(VariantArrayType.Scalar, dataType, func())) {
2✔
UNCOV
957
                        errorLog("func", func());
×
UNCOV
958
                        throw new Error(`bindStandardScalar : func doesn't provide an value of type ${DataType[dataType]}`);
×
UNCOV
959
                    }
×
960

16,065✔
961
                    return bindVariableIfPresent(nodeId, {
16,065✔
962
                        get() {
16,065✔
963
                            return new Variant({
50,360✔
964
                                arrayType: VariantArrayType.Scalar,
50,360✔
965
                                dataType,
50,360✔
966
                                value: func()
50,360✔
967
                            });
50,360✔
968
                        },
14,779✔
969
                        set: setter_func2
16,065✔
970
                    });
16,065✔
971
                };
2,815✔
972

315✔
973
                const bindStandardArray = (id: number, variantDataType: DataType, _dataType: unknown, func: () => unknown[]) => {
315✔
974
                    assert(typeof func === "function");
1,575✔
975
                    assert(variantDataType !== null); // check invalid dataType
1,575✔
976

1,575✔
977
                    const nodeId = makeNodeId(id);
1,575✔
978

1,575✔
979
                    // make sur the provided function returns a valid value for the variant type
1,575✔
980
                    // This test may not be exhaustive but it will detect obvious mistakes.
1,575✔
981
                    assert(isValidVariant(VariantArrayType.Array, variantDataType, func()));
1,575✔
982

1,575✔
983
                    bindVariableIfPresent(nodeId, {
1,575✔
984
                        get() {
1,575✔
985
                            const value = func();
1,427✔
986
                            assert(Array.isArray(value));
1,427✔
987
                            return new Variant({
1,427✔
988
                                arrayType: VariantArrayType.Array,
1,427✔
989
                                dataType: variantDataType,
1,427✔
990
                                value
1,427✔
991
                            });
1,427✔
992
                        },
1,434✔
993
                        set: null // read only
1,575✔
994
                    });
1,575✔
995
                };
515✔
996

315✔
997
                bindStandardScalar(VariableIds.Server_EstimatedReturnTime, DataType.DateTime, () => getMinOPCUADate());
315✔
998

315✔
999
                // TimeZoneDataType
315✔
1000
                addressSpace.findDataType(resolveNodeId(DataTypeIds.TimeZoneDataType));
315✔
1001

315✔
1002
                const timeZone = new TimeZoneDataType({
315✔
1003
                    daylightSavingInOffset: /* boolean*/ false,
315✔
1004
                    offset: /* int16 */ 0
315✔
1005
                });
315✔
1006
                bindStandardScalar(VariableIds.Server_LocalTime, DataType.ExtensionObject, () => {
315✔
1007
                    return timeZone;
581✔
1008
                });
315✔
1009

315✔
1010
                bindStandardScalar(VariableIds.Server_ServiceLevel, DataType.Byte, () => {
315✔
1011
                    return 255;
629✔
1012
                });
315✔
1013

315✔
1014
                bindStandardScalar(VariableIds.Server_Auditing, DataType.Boolean, () => {
315✔
1015
                    return this.isAuditing;
631✔
1016
                });
315✔
1017

315✔
1018
                // eslint-disable-next-line @typescript-eslint/no-this-alias
315✔
1019
                const engine = this;
315✔
1020
                const makeNotReadableIfEnabledFlagIsFalse = (variable: UAVariable) => {
315✔
1021
                    const originalIsReadable = variable.isReadable;
4,228✔
1022
                    variable.isUserReadable = checkReadableFlag;
4,228✔
1023
                    function checkReadableFlag(this: UAVariable, context: SessionContext): boolean {
4,228✔
1024
                        const isEnabled = engine.serverDiagnosticsEnabled;
180✔
1025
                        return originalIsReadable.call(this, context) && isEnabled;
180✔
1026
                    }
180✔
1027
                    for (const c of variable.getAggregates()) {
4,228✔
1028
                        if (c.nodeClass === NodeClass.Variable) {
3,624✔
1029
                            makeNotReadableIfEnabledFlagIsFalse(c as UAVariable);
3,624✔
1030
                        }
3,624✔
1031
                    }
3,624✔
1032
                };
937✔
1033

315✔
1034
                const bindServerDiagnostics = () => {
315✔
1035
                    bindStandardScalar(
315✔
1036
                        VariableIds.Server_ServerDiagnostics_EnabledFlag,
315✔
1037
                        DataType.Boolean,
315✔
1038
                        () => {
315✔
1039
                            return this.serverDiagnosticsEnabled;
629✔
1040
                        },
363✔
1041
                        (newFlag: boolean) => {
315✔
UNCOV
1042
                            this.serverDiagnosticsEnabled = newFlag;
×
UNCOV
1043
                        }
×
1044
                    );
315✔
1045
                    const nodeId = makeNodeId(VariableIds.Server_ServerDiagnostics_ServerDiagnosticsSummary);
315✔
1046
                    const serverDiagnosticsSummaryNode = addressSpace.findNode(
315✔
1047
                        nodeId
315✔
1048
                    ) as UAServerDiagnosticsSummary<ServerDiagnosticsSummaryDataType>;
315✔
1049

315✔
1050
                    if (serverDiagnosticsSummaryNode) {
315✔
1051
                        serverDiagnosticsSummaryNode.bindExtensionObject(this.serverDiagnosticsSummary);
302✔
1052
                        this.serverDiagnosticsSummary = serverDiagnosticsSummaryNode.$extensionObject;
302✔
1053
                        makeNotReadableIfEnabledFlagIsFalse(serverDiagnosticsSummaryNode);
302✔
1054
                    }
302✔
1055
                };
315✔
1056

315✔
1057
                const bindServerStatus = () => {
315✔
1058
                    const serverStatusNode = addressSpace.findNode(
315✔
1059
                        makeNodeId(VariableIds.Server_ServerStatus)
315✔
1060
                    ) as UAServerStatus<DTServerStatus>;
315✔
1061

315✔
1062
                    if (!serverStatusNode) {
315✔
1063
                        return;
13✔
1064
                    }
13✔
1065
                    if (serverStatusNode) {
304✔
1066
                        serverStatusNode.bindExtensionObject(this._serverStatus);
302✔
1067
                        serverStatusNode.minimumSamplingInterval = 1000;
302✔
1068
                    }
302✔
1069

302✔
1070
                    const currentTimeNode = addressSpace.findNode(
302✔
1071
                        makeNodeId(VariableIds.Server_ServerStatus_CurrentTime)
302✔
1072
                    ) as UAVariable;
302✔
1073

302✔
1074
                    if (currentTimeNode) {
302✔
1075
                        currentTimeNode.minimumSamplingInterval = 1000;
302✔
1076
                    }
302✔
1077
                    const secondsTillShutdown = addressSpace.findNode(
302✔
1078
                        makeNodeId(VariableIds.Server_ServerStatus_SecondsTillShutdown)
302✔
1079
                    ) as UAVariable;
302✔
1080

302✔
1081
                    if (secondsTillShutdown) {
302✔
1082
                        secondsTillShutdown.minimumSamplingInterval = 1000;
302✔
1083
                    }
302✔
1084

302✔
1085
                    assert(serverStatusNode.$extensionObject);
302✔
1086

302✔
1087
                    serverStatusNode.$extensionObject = new Proxy(serverStatusNode.$extensionObject, {
302✔
1088
                        get(target, prop) {
302✔
1089
                            if (prop === "currentTime") {
3,067✔
1090
                                serverStatusNode.currentTime.touchValue();
514✔
1091
                                return new Date();
514✔
1092
                            } else if (prop === "secondsTillShutdown") {
3,067✔
1093
                                serverStatusNode.secondsTillShutdown.touchValue();
25✔
1094
                                return engine.secondsTillShutdown();
25✔
1095
                            }
25✔
1096
                            return (target as unknown as Record<string | symbol, unknown>)[prop];
2,538✔
1097
                        }
2,538✔
1098
                    });
302✔
1099
                    this._serverStatus = serverStatusNode.$extensionObject;
302✔
1100
                };
315✔
1101

315✔
1102
                const bindServerCapabilities = () => {
315✔
1103
                    bindStandardArray(
315✔
1104
                        VariableIds.Server_ServerCapabilities_ServerProfileArray,
315✔
1105
                        DataType.String,
315✔
1106
                        DataType.String,
315✔
1107
                        () => {
315✔
1108
                            return this.serverCapabilities.serverProfileArray;
625✔
1109
                        }
625✔
1110
                    );
315✔
1111

315✔
1112
                    bindStandardArray(VariableIds.Server_ServerCapabilities_LocaleIdArray, DataType.String, "LocaleId", () => {
315✔
1113
                        return this.serverCapabilities.localeIdArray;
631✔
1114
                    });
315✔
1115

315✔
1116
                    bindStandardScalar(VariableIds.Server_ServerCapabilities_MinSupportedSampleRate, DataType.Double, () => {
315✔
1117
                        return Math.max(
583✔
1118
                            this.serverCapabilities.minSupportedSampleRate,
583✔
1119
                            defaultServerCapabilities.minSupportedSampleRate
583✔
1120
                        );
583✔
1121
                    });
315✔
1122

315✔
1123
                    bindStandardScalar(VariableIds.Server_ServerCapabilities_MaxBrowseContinuationPoints, DataType.UInt16, () => {
315✔
1124
                        return this.serverCapabilities.maxBrowseContinuationPoints;
18,481✔
1125
                    });
315✔
1126

315✔
1127
                    bindStandardScalar(VariableIds.Server_ServerCapabilities_MaxQueryContinuationPoints, DataType.UInt16, () => {
315✔
1128
                        return this.serverCapabilities.maxQueryContinuationPoints;
583✔
1129
                    });
315✔
1130

315✔
1131
                    bindStandardScalar(VariableIds.Server_ServerCapabilities_MaxHistoryContinuationPoints, DataType.UInt16, () => {
315✔
1132
                        return this.serverCapabilities.maxHistoryContinuationPoints;
585✔
1133
                    });
315✔
1134

315✔
1135
                    // new in 1.05
315✔
1136
                    bindStandardScalar(VariableIds.Server_ServerCapabilities_MaxSessions, DataType.UInt32, () => {
315✔
1137
                        return this.serverCapabilities.maxSessions;
583✔
1138
                    });
315✔
1139
                    bindStandardScalar(VariableIds.Server_ServerCapabilities_MaxSubscriptions, DataType.UInt32, () => {
315✔
1140
                        return this.serverCapabilities.maxSubscriptions;
583✔
1141
                    });
315✔
1142
                    bindStandardScalar(VariableIds.Server_ServerCapabilities_MaxMonitoredItems, DataType.UInt32, () => {
315✔
1143
                        return this.serverCapabilities.maxMonitoredItems;
583✔
1144
                    });
315✔
1145
                    bindStandardScalar(VariableIds.Server_ServerCapabilities_MaxSubscriptionsPerSession, DataType.UInt32, () => {
315✔
1146
                        return this.serverCapabilities.maxSubscriptionsPerSession;
583✔
1147
                    });
315✔
1148
                    bindStandardScalar(VariableIds.Server_ServerCapabilities_MaxSelectClauseParameters, DataType.UInt32, () => {
315✔
1149
                        return this.serverCapabilities.maxSelectClauseParameters;
583✔
1150
                    });
315✔
1151
                    bindStandardScalar(VariableIds.Server_ServerCapabilities_MaxWhereClauseParameters, DataType.UInt32, () => {
315✔
1152
                        return this.serverCapabilities.maxWhereClauseParameters;
583✔
1153
                    });
315✔
1154
                    //bindStandardArray(VariableIds.Server_ServerCapabilities_ConformanceUnits, DataType.QualifiedName, () => {
315✔
1155
                    //    return this.serverCapabilities.conformanceUnits;
315✔
1156
                    //});
315✔
1157
                    bindStandardScalar(
315✔
1158
                        VariableIds.Server_ServerCapabilities_MaxMonitoredItemsPerSubscription,
315✔
1159
                        DataType.UInt32,
315✔
1160
                        () => {
315✔
1161
                            return this.serverCapabilities.maxMonitoredItemsPerSubscription;
583✔
1162
                        }
583✔
1163
                    );
315✔
1164

315✔
1165
                    // added by DI : Server-specific period of time in milliseconds until the Server will revoke a lock.
315✔
1166
                    // TODO bindStandardScalar(VariableIds.Server_ServerCapabilities_MaxInactiveLockTime,
315✔
1167
                    // TODO     DataType.UInt16, function () {
315✔
1168
                    // TODO         return self.serverCapabilities.maxInactiveLockTime;
315✔
1169
                    // TODO });
315✔
1170

315✔
1171
                    bindStandardArray(
315✔
1172
                        VariableIds.Server_ServerCapabilities_SoftwareCertificates,
315✔
1173
                        DataType.ExtensionObject,
315✔
1174
                        "SoftwareCertificates",
315✔
1175
                        () => {
315✔
1176
                            return this.serverCapabilities.softwareCertificates;
583✔
1177
                        }
583✔
1178
                    );
315✔
1179

315✔
1180
                    bindStandardScalar(VariableIds.Server_ServerCapabilities_MaxArrayLength, DataType.UInt32, () => {
315✔
1181
                        return Math.min(this.serverCapabilities.maxArrayLength, Variant.maxArrayLength);
583✔
1182
                    });
315✔
1183

315✔
1184
                    bindStandardScalar(VariableIds.Server_ServerCapabilities_MaxStringLength, DataType.UInt32, () => {
315✔
1185
                        return Math.min(this.serverCapabilities.maxStringLength, BinaryStream.maxStringLength);
583✔
1186
                    });
315✔
1187

315✔
1188
                    bindStandardScalar(VariableIds.Server_ServerCapabilities_MaxByteStringLength, DataType.UInt32, () => {
315✔
1189
                        return Math.min(this.serverCapabilities.maxByteStringLength, BinaryStream.maxByteStringLength);
585✔
1190
                    });
315✔
1191

315✔
1192
                    bindStandardScalar(VariableIds.Server_ServerCapabilities_MaxMonitoredItemsQueueSize, DataType.UInt32, () => {
315✔
1193
                        return Math.max(1, this.serverCapabilities.maxMonitoredItemsQueueSize);
583✔
1194
                    });
315✔
1195

315✔
1196
                    const bindOperationLimits = (operationLimits: ServerOperationLimits) => {
315✔
1197
                        assert(operationLimits !== null && typeof operationLimits === "object");
315✔
1198

315✔
1199
                        const keys = Object.keys(operationLimits);
315✔
1200

315✔
1201
                        keys.forEach((key: string) => {
315✔
1202
                            const uid = `Server_ServerCapabilities_OperationLimits_${upperCaseFirst(key)}`;
3,780✔
1203
                            const nodeId = makeNodeId((VariableIds as unknown as Record<string, number>)[uid]);
3,780✔
1204
                            assert(!nodeId.isEmpty());
3,780✔
1205

3,780✔
1206
                            bindStandardScalar((VariableIds as unknown as Record<string, number>)[uid], DataType.UInt32, () => {
3,780✔
1207
                                return (operationLimits as unknown as Record<string, unknown>)[key];
25,656✔
1208
                            });
3,780✔
1209
                        });
315✔
1210
                    };
315✔
1211

315✔
1212
                    bindOperationLimits(this.serverCapabilities.operationLimits);
315✔
1213

315✔
1214
                    // i=2399 [ProgramStateMachineType_ProgramDiagnostics];
315✔
1215
                    function fix_ProgramStateMachineType_ProgramDiagnostics() {
315✔
1216
                        const nodeId = coerceNodeId("i=2399"); // ProgramStateMachineType_ProgramDiagnostics
315✔
1217
                        const variable = addressSpace.findNode(nodeId) as UAVariable;
315✔
1218
                        if (variable) {
315✔
1219
                            (variable as unknown as Record<string, unknown>).$extensionObject = new ProgramDiagnosticDataType({});
254✔
1220
                            //  variable.setValueFromSource({
254✔
1221
                            //     dataType: DataType.ExtensionObject,
254✔
1222
                            //     //     value: new ProgramDiagnostic2DataType()
254✔
1223
                            //     value: new ProgramDiagnosticDataType({})
254✔
1224
                            // });
254✔
1225
                        }
254✔
1226
                    }
315✔
1227
                    fix_ProgramStateMachineType_ProgramDiagnostics();
315✔
1228
                };
315✔
1229

315✔
1230
                const bindHistoryServerCapabilities = () => {
315✔
1231
                    bindStandardScalar(VariableIds.HistoryServerCapabilities_MaxReturnDataValues, DataType.UInt32, () => {
315✔
1232
                        return this.historyServerCapabilities.maxReturnDataValues;
583✔
1233
                    });
315✔
1234

315✔
1235
                    bindStandardScalar(VariableIds.HistoryServerCapabilities_MaxReturnEventValues, DataType.UInt32, () => {
315✔
1236
                        return this.historyServerCapabilities.maxReturnEventValues;
583✔
1237
                    });
315✔
1238

315✔
1239
                    bindStandardScalar(VariableIds.HistoryServerCapabilities_AccessHistoryDataCapability, DataType.Boolean, () => {
315✔
1240
                        return this.historyServerCapabilities.accessHistoryDataCapability;
583✔
1241
                    });
315✔
1242
                    bindStandardScalar(
315✔
1243
                        VariableIds.HistoryServerCapabilities_AccessHistoryEventsCapability,
315✔
1244
                        DataType.Boolean,
315✔
1245
                        () => {
315✔
1246
                            return this.historyServerCapabilities.accessHistoryEventsCapability;
583✔
1247
                        }
583✔
1248
                    );
315✔
1249
                    bindStandardScalar(VariableIds.HistoryServerCapabilities_InsertDataCapability, DataType.Boolean, () => {
315✔
1250
                        return this.historyServerCapabilities.insertDataCapability;
583✔
1251
                    });
315✔
1252
                    bindStandardScalar(VariableIds.HistoryServerCapabilities_ReplaceDataCapability, DataType.Boolean, () => {
315✔
1253
                        return this.historyServerCapabilities.replaceDataCapability;
583✔
1254
                    });
315✔
1255
                    bindStandardScalar(VariableIds.HistoryServerCapabilities_UpdateDataCapability, DataType.Boolean, () => {
315✔
1256
                        return this.historyServerCapabilities.updateDataCapability;
583✔
1257
                    });
315✔
1258

315✔
1259
                    bindStandardScalar(VariableIds.HistoryServerCapabilities_InsertEventCapability, DataType.Boolean, () => {
315✔
1260
                        return this.historyServerCapabilities.insertEventCapability;
583✔
1261
                    });
315✔
1262

315✔
1263
                    bindStandardScalar(VariableIds.HistoryServerCapabilities_ReplaceEventCapability, DataType.Boolean, () => {
315✔
1264
                        return this.historyServerCapabilities.replaceEventCapability;
583✔
1265
                    });
315✔
1266

315✔
1267
                    bindStandardScalar(VariableIds.HistoryServerCapabilities_UpdateEventCapability, DataType.Boolean, () => {
315✔
1268
                        return this.historyServerCapabilities.updateEventCapability;
583✔
1269
                    });
315✔
1270

315✔
1271
                    bindStandardScalar(VariableIds.HistoryServerCapabilities_DeleteEventCapability, DataType.Boolean, () => {
315✔
1272
                        return this.historyServerCapabilities.deleteEventCapability;
583✔
1273
                    });
315✔
1274

315✔
1275
                    bindStandardScalar(VariableIds.HistoryServerCapabilities_DeleteRawCapability, DataType.Boolean, () => {
315✔
1276
                        return this.historyServerCapabilities.deleteRawCapability;
583✔
1277
                    });
315✔
1278

315✔
1279
                    bindStandardScalar(VariableIds.HistoryServerCapabilities_DeleteAtTimeCapability, DataType.Boolean, () => {
315✔
1280
                        return this.historyServerCapabilities.deleteAtTimeCapability;
583✔
1281
                    });
315✔
1282

315✔
1283
                    bindStandardScalar(VariableIds.HistoryServerCapabilities_InsertAnnotationCapability, DataType.Boolean, () => {
315✔
1284
                        return this.historyServerCapabilities.insertAnnotationCapability;
583✔
1285
                    });
315✔
1286
                };
315✔
1287

315✔
1288
                type Getter<T> = () => T;
315✔
1289
                function r<T>(a: undefined | T | Getter<T>, defaultValue: T): T {
315✔
1290
                    if (a === undefined) return defaultValue;
4,072✔
1291
                    if (typeof a === "function") {
4,072✔
1292
                        return (a as unknown as () => T)();
2,720✔
1293
                    }
2,720✔
1294
                    return a;
976✔
1295
                }
976✔
1296
                const bindServerConfigurationBasic = () => {
315✔
1297
                    bindStandardArray(VariableIds.ServerConfiguration_ServerCapabilities, DataType.String, DataType.String, () =>
315✔
1298
                        r(this.serverConfiguration.serverCapabilities, ["NA"])
582✔
1299
                    );
315✔
1300
                    bindStandardScalar(VariableIds.ServerConfiguration_ApplicationType, DataType.Int32, () =>
315✔
1301
                        r(this.serverConfiguration.applicationType, ApplicationType.Server)
582✔
1302
                    );
315✔
1303
                    bindStandardScalar(VariableIds.ServerConfiguration_ApplicationUri, DataType.String, () =>
315✔
1304
                        r(this.serverConfiguration.applicationUri, "")
582✔
1305
                    );
315✔
1306
                    bindStandardScalar(VariableIds.ServerConfiguration_ProductUri, DataType.String, () =>
315✔
1307
                        r(this.serverConfiguration.productUri, "")
582✔
1308
                    );
315✔
1309
                    bindStandardScalar(VariableIds.ServerConfiguration_HasSecureElement, DataType.Boolean, () =>
315✔
1310
                        r(this.serverConfiguration.hasSecureElement, false)
581✔
1311
                    );
315✔
1312
                    bindStandardScalar(VariableIds.ServerConfiguration_MulticastDnsEnabled, DataType.Boolean, () =>
315✔
1313
                        r(this.serverConfiguration.multicastDnsEnabled, false)
582✔
1314
                    );
315✔
1315
                    bindStandardArray(
315✔
1316
                        VariableIds.ServerConfiguration_SupportedPrivateKeyFormats,
315✔
1317
                        DataType.String,
315✔
1318
                        DataType.String,
315✔
1319
                        () => r(this.serverConfiguration.supportedPrivateKeyFormat, ["PEM"])
315✔
1320
                    );
315✔
1321
                };
315✔
1322

315✔
1323
                bindServerDiagnostics();
315✔
1324

315✔
1325
                bindServerStatus();
315✔
1326

315✔
1327
                bindServerCapabilities();
315✔
1328

315✔
1329
                bindServerConfigurationBasic();
315✔
1330

315✔
1331
                bindHistoryServerCapabilities();
315✔
1332

315✔
1333
                const bindExtraStuff = () => {
315✔
1334
                    // mainly for compliance
315✔
1335
                    /*
315✔
1336
                // The version number for the data type description. i=104
315✔
1337
                bindStandardScalar(VariableIds.DataTypeDescriptionType_DataTypeVersion, DataType.String, () => {
315✔
1338
                    return "0";
315✔
1339
                });
315✔
1340

315✔
1341
                const namingRuleDataTypeNode = addressSpace.findDataType(resolveNodeId(DataTypeIds.NamingRuleType))! as UADataType;
315✔
1342

315✔
1343
                if (namingRuleDataTypeNode) {
315✔
1344
                    const namingRuleType = (namingRuleDataTypeNode as any)._getEnumerationInfo().nameIndex; // getEnumeration("NamingRuleType");
315✔
1345
                    if (!namingRuleType) {
315✔
1346
                        throw new Error("Cannot find Enumeration definition for NamingRuleType");
315✔
1347
                    }
315✔
1348
                    // i=111
315✔
1349
                    bindStandardScalar(VariableIds.ModellingRuleType_NamingRule, DataType.Int32, () => {
315✔
1350
                        return 0;
315✔
1351
                    });
315✔
1352

315✔
1353
                    // i=112
315✔
1354
                    bindStandardScalar(VariableIds.ModellingRule_Mandatory_NamingRule, DataType.Int32, () => {
315✔
1355
                        return namingRuleType.Mandatory ? namingRuleType.Mandatory.value : 0;
315✔
1356
                    });
315✔
1357

315✔
1358
                    // i=113
315✔
1359
                    bindStandardScalar(VariableIds.ModellingRule_Optional_NamingRule, DataType.Int32, () => {
315✔
1360
                        return namingRuleType.Optional ? namingRuleType.Optional.value : 0;
315✔
1361
                    });
315✔
1362
                    // i=114
315✔
1363
                    bindStandardScalar(VariableIds.ModellingRule_ExposesItsArray_NamingRule, DataType.Int32, () => {
315✔
1364
                        return namingRuleType.ExposesItsArray ? namingRuleType.ExposesItsArray.value : 0;
315✔
1365
                    });
315✔
1366
                    bindStandardScalar(VariableIds.ModellingRule_MandatoryPlaceholder_NamingRule, DataType.Int32, () => {
315✔
1367
                        return namingRuleType.MandatoryPlaceholder ? namingRuleType.MandatoryPlaceholder.value : 0;
315✔
1368
                    });
315✔
1369
                }
315✔
1370
*/
315✔
1371
                };
315✔
1372

315✔
1373
                bindExtraStuff();
315✔
1374

315✔
1375
                this.__internal_bindMethod(makeNodeId(MethodIds.Server_GetMonitoredItems), getMonitoredItemsId.bind(this));
315✔
1376
                this.__internal_bindMethod(makeNodeId(MethodIds.Server_SetSubscriptionDurable), setSubscriptionDurable.bind(this));
315✔
1377
                this.__internal_bindMethod(makeNodeId(MethodIds.Server_ResendData), resendData.bind(this));
315✔
1378
                this.__internal_bindMethod(
315✔
1379
                    makeNodeId(MethodIds.Server_RequestServerStateChange),
315✔
1380
                    requestServerStateChange.bind(this)
315✔
1381
                );
315✔
1382

315✔
1383
                // fix getMonitoredItems.outputArguments arrayDimensions
315✔
1384
                const fixGetMonitoredItemArgs = () => {
315✔
1385
                    const objects = this.addressSpace?.rootFolder?.objects;
315✔
1386
                    if (!objects || !objects.server) {
315✔
1387
                        return;
12✔
1388
                    }
12✔
1389
                    const getMonitoredItemsMethod = objects.server.getMethodByName("GetMonitoredItems");
305✔
1390
                    if (!getMonitoredItemsMethod) {
315✔
1391
                        return;
1✔
1392
                    }
1✔
1393
                    const outputArguments = getMonitoredItemsMethod.outputArguments;
304✔
1394
                    if (!outputArguments) {
315!
UNCOV
1395
                        return;
×
UNCOV
1396
                    }
×
1397
                    const dataValue = outputArguments.readValue();
304✔
1398
                    if (!dataValue.value?.value) {
315!
UNCOV
1399
                        // value is null or undefined , meaning no arguments necessary
×
UNCOV
1400
                        return;
×
UNCOV
1401
                    }
✔
1402
                    assert(
302✔
1403
                        dataValue.value.value[0].arrayDimensions.length === 1 && dataValue.value.value[0].arrayDimensions[0] === 0
302✔
1404
                    );
315✔
1405
                    assert(
315✔
1406
                        dataValue.value.value[1].arrayDimensions.length === 1 && dataValue.value.value[1].arrayDimensions[0] === 0
315✔
1407
                    );
315✔
1408
                };
315✔
1409
                fixGetMonitoredItemArgs();
315✔
1410

315✔
1411
                const prepareServerDiagnostics = () => {
315✔
1412
                    const addressSpace1 = this.addressSpace;
315✔
1413
                    if (!addressSpace1) {
315!
UNCOV
1414
                        return;
×
UNCOV
1415
                    }
×
1416

315✔
1417
                    if (!addressSpace1.rootFolder.objects) {
315✔
1418
                        return;
12✔
1419
                    }
12✔
1420
                    const server = addressSpace1.rootFolder.objects.server;
305✔
1421

303✔
1422
                    if (!server) {
315!
UNCOV
1423
                        return;
×
UNCOV
1424
                    }
×
1425

305✔
1426
                    // create SessionsDiagnosticsSummary
305✔
1427
                    const serverDiagnosticsNode = server.getComponentByName("ServerDiagnostics") as UAServerDiagnostics;
305✔
1428
                    if (!serverDiagnosticsNode) {
315✔
1429
                        return;
1✔
1430
                    }
1✔
1431
                    if (true) {
304✔
1432
                        // set serverDiagnosticsNode enabledFlag writeable for admin user only
302✔
1433
                        // TO DO ...
302✔
1434
                        serverDiagnosticsNode.enabledFlag.userAccessLevel = makeAccessLevelFlag("CurrentRead");
302✔
1435
                        serverDiagnosticsNode.enabledFlag.accessLevel = makeAccessLevelFlag("CurrentRead");
302✔
1436
                    }
302✔
1437

302✔
1438
                    // A Server may not expose the SamplingIntervalDiagnosticsArray if it does not use fixed sampling rates.
302✔
1439
                    // because we are not using fixed sampling rate, we need to remove the optional SamplingIntervalDiagnosticsArray
302✔
1440
                    // component
302✔
1441
                    const samplingIntervalDiagnosticsArray = serverDiagnosticsNode.getComponentByName(
302✔
1442
                        "SamplingIntervalDiagnosticsArray"
302✔
1443
                    );
302✔
1444
                    if (samplingIntervalDiagnosticsArray) {
302✔
1445
                        addressSpace.deleteNode(samplingIntervalDiagnosticsArray);
302✔
1446
                    }
302✔
1447

302✔
1448
                    const subscriptionDiagnosticsArrayNode = serverDiagnosticsNode.getComponentByName(
302✔
1449
                        "SubscriptionDiagnosticsArray"
302✔
1450
                    ) as UADynamicVariableArray<SessionDiagnosticsDataType>;
302✔
1451
                    assert(subscriptionDiagnosticsArrayNode.nodeClass === NodeClass.Variable);
302✔
1452
                    bindExtObjArrayNode(subscriptionDiagnosticsArrayNode, "SubscriptionDiagnosticsType", "subscriptionId");
302✔
1453

302✔
1454
                    makeNotReadableIfEnabledFlagIsFalse(subscriptionDiagnosticsArrayNode);
302✔
1455

302✔
1456
                    const sessionsDiagnosticsSummary = serverDiagnosticsNode.getComponentByName("SessionsDiagnosticsSummary");
302✔
1457

302✔
1458
                    if (!sessionsDiagnosticsSummary) {
315!
UNCOV
1459
                        return;
×
UNCOV
1460
                    }
×
1461
                    const sessionDiagnosticsArray = sessionsDiagnosticsSummary.getComponentByName(
304✔
1462
                        "SessionDiagnosticsArray"
302✔
1463
                    ) as UADynamicVariableArray<SessionDiagnosticsDataType>;
302✔
1464
                    assert(sessionDiagnosticsArray.nodeClass === NodeClass.Variable);
302✔
1465

302✔
1466
                    bindExtObjArrayNode(sessionDiagnosticsArray, "SessionDiagnosticsVariableType", "sessionId");
302✔
1467

302✔
1468
                    const varType = addressSpace.findVariableType("SessionSecurityDiagnosticsType");
302✔
1469
                    if (!varType) {
315✔
1470
                        debugLog("Warning cannot find SessionSecurityDiagnosticsType variable Type");
48✔
1471
                    } else {
315✔
1472
                        const sessionSecurityDiagnosticsArray = sessionsDiagnosticsSummary.getComponentByName(
254✔
1473
                            "SessionSecurityDiagnosticsArray"
254✔
1474
                        ) as UADynamicVariableArray<SessionSecurityDiagnosticsDataType>;
254✔
1475
                        assert(sessionSecurityDiagnosticsArray.nodeClass === NodeClass.Variable);
254✔
1476
                        bindExtObjArrayNode(sessionSecurityDiagnosticsArray, "SessionSecurityDiagnosticsType", "sessionId");
254✔
1477
                        ensureObjectIsSecure(sessionSecurityDiagnosticsArray);
254✔
1478
                    }
254✔
1479
                };
315✔
1480

315✔
1481
                prepareServerDiagnostics();
315✔
1482

315✔
1483
                this._internalState = "initialized";
315✔
1484
                this.setServerState(ServerState.Running);
315✔
1485
                setImmediate(() => callback());
315✔
1486
            });
315✔
1487
    }
315✔
1488

53✔
1489
    public async browseWithAutomaticExpansion(
53✔
1490
        nodesToBrowse: BrowseDescription[],
12,663✔
1491
        context: ISessionContext
12,663✔
1492
    ): Promise<BrowseResult[]> {
12,663✔
1493
        // do expansion first
12,663✔
1494
        for (const browseDescription of nodesToBrowse) {
12,663✔
1495
            const nodeId = resolveNodeId(browseDescription.nodeId);
47,620✔
1496
            const node = this.addressSpace?.findNode(nodeId);
47,620✔
1497
            if (node) {
47,620✔
1498
                if (node.onFirstBrowseAction) {
47,620✔
1499
                    try {
1✔
1500
                        await node.onFirstBrowseAction();
1✔
1501
                        node.onFirstBrowseAction = undefined;
1✔
1502
                    } catch (err) {
1!
UNCOV
1503
                        if (types.isNativeError(err)) {
×
UNCOV
1504
                            errorLog("onFirstBrowseAction method has failed", err.message);
×
UNCOV
1505
                        }
×
UNCOV
1506
                        errorLog(err);
×
UNCOV
1507
                    }
×
1508
                    assert(node.onFirstBrowseAction === undefined, "expansion can only be made once");
1✔
1509
                }
1✔
1510
            }
47,620✔
1511
        }
47,620✔
1512
        return await this.browse(context, nodesToBrowse);
12,663✔
1513
    }
12,663✔
1514
    public async browse(context: ISessionContext, nodesToBrowse: BrowseDescriptionOptions[]): Promise<BrowseResult[]> {
53✔
1515
        if (!this.addressSpaceAccessor) {
12,678!
UNCOV
1516
            throw new Error("addressSpaceAccessor is not available");
×
UNCOV
1517
        }
×
1518
        return this.addressSpaceAccessor.browse(context, nodesToBrowse);
12,678✔
1519
    }
12,678✔
1520
    public async read(context: ISessionContext, readRequest: ReadRequestOptions): Promise<DataValue[]> {
53✔
1521
        if (!this.addressSpaceAccessor) {
23,726!
UNCOV
1522
            throw new Error("addressSpaceAccessor is not available");
×
UNCOV
1523
        }
×
1524
        return this.addressSpaceAccessor.read(context, readRequest);
23,726✔
1525
    }
23,726✔
1526
    public async write(context: ISessionContext, nodesToWrite: WriteValue[]): Promise<StatusCode[]> {
53✔
1527
        if (!this.addressSpaceAccessor) {
160!
UNCOV
1528
            throw new Error("addressSpaceAccessor is not available");
×
UNCOV
1529
        }
×
1530
        return await this.addressSpaceAccessor.write(context, nodesToWrite);
160✔
1531
    }
160✔
1532
    public async call(context: ISessionContext, methodsToCall: CallMethodRequest[]): Promise<CallMethodResultOptions[]> {
53✔
1533
        if (!this.addressSpaceAccessor) {
86✔
UNCOV
1534
            throw new Error("addressSpaceAccessor is not available");
×
UNCOV
1535
        }
×
1536
        return await this.addressSpaceAccessor.call(context, methodsToCall);
86✔
1537
    }
86✔
1538
    public async historyRead(context: ISessionContext, historyReadRequest: HistoryReadRequest): Promise<HistoryReadResult[]> {
53✔
1539
        if (!this.addressSpaceAccessor) {
26!
UNCOV
1540
            throw new Error("addressSpaceAccessor is not available");
×
UNCOV
1541
        }
×
1542
        return this.addressSpaceAccessor.historyRead(context, historyReadRequest);
26✔
1543
    }
26✔
1544

53✔
1545
    public getOldestInactiveSession(): ServerSession | null {
53✔
1546
        // search screwed or closed session first
26✔
1547
        let tmp = Object.values(this._sessions).filter(
26✔
1548
            (session1: ServerSession) =>
26✔
1549
                session1.status === "screwed" || session1.status === "disposed" || session1.status === "closed"
309✔
1550
        );
26✔
1551
        if (tmp.length === 0) {
26✔
1552
            // if none available, tap into the session that are not yet activated
26✔
1553
            tmp = Object.values(this._sessions).filter((session1: ServerSession) => session1.status === "new");
26✔
1554
        }
26✔
1555
        if (tmp.length === 0) return null;
26✔
1556
        let session = tmp[0];
14✔
1557
        for (let i = 1; i < tmp.length; i++) {
26✔
1558
            const c = tmp[i];
263✔
1559
            if (session.creationDate.getTime() < c.creationDate.getTime()) {
263✔
1560
                session = c;
193✔
1561
            }
193✔
1562
        }
263✔
1563
        return session;
14✔
1564
    }
14✔
1565

53✔
1566
    /**
53✔
1567
     * create a new server session object.
53✔
1568
     */
53✔
1569
    public createSession(options?: CreateSessionOption): ServerSession {
53✔
1570
        options = options || {};
1,070✔
1571
        options.server = options.server || {};
1,070✔
1572
        debugLog("createSession : increasing serverDiagnosticsSummary cumulatedSessionCount/currentSessionCount ");
1,070✔
1573
        this.serverDiagnosticsSummary.cumulatedSessionCount += 1;
1,070✔
1574
        this.serverDiagnosticsSummary.currentSessionCount += 1;
1,070✔
1575

1,070✔
1576
        this.clientDescription = options.clientDescription || new ApplicationDescription({});
1,070✔
1577

1,070✔
1578
        const sessionTimeout = options.sessionTimeout || 1000;
1,070✔
1579
        assert(typeof sessionTimeout === "number");
1,070✔
1580

1,070✔
1581
        const session = new ServerSession(this, options.server.userManager || {}, sessionTimeout);
1,070✔
1582

1,070✔
1583
        debugLog("createSession :sessionTimeout = ", session.sessionTimeout);
1,070✔
1584

1,070✔
1585
        const key = session.authenticationToken.toString();
1,070✔
1586

1,070✔
1587
        this._sessions[key] = session;
1,070✔
1588

1,070✔
1589
        // see spec OPC Unified Architecture,  Part 2 page 26 Release 1.02
1,070✔
1590
        // TODO : When a Session is created, the Server adds an entry for the Client
1,070✔
1591
        //        in its SessionDiagnosticsArray Variable
1,070✔
1592

1,070✔
1593
        session.on("new_subscription", (_subscription: Subscription) => {
1,070✔
1594
            this.serverDiagnosticsSummary.cumulatedSubscriptionCount += 1;
457✔
1595
            // add the subscription diagnostics in our subscriptions diagnostics array
457✔
1596
            // note currentSubscriptionCount is handled directly with a special getter
457✔
1597
        });
1,070✔
1598

1,070✔
1599
        session.on("subscription_terminated", (_subscription: Subscription) => {
1,070✔
1600
            // remove the subscription diagnostics in our subscriptions diagnostics array
445✔
1601
            // note currentSubscriptionCount is handled directly with a special getter
445✔
1602
        });
1,070✔
1603

1,070✔
1604
        // OPC Unified Architecture, Part 4 23 Release 1.03
1,070✔
1605
        // Sessions are terminated by the Server automatically if the Client fails to issue a Service request on the
1,070✔
1606
        // Session within the timeout period negotiated by the Server in the CreateSession Service response.
1,070✔
1607
        // This protects the Server against Client failures and against situations where a failed underlying
1,070✔
1608
        // connection cannot be re-established. Clients shall be prepared to submit requests in a timely manner
1,070✔
1609
        // prevent the Session from closing automatically. Clients may explicitly terminate sessions using the
1,070✔
1610
        // CloseSession Service.
1,070✔
1611
        session.on("timeout", () => {
1,070✔
1612
            // the session hasn't been active for a while , probably because the client has disconnected abruptly
11✔
1613
            // it is now time to close the session completely
11✔
1614
            this.serverDiagnosticsSummary.sessionTimeoutCount += 1;
11✔
1615
            session.sessionName = session.sessionName || "";
11✔
1616

11✔
1617
            const channel = session.channel;
11✔
1618
            errorLog(
11✔
1619
                chalk.cyan("Server: closing SESSION "),
11✔
1620
                session.status,
11✔
1621
                chalk.yellow(session.sessionName),
11✔
1622
                chalk.yellow(session.nodeId.toString()),
11✔
1623
                chalk.cyan(" because of timeout = "),
11✔
1624
                session.sessionTimeout,
11✔
1625
                chalk.cyan(" has expired without a keep alive"),
11✔
1626
                chalk.bgCyan("channel = "),
11✔
1627
                channel?.remoteAddress,
11✔
1628
                " port = ",
11✔
1629
                channel?.remotePort
11✔
1630
            );
11✔
1631

11✔
1632
            // If a Server terminates a Session for any other reason, Subscriptions  associated with the Session,
11✔
1633
            // are not deleted. => deleteSubscription= false
11✔
1634
            this.closeSession(session.authenticationToken, /*deleteSubscription=*/ false, /* reason =*/ "Timeout");
11✔
1635

11✔
1636
            this.incrementSessionTimeoutCount();
11✔
1637
        });
1,070✔
1638

1,070✔
1639
        return session;
1,070✔
1640
    }
1,070✔
1641

53✔
1642
    /**
53✔
1643
     * @param authenticationToken
53✔
1644
     * @param deleteSubscriptions {Boolean} : true if session's subscription shall be deleted
53✔
1645
     * @param {String} [reason = "CloseSession"] the reason for closing the session (
53✔
1646
     *                 shall be "Timeout", "Terminated" or "CloseSession")
53✔
1647
     *
53✔
1648
     *
53✔
1649
     * what the specs say:
53✔
1650
     * -------------------
53✔
1651
     *
53✔
1652
     * If a Client invokes the CloseSession Service then all Subscriptions associated with the Session are also deleted
53✔
1653
     * if the deleteSubscriptions flag is set to TRUE. If a Server terminates a Session for any other reason,
53✔
1654
     * Subscriptions associated with the Session, are not deleted. Each Subscription has its own lifetime to protect
53✔
1655
     * against data loss in the case of a Session termination. In these cases, the Subscription can be reassigned to
53✔
1656
     * another Client before its lifetime expires.
53✔
1657
     */
53✔
1658
    public closeSession(authenticationToken: NodeId, deleteSubscriptions: boolean, reason: ClosingReason): void {
53✔
1659
        reason = reason || "CloseSession";
1,070!
1660
        assert(typeof reason === "string");
1,070✔
1661
        assert(reason === "Timeout" || reason === "Terminated" || reason === "CloseSession" || reason === "Forcing");
1,070✔
1662

1,070✔
1663
        debugLog("ServerEngine.closeSession ", authenticationToken.toString(), deleteSubscriptions);
1,070✔
1664

1,070✔
1665
        const session = this.getSession(authenticationToken);
1,070✔
1666

1,070✔
1667
        // c8 ignore next
1,070✔
1668
        if (!session) {
1,070!
UNCOV
1669
            throw new Error(`cannot find session with this authenticationToken ${authenticationToken.toString()}`);
×
UNCOV
1670
        }
×
1671

1,070✔
1672
        if (!deleteSubscriptions) {
1,070✔
1673
            // Live Subscriptions will not be deleted, but transferred to the orphanPublishEngine
32✔
1674
            // until they time out or until a other session transfer them back to it.
32✔
1675
            if (!this._orphanPublishEngine) {
32✔
1676
                this._orphanPublishEngine = new ServerSidePublishEngineForOrphanSubscription({ maxPublishRequestInQueue: 0 });
9✔
1677
            }
9✔
1678

32✔
1679
            debugLog("transferring remaining live subscription to orphanPublishEngine !");
32✔
1680
            ServerSidePublishEngine.transferSubscriptionsToOrphan(session.publishEngine, this._orphanPublishEngine);
32✔
1681
        }
32✔
1682

1,070✔
1683
        session.close(deleteSubscriptions, reason);
1,070✔
1684

1,070✔
1685
        assert(session.status === "closed");
1,070✔
1686

1,070✔
1687
        debugLog(" engine.serverDiagnosticsSummary.currentSessionCount -= 1;");
1,070✔
1688
        this.serverDiagnosticsSummary.currentSessionCount -= 1;
1,070✔
1689

1,070✔
1690
        // xx //TODO make sure _closedSessions gets cleaned at some point
1,070✔
1691
        // xx self._closedSessions[key] = session;
1,070✔
1692

1,070✔
1693
        // remove sessionDiagnostics from server.ServerDiagnostics.SessionsDiagnosticsSummary.SessionDiagnosticsSummary
1,070✔
1694
        delete this._sessions[authenticationToken.toString()];
1,070✔
1695
        session.dispose();
1,070✔
1696
    }
1,070✔
1697

53✔
1698
    public findSubscription(subscriptionId: number): Subscription | null {
53✔
1699
        const subscriptions: Subscription[] = [];
78✔
1700
        Object.values(this._sessions).forEach((session) => {
78✔
1701
            if (subscriptions.length) {
94✔
1702
                return;
15✔
1703
            }
15✔
1704
            const subscription = session.publishEngine.getSubscriptionById(subscriptionId);
88✔
1705
            if (subscription) {
94✔
1706
                subscriptions.push(subscription);
16✔
1707
            }
16✔
1708
        });
78✔
1709
        if (subscriptions.length) {
78✔
1710
            assert(subscriptions.length === 1);
16✔
1711
            return subscriptions[0];
16✔
1712
        }
16✔
1713
        return this.findOrphanSubscription(subscriptionId);
72✔
1714
    }
72✔
1715

53✔
1716
    public findOrphanSubscription(subscriptionId: number): Subscription | null {
53✔
1717
        if (!this._orphanPublishEngine) {
362✔
1718
            return null;
319✔
1719
        }
319✔
1720
        return this._orphanPublishEngine.getSubscriptionById(subscriptionId);
43✔
1721
    }
43✔
1722

53✔
1723
    public deleteOrphanSubscription(subscription: Subscription): StatusCode {
53✔
UNCOV
1724
        if (!this._orphanPublishEngine) {
×
UNCOV
1725
            return StatusCodes.BadInternalError;
×
UNCOV
1726
        }
×
UNCOV
1727
        assert(this.findSubscription(subscription.id));
×
UNCOV
1728

×
UNCOV
1729
        const c = this._orphanPublishEngine.subscriptionCount;
×
UNCOV
1730
        subscription.terminate();
×
UNCOV
1731
        subscription.dispose();
×
UNCOV
1732
        assert(this._orphanPublishEngine.subscriptionCount === c - 1);
×
UNCOV
1733
        return StatusCodes.Good;
×
UNCOV
1734
    }
×
1735

53✔
1736
    /**
53✔
1737
     * @param session           {ServerSession}  - the new session that will own the subscription
53✔
1738
     * @param subscriptionId    {IntegerId}      - the subscription Id to transfer
53✔
1739
     * @param sendInitialValues {Boolean}        - true if initial values will be resent.
53✔
1740
     * @return                  {TransferResult}
53✔
1741
     */
53✔
1742
    public async transferSubscription(
53✔
1743
        session: ServerSession,
25✔
1744
        subscriptionId: number,
25✔
1745
        sendInitialValues: boolean
25✔
1746
    ): Promise<TransferResult> {
25✔
1747
        if (subscriptionId <= 0) {
25!
UNCOV
1748
            return new TransferResult({ statusCode: StatusCodes.BadSubscriptionIdInvalid });
×
UNCOV
1749
        }
×
1750

25✔
1751
        const subscription = this.findSubscription(subscriptionId);
25✔
1752
        if (!subscription) {
25✔
1753
            return new TransferResult({ statusCode: StatusCodes.BadSubscriptionIdInvalid });
5✔
1754
        }
5✔
1755

20✔
1756
        // check that session have same userIdentity
20✔
1757
        if (!sessionsCompatibleForTransfer(subscription.$session, session)) {
25!
UNCOV
1758
            return new TransferResult({ statusCode: StatusCodes.BadUserAccessDenied });
×
UNCOV
1759
        }
×
1760

20✔
1761
        // update diagnostics
20✔
1762
        subscription.subscriptionDiagnostics.transferRequestCount++;
20✔
1763

20✔
1764
        // now check that new session has sufficient right
20✔
1765
        // if (session.authenticationToken.toString() !== subscription.authenticationToken.toString()) {
20✔
1766
        //     warningLog("ServerEngine#transferSubscription => BadUserAccessDenied");
20✔
1767
        //     return new TransferResult({ statusCode: StatusCodes.BadUserAccessDenied });
20✔
1768
        // }
20✔
1769
        if (session.publishEngine === (subscription.publishEngine as unknown)) {
25!
UNCOV
1770
            // subscription is already in this session !!
×
UNCOV
1771
            return new TransferResult({ statusCode: StatusCodes.BadNothingToDo });
×
UNCOV
1772
        }
×
1773
        if (session === subscription.$session) {
25!
UNCOV
1774
            // subscription is already in this session !!
×
UNCOV
1775
            return new TransferResult({ statusCode: StatusCodes.BadNothingToDo });
×
UNCOV
1776
        }
×
1777

20✔
1778
        // The number of times the subscription has been transferred to an alternate client.
20✔
1779
        subscription.subscriptionDiagnostics.transferredToAltClientCount++;
20✔
1780
        // The number of times the subscription has been transferred to an alternate session for the same client.
20✔
1781
        subscription.subscriptionDiagnostics.transferredToSameClientCount++;
20✔
1782

20✔
1783
        if (subscription.$session) {
25✔
1784
            subscription.$session._unexposeSubscriptionDiagnostics(subscription);
16✔
1785
        }
16✔
1786

20✔
1787
        subscription.$session = session;
20✔
1788

20✔
1789
        await ServerSidePublishEngine.transferSubscription(subscription, session.publishEngine, sendInitialValues);
20✔
1790

20✔
1791
        session._exposeSubscriptionDiagnostics(subscription);
20✔
1792

20✔
1793
        assert(subscription.publishEngine === session.publishEngine);
20✔
1794
        // assert(session.publishEngine.subscriptionCount === nbSubscriptionBefore + 1);
20✔
1795

20✔
1796
        const result = new TransferResult({
20✔
1797
            availableSequenceNumbers: subscription.getAvailableSequenceNumbers(),
20✔
1798
            statusCode: StatusCodes.Good
20✔
1799
        });
20✔
1800

20✔
1801
        // c8 ignore next
20✔
1802
        if (doDebug) {
25!
UNCOV
1803
            debugLog("TransferResult", result.toString());
×
UNCOV
1804
        }
×
1805

20✔
1806
        return result;
20✔
1807
    }
20✔
1808

53✔
1809
    /**
53✔
1810
     * retrieve a session by its authenticationToken.
53✔
1811
     *
53✔
1812
     * @param authenticationToken
53✔
1813
     * @param activeOnly
53✔
1814
     * @return {ServerSession}
53✔
1815
     */
53✔
1816
    public getSession(authenticationToken: NodeId, activeOnly?: boolean): ServerSession | null {
53✔
1817
        if (
47,872✔
1818
            !authenticationToken ||
47,872✔
1819
            (authenticationToken.identifierType && authenticationToken.identifierType !== NodeIdType.BYTESTRING)
47,872✔
1820
        ) {
47,872✔
1821
            return null; // wrong type !
2,131✔
1822
        }
2,131✔
1823
        const key = authenticationToken.toString();
45,743✔
1824
        let session = this._sessions[key];
45,741✔
1825
        if (!activeOnly && !session) {
47,872✔
1826
            session = this._closedSessions[key];
28✔
1827
        }
28✔
1828
        return session;
45,743✔
1829
    }
45,743✔
1830

53✔
1831
    public async translateBrowsePaths(browsePaths: BrowsePath[]): Promise<BrowsePathResult[]> {
53✔
1832
        const browsePathResults: BrowsePathResult[] = [];
61✔
1833
        for (const browsePath of browsePaths) {
61✔
1834
            const result = await this.translateBrowsePath(browsePath);
115✔
1835
            browsePathResults.push(result);
115✔
1836
        }
115✔
1837
        return browsePathResults;
61✔
1838
    }
61✔
1839
    public async translateBrowsePath(browsePath: BrowsePath): Promise<BrowsePathResult> {
53✔
1840
        if (!this.addressSpace) {
122!
UNCOV
1841
            throw new Error("addressSpace is not available");
×
UNCOV
1842
        }
×
1843
        return this.addressSpace.browsePath(browsePath);
122✔
1844
    }
122✔
1845

53✔
1846
    /**
53✔
1847
     *
53✔
1848
     * performs a call to ```asyncRefresh``` on all variable nodes that provide an async refresh func.
53✔
1849
     *
53✔
1850
     * @param nodesToRefresh {Array<ReadValueId|HistoryReadValueId>}  an array containing the node to consider
53✔
1851
     * Each element of the array shall be of the form { nodeId: <xxx>, attributeIds: <value> }.
53✔
1852
     * @param maxAge {number}  the maximum age of the value to be read, in milliseconds.
53✔
1853
     * @param callback
53✔
1854
     *
53✔
1855
     */
53✔
1856
    public refreshValues(
53✔
1857
        nodesToRefresh: ReadValueId[] | HistoryReadValueId[],
23,706✔
1858
        maxAge: number,
23,706✔
1859
        /**
23,706✔
1860
         * @param err
23,706✔
1861
         * @param dataValues an array containing value read
23,706✔
1862
         * The array length matches the number of  nodeIds that are candidate for an
23,706✔
1863
         * async refresh (i.e: nodes that are of type Variable with asyncRefresh func }
23,706✔
1864
         */
23,706✔
1865
        callback: (err: Error | null, dataValues?: DataValue[]) => void
23,706✔
1866
    ): void {
23,706✔
1867
        const referenceTime = getCurrentClock();
23,706✔
1868
        maxAge && referenceTime.timestamp.setTime(referenceTime.timestamp.getTime() - maxAge);
23,706✔
1869

23,706✔
1870
        assert(typeof callback === "function");
23,706✔
1871

23,706✔
1872
        const nodeMap: Record<string, UAVariable> = {};
23,706✔
1873
        for (const nodeToRefresh of nodesToRefresh) {
23,706✔
1874
            // only consider node  for which the caller wants to read the Value attribute
247,189✔
1875
            // assuming that Value is requested if attributeId is missing,
247,189✔
1876
            if (nodeToRefresh instanceof ReadValueId && nodeToRefresh.attributeId !== AttributeIds.Value) {
247,189✔
1877
                continue;
199,369✔
1878
            }
199,369✔
1879
            // ... and that are valid object and instances of Variables ...
47,839✔
1880
            const uaNode = this.addressSpace?.findNode(nodeToRefresh.nodeId);
247,189✔
1881
            if (!uaNode || !(uaNode.nodeClass === NodeClass.Variable)) {
247,189✔
1882
                continue;
529✔
1883
            }
529✔
1884
            // ... and that have been declared as asynchronously updating
47,310✔
1885
            if (typeof (uaNode as unknown as Record<string, unknown>).refreshFunc !== "function") {
247,189✔
1886
                continue;
28,283✔
1887
            }
28,283✔
1888
            const key = uaNode.nodeId.toString();
19,039✔
1889
            if (nodeMap[key]) {
247,189✔
1890
                continue;
1✔
1891
            }
1✔
1892
            nodeMap[key] = uaNode as UAVariable;
19,039✔
1893
        }
19,007✔
1894

23,706✔
1895
        const uaVariableArray = Object.values(nodeMap);
23,706✔
1896
        if (uaVariableArray.length === 0) {
23,706✔
1897
            // nothing to do
14,048✔
1898
            callback(null, []);
14,048✔
1899
            return;
14,048✔
1900
        }
14,048✔
1901
        // perform all asyncRefresh in parallel
9,671✔
1902
        async.map(
9,671✔
1903
            uaVariableArray,
9,658✔
1904
            (uaVariable: UAVariable, inner_callback: CallbackT<DataValue>) => {
9,658✔
1905
                try {
19,007✔
1906
                    uaVariable.asyncRefresh(referenceTime, (err, dataValue) => {
19,007✔
1907
                        inner_callback(err, dataValue);
19,007✔
1908
                    });
19,007✔
1909
                } catch (err) {
19,007!
UNCOV
1910
                    const _err = err as Error;
×
UNCOV
1911
                    errorLog("asyncRefresh internal error", _err.message);
×
UNCOV
1912
                    inner_callback(_err);
×
UNCOV
1913
                }
×
1914
            },
9,659✔
1915
            (err?: Error | null, arrResult?: (DataValue | undefined)[]) => {
9,658✔
1916
                callback(err || null, arrResult as DataValue[]);
9,658✔
1917
            }
9,658✔
1918
        );
9,658✔
1919
    }
9,671✔
1920

53✔
1921
    private _exposeSubscriptionDiagnostics(subscription: Subscription): void {
53✔
1922
        try {
457✔
1923
            debugLog("ServerEngine#_exposeSubscriptionDiagnostics", subscription.subscriptionId);
457✔
1924
            const subscriptionDiagnosticsArray = this._getServerSubscriptionDiagnosticsArrayNode();
457✔
1925
            const subscriptionDiagnostics = subscription.subscriptionDiagnostics;
457✔
1926
            assert((subscriptionDiagnostics as unknown as Record<string, unknown>).$subscription === subscription);
457✔
1927
            assert(subscriptionDiagnostics instanceof SubscriptionDiagnosticsDataType);
457✔
1928

457✔
1929
            if (subscriptionDiagnostics && subscriptionDiagnosticsArray) {
457✔
1930
                addElement(subscriptionDiagnostics, subscriptionDiagnosticsArray);
455✔
1931
            }
455✔
1932
        } catch (err) {
457!
UNCOV
1933
            errorLog("_exposeSubscriptionDiagnostics err", err);
×
UNCOV
1934
        }
×
1935
    }
457✔
1936

53✔
1937
    protected _unexposeSubscriptionDiagnostics(subscription: Subscription): void {
53✔
1938
        const serverSubscriptionDiagnosticsArray = this._getServerSubscriptionDiagnosticsArrayNode();
457✔
1939
        const subscriptionDiagnostics = subscription.subscriptionDiagnostics;
457✔
1940
        assert(subscriptionDiagnostics instanceof SubscriptionDiagnosticsDataType);
457✔
1941
        if (subscriptionDiagnostics && serverSubscriptionDiagnosticsArray) {
457✔
1942
            removeElement(serverSubscriptionDiagnosticsArray, (a) => a.subscriptionId === subscription.id);
455✔
1943
            /*assert(
455✔
1944
                !(subscriptionDiagnosticsArray as any)[subscription.id],
455✔
1945
                " subscription node must have been removed from subscriptionDiagnosticsArray"
455✔
1946
            );
455✔
1947
            */
455✔
1948
        }
455✔
1949
        debugLog("ServerEngine#_unexposeSubscriptionDiagnostics", subscription.subscriptionId);
457✔
1950
    }
457✔
1951

53✔
1952
    /**
53✔
1953
     * create a new subscription
53✔
1954
     * @return {Subscription}
53✔
1955
     */
53✔
1956
    public _createSubscriptionOnSession(session: ServerSession, request: CreateSubscriptionRequestLike): Subscription {
53✔
1957
        assert(Object.prototype.hasOwnProperty.call(request, "requestedPublishingInterval")); // Duration
457✔
1958
        assert(Object.prototype.hasOwnProperty.call(request, "requestedLifetimeCount")); // Counter
457✔
1959
        assert(Object.prototype.hasOwnProperty.call(request, "requestedMaxKeepAliveCount")); // Counter
457✔
1960
        assert(Object.prototype.hasOwnProperty.call(request, "maxNotificationsPerPublish")); // Counter
457✔
1961
        assert(Object.prototype.hasOwnProperty.call(request, "publishingEnabled")); // Boolean
457✔
1962
        assert(Object.prototype.hasOwnProperty.call(request, "priority")); // Byte
457✔
1963

457✔
1964
        // adjust publishing parameters
457✔
1965
        const publishingInterval = request.requestedPublishingInterval || 0;
457✔
1966
        const maxKeepAliveCount = request.requestedMaxKeepAliveCount || 0;
457✔
1967
        const lifeTimeCount = request.requestedLifetimeCount || 0;
457✔
1968

457✔
1969
        const subscription = new Subscription({
457✔
1970
            id: _get_next_subscriptionId(),
457✔
1971
            lifeTimeCount,
457✔
1972
            maxKeepAliveCount,
457✔
1973
            maxNotificationsPerPublish: request.maxNotificationsPerPublish,
457✔
1974
            priority: request.priority || 0,
457✔
1975
            publishEngine: session.publishEngine as unknown as ServerSidePublishEngine, //
457✔
1976
            publishingEnabled: request.publishingEnabled,
457✔
1977
            publishingInterval,
457✔
1978
            // -------------------
457✔
1979
            sessionId: NodeId.nullNodeId,
457✔
1980
            globalCounter: this._globalCounter,
457✔
1981
            serverCapabilities: this.serverCapabilities // shared
457✔
1982
        });
457✔
1983

457✔
1984
        // add subscriptionDiagnostics
457✔
1985
        this._exposeSubscriptionDiagnostics(subscription);
457✔
1986

457✔
1987
        assert(subscription.publishEngine === session.publishEngine);
457✔
1988
        session.publishEngine.add_subscription(subscription);
457✔
1989

457✔
1990
        // eslint-disable-next-line @typescript-eslint/no-this-alias
457✔
1991
        const engine = this;
457✔
1992
        subscription.once("terminated", function (this: Subscription) {
457✔
1993
            engine._unexposeSubscriptionDiagnostics(this);
457✔
1994
        });
457✔
1995

457✔
1996
        return subscription;
457✔
1997
    }
457✔
1998

53✔
1999
    /**
53✔
2000
     */
53✔
2001
    private __internal_bindMethod(nodeId: NodeId, func: MethodFunctor) {
53✔
2002
        assert(typeof func === "function");
1,260✔
2003
        assert(nodeId instanceof NodeId);
1,260✔
2004

1,260✔
2005
        const methodNode = this.addressSpace?.findNode(nodeId) as UAMethod | undefined;
1,260✔
2006
        if (!methodNode) {
1,260✔
2007
            return;
196✔
2008
        }
196✔
2009
        if (methodNode?.bindMethod) {
1,260✔
2010
            methodNode.bindMethod(func);
1,064✔
2011
        } else {
1,260!
2012
            /* c8 ignore next */
2✔
2013
            warningLog(
2✔
UNCOV
2014
                chalk.yellow("WARNING:  cannot bind a method with id ") +
×
UNCOV
2015
                    chalk.cyan(nodeId.toString()) +
×
UNCOV
2016
                    chalk.yellow(". please check your nodeset.xml file or add this node programmatically")
×
UNCOV
2017
            );
×
UNCOV
2018
            warningLog(traceFromThisProjectOnly());
×
UNCOV
2019
        }
×
2020
    }
1,260✔
2021

53✔
2022
    private _getServerSubscriptionDiagnosticsArrayNode(): UADynamicVariableArray<SubscriptionDiagnosticsDataType> | null {
53✔
2023
        // c8 ignore next
914✔
2024
        if (!this.addressSpace) {
914!
UNCOV
2025
            doDebug && debugLog("ServerEngine#_getServerSubscriptionDiagnosticsArray : no addressSpace");
×
UNCOV
2026

×
UNCOV
2027
            return null; // no addressSpace
×
UNCOV
2028
        }
×
2029
        const subscriptionDiagnosticsType = this.addressSpace.findVariableType("SubscriptionDiagnosticsType");
914✔
2030
        if (!subscriptionDiagnosticsType) {
914✔
2031
            doDebug && debugLog(`ServerEngine#_getServerSubscriptionDiagnosticsArray : cannot find SubscriptionDiagnosticsType`);
4!
2032
        }
4✔
2033

914✔
2034
        // SubscriptionDiagnosticsArray = i=2290
914✔
2035
        const subscriptionDiagnosticsArrayNode = this.addressSpace.findNode(
914✔
2036
            makeNodeId(VariableIds.Server_ServerDiagnostics_SubscriptionDiagnosticsArray)
914✔
2037
        );
914✔
2038

914✔
2039
        return subscriptionDiagnosticsArrayNode as UADynamicVariableArray<SubscriptionDiagnosticsDataType>;
914✔
2040
    }
914✔
2041
}
53!
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc