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

node-opcua / node-opcua / 23974043205

04 Apr 2026 07:17AM UTC coverage: 92.589% (+0.01%) from 92.576%
23974043205

push

github

erossignon
chore: fix Mocha.Suite.settimeout misused

18408 of 21832 branches covered (84.32%)

161708 of 174651 relevant lines covered (92.59%)

461089.77 hits per line

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

90.33
/packages/node-opcua-server/source/opcua_server.ts
1
/* eslint-disable complexity */
2✔
2
/**
2✔
3
 * @module node-opcua-server
2✔
4
 */
2✔
5
import { randomBytes } from "node:crypto";
2✔
6
import { callbackify, types } from "node:util";
2✔
7

2✔
8
import chalk from "chalk";
2✔
9
import {
2✔
10
    type AddressSpace,
2✔
11
    type EventTypeLike,
2✔
12
    type IRolePolicyOverride,
2✔
13
    type IServerBase,
2✔
14
    type ISessionContext,
2✔
15
    innerBrowse,
2✔
16
    innerBrowseNext,
2✔
17
    type PseudoVariant,
2✔
18
    type PseudoVariantBoolean,
2✔
19
    type PseudoVariantByteString,
2✔
20
    type PseudoVariantDateTime,
2✔
21
    type PseudoVariantDuration,
2✔
22
    type PseudoVariantExtensionObject,
2✔
23
    type PseudoVariantExtensionObjectArray,
2✔
24
    type PseudoVariantLocalizedText,
2✔
25
    type PseudoVariantNodeId,
2✔
26
    type PseudoVariantString,
2✔
27
    type PseudoVariantStringPredefined,
2✔
28
    type RaiseEventData,
2✔
29
    SessionContext,
2✔
30
    type UAEventType,
2✔
31
    type UAObject,
2✔
32
    type UAObjectType,
2✔
33
    type UAVariable,
2✔
34
    type UAView
2✔
35
} from "node-opcua-address-space";
2✔
36
import { assert } from "node-opcua-assert";
2✔
37
import type { ByteString, UAString } from "node-opcua-basic-types";
2✔
38
import { getDefaultCertificateManager, type OPCUACertificateManager } from "node-opcua-certificate-manager";
2✔
39
import { SecretHolder, ServerState } from "node-opcua-common";
2✔
40
import { type Certificate, combine_der, exploreCertificate, type Nonce } from "node-opcua-crypto/web";
2✔
41
import {
2✔
42
    AttributeIds,
2✔
43
    filterDiagnosticOperationLevel,
2✔
44
    filterDiagnosticServiceLevel,
2✔
45
    LocalizedText,
2✔
46
    NodeClass,
2✔
47
    RESPONSE_DIAGNOSTICS_MASK_ALL
2✔
48
} from "node-opcua-data-model";
2✔
49
import { DataValue } from "node-opcua-data-value";
2✔
50
import { dump, make_debugLog, make_errorLog, make_warningLog } from "node-opcua-debug";
2✔
51
import { extractFullyQualifiedDomainName, getFullyQualifiedDomainName, isIPAddress } from "node-opcua-hostname";
2✔
52
import type { NodeId } from "node-opcua-nodeid";
2✔
53
import { ObjectRegistry } from "node-opcua-object-registry";
2✔
54
import {
2✔
55
    coerceSecurityPolicy,
2✔
56
    computeSignature,
2✔
57
    fromURI,
2✔
58
    getCryptoFactory,
2✔
59
    type Message,
2✔
60
    MessageSecurityMode,
2✔
61
    nonceAlreadyBeenUsed,
2✔
62
    type Request,
2✔
63
    type Response,
2✔
64
    SecurityPolicy,
2✔
65
    type ServerSecureChannelLayer,
2✔
66
    type SignatureData,
2✔
67
    verifySignature
2✔
68
} from "node-opcua-secure-channel";
2✔
69
import { BrowseNextRequest, BrowseNextResponse, BrowseRequest, BrowseResponse } from "node-opcua-service-browse";
2✔
70
import { CallRequest, CallResponse } from "node-opcua-service-call";
2✔
71
import { ApplicationType, UserTokenType } from "node-opcua-service-endpoints";
2✔
72
import { HistoryReadRequest, HistoryReadResponse, type HistoryReadResult, HistoryUpdateResponse } from "node-opcua-service-history";
2✔
73
import {
2✔
74
    AddNodesResponse,
2✔
75
    AddReferencesResponse,
2✔
76
    DeleteNodesResponse,
2✔
77
    DeleteReferencesResponse
2✔
78
} from "node-opcua-service-node-management";
2✔
79
import { QueryFirstResponse, QueryNextResponse } from "node-opcua-service-query";
2✔
80
import { ReadRequest, ReadResponse, ReadValueId, TimestampsToReturn } from "node-opcua-service-read";
2✔
81
import {
2✔
82
    RegisterNodesRequest,
2✔
83
    RegisterNodesResponse,
2✔
84
    UnregisterNodesRequest,
2✔
85
    UnregisterNodesResponse
2✔
86
} from "node-opcua-service-register-node";
2✔
87
import {
2✔
88
    ActivateSessionRequest,
2✔
89
    ActivateSessionResponse,
2✔
90
    AnonymousIdentityToken,
2✔
91
    CloseSessionRequest,
2✔
92
    CloseSessionResponse,
2✔
93
    CreateSessionRequest,
2✔
94
    CreateSessionResponse,
2✔
95
    UserNameIdentityToken,
2✔
96
    X509IdentityToken
2✔
97
} from "node-opcua-service-session";
2✔
98
import {
2✔
99
    CreateMonitoredItemsRequest,
2✔
100
    CreateMonitoredItemsResponse,
2✔
101
    CreateSubscriptionRequest,
2✔
102
    CreateSubscriptionResponse,
2✔
103
    DeleteMonitoredItemsRequest,
2✔
104
    DeleteMonitoredItemsResponse,
2✔
105
    DeleteSubscriptionsRequest,
2✔
106
    DeleteSubscriptionsResponse,
2✔
107
    ModifyMonitoredItemsRequest,
2✔
108
    ModifyMonitoredItemsResponse,
2✔
109
    ModifySubscriptionRequest,
2✔
110
    ModifySubscriptionResponse,
2✔
111
    MonitoredItemModifyResult,
2✔
112
    PublishRequest,
2✔
113
    PublishResponse,
2✔
114
    RepublishRequest,
2✔
115
    RepublishResponse,
2✔
116
    SetMonitoringModeRequest,
2✔
117
    SetMonitoringModeResponse,
2✔
118
    SetPublishingModeRequest,
2✔
119
    SetPublishingModeResponse,
2✔
120
    SetTriggeringRequest,
2✔
121
    SetTriggeringResponse,
2✔
122
    TransferSubscriptionsRequest,
2✔
123
    TransferSubscriptionsResponse
2✔
124
} from "node-opcua-service-subscription";
2✔
125
import {
2✔
126
    TranslateBrowsePathsToNodeIdsRequest,
2✔
127
    TranslateBrowsePathsToNodeIdsResponse
2✔
128
} from "node-opcua-service-translate-browse-path";
2✔
129
import { WriteRequest, WriteResponse } from "node-opcua-service-write";
2✔
130
import { type CallbackT, StatusCode, StatusCodes } from "node-opcua-status-code";
2✔
131
import {
2✔
132
    type ApplicationDescriptionOptions,
2✔
133
    type BrowseDescriptionOptions,
2✔
134
    type BrowseResult,
2✔
135
    type BuildInfo,
2✔
136
    type BuildInfoOptions,
2✔
137
    CancelResponse,
2✔
138
    EndpointDescription,
2✔
139
    IssuedIdentityToken,
2✔
140
    type MonitoredItemCreateResult,
2✔
141
    type MonitoredItemModifyRequest,
2✔
142
    MonitoringMode,
2✔
143
    ServiceFault,
2✔
144
    type UserIdentityToken,
2✔
145
    type UserTokenPolicy
2✔
146
} from "node-opcua-types";
2✔
147
import { isNullOrUndefined, matchUri } from "node-opcua-utils";
2✔
148
import { DataType, type Variant, VariantArrayType } from "node-opcua-variant";
2✔
149
import { withCallback } from "thenify-ex";
2✔
150

2✔
151
import { OPCUABaseServer, type OPCUABaseServerOptions } from "./base_server";
2✔
152
import { extractPasswordFromDecryptedBlob } from "./extract_password_from_blob";
2✔
153
import { Factory } from "./factory";
2✔
154
import type { IChannelData } from "./i_channel_data";
2✔
155
import type { IRegisterServerManager } from "./i_register_server_manager";
2✔
156
import type { ISocketData } from "./i_socket_data";
2✔
157
import { MonitoredItem } from "./monitored_item";
2✔
158
import { RegisterServerManager } from "./register_server_manager";
2✔
159
import { RegisterServerManagerHidden } from "./register_server_manager_hidden";
2✔
160
import { RegisterServerManagerMDNSONLY } from "./register_server_manager_mdns_only";
2✔
161
import type { SamplingFunc } from "./sampling_func";
2✔
162
import type { ServerCapabilitiesOptions } from "./server_capabilities";
2✔
163
import {
2✔
164
    type AdvertisedEndpoint,
2✔
165
    type EndpointDescriptionEx,
2✔
166
    type IServerTransportSettings,
2✔
167
    normalizeAdvertisedEndpoints,
2✔
168
    OPCUAServerEndPoint,
2✔
169
    parseOpcTcpUrl
2✔
170
} from "./server_end_point";
2✔
171
import { type ClosingReason, type CreateSessionOption, ServerEngine } from "./server_engine";
2✔
172
import type { ServerSession } from "./server_session";
2✔
173
import type { CreateMonitoredItemHook, DeleteMonitoredItemHook, Subscription } from "./server_subscription";
2✔
174
import { makeUserManager, type UAUserManagerBase, type UserManagerOptions } from "./user_manager";
2✔
175
import { bindRoleSet } from "./user_manager_ua";
2✔
176

2✔
177
function isSubscriptionIdInvalid(subscriptionId: number): boolean {
4✔
178
    return subscriptionId < 0 || subscriptionId >= 0xffffffff;
4✔
179
}
4✔
180
const package_info = require("../package.json");
2✔
181
const debugLog = make_debugLog(__filename);
2✔
182
const errorLog = make_errorLog(__filename);
2✔
183
const warningLog = make_warningLog(__filename);
2✔
184

2✔
185
const default_maxConnectionsPerEndpoint = 10;
2✔
186

2✔
187
function g_sendError(
80✔
188
    channel: ServerSecureChannelLayer,
80✔
189
    message: Message,
80✔
190
    _ResponseClass: ResponseClassType,
80✔
191
    statusCode: StatusCode
80✔
192
): void {
80✔
193
    const response = new ServiceFault({
80✔
194
        responseHeader: { serviceResult: statusCode }
80✔
195
    });
80✔
196
    channel.send_response("MSG", response, message);
80✔
197
}
80✔
198

2✔
199
const default_build_info: BuildInfoOptions = {
2✔
200
    manufacturerName: "NodeOPCUA : MIT Licence ( see http://node-opcua.github.io/)",
2✔
201
    productName: "NodeOPCUA-Server",
2✔
202
    productUri: null, // << should be same as default_server_info.productUri?
2✔
203
    softwareVersion: package_info.version,
2✔
204
    buildNumber: "0",
2✔
205
    buildDate: new Date(2020, 1, 1)
2✔
206
    // xx buildDate: fs.statSync(package_json_file).mtime
2✔
207
};
2✔
208

2✔
209
const minSessionTimeout = 100; // 100 milliseconds
2✔
210
const defaultSessionTimeout = 1000 * 30; // 30 seconds
2✔
211
const maxSessionTimeout = 1000 * 60 * 50; // 50 minutes
2✔
212
let unnamed_session_count = 0;
2✔
213

2✔
214
type ResponseClassType =
2✔
215
    | typeof BrowseResponse
2✔
216
    | typeof BrowseNextResponse
2✔
217
    | typeof CallResponse
2✔
218
    | typeof CreateMonitoredItemsResponse
2✔
219
    | typeof CreateSubscriptionResponse
2✔
220
    | typeof DeleteSubscriptionsResponse
2✔
221
    | typeof HistoryReadResponse
2✔
222
    | typeof ModifyMonitoredItemsResponse
2✔
223
    | typeof ModifySubscriptionResponse
2✔
224
    | typeof ReadResponse
2✔
225
    | typeof RegisterNodesResponse
2✔
226
    | typeof RepublishResponse
2✔
227
    | typeof SetPublishingModeResponse
2✔
228
    | typeof SetTriggeringResponse
2✔
229
    | typeof TransferSubscriptionsResponse
2✔
230
    | typeof TranslateBrowsePathsToNodeIdsResponse
2✔
231
    | typeof UnregisterNodesResponse
2✔
232
    | typeof WriteResponse;
2✔
233

2✔
234
function _adjust_session_timeout(sessionTimeout: number) {
1,037✔
235
    let revisedSessionTimeout = sessionTimeout || defaultSessionTimeout;
1,037✔
236
    revisedSessionTimeout = Math.min(revisedSessionTimeout, maxSessionTimeout);
1,037✔
237
    revisedSessionTimeout = Math.max(revisedSessionTimeout, minSessionTimeout);
1,037✔
238
    return revisedSessionTimeout;
1,037✔
239
}
1,037✔
240

2✔
241
function channel_has_session(channel: ServerSecureChannelLayer, session: ServerSession): boolean {
44,544✔
242
    if (session.channel === channel) {
44,544✔
243
        assert(Object.hasOwn(channel.sessionTokens, session.authenticationToken.toString()));
44,543✔
244
        return true;
44,543✔
245
    }
44,543✔
246
    return false;
1✔
247
}
1✔
248

2✔
249
function moveSessionToChannel(session: ServerSession, channel: ServerSecureChannelLayer) {
21✔
250
    debugLog("moveSessionToChannel sessionId", session.nodeId, " channelId=", channel.channelId);
21✔
251
    if (session.publishEngine) {
21✔
252
        session.publishEngine.cancelPendingPublishRequestBeforeChannelChange();
21✔
253
    }
21✔
254

21✔
255
    session._detach_channel();
21✔
256
    session._attach_channel(channel);
21✔
257

21✔
258
    assert(session.channel?.channelId === channel.channelId);
21✔
259
}
21✔
260

2✔
261
async function _attempt_to_close_some_old_unactivated_session(server: OPCUAServer) {
26✔
262
    const session = server.engine?.getOldestInactiveSession();
26✔
263
    if (session) {
26✔
264
        await server.engine?.closeSession(session.authenticationToken, false, "Forcing");
14✔
265
    }
14✔
266
}
26✔
267

2✔
268
function getRequiredEndpointInfo(endpoint: EndpointDescription) {
7,592✔
269
    assert(endpoint instanceof EndpointDescription);
7,592✔
270
    // https://reference.opcfoundation.org/v104/Core/docs/Part4/5.6.2/
7,592✔
271
    // https://reference.opcfoundation.org/v105/Core/docs/Part4/5.6.2/
7,592✔
272
    const e = new EndpointDescription({
7,592✔
273
        endpointUrl: endpoint.endpointUrl,
7,592✔
274
        securityLevel: endpoint.securityLevel,
7,592✔
275
        securityMode: endpoint.securityMode,
7,592✔
276
        securityPolicyUri: endpoint.securityPolicyUri,
7,592✔
277
        server: {
7,592✔
278
            applicationUri: endpoint.server.applicationUri,
7,592✔
279
            applicationType: endpoint.server.applicationType,
7,592✔
280
            applicationName: endpoint.server.applicationName,
7,592✔
281
            productUri: endpoint.server.productUri
7,592✔
282
        },
7,592✔
283
        transportProfileUri: endpoint.transportProfileUri,
7,592✔
284
        userIdentityTokens: endpoint.userIdentityTokens
7,592✔
285
    });
7,592✔
286
    // reduce even further by explicitly setting unwanted members to null
7,592✔
287
    e.server.applicationName = new LocalizedText({ text: "" });
7,592✔
288
    // xx e.server.applicationType = null as any;
7,592✔
289
    e.server.gatewayServerUri = null;
7,592✔
290
    e.server.discoveryProfileUri = null;
7,592✔
291
    e.server.discoveryUrls = null;
7,592✔
292
    e.serverCertificate = null as unknown as Buffer;
7,592✔
293
    return e;
7,592✔
294
}
7,592✔
295

2✔
296
// serverUri  String This value is only specified if the EndpointDescription has a gatewayServerUri.
2✔
297
//            This value is the applicationUri from the EndpointDescription which is the applicationUri for the
2✔
298
//            underlying Server. The type EndpointDescription is defined in 7.10.
2✔
299

2✔
300
function _serverEndpointsForCreateSessionResponse(server: OPCUAServer, endpointUrl: string | null, serverUri: string | null) {
1,035✔
301
    serverUri = null; // unused then
1,035✔
302

1,035✔
303
    // https://reference.opcfoundation.org/v104/Core/docs/Part4/5.6.2/
1,035✔
304
    // https://reference.opcfoundation.org/v105/Core/docs/Part4/5.6.2/
1,035✔
305
    return server
1,035✔
306
        .findMatchingEndpoints(endpointUrl)
1,035✔
307
        .filter((e) => !(e as unknown as { restricted: boolean }).restricted) // remove restricted endpoints
1,035✔
308
        .filter((e) => matchUri(e.endpointUrl, endpointUrl))
1,035✔
309
        .map(getRequiredEndpointInfo);
1,035✔
310
}
1,035✔
311

2✔
312
function adjustSecurityPolicy(channel: ServerSecureChannelLayer, userTokenPolicy_securityPolicyUri: UAString): SecurityPolicy {
162✔
313
    // check that userIdentityToken
162✔
314
    let securityPolicy = fromURI(userTokenPolicy_securityPolicyUri);
162✔
315

162✔
316
    // if the security policy is not specified we use the session security policy
162✔
317
    if (securityPolicy === SecurityPolicy.Invalid) {
162✔
318
        securityPolicy = fromURI(channel.securityPolicy);
56✔
319
        assert(securityPolicy !== SecurityPolicy.Invalid);
56✔
320
    }
56✔
321
    return securityPolicy;
162✔
322
}
162✔
323

2✔
324
function findUserTokenByPolicy(
2,049✔
325
    endpoint_description: EndpointDescription,
2,049✔
326
    userTokenType: UserTokenType,
2,049✔
327
    policyId: SecurityPolicy | string | null
2,049✔
328
): UserTokenPolicy | null {
2,049✔
329
    assert(endpoint_description instanceof EndpointDescription);
2,049✔
330
    if (!endpoint_description.userIdentityTokens) {
2,049!
331
        return null;
×
332
    }
×
333
    const r = endpoint_description.userIdentityTokens.filter(
2,049✔
334
        (userIdentity: UserTokenPolicy) =>
2,049✔
335
            userIdentity.tokenType === userTokenType && (!policyId || userIdentity.policyId === policyId)
9,487✔
336
    );
2,049✔
337
    return r.length === 0 ? null : r[0];
2,049!
338
}
2,049✔
339

2✔
340
function findUserTokenPolicy(endpoint_description: EndpointDescription, userTokenType: UserTokenType): UserTokenPolicy | null {
×
341
    assert(endpoint_description instanceof EndpointDescription);
×
342
    if (!endpoint_description.userIdentityTokens) {
×
343
        return null;
×
344
    }
×
345
    const r = endpoint_description.userIdentityTokens.filter((userIdentity: UserTokenPolicy) => {
×
346
        assert(userIdentity.tokenType !== undefined);
×
347
        return userIdentity.tokenType === userTokenType;
×
348
    });
×
349
    return r.length === 0 ? null : r[0];
×
350
}
×
351

2✔
352
function createAnonymousIdentityToken(endpoint_desc: EndpointDescription) {
×
353
    assert(endpoint_desc instanceof EndpointDescription);
×
354
    const userTokenPolicy = findUserTokenPolicy(endpoint_desc, UserTokenType.Anonymous);
×
355
    if (!userTokenPolicy) {
×
356
        throw new Error("Cannot find ANONYMOUS user token policy in end point description");
×
357
    }
×
358
    return new AnonymousIdentityToken({ policyId: userTokenPolicy.policyId });
×
359
}
×
360

2✔
361
function sameIdentityToken(token1?: UserIdentityToken, token2?: UserIdentityToken): boolean {
16✔
362
    if (!token1 && !token2) {
16!
363
        return true;
×
364
    }
×
365
    if (!token1 || !token2) {
16!
366
        return false;
×
367
    }
×
368
    if (token1 instanceof UserNameIdentityToken) {
16✔
369
        if (!(token2 instanceof UserNameIdentityToken)) {
4✔
370
            return false;
1✔
371
        }
1✔
372
        if (token1.userName !== token2.userName) {
4!
373
            return false;
×
374
        }
×
375
        if (token1.password.toString("hex") !== token2.password.toString("hex")) {
3✔
376
            // note pasword hash may be different from two request and cannot be verified at this stage
3✔
377
            // we assume that we have a valid password
3✔
378
            // NOT CALLING return false;
3✔
379
        }
3✔
380
        return true;
3✔
381
    } else if (token1 instanceof AnonymousIdentityToken) {
16✔
382
        if (!(token2 instanceof AnonymousIdentityToken)) {
12!
383
            return false;
×
384
        }
×
385
        if (token1.policyId !== token2.policyId) {
12!
386
            return false;
×
387
        }
×
388
        return true;
12✔
389
    }
12✔
390
    assert(false, " Not implemented yet");
×
391
    return false;
×
392
}
×
393
function getTokenType(userIdentityToken: UserIdentityToken): UserTokenType {
2,049✔
394
    if (userIdentityToken instanceof AnonymousIdentityToken) {
2,049✔
395
        return UserTokenType.Anonymous;
1,886✔
396
    } else if (userIdentityToken instanceof UserNameIdentityToken) {
2,049✔
397
        return UserTokenType.UserName;
155✔
398
    } else if (userIdentityToken instanceof IssuedIdentityToken) {
163!
399
        return UserTokenType.IssuedToken;
×
400
    } else if (userIdentityToken instanceof X509IdentityToken) {
8✔
401
        return UserTokenType.Certificate;
8✔
402
    }
8✔
403
    return UserTokenType.Invalid;
×
404
}
×
405
function thumbprint(certificate?: Certificate | null): string {
38✔
406
    return certificate ? certificate.toString("base64") : "";
38✔
407
}
38✔
408

2✔
409
/*=== private
2✔
410
 *
2✔
411
 * perform the read operation on a given node for a monitored item.
2✔
412
 * this method DOES NOT apply to Variable Values attribute
2✔
413
 *
2✔
414
 * @param self
2✔
415
 * @param oldValue
2✔
416
 * @param node
2✔
417
 * @param itemToMonitor
2✔
418
 * @private
2✔
419
 */
2✔
420
function monitoredItem_read_and_record_value(
×
421
    self: MonitoredItem,
×
422
    context: ISessionContext,
×
423
    oldValue: DataValue,
×
424
    node: UAVariable,
×
425
    itemToMonitor: ReadValueId,
×
426
    callback: (err: Error | null, dataValue?: DataValue) => void
×
427
) {
×
428
    assert(self instanceof MonitoredItem);
×
429
    assert(oldValue instanceof DataValue);
×
430
    assert(itemToMonitor.attributeId === AttributeIds.Value);
×
431

×
432
    const dataValue = node.readAttribute(context, itemToMonitor.attributeId, itemToMonitor.indexRange, itemToMonitor.dataEncoding);
×
433

×
434
    callback(null, dataValue);
×
435
}
×
436

2✔
437
/*== private
2✔
438
 * this method applies to Variable Values attribute
2✔
439
 * @private
2✔
440
 */
2✔
441
function monitoredItem_read_and_record_value_async(
304,059✔
442
    self: MonitoredItem,
304,059✔
443
    context: ISessionContext,
304,059✔
444
    oldValue: DataValue,
304,059✔
445
    node: UAVariable,
304,059✔
446
    itemToMonitor: ReadValueId,
304,059✔
447
    callback: (err: Error | null, dataValue?: DataValue) => void
304,059✔
448
) {
304,059✔
449
    assert(context instanceof SessionContext);
304,059✔
450
    assert(itemToMonitor.attributeId === AttributeIds.Value);
304,059✔
451
    assert(self instanceof MonitoredItem);
304,059✔
452
    assert(oldValue instanceof DataValue);
304,059✔
453
    // do it asynchronously ( this is only valid for value attributes )
304,059✔
454
    assert(itemToMonitor.attributeId === AttributeIds.Value);
304,059✔
455

304,059✔
456
    node.readValueAsync(context, (err: Error | null, dataValue?: DataValue) => {
304,059✔
457
        callback(err, dataValue);
304,059✔
458
    });
304,059✔
459
}
304,059✔
460

2✔
461
function build_scanning_node_function(addressSpace: AddressSpace, itemToMonitor: ReadValueId): SamplingFunc {
14,682✔
462
    assert(itemToMonitor instanceof ReadValueId);
14,682✔
463

14,682✔
464
    const node = addressSpace.findNode(itemToMonitor.nodeId) as UAVariable;
14,682✔
465

14,682✔
466
    /* c8 ignore next */
2✔
467
    if (!node) {
2✔
468
        errorLog(" INVALID NODE ID  , ", itemToMonitor.nodeId.toString());
×
469
        dump(itemToMonitor);
×
470
        return (
×
471
            _sessionContext: ISessionContext,
×
472
            _oldData: DataValue,
×
473
            callback: (err: Error | null, dataValue?: DataValue) => void
×
474
        ) => {
×
475
            callback(
×
476
                null,
×
477
                new DataValue({
×
478
                    statusCode: StatusCodes.BadNodeIdUnknown,
×
479
                    value: { dataType: DataType.Null, value: 0 }
×
480
                })
×
481
            );
×
482
        };
×
483
    }
×
484

14,682✔
485
    ///// !!monitoredItem.setNode(node);
14,682✔
486

14,682✔
487
    if (itemToMonitor.attributeId === AttributeIds.Value) {
14,682✔
488
        const monitoredItem_read_and_record_value_func =
14,564✔
489
            itemToMonitor.attributeId === AttributeIds.Value && typeof node.readValueAsync === "function"
14,564✔
490
                ? monitoredItem_read_and_record_value_async
14,564✔
491
                : monitoredItem_read_and_record_value;
14,564!
492

14,564✔
493
        return function func(
14,564✔
494
            this: MonitoredItem,
304,059✔
495
            sessionContext: ISessionContext,
304,059✔
496
            oldDataValue: DataValue,
304,059✔
497
            callback: (err: Error | null, dataValue?: DataValue) => void
304,059✔
498
        ) {
304,059✔
499
            assert(this instanceof MonitoredItem);
304,059✔
500
            assert(oldDataValue instanceof DataValue);
304,059✔
501
            assert(typeof callback === "function");
304,059✔
502
            monitoredItem_read_and_record_value_func(this, sessionContext, oldDataValue, node, itemToMonitor, callback);
304,059✔
503
        };
14,564✔
504
    } else {
14,682✔
505
        // Attributes, other than the  Value  Attribute, are only monitored for a change in value.
118✔
506
        // The filter is not used for these  Attributes. Any change in value for these  Attributes
118✔
507
        // causes a  Notification  to be  generated.
118✔
508

118✔
509
        // only record value when it has changed
118✔
510
        return function func(
118✔
511
            this: MonitoredItem,
×
512
            sessionContext: ISessionContext,
×
513
            oldDataValue: DataValue,
×
514
            callback: (err: Error | null, dataValue?: DataValue) => void
×
515
        ) {
×
516
            assert(this instanceof MonitoredItem);
×
517
            assert(oldDataValue instanceof DataValue);
×
518
            assert(typeof callback === "function");
×
519
            const newDataValue = node.readAttribute(sessionContext, itemToMonitor.attributeId);
×
520
            callback(null, newDataValue);
×
521
        };
118✔
522
    }
118✔
523
}
14,682✔
524

2✔
525
function prepareMonitoredItem(_context: ISessionContext, addressSpace: AddressSpace, monitoredItem: MonitoredItem) {
14,682✔
526
    const itemToMonitor = monitoredItem.itemToMonitor;
14,682✔
527
    const readNodeFunc = build_scanning_node_function(addressSpace, itemToMonitor);
14,682✔
528
    monitoredItem.samplingFunc = readNodeFunc;
14,682✔
529
}
14,682✔
530

2✔
531
function isMonitoringModeValid(monitoringMode: MonitoringMode): boolean {
16✔
532
    assert(MonitoringMode.Invalid !== undefined);
16✔
533
    return monitoringMode !== MonitoringMode.Invalid && monitoringMode <= MonitoringMode.Reporting;
16✔
534
}
16✔
535

2✔
536
function _installRegisterServerManager(self: OPCUAServer) {
301✔
537
    assert(self instanceof OPCUAServer);
301✔
538
    assert(!self.registerServerManager);
301✔
539

301✔
540
    /* c8 ignore next */
2✔
541
    if (!self.registerServerMethod) {
2✔
542
        throw new Error("Internal Error");
×
543
    }
×
544

301✔
545
    switch (self.registerServerMethod) {
301✔
546
        case RegisterServerMethod.HIDDEN:
301✔
547
            self.registerServerManager = new RegisterServerManagerHidden({
185✔
548
                server: self
185✔
549
            });
185✔
550
            break;
185✔
551
        case RegisterServerMethod.MDNS:
301✔
552
            self.registerServerManager = new RegisterServerManagerMDNSONLY({
1✔
553
                server: self
1✔
554
            });
1✔
555
            break;
1✔
556
        case RegisterServerMethod.LDS:
301✔
557
            self.registerServerManager = new RegisterServerManager({
115✔
558
                discoveryServerEndpointUrl: self.discoveryServerEndpointUrl,
115✔
559
                server: self
115✔
560
            });
115✔
561
            break;
115✔
562
        /* c8 ignore next */
2✔
563
        default:
2✔
564
            throw new Error("Invalid switch");
×
565
    }
301✔
566

301✔
567
    self.registerServerManager.on("serverRegistrationPending", () => {
301✔
568
        /**
129✔
569
         * emitted when the server is trying to registered the LDS
129✔
570
         * but when the connection to the lds has failed
129✔
571
         * serverRegistrationPending is sent when the backoff signal of the
129✔
572
         * connection process is raised
129✔
573
         * @event serverRegistrationPending
129✔
574
         */
129✔
575
        debugLog("serverRegistrationPending");
129✔
576
        self.emit("serverRegistrationPending");
129✔
577
    });
301✔
578
    self.registerServerManager.on("serverRegistered", () => {
301✔
579
        /**
102✔
580
         * emitted when the server is successfully registered to the LDS
102✔
581
         * @event serverRegistered
102✔
582
         */
102✔
583
        debugLog("serverRegistered");
102✔
584
        self.emit("serverRegistered");
102✔
585
    });
301✔
586
    self.registerServerManager.on("serverRegistrationRenewed", () => {
301✔
587
        /**
18✔
588
         * emitted when the server has successfully renewed its registration to the LDS
18✔
589
         * @event serverRegistrationRenewed
18✔
590
         */
18✔
591
        debugLog("serverRegistrationRenewed");
18✔
592
        self.emit("serverRegistrationRenewed");
18✔
593
    });
301✔
594

301✔
595
    self.registerServerManager.on("serverUnregistered", () => {
301✔
596
        debugLog("serverUnregistered");
107✔
597
        /**
107✔
598
         * emitted when the server is successfully unregistered to the LDS
107✔
599
         * ( for instance during shutdown)
107✔
600
         * @event serverUnregistered
107✔
601
         */
107✔
602
        self.emit("serverUnregistered");
107✔
603
    });
301✔
604
}
301✔
605

2✔
606
function validate_applicationUri(channel: ServerSecureChannelLayer, request: CreateSessionRequest): boolean {
1,036✔
607
    const applicationUri = request.clientDescription.applicationUri || "";
1,036!
608
    const clientCertificate = request.clientCertificate;
1,036✔
609
    // if session is insecure there is no need to check certificate information
1,036✔
610
    if (channel.securityMode === MessageSecurityMode.None) {
1,036✔
611
        return true; // assume correct
872✔
612
    }
872✔
613
    if (!clientCertificate || clientCertificate.length === 0) {
1,036!
614
        return true; // can't check
×
615
    }
×
616
    const e = exploreCertificate(clientCertificate);
164✔
617
    const uniformResourceIdentifier = e.tbsCertificate.extensions?.subjectAltName?.uniformResourceIdentifier ?? null;
1,036!
618
    const applicationUriFromCert =
1,036✔
619
        uniformResourceIdentifier && uniformResourceIdentifier.length > 0 ? uniformResourceIdentifier[0] : null;
1,036!
620

1,036✔
621
    /* c8 ignore next */
2✔
622
    if (applicationUriFromCert !== applicationUri) {
2✔
623
        errorLog("BadCertificateUriInvalid!");
×
624
        errorLog("applicationUri           = ", applicationUri);
×
625
        errorLog("applicationUriFromCert   = ", applicationUriFromCert);
×
626
    }
×
627

164✔
628
    return applicationUriFromCert === applicationUri;
164✔
629
}
164✔
630

2✔
631
function validate_security_endpoint(
1,036✔
632
    server: OPCUAServer,
1,036✔
633
    request: CreateSessionRequest,
1,036✔
634
    channel: ServerSecureChannelLayer
1,036✔
635
): {
1,036✔
636
    errCode: StatusCode;
1,036✔
637
    endpoint?: EndpointDescription;
1,036✔
638
} {
1,036✔
639
    debugLog("validate_security_endpoint = ", request.endpointUrl);
1,036✔
640
    let endpoints = server.findMatchingEndpoints(request.endpointUrl);
1,036✔
641
    // endpointUrl String The network address that the Client used to access the Session Endpoint.
1,036✔
642
    //             The HostName portion of the URL should be one of the HostNames for the application that are
1,036✔
643
    //             specified in the Server’s ApplicationInstanceCertificate (see 7.2). The Server shall raise an
1,036✔
644
    //             AuditUrlMismatchEventType event if the URL does not match the Server’s HostNames.
1,036✔
645
    //             AuditUrlMismatchEventType event type is defined in Part 5.
1,036✔
646
    //             The Server uses this information for diagnostics and to determine the set of
1,036✔
647
    //             EndpointDescriptions to return in the response.
1,036✔
648
    // ToDo: check endpointUrl validity and emit an AuditUrlMismatchEventType event if not
1,036✔
649

1,036✔
650
    // sometime endpoints have a extra leading "/" that can be ignored
1,036✔
651
    // don't be too harsh.
1,036✔
652
    if (endpoints.length === 0 && request.endpointUrl?.endsWith("/")) {
1,036!
653
        endpoints = server.findMatchingEndpoints(request.endpointUrl.slice(0, -1));
×
654
    }
×
655

1,036✔
656
    if (endpoints.length === 0) {
1,036✔
657
        // we have a UrlMismatch here
27✔
658
        const ua_server = server.engine.addressSpace?.rootFolder.objects.server;
27✔
659
        if (!request.endpointUrl?.match(/localhost/i) || OPCUAServer.requestExactEndpointUrl) {
27✔
660
            warningLog("Cannot find suitable endpoints in available endpoints. endpointUri =", request.endpointUrl);
22✔
661
        }
22✔
662
        ua_server?.raiseEvent("AuditUrlMismatchEventType", {
27✔
663
            endpointUrl: { dataType: DataType.String, value: request.endpointUrl }
27✔
664
        });
27✔
665
        if (OPCUAServer.requestExactEndpointUrl) {
27!
666
            return { errCode: StatusCodes.BadServiceUnsupported };
×
667
        } else {
27✔
668
            endpoints = server.findMatchingEndpoints(null);
27✔
669
        }
27✔
670
    }
27✔
671
    // ignore restricted endpoints
1,036✔
672
    endpoints = endpoints.filter((e: EndpointDescription) => !(e as EndpointDescriptionEx).restricted);
1,036✔
673

1,036✔
674
    const endpoints_matching_security_mode = endpoints.filter((e: EndpointDescription) => {
1,036✔
675
        return e.securityMode === channel.securityMode;
7,593✔
676
    });
1,036✔
677

1,036✔
678
    if (endpoints_matching_security_mode.length === 0) {
1,036✔
679
        return { errCode: StatusCodes.BadSecurityModeRejected };
1✔
680
    }
1✔
681
    const endpoints_matching_security_policy = endpoints_matching_security_mode.filter((e: EndpointDescription) => {
1,035✔
682
        return e.securityPolicyUri === channel?.securityPolicy;
1,578✔
683
    });
1,035✔
684

1,035✔
685
    if (endpoints_matching_security_policy.length === 0) {
1,036!
686
        return { errCode: StatusCodes.BadSecurityPolicyRejected };
×
687
    }
×
688
    if (endpoints_matching_security_policy.length !== 1) {
1,036!
689
        debugLog("endpoints_matching_security_policy= ", endpoints_matching_security_policy.length);
×
690
    }
×
691
    return {
1,035✔
692
        errCode: StatusCodes.Good,
1,035✔
693
        endpoint: endpoints_matching_security_policy[0]
1,035✔
694
    };
1,035✔
695
}
1,035✔
696

2✔
697
export function filterDiagnosticInfo(returnDiagnostics: number, response: CallResponse): void {
87✔
698
    if (RESPONSE_DIAGNOSTICS_MASK_ALL & returnDiagnostics) {
87✔
699
        response.responseHeader.serviceDiagnostics = filterDiagnosticServiceLevel(
1✔
700
            returnDiagnostics,
1✔
701
            response.responseHeader.serviceDiagnostics
1✔
702
        );
1✔
703

1✔
704
        if (response.diagnosticInfos && response.diagnosticInfos.length > 0) {
1✔
705
            response.diagnosticInfos = response.diagnosticInfos.map((d) => filterDiagnosticOperationLevel(returnDiagnostics, d));
1✔
706
        } else {
1!
707
            response.diagnosticInfos = [];
×
708
        }
×
709

1✔
710
        if (response.results) {
1✔
711
            for (const entry of response.results) {
1✔
712
                if (entry.inputArgumentDiagnosticInfos && entry.inputArgumentDiagnosticInfos.length > 0) {
1✔
713
                    entry.inputArgumentDiagnosticInfos = entry.inputArgumentDiagnosticInfos.map((d) =>
1✔
714
                        filterDiagnosticOperationLevel(returnDiagnostics, d)
1✔
715
                    );
1✔
716
                } else {
1!
717
                    entry.inputArgumentDiagnosticInfos = [];
×
718
                }
×
719
            }
1✔
720
        }
1✔
721
    }
1✔
722
}
87✔
723

2✔
724
export enum RegisterServerMethod {
2✔
725
    HIDDEN = 1, // the server doesn't expose itself to the external world
2✔
726
    MDNS = 2, // the server publish itself to the mDNS Multicast network directly
2✔
727
    LDS = 3 // the server registers itself to the LDS or LDS-ME (Local Discovery Server)
2✔
728
}
2✔
729

2✔
730
export interface OPCUAServerEndpointOptions {
2✔
731
    /**
2✔
732
     * the primary hostname of the endpoint.
2✔
733
     * @default getFullyQualifiedDomainName()
2✔
734
     */
2✔
735
    hostname?: string;
2✔
736
    /**
2✔
737
     * Host IP address or hostname where the TCP server listens for connections.
2✔
738
     * If omitted, defaults to listening on all network interfaces:
2✔
739
     * - Unspecified IPv6 address (::) if IPv6 is available,
2✔
740
     * - Unspecified IPv4 address (0.0.0.0) otherwise.
2✔
741
     * Use this to bind the server to a specific interface or IP.
2✔
742
     */
2✔
743
    host?: string;
2✔
744
    /**
2✔
745
     * the TCP port to listen to.
2✔
746
     * @default 26543
2✔
747
     */
2✔
748
    port?: number;
2✔
749
    /**
2✔
750
     * the possible security policies that the server will expose
2✔
751
     * @default  [SecurityPolicy.None, SecurityPolicy.Basic128Rsa15, SecurityPolicy.Basic256Sha256, SecurityPolicy.Aes128_Sha256_RsaOaep, SecurityPolicy.Aes256_Sha256_RsaPss  ]
2✔
752
     */
2✔
753
    securityPolicies?: SecurityPolicy[];
2✔
754
    /**
2✔
755
     * the possible security mode that the server will expose
2✔
756
     * @default [MessageSecurityMode.None, MessageSecurityMode.Sign, MessageSecurityMode.SignAndEncrypt]
2✔
757
     */
2✔
758
    securityModes?: MessageSecurityMode[];
2✔
759
    /**
2✔
760
     * tells if the server default endpoints should allow anonymous connection.
2✔
761
     * @default true
2✔
762
     */
2✔
763
    allowAnonymous?: boolean;
2✔
764

2✔
765
    /** alternate hostname  or IP to use */
2✔
766
    alternateHostname?: string | string[];
2✔
767

2✔
768
    /**
2✔
769
     * Additional endpoint URL(s) to advertise.
2✔
770
     *
2✔
771
     * Use when the server is behind Docker port-mapping, a reverse proxy,
2✔
772
     * or a NAT gateway. Each URL is parsed to extract hostname and port.
2✔
773
     * Each entry can be a plain URL string (inherits all security
2✔
774
     * settings from the main endpoint) or an
2✔
775
     * `AdvertisedEndpointConfig` object with per-URL overrides.
2✔
776
     * The server still listens on `port`.
2✔
777
     *
2✔
778
     * @example "opc.tcp://localhost:48481"
2✔
779
     * @example ["opc.tcp://localhost:48481", { url: "opc.tcp://public:4840", securityModes: [MessageSecurityMode.SignAndEncrypt] }]
2✔
780
     */
2✔
781
    advertisedEndpoints?: AdvertisedEndpoint | AdvertisedEndpoint[];
2✔
782

2✔
783
    /**
2✔
784
     *  true, if discovery service on secure channel shall be disabled
2✔
785
     */
2✔
786
    disableDiscovery?: boolean;
2✔
787
}
2✔
788

2✔
789
export interface OPCUAServerOptions extends OPCUABaseServerOptions, OPCUAServerEndpointOptions {
2✔
790
    /**
2✔
791
     * @deprecated
2✔
792
     */
2✔
793
    alternateEndpoints?: OPCUAServerEndpointOptions[];
2✔
794
    endpoints?: OPCUAServerEndpointOptions[];
2✔
795

2✔
796
    /**
2✔
797
     * the server certificate full path filename
2✔
798
     *
2✔
799
     * the certificate should be in PEM format
2✔
800
     */
2✔
801
    certificateFile?: string;
2✔
802
    /**
2✔
803
     * the server private key full path filename
2✔
804
     *
2✔
805
     * This file should contains the private key that has been used to generate
2✔
806
     * the server certificate file.
2✔
807
     *
2✔
808
     * the private key should be in PEM format
2✔
809
     *
2✔
810
     */
2✔
811
    privateKeyFile?: string;
2✔
812

2✔
813
    /**
2✔
814
     * the default secure token life time in ms.
2✔
815
     */
2✔
816
    defaultSecureTokenLifetime?: number;
2✔
817
    /**
2✔
818
     * the HEL/ACK transaction timeout in ms.
2✔
819
     *
2✔
820
     * Use a large value ( i.e 15000 ms) for slow connections or embedded devices.
2✔
821
     * @default 10000
2✔
822
     */
2✔
823
    timeout?: number;
2✔
824

2✔
825
    /**
2✔
826
     * the maximum number of simultaneous sessions allowed.
2✔
827
     * @default 10
2✔
828
     * @deprecated use serverCapabilities: { maxSessions: } instead
2✔
829

2✔
830
     */
2✔
831
    maxAllowedSessionNumber?: number;
2✔
832

2✔
833
    /**
2✔
834
     * the maximum number authorized simultaneous connections per endpoint
2✔
835
     * @default 10
2✔
836
     */
2✔
837
    maxConnectionsPerEndpoint?: number;
2✔
838

2✔
839
    /**
2✔
840
     * the nodeset.xml file(s) to load
2✔
841
     *
2✔
842
     * node-opcua comes with pre-installed node-set files that can be used
2✔
843
     *
2✔
844
     * example:
2✔
845
     *
2✔
846
     * ```javascript
2✔
847
     * import { nodesets } from "node-opcua-nodesets";
2✔
848
     * const server = new OPCUAServer({
2✔
849
     *     nodeset_filename: [
2✔
850
     *         nodesets.standard,
2✔
851
     *         nodesets.di,
2✔
852
     *         nodesets.adi,
2✔
853
     *         nodesets.machinery,
2✔
854
     *     ],
2✔
855
     * });
2✔
856
     * ```
2✔
857
     */
2✔
858
    nodeset_filename?: string[] | string;
2✔
859

2✔
860
    /**
2✔
861
     * the server Info
2✔
862
     *
2✔
863
     * this object contains the value that will populate the
2✔
864
     * Root/ObjectS/Server/ServerInfo OPCUA object in the address space.
2✔
865
     */
2✔
866
    serverInfo?: ApplicationDescriptionOptions;
2✔
867
    /*{
2✔
868
          applicationUri?: string;
2✔
869
          productUri?: string;
2✔
870
          applicationName?: LocalizedTextLike | string;
2✔
871
          gatewayServerUri?: string | null;
2✔
872
          discoveryProfileUri?: string | null;
2✔
873
          discoveryUrls?: string[];
2✔
874
      };
2✔
875
  */
2✔
876
    buildInfo?: {
2✔
877
        productName?: string;
2✔
878
        productUri?: string | null; // << should be same as default_server_info.productUri?
2✔
879
        manufacturerName?: string;
2✔
880
        softwareVersion?: string;
2✔
881
        buildNumber?: string;
2✔
882
        buildDate?: Date;
2✔
883
    };
2✔
884

2✔
885
    /**
2✔
886
     *  an object that implements user authentication methods
2✔
887
     */
2✔
888
    userManager?: UserManagerOptions;
2✔
889

2✔
890
    /** resource Path is a string added at the end of the url such as "/UA/Server" */
2✔
891
    resourcePath?: string;
2✔
892

2✔
893
    /**
2✔
894
     *
2✔
895
     */
2✔
896
    serverCapabilities?: ServerCapabilitiesOptions;
2✔
897
    /**
2✔
898
     * if server shall raise AuditingEvent
2✔
899
     * @default true
2✔
900
     */
2✔
901
    isAuditing?: boolean;
2✔
902

2✔
903
    /**
2✔
904
     * strategy used by the server to declare itself to a discovery server
2✔
905
     *
2✔
906
     * - HIDDEN: the server doesn't expose itself to the external world
2✔
907
     * - MDNS: the server publish itself to the mDNS Multicast network directly
2✔
908
     * - LDS: the server registers itself to the LDS or LDS-ME (Local Discovery Server)
2✔
909
     *
2✔
910
     *  @default    .HIDDEN - by default the server
2✔
911
     *            will not register itself to the local discovery server
2✔
912
     *
2✔
913
     */
2✔
914
    registerServerMethod?: RegisterServerMethod;
2✔
915
    /**
2✔
916
     *
2✔
917
     * @default "opc.tcp://localhost:4840"]
2✔
918
     */
2✔
919
    discoveryServerEndpointUrl?: string;
2✔
920
    /**
2✔
921
     *
2✔
922
     *  supported server capabilities for the Multicast (mDNS)
2✔
923
     *  @default ["NA"]
2✔
924
     *  the possible values are any of node-opcua-discovery.serverCapabilities)
2✔
925
     *
2✔
926
     */
2✔
927
    capabilitiesForMDNS?: string[];
2✔
928

2✔
929
    /**
2✔
930
     * user Certificate Manager
2✔
931
     * this certificate manager holds the X509 certificates used
2✔
932
     * by client that uses X509 certificate token to impersonate a user
2✔
933
     */
2✔
934
    userCertificateManager?: OPCUACertificateManager;
2✔
935
    /**
2✔
936
     * Server Certificate Manager
2✔
937
     *
2✔
938
     * this certificate manager will be used by the server to access
2✔
939
     * and store certificates from the connecting clients
2✔
940
     */
2✔
941
    serverCertificateManager?: OPCUACertificateManager;
2✔
942

2✔
943
    /**
2✔
944
     *
2✔
945
     */
2✔
946
    onCreateMonitoredItem?: CreateMonitoredItemHook;
2✔
947
    onDeleteMonitoredItem?: DeleteMonitoredItemHook;
2✔
948

2✔
949
    /**
2✔
950
     * skipOwnNamespace to true, if you don't want the server to create
2✔
951
     * a dedicated namespace for its own (namespace=1).
2✔
952
     * Use this flag if you intend to load the server own namespace
2✔
953
     * from an external source.
2✔
954
     * @default false
2✔
955
     */
2✔
956
    skipOwnNamespace?: boolean;
2✔
957
    transportSettings?: IServerTransportSettings;
2✔
958
}
2✔
959

2✔
960
const g_requestExactEndpointUrl = !!process.env.NODEOPCUA_SERVER_REQUEST_EXACT_ENDPOINT_URL;
2✔
961
export interface OPCUAServerEvents {
2✔
962
    /** event raised when a new session is created */
2✔
963
    create_session: [session: ServerSession];
2✔
964
    /** event raised when a session is activated */
2✔
965
    session_activated: [session: ServerSession, userIdentityToken: UserIdentityToken];
2✔
966
    /** event raised when a session is closed */
2✔
967
    session_closed: [session: ServerSession, deleteSubscriptions: boolean];
2✔
968
    /** event raised after the server address space has been initialized */
2✔
969
    post_initialize: [];
2✔
970
    /**
2✔
971
     * emitted when the server is trying to register with the LDS
2✔
972
     * but the connection has failed (backoff signal)
2✔
973
     */
2✔
974
    serverRegistrationPending: [];
2✔
975
    /** event raised when server has been successfully registered on the LDS */
2✔
976
    serverRegistered: [];
2✔
977
    /** event raised when server registration has been successfully renewed on the LDS */
2✔
978
    serverRegistrationRenewed: [];
2✔
979
    /** event raised when server has been successfully unregistered from the LDS */
2✔
980
    serverUnregistered: [];
2✔
981
    /** event raised after the server has raised an OPCUA event toward a client */
2✔
982
    event: [eventData: unknown];
2✔
983
    /**
2✔
984
     * event raised when the server receives a request from a connected client.
2✔
985
     * Useful for trace/diagnostics.
2✔
986
     */
2✔
987
    request: [request: Request, channel: ServerSecureChannelLayer];
2✔
988
    /**
2✔
989
     * event raised when the server sends a response to a connected client.
2✔
990
     * Useful for trace/diagnostics.
2✔
991
     */
2✔
992
    response: [response: Response, channel: ServerSecureChannelLayer];
2✔
993
    /**
2✔
994
     * event raised when a new secure channel transport is initialized (HEL/ACK complete).
2✔
995
     * Note: securityPolicy and securityMode are NOT yet established at this point.
2✔
996
     * Use "channelSecured" for post-handshake notifications.
2✔
997
     */
2✔
998
    newChannel: [channel: ServerSecureChannelLayer, endpoint: OPCUAServerEndPoint];
2✔
999
    /**
2✔
1000
     * event raised when a secure channel has completed the OpenSecureChannel handshake.
2✔
1001
     * At this point securityPolicy, securityMode, and clientCertificate are available.
2✔
1002
     */
2✔
1003
    channelSecured: [channel: ServerSecureChannelLayer, endpoint: OPCUAServerEndPoint];
2✔
1004
    /** event raised when a secure channel is closed */
2✔
1005
    closeChannel: [channel: ServerSecureChannelLayer, endpoint: OPCUAServerEndPoint];
2✔
1006
    /**
2✔
1007
     * event raised when the server refused a TCP connection from a client
2✔
1008
     * (for instance because too many connections)
2✔
1009
     */
2✔
1010
    connectionRefused: [socketData: ISocketData, endpoint: OPCUAServerEndPoint];
2✔
1011
    /**
2✔
1012
     * event raised when an OpenSecureChannel has failed,
2✔
1013
     * e.g. invalid certificate or malformed message
2✔
1014
     */
2✔
1015
    openSecureChannelFailure: [socketData: ISocketData, channelData: IChannelData, endpoint: OPCUAServerEndPoint];
2✔
1016
}
2✔
1017

2✔
1018
export class OPCUAServer extends OPCUABaseServer<OPCUAServerEvents> {
2✔
1019
    public engine!: ServerEngine;
35✔
1020
    public registerServerMethod: RegisterServerMethod;
35✔
1021
    public discoveryServerEndpointUrl!: string;
35✔
1022
    public registerServerManager?: IRegisterServerManager;
35✔
1023
    public capabilitiesForMDNS: string[];
35✔
1024
    public userCertificateManager: OPCUACertificateManager;
35✔
1025

35✔
1026
    static defaultShutdownTimeout = 100; // 250 ms
35✔
1027
    /**
35✔
1028
     * if requestExactEndpointUrl is set to true the server will only accept createSession that have a endpointUrl that strictly matches
35✔
1029
     * one of the provided endpoint.
35✔
1030
     * This mean that if the server expose a endpoint with url such as opc.tcp://MYHOSTNAME:1234, client will not be able to reach the server
35✔
1031
     * with the ip address of the server.
35✔
1032
     * requestExactEndpointUrl = true => emulates the Prosys Server behavior
35✔
1033
     * requestExactEndpointUrl = false => emulates the Unified Automation behavior.
35✔
1034
     */
35✔
1035
    static requestExactEndpointUrl: boolean = g_requestExactEndpointUrl;
35✔
1036
    /**
35✔
1037
     * total number of bytes written  by the server since startup
35✔
1038
     */
35✔
1039
    public get bytesWritten(): number {
35✔
1040
        return this.endpoints.reduce((accumulated: number, endpoint: OPCUAServerEndPoint) => {
1✔
1041
            return accumulated + endpoint.bytesWritten;
1✔
1042
        }, 0);
1✔
1043
    }
1✔
1044

35✔
1045
    /**
35✔
1046
     * total number of bytes read  by the server since startup
35✔
1047
     */
35✔
1048
    public get bytesRead(): number {
35✔
1049
        return this.endpoints.reduce((accumulated: number, endpoint: OPCUAServerEndPoint) => {
1✔
1050
            return accumulated + endpoint.bytesRead;
1✔
1051
        }, 0);
1✔
1052
    }
1✔
1053

35✔
1054
    /**
35✔
1055
     * Number of transactions processed by the server since startup
35✔
1056
     */
35✔
1057
    public get transactionsCount(): number {
35✔
1058
        return this.endpoints.reduce((accumulated: number, endpoint: OPCUAServerEndPoint) => {
1✔
1059
            return accumulated + endpoint.transactionsCount;
1✔
1060
        }, 0);
1✔
1061
    }
1✔
1062

35✔
1063
    /**
35✔
1064
     * The server build info
35✔
1065
     */
35✔
1066
    public get buildInfo(): BuildInfo {
35✔
1067
        return this.engine.buildInfo;
300✔
1068
    }
300✔
1069

35✔
1070
    /**
35✔
1071
     * the number of connected channel on all existing end points
35✔
1072
     */
35✔
1073
    public get currentChannelCount(): number {
35✔
1074
        // TODO : move to base
65✔
1075
        return this.endpoints.reduce((currentValue: number, endPoint: OPCUAServerEndPoint) => {
65✔
1076
            return currentValue + endPoint.currentChannelCount;
64✔
1077
        }, 0);
65✔
1078
    }
65✔
1079

35✔
1080
    /**
35✔
1081
     * The number of active subscriptions from all sessions
35✔
1082
     */
35✔
1083
    public get currentSubscriptionCount(): number {
35✔
1084
        return this.engine ? this.engine.currentSubscriptionCount : 0;
431✔
1085
    }
431✔
1086

35✔
1087
    /**
35✔
1088
     * the number of session activation requests that have been rejected
35✔
1089
     */
35✔
1090
    public get rejectedSessionCount(): number {
35✔
1091
        return this.engine ? this.engine.rejectedSessionCount : 0;
2✔
1092
    }
2✔
1093

35✔
1094
    /**
35✔
1095
     * the number of request that have been rejected
35✔
1096
     */
35✔
1097
    public get rejectedRequestsCount(): number {
35✔
1098
        return this.engine ? this.engine.rejectedRequestsCount : 0;
2✔
1099
    }
2✔
1100

35✔
1101
    /**
35✔
1102
     * the number of sessions that have been aborted
35✔
1103
     */
35✔
1104
    public get sessionAbortCount(): number {
35✔
1105
        return this.engine ? this.engine.sessionAbortCount : 0;
2✔
1106
    }
2✔
1107

35✔
1108
    /**
35✔
1109
     * the publishing interval count
35✔
1110
     */
35✔
1111
    public get publishingIntervalCount(): number {
35✔
1112
        return this.engine ? this.engine.publishingIntervalCount : 0;
2✔
1113
    }
2✔
1114

35✔
1115
    /**
35✔
1116
     * the number of sessions currently active
35✔
1117
     */
35✔
1118
    public get currentSessionCount(): number {
35✔
1119
        return this.engine ? this.engine.currentSessionCount : 0;
2,120✔
1120
    }
2,120✔
1121

35✔
1122
    /**
35✔
1123
     * true if the server has been initialized
35✔
1124
     *
35✔
1125
     */
35✔
1126
    public get initialized(): boolean {
35✔
1127
        return this.engine && this.engine.addressSpace !== null;
576✔
1128
    }
576✔
1129

35✔
1130
    /**
35✔
1131
     * is the server auditing ?
35✔
1132
     */
35✔
1133
    public get isAuditing(): boolean {
35✔
1134
        return this.engine ? this.engine.isAuditing : false;
3,075✔
1135
    }
3,075✔
1136

35✔
1137
    /**
35✔
1138
     * Set the current server state.
35✔
1139
     *
35✔
1140
     * Updates both the internal state and the
35✔
1141
     * `Server.ServerStatus.State` variable in the
35✔
1142
     * address space so that OPC UA reads reflect the
35✔
1143
     * new state immediately.
35✔
1144
     */
35✔
1145
    public setServerState(serverState: ServerState): void {
35✔
1146
        this.engine.setServerState(serverState);
×
1147
    }
×
1148

35✔
1149
    /**
35✔
1150
     * Read the current `ServerState` from the
35✔
1151
     * internal server status.
35✔
1152
     */
35✔
1153
    public getServerState(): ServerState {
35✔
1154
        return this.engine.getServerState();
×
1155
    }
×
1156

35✔
1157
    /**
35✔
1158
     * Set or clear a temporary role-policy override.
35✔
1159
     *
35✔
1160
     * When set, the override's `getUserRoles(username)`
35✔
1161
     * is called **before** the default `userManager`.
35✔
1162
     * Returning a `NodeId[]` overrides the roles;
35✔
1163
     * returning `null` falls through to the default.
35✔
1164
     *
35✔
1165
     * Call with `null` to remove the override and
35✔
1166
     * restore default behavior.
35✔
1167
     */
35✔
1168
    public setRolePolicyOverride(override: IRolePolicyOverride | null): void {
35✔
1169
        (this as unknown as IServerBase).rolePolicyOverride = override;
×
1170
    }
×
1171

35✔
1172
    /**
35✔
1173
     * Set `ServerConfiguration.InApplicationSetup` in
35✔
1174
     * the address space.
35✔
1175
     *
35✔
1176
     * Indicates whether the server is in its initial
35✔
1177
     * application setup phase (e.g. awaiting GDS
35✔
1178
     * provisioning).
35✔
1179
     */
35✔
1180
    public setInApplicationSetup(value: boolean): void {
35✔
1181
        this.engine.setInApplicationSetup(value);
×
1182
    }
×
1183

35✔
1184
    /**
35✔
1185
     * Read the current value of
35✔
1186
     * `ServerConfiguration.InApplicationSetup`.
35✔
1187
     */
35✔
1188
    public getInApplicationSetup(): boolean {
35✔
1189
        return this.engine.getInApplicationSetup();
×
1190
    }
×
1191

35✔
1192
    /**
35✔
1193
     * Collect additional hostnames for the self-signed certificate SAN.
35✔
1194
     *
35✔
1195
     * Merges hostnames from `alternateHostname` and parsed
35✔
1196
     * `advertisedEndpoints` URLs so the certificate covers all
35✔
1197
     * configured addresses.
35✔
1198
     *
35✔
1199
     * IP literals (v4/v6) are **excluded** — they are handled by
35✔
1200
     * `getConfiguredIPs()` and placed in the SAN `iPAddress` entries.
35✔
1201
     *
35✔
1202
     * @internal
35✔
1203
     */
35✔
1204
    protected override getConfiguredHostnames(): string[] {
35✔
1205
        return this._collectAlternateValues().hostnames;
372✔
1206
    }
372✔
1207

35✔
1208
    /**
35✔
1209
     * Collect additional IP addresses for the self-signed certificate SAN.
35✔
1210
     *
35✔
1211
     * Merges IP literals from `alternateHostname` and parsed
35✔
1212
     * `advertisedEndpoints` URLs so the certificate covers all
35✔
1213
     * configured IP addresses.
35✔
1214
     *
35✔
1215
     * @internal
35✔
1216
     */
35✔
1217
    protected override getConfiguredIPs(): string[] {
35✔
1218
        return this._collectAlternateValues().ips;
372✔
1219
    }
372✔
1220

35✔
1221
    /**
35✔
1222
     * Classify all values from `alternateHostname` and
35✔
1223
     * `advertisedEndpoints` into hostnames vs IP addresses using
35✔
1224
     * `isIPAddress()` (wraps `net.isIP()`).
35✔
1225
     */
35✔
1226
    private _collectAlternateValues(): { hostnames: string[]; ips: string[] } {
35✔
1227
        const hostnames: string[] = [];
744✔
1228
        const ips: string[] = [];
744✔
1229

744✔
1230
        // alternateHostname
744✔
1231
        const alt = this.options.alternateHostname;
744✔
1232
        if (alt) {
744✔
1233
            const altArray = Array.isArray(alt) ? alt : [alt];
46!
1234
            for (const value of altArray) {
46✔
1235
                if (isIPAddress(value)) {
66✔
1236
                    ips.push(value);
20✔
1237
                } else {
66✔
1238
                    hostnames.push(value);
46✔
1239
                }
46✔
1240
            }
66✔
1241
        }
46✔
1242

744✔
1243
        // advertisedEndpoints — normalize to AdvertisedEndpointConfig[]
744✔
1244
        const advList = normalizeAdvertisedEndpoints(this.options.advertisedEndpoints);
744✔
1245
        for (const config of advList) {
744✔
1246
            const { hostname } = parseOpcTcpUrl(config.url);
56✔
1247
            if (isIPAddress(hostname)) {
56✔
1248
                ips.push(hostname);
4✔
1249
            } else {
56✔
1250
                hostnames.push(hostname);
52✔
1251
            }
52✔
1252
        }
56✔
1253

744✔
1254
        return { hostnames, ips };
744✔
1255
    }
744✔
1256

35✔
1257
    public static registry = new ObjectRegistry();
35✔
1258
    public static fallbackSessionName = "Client didn't provide a meaningful sessionName ...";
35✔
1259
    /**
35✔
1260
     * the maximum number of subscription that can be created per server
35✔
1261
     * @deprecated
35✔
1262
     */
35✔
1263
    public static deprecated_MAX_SUBSCRIPTION = 50;
35✔
1264

35✔
1265
    /**
35✔
1266
     * the maximum number of concurrent sessions allowed on the server
35✔
1267
     */
35✔
1268
    public get maxAllowedSessionNumber(): number {
35✔
1269
        return this.engine.serverCapabilities.maxSessions;
×
1270
    }
×
1271

35✔
1272
    /**
35✔
1273
     * the maximum number for concurrent connection per end point
35✔
1274
     */
35✔
1275
    public maxConnectionsPerEndpoint: number;
35✔
1276

35✔
1277
    /**
35✔
1278
     * false if anonymous connection are not allowed
35✔
1279
     */
35✔
1280
    public allowAnonymous = false;
35✔
1281

35✔
1282
    /**
35✔
1283
     * the user manager
35✔
1284
     */
35✔
1285
    public userManager: UAUserManagerBase;
35✔
1286

35✔
1287
    public readonly options: OPCUAServerOptions;
35✔
1288

35✔
1289
    private objectFactory?: Factory;
35✔
1290

35✔
1291
    private _delayInit?: () => Promise<void>;
35✔
1292

35✔
1293
    constructor(options?: OPCUAServerOptions) {
35✔
1294
        super(options);
301✔
1295

301✔
1296
        this.allowAnonymous = false;
301✔
1297
        options = options || {};
301✔
1298

301✔
1299
        this.options = options;
301✔
1300

301✔
1301
        if (options.maxAllowedSessionNumber !== undefined) {
301✔
1302
            warningLog(
1✔
1303
                "[NODE-OPCUA-W21] maxAllowedSessionNumber property is now deprecated , please use serverCapabilities.maxSessions instead"
1✔
1304
            );
1✔
1305
            options.serverCapabilities = options.serverCapabilities || {};
1✔
1306
            options.serverCapabilities.maxSessions = options.maxAllowedSessionNumber;
1✔
1307
        }
1✔
1308
        // adjust securityPolicies if any
301✔
1309
        if (options.securityPolicies) {
301✔
1310
            options.securityPolicies = options.securityPolicies.map(coerceSecurityPolicy);
45✔
1311
        }
45✔
1312

301✔
1313
        /**
301✔
1314
         * @property maxConnectionsPerEndpoint
301✔
1315
         */
301✔
1316
        this.maxConnectionsPerEndpoint = options.maxConnectionsPerEndpoint || default_maxConnectionsPerEndpoint;
301✔
1317

301✔
1318
        // build Info
301✔
1319
        const buildInfo: BuildInfoOptions = {
301✔
1320
            ...default_build_info,
301✔
1321
            ...options.buildInfo
301✔
1322
        };
301✔
1323

301✔
1324
        // repair product name
301✔
1325
        buildInfo.productUri = buildInfo.productUri || this.serverInfo.productUri;
301✔
1326
        this.serverInfo.productUri = this.serverInfo.productUri || buildInfo.productUri;
301!
1327

301✔
1328
        this.userManager = makeUserManager(options.userManager);
301✔
1329

301✔
1330
        options.allowAnonymous = options.allowAnonymous === undefined ? true : !!options.allowAnonymous;
301✔
1331
        /**
301✔
1332
         * @property allowAnonymous
301✔
1333
         */
301✔
1334
        this.allowAnonymous = options.allowAnonymous;
301✔
1335

301✔
1336
        this.discoveryServerEndpointUrl = options.discoveryServerEndpointUrl || "opc.tcp://%FQDN%:4840";
301✔
1337
        assert(typeof this.discoveryServerEndpointUrl === "string");
301✔
1338

301✔
1339
        this.serverInfo.applicationType =
301✔
1340
            options.serverInfo?.applicationType === undefined ? ApplicationType.Server : options.serverInfo.applicationType;
301✔
1341

301✔
1342
        this.capabilitiesForMDNS = options.capabilitiesForMDNS || ["NA"];
301✔
1343
        this.registerServerMethod = options.registerServerMethod || RegisterServerMethod.HIDDEN;
301✔
1344
        _installRegisterServerManager(this);
301✔
1345

301✔
1346
        if (!options.userCertificateManager) {
301✔
1347
            this.userCertificateManager = getDefaultCertificateManager("UserPKI");
293✔
1348
        } else {
301✔
1349
            this.userCertificateManager = options.userCertificateManager;
8✔
1350
        }
8✔
1351

301✔
1352
        // note: we need to delay initialization of endpoint as certain resources
301✔
1353
        // such as %FQDN% might not be ready yet at this stage
301✔
1354
        this._delayInit = async () => {
301✔
1355
            /* c8 ignore next */
2✔
1356
            if (!options) {
2✔
1357
                throw new Error("Internal Error");
×
1358
            }
×
1359
            // to check => this.serverInfo.applicationName = this.serverInfo.productName || buildInfo.productName;
299✔
1360

299✔
1361
            // note: applicationUri is handled in a special way
299✔
1362
            this.engine = new ServerEngine({
299✔
1363
                applicationUri: () => this.serverInfo.applicationUri || "",
299!
1364
                buildInfo,
299✔
1365
                isAuditing: options.isAuditing,
299✔
1366
                serverCapabilities: options.serverCapabilities,
299✔
1367

299✔
1368
                serverConfiguration: {
299✔
1369
                    serverCapabilities: () => {
299✔
1370
                        return this.capabilitiesForMDNS || ["NA"];
580!
1371
                    },
331✔
1372
                    supportedPrivateKeyFormat: ["PEM"],
299✔
1373
                    applicationType: () => this.serverInfo.applicationType,
299✔
1374
                    applicationUri: () => this.serverInfo.applicationUri || "",
299!
1375
                    productUri: () => this.serverInfo.productUri || "",
299!
1376
                    // hasSecureElement: () => false,
299✔
1377
                    multicastDnsEnabled: () => this.registerServerMethod === RegisterServerMethod.MDNS
299✔
1378
                }
299✔
1379
            });
299✔
1380

299✔
1381
            this.objectFactory = new Factory(this.engine);
299✔
1382

299✔
1383
            const endpointDefinitions: OPCUAServerEndpointOptions[] = [
299✔
1384
                ...(options.endpoints || []),
299✔
1385
                ...(options.alternateEndpoints || [])
299✔
1386
            ];
299✔
1387

299✔
1388
            const hostname = getFullyQualifiedDomainName();
299✔
1389
            endpointDefinitions.forEach((endpointDefinition) => {
299✔
1390
                endpointDefinition.port = endpointDefinition.port === undefined ? 26543 : endpointDefinition.port;
1✔
1391
                endpointDefinition.hostname = endpointDefinition.hostname || hostname;
1✔
1392
            });
299✔
1393

299✔
1394
            if (!options.endpoints) {
299✔
1395
                endpointDefinitions.push({
299✔
1396
                    port: options.port === undefined ? 26543 : options.port,
299!
1397
                    hostname: options.hostname || hostname,
299✔
1398
                    host: options.host,
299✔
1399
                    allowAnonymous: options.allowAnonymous,
299✔
1400
                    alternateHostname: options.alternateHostname,
299✔
1401
                    advertisedEndpoints: options.advertisedEndpoints,
299✔
1402
                    disableDiscovery: options.disableDiscovery,
299✔
1403
                    securityModes: options.securityModes,
299✔
1404
                    securityPolicies: options.securityPolicies
299✔
1405
                });
299✔
1406
            }
299✔
1407
            // todo  should self.serverInfo.productUri  match self.engine.buildInfo.productUri ?
299✔
1408
            for (const endpointOptions of endpointDefinitions) {
299✔
1409
                const endPoint = this.createEndpointDescriptions(options, endpointOptions);
300✔
1410
                this.endpoints.push(endPoint);
300✔
1411
                endPoint.on("message", (message: Message, channel: ServerSecureChannelLayer) => {
300✔
1412
                    this.on_request(message, channel);
45,756✔
1413
                });
300✔
1414

300✔
1415
                // endPoint.on("error", (err: Error) => {
300✔
1416
                //     errorLog("OPCUAServer endpoint error", err);
300✔
1417
                //     // set serverState to ServerState.Failed;
300✔
1418
                //     this.engine.setServerState(ServerState.Failed);
300✔
1419
                //     this.shutdown(() => {
300✔
1420
                //         /* empty */
300✔
1421
                //     });
300✔
1422
                // });
300✔
1423
            }
300✔
1424
        };
300✔
1425
    }
301✔
1426

35✔
1427
    /**
35✔
1428
     * Initialize the server by installing default node set.
35✔
1429
     *
35✔
1430
     * and instruct the server to listen to its endpoints.
35✔
1431
     *
35✔
1432
     * ```javascript
35✔
1433
     * const server = new OPCUAServer();
35✔
1434
     * await server.initialize();
35✔
1435
     *
35✔
1436
     * // default server namespace is now initialized
35✔
1437
     * // it is a good time to create life instance objects
35✔
1438
     * const namespace = server.engine.addressSpace.getOwnNamespace();
35✔
1439
     * namespace.addObject({
35✔
1440
     *     browseName: "SomeObject",
35✔
1441
     *     organizedBy: server.engine.addressSpace.rootFolder.objects
35✔
1442
     * });
35✔
1443
     *
35✔
1444
     * // the addressSpace is now complete
35✔
1445
     * // let's now start listening to clients
35✔
1446
     * await server.start();
35✔
1447
     * ```
35✔
1448
     */
35✔
1449
    public initialize(): Promise<void>;
35✔
1450
    public initialize(done: () => void): void;
35✔
1451
    public initialize(...args: [((err?: Error) => void)?]): Promise<void> | void {
35✔
1452
        const done = args[0] as (err?: Error) => void;
300✔
1453
        assert(!this.initialized, "server is already initialized"); // already initialized ?
300✔
1454

300✔
1455
        this._preInitTask.push(async () => {
300✔
1456
            /* c8 ignore next */
2✔
1457
            if (this._delayInit) {
2✔
1458
                await this._delayInit();
299✔
1459
                this._delayInit = undefined;
299✔
1460
            }
299✔
1461
        });
300✔
1462

300✔
1463
        this.performPreInitialization()
300✔
1464
            .then(() => {
300✔
1465
                OPCUAServer.registry.register(this);
299✔
1466
                this.engine.initialize(this.options, () => {
299✔
1467
                    if (!this.engine.addressSpace) {
299!
1468
                        done(new Error("no addressSpace"));
×
1469
                        return;
×
1470
                    }
×
1471
                    bindRoleSet(this.userManager, this.engine.addressSpace);
299✔
1472
                    setImmediate(() => {
299✔
1473
                        this.emit("post_initialize");
299✔
1474
                        done();
299✔
1475
                    });
299✔
1476
                });
299✔
1477
            })
300✔
1478
            .catch((err) => {
300✔
1479
                done(err);
1✔
1480
            });
300✔
1481
    }
300✔
1482

35✔
1483
    /**
35✔
1484
     * Initiate the server by starting all its endpoints
35✔
1485
     */
35✔
1486
    public start(): Promise<void>;
35✔
1487
    public start(done: () => void): void;
35✔
1488
    public start(...args: [((err?: Error) => void)?]): Promise<void> | void {
35✔
1489
        const callback = args[0];
276✔
1490
        if (callback) {
276✔
1491
            return super.start(callback);
2✔
1492
        }
2✔
1493
        return super.start();
274✔
1494
    }
274✔
1495
    /**
35✔
1496
     * Initiate the server by starting all its endpoints
35✔
1497
     * @private
35✔
1498
     */
35✔
1499
    public async startAsync(): Promise<void> {
35✔
1500
        await extractFullyQualifiedDomainName();
276✔
1501

276✔
1502
        if (!this.initialized) {
276✔
1503
            await this.initialize();
179✔
1504
        }
179✔
1505

275✔
1506
        try {
275✔
1507
            await super.startAsync();
275✔
1508
        } catch (err) {
276✔
1509
            await this.shutdown();
2✔
1510
            throw err;
2✔
1511
        }
2✔
1512

275✔
1513
        // we start the registration process asynchronously
275✔
1514
        // as we want to make server immediately available
275✔
1515
        this.registerServerManager?.start().catch(() => {
276✔
1516
            /* empty */
×
1517
        });
276✔
1518
    }
276✔
1519

35✔
1520
    /**
35✔
1521
     * shutdown all server endpoints
35✔
1522
     * @param  timeout the timeout (in ms) before the server is actually shutdown
35✔
1523
     *
35✔
1524
     * @example
35✔
1525
     *
35✔
1526
     * ```javascript
35✔
1527
     *    // shutdown immediately
35✔
1528
     *    server.shutdown(function(err) {
35✔
1529
     *    });
35✔
1530
     * ```
35✔
1531
     * ```ts
35✔
1532
     *   // in typescript with promises
35✔
1533
     *   server.shutdown(10000).then(()=>{
35✔
1534
     *      console.log("Server has shutdown");
35✔
1535
     *   });
35✔
1536
     * ```
35✔
1537
     * ```javascript
35✔
1538
     *    // shutdown within 10 seconds
35✔
1539
     *    server.engine.shutdownReason = coerceLocalizedText("Shutdown for maintenance");
35✔
1540
     *    server.shutdown(10000,function(err) {
35✔
1541
     *    });
35✔
1542
     *   ```
35✔
1543
     */
35✔
1544
    public shutdown(timeout?: number): Promise<void>;
35✔
1545
    public shutdown(callback: (err?: Error) => void): void;
35✔
1546
    public shutdown(timeout: number, callback: (err?: Error) => void): void;
35✔
1547
    public shutdown(...args: [number | ((err?: Error) => void) | undefined, ...((err?: Error) => void)[]]): Promise<void> | void {
35✔
1548
        const timeout = args.length === 1 ? OPCUAServer.defaultShutdownTimeout : (args[0] as number);
300✔
1549
        const callback = (args.length === 1 ? args[0] : args[1]) as (err?: Error) => void;
300✔
1550
        assert(typeof callback === "function");
300✔
1551
        debugLog("OPCUAServer#shutdown (timeout = ", timeout, ")");
300✔
1552

300✔
1553
        /* c8 ignore next */
2✔
1554
        if (!this.engine) {
2✔
1555
            return callback();
1✔
1556
        }
1✔
1557
        assert(this.engine);
299✔
1558
        if (!this.engine.isStarted()) {
300!
1559
            // server may have been shot down already  , or may have fail to start !!
×
1560
            const err = new Error("OPCUAServer#shutdown failure ! server doesn't seems to be started yet");
×
1561
            return callback(err);
×
1562
        }
×
1563

300✔
1564
        this.userCertificateManager.dispose();
300✔
1565

299✔
1566
        this.engine.setServerState(ServerState.Shutdown);
299✔
1567

299✔
1568
        const shutdownTime = new Date(Date.now() + timeout);
299✔
1569
        this.engine.setShutdownTime(shutdownTime);
299✔
1570

299✔
1571
        debugLog("OPCUAServer is now un-registering itself from  the discovery server ", this.buildInfo);
299✔
1572
        if (!this.registerServerManager) {
300!
1573
            callback(new Error("invalid register server manager"));
×
1574
            return;
×
1575
        }
×
1576
        this.registerServerManager
300✔
1577
            .stop()
299✔
1578
            .then(() => {
299✔
1579
                debugLog("OPCUAServer unregistered from discovery server successfully");
299✔
1580
            })
299✔
1581
            .catch((err) => {
299✔
1582
                debugLog("OPCUAServer unregistered from discovery server with err: ", err.message);
×
1583
            })
299✔
1584
            .finally(() => {
299✔
1585
                setTimeout(async () => {
299✔
1586
                    await this.engine.shutdown();
299✔
1587

299✔
1588
                    debugLog("OPCUAServer#shutdown: started");
299✔
1589
                    OPCUABaseServer.prototype.shutdown.call(this, (err1?: Error | null) => {
299✔
1590
                        debugLog("OPCUAServer#shutdown: completed");
299✔
1591

299✔
1592
                        this.dispose();
299✔
1593
                        callback(err1 || undefined);
299✔
1594
                    });
299✔
1595
                }, timeout);
299✔
1596
            });
299✔
1597
    }
300✔
1598

35✔
1599
    public dispose(): void {
35✔
1600
        for (const endpoint of this.endpoints) {
333✔
1601
            endpoint.dispose();
300✔
1602
        }
300✔
1603
        this.endpoints = [];
333✔
1604

333✔
1605
        this.removeAllListeners();
333✔
1606

333✔
1607
        if (this.registerServerManager) {
333✔
1608
            this.registerServerManager.dispose();
300✔
1609
            this.registerServerManager = undefined;
300✔
1610
        }
300✔
1611
        OPCUAServer.registry.unregister(this);
333✔
1612

333✔
1613
        /* c8 ignore next */
2✔
1614
        if (this.engine) {
2✔
1615
            this.engine.dispose();
332✔
1616
        }
332✔
1617
    }
333✔
1618

35✔
1619
    public raiseEvent(eventType: "AuditSessionEventType", options: RaiseEventAuditSessionEventData): void;
35✔
1620
    public raiseEvent(eventType: "AuditCreateSessionEventType", options: RaiseEventAuditCreateSessionEventData): void;
35✔
1621
    public raiseEvent(eventType: "AuditActivateSessionEventType", options: RaiseEventAuditActivateSessionEventData): void;
35✔
1622
    public raiseEvent(eventType: "AuditCreateSessionEventType", options: RaiseEventData): void;
35✔
1623
    public raiseEvent(eventType: "AuditConditionCommentEventType", options: RaiseEventAuditConditionCommentEventData): void;
35✔
1624
    public raiseEvent(eventType: "AuditUrlMismatchEventType", options: RaiseEventAuditUrlMismatchEventTypeData): void;
35✔
1625
    public raiseEvent(eventType: "TransitionEventType", options: RaiseEventTransitionEventData): void;
35✔
1626
    public raiseEvent(eventType: "AuditCertificateInvalidEventType", options: RaiseAuditCertificateInvalidEventData): void;
35✔
1627
    public raiseEvent(eventType: "AuditCertificateExpiredEventType", options: RaiseAuditCertificateExpiredEventData): void;
35✔
1628
    public raiseEvent(eventType: "AuditCertificateUntrustedEventType", options: RaiseAuditCertificateUntrustedEventData): void;
35✔
1629
    public raiseEvent(eventType: "AuditCertificateRevokedEventType", options: RaiseAuditCertificateRevokedEventData): void;
35✔
1630
    public raiseEvent(eventType: "AuditCertificateMismatchEventType", options: RaiseAuditCertificateMismatchEventData): void;
35✔
1631

35✔
1632
    public raiseEvent(eventType: EventTypeLike | UAObjectType, options: RaiseEventData): void {
35✔
1633
        /* c8 ignore next */
2✔
1634
        if (!this.engine.addressSpace) {
2✔
1635
            errorLog("addressSpace missing");
×
1636
            return;
×
1637
        }
×
1638

12✔
1639
        const server = this.engine.addressSpace.findNode("Server") as UAObject;
12✔
1640

12✔
1641
        /* c8 ignore next */
2✔
1642
        if (!server) {
2✔
1643
            // xx throw new Error("OPCUAServer#raiseEvent : cannot find Server object");
×
1644
            return;
×
1645
        }
×
1646

12✔
1647
        let eventTypeNode: EventTypeLike | UAObjectType | null = eventType;
12✔
1648
        if (typeof eventType === "string") {
12✔
1649
            eventTypeNode = this.engine.addressSpace.findEventType(eventType);
12✔
1650
            if (eventTypeNode) {
12✔
1651
                server.raiseEvent(eventTypeNode, options);
12✔
1652
            } else {
12✔
1653
                console.warn(" cannot find event type ", eventType);
×
1654
            }
×
1655
        } else {
12✔
1656
            server.raiseEvent(eventTypeNode, options);
×
1657
        }
×
1658
    }
12✔
1659

35✔
1660
    /**
35✔
1661
     * create and register a new session
35✔
1662
     * @private
35✔
1663
     */
35✔
1664
    protected createSession(options: CreateSessionOption): ServerSession {
35✔
1665
        /* c8 ignore next */
2✔
1666
        if (!this.engine) {
2✔
1667
            throw new Error("Internal Error");
×
1668
        }
×
1669
        return this.engine.createSession(options);
1,037✔
1670
    }
1,037✔
1671

35✔
1672
    /**
35✔
1673
     * retrieve a session by authentication token
35✔
1674
     * @private
35✔
1675
     */
35✔
1676
    public getSession(authenticationToken: NodeId, activeOnly?: boolean): ServerSession | null {
35✔
1677
        return this.engine ? this.engine.getSession(authenticationToken, activeOnly) : null;
46,828✔
1678
    }
46,828✔
1679

35✔
1680
    /**
35✔
1681
     *
35✔
1682
     * @param channel
35✔
1683
     * @param clientCertificate
35✔
1684
     * @param clientNonce
35✔
1685
     * @private
35✔
1686
     */
35✔
1687
    protected computeServerSignature(
35✔
1688
        channel: ServerSecureChannelLayer,
1,035✔
1689
        clientCertificate: Certificate,
1,035✔
1690
        clientNonce: Nonce
1,035✔
1691
    ): SignatureData | undefined {
1,035✔
1692
        return computeSignature(clientCertificate, clientNonce, this.getPrivateKey(), channel.securityPolicy);
1,035✔
1693
    }
1,035✔
1694

35✔
1695
    /**
35✔
1696
     *
35✔
1697
     * @param session
35✔
1698
     * @param channel
35✔
1699
     * @param clientSignature
35✔
1700
     */
35✔
1701
    protected verifyClientSignature(
35✔
1702
        session: ServerSession,
1,034✔
1703
        channel: ServerSecureChannelLayer,
1,034✔
1704
        clientSignature: SignatureData
1,034✔
1705
    ): boolean {
1,034✔
1706
        const clientCertificate = channel.clientCertificate;
1,034✔
1707
        const securityPolicy = channel.securityPolicy;
1,034✔
1708
        const serverCertificate = this.getCertificate();
1,034✔
1709
        const result = verifySignature(serverCertificate, session.nonce, clientSignature, clientCertificate, securityPolicy);
1,034✔
1710
        return result;
1,034✔
1711
    }
1,034✔
1712

35✔
1713
    protected isValidUserNameIdentityToken(
35✔
1714
        channel: ServerSecureChannelLayer,
78✔
1715
        _session: ServerSession,
78✔
1716
        userTokenPolicy: UserTokenPolicy,
78✔
1717
        userIdentityToken: UserNameIdentityToken,
78✔
1718
        _userTokenSignature: SignatureData,
78✔
1719
        callback: (err: Error | null, statusCode?: StatusCode) => void
78✔
1720
    ): void {
78✔
1721
        assert(userIdentityToken instanceof UserNameIdentityToken);
78✔
1722

78✔
1723
        const securityPolicy = adjustSecurityPolicy(channel, userTokenPolicy.securityPolicyUri);
78✔
1724
        if (securityPolicy === SecurityPolicy.None) {
78✔
1725
            callback(null, StatusCodes.Good);
1✔
1726
            return;
1✔
1727
        }
1✔
1728
        const cryptoFactory = getCryptoFactory(securityPolicy);
77✔
1729

77✔
1730
        /* c8 ignore next */
2✔
1731
        if (!cryptoFactory) {
2✔
1732
            callback(null, StatusCodes.BadSecurityPolicyRejected);
×
1733
            return;
×
1734
        }
×
1735

77✔
1736
        /* c8 ignore next */
2✔
1737
        if (userIdentityToken.encryptionAlgorithm !== cryptoFactory.asymmetricEncryptionAlgorithm) {
2✔
1738
            errorLog("invalid encryptionAlgorithm");
×
1739
            errorLog("userTokenPolicy", userTokenPolicy.toString());
×
1740
            errorLog("userTokenPolicy", userIdentityToken.toString());
×
1741
            callback(null, StatusCodes.BadIdentityTokenInvalid);
×
1742
            return;
×
1743
        }
×
1744
        const userName = userIdentityToken.userName;
77✔
1745
        const password = userIdentityToken.password;
77✔
1746
        if (!userName || !password) {
78✔
1747
            callback(null, StatusCodes.BadIdentityTokenInvalid);
1✔
1748
            return;
1✔
1749
        }
1✔
1750
        callback(null, StatusCodes.Good);
76✔
1751
    }
76✔
1752

35✔
1753
    protected isValidX509IdentityToken(
35✔
1754
        channel: ServerSecureChannelLayer,
7✔
1755
        session: ServerSession,
7✔
1756
        userTokenPolicy: UserTokenPolicy,
7✔
1757
        userIdentityToken: X509IdentityToken,
7✔
1758
        userTokenSignature: SignatureData,
7✔
1759
        callback: (err: Error | null, statusCode?: StatusCode) => void
7✔
1760
    ): void {
7✔
1761
        assert(userIdentityToken instanceof X509IdentityToken);
7✔
1762
        assert(typeof callback === "function");
7✔
1763

7✔
1764
        const securityPolicy = adjustSecurityPolicy(channel, userTokenPolicy.securityPolicyUri);
7✔
1765

7✔
1766
        const cryptoFactory = getCryptoFactory(securityPolicy);
7✔
1767
        /* c8 ignore next */
2✔
1768
        if (!cryptoFactory) {
2✔
1769
            callback(null, StatusCodes.BadSecurityPolicyRejected);
×
1770
            return;
×
1771
        }
×
1772

7✔
1773
        if (!userTokenSignature || !userTokenSignature.signature) {
7✔
1774
            this.raiseEvent("AuditCreateSessionEventType", {});
×
1775
            callback(null, StatusCodes.BadUserSignatureInvalid);
×
1776
            return;
×
1777
        }
×
1778

7✔
1779
        if (userIdentityToken.policyId !== userTokenPolicy.policyId) {
7✔
1780
            errorLog("invalid encryptionAlgorithm");
×
1781
            errorLog("userTokenPolicy", userTokenPolicy.toString());
×
1782
            errorLog("userTokenPolicy", userIdentityToken.toString());
×
1783
            callback(null, StatusCodes.BadSecurityPolicyRejected);
×
1784
            return;
×
1785
        }
×
1786
        const certificate = userIdentityToken.certificateData; /* as Certificate*/
7✔
1787
        const nonce = session.nonce;
7✔
1788
        if (!nonce || nonce.length === 0) {
7✔
1789
            callback(null, StatusCodes.BadNonceInvalid);
×
1790
            return;
×
1791
        }
×
1792
        const serverCertificate = this.getCertificate();
7✔
1793
        assert(userTokenSignature.signature instanceof Buffer, "expecting userTokenSignature to be a Buffer");
7✔
1794

7✔
1795
        // verify proof of possession by checking certificate signature & server nonce correctness
7✔
1796
        if (!verifySignature(serverCertificate, nonce, userTokenSignature, certificate, securityPolicy)) {
7✔
1797
            callback(null, StatusCodes.BadUserSignatureInvalid);
3✔
1798
            return;
3✔
1799
        }
3✔
1800

4✔
1801
        // verify if certificate is Valid
4✔
1802
        this.userCertificateManager.checkCertificate(certificate, (err, certificateStatus) => {
4✔
1803
            /* c8 ignore next */
2✔
1804
            if (err) {
2✔
1805
                return callback(err);
×
1806
            }
×
1807
            if (this.isAuditing) {
4✔
1808
                switch (certificateStatus) {
×
1809
                    case StatusCodes.Good:
×
1810
                        break;
×
1811
                    case StatusCodes.BadCertificateUntrusted:
×
1812
                        this.raiseEvent("AuditCertificateUntrustedEventType", {
×
1813
                            certificate: {
×
1814
                                dataType: DataType.ByteString,
×
1815
                                value: certificate
×
1816
                            },
×
1817
                            sourceName: {
×
1818
                                dataType: DataType.String,
×
1819
                                value: "Security/Certificate"
×
1820
                            }
×
1821
                        });
×
1822
                        break;
×
1823
                    case StatusCodes.BadCertificateTimeInvalid:
×
1824
                    case StatusCodes.BadCertificateIssuerTimeInvalid:
×
1825
                        this.raiseEvent("AuditCertificateExpiredEventType", {
×
1826
                            certificate: {
×
1827
                                dataType: DataType.ByteString,
×
1828
                                value: certificate
×
1829
                            },
×
1830
                            sourceName: {
×
1831
                                dataType: DataType.String,
×
1832
                                value: "Security/Certificate"
×
1833
                            }
×
1834
                        });
×
1835
                        break;
×
1836
                    case StatusCodes.BadCertificateRevoked:
×
1837
                    case StatusCodes.BadCertificateRevocationUnknown:
×
1838
                    case StatusCodes.BadCertificateIssuerRevocationUnknown:
×
1839
                        this.raiseEvent("AuditCertificateRevokedEventType", {
×
1840
                            certificate: {
×
1841
                                dataType: DataType.ByteString,
×
1842
                                value: certificate
×
1843
                            },
×
1844
                            sourceName: {
×
1845
                                dataType: DataType.String,
×
1846
                                value: "Security/Certificate"
×
1847
                            }
×
1848
                        });
×
1849
                        break;
×
1850
                    case StatusCodes.BadCertificateIssuerUseNotAllowed:
×
1851
                    case StatusCodes.BadCertificateUseNotAllowed:
×
1852
                    case StatusCodes.BadSecurityChecksFailed:
×
1853
                        this.raiseEvent("AuditCertificateMismatchEventType", {
×
1854
                            certificate: {
×
1855
                                dataType: DataType.ByteString,
×
1856
                                value: certificate
×
1857
                            },
×
1858
                            sourceName: {
×
1859
                                dataType: DataType.String,
×
1860
                                value: "Security/Certificate"
×
1861
                            }
×
1862
                        });
×
1863
                        break;
×
1864
                }
×
1865
            }
×
1866
            if (
4✔
1867
                certificateStatus &&
4✔
1868
                (StatusCodes.BadCertificateUntrusted.equals(certificateStatus) ||
4✔
1869
                    StatusCodes.BadCertificateTimeInvalid.equals(certificateStatus) ||
4✔
1870
                    StatusCodes.BadCertificateIssuerTimeInvalid.equals(certificateStatus) ||
4✔
1871
                    StatusCodes.BadCertificateIssuerUseNotAllowed.equals(certificateStatus) ||
4✔
1872
                    StatusCodes.BadCertificateIssuerRevocationUnknown.equals(certificateStatus) ||
4✔
1873
                    StatusCodes.BadCertificateRevocationUnknown.equals(certificateStatus) ||
4✔
1874
                    StatusCodes.BadCertificateRevoked.equals(certificateStatus) ||
4✔
1875
                    StatusCodes.BadCertificateUseNotAllowed.equals(certificateStatus) ||
4✔
1876
                    StatusCodes.BadSecurityChecksFailed.equals(certificateStatus) ||
4✔
1877
                    !StatusCodes.Good.equals(certificateStatus))
4✔
1878
            ) {
4✔
1879
                debugLog("isValidX509IdentityToken => certificateStatus = ", certificateStatus?.toString());
3✔
1880

3✔
1881
                return callback(null, StatusCodes.BadIdentityTokenRejected);
3✔
1882
            }
3✔
1883
            if (StatusCodes.Good !== certificateStatus) {
4✔
1884
                assert(certificateStatus instanceof StatusCode);
×
1885
                return callback(null, certificateStatus);
×
1886
                // return callback(null, StatusCodes.BadIdentityTokenInvalid);
×
1887
            }
×
1888

1✔
1889
            // verify if certificate is truster or rejected
1✔
1890
            // todo: StatusCodes.BadCertificateUntrusted
1✔
1891

1✔
1892
            // store untrusted certificate to rejected folder
1✔
1893
            // todo:
1✔
1894
            return callback(null, StatusCodes.Good);
1✔
1895
        });
4✔
1896
    }
4✔
1897

35✔
1898
    /**
35✔
1899
     * @internal
35✔
1900
     */
35✔
1901
    protected userNameIdentityTokenAuthenticateUser(
35✔
1902
        channel: ServerSecureChannelLayer,
77✔
1903
        session: ServerSession,
77✔
1904
        userTokenPolicy: UserTokenPolicy,
77✔
1905
        userIdentityToken: UserNameIdentityToken,
77✔
1906
        callback: (err: Error | null, isAuthorized?: boolean) => void
77✔
1907
    ): void {
77✔
1908
        assert(userIdentityToken instanceof UserNameIdentityToken);
77✔
1909
        // assert(this.isValidUserNameIdentityToken(channel, session, userTokenPolicy, userIdentityToken));
77✔
1910

77✔
1911
        const securityPolicy = adjustSecurityPolicy(channel, userTokenPolicy.securityPolicyUri);
77✔
1912

77✔
1913
        const userName = userIdentityToken.userName;
77✔
1914
        if (!userName) {
77✔
1915
            callback(new Error(" expecting a no null username"));
×
1916
            return;
×
1917
        }
×
1918
        let password: ByteString | string = userIdentityToken.password;
77✔
1919

77✔
1920
        // decrypt password if necessary
77✔
1921
        if (securityPolicy === SecurityPolicy.None) {
77✔
1922
            // not good, password was sent in clear text ...
1✔
1923
            password = password.toString();
1✔
1924
        } else {
77✔
1925
            const serverPrivateKey = this.getPrivateKey();
76✔
1926

76✔
1927
            const serverNonce = session.nonce;
76✔
1928
            if (!serverNonce) {
76✔
1929
                callback(new Error(" expecting a no null nonce"));
×
1930
                return;
×
1931
            }
×
1932

76✔
1933
            const cryptoFactory = getCryptoFactory(securityPolicy);
76✔
1934
            /* c8 ignore next */
2✔
1935
            if (!cryptoFactory) {
2✔
1936
                callback(new Error(" Unsupported security Policy"));
×
1937
                return;
×
1938
            }
×
1939

76✔
1940
            const buff = cryptoFactory.asymmetricDecrypt(password, serverPrivateKey);
76✔
1941

76✔
1942
            const result = extractPasswordFromDecryptedBlob(buff, serverNonce);
76✔
1943
            if (!result.valid) {
76✔
1944
                setImmediate(() => callback(null, false));
3✔
1945
                return;
3✔
1946
            }
3✔
1947
            password = result.password;
73✔
1948
        }
73✔
1949

74✔
1950
        this.userManager
74✔
1951
            .isValidUser(session, userName, password)
74✔
1952
            .then((isValid) => callback(null, isValid))
74✔
1953
            .catch((err) => callback(err));
74✔
1954
    }
74✔
1955

35✔
1956
    /**
35✔
1957
     * @internal
35✔
1958
     */
35✔
1959
    protected isValidUserIdentityToken(
35✔
1960
        channel: ServerSecureChannelLayer,
1,028✔
1961
        session: ServerSession,
1,028✔
1962
        userIdentityToken: UserIdentityToken,
1,028✔
1963
        userTokenSignature: SignatureData,
1,028✔
1964
        endpointDescription: EndpointDescription,
1,028✔
1965
        callback: (err: Error | null, statusCode?: StatusCode) => void
1,028✔
1966
    ): void {
1,028✔
1967
        assert(typeof callback === "function");
1,028✔
1968
        /* c8 ignore next */
2✔
1969
        if (!userIdentityToken) {
2✔
1970
            throw new Error("Invalid token");
×
1971
        }
×
1972

1,028✔
1973
        const userTokenType = getTokenType(userIdentityToken);
1,028✔
1974

1,028✔
1975
        const userTokenPolicy = findUserTokenByPolicy(endpointDescription, userTokenType, userIdentityToken.policyId);
1,028✔
1976
        if (!userTokenPolicy) {
1,028✔
1977
            // cannot find token with this policyId
×
1978
            callback(null, StatusCodes.BadIdentityTokenInvalid);
×
1979
            return;
×
1980
        }
×
1981
        //
1,028✔
1982
        if (userIdentityToken instanceof UserNameIdentityToken) {
1,028✔
1983
            this.isValidUserNameIdentityToken(channel, session, userTokenPolicy, userIdentityToken, userTokenSignature, callback);
78✔
1984
            return;
78✔
1985
        }
78✔
1986
        if (userIdentityToken instanceof X509IdentityToken) {
1,028✔
1987
            this.isValidX509IdentityToken(channel, session, userTokenPolicy, userIdentityToken, userTokenSignature, callback);
7✔
1988
            return;
7✔
1989
        }
7✔
1990

943✔
1991
        callback(null, StatusCodes.Good);
943✔
1992
    }
943✔
1993

35✔
1994
    /**
35✔
1995
     *
35✔
1996
     * @internal
35✔
1997
     * @param channel
35✔
1998
     * @param session
35✔
1999
     * @param userIdentityToken
35✔
2000
     * @param callback
35✔
2001
     * @returns {*}
35✔
2002
     */
35✔
2003
    protected isUserAuthorized(
35✔
2004
        channel: ServerSecureChannelLayer,
1,021✔
2005
        session: ServerSession,
1,021✔
2006
        userIdentityToken: UserIdentityToken,
1,021✔
2007
        callback: (err: Error | null, isAuthorized?: boolean) => void
1,021✔
2008
    ): void {
1,021✔
2009
        assert(userIdentityToken);
1,021✔
2010
        assert(typeof callback === "function");
1,021✔
2011

1,021✔
2012
        const userTokenType = getTokenType(userIdentityToken);
1,021✔
2013
        const userTokenPolicy = findUserTokenByPolicy(session.getEndpointDescription(), userTokenType, userIdentityToken.policyId);
1,021✔
2014
        /** c8 ignore next */
1,021✔
2015
        if (!userTokenPolicy) {
1,021✔
2016
            callback(null, false);
×
2017
            return;
×
2018
        }
×
2019
        // find if a userToken exists
1,021✔
2020
        if (userIdentityToken instanceof UserNameIdentityToken) {
1,021✔
2021
            this.userNameIdentityTokenAuthenticateUser(channel, session, userTokenPolicy, userIdentityToken, callback);
77✔
2022
            return;
77✔
2023
        }
77✔
2024
        setImmediate(callback.bind(null, null, true));
944✔
2025
    }
944✔
2026

35✔
2027
    protected makeServerNonce(): Nonce {
35✔
2028
        return randomBytes(32);
2,034✔
2029
    }
2,034✔
2030

35✔
2031
    // session services
35✔
2032
    // eslint-disable-next-line max-statements
35✔
2033
    protected async _on_CreateSessionRequest(message: Message, channel: ServerSecureChannelLayer): Promise<void> {
35✔
2034
        const request = message.request as CreateSessionRequest;
1,054✔
2035
        assert(request instanceof CreateSessionRequest);
1,054✔
2036

1,054✔
2037
        function rejectConnection(server: OPCUAServer, statusCode: StatusCode): void {
1,054✔
2038
            server.engine.incrementSecurityRejectedSessionCount();
19✔
2039

19✔
2040
            const response1 = new ServiceFault({
19✔
2041
                responseHeader: { serviceResult: statusCode }
19✔
2042
            });
19✔
2043
            channel.send_response("MSG", response1, message);
19✔
2044
            // and close !
19✔
2045
        }
19✔
2046

1,054✔
2047
        // From OPCUA V1.03 Part 4 5.6.2 CreateSession
1,054✔
2048
        // A Server application should limit the number of Sessions. To protect against misbehaving Clients and denial
1,054✔
2049
        // of service attacks, the Server shall close the oldest Session that is not activated before reaching the
1,054✔
2050
        // maximum number of supported Sessions
1,054✔
2051
        if (this.currentSessionCount >= this.engine.serverCapabilities.maxSessions) {
1,054✔
2052
            await _attempt_to_close_some_old_unactivated_session(this);
26✔
2053
        }
26✔
2054

1,054✔
2055
        // check if session count hasn't reach the maximum allowed sessions
1,054✔
2056
        if (this.currentSessionCount >= this.engine.serverCapabilities.maxSessions) {
1,054✔
2057
            return rejectConnection(this, StatusCodes.BadTooManySessions);
17✔
2058
        }
17✔
2059

1,037✔
2060
        // Release 1.03 OPC Unified Architecture, Part 4 page 24 - CreateSession Parameters
1,037✔
2061
        // client should prove a sessionName
1,037✔
2062
        // Session name is a Human readable string that identifies the Session. The Server makes this name and the
1,037✔
2063
        // sessionId visible in its AddressSpace for diagnostic purposes. The Client should provide a name that is
1,037✔
2064
        // unique for the instance of the Client.
1,037✔
2065
        // If this parameter is not specified the Server shall assign a value.
1,037✔
2066

1,037✔
2067
        if (isNullOrUndefined(request.sessionName)) {
1,054✔
2068
            // see also #198
1✔
2069
            // let's the server assign a sessionName for this lazy client.
1✔
2070

1✔
2071
            debugLog(
1✔
2072
                "assigning OPCUAServer.fallbackSessionName because client's sessionName is null ",
1✔
2073
                OPCUAServer.fallbackSessionName
1✔
2074
            );
1✔
2075

1✔
2076
            request.sessionName = OPCUAServer.fallbackSessionName;
1✔
2077
        }
1✔
2078

1,037✔
2079
        // Duration Requested maximum number of milliseconds that a Session should remain open without activity.
1,037✔
2080
        // If the Client fails to issue a Service request within this interval, then the Server shall automatically
1,037✔
2081
        // terminate the Client Session.
1,037✔
2082
        const revisedSessionTimeout = _adjust_session_timeout(request.requestedSessionTimeout);
1,037✔
2083

1,037✔
2084
        // Release 1.02 page 27 OPC Unified Architecture, Part 4: CreateSession.clientNonce
1,037✔
2085
        // A random number that should never be used in any other request. This number shall have a minimum length of 32
1,037✔
2086
        // bytes. Profiles may increase the required length. The Server shall use this value to prove possession of
1,037✔
2087
        // its application instance Certificate in the response.
1,037✔
2088
        if (!request.clientNonce || request.clientNonce.length < 32) {
1,054✔
2089
            if (channel.securityMode !== MessageSecurityMode.None) {
1✔
2090
                errorLog(
1✔
2091
                    chalk.red("SERVER with secure connection: Missing or invalid client Nonce "),
1✔
2092
                    request.clientNonce?.toString("hex")
1✔
2093
                );
1✔
2094

1✔
2095
                return rejectConnection(this, StatusCodes.BadNonceInvalid);
1✔
2096
            }
1✔
2097
        }
1✔
2098
        if (nonceAlreadyBeenUsed(request.clientNonce)) {
1,054✔
2099
            errorLog(chalk.red("SERVER with secure connection: Nonce has already been used"), request.clientNonce?.toString("hex"));
×
2100

×
2101
            return rejectConnection(this, StatusCodes.BadNonceInvalid);
×
2102
        }
×
2103
        // check application spoofing
1,036✔
2104
        // check if applicationUri in createSessionRequest matches applicationUri in client Certificate
1,036✔
2105
        if (!validate_applicationUri(channel, request)) {
1,054✔
2106
            return rejectConnection(this, StatusCodes.BadCertificateUriInvalid);
×
2107
        }
×
2108

1,036✔
2109
        const { errCode, endpoint } = validate_security_endpoint(this, request, channel);
1,036✔
2110
        if (errCode !== StatusCodes.Good) {
1,054✔
2111
            return rejectConnection(this, errCode);
1✔
2112
        }
1✔
2113

1,035✔
2114
        // see Release 1.02  27  OPC Unified Architecture, Part 4
1,035✔
2115
        const session = this.createSession({
1,035✔
2116
            clientDescription: request.clientDescription,
1,035✔
2117
            sessionTimeout: revisedSessionTimeout,
1,035✔
2118
            server: this
1,035✔
2119
        });
1,035✔
2120
        session.endpoint = endpoint;
1,035✔
2121

1,035✔
2122
        assert(session);
1,035✔
2123
        assert(session.sessionTimeout === revisedSessionTimeout);
1,035✔
2124

1,035✔
2125
        session.clientDescription = request.clientDescription;
1,035✔
2126
        session.sessionName = request.sessionName || `<unknown session name ${unnamed_session_count++}>`;
1,054✔
2127

1,054✔
2128
        // Depending upon on the  SecurityPolicy  and the  SecurityMode  of the  SecureChannel,  the exchange of
1,054✔
2129
        // ApplicationInstanceCertificates   and  Nonces  may be optional and the signatures may be empty. See
1,054✔
2130
        // Part  7  for the definition of  SecurityPolicies  and the handling of these parameters
1,054✔
2131

1,054✔
2132
        // serverNonce:
1,054✔
2133
        // A random number that should never be used in any other request.
1,054✔
2134
        // This number shall have a minimum length of 32 bytes.
1,054✔
2135
        // The Client shall use this value to prove possession of its application instance
1,054✔
2136
        // Certificate in the ActivateSession request.
1,054✔
2137
        // This value may also be used to prove possession of the userIdentityToken it
1,054✔
2138
        // specified in the ActivateSession request.
1,054✔
2139
        //
1,054✔
2140
        // ( this serverNonce will only be used up to the _on_ActivateSessionRequest
1,054✔
2141
        //   where a new nonce will be created)
1,054✔
2142
        session.nonce = this.makeServerNonce();
1,054✔
2143
        session.channelId = channel.channelId;
1,054✔
2144

1,054✔
2145
        session._attach_channel(channel);
1,054✔
2146

1,054✔
2147
        const serverCertificateChain = this.getCertificateChain();
1,054✔
2148

1,054✔
2149
        const hasEncryption = true;
1,054✔
2150
        // If the securityPolicyUri is None and none of the UserTokenPolicies requires encryption
1,054✔
2151
        if (session.channel?.securityMode === MessageSecurityMode.None) {
1,054✔
2152
            // ToDo: Check that none of our insecure endpoint has a a UserTokenPolicy that require encryption
871✔
2153
            // and set hasEncryption = false under this condition
871✔
2154
        }
871✔
2155

1,035✔
2156
        const response = new CreateSessionResponse({
1,035✔
2157
            // A identifier which uniquely identifies the session.
1,035✔
2158
            sessionId: session.nodeId,
1,035✔
2159

1,035✔
2160
            // A unique identifier assigned by the Server to the Session.
1,035✔
2161
            // The token used to authenticate the client in subsequent requests.
1,035✔
2162
            authenticationToken: session.authenticationToken,
1,035✔
2163

1,035✔
2164
            revisedSessionTimeout,
1,035✔
2165

1,035✔
2166
            serverNonce: session.nonce,
1,035✔
2167

1,035✔
2168
            // serverCertificate: type ApplicationServerCertificate
1,035✔
2169
            // The application instance Certificate issued to the Server.
1,035✔
2170
            // A Server shall prove possession by using the private key to sign the Nonce provided
1,035✔
2171
            // by the Client in the request. The Client shall verify that this Certificate is the same as
1,035✔
2172
            // the one it used to create the SecureChannel.
1,035✔
2173
            // The ApplicationInstanceCertificate type is defined in OpCUA 1.03 part 4 - $7.2 page 108
1,035✔
2174
            // If the securityPolicyUri is None and none of the UserTokenPolicies requires
1,035✔
2175
            // encryption, the Server shall not send an ApplicationInstanceCertificate and the Client
1,035✔
2176
            // shall ignore the ApplicationInstanceCertificate.
1,035✔
2177
            serverCertificate: hasEncryption && serverCertificateChain.length > 0 ? combine_der(serverCertificateChain) : undefined,
1,054✔
2178

1,054✔
2179
            // The endpoints provided by the server.
1,054✔
2180
            // The Server shall return a set of EndpointDescriptions available for the serverUri
1,054✔
2181
            // specified in the request.[...]
1,054✔
2182
            // The Client shall verify this list with the list from a Discovery Endpoint if it used a Discovery
1,054✔
2183
            // Endpoint to fetch the EndpointDescriptions.
1,054✔
2184
            // It is recommended that Servers only include the endpointUrl, securityMode,
1,054✔
2185
            // securityPolicyUri, userIdentityTokens, transportProfileUri and securityLevel with all
1,054✔
2186
            // other parameters set to null. Only the recommended parameters shall be verified by
1,054✔
2187
            // the client.
1,054✔
2188
            serverEndpoints: _serverEndpointsForCreateSessionResponse(this, session.endpoint?.endpointUrl || "", request.serverUri),
1,054✔
2189

1,054✔
2190
            // This parameter is deprecated and the array shall be empty.
1,054✔
2191
            serverSoftwareCertificates: null,
1,054✔
2192

1,054✔
2193
            // This is a signature generated with the private key associated with the
1,054✔
2194
            // serverCertificate. This parameter is calculated by appending the clientNonce to the
1,054✔
2195
            // clientCertificate and signing the resulting sequence of bytes.
1,054✔
2196
            // The SignatureAlgorithm shall be the AsymmetricSignatureAlgorithm specified in the
1,054✔
2197
            // SecurityPolicy for the Endpoint.
1,054✔
2198
            // The SignatureData type is defined in 7.30.
1,054✔
2199
            serverSignature: this.computeServerSignature(channel, request.clientCertificate, request.clientNonce),
1,054✔
2200

1,054✔
2201
            // The maximum message size accepted by the server
1,054✔
2202
            // The Client Communication Stack should return a Bad_RequestTooLarge error to the
1,054✔
2203
            // application if a request message exceeds this limit.
1,054✔
2204
            // The value zero indicates that this parameter is not used.
1,054✔
2205
            maxRequestMessageSize: 0x4000000
1,054✔
2206
        });
1,054✔
2207

1,054✔
2208
        this.emit("create_session", session);
1,054✔
2209

1,054✔
2210
        session.on("session_closed", (session1: ServerSession, deleteSubscriptions: boolean, reason: string) => {
1,054✔
2211
            assert(typeof reason === "string");
1,035✔
2212
            if (this.isAuditing) {
1,035✔
2213
                assert(reason === "Timeout" || reason === "Terminated" || reason === "CloseSession" || reason === "Forcing");
4✔
2214
                const sourceName = `Session/${reason}`;
4✔
2215

4✔
2216
                this.raiseEvent("AuditSessionEventType", {
4✔
2217
                    /* part 5 -  6.4.3 AuditEventType */
4✔
2218
                    actionTimeStamp: { dataType: "DateTime", value: new Date() },
4✔
2219
                    status: { dataType: "Boolean", value: true },
4✔
2220

4✔
2221
                    serverId: { dataType: "String", value: "" },
4✔
2222

4✔
2223
                    // ClientAuditEntryId contains the human-readable AuditEntryId defined in Part 3.
4✔
2224
                    clientAuditEntryId: { dataType: "String", value: "" },
4✔
2225

4✔
2226
                    // The ClientUserId identifies the user of the client requesting an action. The ClientUserId can be
4✔
2227
                    // obtained from the UserIdentityToken passed in the ActivateSession call.
4✔
2228
                    clientUserId: { dataType: "String", value: "" },
4✔
2229

4✔
2230
                    sourceName: { dataType: "String", value: sourceName },
4✔
2231

4✔
2232
                    /* part 5 - 6.4.7 AuditSessionEventType */
4✔
2233
                    sessionId: { dataType: "NodeId", value: session1.nodeId }
4✔
2234
                });
4✔
2235
            }
4✔
2236

1,035✔
2237
            this.emit("session_closed", session1, deleteSubscriptions);
1,035✔
2238
        });
1,054✔
2239

1,054✔
2240
        if (this.isAuditing) {
1,054✔
2241
            // ------------------------------------------------------------------------------------------------------
4✔
2242
            this.raiseEvent("AuditCreateSessionEventType", {
4✔
2243
                /* part 5 -  6.4.3 AuditEventType */
4✔
2244
                actionTimeStamp: { dataType: "DateTime", value: new Date() },
4✔
2245
                status: { dataType: "Boolean", value: true },
4✔
2246

4✔
2247
                serverId: { dataType: "String", value: "" },
4✔
2248

4✔
2249
                // ClientAuditEntryId contains the human-readable AuditEntryId defined in Part 3.
4✔
2250
                clientAuditEntryId: { dataType: "String", value: "" },
4✔
2251

4✔
2252
                // The ClientUserId identifies the user of the client requesting an action. The ClientUserId can be
4✔
2253
                // obtained from the UserIdentityToken passed in the ActivateSession call.
4✔
2254
                clientUserId: { dataType: "String", value: "" },
4✔
2255

4✔
2256
                sourceName: { dataType: "String", value: "Session/CreateSession" },
4✔
2257

4✔
2258
                /* part 5 - 6.4.7 AuditSessionEventType */
4✔
2259
                sessionId: { dataType: "NodeId", value: session.nodeId },
4✔
2260

4✔
2261
                /* part 5 - 6.4.8 AuditCreateSessionEventType */
4✔
2262
                // SecureChannelId shall uniquely identify the SecureChannel. The application shall use the same
4✔
2263
                // identifier in all AuditEvents related to the Session Service Set (AuditCreateSessionEventType,
4✔
2264
                // AuditActivateSessionEventType and their subtypes) and the SecureChannel Service Set
4✔
2265
                // (AuditChannelEventType and its subtypes
4✔
2266
                secureChannelId: {
4✔
2267
                    dataType: "String",
4✔
2268
                    value: session.channel?.channelId?.toString() ?? ""
4✔
2269
                },
4✔
2270

4✔
2271
                // Duration
4✔
2272
                revisedSessionTimeout: {
4✔
2273
                    dataType: "Duration",
4✔
2274
                    value: session.sessionTimeout
4✔
2275
                },
4✔
2276

4✔
2277
                // clientCertificate
4✔
2278
                clientCertificate: {
4✔
2279
                    dataType: "ByteString",
4✔
2280
                    value: session.channel?.clientCertificate ?? null
4✔
2281
                },
4✔
2282

4✔
2283
                // clientCertificateThumbprint
4✔
2284
                clientCertificateThumbprint: {
4✔
2285
                    dataType: "String",
4✔
2286
                    value: thumbprint(session.channel?.clientCertificate)
4✔
2287
                }
4✔
2288
            });
4✔
2289
        }
4✔
2290
        // -----------------------------------------------------------------------------------------------------------
1,035✔
2291

1,035✔
2292
        assert(response.authenticationToken);
1,035✔
2293
        channel.send_response("MSG", response, message);
1,035✔
2294
    }
1,035✔
2295

35✔
2296
    // TODO : implement this:
35✔
2297
    //
35✔
2298
    // When the ActivateSession Service is called for the first time then the Server shall reject the request
35✔
2299
    // if the SecureChannel is not same as the one associated with the CreateSession request.
35✔
2300
    // Subsequent calls to ActivateSession may be associated with different SecureChannels. If this is the
35✔
2301
    // case then the Server shall verify that the Certificate the Client used to create the new
35✔
2302
    // SecureChannel is the same as the Certificate used to create the original SecureChannel. In addition,
35✔
2303
    // the Server shall verify that the Client supplied a UserIdentityToken that is identical to the token
35✔
2304
    // currently associated with the Session. Once the Server accepts the new SecureChannel it shall
35✔
2305
    // reject requests sent via the old SecureChannel.
35✔
2306
    /**
35✔
2307
     *
35✔
2308
     * @private
35✔
2309
     *
35✔
2310
     *
35✔
2311
     */
35✔
2312
    protected _on_ActivateSessionRequest(message: Message, channel: ServerSecureChannelLayer): void {
35✔
2313
        const request = message.request as ActivateSessionRequest;
1,067✔
2314
        assert(request instanceof ActivateSessionRequest);
1,067✔
2315

1,067✔
2316
        // get session from authenticationToken
1,067✔
2317
        const authenticationToken = request.requestHeader.authenticationToken;
1,067✔
2318

1,067✔
2319
        const session = this.getSession(authenticationToken);
1,067✔
2320

1,067✔
2321
        function rejectConnection(server: OPCUAServer, statusCode: StatusCode): void {
1,067✔
2322
            if (statusCode.equals(StatusCodes.BadSessionIdInvalid)) {
67✔
2323
                server.engine.incrementRejectedSessionCount();
29✔
2324
            } else {
67✔
2325
                server.engine.incrementRejectedSessionCount();
38✔
2326
                server.engine.incrementSecurityRejectedSessionCount();
38✔
2327
            }
38✔
2328

67✔
2329
            const response1 = new ActivateSessionResponse({
67✔
2330
                responseHeader: { serviceResult: statusCode }
67✔
2331
            });
67✔
2332

67✔
2333
            channel.send_response("MSG", response1, message);
67✔
2334
        }
67✔
2335

1,067✔
2336
        /* c8 ignore next */
2✔
2337
        if (!session) {
2✔
2338
            // this may happen when the server has been restarted and a client tries to reconnect, thinking
29✔
2339
            // that the previous session may still be active
29✔
2340
            debugLog(chalk.yellow.bold(" Bad Session in  _on_ActivateSessionRequest"), authenticationToken.toString());
29✔
2341

29✔
2342
            rejectConnection(this, StatusCodes.BadSessionIdInvalid);
29✔
2343
            return;
29✔
2344
        }
29✔
2345

1,038✔
2346
        // tslint:disable-next-line: no-unused-expression
1,038✔
2347
        session.keepAlive ? session.keepAlive() : void 0;
1,067✔
2348

1,067✔
2349
        // OpcUA 1.02 part 3 $5.6.3.1 ActiveSession Set page 29
1,067✔
2350
        // When the ActivateSession  Service  is called f or the first time then the Server shall reject the request
1,067✔
2351
        // if the  SecureChannel  is not same as the one associated with the  CreateSession  request.
1,067✔
2352
        if (session.status === "new") {
1,067✔
2353
            // xx if (channel.session_nonce !== session.nonce) {
1,008✔
2354
            if (!channel_has_session(channel, session)) {
1,008✔
2355
                // it looks like session activation is being using a channel that is not the
1✔
2356
                // one that have been used to create the session
1✔
2357
                errorLog(` channel.sessionTokens === ${Object.keys(channel.sessionTokens).join(" ")}`);
1✔
2358
                rejectConnection(this, StatusCodes.BadSessionNotActivated);
1✔
2359
                return;
1✔
2360
            }
1✔
2361
        }
1,008✔
2362

1,037✔
2363
        // OpcUA 1.02 part 3 $5.6.3.1 ActiveSession Set page 29
1,037✔
2364
        // ... Subsequent calls to  ActivateSession  may be associated with different  SecureChannels.  If this is the
1,037✔
2365
        // case then  the  Server  shall verify that the  Certificate  the  Client  used to create the new
1,037✔
2366
        // SecureChannel  is the same as the  Certificate  used to create the original  SecureChannel.
1,037✔
2367

1,037✔
2368
        if (session.status === "active") {
1,067✔
2369
            if (session.channel?.channelId !== channel.channelId) {
29✔
2370
                warningLog(
17✔
2371
                    " Session ",
17✔
2372
                    session.sessionName,
17✔
2373
                    " is being transferred from channel",
17✔
2374
                    chalk.cyan(session.channel?.channelId?.toString()),
17✔
2375
                    " to channel ",
17✔
2376
                    chalk.cyan(channel.channelId?.toString())
17✔
2377
                );
17✔
2378

17✔
2379
                // session is being reassigned to a new Channel,
17✔
2380
                // we shall verify that the certificate used to create the Session is the same as the current
17✔
2381
                // channel certificate.
17✔
2382
                const old_channel_cert_thumbprint = thumbprint(session.channel?.clientCertificate);
17✔
2383
                const new_channel_cert_thumbprint = thumbprint(channel.clientCertificate);
17✔
2384

17✔
2385
                if (old_channel_cert_thumbprint !== new_channel_cert_thumbprint) {
17✔
2386
                    rejectConnection(this, StatusCodes.BadNoValidCertificates); // not sure about this code !
1✔
2387
                    return;
1✔
2388
                }
1✔
2389

16✔
2390
                // ... In addition the Server shall verify that the  Client  supplied a  UserIdentityToken  that is
16✔
2391
                // identical to the token currently associated with the  Session reassign session to new channel.
16✔
2392
                if (!sameIdentityToken(session.userIdentityToken, request.userIdentityToken as UserIdentityToken)) {
17✔
2393
                    rejectConnection(this, StatusCodes.BadIdentityChangeNotSupported); // not sure about this code !
1✔
2394
                    return;
1✔
2395
                }
1✔
2396
            }
17✔
2397
        } else if (session.status === "screwed") {
1,067✔
2398
            // session has been used before being activated => this should be detected and session should be dismissed.
1✔
2399
            rejectConnection(this, StatusCodes.BadSessionClosed);
1✔
2400
            return;
1✔
2401
        } else if (session.status === "closed") {
1,008✔
2402
            warningLog(chalk.yellow.bold(" Bad Session Closed in  _on_ActivateSessionRequest"), authenticationToken.toString());
×
2403
            rejectConnection(this, StatusCodes.BadSessionClosed);
×
2404
            return;
×
2405
        }
×
2406

1,034✔
2407
        // verify clientSignature provided by the client
1,034✔
2408
        if (!this.verifyClientSignature(session, channel, request.clientSignature)) {
1,067✔
2409
            rejectConnection(this, StatusCodes.BadApplicationSignatureInvalid);
6✔
2410
            return;
6✔
2411
        }
6✔
2412

1,028✔
2413
        const endpoint = session.endpoint;
1,028✔
2414
        if (!endpoint) {
1,067✔
2415
            rejectConnection(this, StatusCodes.BadSessionIdInvalid);
×
2416
            return;
×
2417
        }
×
2418

1,028✔
2419
        // userIdentityToken may be missing , assume anonymous access then
1,028✔
2420
        request.userIdentityToken = request.userIdentityToken || createAnonymousIdentityToken(endpoint);
1,067✔
2421

1,067✔
2422
        // check request.userIdentityToken is correct ( expected type and correctly formed)
1,067✔
2423
        this.isValidUserIdentityToken(
1,067✔
2424
            channel,
1,067✔
2425
            session,
1,067✔
2426
            request.userIdentityToken as UserIdentityToken,
1,067✔
2427
            request.userTokenSignature,
1,067✔
2428
            endpoint,
1,067✔
2429
            (_err: Error | null, statusCode?: StatusCode) => {
1,067✔
2430
                if (!statusCode || statusCode.isNotGood()) {
1,028✔
2431
                    /* c8 ignore next */
2✔
2432
                    if (!(statusCode && statusCode instanceof StatusCode)) {
2✔
2433
                        return rejectConnection(this, StatusCodes.BadCertificateInvalid);
×
2434
                    }
×
2435
                    return rejectConnection(this, statusCode);
7✔
2436
                }
7✔
2437

1,021✔
2438
                // check if user access is granted
1,021✔
2439
                this.isUserAuthorized(
1,021✔
2440
                    channel,
1,021✔
2441
                    session,
1,021✔
2442
                    request.userIdentityToken as UserIdentityToken,
1,021✔
2443
                    (err1: Error | null, authorized?: boolean) => {
1,021✔
2444
                        /* c8 ignore next */
2✔
2445
                        if (err1) {
2✔
2446
                            return rejectConnection(this, StatusCodes.BadInternalError);
×
2447
                        }
×
2448

1,021✔
2449
                        if (!authorized) {
1,021✔
2450
                            return rejectConnection(this, StatusCodes.BadUserAccessDenied);
21✔
2451
                        } else {
1,021✔
2452
                            if (session.status === "active") {
1,000✔
2453
                                moveSessionToChannel(session, channel);
21✔
2454
                            }
21✔
2455
                            session.userIdentityToken = request.userIdentityToken as UserIdentityToken;
1,000✔
2456

1,000✔
2457
                            // extract : OPC UA part 4 - 5.6.3
1,000✔
2458
                            // Once used, a serverNonce cannot be used again. For that reason, the Server returns a new
1,000✔
2459
                            // serverNonce each time the ActivateSession Service is called.
1,000✔
2460
                            session.nonce = this.makeServerNonce();
1,000✔
2461

1,000✔
2462
                            session.status = "active";
1,000✔
2463

1,000✔
2464
                            const response = new ActivateSessionResponse({
1,000✔
2465
                                serverNonce: session.nonce
1,000✔
2466
                            });
1,000✔
2467
                            channel.send_response("MSG", response, message);
1,000✔
2468

1,000✔
2469
                            // send OPCUA Event Notification
1,000✔
2470
                            // see part 5 : 6.4.3 AuditEventType
1,000✔
2471
                            //              6.4.7 AuditSessionEventType
1,000✔
2472
                            //              6.4.10 AuditActivateSessionEventType
1,000✔
2473
                            assert(session.nodeId); // sessionId
1,000✔
2474
                            // xx assert(session.channel.clientCertificate instanceof Buffer);
1,000✔
2475
                            assert(session.sessionTimeout > 0);
1,000✔
2476

1,000✔
2477
                            raiseAuditActivateSessionEventType.call(this, session);
1,000✔
2478

1,000✔
2479
                            this.emit("session_activated", session, userIdentityTokenPasswordRemoved(session.userIdentityToken));
1,000✔
2480

1,000✔
2481
                            session.resendMonitoredItemInitialValues();
1,000✔
2482
                        }
1,000✔
2483
                    }
1,021✔
2484
                );
1,021✔
2485
            }
1,021✔
2486
        );
1,067✔
2487
    }
1,067✔
2488

35✔
2489
    protected prepare(message: Message, channel: ServerSecureChannelLayer): void {
35✔
2490
        const request = message.request;
45,756✔
2491

45,756✔
2492
        // --- check that session is correct
45,756✔
2493
        const authenticationToken = request.requestHeader.authenticationToken;
45,756✔
2494
        const session = this.getSession(authenticationToken, /*activeOnly*/ true);
45,756✔
2495
        if (!session) {
45,756✔
2496
            message.session_statusCode = StatusCodes.BadSessionIdInvalid;
2,202✔
2497
            return;
2,202✔
2498
        }
2,202✔
2499
        message.session = session;
43,558✔
2500

43,554✔
2501
        // --- check that provided session matches session attached to channel
43,554✔
2502
        if (channel.channelId !== session.channelId) {
45,752✔
2503
            if (!(request instanceof ActivateSessionRequest)) {
18!
2504
                // Note: PublishRequests arriving on the new channel before
×
2505
                // ActivateSession completes the session transfer are expected
×
2506
                // transient occurrences, not errors worth logging.
×
2507
                if (request.constructor.name !== "PublishRequest") {
×
2508
                    errorLog(
×
2509
                        chalk.red.bgWhite(
×
2510
                            `ERROR: channel.channelId !== session.channelId  on processing request ${request.constructor.name}`
×
2511
                        ),
×
2512
                        channel.channelId,
×
2513
                        session.channelId
×
2514
                    );
×
2515
                }
×
2516
            }
×
2517
            message.session_statusCode = StatusCodes.BadSecureChannelIdInvalid;
18✔
2518
        } else if (channel_has_session(channel, session)) {
45,752✔
2519
            message.session_statusCode = StatusCodes.Good;
43,536✔
2520
        } else {
43,536!
2521
            // session ma y have been moved to a different channel
×
2522
            message.session_statusCode = StatusCodes.BadSecureChannelIdInvalid;
×
2523
        }
×
2524
    }
45,756✔
2525

35✔
2526
    /**
35✔
2527
     * ensure that action is performed on a valid session object,
35✔
2528
     * @param ResponseClass the constructor of the response Class
35✔
2529
     * @param message
35✔
2530
     * @param channel
35✔
2531
     * @param actionToPerform
35✔
2532
     * @param actionToPerform.session {ServerSession}
35✔
2533
     * @param actionToPerform.sendResponse
35✔
2534
     * @param actionToPerform.sendResponse.response
35✔
2535
     * @param actionToPerform.sendError
35✔
2536
     * @param actionToPerform.sendError.statusCode
35✔
2537
     * @param actionToPerform.sendError.diagnostics
35✔
2538
     *
35✔
2539
     * @private
35✔
2540
     */
35✔
2541
    protected async _apply_on_SessionObject(
35✔
2542
        ResponseClass: ResponseClassType,
41,545✔
2543
        message: Message,
41,545✔
2544
        channel: ServerSecureChannelLayer,
41,545✔
2545
        actionToPerform: (
41,545✔
2546
            session: ServerSession,
41,545✔
2547
            sendResponse: (response: Response) => void,
41,545✔
2548
            sendError: (statusCode: StatusCode) => void
41,545✔
2549
        ) => void | Promise<void>
41,545✔
2550
    ): Promise<void> {
41,545✔
2551
        assert(typeof actionToPerform === "function");
41,545✔
2552

41,545✔
2553
        function sendResponse(response1: Response) {
41,545✔
2554
            try {
41,488✔
2555
                assert(response1 instanceof ResponseClass || response1 instanceof ServiceFault);
41,488✔
2556
                if (message.session) {
41,488✔
2557
                    const counterName = ResponseClass.schema.name.replace("Response", "");
41,476✔
2558
                    message.session.incrementRequestTotalCounter(counterName);
41,476✔
2559
                }
41,476✔
2560
                return channel.send_response("MSG", response1, message);
41,488✔
2561
            } catch (err) {
41,488✔
2562
                warningLog(err);
×
2563
                // c8 ignore next
×
2564
                if (types.isNativeError(err)) {
×
2565
                    // c8 ignore next
×
2566
                    errorLog(
×
2567
                        "Internal error in issuing response\nplease contact support@sterfive.com",
×
2568
                        message.request.toString(),
×
2569
                        "\n",
×
2570
                        response1.toString()
×
2571
                    );
×
2572
                }
×
2573
                // c8 ignore next
×
2574
                throw err;
×
2575
            }
×
2576
        }
41,488✔
2577

41,545✔
2578
        function sendError(statusCode: StatusCode) {
41,545✔
2579
            if (message.session) {
57✔
2580
                message.session.incrementRequestErrorCounter(ResponseClass.schema.name.replace("Response", ""));
57✔
2581
            }
57✔
2582
            return g_sendError(channel, message, ResponseClass, statusCode);
57✔
2583
        }
57✔
2584

41,545✔
2585
        /* c8 ignore next */
2✔
2586
        if (!message.session || message.session_statusCode !== StatusCodes.Good) {
2✔
2587
            const sessionStatusCode = message.session_statusCode ?? StatusCodes.BadInternalError;
12✔
2588
            const errMessage = `=>${sessionStatusCode.toString()}`;
12✔
2589
            const response = new ServiceFault({
12✔
2590
                responseHeader: { serviceResult: sessionStatusCode }
12✔
2591
            });
12✔
2592
            debugLog(chalk.red.bold(errMessage), chalk.yellow(sessionStatusCode.toString()), response.constructor.name);
12✔
2593
            return sendResponse(response);
12✔
2594
        }
12✔
2595

41,533✔
2596
        assert(message.session_statusCode.isGood());
41,533✔
2597

41,533✔
2598
        // OPC UA Specification 1.02 part 4 page 26
41,533✔
2599
        // When a  Session  is terminated, all outstanding requests on the  Session  are aborted and
41,533✔
2600
        // Bad_SessionClosed  StatusCodes  are returned to the  Client. In addition,   the  Server  deletes the entry
41,533✔
2601
        // for the  Client  from its  SessionDiagnostics Array  Variable  and notifies any other  Clients  who were
41,533✔
2602
        // subscribed to this entry.
41,533✔
2603
        if (message.session.status === "closed") {
41,545✔
2604
            // note : use StatusCodes.BadSessionClosed , for pending message for this session
×
2605
            return sendError(StatusCodes.BadSessionIdInvalid);
×
2606
        }
×
2607

41,533✔
2608
        if (message.session.status === "new") {
41,545✔
2609
            // mark session as being screwed ! so it cannot be activated anymore
1✔
2610
            message.session.status = "screwed";
1✔
2611

1✔
2612
            return sendError(StatusCodes.BadSessionNotActivated);
1✔
2613
        }
1✔
2614

41,532✔
2615
        if (message.session.status !== "active") {
41,545✔
2616
            // mark session as being screwed ! so it cannot be activated anymore
×
2617
            message.session.status = "screwed";
×
2618

×
2619
            // note : use StatusCodes.BadSessionClosed , for pending message for this session
×
2620
            return sendError(StatusCodes.BadSessionIdInvalid);
×
2621
        }
×
2622

41,532✔
2623
        // lets also reset the session watchdog so it doesn't
41,532✔
2624
        // (Sessions are terminated by the Server automatically if the Client fails to issue a Service
41,532✔
2625
        // request on the Session within the timeout period negotiated by the Server in the
41,532✔
2626
        // CreateSession Service response. )
41,532✔
2627
        if (message.session.keepAlive) {
41,532✔
2628
            assert(typeof message.session.keepAlive === "function");
41,532✔
2629
            message.session.keepAlive();
41,532✔
2630
        }
41,532✔
2631
        message.session.incrementTotalRequestCount();
41,532✔
2632
        await actionToPerform(message.session as ServerSession, sendResponse, sendError);
41,532✔
2633
    }
41,532✔
2634

35✔
2635
    protected async _apply_on_Subscription(
35✔
2636
        ResponseClass: ResponseClassType,
857✔
2637
        message: Message,
857✔
2638
        channel: ServerSecureChannelLayer,
857✔
2639
        actionToPerform: (
857✔
2640
            session: ServerSession,
857✔
2641
            subscription: Subscription,
857✔
2642
            sendResponse: (response: Response) => void,
857✔
2643
            sendError: (statusCode: StatusCode) => void
857✔
2644
        ) => Promise<void>
857✔
2645
    ): Promise<void> {
857✔
2646
        assert(typeof actionToPerform === "function");
857✔
2647
        const request = message.request as unknown as { subscriptionId: number };
857✔
2648
        assert(Object.hasOwn(request, "subscriptionId"));
857✔
2649

857✔
2650
        this._apply_on_SessionObject(
857✔
2651
            ResponseClass,
857✔
2652
            message,
857✔
2653
            channel,
857✔
2654
            async (
857✔
2655
                session: ServerSession,
857✔
2656
                sendResponse: (response: Response) => void,
857✔
2657
                sendError: (statusCode: StatusCode) => void
857✔
2658
            ) => {
857✔
2659
                const subscription = session.getSubscription(request.subscriptionId);
857✔
2660
                if (!subscription) {
857✔
2661
                    return sendError(StatusCodes.BadSubscriptionIdInvalid);
7✔
2662
                }
7✔
2663
                subscription.resetLifeTimeAndKeepAliveCounters();
850✔
2664
                await actionToPerform(session, subscription, sendResponse, sendError);
850✔
2665
            }
850✔
2666
        );
857✔
2667
    }
857✔
2668

35✔
2669
    protected _apply_on_SubscriptionIds<T>(
35✔
2670
        ResponseClass: typeof SetPublishingModeResponse | typeof TransferSubscriptionsResponse | typeof DeleteSubscriptionsResponse,
314✔
2671
        message: Message,
314✔
2672
        channel: ServerSecureChannelLayer,
314✔
2673
        actionToPerform: (session: ServerSession, subscriptionId: number) => Promise<T>
314✔
2674
    ): void {
314✔
2675
        assert(typeof actionToPerform === "function");
314✔
2676
        const request = message.request as unknown as { subscriptionIds: number[] };
314✔
2677
        assert(Object.hasOwn(request, "subscriptionIds"));
314✔
2678

314✔
2679
        this._apply_on_SessionObject(
314✔
2680
            ResponseClass,
314✔
2681
            message,
314✔
2682
            channel,
314✔
2683
            async (
314✔
2684
                session: ServerSession,
314✔
2685
                sendResponse: (response: Response) => void,
314✔
2686
                sendError: (statusCode: StatusCode) => void
314✔
2687
            ) => {
314✔
2688
                const subscriptionIds = request.subscriptionIds;
314✔
2689

314✔
2690
                if (!request.subscriptionIds || request.subscriptionIds.length === 0) {
314✔
2691
                    return sendError(StatusCodes.BadNothingToDo);
1✔
2692
                }
1✔
2693

313✔
2694
                // check minimal
313✔
2695
                if (
313✔
2696
                    request.subscriptionIds.length >
313✔
2697
                    Math.min(
313✔
2698
                        this.engine.serverCapabilities.maxSubscriptionsPerSession,
313✔
2699
                        this.engine.serverCapabilities.maxSubscriptions
313✔
2700
                    )
313✔
2701
                ) {
314✔
2702
                    return sendError(StatusCodes.BadTooManyOperations);
×
2703
                }
×
2704

313✔
2705
                const promises: Promise<T>[] = subscriptionIds.map((subscriptionId: number) =>
313✔
2706
                    actionToPerform(session, subscriptionId)
318✔
2707
                );
313✔
2708
                const results: T[] = await Promise.all(promises);
313✔
2709

313✔
2710
                const serviceResult: StatusCode = StatusCodes.Good;
313✔
2711
                const response = new ResponseClass({
313✔
2712
                    responseHeader: {
313✔
2713
                        serviceResult
313✔
2714
                    },
313✔
2715
                    results: results as unknown as StatusCode[]
313✔
2716
                });
313✔
2717
                sendResponse(response);
313✔
2718
            }
313✔
2719
        );
314✔
2720
    }
314✔
2721

35✔
2722
    protected _apply_on_Subscriptions(
35✔
2723
        ResponseClass: typeof SetPublishingModeResponse | typeof TransferSubscriptionsResponse | typeof DeleteSubscriptionsResponse,
4✔
2724
        message: Message,
4✔
2725
        channel: ServerSecureChannelLayer,
4✔
2726
        actionToPerform: (session: ServerSession, subscription: Subscription) => Promise<StatusCode>
4✔
2727
    ): void {
4✔
2728
        this._apply_on_SubscriptionIds<StatusCode>(
4✔
2729
            ResponseClass,
4✔
2730
            message,
4✔
2731
            channel,
4✔
2732
            async (session: ServerSession, subscriptionId: number) => {
4✔
2733
                /* c8 ignore next */
2✔
2734
                if (isSubscriptionIdInvalid(subscriptionId)) {
2✔
2735
                    return StatusCodes.BadSubscriptionIdInvalid;
×
2736
                }
×
2737
                const subscription = session.getSubscription(subscriptionId);
4✔
2738
                if (!subscription) {
4✔
2739
                    return StatusCodes.BadSubscriptionIdInvalid;
2✔
2740
                }
2✔
2741
                return actionToPerform(session, subscription);
2✔
2742
            }
2✔
2743
        );
4✔
2744
    }
4✔
2745

35✔
2746
    private async _closeSession(authenticationToken: NodeId, deleteSubscriptions: boolean, reason: ClosingReason): Promise<void> {
35✔
2747
        if (deleteSubscriptions && this.options.onDeleteMonitoredItem) {
981✔
2748
            const session = this.getSession(authenticationToken);
4✔
2749
            if (session) {
4✔
2750
                const subscriptions = session.publishEngine.subscriptions;
4✔
2751
                for (const subscription of subscriptions) {
4✔
2752
                    const onDeleteFn = this.options.onDeleteMonitoredItem;
1✔
2753
                    await subscription.applyOnMonitoredItem(async (monitoredItem: MonitoredItem) => {
1✔
2754
                        await onDeleteFn(subscription, monitoredItem);
1✔
2755
                    });
1✔
2756
                }
1✔
2757
            }
4✔
2758
        }
4✔
2759
        await this.engine.closeSession(authenticationToken, deleteSubscriptions, reason);
981✔
2760
    }
981✔
2761
    /**
35✔
2762
     * @param message
35✔
2763
     * @param channel
35✔
2764
     * @private
35✔
2765
     */
35✔
2766
    protected _on_CloseSessionRequest(message: Message, channel: ServerSecureChannelLayer): void {
35✔
2767
        const request = message.request as CloseSessionRequest;
1,004✔
2768
        assert(request instanceof CloseSessionRequest);
1,004✔
2769

1,004✔
2770
        message.session_statusCode = StatusCodes.Good;
1,004✔
2771

1,004✔
2772
        function sendError(statusCode: StatusCode) {
1,004✔
2773
            return g_sendError(channel, message, CloseSessionResponse, statusCode);
23✔
2774
        }
23✔
2775

1,004✔
2776
        function sendResponse(response1: CloseSessionResponse) {
1,004✔
2777
            channel.send_response("MSG", response1, message);
981✔
2778
        }
981✔
2779

1,004✔
2780
        // do not use _apply_on_SessionObject
1,004✔
2781
        // this._apply_on_SessionObject(CloseSessionResponse, message, channel, function (session) {
1,004✔
2782
        // });
1,004✔
2783

1,004✔
2784
        const session = message.session;
1,004✔
2785
        if (!session) {
1,004✔
2786
            sendError(StatusCodes.BadSessionIdInvalid);
23✔
2787
            return;
23✔
2788
        }
23✔
2789

981✔
2790
        // session has been created but not activated !
981✔
2791
        const _wasNotActivated = session.status === "new";
981✔
2792

981✔
2793
        (async () => {
981✔
2794
            try {
981✔
2795
                await this._closeSession(request.requestHeader.authenticationToken, request.deleteSubscriptions, "CloseSession");
981✔
2796

981✔
2797
                // if (false && wasNotActivated) {
981✔
2798
                //  return sendError(StatusCodes.BadSessionNotActivated);
981✔
2799
                // }
981✔
2800

981✔
2801
                const response = new CloseSessionResponse({});
981✔
2802
                sendResponse(response);
981✔
2803
            } catch (_err) {
981✔
2804
                sendError(StatusCodes.BadInternalError);
×
2805
            }
×
2806
        })();
981✔
2807
    }
981✔
2808

35✔
2809
    // browse services
35✔
2810
    /**
35✔
2811
     * @param message
35✔
2812
     * @param channel
35✔
2813
     * @private
35✔
2814
     */
35✔
2815
    protected _on_BrowseRequest(message: Message, channel: ServerSecureChannelLayer): void {
35✔
2816
        const request = message.request as BrowseRequest;
12,666✔
2817
        assert(request instanceof BrowseRequest);
12,666✔
2818
        const diagnostic: Record<string, unknown> = {};
12,666✔
2819

12,666✔
2820
        this._apply_on_SessionObject(
12,666✔
2821
            BrowseResponse,
12,666✔
2822
            message,
12,666✔
2823
            channel,
12,666✔
2824
            (session: ServerSession, sendResponse: (response: Response) => void, sendError: (statusCode: StatusCode) => void) => {
12,666✔
2825
                let response: BrowseResponse;
12,666✔
2826
                // test view
12,666✔
2827
                if (request.view && !request.view.viewId.isEmpty()) {
12,666✔
2828
                    const addressSpace = this.engine.addressSpace;
2✔
2829
                    if (!addressSpace) {
2✔
2830
                        return sendError(StatusCodes.BadInternalError);
×
2831
                    }
×
2832
                    let theView: UAView | null = addressSpace.findNode(request.view.viewId) as UAView;
2✔
2833
                    if (theView && theView.nodeClass !== NodeClass.View) {
2✔
2834
                        // Error: theView is not a View
1✔
2835
                        diagnostic.localizedText = { text: "Expecting a view here" };
1✔
2836
                        theView = null;
1✔
2837
                    }
1✔
2838
                    if (!theView) {
2✔
2839
                        return sendError(StatusCodes.BadViewIdUnknown);
2✔
2840
                    }
2✔
2841
                }
2✔
2842

12,664✔
2843
                if (!request.nodesToBrowse || request.nodesToBrowse.length === 0) {
12,666✔
2844
                    return sendError(StatusCodes.BadNothingToDo);
1✔
2845
                }
1✔
2846

12,663✔
2847
                if (this.engine.serverCapabilities.operationLimits.maxNodesPerBrowse > 0) {
12,666✔
2848
                    if (request.nodesToBrowse.length > this.engine.serverCapabilities.operationLimits.maxNodesPerBrowse) {
1,943✔
2849
                        return sendError(StatusCodes.BadTooManyOperations);
1✔
2850
                    }
1✔
2851
                }
1,943✔
2852

12,662✔
2853
                // limit results to requestedMaxReferencesPerNode further so it never exceed a too big number
12,662✔
2854
                const requestedMaxReferencesPerNode = Math.min(9876, request.requestedMaxReferencesPerNode);
12,662✔
2855
                assert(request.nodesToBrowse[0].schema.name === "BrowseDescription");
12,662✔
2856

12,662✔
2857
                const context = session.sessionContext;
12,662✔
2858
                const browseAll = (_nodesToBrowse: BrowseDescriptionOptions[], callack: CallbackT<BrowseResult[]>) => {
12,662✔
2859
                    const f = callbackify(this.engine.browseWithAutomaticExpansion).bind(this.engine);
12,662✔
2860
                    f(request.nodesToBrowse ?? [], context, callack);
12,662✔
2861
                };
12,662✔
2862
                // handle continuation point and requestedMaxReferencesPerNode
12,662✔
2863
                const maxBrowseContinuationPoints = this.engine.serverCapabilities.maxBrowseContinuationPoints;
12,662✔
2864

12,662✔
2865
                innerBrowse(
12,662✔
2866
                    {
12,662✔
2867
                        browseAll,
12,662✔
2868
                        context,
12,662✔
2869
                        continuationPointManager: session.continuationPointManager,
12,662✔
2870
                        requestedMaxReferencesPerNode,
12,662✔
2871
                        maxBrowseContinuationPoints
12,662✔
2872
                    },
12,662✔
2873
                    request.nodesToBrowse,
12,662✔
2874
                    (_err, results) => {
12,662✔
2875
                        if (!results) {
12,662✔
2876
                            return sendError(StatusCodes.BadInternalError);
×
2877
                        }
×
2878
                        assert(results[0].schema.name === "BrowseResult");
12,662✔
2879
                        response = new BrowseResponse({
12,662✔
2880
                            diagnosticInfos: undefined,
12,662✔
2881
                            results
12,662✔
2882
                        });
12,662✔
2883
                        sendResponse(response);
12,662✔
2884
                    }
12,662✔
2885
                );
12,662✔
2886
            }
12,662✔
2887
        );
12,666✔
2888
    }
12,666✔
2889

35✔
2890
    /**
35✔
2891
     */
35✔
2892
    protected _on_BrowseNextRequest(message: Message, channel: ServerSecureChannelLayer): void {
35✔
2893
        const request = message.request as BrowseNextRequest;
7✔
2894
        assert(request instanceof BrowseNextRequest);
7✔
2895
        this._apply_on_SessionObject(
7✔
2896
            BrowseNextResponse,
7✔
2897
            message,
7✔
2898
            channel,
7✔
2899
            (session: ServerSession, sendResponse: (response: Response) => void, sendError: (statusCode: StatusCode) => void) => {
7✔
2900
                if (!request.continuationPoints || request.continuationPoints.length === 0) {
7✔
2901
                    return sendError(StatusCodes.BadNothingToDo);
1✔
2902
                }
1✔
2903
                innerBrowseNext(
6✔
2904
                    {
6✔
2905
                        continuationPointManager: session.continuationPointManager
6✔
2906
                    },
6✔
2907
                    request.continuationPoints,
6✔
2908
                    request.releaseContinuationPoints,
6✔
2909
                    (err, results) => {
6✔
2910
                        if (err) {
6✔
2911
                            return sendError(StatusCodes.BadInternalError);
×
2912
                        }
×
2913
                        const response = new BrowseNextResponse({
6✔
2914
                            diagnosticInfos: undefined,
6✔
2915
                            results
6✔
2916
                        });
6✔
2917
                        sendResponse(response);
6✔
2918
                    }
6✔
2919
                );
6✔
2920
            }
6✔
2921
        );
7✔
2922
    }
7✔
2923

35✔
2924
    // read services
35✔
2925
    protected _on_ReadRequest(message: Message, channel: ServerSecureChannelLayer): void {
35✔
2926
        const request = message.request as ReadRequest;
23,676✔
2927
        assert(request instanceof ReadRequest);
23,676✔
2928

23,676✔
2929
        this._apply_on_SessionObject(
23,676✔
2930
            ReadResponse,
23,676✔
2931
            message,
23,676✔
2932
            channel,
23,676✔
2933
            (session: ServerSession, sendResponse: (response: Response) => void, sendError: (statusCode: StatusCode) => void) => {
23,676✔
2934
                const context = session.sessionContext;
23,663✔
2935

23,663✔
2936
                const timestampsToReturn = request.timestampsToReturn;
23,663✔
2937

23,663✔
2938
                if (timestampsToReturn === TimestampsToReturn.Invalid) {
23,663✔
2939
                    return sendError(StatusCodes.BadTimestampsToReturnInvalid);
1✔
2940
                }
1✔
2941

23,662✔
2942
                if (request.maxAge < 0) {
23,663✔
2943
                    return sendError(StatusCodes.BadMaxAgeInvalid);
1✔
2944
                }
1✔
2945

23,661✔
2946
                request.nodesToRead = request.nodesToRead || [];
23,663✔
2947

23,663✔
2948
                if (!request.nodesToRead || request.nodesToRead.length <= 0) {
23,663✔
2949
                    return sendError(StatusCodes.BadNothingToDo);
5✔
2950
                }
5✔
2951

23,656✔
2952
                assert(request.nodesToRead[0].schema.name === "ReadValueId");
23,656✔
2953

23,656✔
2954
                // limit size of nodesToRead array to maxNodesPerRead
23,656✔
2955
                if (this.engine.serverCapabilities.operationLimits.maxNodesPerRead > 0) {
23,663✔
2956
                    if (request.nodesToRead.length > this.engine.serverCapabilities.operationLimits.maxNodesPerRead) {
4,361✔
2957
                        return sendError(StatusCodes.BadTooManyOperations);
1✔
2958
                    }
1✔
2959
                }
4,361✔
2960

23,655✔
2961
                // proceed with registered nodes alias resolution
23,655✔
2962
                for (const nodeToRead of request.nodesToRead) {
23,663✔
2963
                    nodeToRead.nodeId = session.resolveRegisteredNode(nodeToRead.nodeId);
247,117✔
2964
                }
247,117✔
2965

23,655✔
2966
                // ask for a refresh of asynchronous variables
23,655✔
2967
                this.engine.refreshValues(request.nodesToRead, request.maxAge, (_err?: Error | null) => {
23,655✔
2968
                    this.engine.read(context, request).then((results) => {
23,655✔
2969
                        assert(results[0].schema.name === "DataValue");
23,655✔
2970
                        assert(results.length === request.nodesToRead?.length);
23,655✔
2971

23,655✔
2972
                        const response = new ReadResponse({
23,655✔
2973
                            diagnosticInfos: undefined,
23,655✔
2974
                            results: undefined
23,655✔
2975
                        });
23,655✔
2976
                        // set it here for performance
23,655✔
2977
                        response.results = results;
23,655✔
2978
                        assert(response.diagnosticInfos?.length === 0);
23,655✔
2979
                        sendResponse(response);
23,655✔
2980
                    });
23,655✔
2981
                });
23,655✔
2982
            }
23,655✔
2983
        );
23,676✔
2984
    }
23,676✔
2985

35✔
2986
    // read services
35✔
2987
    protected _on_HistoryReadRequest(message: Message, channel: ServerSecureChannelLayer): void {
35✔
2988
        const request = message.request as HistoryReadRequest;
25✔
2989

25✔
2990
        assert(request instanceof HistoryReadRequest);
25✔
2991

25✔
2992
        this._apply_on_SessionObject(
25✔
2993
            HistoryReadResponse,
25✔
2994
            message,
25✔
2995
            channel,
25✔
2996
            (session: ServerSession, sendResponse: (response: Response) => void, sendError: (statsCode: StatusCode) => void) => {
25✔
2997
                const timestampsToReturn = request.timestampsToReturn;
25✔
2998

25✔
2999
                if (timestampsToReturn === TimestampsToReturn.Invalid) {
25✔
3000
                    return sendError(StatusCodes.BadTimestampsToReturnInvalid);
×
3001
                }
×
3002

25✔
3003
                request.nodesToRead = request.nodesToRead || [];
25✔
3004

25✔
3005
                if (!request.nodesToRead || request.nodesToRead.length <= 0) {
25✔
3006
                    return sendError(StatusCodes.BadNothingToDo);
×
3007
                }
×
3008

25✔
3009
                assert(request.nodesToRead[0].schema.name === "HistoryReadValueId");
25✔
3010

25✔
3011
                // limit size of nodesToRead array to maxNodesPerRead
25✔
3012
                if (this.engine.serverCapabilities.operationLimits.maxNodesPerRead > 0) {
25✔
3013
                    if (request.nodesToRead.length > this.engine.serverCapabilities.operationLimits.maxNodesPerRead) {
×
3014
                        return sendError(StatusCodes.BadTooManyOperations);
×
3015
                    }
×
3016
                }
×
3017
                // todo : handle
25✔
3018
                if (this.engine.serverCapabilities.operationLimits.maxNodesPerHistoryReadData > 0) {
25✔
3019
                    if (request.nodesToRead.length > this.engine.serverCapabilities.operationLimits.maxNodesPerHistoryReadData) {
×
3020
                        return sendError(StatusCodes.BadTooManyOperations);
×
3021
                    }
×
3022
                }
×
3023
                if (this.engine.serverCapabilities.operationLimits.maxNodesPerHistoryReadEvents > 0) {
25✔
3024
                    if (request.nodesToRead.length > this.engine.serverCapabilities.operationLimits.maxNodesPerHistoryReadEvents) {
×
3025
                        return sendError(StatusCodes.BadTooManyOperations);
×
3026
                    }
×
3027
                }
×
3028

25✔
3029
                const context = session.sessionContext;
25✔
3030

25✔
3031
                // ask for a refresh of asynchronous variables
25✔
3032
                this.engine.refreshValues(request.nodesToRead, 0, (err?: Error | null) => {
25✔
3033
                    assert(!err, " error not handled here , fix me"); // TODO
25✔
3034

25✔
3035
                    this.engine
25✔
3036
                        .historyRead(context, request)
25✔
3037
                        .then((results: HistoryReadResult[]) => {
25✔
3038
                            assert(results[0].schema.name === "HistoryReadResult");
25✔
3039
                            assert(results.length === request.nodesToRead?.length);
25✔
3040

25✔
3041
                            const response = new HistoryReadResponse({
25✔
3042
                                diagnosticInfos: undefined,
25✔
3043
                                results
25✔
3044
                            });
25✔
3045

25✔
3046
                            assert(response.diagnosticInfos?.length === 0);
25✔
3047
                            sendResponse(response);
25✔
3048
                        })
25✔
3049
                        .catch((_err) => {
25✔
3050
                            return sendError(StatusCodes.BadHistoryOperationInvalid);
×
3051
                        });
25✔
3052
                });
25✔
3053
            }
25✔
3054
        );
25✔
3055
    }
25✔
3056

35✔
3057
    /*
35✔
3058
   // write services
35✔
3059
   // OPCUA Specification 1.02 Part 3 : 5.10.4 Write
35✔
3060
   // This Service is used to write values to one or more Attributes of one or more Nodes. For constructed
35✔
3061
   // Attribute values whose elements are indexed, such as an array, this Service allows Clients to write
35✔
3062
   // the entire set of indexed values as a composite, to write individual elements or to write ranges of
35✔
3063
   // elements of the composite.
35✔
3064
   // The values are written to the data source, such as a device, and the Service does not return until it writes
35✔
3065
   // the values or determines that the value cannot be written. In certain cases, the Server will successfully
35✔
3066
   // to an intermediate system or Server, and will not know if the data source was updated properly. In these cases,
35✔
3067
   // the Server should report a success code that indicates that the write was not verified.
35✔
3068
   // In the cases where the Server is able to verify that it has successfully written to the data source,
35✔
3069
   // it reports an unconditional success.
35✔
3070
   */
35✔
3071
    protected _on_WriteRequest(message: Message, channel: ServerSecureChannelLayer): void {
35✔
3072
        const request = message.request as WriteRequest;
159✔
3073
        assert(request instanceof WriteRequest);
159✔
3074
        assert(!request.nodesToWrite || Array.isArray(request.nodesToWrite));
159✔
3075

159✔
3076
        this._apply_on_SessionObject(
159✔
3077
            WriteResponse,
159✔
3078
            message,
159✔
3079
            channel,
159✔
3080
            (session: ServerSession, sendResponse: (response: Response) => void, sendError: (statusCode: StatusCode) => void) => {
159✔
3081
                if (!request.nodesToWrite || request.nodesToWrite.length === 0) {
159✔
3082
                    return sendError(StatusCodes.BadNothingToDo);
2✔
3083
                }
2✔
3084

157✔
3085
                if (this.engine.serverCapabilities.operationLimits.maxNodesPerWrite > 0) {
159✔
3086
                    if (request.nodesToWrite.length > this.engine.serverCapabilities.operationLimits.maxNodesPerWrite) {
1✔
3087
                        return sendError(StatusCodes.BadTooManyOperations);
1✔
3088
                    }
1✔
3089
                }
1✔
3090

156✔
3091
                // proceed with registered nodes alias resolution
156✔
3092
                for (const nodeToWrite of request.nodesToWrite) {
159✔
3093
                    nodeToWrite.nodeId = session.resolveRegisteredNode(nodeToWrite.nodeId);
213✔
3094
                }
213✔
3095

156✔
3096
                const context = session.sessionContext;
156✔
3097

156✔
3098
                assert(request.nodesToWrite[0].schema.name === "WriteValue");
156✔
3099

156✔
3100
                this.engine
156✔
3101
                    .write(context, request.nodesToWrite)
156✔
3102
                    .then((results: StatusCode[]) => {
156✔
3103
                        assert(results?.length === request.nodesToWrite?.length);
156✔
3104
                        const response = new WriteResponse({
156✔
3105
                            diagnosticInfos: undefined,
156✔
3106
                            results
156✔
3107
                        });
156✔
3108
                        sendResponse(response);
156✔
3109
                    })
156✔
3110
                    .catch((err) => {
156✔
3111
                        errorLog(err);
×
3112
                        sendError(StatusCodes.BadInternalError);
×
3113
                    });
156✔
3114
            }
156✔
3115
        );
159✔
3116
    }
159✔
3117

35✔
3118
    // subscription services
35✔
3119
    protected _on_CreateSubscriptionRequest(message: Message, channel: ServerSecureChannelLayer): void {
35✔
3120
        const engine = this.engine;
431✔
3121
        const addressSpace = engine.addressSpace;
431✔
3122
        if (!addressSpace) {
431✔
3123
            g_sendError(channel, message, CreateSubscriptionResponse, StatusCodes.BadSessionClosed);
×
3124
            return;
×
3125
        }
×
3126
        const request = message.request as CreateSubscriptionRequest;
431✔
3127
        assert(request instanceof CreateSubscriptionRequest);
431✔
3128

431✔
3129
        this._apply_on_SessionObject(
431✔
3130
            CreateSubscriptionResponse,
431✔
3131
            message,
431✔
3132
            channel,
431✔
3133
            (session: ServerSession, sendResponse: (response: Response) => void, sendError: (statusCode: StatusCode) => void) => {
431✔
3134
                const context = session.sessionContext;
431✔
3135

431✔
3136
                if (session.currentSubscriptionCount >= this.engine.serverCapabilities.maxSubscriptionsPerSession) {
431✔
3137
                    return sendError(StatusCodes.BadTooManySubscriptions);
2✔
3138
                }
2✔
3139

429✔
3140
                if (this.currentSubscriptionCount >= this.engine.serverCapabilities.maxSubscriptions) {
431✔
3141
                    return sendError(StatusCodes.BadTooManySubscriptions);
4✔
3142
                }
4✔
3143

425✔
3144
                const subscription = session.createSubscription(request);
425✔
3145

425✔
3146
                subscription.on("monitoredItem", (monitoredItem: MonitoredItem) => {
425✔
3147
                    prepareMonitoredItem(context, addressSpace, monitoredItem);
14,682✔
3148
                });
425✔
3149

425✔
3150
                const response = new CreateSubscriptionResponse({
425✔
3151
                    revisedLifetimeCount: subscription.lifeTimeCount,
425✔
3152
                    revisedMaxKeepAliveCount: subscription.maxKeepAliveCount,
425✔
3153
                    revisedPublishingInterval: subscription.publishingInterval,
425✔
3154
                    subscriptionId: subscription.id
425✔
3155
                });
425✔
3156
                sendResponse(response);
425✔
3157
            }
425✔
3158
        );
431✔
3159
    }
431✔
3160

35✔
3161
    protected _on_DeleteSubscriptionsRequest(message: Message, channel: ServerSecureChannelLayer): void {
35✔
3162
        const request = message.request as DeleteSubscriptionsRequest;
296✔
3163
        assert(request instanceof DeleteSubscriptionsRequest);
296✔
3164
        this._apply_on_SubscriptionIds(
296✔
3165
            DeleteSubscriptionsResponse,
296✔
3166
            message,
296✔
3167
            channel,
296✔
3168
            async (session: ServerSession, subscriptionId: number) => {
296✔
3169
                let subscription = this.engine.findOrphanSubscription(subscriptionId);
300✔
3170
                // c8 ignore next
300✔
3171
                if (subscription) {
300✔
3172
                    warningLog("Deleting an orphan subscription", subscriptionId);
×
3173

×
3174
                    await this._beforeDeleteSubscription(subscription);
×
3175
                    return this.engine.deleteOrphanSubscription(subscription);
×
3176
                }
×
3177

300✔
3178
                subscription = session.getSubscription(subscriptionId);
300✔
3179
                if (subscription) {
300✔
3180
                    await this._beforeDeleteSubscription(subscription);
294✔
3181
                }
294✔
3182

300✔
3183
                return session.deleteSubscription(subscriptionId);
300✔
3184
            }
300✔
3185
        );
296✔
3186
    }
296✔
3187

35✔
3188
    protected _on_TransferSubscriptionsRequest(message: Message, channel: ServerSecureChannelLayer): void {
35✔
3189
        //
14✔
3190
        // sendInitialValue Boolean
14✔
3191
        //    A Boolean parameter with the following values:
14✔
3192
        //    TRUE      the first Publish response(s) after the TransferSubscriptions call shall
14✔
3193
        //              contain the current values of all Monitored Items in the Subscription where
14✔
3194
        //              the Monitoring Mode is set to Reporting.
14✔
3195
        //    FALSE     the first Publish response after the TransferSubscriptions call shall contain only the value
14✔
3196
        //              changes since the last Publish response was sent.
14✔
3197
        //    This parameter only applies to MonitoredItems used for monitoring Attribute changes.
14✔
3198
        //
14✔
3199

14✔
3200
        const engine = this.engine;
14✔
3201

14✔
3202
        const request = message.request as TransferSubscriptionsRequest;
14✔
3203
        assert(request instanceof TransferSubscriptionsRequest);
14✔
3204
        this._apply_on_SubscriptionIds(
14✔
3205
            TransferSubscriptionsResponse,
14✔
3206
            message,
14✔
3207
            channel,
14✔
3208
            async (session: ServerSession, subscriptionId: number) =>
14✔
3209
                await engine.transferSubscription(session, subscriptionId, request.sendInitialValues)
14✔
3210
        );
14✔
3211
    }
14✔
3212

35✔
3213
    protected _on_CreateMonitoredItemsRequest(message: Message, channel: ServerSecureChannelLayer): void {
35✔
3214
        const engine = this.engine;
751✔
3215
        const addressSpace = engine.addressSpace;
751✔
3216
        if (!addressSpace) {
751✔
3217
            g_sendError(channel, message, CreateMonitoredItemsResponse, StatusCodes.BadSessionClosed);
×
3218
            return;
×
3219
        }
×
3220

751✔
3221
        const request = message.request as CreateMonitoredItemsRequest;
751✔
3222
        assert(request instanceof CreateMonitoredItemsRequest);
751✔
3223

751✔
3224
        this._apply_on_Subscription(
751✔
3225
            CreateMonitoredItemsResponse,
751✔
3226
            message,
751✔
3227
            channel,
751✔
3228
            async (
751✔
3229
                _session: ServerSession,
750✔
3230
                subscription: Subscription,
750✔
3231
                sendResponse: (response: Response) => void,
750✔
3232
                sendError: (statusCode: StatusCode) => void
750✔
3233
            ): Promise<void> => {
750✔
3234
                const timestampsToReturn = request.timestampsToReturn;
750✔
3235
                if (timestampsToReturn === TimestampsToReturn.Invalid) {
750✔
3236
                    return sendError(StatusCodes.BadTimestampsToReturnInvalid);
4✔
3237
                }
4✔
3238

746✔
3239
                if (!request.itemsToCreate || request.itemsToCreate.length === 0) {
750✔
3240
                    return sendError(StatusCodes.BadNothingToDo);
1✔
3241
                }
1✔
3242
                if (this.engine.serverCapabilities.operationLimits.maxMonitoredItemsPerCall > 0) {
750✔
3243
                    if (request.itemsToCreate.length > this.engine.serverCapabilities.operationLimits.maxMonitoredItemsPerCall) {
5✔
3244
                        return sendError(StatusCodes.BadTooManyOperations);
×
3245
                    }
×
3246
                }
5✔
3247

745✔
3248
                const options = this.options as OPCUAServerOptions;
745✔
3249
                let results: MonitoredItemCreateResult[] = [];
745✔
3250
                if (options.onCreateMonitoredItem) {
750✔
3251
                    const resultsPromise = request.itemsToCreate.map(async (monitoredItemCreateRequest) => {
6✔
3252
                        const { monitoredItem, createResult } = subscription.preCreateMonitoredItem(
6✔
3253
                            addressSpace,
6✔
3254
                            timestampsToReturn,
6✔
3255
                            monitoredItemCreateRequest
6✔
3256
                        );
6✔
3257
                        if (monitoredItem) {
6✔
3258
                            if (options.onCreateMonitoredItem) {
6✔
3259
                                await options.onCreateMonitoredItem(subscription, monitoredItem);
6✔
3260
                            }
6✔
3261
                            subscription.postCreateMonitoredItem(monitoredItem, monitoredItemCreateRequest, createResult);
6✔
3262
                        }
6✔
3263
                        return createResult;
6✔
3264
                    });
6✔
3265
                    results = await Promise.all(resultsPromise);
6✔
3266
                } else {
750✔
3267
                    results = request.itemsToCreate.map((monitoredItemCreateRequest) => {
739✔
3268
                        const { monitoredItem, createResult } = subscription.preCreateMonitoredItem(
14,684✔
3269
                            addressSpace,
14,684✔
3270
                            timestampsToReturn,
14,684✔
3271
                            monitoredItemCreateRequest
14,684✔
3272
                        );
14,684✔
3273
                        if (monitoredItem) {
14,684✔
3274
                            subscription.postCreateMonitoredItem(monitoredItem, monitoredItemCreateRequest, createResult);
14,676✔
3275
                        }
14,676✔
3276
                        return createResult;
14,684✔
3277
                    });
739✔
3278
                }
739✔
3279
                const response = new CreateMonitoredItemsResponse({
745✔
3280
                    responseHeader: { serviceResult: StatusCodes.Good },
745✔
3281
                    results
745✔
3282
                    // ,diagnosticInfos: []
745✔
3283
                });
745✔
3284
                sendResponse(response);
745✔
3285
            }
745✔
3286
        );
751✔
3287
    }
751✔
3288

35✔
3289
    protected _on_ModifySubscriptionRequest(message: Message, channel: ServerSecureChannelLayer): void {
35✔
3290
        const request = message.request as ModifySubscriptionRequest;
8✔
3291
        assert(request instanceof ModifySubscriptionRequest);
8✔
3292

8✔
3293
        this._apply_on_Subscription(
8✔
3294
            ModifySubscriptionResponse,
8✔
3295
            message,
8✔
3296
            channel,
8✔
3297
            async (
8✔
3298
                _session: ServerSession,
7✔
3299
                subscription: Subscription,
7✔
3300
                sendResponse: (response: ModifySubscriptionResponse) => void,
7✔
3301
                _sendError: (statusCode: StatusCode) => void
7✔
3302
            ) => {
7✔
3303
                subscription.modify(request);
7✔
3304

7✔
3305
                const response = new ModifySubscriptionResponse({
7✔
3306
                    revisedLifetimeCount: subscription.lifeTimeCount,
7✔
3307
                    revisedMaxKeepAliveCount: subscription.maxKeepAliveCount,
7✔
3308
                    revisedPublishingInterval: subscription.publishingInterval
7✔
3309
                });
7✔
3310

7✔
3311
                sendResponse(response);
7✔
3312
            }
7✔
3313
        );
8✔
3314
    }
8✔
3315

35✔
3316
    protected _on_ModifyMonitoredItemsRequest(message: Message, channel: ServerSecureChannelLayer): void {
35✔
3317
        const request = message.request as ModifyMonitoredItemsRequest;
12✔
3318

12✔
3319
        assert(request instanceof ModifyMonitoredItemsRequest);
12✔
3320
        this._apply_on_Subscription(
12✔
3321
            ModifyMonitoredItemsResponse,
12✔
3322
            message,
12✔
3323
            channel,
12✔
3324
            async (
12✔
3325
                _session: ServerSession,
11✔
3326
                subscription: Subscription,
11✔
3327
                sendResponse: (response: ModifyMonitoredItemsResponse) => void,
11✔
3328
                sendError: (statusCode: StatusCode) => void
11✔
3329
            ) => {
11✔
3330
                const timestampsToReturn = request.timestampsToReturn;
11✔
3331
                if (timestampsToReturn === TimestampsToReturn.Invalid) {
11✔
3332
                    return sendError(StatusCodes.BadTimestampsToReturnInvalid);
1✔
3333
                }
1✔
3334

10✔
3335
                if (!request.itemsToModify || request.itemsToModify.length === 0) {
11✔
3336
                    return sendError(StatusCodes.BadNothingToDo);
1✔
3337
                }
1✔
3338

9✔
3339
                /* c8 ignore next */
2✔
3340
                if (this.engine.serverCapabilities.operationLimits.maxMonitoredItemsPerCall > 0) {
2✔
3341
                    if (request.itemsToModify.length > this.engine.serverCapabilities.operationLimits.maxMonitoredItemsPerCall) {
×
3342
                        return sendError(StatusCodes.BadTooManyOperations);
×
3343
                    }
×
3344
                }
×
3345

9✔
3346
                const itemsToModify = request.itemsToModify; // MonitoredItemModifyRequest
9✔
3347

9✔
3348
                function modifyMonitoredItem(item: MonitoredItemModifyRequest) {
9✔
3349
                    const monitoredItemId = item.monitoredItemId;
6,664✔
3350
                    const monitoredItem = subscription.getMonitoredItem(monitoredItemId);
6,664✔
3351
                    if (!monitoredItem) {
6,664✔
3352
                        return new MonitoredItemModifyResult({
1✔
3353
                            statusCode: StatusCodes.BadMonitoredItemIdInvalid
1✔
3354
                        });
1✔
3355
                    }
1✔
3356

6,663✔
3357
                    // adjust samplingInterval if === -1
6,663✔
3358
                    if (item.requestedParameters.samplingInterval === -1) {
6,664✔
3359
                        item.requestedParameters.samplingInterval = subscription.publishingInterval;
2✔
3360
                    }
2✔
3361
                    return monitoredItem.modify(timestampsToReturn, item.requestedParameters);
6,663✔
3362
                }
6,663✔
3363

9✔
3364
                const results = itemsToModify.map(modifyMonitoredItem);
9✔
3365

9✔
3366
                const response = new ModifyMonitoredItemsResponse({
9✔
3367
                    results
9✔
3368
                });
9✔
3369
                sendResponse(response);
9✔
3370
            }
9✔
3371
        );
12✔
3372
    }
12✔
3373

35✔
3374
    protected _on_PublishRequest(message: Message, channel: ServerSecureChannelLayer): void {
35✔
3375
        const request = message.request as PublishRequest;
3,254✔
3376
        assert(request instanceof PublishRequest);
3,254✔
3377

3,254✔
3378
        this._apply_on_SessionObject(
3,254✔
3379
            PublishResponse,
3,254✔
3380
            message,
3,254✔
3381
            channel,
3,254✔
3382
            (session: ServerSession, sendResponse: (response: Response) => void, _sendError: (statusCode: StatusCode) => void) => {
3,254✔
3383
                assert(session);
3,254✔
3384
                assert(session.publishEngine); // server.publishEngine doesn't exists, OPCUAServer has probably shut down already
3,254✔
3385
                session.publishEngine._on_PublishRequest(request, (_request1, response) => {
3,254✔
3386
                    sendResponse(response);
3,254✔
3387
                });
3,254✔
3388
            }
3,254✔
3389
        );
3,254✔
3390
    }
3,254✔
3391

35✔
3392
    protected _on_SetPublishingModeRequest(message: Message, channel: ServerSecureChannelLayer): void {
35✔
3393
        const request = message.request as SetPublishingModeRequest;
4✔
3394
        assert(request instanceof SetPublishingModeRequest);
4✔
3395
        const publishingEnabled = request.publishingEnabled;
4✔
3396
        this._apply_on_Subscriptions(
4✔
3397
            SetPublishingModeResponse,
4✔
3398
            message,
4✔
3399
            channel,
4✔
3400
            async (_session: ServerSession, subscription: Subscription) => {
4✔
3401
                return subscription.setPublishingMode(publishingEnabled);
2✔
3402
            }
2✔
3403
        );
4✔
3404
    }
4✔
3405

35✔
3406
    protected _on_DeleteMonitoredItemsRequest(message: Message, channel: ServerSecureChannelLayer): void {
35✔
3407
        const request = message.request as DeleteMonitoredItemsRequest;
38✔
3408
        assert(request instanceof DeleteMonitoredItemsRequest);
38✔
3409

38✔
3410
        this._apply_on_Subscription(
38✔
3411
            DeleteMonitoredItemsResponse,
38✔
3412
            message,
38✔
3413
            channel,
38✔
3414
            async (
38✔
3415
                _session: ServerSession,
37✔
3416
                subscription: Subscription,
37✔
3417
                sendResponse: (response: Response) => void,
37✔
3418
                sendError: (statusCode: StatusCode) => void
37✔
3419
            ) => {
37✔
3420
                /* c8 ignore next */
2✔
3421
                if (!request.monitoredItemIds || request.monitoredItemIds.length === 0) {
2✔
3422
                    return sendError(StatusCodes.BadNothingToDo);
1✔
3423
                }
1✔
3424

36✔
3425
                /* c8 ignore next */
2✔
3426
                if (this.engine.serverCapabilities.operationLimits.maxMonitoredItemsPerCall > 0) {
2✔
3427
                    if (request.monitoredItemIds.length > this.engine.serverCapabilities.operationLimits.maxMonitoredItemsPerCall) {
×
3428
                        return sendError(StatusCodes.BadTooManyOperations);
×
3429
                    }
×
3430
                }
×
3431

36✔
3432
                const resultsPromises = request.monitoredItemIds.map(async (monitoredItemId: number) => {
36✔
3433
                    if (this.options.onDeleteMonitoredItem) {
6,712✔
3434
                        const monitoredItem = subscription.getMonitoredItem(monitoredItemId);
1✔
3435
                        if (monitoredItem) {
1✔
3436
                            await this.options.onDeleteMonitoredItem(subscription, monitoredItem);
1✔
3437
                        }
1✔
3438
                    }
1✔
3439
                    return subscription.removeMonitoredItem(monitoredItemId);
6,712✔
3440
                });
36✔
3441

36✔
3442
                try {
36✔
3443
                    const results = await Promise.all(resultsPromises);
36✔
3444

36✔
3445
                    const response = new DeleteMonitoredItemsResponse({
36✔
3446
                        diagnosticInfos: undefined,
36✔
3447
                        results
36✔
3448
                    });
36✔
3449

36✔
3450
                    sendResponse(response);
36✔
3451
                } catch (err) {
37✔
3452
                    warningLog(err);
×
3453
                    return sendError(StatusCodes.BadInternalError);
×
3454
                }
×
3455
            }
37✔
3456
        );
38✔
3457
    }
38✔
3458
    protected _on_SetTriggeringRequest(message: Message, channel: ServerSecureChannelLayer): void {
35✔
3459
        const request = message.request as SetTriggeringRequest;
5✔
3460
        assert(request instanceof SetTriggeringRequest);
5✔
3461

5✔
3462
        this._apply_on_Subscription(
5✔
3463
            SetTriggeringResponse,
5✔
3464
            message,
5✔
3465
            channel,
5✔
3466
            async (
5✔
3467
                _session: ServerSession,
5✔
3468
                subscription: Subscription,
5✔
3469
                sendResponse: (response: Response) => void,
5✔
3470
                sendError: (statusCode: StatusCode) => void
5✔
3471
            ) => {
5✔
3472
                /* */
5✔
3473
                const { triggeringItemId, linksToAdd, linksToRemove } = request;
5✔
3474

5✔
3475
                /**
5✔
3476
                 * The MaxMonitoredItemsPerCall Property indicates
5✔
3477
                 * [...]
5✔
3478
                 *  • the maximum size of the sum of the linksToAdd and linksToRemove arrays when a
5✔
3479
                 *    Client calls the SetTriggering Service.
5✔
3480
                 *
5✔
3481
                 */
5✔
3482
                const maxElements = (linksToAdd ? linksToAdd.length : 0) + (linksToRemove ? linksToRemove.length : 0);
5✔
3483
                if (this.engine.serverCapabilities.operationLimits.maxMonitoredItemsPerCall > 0) {
5✔
3484
                    if (maxElements > this.engine.serverCapabilities.operationLimits.maxMonitoredItemsPerCall) {
×
3485
                        return sendError(StatusCodes.BadTooManyOperations);
×
3486
                    }
×
3487
                }
×
3488

5✔
3489
                const { addResults, removeResults, statusCode } = subscription.setTriggering(
5✔
3490
                    triggeringItemId,
5✔
3491
                    linksToAdd,
5✔
3492
                    linksToRemove
5✔
3493
                );
5✔
3494
                if (statusCode.isNotGood()) {
5✔
3495
                    const response = new ServiceFault({
1✔
3496
                        responseHeader: { serviceResult: statusCode }
1✔
3497
                    });
1✔
3498
                    sendResponse(response);
1✔
3499
                } else {
5✔
3500
                    const response = new SetTriggeringResponse({
4✔
3501
                        responseHeader: { serviceResult: statusCode },
4✔
3502

4✔
3503
                        addResults,
4✔
3504
                        removeResults,
4✔
3505
                        addDiagnosticInfos: null,
4✔
3506
                        removeDiagnosticInfos: null
4✔
3507
                    });
4✔
3508
                    sendResponse(response);
4✔
3509
                }
4✔
3510
            }
5✔
3511
        );
5✔
3512
    }
5✔
3513

35✔
3514
    protected async _beforeDeleteSubscription(subscription: Subscription): Promise<void> {
35✔
3515
        if (!this.options.onDeleteMonitoredItem) {
294✔
3516
            return;
291✔
3517
        }
291✔
3518
        const t = this.options.onDeleteMonitoredItem.bind(null, subscription);
3✔
3519
        const functor = async (monitoredItem: MonitoredItem) => {
3✔
3520
            await t(monitoredItem);
4✔
3521
        };
3✔
3522
        await subscription.applyOnMonitoredItem(functor);
3✔
3523
    }
3✔
3524
    protected _on_RepublishRequest(message: Message, channel: ServerSecureChannelLayer): void {
35✔
3525
        const request = message.request as RepublishRequest;
23✔
3526
        assert(request instanceof RepublishRequest);
23✔
3527

23✔
3528
        this._apply_on_Subscription(
23✔
3529
            RepublishResponse,
23✔
3530
            message,
23✔
3531
            channel,
23✔
3532
            async (
23✔
3533
                _session: ServerSession,
22✔
3534
                subscription: Subscription,
22✔
3535
                sendResponse: (response: Response) => void,
22✔
3536
                sendError: (statusCode: StatusCode) => void
22✔
3537
            ) => {
22✔
3538
                // update diagnostic counter
22✔
3539
                subscription.subscriptionDiagnostics.republishRequestCount += 1;
22✔
3540
                subscription.subscriptionDiagnostics.republishMessageRequestCount += 1;
22✔
3541

22✔
3542
                const retransmitSequenceNumber = request.retransmitSequenceNumber;
22✔
3543
                const notificationMessage = subscription.getMessageForSequenceNumber(retransmitSequenceNumber);
22✔
3544

22✔
3545
                if (!notificationMessage) {
22✔
3546
                    return sendError(StatusCodes.BadMessageNotAvailable);
10✔
3547
                }
10✔
3548
                const response = new RepublishResponse({
12✔
3549
                    notificationMessage,
12✔
3550
                    responseHeader: {
12✔
3551
                        serviceResult: StatusCodes.Good
12✔
3552
                    }
12✔
3553
                });
12✔
3554
                // update diagnostic counter
12✔
3555
                subscription.subscriptionDiagnostics.republishMessageCount += 1;
12✔
3556

12✔
3557
                sendResponse(response);
12✔
3558
            }
12✔
3559
        );
23✔
3560
    }
23✔
3561

35✔
3562
    // Bad_NothingToDo
35✔
3563
    // Bad_TooManyOperations
35✔
3564
    // Bad_SubscriptionIdInvalid
35✔
3565
    // Bad_MonitoringModeInvalid
35✔
3566
    protected _on_SetMonitoringModeRequest(message: Message, channel: ServerSecureChannelLayer): void {
35✔
3567
        const request = message.request as SetMonitoringModeRequest;
20✔
3568
        assert(request instanceof SetMonitoringModeRequest);
20✔
3569

20✔
3570
        this._apply_on_Subscription(
20✔
3571
            SetMonitoringModeResponse,
20✔
3572
            message,
20✔
3573
            channel,
20✔
3574
            async (
20✔
3575
                _session: ServerSession,
18✔
3576
                subscription: Subscription,
18✔
3577
                sendResponse: (response: Response) => void,
18✔
3578
                sendError: (statusCode: StatusCode) => void
18✔
3579
            ) => {
18✔
3580
                /* c8 ignore next */
2✔
3581
                if (!request.monitoredItemIds || request.monitoredItemIds.length === 0) {
2✔
3582
                    return sendError(StatusCodes.BadNothingToDo);
2✔
3583
                }
2✔
3584

16✔
3585
                /* c8 ignore next */
2✔
3586
                if (this.engine.serverCapabilities.operationLimits.maxMonitoredItemsPerCall > 0) {
2✔
3587
                    if (request.monitoredItemIds.length > this.engine.serverCapabilities.operationLimits.maxMonitoredItemsPerCall) {
×
3588
                        return sendError(StatusCodes.BadTooManyOperations);
×
3589
                    }
×
3590
                }
×
3591
                const monitoringMode = request.monitoringMode;
16✔
3592

16✔
3593
                if (!isMonitoringModeValid(monitoringMode)) {
18✔
3594
                    return sendError(StatusCodes.BadMonitoringModeInvalid);
1✔
3595
                }
1✔
3596

15✔
3597
                const results = request.monitoredItemIds.map((monitoredItemId) => {
15✔
3598
                    const monitoredItem = subscription.getMonitoredItem(monitoredItemId);
22✔
3599
                    if (!monitoredItem) {
22✔
3600
                        return StatusCodes.BadMonitoredItemIdInvalid;
1✔
3601
                    }
1✔
3602
                    const statusCode = monitoredItem.setMonitoringMode(monitoringMode);
21✔
3603
                    return statusCode;
21✔
3604
                });
15✔
3605

15✔
3606
                const response = new SetMonitoringModeResponse({
15✔
3607
                    results
15✔
3608
                });
15✔
3609
                sendResponse(response);
15✔
3610
            }
15✔
3611
        );
20✔
3612
    }
20✔
3613

35✔
3614
    // _on_TranslateBrowsePathsToNodeIds service
35✔
3615
    protected _on_TranslateBrowsePathsToNodeIdsRequest(message: Message, channel: ServerSecureChannelLayer): void {
35✔
3616
        const request = message.request as TranslateBrowsePathsToNodeIdsRequest;
62✔
3617
        assert(request instanceof TranslateBrowsePathsToNodeIdsRequest);
62✔
3618

62✔
3619
        this._apply_on_SessionObject(
62✔
3620
            TranslateBrowsePathsToNodeIdsResponse,
62✔
3621
            message,
62✔
3622
            channel,
62✔
3623
            async (
62✔
3624
                _session: ServerSession,
62✔
3625
                sendResponse: (response: Response) => void,
62✔
3626
                sendError: (statusCode: StatusCode) => void
62✔
3627
            ) => {
62✔
3628
                if (!request.browsePaths || request.browsePaths.length === 0) {
62✔
3629
                    return sendError(StatusCodes.BadNothingToDo);
×
3630
                }
×
3631
                if (this.engine.serverCapabilities.operationLimits.maxNodesPerTranslateBrowsePathsToNodeIds > 0) {
62✔
3632
                    if (
1✔
3633
                        request.browsePaths.length >
1✔
3634
                        this.engine.serverCapabilities.operationLimits.maxNodesPerTranslateBrowsePathsToNodeIds
1✔
3635
                    ) {
1✔
3636
                        return sendError(StatusCodes.BadTooManyOperations);
1✔
3637
                    }
1✔
3638
                }
1✔
3639

61✔
3640
                this.engine
61✔
3641
                    .translateBrowsePaths(request.browsePaths)
61✔
3642
                    .then((browsePathsResults) => {
61✔
3643
                        const response = new TranslateBrowsePathsToNodeIdsResponse({
61✔
3644
                            diagnosticInfos: null,
61✔
3645
                            results: browsePathsResults
61✔
3646
                        });
61✔
3647
                        sendResponse(response);
61✔
3648
                    })
61✔
3649
                    .catch((_err) => {
61✔
3650
                        sendError(StatusCodes.BadInternalError);
×
3651
                    });
61✔
3652
            }
61✔
3653
        );
62✔
3654
    }
62✔
3655

35✔
3656
    // Call Service Result Codes
35✔
3657
    // Symbolic Id Description
35✔
3658
    // Bad_NothingToDo       See Table 165 for the description of this result code.
35✔
3659
    // Bad_TooManyOperations See Table 165 for the description of this result code.
35✔
3660
    //
35✔
3661
    protected _on_CallRequest(message: Message, channel: ServerSecureChannelLayer): void {
35✔
3662
        const request = message.request as CallRequest;
88✔
3663
        assert(request instanceof CallRequest);
88✔
3664

88✔
3665
        this._apply_on_SessionObject(
88✔
3666
            CallResponse,
88✔
3667
            message,
88✔
3668
            channel,
88✔
3669
            (session: ServerSession, sendResponse: (response: Response) => void, sendError: (statusCode: StatusCode) => void) => {
88✔
3670
                if (!request.methodsToCall || request.methodsToCall.length === 0) {
88✔
3671
                    return sendError(StatusCodes.BadNothingToDo);
1✔
3672
                }
1✔
3673

87✔
3674
                // the MaxNodesPerMethodCall Property indicates the maximum size of the methodsToCall array when
87✔
3675
                // a Client calls the Call Service.
87✔
3676
                let maxNodesPerMethodCall = this.engine.serverCapabilities.operationLimits.maxNodesPerMethodCall;
87✔
3677
                maxNodesPerMethodCall = maxNodesPerMethodCall <= 0 ? 1000 : maxNodesPerMethodCall;
88✔
3678
                if (request.methodsToCall.length > maxNodesPerMethodCall) {
88✔
3679
                    return sendError(StatusCodes.BadTooManyOperations);
1✔
3680
                }
1✔
3681

86✔
3682
                const context = session.sessionContext;
86✔
3683
                this.engine
86✔
3684
                    .call(context, request.methodsToCall)
86✔
3685
                    .then((results) => {
86✔
3686
                        const response = new CallResponse({ results });
86✔
3687
                        filterDiagnosticInfo(request.requestHeader.returnDiagnostics, response);
86✔
3688
                        sendResponse(response);
86✔
3689
                    })
86✔
3690
                    .catch((_err) => {
86✔
3691
                        sendError(StatusCodes.BadInternalError);
×
3692
                    });
86✔
3693
            }
86✔
3694
        );
88✔
3695
    }
88✔
3696

35✔
3697
    protected _on_RegisterNodesRequest(message: Message, channel: ServerSecureChannelLayer): void {
35✔
3698
        const request = message.request as RegisterNodesRequest;
3✔
3699
        assert(request instanceof RegisterNodesRequest);
3✔
3700

3✔
3701
        this._apply_on_SessionObject(
3✔
3702
            RegisterNodesResponse,
3✔
3703
            message,
3✔
3704
            channel,
3✔
3705
            (session: ServerSession, sendResponse: (response: Response) => void, sendError: (statusCode: StatusCode) => void) => {
3✔
3706
                if (!request.nodesToRegister || request.nodesToRegister.length === 0) {
3✔
3707
                    return sendError(StatusCodes.BadNothingToDo);
1✔
3708
                }
1✔
3709
                if (this.engine.serverCapabilities.operationLimits.maxNodesPerRegisterNodes > 0) {
3✔
3710
                    if (request.nodesToRegister.length > this.engine.serverCapabilities.operationLimits.maxNodesPerRegisterNodes) {
×
3711
                        return sendError(StatusCodes.BadTooManyOperations);
×
3712
                    }
×
3713
                }
×
3714
                // A list of NodeIds which the Client shall use for subsequent access operations. The
2✔
3715
                // size and order of this list matches the size and order of the nodesToRegister
2✔
3716
                // request parameter.
2✔
3717
                // The Server may return the NodeId from the request or a new (an alias) NodeId. It
2✔
3718
                // is recommended that the Server return a numeric NodeIds for aliasing.
2✔
3719
                // In case no optimization is supported for a Node, the Server shall return the
2✔
3720
                // NodeId from the request.
2✔
3721
                const registeredNodeIds = request.nodesToRegister.map((nodeId) => session.registerNode(nodeId));
2✔
3722

2✔
3723
                const response = new RegisterNodesResponse({
2✔
3724
                    registeredNodeIds
2✔
3725
                });
2✔
3726
                sendResponse(response);
2✔
3727
            }
2✔
3728
        );
3✔
3729
    }
3✔
3730

35✔
3731
    protected _on_UnregisterNodesRequest(message: Message, channel: ServerSecureChannelLayer): void {
35✔
3732
        const request = message.request as UnregisterNodesRequest;
3✔
3733
        assert(request instanceof UnregisterNodesRequest);
3✔
3734

3✔
3735
        this._apply_on_SessionObject(
3✔
3736
            UnregisterNodesResponse,
3✔
3737
            message,
3✔
3738
            channel,
3✔
3739
            (session: ServerSession, sendResponse: (response: Response) => void, sendError: (statusCode: StatusCode) => void) => {
3✔
3740
                request.nodesToUnregister = request.nodesToUnregister || [];
3✔
3741

3✔
3742
                if (!request.nodesToUnregister || request.nodesToUnregister.length === 0) {
3✔
3743
                    return sendError(StatusCodes.BadNothingToDo);
1✔
3744
                }
1✔
3745

2✔
3746
                if (this.engine.serverCapabilities.operationLimits.maxNodesPerRegisterNodes > 0) {
3✔
3747
                    if (
×
3748
                        request.nodesToUnregister.length > this.engine.serverCapabilities.operationLimits.maxNodesPerRegisterNodes
×
3749
                    ) {
×
3750
                        return sendError(StatusCodes.BadTooManyOperations);
×
3751
                    }
×
3752
                }
×
3753

2✔
3754
                request.nodesToUnregister.map((nodeId: NodeId) => session.unRegisterNode(nodeId));
2✔
3755

2✔
3756
                const response = new UnregisterNodesResponse({});
2✔
3757
                sendResponse(response);
2✔
3758
            }
2✔
3759
        );
3✔
3760
    }
3✔
3761

35✔
3762
    /* c8 ignore next */
2✔
3763
    protected _on_Cancel(message: Message, channel: ServerSecureChannelLayer): void {
2✔
3764
        g_sendError(channel, message, CancelResponse, StatusCodes.BadServiceUnsupported);
×
3765
    }
×
3766

35✔
3767
    // NodeManagement Service Set Overview
35✔
3768
    // This Service Set defines Services to add and delete AddressSpace Nodes and References between them. All added
35✔
3769
    // Nodes continue to exist in the AddressSpace even if the Client that created them disconnects from the Server.
35✔
3770
    //
35✔
3771
    /* c8 ignore next */
2✔
3772
    protected _on_AddNodes(message: Message, channel: ServerSecureChannelLayer): void {
2✔
3773
        g_sendError(channel, message, AddNodesResponse, StatusCodes.BadServiceUnsupported);
×
3774
    }
×
3775

35✔
3776
    /* c8 ignore next */
2✔
3777
    protected _on_AddReferences(message: Message, channel: ServerSecureChannelLayer): void {
2✔
3778
        g_sendError(channel, message, AddReferencesResponse, StatusCodes.BadServiceUnsupported);
×
3779
    }
×
3780

35✔
3781
    /* c8 ignore next */
2✔
3782
    protected _on_DeleteNodes(message: Message, channel: ServerSecureChannelLayer): void {
2✔
3783
        g_sendError(channel, message, DeleteNodesResponse, StatusCodes.BadServiceUnsupported);
×
3784
    }
×
3785

35✔
3786
    /* c8 ignore next */
2✔
3787
    protected _on_DeleteReferences(message: Message, channel: ServerSecureChannelLayer): void {
2✔
3788
        g_sendError(channel, message, DeleteReferencesResponse, StatusCodes.BadServiceUnsupported);
×
3789
    }
×
3790

35✔
3791
    // Query Service
35✔
3792
    /* c8 ignore next */
2✔
3793
    protected _on_QueryFirst(message: Message, channel: ServerSecureChannelLayer): void {
2✔
3794
        g_sendError(channel, message, QueryFirstResponse, StatusCodes.BadServiceUnsupported);
×
3795
    }
×
3796

35✔
3797
    /* c8 ignore next */
2✔
3798
    protected _on_QueryNext(message: Message, channel: ServerSecureChannelLayer): void {
2✔
3799
        g_sendError(channel, message, QueryNextResponse, StatusCodes.BadServiceUnsupported);
×
3800
    }
×
3801

35✔
3802
    /* c8 ignore next */
2✔
3803
    protected _on_HistoryUpdate(message: Message, channel: ServerSecureChannelLayer): void {
2✔
3804
        g_sendError(channel, message, HistoryUpdateResponse, StatusCodes.BadServiceUnsupported);
×
3805
    }
×
3806

35✔
3807
    private createEndpoint(
35✔
3808
        port1: number,
300✔
3809
        serverOptions: {
300✔
3810
            defaultSecureTokenLifetime?: number;
300✔
3811
            timeout?: number;
300✔
3812
            host?: string;
300✔
3813
            transportSettings?: IServerTransportSettings;
300✔
3814
        }
300✔
3815
    ): OPCUAServerEndPoint {
300✔
3816
        // add the tcp/ip endpoint with no security
300✔
3817
        const endPoint = new OPCUAServerEndPoint({
300✔
3818
            port: port1,
300✔
3819
            host: serverOptions.host,
300✔
3820
            certificateManager: this.serverCertificateManager,
300✔
3821

300✔
3822
            certificateChain: this.getCertificateChain(),
300✔
3823
            privateKey: this.getPrivateKey(),
300✔
3824

300✔
3825
            defaultSecureTokenLifetime: serverOptions.defaultSecureTokenLifetime || 600000,
300✔
3826
            timeout: serverOptions.timeout || 3 * 60 * 1000,
300✔
3827

300✔
3828
            maxConnections: this.maxConnectionsPerEndpoint,
300✔
3829
            objectFactory: this.objectFactory,
300✔
3830
            serverInfo: this.serverInfo,
300✔
3831
            transportSettings: serverOptions.transportSettings
300✔
3832
        });
300✔
3833

300✔
3834
        // SecretHolder reads certificateFile/privateKeyFile from `this`
300✔
3835
        // on each access, so it follows Object.defineProperty redirects
300✔
3836
        // (e.g. from push cert management) automatically.
300✔
3837
        // Note: creates a cycle (server → endpoint → SecretHolder → server)
300✔
3838
        // which is harmless — V8 mark-and-sweep handles it, and
300✔
3839
        // endpoint.dispose() breaks it by replacing #certProvider.
300✔
3840
        endPoint.setCertificateProvider(new SecretHolder(this));
300✔
3841

300✔
3842
        return endPoint;
300✔
3843
    }
300✔
3844

35✔
3845
    private createEndpointDescriptions(
35✔
3846
        serverOption: OPCUAServerOptions,
300✔
3847
        endpointOptions: OPCUAServerEndpointOptions
300✔
3848
    ): OPCUAServerEndPoint {
300✔
3849
        /* c8 ignore next */
2✔
3850
        if (!endpointOptions) {
2✔
3851
            throw new Error("internal error");
×
3852
        }
×
3853
        const hostname = getFullyQualifiedDomainName();
300✔
3854
        endpointOptions.hostname = endpointOptions.hostname || hostname;
300!
3855
        endpointOptions.port = endpointOptions.port === undefined ? 26543 : endpointOptions.port;
300!
3856

300✔
3857
        /* c8 ignore next */
2✔
3858
        if (
2✔
3859
            !Object.hasOwn(endpointOptions, "port") ||
300✔
3860
            !Number.isFinite(endpointOptions.port) ||
300✔
3861
            typeof endpointOptions.port !== "number"
300✔
3862
        ) {
300!
3863
            throw new Error(
×
3864
                "expecting a valid port (number) when specified. alternatively you can specify port:0 and node-opcua will choose the first available port"
×
3865
            );
×
3866
        }
×
3867

300✔
3868
        const port = Number(endpointOptions.port || 0);
300✔
3869

300✔
3870
        const endPoint = this.createEndpoint(port, serverOption);
300✔
3871

300✔
3872
        endpointOptions.alternateHostname = endpointOptions.alternateHostname || [];
300✔
3873
        const alternateHostname = Array.isArray(endpointOptions.alternateHostname)
300✔
3874
            ? endpointOptions.alternateHostname
300✔
3875
            : [endpointOptions.alternateHostname];
267!
3876
        const allowAnonymous = endpointOptions.allowAnonymous === undefined ? true : !!endpointOptions.allowAnonymous;
300!
3877

300✔
3878
        endPoint.addStandardEndpointDescriptions({
300✔
3879
            allowAnonymous,
300✔
3880
            securityModes: endpointOptions.securityModes,
300✔
3881
            securityPolicies: endpointOptions.securityPolicies,
300✔
3882

300✔
3883
            hostname: endpointOptions.hostname,
300✔
3884

300✔
3885
            alternateHostname,
300✔
3886

300✔
3887
            disableDiscovery: !!endpointOptions.disableDiscovery,
300✔
3888
            // xx                hostname,
300✔
3889
            resourcePath: serverOption.resourcePath || "",
300✔
3890

300✔
3891
            advertisedEndpoints: endpointOptions.advertisedEndpoints
300✔
3892

300✔
3893
            // TODO  userTokenTypes: endpointOptions.userTokenTypes || undefined,
300✔
3894

300✔
3895
            // TODO allowUnsecurePassword: endpointOptions.allowUnsecurePassword || false
300✔
3896
        });
300✔
3897
        return endPoint;
300✔
3898
    }
300✔
3899

35✔
3900
    public async initializeCM(): Promise<void> {
35✔
3901
        await super.initializeCM();
301✔
3902
        await this.userCertificateManager.initialize();
301✔
3903
    }
301✔
3904

35✔
3905
}
35✔
3906

2✔
3907
const userIdentityTokenPasswordRemoved = (userIdentityToken?: UserIdentityToken): UserIdentityToken => {
2✔
3908
    if (!userIdentityToken) return new AnonymousIdentityToken();
1,004!
3909
    const a: UserIdentityToken = userIdentityToken.clone();
1,004✔
3910
    // For Username/Password tokens the password shall not be included.
1,004✔
3911
    if (a instanceof UserNameIdentityToken) {
1,004✔
3912
        // remove password
57✔
3913
        a.password = Buffer.from("*************", "ascii");
57✔
3914
    }
57✔
3915
    // if (a instanceof X509IdentityToken) {
1,004✔
3916
    //     a.certificateData = Buffer.alloc(0);
1,004✔
3917
    // }
1,004✔
3918
    return a;
1,004✔
3919
};
1,004✔
3920

2✔
3921
function raiseAuditActivateSessionEventType(this: OPCUAServer, session: ServerSession) {
1,000✔
3922
    if (this.isAuditing) {
1,000✔
3923
        this.raiseEvent("AuditActivateSessionEventType", {
4✔
3924
            /* part 5 -  6.4.3 AuditEventType */
4✔
3925
            actionTimeStamp: { dataType: "DateTime", value: new Date() },
4✔
3926
            status: { dataType: "Boolean", value: true },
4✔
3927

4✔
3928
            serverId: { dataType: "String", value: "" },
4✔
3929

4✔
3930
            // ClientAuditEntryId contains the human-readable AuditEntryId defined in Part 3.
4✔
3931
            clientAuditEntryId: { dataType: "String", value: "" },
4✔
3932

4✔
3933
            // The ClientUserId identifies the user of the client requesting an action.
4✔
3934
            // The ClientUserId can be obtained from the UserIdentityToken passed in the
4✔
3935
            // ActivateSession call.
4✔
3936
            clientUserId: { dataType: "String", value: "cc" },
4✔
3937

4✔
3938
            sourceName: { dataType: "String", value: "Session/ActivateSession" },
4✔
3939

4✔
3940
            /* part 5 - 6.4.7 AuditSessionEventType */
4✔
3941
            sessionId: { dataType: "NodeId", value: session.nodeId },
4✔
3942

4✔
3943
            /* part 5 - 6.4.10 AuditActivateSessionEventType */
4✔
3944
            clientSoftwareCertificates: {
4✔
3945
                arrayType: VariantArrayType.Array,
4✔
3946
                dataType: "ExtensionObject" /* SignedSoftwareCertificate */,
4✔
3947
                value: []
4✔
3948
            },
4✔
3949
            // UserIdentityToken reflects the userIdentityToken parameter of the ActivateSession
4✔
3950
            // Service call.
4✔
3951
            // For Username/Password tokens the password should NOT be included.
4✔
3952
            userIdentityToken: {
4✔
3953
                dataType: "ExtensionObject" /*  UserIdentityToken */,
4✔
3954
                value: userIdentityTokenPasswordRemoved(session.userIdentityToken)
4✔
3955
            },
4✔
3956

4✔
3957
            // SecureChannelId shall uniquely identify the SecureChannel. The application shall
4✔
3958
            // use the same identifier in all AuditEvents related to the Session Service Set
4✔
3959
            // (AuditCreateSessionEventType, AuditActivateSessionEventType and their subtypes) and
4✔
3960
            // the SecureChannel Service Set (AuditChannelEventType and its subtypes).
4✔
3961
            secureChannelId: {
4✔
3962
                dataType: "String",
4✔
3963
                value: session.channel?.channelId?.toString() ?? ""
4!
3964
            }
4✔
3965
        });
4✔
3966
    }
4✔
3967
}
1,000✔
3968

2✔
3969
export interface RaiseEventAuditEventData extends RaiseEventData {
2✔
3970
    actionTimeStamp: PseudoVariantDateTime;
2✔
3971
    status: PseudoVariantBoolean;
2✔
3972
    serverId: PseudoVariantString;
2✔
3973
    /**
2✔
3974
     * ClientAuditEntryId contains the human-readable AuditEntryId defined in Part 3.
2✔
3975
     */
2✔
3976
    clientAuditEntryId: PseudoVariantString;
2✔
3977
    /**
2✔
3978
     * The ClientUserId identifies the user of the client requesting an action. The ClientUserId can be
2✔
3979
     * obtained from the UserIdentityToken passed in the ActivateSession call.
2✔
3980
     */
2✔
3981
    clientUserId: PseudoVariantString;
2✔
3982
    sourceName: PseudoVariantString;
2✔
3983
}
2✔
3984

2✔
3985
export interface RaiseEventAuditUpdateMethodEventData extends RaiseEventAuditEventData {
2✔
3986
    methodId: PseudoVariantNodeId;
2✔
3987
    inputArguments: PseudoVariant | Variant | UAEventType | undefined;
2✔
3988
}
2✔
3989

2✔
3990
export interface RaiseEventAuditConditionCommentEventData extends RaiseEventAuditUpdateMethodEventData {
2✔
3991
    eventId: PseudoVariantByteString;
2✔
3992
    comment: PseudoVariantLocalizedText;
2✔
3993
}
2✔
3994

2✔
3995
export interface RaiseEventAuditSessionEventData extends RaiseEventAuditEventData {
2✔
3996
    /**
2✔
3997
     *  part 5 - 6.4.7 AuditSessionEventType
2✔
3998
     */
2✔
3999
    sessionId: PseudoVariantNodeId;
2✔
4000
}
2✔
4001

2✔
4002
export interface RaiseEventAuditCreateSessionEventData extends RaiseEventAuditSessionEventData {
2✔
4003
    sessionId: PseudoVariantNodeId;
2✔
4004
    /**
2✔
4005
     *  part 5 - 6.4.8 AuditCreateSessionEventType
2✔
4006
     *  SecureChannelId shall uniquely identify the SecureChannel.
2✔
4007
     *  The application shall use the same identifier in
2✔
4008
     *  all AuditEvents related to the Session Service Set (AuditCreateSessionEventType, AuditActivateSessionEventType
2✔
4009
     *  and their subtypes) and the SecureChannel Service Set (AuditChannelEventType and its subtype
2✔
4010
     */
2✔
4011
    secureChannelId: PseudoVariantString;
2✔
4012
    revisedSessionTimeout: PseudoVariantDuration;
2✔
4013
    clientCertificate: PseudoVariantByteString;
2✔
4014
    clientCertificateThumbprint: PseudoVariantString;
2✔
4015
}
2✔
4016

2✔
4017
export interface RaiseEventAuditActivateSessionEventData extends RaiseEventAuditSessionEventData {
2✔
4018
    /**
2✔
4019
     * part 5 - 6.4.10 AuditActivateSessionEventType
2✔
4020
     */
2✔
4021
    clientSoftwareCertificates: PseudoVariantExtensionObjectArray;
2✔
4022
    /**
2✔
4023
     * UserIdentityToken reflects the userIdentityToken parameter of the ActivateSession Service call.
2✔
4024
     * For Username/Password tokens the password should NOT be included.
2✔
4025
     */
2✔
4026
    userIdentityToken: PseudoVariantExtensionObject;
2✔
4027
    /**
2✔
4028
     * SecureChannelId shall uniquely identify the SecureChannel. The application shall use the same identifier
2✔
4029
     * in all AuditEvents related to the Session Service Set (AuditCreateSessionEventType,
2✔
4030
     * AuditActivateSessionEventType and their subtypes) and the SecureChannel Service Set
2✔
4031
     * (AuditChannelEventType and its subtypes).
2✔
4032
     */
2✔
4033
    secureChannelId: PseudoVariantString;
2✔
4034
}
2✔
4035

2✔
4036
// tslint:disable:no-empty-interface
2✔
4037
export interface RaiseEventTransitionEventData extends RaiseEventData {}
2✔
4038

2✔
4039
export interface RaiseEventAuditUrlMismatchEventTypeData extends RaiseEventData {
2✔
4040
    endpointUrl: PseudoVariantString;
2✔
4041
}
2✔
4042

2✔
4043
/**
2✔
4044
 * The SourceName for Events of this type shall be “Security/Certificate”.
2✔
4045
 */
2✔
4046
export interface RaiseAuditCertificateEventData extends RaiseEventData {
2✔
4047
    certificate: PseudoVariantByteString;
2✔
4048
    sourceName: PseudoVariantStringPredefined<"Security/Certificate">;
2✔
4049
}
2✔
4050

2✔
4051
/**
2✔
4052
 * This EventType inherits all Properties of the AuditCertificateEventType.
2✔
4053
 * Either the InvalidHostname or InvalidUri shall be provided.
2✔
4054
 */
2✔
4055
export interface RaiseAuditCertificateDataMismatchEventData extends RaiseAuditCertificateEventData {
2✔
4056
    /**
2✔
4057
     * InvalidHostname is the string that represents the host name passed in as part of the URL
2✔
4058
     * that is found to be invalid. If the host name was not invalid it can be null.
2✔
4059
     */
2✔
4060
    invalidHostname: PseudoVariantString;
2✔
4061
    /*
2✔
4062
     * InvalidUri is the URI that was passed in and found to not match what is contained in
2✔
4063
     * the certificate. If the URI was not invalid it can be null.
2✔
4064
     */
2✔
4065
    invalidUri: PseudoVariantString;
2✔
4066
}
2✔
4067
export interface RaiseAuditCertificateUntrustedEventData extends RaiseAuditCertificateEventData {}
2✔
4068
/**
2✔
4069
 * This EventType inherits all Properties of the AuditCertificateEventType.
2✔
4070
 *
2✔
4071
 * The SourceName for Events of this type shall be “Security/Certificate”.
2✔
4072
 *
2✔
4073
 * The Message Variable shall include a description of why the certificate was expired
2✔
4074
 * (i.e. time before start or time after end).
2✔
4075
 *
2✔
4076
 * There are no additional Properties defined for this EventType.
2✔
4077
 *
2✔
4078
 */
2✔
4079
export interface RaiseAuditCertificateExpiredEventData extends RaiseAuditCertificateEventData {}
2✔
4080
/**
2✔
4081
 * This EventType inherits all Properties of the AuditCertificateEventType.
2✔
4082
 *
2✔
4083
 * The SourceName for Events of this type shall be “Security/Certificate”.
2✔
4084
 *
2✔
4085
 * The Message shall include a description of why the certificate is invalid.
2✔
4086
 *
2✔
4087
 * There are no additional Properties defined for this EventType.
2✔
4088
 */
2✔
4089
export interface RaiseAuditCertificateInvalidEventData extends RaiseAuditCertificateEventData {}
2✔
4090
/**
2✔
4091
 * This EventType inherits all Properties of the AuditCertificateEventType.
2✔
4092
 *
2✔
4093
 * The SourceName for Events of this type shall be “Security/Certificate”.
2✔
4094
 *
2✔
4095
 * The Message Variable shall include a description of why the certificate is not trusted.
2✔
4096
 * If a trust chain is involved then the certificate that failed in the trust chain should be described.
2✔
4097
 * There are no additional Properties defined for this EventType.
2✔
4098
 */
2✔
4099
export interface RaiseAuditCertificateUntrustedEventData extends RaiseAuditCertificateEventData {}
2✔
4100
/**
2✔
4101
 * This EventType inherits all Properties of the AuditCertificateEventType.
2✔
4102
 *
2✔
4103
 * The SourceName for Events of this type shall be “Security/Certificate”.
2✔
4104
 *
2✔
4105
 * The Message Variable shall include a description of why the certificate is revoked
2✔
4106
 * (was the revocation list unavailable or was the certificate on the list).
2✔
4107
 *
2✔
4108
 *  There are no additional Properties defined for this EventType.
2✔
4109
 */
2✔
4110
export interface RaiseAuditCertificateRevokedEventData extends RaiseAuditCertificateEventData {
2✔
4111
    sourceName: PseudoVariantStringPredefined<"Security/Certificate">;
2✔
4112
}
2✔
4113
/**
2✔
4114
 * This EventType inherits all Properties of the AuditCertificateEventType.
2✔
4115
 *
2✔
4116
 * The SourceName for Events of this type shall be “Security/Certificate”.
2✔
4117
 *
2✔
4118
 * The Message Variable shall include a description of misuse of the certificate.
2✔
4119
 *
2✔
4120
 * There are no additional Properties defined for this EventType
2✔
4121
 */
2✔
4122
export interface RaiseAuditCertificateMismatchEventData extends RaiseAuditCertificateEventData {}
2✔
4123

2✔
4124
const opts = { multiArgs: false };
2✔
4125
OPCUAServer.prototype.initialize = withCallback(OPCUAServer.prototype.initialize, opts);
2✔
4126
OPCUAServer.prototype.shutdown = withCallback(OPCUAServer.prototype.shutdown, opts);
2!
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc