node-opcua-server 2.164.2 → 2.165.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/base_server.d.ts +95 -18
- package/dist/base_server.js +211 -63
- package/dist/base_server.js.map +1 -1
- package/dist/opcua_server.d.ts +171 -123
- package/dist/opcua_server.js +419 -181
- package/dist/opcua_server.js.map +1 -1
- package/dist/server_end_point.d.ts +108 -12
- package/dist/server_end_point.js +146 -46
- package/dist/server_end_point.js.map +1 -1
- package/dist/server_engine.d.ts +32 -14
- package/dist/server_engine.js +155 -54
- package/dist/server_engine.js.map +1 -1
- package/package.json +42 -42
- package/source/base_server.ts +246 -84
- package/source/opcua_server.ts +621 -441
- package/source/server_end_point.ts +267 -83
- package/source/server_engine.ts +240 -130
package/source/opcua_server.ts
CHANGED
|
@@ -2,76 +2,74 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* @module node-opcua-server
|
|
4
4
|
*/
|
|
5
|
-
import { randomBytes } from "crypto";
|
|
6
|
-
import {
|
|
7
|
-
import { callbackify, types } from "util";
|
|
5
|
+
import { randomBytes } from "node:crypto";
|
|
6
|
+
import { callbackify, types } from "node:util";
|
|
8
7
|
|
|
9
|
-
import async from "async";
|
|
10
8
|
import chalk from "chalk";
|
|
11
|
-
|
|
12
|
-
import { extractFullyQualifiedDomainName, getFullyQualifiedDomainName } from "node-opcua-hostname";
|
|
13
|
-
|
|
14
|
-
import { assert } from "node-opcua-assert";
|
|
15
|
-
import { isNullOrUndefined } from "node-opcua-utils";
|
|
16
|
-
|
|
17
9
|
import {
|
|
18
|
-
AddressSpace,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
PseudoVariantExtensionObject,
|
|
24
|
-
PseudoVariantExtensionObjectArray,
|
|
25
|
-
PseudoVariantLocalizedText,
|
|
26
|
-
PseudoVariantNodeId,
|
|
27
|
-
PseudoVariantString,
|
|
28
|
-
RaiseEventData,
|
|
29
|
-
SessionContext,
|
|
30
|
-
UAObject,
|
|
31
|
-
UAVariable,
|
|
32
|
-
ISessionContext,
|
|
33
|
-
UAView,
|
|
34
|
-
EventTypeLike,
|
|
35
|
-
UAObjectType,
|
|
36
|
-
PseudoVariantStringPredefined,
|
|
10
|
+
type AddressSpace,
|
|
11
|
+
type EventTypeLike,
|
|
12
|
+
type IRolePolicyOverride,
|
|
13
|
+
type IServerBase,
|
|
14
|
+
type ISessionContext,
|
|
37
15
|
innerBrowse,
|
|
38
16
|
innerBrowseNext,
|
|
39
|
-
|
|
17
|
+
type PseudoVariant,
|
|
18
|
+
type PseudoVariantBoolean,
|
|
19
|
+
type PseudoVariantByteString,
|
|
20
|
+
type PseudoVariantDateTime,
|
|
21
|
+
type PseudoVariantDuration,
|
|
22
|
+
type PseudoVariantExtensionObject,
|
|
23
|
+
type PseudoVariantExtensionObjectArray,
|
|
24
|
+
type PseudoVariantLocalizedText,
|
|
25
|
+
type PseudoVariantNodeId,
|
|
26
|
+
type PseudoVariantString,
|
|
27
|
+
type PseudoVariantStringPredefined,
|
|
28
|
+
type RaiseEventData,
|
|
29
|
+
SessionContext,
|
|
30
|
+
type UAEventType,
|
|
31
|
+
type UAObject,
|
|
32
|
+
type UAObjectType,
|
|
33
|
+
type UAVariable,
|
|
34
|
+
type UAView
|
|
40
35
|
} from "node-opcua-address-space";
|
|
41
|
-
import {
|
|
36
|
+
import { assert } from "node-opcua-assert";
|
|
37
|
+
import type { ByteString, UAString } from "node-opcua-basic-types";
|
|
38
|
+
import { getDefaultCertificateManager, type OPCUACertificateManager } from "node-opcua-certificate-manager";
|
|
42
39
|
import { ServerState } from "node-opcua-common";
|
|
43
|
-
import { Certificate, exploreCertificate, Nonce } from "node-opcua-crypto/web";
|
|
40
|
+
import { type Certificate, exploreCertificate, type Nonce } from "node-opcua-crypto/web";
|
|
44
41
|
import {
|
|
45
42
|
AttributeIds,
|
|
46
43
|
filterDiagnosticOperationLevel,
|
|
47
44
|
filterDiagnosticServiceLevel,
|
|
45
|
+
LocalizedText,
|
|
48
46
|
NodeClass,
|
|
49
47
|
RESPONSE_DIAGNOSTICS_MASK_ALL
|
|
50
48
|
} from "node-opcua-data-model";
|
|
51
49
|
import { DataValue } from "node-opcua-data-value";
|
|
52
50
|
import { dump, make_debugLog, make_errorLog, make_warningLog } from "node-opcua-debug";
|
|
53
|
-
import {
|
|
51
|
+
import { extractFullyQualifiedDomainName, getFullyQualifiedDomainName, isIPAddress } from "node-opcua-hostname";
|
|
52
|
+
import type { NodeId } from "node-opcua-nodeid";
|
|
54
53
|
import { ObjectRegistry } from "node-opcua-object-registry";
|
|
55
54
|
import {
|
|
56
|
-
AsymmetricAlgorithmSecurityHeader,
|
|
57
55
|
coerceSecurityPolicy,
|
|
58
56
|
computeSignature,
|
|
59
57
|
fromURI,
|
|
60
58
|
getCryptoFactory,
|
|
61
|
-
Message,
|
|
59
|
+
type Message,
|
|
62
60
|
MessageSecurityMode,
|
|
63
61
|
nonceAlreadyBeenUsed,
|
|
64
|
-
Request,
|
|
65
|
-
Response,
|
|
62
|
+
type Request,
|
|
63
|
+
type Response,
|
|
66
64
|
SecurityPolicy,
|
|
67
|
-
ServerSecureChannelLayer,
|
|
68
|
-
SignatureData,
|
|
65
|
+
type ServerSecureChannelLayer,
|
|
66
|
+
type SignatureData,
|
|
69
67
|
verifySignature
|
|
70
68
|
} from "node-opcua-secure-channel";
|
|
71
69
|
import { BrowseNextRequest, BrowseNextResponse, BrowseRequest, BrowseResponse } from "node-opcua-service-browse";
|
|
72
70
|
import { CallRequest, CallResponse } from "node-opcua-service-call";
|
|
73
71
|
import { ApplicationType, UserTokenType } from "node-opcua-service-endpoints";
|
|
74
|
-
import { HistoryReadRequest, HistoryReadResponse, HistoryReadResult, HistoryUpdateResponse } from "node-opcua-service-history";
|
|
72
|
+
import { HistoryReadRequest, HistoryReadResponse, type HistoryReadResult, HistoryUpdateResponse } from "node-opcua-service-history";
|
|
75
73
|
import {
|
|
76
74
|
AddNodesResponse,
|
|
77
75
|
AddReferencesResponse,
|
|
@@ -129,57 +127,48 @@ import {
|
|
|
129
127
|
TranslateBrowsePathsToNodeIdsResponse
|
|
130
128
|
} from "node-opcua-service-translate-browse-path";
|
|
131
129
|
import { WriteRequest, WriteResponse } from "node-opcua-service-write";
|
|
132
|
-
import { CallbackT,
|
|
130
|
+
import { type CallbackT, StatusCode, StatusCodes } from "node-opcua-status-code";
|
|
133
131
|
import {
|
|
134
|
-
ApplicationDescriptionOptions,
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
132
|
+
type ApplicationDescriptionOptions,
|
|
133
|
+
type BrowseDescriptionOptions,
|
|
134
|
+
type BrowseResult,
|
|
135
|
+
type BuildInfo,
|
|
136
|
+
type BuildInfoOptions,
|
|
138
137
|
CancelResponse,
|
|
139
138
|
EndpointDescription,
|
|
140
|
-
MonitoredItemModifyRequest,
|
|
141
|
-
MonitoringMode,
|
|
142
|
-
UserIdentityToken,
|
|
143
|
-
UserTokenPolicy,
|
|
144
|
-
BuildInfoOptions,
|
|
145
|
-
MonitoredItemCreateResult,
|
|
146
139
|
IssuedIdentityToken,
|
|
147
|
-
|
|
140
|
+
type MonitoredItemCreateResult,
|
|
141
|
+
type MonitoredItemModifyRequest,
|
|
142
|
+
MonitoringMode,
|
|
148
143
|
ServiceFault,
|
|
149
|
-
|
|
150
|
-
|
|
144
|
+
type UserIdentityToken,
|
|
145
|
+
type UserTokenPolicy
|
|
151
146
|
} from "node-opcua-types";
|
|
152
|
-
import {
|
|
153
|
-
import { VariantArrayType } from "node-opcua-variant";
|
|
154
|
-
import {
|
|
147
|
+
import { isNullOrUndefined, matchUri } from "node-opcua-utils";
|
|
148
|
+
import { DataType, type Variant, VariantArrayType } from "node-opcua-variant";
|
|
149
|
+
import { withCallback } from "thenify-ex";
|
|
155
150
|
|
|
156
|
-
import {
|
|
157
|
-
import { ObjectIds, ObjectTypeIds } from "node-opcua-constants";
|
|
158
|
-
import { OPCUABaseServer, OPCUABaseServerOptions } from "./base_server";
|
|
151
|
+
import { OPCUABaseServer, type OPCUABaseServerOptions } from "./base_server";
|
|
159
152
|
import { Factory } from "./factory";
|
|
160
|
-
import {
|
|
153
|
+
import type { IChannelData } from "./i_channel_data";
|
|
154
|
+
import type { IRegisterServerManager } from "./i_register_server_manager";
|
|
155
|
+
import type { ISocketData } from "./i_socket_data";
|
|
161
156
|
import { MonitoredItem } from "./monitored_item";
|
|
162
157
|
import { RegisterServerManager } from "./register_server_manager";
|
|
163
158
|
import { RegisterServerManagerHidden } from "./register_server_manager_hidden";
|
|
164
159
|
import { RegisterServerManagerMDNSONLY } from "./register_server_manager_mdns_only";
|
|
165
|
-
import {
|
|
166
|
-
import {
|
|
167
|
-
import {
|
|
168
|
-
import {
|
|
169
|
-
import {
|
|
170
|
-
import {
|
|
171
|
-
import {
|
|
172
|
-
import { UAUserManagerBase, makeUserManager, UserManagerOptions } from "./user_manager";
|
|
160
|
+
import type { SamplingFunc } from "./sampling_func";
|
|
161
|
+
import type { ServerCapabilitiesOptions } from "./server_capabilities";
|
|
162
|
+
import { type AdvertisedEndpoint, type EndpointDescriptionEx, type IServerTransportSettings, OPCUAServerEndPoint, normalizeAdvertisedEndpoints, parseOpcTcpUrl } from "./server_end_point";
|
|
163
|
+
import { type ClosingReason, type CreateSessionOption, ServerEngine } from "./server_engine";
|
|
164
|
+
import type { ServerSession } from "./server_session";
|
|
165
|
+
import type { CreateMonitoredItemHook, DeleteMonitoredItemHook, Subscription } from "./server_subscription";
|
|
166
|
+
import { makeUserManager, type UAUserManagerBase, type UserManagerOptions } from "./user_manager";
|
|
173
167
|
import { bindRoleSet } from "./user_manager_ua";
|
|
174
|
-
import { SamplingFunc } from "./sampling_func";
|
|
175
168
|
|
|
176
169
|
function isSubscriptionIdInvalid(subscriptionId: number): boolean {
|
|
177
170
|
return subscriptionId < 0 || subscriptionId >= 0xffffffff;
|
|
178
171
|
}
|
|
179
|
-
|
|
180
|
-
// tslint:disable-next-line:no-var-requires
|
|
181
|
-
import { withCallback } from "thenify-ex";
|
|
182
|
-
// tslint:disable-next-line:no-var-requires
|
|
183
172
|
const package_info = require("../package.json");
|
|
184
173
|
const debugLog = make_debugLog(__filename);
|
|
185
174
|
const errorLog = make_errorLog(__filename);
|
|
@@ -187,11 +176,16 @@ const warningLog = make_warningLog(__filename);
|
|
|
187
176
|
|
|
188
177
|
const default_maxConnectionsPerEndpoint = 10;
|
|
189
178
|
|
|
190
|
-
function g_sendError(
|
|
179
|
+
function g_sendError(
|
|
180
|
+
channel: ServerSecureChannelLayer,
|
|
181
|
+
message: Message,
|
|
182
|
+
_ResponseClass: ResponseClassType,
|
|
183
|
+
statusCode: StatusCode
|
|
184
|
+
): void {
|
|
191
185
|
const response = new ServiceFault({
|
|
192
186
|
responseHeader: { serviceResult: statusCode }
|
|
193
187
|
});
|
|
194
|
-
|
|
188
|
+
channel.send_response("MSG", response, message);
|
|
195
189
|
}
|
|
196
190
|
|
|
197
191
|
const default_build_info: BuildInfoOptions = {
|
|
@@ -238,7 +232,7 @@ function _adjust_session_timeout(sessionTimeout: number) {
|
|
|
238
232
|
|
|
239
233
|
function channel_has_session(channel: ServerSecureChannelLayer, session: ServerSession): boolean {
|
|
240
234
|
if (session.channel === channel) {
|
|
241
|
-
assert(Object.
|
|
235
|
+
assert(Object.hasOwn(channel.sessionTokens, session.authenticationToken.toString()));
|
|
242
236
|
return true;
|
|
243
237
|
}
|
|
244
238
|
return false;
|
|
@@ -253,13 +247,13 @@ function moveSessionToChannel(session: ServerSession, channel: ServerSecureChann
|
|
|
253
247
|
session._detach_channel();
|
|
254
248
|
session._attach_channel(channel);
|
|
255
249
|
|
|
256
|
-
assert(session.channel
|
|
250
|
+
assert(session.channel?.channelId === channel.channelId);
|
|
257
251
|
}
|
|
258
252
|
|
|
259
253
|
async function _attempt_to_close_some_old_unactivated_session(server: OPCUAServer) {
|
|
260
|
-
const session = server.engine
|
|
254
|
+
const session = server.engine?.getOldestInactiveSession();
|
|
261
255
|
if (session) {
|
|
262
|
-
await server.engine
|
|
256
|
+
await server.engine?.closeSession(session.authenticationToken, false, "Forcing");
|
|
263
257
|
}
|
|
264
258
|
}
|
|
265
259
|
|
|
@@ -282,12 +276,12 @@ function getRequiredEndpointInfo(endpoint: EndpointDescription) {
|
|
|
282
276
|
userIdentityTokens: endpoint.userIdentityTokens
|
|
283
277
|
});
|
|
284
278
|
// reduce even further by explicitly setting unwanted members to null
|
|
285
|
-
e.server.applicationName =
|
|
279
|
+
e.server.applicationName = new LocalizedText({ text: "" });
|
|
286
280
|
// xx e.server.applicationType = null as any;
|
|
287
281
|
e.server.gatewayServerUri = null;
|
|
288
282
|
e.server.discoveryProfileUri = null;
|
|
289
283
|
e.server.discoveryUrls = null;
|
|
290
|
-
e.serverCertificate = null as
|
|
284
|
+
e.serverCertificate = null as unknown as Buffer;
|
|
291
285
|
return e;
|
|
292
286
|
}
|
|
293
287
|
|
|
@@ -301,8 +295,8 @@ function _serverEndpointsForCreateSessionResponse(server: OPCUAServer, endpointU
|
|
|
301
295
|
// https://reference.opcfoundation.org/v104/Core/docs/Part4/5.6.2/
|
|
302
296
|
// https://reference.opcfoundation.org/v105/Core/docs/Part4/5.6.2/
|
|
303
297
|
return server
|
|
304
|
-
.
|
|
305
|
-
.filter((e) => !(e as
|
|
298
|
+
.findMatchingEndpoints(endpointUrl)
|
|
299
|
+
.filter((e) => !(e as unknown as { restricted: boolean }).restricted) // remove restricted endpoints
|
|
306
300
|
.filter((e) => matchUri(e.endpointUrl, endpointUrl))
|
|
307
301
|
.map(getRequiredEndpointInfo);
|
|
308
302
|
}
|
|
@@ -325,7 +319,10 @@ function findUserTokenByPolicy(
|
|
|
325
319
|
policyId: SecurityPolicy | string | null
|
|
326
320
|
): UserTokenPolicy | null {
|
|
327
321
|
assert(endpoint_description instanceof EndpointDescription);
|
|
328
|
-
|
|
322
|
+
if (!endpoint_description.userIdentityTokens) {
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
const r = endpoint_description.userIdentityTokens.filter(
|
|
329
326
|
(userIdentity: UserTokenPolicy) =>
|
|
330
327
|
userIdentity.tokenType === userTokenType && (!policyId || userIdentity.policyId === policyId)
|
|
331
328
|
);
|
|
@@ -334,7 +331,10 @@ function findUserTokenByPolicy(
|
|
|
334
331
|
|
|
335
332
|
function findUserTokenPolicy(endpoint_description: EndpointDescription, userTokenType: UserTokenType): UserTokenPolicy | null {
|
|
336
333
|
assert(endpoint_description instanceof EndpointDescription);
|
|
337
|
-
|
|
334
|
+
if (!endpoint_description.userIdentityTokens) {
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
const r = endpoint_description.userIdentityTokens.filter((userIdentity: UserTokenPolicy) => {
|
|
338
338
|
assert(userIdentity.tokenType !== undefined);
|
|
339
339
|
return userIdentity.tokenType === userTokenType;
|
|
340
340
|
});
|
|
@@ -350,7 +350,13 @@ function createAnonymousIdentityToken(endpoint_desc: EndpointDescription) {
|
|
|
350
350
|
return new AnonymousIdentityToken({ policyId: userTokenPolicy.policyId });
|
|
351
351
|
}
|
|
352
352
|
|
|
353
|
-
function sameIdentityToken(token1
|
|
353
|
+
function sameIdentityToken(token1?: UserIdentityToken, token2?: UserIdentityToken): boolean {
|
|
354
|
+
if (!token1 && !token2) {
|
|
355
|
+
return true;
|
|
356
|
+
}
|
|
357
|
+
if (!token1 || !token2) {
|
|
358
|
+
return false;
|
|
359
|
+
}
|
|
354
360
|
if (token1 instanceof UserNameIdentityToken) {
|
|
355
361
|
if (!(token2 instanceof UserNameIdentityToken)) {
|
|
356
362
|
return false;
|
|
@@ -388,7 +394,7 @@ function getTokenType(userIdentityToken: UserIdentityToken): UserTokenType {
|
|
|
388
394
|
}
|
|
389
395
|
return UserTokenType.Invalid;
|
|
390
396
|
}
|
|
391
|
-
function thumbprint(certificate?: Certificate): string {
|
|
397
|
+
function thumbprint(certificate?: Certificate | null): string {
|
|
392
398
|
return certificate ? certificate.toString("base64") : "";
|
|
393
399
|
}
|
|
394
400
|
|
|
@@ -408,7 +414,7 @@ function monitoredItem_read_and_record_value(
|
|
|
408
414
|
context: ISessionContext,
|
|
409
415
|
oldValue: DataValue,
|
|
410
416
|
node: UAVariable,
|
|
411
|
-
itemToMonitor:
|
|
417
|
+
itemToMonitor: ReadValueId,
|
|
412
418
|
callback: (err: Error | null, dataValue?: DataValue) => void
|
|
413
419
|
) {
|
|
414
420
|
assert(self instanceof MonitoredItem);
|
|
@@ -429,7 +435,7 @@ function monitoredItem_read_and_record_value_async(
|
|
|
429
435
|
context: ISessionContext,
|
|
430
436
|
oldValue: DataValue,
|
|
431
437
|
node: UAVariable,
|
|
432
|
-
itemToMonitor:
|
|
438
|
+
itemToMonitor: ReadValueId,
|
|
433
439
|
callback: (err: Error | null, dataValue?: DataValue) => void
|
|
434
440
|
) {
|
|
435
441
|
assert(context instanceof SessionContext);
|
|
@@ -444,7 +450,7 @@ function monitoredItem_read_and_record_value_async(
|
|
|
444
450
|
});
|
|
445
451
|
}
|
|
446
452
|
|
|
447
|
-
function build_scanning_node_function(addressSpace: AddressSpace, itemToMonitor:
|
|
453
|
+
function build_scanning_node_function(addressSpace: AddressSpace, itemToMonitor: ReadValueId): SamplingFunc {
|
|
448
454
|
assert(itemToMonitor instanceof ReadValueId);
|
|
449
455
|
|
|
450
456
|
const node = addressSpace.findNode(itemToMonitor.nodeId) as UAVariable;
|
|
@@ -508,7 +514,7 @@ function build_scanning_node_function(addressSpace: AddressSpace, itemToMonitor:
|
|
|
508
514
|
}
|
|
509
515
|
}
|
|
510
516
|
|
|
511
|
-
function prepareMonitoredItem(
|
|
517
|
+
function prepareMonitoredItem(_context: ISessionContext, addressSpace: AddressSpace, monitoredItem: MonitoredItem) {
|
|
512
518
|
const itemToMonitor = monitoredItem.itemToMonitor;
|
|
513
519
|
const readNodeFunc = build_scanning_node_function(addressSpace, itemToMonitor);
|
|
514
520
|
monitoredItem.samplingFunc = readNodeFunc;
|
|
@@ -590,7 +596,7 @@ function _installRegisterServerManager(self: OPCUAServer) {
|
|
|
590
596
|
}
|
|
591
597
|
|
|
592
598
|
function validate_applicationUri(channel: ServerSecureChannelLayer, request: CreateSessionRequest): boolean {
|
|
593
|
-
const applicationUri = request.clientDescription.applicationUri
|
|
599
|
+
const applicationUri = request.clientDescription.applicationUri || "";
|
|
594
600
|
const clientCertificate = request.clientCertificate;
|
|
595
601
|
// if session is insecure there is no need to check certificate information
|
|
596
602
|
if (channel.securityMode === MessageSecurityMode.None) {
|
|
@@ -600,7 +606,7 @@ function validate_applicationUri(channel: ServerSecureChannelLayer, request: Cre
|
|
|
600
606
|
return true; // can't check
|
|
601
607
|
}
|
|
602
608
|
const e = exploreCertificate(clientCertificate);
|
|
603
|
-
const uniformResourceIdentifier = e.tbsCertificate.extensions
|
|
609
|
+
const uniformResourceIdentifier = e.tbsCertificate.extensions?.subjectAltName?.uniformResourceIdentifier ?? null;
|
|
604
610
|
const applicationUriFromCert =
|
|
605
611
|
uniformResourceIdentifier && uniformResourceIdentifier.length > 0 ? uniformResourceIdentifier[0] : null;
|
|
606
612
|
|
|
@@ -623,7 +629,7 @@ function validate_security_endpoint(
|
|
|
623
629
|
endpoint?: EndpointDescription;
|
|
624
630
|
} {
|
|
625
631
|
debugLog("validate_security_endpoint = ", request.endpointUrl);
|
|
626
|
-
let endpoints = server.
|
|
632
|
+
let endpoints = server.findMatchingEndpoints(request.endpointUrl);
|
|
627
633
|
// endpointUrl String The network address that the Client used to access the Session Endpoint.
|
|
628
634
|
// The HostName portion of the URL should be one of the HostNames for the application that are
|
|
629
635
|
// specified in the Server’s ApplicationInstanceCertificate (see 7.2). The Server shall raise an
|
|
@@ -636,22 +642,22 @@ function validate_security_endpoint(
|
|
|
636
642
|
// sometime endpoints have a extra leading "/" that can be ignored
|
|
637
643
|
// don't be too harsh.
|
|
638
644
|
if (endpoints.length === 0 && request.endpointUrl?.endsWith("/")) {
|
|
639
|
-
endpoints = server.
|
|
645
|
+
endpoints = server.findMatchingEndpoints(request.endpointUrl.slice(0, -1));
|
|
640
646
|
}
|
|
641
647
|
|
|
642
648
|
if (endpoints.length === 0) {
|
|
643
649
|
// we have a UrlMismatch here
|
|
644
|
-
const ua_server = server.engine.addressSpace
|
|
650
|
+
const ua_server = server.engine.addressSpace?.rootFolder.objects.server;
|
|
645
651
|
if (!request.endpointUrl?.match(/localhost/i) || OPCUAServer.requestExactEndpointUrl) {
|
|
646
652
|
warningLog("Cannot find suitable endpoints in available endpoints. endpointUri =", request.endpointUrl);
|
|
647
653
|
}
|
|
648
|
-
ua_server
|
|
654
|
+
ua_server?.raiseEvent("AuditUrlMismatchEventType", {
|
|
649
655
|
endpointUrl: { dataType: DataType.String, value: request.endpointUrl }
|
|
650
656
|
});
|
|
651
657
|
if (OPCUAServer.requestExactEndpointUrl) {
|
|
652
658
|
return { errCode: StatusCodes.BadServiceUnsupported };
|
|
653
659
|
} else {
|
|
654
|
-
endpoints = server.
|
|
660
|
+
endpoints = server.findMatchingEndpoints(null);
|
|
655
661
|
}
|
|
656
662
|
}
|
|
657
663
|
// ignore restricted endpoints
|
|
@@ -665,7 +671,7 @@ function validate_security_endpoint(
|
|
|
665
671
|
return { errCode: StatusCodes.BadSecurityModeRejected };
|
|
666
672
|
}
|
|
667
673
|
const endpoints_matching_security_policy = endpoints_matching_security_mode.filter((e: EndpointDescription) => {
|
|
668
|
-
return e.securityPolicyUri === channel
|
|
674
|
+
return e.securityPolicyUri === channel?.securityPolicy;
|
|
669
675
|
});
|
|
670
676
|
|
|
671
677
|
if (endpoints_matching_security_policy.length === 0) {
|
|
@@ -674,7 +680,10 @@ function validate_security_endpoint(
|
|
|
674
680
|
if (endpoints_matching_security_policy.length !== 1) {
|
|
675
681
|
debugLog("endpoints_matching_security_policy= ", endpoints_matching_security_policy.length);
|
|
676
682
|
}
|
|
677
|
-
return {
|
|
683
|
+
return {
|
|
684
|
+
errCode: StatusCodes.Good,
|
|
685
|
+
endpoint: endpoints_matching_security_policy[0]
|
|
686
|
+
};
|
|
678
687
|
}
|
|
679
688
|
|
|
680
689
|
export function filterDiagnosticInfo(returnDiagnostics: number, response: CallResponse): void {
|
|
@@ -748,6 +757,21 @@ export interface OPCUAServerEndpointOptions {
|
|
|
748
757
|
/** alternate hostname or IP to use */
|
|
749
758
|
alternateHostname?: string | string[];
|
|
750
759
|
|
|
760
|
+
/**
|
|
761
|
+
* Additional endpoint URL(s) to advertise.
|
|
762
|
+
*
|
|
763
|
+
* Use when the server is behind Docker port-mapping, a reverse proxy,
|
|
764
|
+
* or a NAT gateway. Each URL is parsed to extract hostname and port.
|
|
765
|
+
* Each entry can be a plain URL string (inherits all security
|
|
766
|
+
* settings from the main endpoint) or an
|
|
767
|
+
* `AdvertisedEndpointConfig` object with per-URL overrides.
|
|
768
|
+
* The server still listens on `port`.
|
|
769
|
+
*
|
|
770
|
+
* @example "opc.tcp://localhost:48481"
|
|
771
|
+
* @example ["opc.tcp://localhost:48481", { url: "opc.tcp://public:4840", securityModes: [MessageSecurityMode.SignAndEncrypt] }]
|
|
772
|
+
*/
|
|
773
|
+
advertisedEndpoints?: AdvertisedEndpoint | AdvertisedEndpoint[];
|
|
774
|
+
|
|
751
775
|
/**
|
|
752
776
|
* true, if discovery service on secure channel shall be disabled
|
|
753
777
|
*/
|
|
@@ -925,39 +949,18 @@ export interface OPCUAServerOptions extends OPCUABaseServerOptions, OPCUAServerE
|
|
|
925
949
|
transportSettings?: IServerTransportSettings;
|
|
926
950
|
}
|
|
927
951
|
|
|
928
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
|
|
929
|
-
export interface OPCUAServer {
|
|
930
|
-
/**
|
|
931
|
-
*
|
|
932
|
-
*/
|
|
933
|
-
engine: ServerEngine;
|
|
934
|
-
/**
|
|
935
|
-
*
|
|
936
|
-
*/
|
|
937
|
-
registerServerMethod: RegisterServerMethod;
|
|
938
|
-
/**
|
|
939
|
-
*
|
|
940
|
-
*/
|
|
941
|
-
discoveryServerEndpointUrl: string;
|
|
942
|
-
/**
|
|
943
|
-
*
|
|
944
|
-
*/
|
|
945
|
-
registerServerManager?: IRegisterServerManager;
|
|
946
|
-
/**
|
|
947
|
-
*
|
|
948
|
-
*/
|
|
949
|
-
capabilitiesForMDNS: string[];
|
|
950
|
-
/**
|
|
951
|
-
*
|
|
952
|
-
*/
|
|
953
|
-
userCertificateManager: OPCUACertificateManager;
|
|
954
|
-
}
|
|
955
|
-
|
|
956
952
|
const g_requestExactEndpointUrl = !!process.env.NODEOPCUA_SERVER_REQUEST_EXACT_ENDPOINT_URL;
|
|
957
953
|
/**
|
|
958
954
|
*
|
|
959
955
|
*/
|
|
960
956
|
export class OPCUAServer extends OPCUABaseServer {
|
|
957
|
+
public engine!: ServerEngine;
|
|
958
|
+
public registerServerMethod: RegisterServerMethod;
|
|
959
|
+
public discoveryServerEndpointUrl!: string;
|
|
960
|
+
public registerServerManager?: IRegisterServerManager;
|
|
961
|
+
public capabilitiesForMDNS: string[];
|
|
962
|
+
public userCertificateManager: OPCUACertificateManager;
|
|
963
|
+
|
|
961
964
|
static defaultShutdownTimeout = 100; // 250 ms
|
|
962
965
|
/**
|
|
963
966
|
* if requestExactEndpointUrl is set to true the server will only accept createSession that have a endpointUrl that strictly matches
|
|
@@ -1069,6 +1072,126 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
1069
1072
|
return this.engine ? this.engine.isAuditing : false;
|
|
1070
1073
|
}
|
|
1071
1074
|
|
|
1075
|
+
/**
|
|
1076
|
+
* Set the current server state.
|
|
1077
|
+
*
|
|
1078
|
+
* Updates both the internal state and the
|
|
1079
|
+
* `Server.ServerStatus.State` variable in the
|
|
1080
|
+
* address space so that OPC UA reads reflect the
|
|
1081
|
+
* new state immediately.
|
|
1082
|
+
*/
|
|
1083
|
+
public setServerState(serverState: ServerState): void {
|
|
1084
|
+
this.engine.setServerState(serverState);
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
/**
|
|
1088
|
+
* Read the current `ServerState` from the
|
|
1089
|
+
* internal server status.
|
|
1090
|
+
*/
|
|
1091
|
+
public getServerState(): ServerState {
|
|
1092
|
+
return this.engine.getServerState();
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
/**
|
|
1096
|
+
* Set or clear a temporary role-policy override.
|
|
1097
|
+
*
|
|
1098
|
+
* When set, the override's `getUserRoles(username)`
|
|
1099
|
+
* is called **before** the default `userManager`.
|
|
1100
|
+
* Returning a `NodeId[]` overrides the roles;
|
|
1101
|
+
* returning `null` falls through to the default.
|
|
1102
|
+
*
|
|
1103
|
+
* Call with `null` to remove the override and
|
|
1104
|
+
* restore default behavior.
|
|
1105
|
+
*/
|
|
1106
|
+
public setRolePolicyOverride(override: IRolePolicyOverride | null): void {
|
|
1107
|
+
(this as unknown as IServerBase).rolePolicyOverride = override;
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
/**
|
|
1111
|
+
* Set `ServerConfiguration.InApplicationSetup` in
|
|
1112
|
+
* the address space.
|
|
1113
|
+
*
|
|
1114
|
+
* Indicates whether the server is in its initial
|
|
1115
|
+
* application setup phase (e.g. awaiting GDS
|
|
1116
|
+
* provisioning).
|
|
1117
|
+
*/
|
|
1118
|
+
public setInApplicationSetup(value: boolean): void {
|
|
1119
|
+
this.engine.setInApplicationSetup(value);
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
/**
|
|
1123
|
+
* Read the current value of
|
|
1124
|
+
* `ServerConfiguration.InApplicationSetup`.
|
|
1125
|
+
*/
|
|
1126
|
+
public getInApplicationSetup(): boolean {
|
|
1127
|
+
return this.engine.getInApplicationSetup();
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
/**
|
|
1131
|
+
* Collect additional hostnames for the self-signed certificate SAN.
|
|
1132
|
+
*
|
|
1133
|
+
* Merges hostnames from `alternateHostname` and parsed
|
|
1134
|
+
* `advertisedEndpoints` URLs so the certificate covers all
|
|
1135
|
+
* configured addresses.
|
|
1136
|
+
*
|
|
1137
|
+
* IP literals (v4/v6) are **excluded** — they are handled by
|
|
1138
|
+
* `getConfiguredIPs()` and placed in the SAN `iPAddress` entries.
|
|
1139
|
+
*
|
|
1140
|
+
* @internal
|
|
1141
|
+
*/
|
|
1142
|
+
protected override getConfiguredHostnames(): string[] {
|
|
1143
|
+
return this._collectAlternateValues().hostnames;
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
/**
|
|
1147
|
+
* Collect additional IP addresses for the self-signed certificate SAN.
|
|
1148
|
+
*
|
|
1149
|
+
* Merges IP literals from `alternateHostname` and parsed
|
|
1150
|
+
* `advertisedEndpoints` URLs so the certificate covers all
|
|
1151
|
+
* configured IP addresses.
|
|
1152
|
+
*
|
|
1153
|
+
* @internal
|
|
1154
|
+
*/
|
|
1155
|
+
protected override getConfiguredIPs(): string[] {
|
|
1156
|
+
return this._collectAlternateValues().ips;
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
/**
|
|
1160
|
+
* Classify all values from `alternateHostname` and
|
|
1161
|
+
* `advertisedEndpoints` into hostnames vs IP addresses using
|
|
1162
|
+
* `isIPAddress()` (wraps `net.isIP()`).
|
|
1163
|
+
*/
|
|
1164
|
+
private _collectAlternateValues(): { hostnames: string[]; ips: string[] } {
|
|
1165
|
+
const hostnames: string[] = [];
|
|
1166
|
+
const ips: string[] = [];
|
|
1167
|
+
|
|
1168
|
+
// alternateHostname
|
|
1169
|
+
const alt = this.options.alternateHostname;
|
|
1170
|
+
if (alt) {
|
|
1171
|
+
const altArray = Array.isArray(alt) ? alt : [alt];
|
|
1172
|
+
for (const value of altArray) {
|
|
1173
|
+
if (isIPAddress(value)) {
|
|
1174
|
+
ips.push(value);
|
|
1175
|
+
} else {
|
|
1176
|
+
hostnames.push(value);
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
// advertisedEndpoints — normalize to AdvertisedEndpointConfig[]
|
|
1182
|
+
const advList = normalizeAdvertisedEndpoints(this.options.advertisedEndpoints);
|
|
1183
|
+
for (const config of advList) {
|
|
1184
|
+
const { hostname } = parseOpcTcpUrl(config.url);
|
|
1185
|
+
if (isIPAddress(hostname)) {
|
|
1186
|
+
ips.push(hostname);
|
|
1187
|
+
} else {
|
|
1188
|
+
hostnames.push(hostname);
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
return { hostnames, ips };
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1072
1195
|
public static registry = new ObjectRegistry();
|
|
1073
1196
|
public static fallbackSessionName = "Client didn't provide a meaningful sessionName ...";
|
|
1074
1197
|
/**
|
|
@@ -1175,7 +1298,7 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
1175
1298
|
|
|
1176
1299
|
// note: applicationUri is handled in a special way
|
|
1177
1300
|
this.engine = new ServerEngine({
|
|
1178
|
-
applicationUri: () => this.serverInfo.applicationUri
|
|
1301
|
+
applicationUri: () => this.serverInfo.applicationUri || "",
|
|
1179
1302
|
buildInfo,
|
|
1180
1303
|
isAuditing: options.isAuditing,
|
|
1181
1304
|
serverCapabilities: options.serverCapabilities,
|
|
@@ -1213,6 +1336,7 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
1213
1336
|
host: options.host,
|
|
1214
1337
|
allowAnonymous: options.allowAnonymous,
|
|
1215
1338
|
alternateHostname: options.alternateHostname,
|
|
1339
|
+
advertisedEndpoints: options.advertisedEndpoints,
|
|
1216
1340
|
disableDiscovery: options.disableDiscovery,
|
|
1217
1341
|
securityModes: options.securityModes,
|
|
1218
1342
|
securityPolicies: options.securityPolicies
|
|
@@ -1220,7 +1344,7 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
1220
1344
|
}
|
|
1221
1345
|
// todo should self.serverInfo.productUri match self.engine.buildInfo.productUri ?
|
|
1222
1346
|
for (const endpointOptions of endpointDefinitions) {
|
|
1223
|
-
const endPoint = this.createEndpointDescriptions(options
|
|
1347
|
+
const endPoint = this.createEndpointDescriptions(options, endpointOptions);
|
|
1224
1348
|
this.endpoints.push(endPoint);
|
|
1225
1349
|
endPoint.on("message", (message: Message, channel: ServerSecureChannelLayer) => {
|
|
1226
1350
|
this.on_request(message, channel);
|
|
@@ -1262,7 +1386,7 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
1262
1386
|
*/
|
|
1263
1387
|
public initialize(): Promise<void>;
|
|
1264
1388
|
public initialize(done: () => void): void;
|
|
1265
|
-
public initialize(...args: [
|
|
1389
|
+
public initialize(...args: [((err?: Error) => void)?]): Promise<void> | void {
|
|
1266
1390
|
const done = args[0] as (err?: Error) => void;
|
|
1267
1391
|
assert(!this.initialized, "server is already initialized"); // already initialized ?
|
|
1268
1392
|
|
|
@@ -1278,7 +1402,11 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
1278
1402
|
.then(() => {
|
|
1279
1403
|
OPCUAServer.registry.register(this);
|
|
1280
1404
|
this.engine.initialize(this.options, () => {
|
|
1281
|
-
|
|
1405
|
+
if (!this.engine.addressSpace) {
|
|
1406
|
+
done(new Error("no addressSpace"));
|
|
1407
|
+
return;
|
|
1408
|
+
}
|
|
1409
|
+
bindRoleSet(this.userManager, this.engine.addressSpace);
|
|
1282
1410
|
setImmediate(() => {
|
|
1283
1411
|
this.emit("post_initialize");
|
|
1284
1412
|
done();
|
|
@@ -1295,38 +1423,36 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
1295
1423
|
*/
|
|
1296
1424
|
public start(): Promise<void>;
|
|
1297
1425
|
public start(done: () => void): void;
|
|
1298
|
-
public start(...args: [
|
|
1299
|
-
const
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1426
|
+
public start(...args: [((err?: Error) => void)?]): Promise<void> | void {
|
|
1427
|
+
const callback = args[0];
|
|
1428
|
+
if (callback) {
|
|
1429
|
+
return super.start(callback);
|
|
1430
|
+
}
|
|
1431
|
+
return super.start();
|
|
1432
|
+
}
|
|
1433
|
+
/**
|
|
1434
|
+
* Initiate the server by starting all its endpoints
|
|
1435
|
+
* @private
|
|
1436
|
+
*/
|
|
1437
|
+
public async startAsync(): Promise<void> {
|
|
1438
|
+
await extractFullyQualifiedDomainName();
|
|
1303
1439
|
|
|
1304
1440
|
if (!this.initialized) {
|
|
1305
|
-
|
|
1306
|
-
this.initialize(callback);
|
|
1307
|
-
});
|
|
1441
|
+
await this.initialize();
|
|
1308
1442
|
}
|
|
1309
|
-
tasks.push((callback: ErrorCallback) => {
|
|
1310
|
-
super.start((err?: Error | null) => {
|
|
1311
|
-
if (err) {
|
|
1312
|
-
this.shutdown((/*err2*/ err2?: Error) => {
|
|
1313
|
-
callback(err);
|
|
1314
|
-
});
|
|
1315
|
-
} else {
|
|
1316
|
-
// we start the registration process asynchronously
|
|
1317
|
-
// as we want to make server immediately available
|
|
1318
|
-
this.registerServerManager!.start().then(() => {
|
|
1319
|
-
/* empty */
|
|
1320
|
-
}).catch((err) => {
|
|
1321
|
-
/* empty */
|
|
1322
|
-
});
|
|
1323
1443
|
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1444
|
+
try {
|
|
1445
|
+
await super.startAsync();
|
|
1446
|
+
} catch (err) {
|
|
1447
|
+
await this.shutdown();
|
|
1448
|
+
throw err;
|
|
1449
|
+
}
|
|
1328
1450
|
|
|
1329
|
-
|
|
1451
|
+
// we start the registration process asynchronously
|
|
1452
|
+
// as we want to make server immediately available
|
|
1453
|
+
this.registerServerManager?.start().catch(() => {
|
|
1454
|
+
/* empty */
|
|
1455
|
+
});
|
|
1330
1456
|
}
|
|
1331
1457
|
|
|
1332
1458
|
/**
|
|
@@ -1356,7 +1482,7 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
1356
1482
|
public shutdown(timeout?: number): Promise<void>;
|
|
1357
1483
|
public shutdown(callback: (err?: Error) => void): void;
|
|
1358
1484
|
public shutdown(timeout: number, callback: (err?: Error) => void): void;
|
|
1359
|
-
public shutdown(...args: [
|
|
1485
|
+
public shutdown(...args: [number | ((err?: Error) => void) | undefined, ...((err?: Error) => void)[]]): Promise<void> | void {
|
|
1360
1486
|
const timeout = args.length === 1 ? OPCUAServer.defaultShutdownTimeout : (args[0] as number);
|
|
1361
1487
|
const callback = (args.length === 1 ? args[0] : args[1]) as (err?: Error) => void;
|
|
1362
1488
|
assert(typeof callback === "function");
|
|
@@ -1380,28 +1506,31 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
1380
1506
|
const shutdownTime = new Date(Date.now() + timeout);
|
|
1381
1507
|
this.engine.setShutdownTime(shutdownTime);
|
|
1382
1508
|
|
|
1383
|
-
debugLog("OPCUAServer is now un-registering itself from the discovery server "
|
|
1384
|
-
this.registerServerManager
|
|
1509
|
+
debugLog("OPCUAServer is now un-registering itself from the discovery server ", this.buildInfo);
|
|
1510
|
+
if (!this.registerServerManager) {
|
|
1511
|
+
callback(new Error("invalid register server manager"));
|
|
1512
|
+
return;
|
|
1513
|
+
}
|
|
1514
|
+
this.registerServerManager
|
|
1515
|
+
.stop()
|
|
1385
1516
|
.then(() => {
|
|
1386
1517
|
debugLog("OPCUAServer unregistered from discovery server successfully");
|
|
1387
|
-
|
|
1388
1518
|
})
|
|
1389
1519
|
.catch((err) => {
|
|
1390
1520
|
debugLog("OPCUAServer unregistered from discovery server with err: ", err.message);
|
|
1391
|
-
})
|
|
1392
|
-
|
|
1521
|
+
})
|
|
1522
|
+
.finally(() => {
|
|
1393
1523
|
setTimeout(async () => {
|
|
1394
1524
|
await this.engine.shutdown();
|
|
1395
1525
|
|
|
1396
1526
|
debugLog("OPCUAServer#shutdown: started");
|
|
1397
|
-
OPCUABaseServer.prototype.shutdown.call(this, (err1?: Error) => {
|
|
1527
|
+
OPCUABaseServer.prototype.shutdown.call(this, (err1?: Error | null) => {
|
|
1398
1528
|
debugLog("OPCUAServer#shutdown: completed");
|
|
1399
1529
|
|
|
1400
1530
|
this.dispose();
|
|
1401
|
-
callback(err1);
|
|
1531
|
+
callback(err1 || undefined);
|
|
1402
1532
|
});
|
|
1403
1533
|
}, timeout);
|
|
1404
|
-
|
|
1405
1534
|
});
|
|
1406
1535
|
}
|
|
1407
1536
|
|
|
@@ -1457,12 +1586,12 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
1457
1586
|
if (typeof eventType === "string") {
|
|
1458
1587
|
eventTypeNode = this.engine.addressSpace.findEventType(eventType);
|
|
1459
1588
|
if (eventTypeNode) {
|
|
1460
|
-
|
|
1589
|
+
server.raiseEvent(eventTypeNode, options);
|
|
1461
1590
|
} else {
|
|
1462
1591
|
console.warn(" cannot find event type ", eventType);
|
|
1463
1592
|
}
|
|
1464
1593
|
} else {
|
|
1465
|
-
|
|
1594
|
+
server.raiseEvent(eventTypeNode, options);
|
|
1466
1595
|
}
|
|
1467
1596
|
}
|
|
1468
1597
|
|
|
@@ -1512,32 +1641,34 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
1512
1641
|
channel: ServerSecureChannelLayer,
|
|
1513
1642
|
clientSignature: SignatureData
|
|
1514
1643
|
): boolean {
|
|
1515
|
-
const clientCertificate = channel.clientCertificate
|
|
1644
|
+
const clientCertificate = channel.clientCertificate;
|
|
1516
1645
|
const securityPolicy = channel.securityPolicy;
|
|
1517
1646
|
const serverCertificate = this.getCertificate();
|
|
1518
|
-
const result = verifySignature(serverCertificate, session.nonce
|
|
1647
|
+
const result = verifySignature(serverCertificate, session.nonce, clientSignature, clientCertificate, securityPolicy);
|
|
1519
1648
|
return result;
|
|
1520
1649
|
}
|
|
1521
1650
|
|
|
1522
1651
|
protected isValidUserNameIdentityToken(
|
|
1523
1652
|
channel: ServerSecureChannelLayer,
|
|
1524
|
-
|
|
1653
|
+
_session: ServerSession,
|
|
1525
1654
|
userTokenPolicy: UserTokenPolicy,
|
|
1526
1655
|
userIdentityToken: UserNameIdentityToken,
|
|
1527
|
-
|
|
1656
|
+
_userTokenSignature: SignatureData,
|
|
1528
1657
|
callback: (err: Error | null, statusCode?: StatusCode) => void
|
|
1529
1658
|
): void {
|
|
1530
1659
|
assert(userIdentityToken instanceof UserNameIdentityToken);
|
|
1531
1660
|
|
|
1532
1661
|
const securityPolicy = adjustSecurityPolicy(channel, userTokenPolicy.securityPolicyUri);
|
|
1533
1662
|
if (securityPolicy === SecurityPolicy.None) {
|
|
1534
|
-
|
|
1663
|
+
callback(null, StatusCodes.Good);
|
|
1664
|
+
return;
|
|
1535
1665
|
}
|
|
1536
1666
|
const cryptoFactory = getCryptoFactory(securityPolicy);
|
|
1537
1667
|
|
|
1538
1668
|
/* c8 ignore next */
|
|
1539
1669
|
if (!cryptoFactory) {
|
|
1540
|
-
|
|
1670
|
+
callback(null, StatusCodes.BadSecurityPolicyRejected);
|
|
1671
|
+
return;
|
|
1541
1672
|
}
|
|
1542
1673
|
|
|
1543
1674
|
/* c8 ignore next */
|
|
@@ -1545,14 +1676,16 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
1545
1676
|
errorLog("invalid encryptionAlgorithm");
|
|
1546
1677
|
errorLog("userTokenPolicy", userTokenPolicy.toString());
|
|
1547
1678
|
errorLog("userTokenPolicy", userIdentityToken.toString());
|
|
1548
|
-
|
|
1679
|
+
callback(null, StatusCodes.BadIdentityTokenInvalid);
|
|
1680
|
+
return;
|
|
1549
1681
|
}
|
|
1550
1682
|
const userName = userIdentityToken.userName;
|
|
1551
1683
|
const password = userIdentityToken.password;
|
|
1552
1684
|
if (!userName || !password) {
|
|
1553
|
-
|
|
1685
|
+
callback(null, StatusCodes.BadIdentityTokenInvalid);
|
|
1686
|
+
return;
|
|
1554
1687
|
}
|
|
1555
|
-
|
|
1688
|
+
callback(null, StatusCodes.Good);
|
|
1556
1689
|
}
|
|
1557
1690
|
|
|
1558
1691
|
protected isValidX509IdentityToken(
|
|
@@ -1571,37 +1704,40 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
1571
1704
|
const cryptoFactory = getCryptoFactory(securityPolicy);
|
|
1572
1705
|
/* c8 ignore next */
|
|
1573
1706
|
if (!cryptoFactory) {
|
|
1574
|
-
|
|
1707
|
+
callback(null, StatusCodes.BadSecurityPolicyRejected);
|
|
1708
|
+
return;
|
|
1575
1709
|
}
|
|
1576
1710
|
|
|
1577
1711
|
if (!userTokenSignature || !userTokenSignature.signature) {
|
|
1578
1712
|
this.raiseEvent("AuditCreateSessionEventType", {});
|
|
1579
|
-
|
|
1580
|
-
return
|
|
1713
|
+
callback(null, StatusCodes.BadUserSignatureInvalid);
|
|
1714
|
+
return;
|
|
1581
1715
|
}
|
|
1582
1716
|
|
|
1583
1717
|
if (userIdentityToken.policyId !== userTokenPolicy.policyId) {
|
|
1584
1718
|
errorLog("invalid encryptionAlgorithm");
|
|
1585
1719
|
errorLog("userTokenPolicy", userTokenPolicy.toString());
|
|
1586
1720
|
errorLog("userTokenPolicy", userIdentityToken.toString());
|
|
1587
|
-
|
|
1721
|
+
callback(null, StatusCodes.BadSecurityPolicyRejected);
|
|
1722
|
+
return;
|
|
1588
1723
|
}
|
|
1589
1724
|
const certificate = userIdentityToken.certificateData; /* as Certificate*/
|
|
1590
|
-
const nonce = session.nonce
|
|
1725
|
+
const nonce = session.nonce;
|
|
1726
|
+
if (!nonce || nonce.length === 0) {
|
|
1727
|
+
callback(null, StatusCodes.BadNonceInvalid);
|
|
1728
|
+
return;
|
|
1729
|
+
}
|
|
1591
1730
|
const serverCertificate = this.getCertificate();
|
|
1592
|
-
|
|
1593
|
-
assert(serverCertificate instanceof Buffer);
|
|
1594
|
-
assert(certificate instanceof Buffer, "expecting certificate to be a Buffer");
|
|
1595
|
-
assert(nonce instanceof Buffer, "expecting nonce to be a Buffer");
|
|
1596
1731
|
assert(userTokenSignature.signature instanceof Buffer, "expecting userTokenSignature to be a Buffer");
|
|
1597
1732
|
|
|
1598
1733
|
// verify proof of possession by checking certificate signature & server nonce correctness
|
|
1599
1734
|
if (!verifySignature(serverCertificate, nonce, userTokenSignature, certificate, securityPolicy)) {
|
|
1600
|
-
|
|
1735
|
+
callback(null, StatusCodes.BadUserSignatureInvalid);
|
|
1736
|
+
return;
|
|
1601
1737
|
}
|
|
1602
1738
|
|
|
1603
1739
|
// verify if certificate is Valid
|
|
1604
|
-
this.userCertificateManager
|
|
1740
|
+
this.userCertificateManager.checkCertificate(certificate, (err, certificateStatus) => {
|
|
1605
1741
|
/* c8 ignore next */
|
|
1606
1742
|
if (err) {
|
|
1607
1743
|
return callback(err);
|
|
@@ -1612,31 +1748,55 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
1612
1748
|
break;
|
|
1613
1749
|
case StatusCodes.BadCertificateUntrusted:
|
|
1614
1750
|
this.raiseEvent("AuditCertificateUntrustedEventType", {
|
|
1615
|
-
certificate: {
|
|
1616
|
-
|
|
1751
|
+
certificate: {
|
|
1752
|
+
dataType: DataType.ByteString,
|
|
1753
|
+
value: certificate
|
|
1754
|
+
},
|
|
1755
|
+
sourceName: {
|
|
1756
|
+
dataType: DataType.String,
|
|
1757
|
+
value: "Security/Certificate"
|
|
1758
|
+
}
|
|
1617
1759
|
});
|
|
1618
1760
|
break;
|
|
1619
1761
|
case StatusCodes.BadCertificateTimeInvalid:
|
|
1620
1762
|
case StatusCodes.BadCertificateIssuerTimeInvalid:
|
|
1621
1763
|
this.raiseEvent("AuditCertificateExpiredEventType", {
|
|
1622
|
-
certificate: {
|
|
1623
|
-
|
|
1764
|
+
certificate: {
|
|
1765
|
+
dataType: DataType.ByteString,
|
|
1766
|
+
value: certificate
|
|
1767
|
+
},
|
|
1768
|
+
sourceName: {
|
|
1769
|
+
dataType: DataType.String,
|
|
1770
|
+
value: "Security/Certificate"
|
|
1771
|
+
}
|
|
1624
1772
|
});
|
|
1625
1773
|
break;
|
|
1626
1774
|
case StatusCodes.BadCertificateRevoked:
|
|
1627
1775
|
case StatusCodes.BadCertificateRevocationUnknown:
|
|
1628
1776
|
case StatusCodes.BadCertificateIssuerRevocationUnknown:
|
|
1629
1777
|
this.raiseEvent("AuditCertificateRevokedEventType", {
|
|
1630
|
-
certificate: {
|
|
1631
|
-
|
|
1778
|
+
certificate: {
|
|
1779
|
+
dataType: DataType.ByteString,
|
|
1780
|
+
value: certificate
|
|
1781
|
+
},
|
|
1782
|
+
sourceName: {
|
|
1783
|
+
dataType: DataType.String,
|
|
1784
|
+
value: "Security/Certificate"
|
|
1785
|
+
}
|
|
1632
1786
|
});
|
|
1633
1787
|
break;
|
|
1634
1788
|
case StatusCodes.BadCertificateIssuerUseNotAllowed:
|
|
1635
1789
|
case StatusCodes.BadCertificateUseNotAllowed:
|
|
1636
1790
|
case StatusCodes.BadSecurityChecksFailed:
|
|
1637
1791
|
this.raiseEvent("AuditCertificateMismatchEventType", {
|
|
1638
|
-
certificate: {
|
|
1639
|
-
|
|
1792
|
+
certificate: {
|
|
1793
|
+
dataType: DataType.ByteString,
|
|
1794
|
+
value: certificate
|
|
1795
|
+
},
|
|
1796
|
+
sourceName: {
|
|
1797
|
+
dataType: DataType.String,
|
|
1798
|
+
value: "Security/Certificate"
|
|
1799
|
+
}
|
|
1640
1800
|
});
|
|
1641
1801
|
break;
|
|
1642
1802
|
}
|
|
@@ -1688,8 +1848,12 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
1688
1848
|
|
|
1689
1849
|
const securityPolicy = adjustSecurityPolicy(channel, userTokenPolicy.securityPolicyUri);
|
|
1690
1850
|
|
|
1691
|
-
const userName = userIdentityToken.userName
|
|
1692
|
-
|
|
1851
|
+
const userName = userIdentityToken.userName;
|
|
1852
|
+
if (!userName) {
|
|
1853
|
+
callback(new Error(" expecting a no null username"));
|
|
1854
|
+
return;
|
|
1855
|
+
}
|
|
1856
|
+
let password: ByteString | string = userIdentityToken.password;
|
|
1693
1857
|
|
|
1694
1858
|
// decrypt password if necessary
|
|
1695
1859
|
if (securityPolicy === SecurityPolicy.None) {
|
|
@@ -1698,20 +1862,24 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
1698
1862
|
} else {
|
|
1699
1863
|
const serverPrivateKey = this.getPrivateKey();
|
|
1700
1864
|
|
|
1701
|
-
const serverNonce = session.nonce
|
|
1702
|
-
|
|
1865
|
+
const serverNonce = session.nonce;
|
|
1866
|
+
if (!serverNonce) {
|
|
1867
|
+
callback(new Error(" expecting a no null nonce"));
|
|
1868
|
+
return;
|
|
1869
|
+
}
|
|
1703
1870
|
|
|
1704
1871
|
const cryptoFactory = getCryptoFactory(securityPolicy);
|
|
1705
1872
|
/* c8 ignore next */
|
|
1706
1873
|
if (!cryptoFactory) {
|
|
1707
|
-
|
|
1874
|
+
callback(new Error(" Unsupported security Policy"));
|
|
1875
|
+
return;
|
|
1708
1876
|
}
|
|
1709
1877
|
|
|
1710
1878
|
const buff = cryptoFactory.asymmetricDecrypt(password, serverPrivateKey);
|
|
1711
1879
|
|
|
1712
1880
|
// server certificate may be invalid and asymmetricDecrypt may fail
|
|
1713
1881
|
if (!buff || buff.length < 4) {
|
|
1714
|
-
|
|
1882
|
+
setImmediate(() => callback(null, false));
|
|
1715
1883
|
return;
|
|
1716
1884
|
}
|
|
1717
1885
|
|
|
@@ -1744,34 +1912,23 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
1744
1912
|
|
|
1745
1913
|
const userTokenType = getTokenType(userIdentityToken);
|
|
1746
1914
|
|
|
1747
|
-
const userTokenPolicy = findUserTokenByPolicy(endpointDescription, userTokenType, userIdentityToken.policyId
|
|
1915
|
+
const userTokenPolicy = findUserTokenByPolicy(endpointDescription, userTokenType, userIdentityToken.policyId);
|
|
1748
1916
|
if (!userTokenPolicy) {
|
|
1749
1917
|
// cannot find token with this policyId
|
|
1750
|
-
|
|
1918
|
+
callback(null, StatusCodes.BadIdentityTokenInvalid);
|
|
1919
|
+
return;
|
|
1751
1920
|
}
|
|
1752
1921
|
//
|
|
1753
1922
|
if (userIdentityToken instanceof UserNameIdentityToken) {
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
session,
|
|
1757
|
-
userTokenPolicy,
|
|
1758
|
-
userIdentityToken,
|
|
1759
|
-
userTokenSignature,
|
|
1760
|
-
callback
|
|
1761
|
-
);
|
|
1923
|
+
this.isValidUserNameIdentityToken(channel, session, userTokenPolicy, userIdentityToken, userTokenSignature, callback);
|
|
1924
|
+
return;
|
|
1762
1925
|
}
|
|
1763
1926
|
if (userIdentityToken instanceof X509IdentityToken) {
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
session,
|
|
1767
|
-
userTokenPolicy,
|
|
1768
|
-
userIdentityToken,
|
|
1769
|
-
userTokenSignature,
|
|
1770
|
-
callback
|
|
1771
|
-
);
|
|
1927
|
+
this.isValidX509IdentityToken(channel, session, userTokenPolicy, userIdentityToken, userTokenSignature, callback);
|
|
1928
|
+
return;
|
|
1772
1929
|
}
|
|
1773
1930
|
|
|
1774
|
-
|
|
1931
|
+
callback(null, StatusCodes.Good);
|
|
1775
1932
|
}
|
|
1776
1933
|
|
|
1777
1934
|
/**
|
|
@@ -1793,16 +1950,18 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
1793
1950
|
assert(typeof callback === "function");
|
|
1794
1951
|
|
|
1795
1952
|
const userTokenType = getTokenType(userIdentityToken);
|
|
1796
|
-
const userTokenPolicy = findUserTokenByPolicy(session.getEndpointDescription(), userTokenType, userIdentityToken.policyId
|
|
1953
|
+
const userTokenPolicy = findUserTokenByPolicy(session.getEndpointDescription(), userTokenType, userIdentityToken.policyId);
|
|
1797
1954
|
/** c8 ignore next */
|
|
1798
1955
|
if (!userTokenPolicy) {
|
|
1799
|
-
|
|
1956
|
+
callback(null, false);
|
|
1957
|
+
return;
|
|
1800
1958
|
}
|
|
1801
1959
|
// find if a userToken exists
|
|
1802
1960
|
if (userIdentityToken instanceof UserNameIdentityToken) {
|
|
1803
|
-
|
|
1961
|
+
this.userNameIdentityTokenAuthenticateUser(channel, session, userTokenPolicy, userIdentityToken, callback);
|
|
1962
|
+
return;
|
|
1804
1963
|
}
|
|
1805
|
-
|
|
1964
|
+
setImmediate(callback.bind(null, null, true));
|
|
1806
1965
|
}
|
|
1807
1966
|
|
|
1808
1967
|
protected makeServerNonce(): Nonce {
|
|
@@ -1870,17 +2029,14 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
1870
2029
|
if (channel.securityMode !== MessageSecurityMode.None) {
|
|
1871
2030
|
errorLog(
|
|
1872
2031
|
chalk.red("SERVER with secure connection: Missing or invalid client Nonce "),
|
|
1873
|
-
request.clientNonce
|
|
2032
|
+
request.clientNonce?.toString("hex")
|
|
1874
2033
|
);
|
|
1875
2034
|
|
|
1876
2035
|
return rejectConnection(this, StatusCodes.BadNonceInvalid);
|
|
1877
2036
|
}
|
|
1878
2037
|
}
|
|
1879
2038
|
if (nonceAlreadyBeenUsed(request.clientNonce)) {
|
|
1880
|
-
errorLog(
|
|
1881
|
-
chalk.red("SERVER with secure connection: Nonce has already been used"),
|
|
1882
|
-
request.clientNonce && request.clientNonce.toString("hex")
|
|
1883
|
-
);
|
|
2039
|
+
errorLog(chalk.red("SERVER with secure connection: Nonce has already been used"), request.clientNonce?.toString("hex"));
|
|
1884
2040
|
|
|
1885
2041
|
return rejectConnection(this, StatusCodes.BadNonceInvalid);
|
|
1886
2042
|
}
|
|
@@ -1932,7 +2088,7 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
1932
2088
|
|
|
1933
2089
|
const hasEncryption = true;
|
|
1934
2090
|
// If the securityPolicyUri is None and none of the UserTokenPolicies requires encryption
|
|
1935
|
-
if (session.channel
|
|
2091
|
+
if (session.channel?.securityMode === MessageSecurityMode.None) {
|
|
1936
2092
|
// ToDo: Check that none of our insecure endpoint has a a UserTokenPolicy that require encryption
|
|
1937
2093
|
// and set hasEncryption = false under this condition
|
|
1938
2094
|
}
|
|
@@ -1969,7 +2125,7 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
1969
2125
|
// securityPolicyUri, userIdentityTokens, transportProfileUri and securityLevel with all
|
|
1970
2126
|
// other parameters set to null. Only the recommended parameters shall be verified by
|
|
1971
2127
|
// the client.
|
|
1972
|
-
serverEndpoints: _serverEndpointsForCreateSessionResponse(this, session.endpoint
|
|
2128
|
+
serverEndpoints: _serverEndpointsForCreateSessionResponse(this, session.endpoint?.endpointUrl || "", request.serverUri),
|
|
1973
2129
|
|
|
1974
2130
|
// This parameter is deprecated and the array shall be empty.
|
|
1975
2131
|
serverSoftwareCertificates: null,
|
|
@@ -1995,7 +2151,7 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
1995
2151
|
assert(typeof reason === "string");
|
|
1996
2152
|
if (this.isAuditing) {
|
|
1997
2153
|
assert(reason === "Timeout" || reason === "Terminated" || reason === "CloseSession" || reason === "Forcing");
|
|
1998
|
-
const sourceName =
|
|
2154
|
+
const sourceName = `Session/${reason}`;
|
|
1999
2155
|
|
|
2000
2156
|
this.raiseEvent("AuditSessionEventType", {
|
|
2001
2157
|
/* part 5 - 6.4.3 AuditEventType */
|
|
@@ -2047,18 +2203,27 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
2047
2203
|
// identifier in all AuditEvents related to the Session Service Set (AuditCreateSessionEventType,
|
|
2048
2204
|
// AuditActivateSessionEventType and their subtypes) and the SecureChannel Service Set
|
|
2049
2205
|
// (AuditChannelEventType and its subtypes
|
|
2050
|
-
secureChannelId: {
|
|
2206
|
+
secureChannelId: {
|
|
2207
|
+
dataType: "String",
|
|
2208
|
+
value: session.channel?.channelId?.toString() ?? ""
|
|
2209
|
+
},
|
|
2051
2210
|
|
|
2052
2211
|
// Duration
|
|
2053
|
-
revisedSessionTimeout: {
|
|
2212
|
+
revisedSessionTimeout: {
|
|
2213
|
+
dataType: "Duration",
|
|
2214
|
+
value: session.sessionTimeout
|
|
2215
|
+
},
|
|
2054
2216
|
|
|
2055
2217
|
// clientCertificate
|
|
2056
|
-
clientCertificate: {
|
|
2218
|
+
clientCertificate: {
|
|
2219
|
+
dataType: "ByteString",
|
|
2220
|
+
value: session.channel?.clientCertificate ?? null
|
|
2221
|
+
},
|
|
2057
2222
|
|
|
2058
2223
|
// clientCertificateThumbprint
|
|
2059
2224
|
clientCertificateThumbprint: {
|
|
2060
2225
|
dataType: "String",
|
|
2061
|
-
value: thumbprint(session.channel
|
|
2226
|
+
value: thumbprint(session.channel?.clientCertificate)
|
|
2062
2227
|
}
|
|
2063
2228
|
});
|
|
2064
2229
|
}
|
|
@@ -2101,20 +2266,21 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
2101
2266
|
server.engine.incrementSecurityRejectedSessionCount();
|
|
2102
2267
|
}
|
|
2103
2268
|
|
|
2104
|
-
const response1 = new ActivateSessionResponse({
|
|
2269
|
+
const response1 = new ActivateSessionResponse({
|
|
2270
|
+
responseHeader: { serviceResult: statusCode }
|
|
2271
|
+
});
|
|
2105
2272
|
|
|
2106
2273
|
channel.send_response("MSG", response1, message);
|
|
2107
2274
|
}
|
|
2108
2275
|
|
|
2109
|
-
let response;
|
|
2110
|
-
|
|
2111
2276
|
/* c8 ignore next */
|
|
2112
2277
|
if (!session) {
|
|
2113
2278
|
// this may happen when the server has been restarted and a client tries to reconnect, thinking
|
|
2114
2279
|
// that the previous session may still be active
|
|
2115
2280
|
debugLog(chalk.yellow.bold(" Bad Session in _on_ActivateSessionRequest"), authenticationToken.toString());
|
|
2116
2281
|
|
|
2117
|
-
|
|
2282
|
+
rejectConnection(this, StatusCodes.BadSessionIdInvalid);
|
|
2283
|
+
return;
|
|
2118
2284
|
}
|
|
2119
2285
|
|
|
2120
2286
|
// tslint:disable-next-line: no-unused-expression
|
|
@@ -2128,8 +2294,9 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
2128
2294
|
if (!channel_has_session(channel, session)) {
|
|
2129
2295
|
// it looks like session activation is being using a channel that is not the
|
|
2130
2296
|
// one that have been used to create the session
|
|
2131
|
-
errorLog(
|
|
2132
|
-
|
|
2297
|
+
errorLog(` channel.sessionTokens === ${Object.keys(channel.sessionTokens).join(" ")}`);
|
|
2298
|
+
rejectConnection(this, StatusCodes.BadSessionNotActivated);
|
|
2299
|
+
return;
|
|
2133
2300
|
}
|
|
2134
2301
|
}
|
|
2135
2302
|
|
|
@@ -2139,47 +2306,58 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
2139
2306
|
// SecureChannel is the same as the Certificate used to create the original SecureChannel.
|
|
2140
2307
|
|
|
2141
2308
|
if (session.status === "active") {
|
|
2142
|
-
if (session.channel
|
|
2309
|
+
if (session.channel?.channelId !== channel.channelId) {
|
|
2143
2310
|
warningLog(
|
|
2144
2311
|
" Session ",
|
|
2145
2312
|
session.sessionName,
|
|
2146
2313
|
" is being transferred from channel",
|
|
2147
|
-
chalk.cyan(session.channel
|
|
2314
|
+
chalk.cyan(session.channel?.channelId?.toString()),
|
|
2148
2315
|
" to channel ",
|
|
2149
|
-
chalk.cyan(channel.channelId
|
|
2316
|
+
chalk.cyan(channel.channelId?.toString())
|
|
2150
2317
|
);
|
|
2151
2318
|
|
|
2152
2319
|
// session is being reassigned to a new Channel,
|
|
2153
2320
|
// we shall verify that the certificate used to create the Session is the same as the current
|
|
2154
2321
|
// channel certificate.
|
|
2155
|
-
const old_channel_cert_thumbprint = thumbprint(session.channel
|
|
2156
|
-
const new_channel_cert_thumbprint = thumbprint(channel.clientCertificate
|
|
2322
|
+
const old_channel_cert_thumbprint = thumbprint(session.channel?.clientCertificate);
|
|
2323
|
+
const new_channel_cert_thumbprint = thumbprint(channel.clientCertificate);
|
|
2157
2324
|
|
|
2158
2325
|
if (old_channel_cert_thumbprint !== new_channel_cert_thumbprint) {
|
|
2159
|
-
|
|
2326
|
+
rejectConnection(this, StatusCodes.BadNoValidCertificates); // not sure about this code !
|
|
2327
|
+
return;
|
|
2160
2328
|
}
|
|
2161
2329
|
|
|
2162
2330
|
// ... In addition the Server shall verify that the Client supplied a UserIdentityToken that is
|
|
2163
2331
|
// identical to the token currently associated with the Session reassign session to new channel.
|
|
2164
|
-
if (!sameIdentityToken(session.userIdentityToken
|
|
2165
|
-
|
|
2332
|
+
if (!sameIdentityToken(session.userIdentityToken, request.userIdentityToken as UserIdentityToken)) {
|
|
2333
|
+
rejectConnection(this, StatusCodes.BadIdentityChangeNotSupported); // not sure about this code !
|
|
2334
|
+
return;
|
|
2166
2335
|
}
|
|
2167
2336
|
}
|
|
2168
2337
|
} else if (session.status === "screwed") {
|
|
2169
2338
|
// session has been used before being activated => this should be detected and session should be dismissed.
|
|
2170
|
-
|
|
2339
|
+
rejectConnection(this, StatusCodes.BadSessionClosed);
|
|
2340
|
+
return;
|
|
2171
2341
|
} else if (session.status === "closed") {
|
|
2172
2342
|
warningLog(chalk.yellow.bold(" Bad Session Closed in _on_ActivateSessionRequest"), authenticationToken.toString());
|
|
2173
|
-
|
|
2343
|
+
rejectConnection(this, StatusCodes.BadSessionClosed);
|
|
2344
|
+
return;
|
|
2174
2345
|
}
|
|
2175
2346
|
|
|
2176
2347
|
// verify clientSignature provided by the client
|
|
2177
2348
|
if (!this.verifyClientSignature(session, channel, request.clientSignature)) {
|
|
2178
|
-
|
|
2349
|
+
rejectConnection(this, StatusCodes.BadApplicationSignatureInvalid);
|
|
2350
|
+
return;
|
|
2351
|
+
}
|
|
2352
|
+
|
|
2353
|
+
const endpoint = session.endpoint;
|
|
2354
|
+
if (!endpoint) {
|
|
2355
|
+
rejectConnection(this, StatusCodes.BadSessionIdInvalid);
|
|
2356
|
+
return;
|
|
2179
2357
|
}
|
|
2180
2358
|
|
|
2181
2359
|
// userIdentityToken may be missing , assume anonymous access then
|
|
2182
|
-
request.userIdentityToken = request.userIdentityToken || createAnonymousIdentityToken(
|
|
2360
|
+
request.userIdentityToken = request.userIdentityToken || createAnonymousIdentityToken(endpoint);
|
|
2183
2361
|
|
|
2184
2362
|
// check request.userIdentityToken is correct ( expected type and correctly formed)
|
|
2185
2363
|
this.isValidUserIdentityToken(
|
|
@@ -2187,8 +2365,8 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
2187
2365
|
session,
|
|
2188
2366
|
request.userIdentityToken as UserIdentityToken,
|
|
2189
2367
|
request.userTokenSignature,
|
|
2190
|
-
|
|
2191
|
-
(
|
|
2368
|
+
endpoint,
|
|
2369
|
+
(_err: Error | null, statusCode?: StatusCode) => {
|
|
2192
2370
|
if (!statusCode || statusCode.isNotGood()) {
|
|
2193
2371
|
/* c8 ignore next */
|
|
2194
2372
|
if (!(statusCode && statusCode instanceof StatusCode)) {
|
|
@@ -2223,7 +2401,9 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
2223
2401
|
|
|
2224
2402
|
session.status = "active";
|
|
2225
2403
|
|
|
2226
|
-
response = new ActivateSessionResponse({
|
|
2404
|
+
const response = new ActivateSessionResponse({
|
|
2405
|
+
serverNonce: session.nonce
|
|
2406
|
+
});
|
|
2227
2407
|
channel.send_response("MSG", response, message);
|
|
2228
2408
|
|
|
2229
2409
|
// send OPCUA Event Notification
|
|
@@ -2267,7 +2447,7 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
2267
2447
|
if (request.constructor.name !== "PublishRequest") {
|
|
2268
2448
|
errorLog(
|
|
2269
2449
|
chalk.red.bgWhite(
|
|
2270
|
-
|
|
2450
|
+
`ERROR: channel.channelId !== session.channelId on processing request ${request.constructor.name}`
|
|
2271
2451
|
),
|
|
2272
2452
|
channel.channelId,
|
|
2273
2453
|
session.channelId
|
|
@@ -2342,12 +2522,14 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
2342
2522
|
return g_sendError(channel, message, ResponseClass, statusCode);
|
|
2343
2523
|
}
|
|
2344
2524
|
|
|
2345
|
-
let response: any;
|
|
2346
2525
|
/* c8 ignore next */
|
|
2347
2526
|
if (!message.session || message.session_statusCode !== StatusCodes.Good) {
|
|
2348
|
-
const
|
|
2349
|
-
|
|
2350
|
-
|
|
2527
|
+
const sessionStatusCode = message.session_statusCode ?? StatusCodes.BadInternalError;
|
|
2528
|
+
const errMessage = `=>${sessionStatusCode.toString()}`;
|
|
2529
|
+
const response = new ServiceFault({
|
|
2530
|
+
responseHeader: { serviceResult: sessionStatusCode }
|
|
2531
|
+
});
|
|
2532
|
+
debugLog(chalk.red.bold(errMessage), chalk.yellow(sessionStatusCode.toString()), response.constructor.name);
|
|
2351
2533
|
return sendResponse(response);
|
|
2352
2534
|
}
|
|
2353
2535
|
|
|
@@ -2403,7 +2585,7 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
2403
2585
|
): Promise<void> {
|
|
2404
2586
|
assert(typeof actionToPerform === "function");
|
|
2405
2587
|
const request = message.request as unknown as { subscriptionId: number };
|
|
2406
|
-
assert(Object.
|
|
2588
|
+
assert(Object.hasOwn(request, "subscriptionId"));
|
|
2407
2589
|
|
|
2408
2590
|
this._apply_on_SessionObject(
|
|
2409
2591
|
ResponseClass,
|
|
@@ -2432,7 +2614,7 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
2432
2614
|
): void {
|
|
2433
2615
|
assert(typeof actionToPerform === "function");
|
|
2434
2616
|
const request = message.request as unknown as { subscriptionIds: number[] };
|
|
2435
|
-
assert(Object.
|
|
2617
|
+
assert(Object.hasOwn(request, "subscriptionIds"));
|
|
2436
2618
|
|
|
2437
2619
|
this._apply_on_SessionObject(
|
|
2438
2620
|
ResponseClass,
|
|
@@ -2470,7 +2652,7 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
2470
2652
|
responseHeader: {
|
|
2471
2653
|
serviceResult
|
|
2472
2654
|
},
|
|
2473
|
-
results: results as
|
|
2655
|
+
results: results as unknown as StatusCode[]
|
|
2474
2656
|
});
|
|
2475
2657
|
sendResponse(response);
|
|
2476
2658
|
}
|
|
@@ -2507,7 +2689,10 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
2507
2689
|
if (session) {
|
|
2508
2690
|
const subscriptions = session.publishEngine.subscriptions;
|
|
2509
2691
|
for (const subscription of subscriptions) {
|
|
2510
|
-
|
|
2692
|
+
const onDeleteFn = this.options.onDeleteMonitoredItem;
|
|
2693
|
+
await subscription.applyOnMonitoredItem(async (monitoredItem: MonitoredItem) => {
|
|
2694
|
+
await onDeleteFn(subscription, monitoredItem);
|
|
2695
|
+
});
|
|
2511
2696
|
}
|
|
2512
2697
|
}
|
|
2513
2698
|
}
|
|
@@ -2522,8 +2707,6 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
2522
2707
|
const request = message.request as CloseSessionRequest;
|
|
2523
2708
|
assert(request instanceof CloseSessionRequest);
|
|
2524
2709
|
|
|
2525
|
-
let response;
|
|
2526
|
-
|
|
2527
2710
|
message.session_statusCode = StatusCodes.Good;
|
|
2528
2711
|
|
|
2529
2712
|
function sendError(statusCode: StatusCode) {
|
|
@@ -2540,11 +2723,12 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
2540
2723
|
|
|
2541
2724
|
const session = message.session;
|
|
2542
2725
|
if (!session) {
|
|
2543
|
-
|
|
2726
|
+
sendError(StatusCodes.BadSessionIdInvalid);
|
|
2727
|
+
return;
|
|
2544
2728
|
}
|
|
2545
2729
|
|
|
2546
2730
|
// session has been created but not activated !
|
|
2547
|
-
const
|
|
2731
|
+
const _wasNotActivated = session.status === "new";
|
|
2548
2732
|
|
|
2549
2733
|
(async () => {
|
|
2550
2734
|
try {
|
|
@@ -2554,9 +2738,9 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
2554
2738
|
// return sendError(StatusCodes.BadSessionNotActivated);
|
|
2555
2739
|
// }
|
|
2556
2740
|
|
|
2557
|
-
response = new CloseSessionResponse({});
|
|
2741
|
+
const response = new CloseSessionResponse({});
|
|
2558
2742
|
sendResponse(response);
|
|
2559
|
-
} catch (
|
|
2743
|
+
} catch (_err) {
|
|
2560
2744
|
sendError(StatusCodes.BadInternalError);
|
|
2561
2745
|
}
|
|
2562
2746
|
})();
|
|
@@ -2571,7 +2755,7 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
2571
2755
|
protected _on_BrowseRequest(message: Message, channel: ServerSecureChannelLayer): void {
|
|
2572
2756
|
const request = message.request as BrowseRequest;
|
|
2573
2757
|
assert(request instanceof BrowseRequest);
|
|
2574
|
-
const diagnostic:
|
|
2758
|
+
const diagnostic: Record<string, unknown> = {};
|
|
2575
2759
|
|
|
2576
2760
|
this._apply_on_SessionObject(
|
|
2577
2761
|
BrowseResponse,
|
|
@@ -2581,7 +2765,11 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
2581
2765
|
let response: BrowseResponse;
|
|
2582
2766
|
// test view
|
|
2583
2767
|
if (request.view && !request.view.viewId.isEmpty()) {
|
|
2584
|
-
|
|
2768
|
+
const addressSpace = this.engine.addressSpace;
|
|
2769
|
+
if (!addressSpace) {
|
|
2770
|
+
return sendError(StatusCodes.BadInternalError);
|
|
2771
|
+
}
|
|
2772
|
+
let theView: UAView | null = addressSpace.findNode(request.view.viewId) as UAView;
|
|
2585
2773
|
if (theView && theView.nodeClass !== NodeClass.View) {
|
|
2586
2774
|
// Error: theView is not a View
|
|
2587
2775
|
diagnostic.localizedText = { text: "Expecting a view here" };
|
|
@@ -2607,9 +2795,9 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
2607
2795
|
assert(request.nodesToBrowse[0].schema.name === "BrowseDescription");
|
|
2608
2796
|
|
|
2609
2797
|
const context = session.sessionContext;
|
|
2610
|
-
const browseAll = (
|
|
2798
|
+
const browseAll = (_nodesToBrowse: BrowseDescriptionOptions[], callack: CallbackT<BrowseResult[]>) => {
|
|
2611
2799
|
const f = callbackify(this.engine.browseWithAutomaticExpansion).bind(this.engine);
|
|
2612
|
-
|
|
2800
|
+
f(request.nodesToBrowse ?? [], context, callack);
|
|
2613
2801
|
};
|
|
2614
2802
|
// handle continuation point and requestedMaxReferencesPerNode
|
|
2615
2803
|
const maxBrowseContinuationPoints = this.engine.serverCapabilities.maxBrowseContinuationPoints;
|
|
@@ -2623,7 +2811,7 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
2623
2811
|
maxBrowseContinuationPoints
|
|
2624
2812
|
},
|
|
2625
2813
|
request.nodesToBrowse,
|
|
2626
|
-
(
|
|
2814
|
+
(_err, results) => {
|
|
2627
2815
|
if (!results) {
|
|
2628
2816
|
return sendError(StatusCodes.BadInternalError);
|
|
2629
2817
|
}
|
|
@@ -2716,10 +2904,10 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
2716
2904
|
}
|
|
2717
2905
|
|
|
2718
2906
|
// ask for a refresh of asynchronous variables
|
|
2719
|
-
this.engine.refreshValues(request.nodesToRead, request.maxAge, (
|
|
2907
|
+
this.engine.refreshValues(request.nodesToRead, request.maxAge, (_err?: Error | null) => {
|
|
2720
2908
|
this.engine.read(context, request).then((results) => {
|
|
2721
2909
|
assert(results[0].schema.name === "DataValue");
|
|
2722
|
-
assert(results.length === request.nodesToRead
|
|
2910
|
+
assert(results.length === request.nodesToRead?.length);
|
|
2723
2911
|
|
|
2724
2912
|
const response = new ReadResponse({
|
|
2725
2913
|
diagnosticInfos: undefined,
|
|
@@ -2727,7 +2915,7 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
2727
2915
|
});
|
|
2728
2916
|
// set it here for performance
|
|
2729
2917
|
response.results = results;
|
|
2730
|
-
assert(response.diagnosticInfos
|
|
2918
|
+
assert(response.diagnosticInfos?.length === 0);
|
|
2731
2919
|
sendResponse(response);
|
|
2732
2920
|
});
|
|
2733
2921
|
});
|
|
@@ -2746,8 +2934,6 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
2746
2934
|
message,
|
|
2747
2935
|
channel,
|
|
2748
2936
|
(session: ServerSession, sendResponse: (response: Response) => void, sendError: (statsCode: StatusCode) => void) => {
|
|
2749
|
-
let response;
|
|
2750
|
-
|
|
2751
2937
|
const timestampsToReturn = request.timestampsToReturn;
|
|
2752
2938
|
|
|
2753
2939
|
if (timestampsToReturn === TimestampsToReturn.Invalid) {
|
|
@@ -2790,17 +2976,17 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
2790
2976
|
.historyRead(context, request)
|
|
2791
2977
|
.then((results: HistoryReadResult[]) => {
|
|
2792
2978
|
assert(results[0].schema.name === "HistoryReadResult");
|
|
2793
|
-
assert(results.length === request.nodesToRead
|
|
2979
|
+
assert(results.length === request.nodesToRead?.length);
|
|
2794
2980
|
|
|
2795
|
-
response = new HistoryReadResponse({
|
|
2981
|
+
const response = new HistoryReadResponse({
|
|
2796
2982
|
diagnosticInfos: undefined,
|
|
2797
2983
|
results
|
|
2798
2984
|
});
|
|
2799
2985
|
|
|
2800
|
-
assert(response.diagnosticInfos
|
|
2986
|
+
assert(response.diagnosticInfos?.length === 0);
|
|
2801
2987
|
sendResponse(response);
|
|
2802
2988
|
})
|
|
2803
|
-
.catch((
|
|
2989
|
+
.catch((_err) => {
|
|
2804
2990
|
return sendError(StatusCodes.BadHistoryOperationInvalid);
|
|
2805
2991
|
});
|
|
2806
2992
|
});
|
|
@@ -2832,8 +3018,6 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
2832
3018
|
message,
|
|
2833
3019
|
channel,
|
|
2834
3020
|
(session: ServerSession, sendResponse: (response: Response) => void, sendError: (statusCode: StatusCode) => void) => {
|
|
2835
|
-
let response;
|
|
2836
|
-
|
|
2837
3021
|
if (!request.nodesToWrite || request.nodesToWrite.length === 0) {
|
|
2838
3022
|
return sendError(StatusCodes.BadNothingToDo);
|
|
2839
3023
|
}
|
|
@@ -2856,8 +3040,8 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
2856
3040
|
this.engine
|
|
2857
3041
|
.write(context, request.nodesToWrite)
|
|
2858
3042
|
.then((results: StatusCode[]) => {
|
|
2859
|
-
assert(results
|
|
2860
|
-
response = new WriteResponse({
|
|
3043
|
+
assert(results?.length === request.nodesToWrite?.length);
|
|
3044
|
+
const response = new WriteResponse({
|
|
2861
3045
|
diagnosticInfos: undefined,
|
|
2862
3046
|
results
|
|
2863
3047
|
});
|
|
@@ -2874,8 +3058,11 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
2874
3058
|
// subscription services
|
|
2875
3059
|
protected _on_CreateSubscriptionRequest(message: Message, channel: ServerSecureChannelLayer): void {
|
|
2876
3060
|
const engine = this.engine;
|
|
2877
|
-
const addressSpace = engine.addressSpace
|
|
2878
|
-
|
|
3061
|
+
const addressSpace = engine.addressSpace;
|
|
3062
|
+
if (!addressSpace) {
|
|
3063
|
+
g_sendError(channel, message, CreateSubscriptionResponse, StatusCodes.BadSessionClosed);
|
|
3064
|
+
return;
|
|
3065
|
+
}
|
|
2879
3066
|
const request = message.request as CreateSubscriptionRequest;
|
|
2880
3067
|
assert(request instanceof CreateSubscriptionRequest);
|
|
2881
3068
|
|
|
@@ -2965,7 +3152,11 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
2965
3152
|
|
|
2966
3153
|
protected _on_CreateMonitoredItemsRequest(message: Message, channel: ServerSecureChannelLayer): void {
|
|
2967
3154
|
const engine = this.engine;
|
|
2968
|
-
const addressSpace = engine.addressSpace
|
|
3155
|
+
const addressSpace = engine.addressSpace;
|
|
3156
|
+
if (!addressSpace) {
|
|
3157
|
+
g_sendError(channel, message, CreateMonitoredItemsResponse, StatusCodes.BadSessionClosed);
|
|
3158
|
+
return;
|
|
3159
|
+
}
|
|
2969
3160
|
|
|
2970
3161
|
const request = message.request as CreateMonitoredItemsRequest;
|
|
2971
3162
|
assert(request instanceof CreateMonitoredItemsRequest);
|
|
@@ -2975,7 +3166,7 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
2975
3166
|
message,
|
|
2976
3167
|
channel,
|
|
2977
3168
|
async (
|
|
2978
|
-
|
|
3169
|
+
_session: ServerSession,
|
|
2979
3170
|
subscription: Subscription,
|
|
2980
3171
|
sendResponse: (response: Response) => void,
|
|
2981
3172
|
sendError: (statusCode: StatusCode) => void
|
|
@@ -3004,7 +3195,9 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
3004
3195
|
monitoredItemCreateRequest
|
|
3005
3196
|
);
|
|
3006
3197
|
if (monitoredItem) {
|
|
3007
|
-
|
|
3198
|
+
if (options.onCreateMonitoredItem) {
|
|
3199
|
+
await options.onCreateMonitoredItem(subscription, monitoredItem);
|
|
3200
|
+
}
|
|
3008
3201
|
subscription.postCreateMonitoredItem(monitoredItem, monitoredItemCreateRequest, createResult);
|
|
3009
3202
|
}
|
|
3010
3203
|
return createResult;
|
|
@@ -3042,10 +3235,10 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
3042
3235
|
message,
|
|
3043
3236
|
channel,
|
|
3044
3237
|
async (
|
|
3045
|
-
|
|
3238
|
+
_session: ServerSession,
|
|
3046
3239
|
subscription: Subscription,
|
|
3047
3240
|
sendResponse: (response: ModifySubscriptionResponse) => void,
|
|
3048
|
-
|
|
3241
|
+
_sendError: (statusCode: StatusCode) => void
|
|
3049
3242
|
) => {
|
|
3050
3243
|
subscription.modify(request);
|
|
3051
3244
|
|
|
@@ -3069,7 +3262,7 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
3069
3262
|
message,
|
|
3070
3263
|
channel,
|
|
3071
3264
|
async (
|
|
3072
|
-
|
|
3265
|
+
_session: ServerSession,
|
|
3073
3266
|
subscription: Subscription,
|
|
3074
3267
|
sendResponse: (response: ModifyMonitoredItemsResponse) => void,
|
|
3075
3268
|
sendError: (statusCode: StatusCode) => void
|
|
@@ -3096,7 +3289,9 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
3096
3289
|
const monitoredItemId = item.monitoredItemId;
|
|
3097
3290
|
const monitoredItem = subscription.getMonitoredItem(monitoredItemId);
|
|
3098
3291
|
if (!monitoredItem) {
|
|
3099
|
-
return new MonitoredItemModifyResult({
|
|
3292
|
+
return new MonitoredItemModifyResult({
|
|
3293
|
+
statusCode: StatusCodes.BadMonitoredItemIdInvalid
|
|
3294
|
+
});
|
|
3100
3295
|
}
|
|
3101
3296
|
|
|
3102
3297
|
// adjust samplingInterval if === -1
|
|
@@ -3124,7 +3319,7 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
3124
3319
|
PublishResponse,
|
|
3125
3320
|
message,
|
|
3126
3321
|
channel,
|
|
3127
|
-
(session: ServerSession, sendResponse: (response: Response) => void,
|
|
3322
|
+
(session: ServerSession, sendResponse: (response: Response) => void, _sendError: (statusCode: StatusCode) => void) => {
|
|
3128
3323
|
assert(session);
|
|
3129
3324
|
assert(session.publishEngine); // server.publishEngine doesn't exists, OPCUAServer has probably shut down already
|
|
3130
3325
|
session.publishEngine._on_PublishRequest(request, (_request1, response) => {
|
|
@@ -3142,7 +3337,7 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
3142
3337
|
SetPublishingModeResponse,
|
|
3143
3338
|
message,
|
|
3144
3339
|
channel,
|
|
3145
|
-
async (
|
|
3340
|
+
async (_session: ServerSession, subscription: Subscription) => {
|
|
3146
3341
|
return subscription.setPublishingMode(publishingEnabled);
|
|
3147
3342
|
}
|
|
3148
3343
|
);
|
|
@@ -3157,7 +3352,7 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
3157
3352
|
message,
|
|
3158
3353
|
channel,
|
|
3159
3354
|
async (
|
|
3160
|
-
|
|
3355
|
+
_session: ServerSession,
|
|
3161
3356
|
subscription: Subscription,
|
|
3162
3357
|
sendResponse: (response: Response) => void,
|
|
3163
3358
|
sendError: (statusCode: StatusCode) => void
|
|
@@ -3209,7 +3404,7 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
3209
3404
|
message,
|
|
3210
3405
|
channel,
|
|
3211
3406
|
async (
|
|
3212
|
-
|
|
3407
|
+
_session: ServerSession,
|
|
3213
3408
|
subscription: Subscription,
|
|
3214
3409
|
sendResponse: (response: Response) => void,
|
|
3215
3410
|
sendError: (statusCode: StatusCode) => void
|
|
@@ -3237,7 +3432,9 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
3237
3432
|
linksToRemove
|
|
3238
3433
|
);
|
|
3239
3434
|
if (statusCode.isNotGood()) {
|
|
3240
|
-
const response = new ServiceFault({
|
|
3435
|
+
const response = new ServiceFault({
|
|
3436
|
+
responseHeader: { serviceResult: statusCode }
|
|
3437
|
+
});
|
|
3241
3438
|
sendResponse(response);
|
|
3242
3439
|
} else {
|
|
3243
3440
|
const response = new SetTriggeringResponse({
|
|
@@ -3258,7 +3455,11 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
3258
3455
|
if (!this.options.onDeleteMonitoredItem) {
|
|
3259
3456
|
return;
|
|
3260
3457
|
}
|
|
3261
|
-
|
|
3458
|
+
const t = this.options.onDeleteMonitoredItem.bind(null, subscription);
|
|
3459
|
+
const functor = async (monitoredItem: MonitoredItem) => {
|
|
3460
|
+
await t(monitoredItem);
|
|
3461
|
+
};
|
|
3462
|
+
await subscription.applyOnMonitoredItem(functor);
|
|
3262
3463
|
}
|
|
3263
3464
|
protected _on_RepublishRequest(message: Message, channel: ServerSecureChannelLayer): void {
|
|
3264
3465
|
const request = message.request as RepublishRequest;
|
|
@@ -3269,7 +3470,7 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
3269
3470
|
message,
|
|
3270
3471
|
channel,
|
|
3271
3472
|
async (
|
|
3272
|
-
|
|
3473
|
+
_session: ServerSession,
|
|
3273
3474
|
subscription: Subscription,
|
|
3274
3475
|
sendResponse: (response: Response) => void,
|
|
3275
3476
|
sendError: (statusCode: StatusCode) => void
|
|
@@ -3311,7 +3512,7 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
3311
3512
|
message,
|
|
3312
3513
|
channel,
|
|
3313
3514
|
async (
|
|
3314
|
-
|
|
3515
|
+
_session: ServerSession,
|
|
3315
3516
|
subscription: Subscription,
|
|
3316
3517
|
sendResponse: (response: Response) => void,
|
|
3317
3518
|
sendError: (statusCode: StatusCode) => void
|
|
@@ -3360,7 +3561,7 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
3360
3561
|
message,
|
|
3361
3562
|
channel,
|
|
3362
3563
|
async (
|
|
3363
|
-
|
|
3564
|
+
_session: ServerSession,
|
|
3364
3565
|
sendResponse: (response: Response) => void,
|
|
3365
3566
|
sendError: (statusCode: StatusCode) => void
|
|
3366
3567
|
) => {
|
|
@@ -3385,7 +3586,7 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
3385
3586
|
});
|
|
3386
3587
|
sendResponse(response);
|
|
3387
3588
|
})
|
|
3388
|
-
.catch((
|
|
3589
|
+
.catch((_err) => {
|
|
3389
3590
|
sendError(StatusCodes.BadInternalError);
|
|
3390
3591
|
});
|
|
3391
3592
|
}
|
|
@@ -3406,8 +3607,6 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
3406
3607
|
message,
|
|
3407
3608
|
channel,
|
|
3408
3609
|
(session: ServerSession, sendResponse: (response: Response) => void, sendError: (statusCode: StatusCode) => void) => {
|
|
3409
|
-
let response;
|
|
3410
|
-
|
|
3411
3610
|
if (!request.methodsToCall || request.methodsToCall.length === 0) {
|
|
3412
3611
|
return sendError(StatusCodes.BadNothingToDo);
|
|
3413
3612
|
}
|
|
@@ -3428,7 +3627,7 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
3428
3627
|
filterDiagnosticInfo(request.requestHeader.returnDiagnostics, response);
|
|
3429
3628
|
sendResponse(response);
|
|
3430
3629
|
})
|
|
3431
|
-
.catch((
|
|
3630
|
+
.catch((_err) => {
|
|
3432
3631
|
sendError(StatusCodes.BadInternalError);
|
|
3433
3632
|
});
|
|
3434
3633
|
}
|
|
@@ -3502,7 +3701,7 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
3502
3701
|
|
|
3503
3702
|
/* c8 ignore next */
|
|
3504
3703
|
protected _on_Cancel(message: Message, channel: ServerSecureChannelLayer): void {
|
|
3505
|
-
|
|
3704
|
+
g_sendError(channel, message, CancelResponse, StatusCodes.BadServiceUnsupported);
|
|
3506
3705
|
}
|
|
3507
3706
|
|
|
3508
3707
|
// NodeManagement Service Set Overview
|
|
@@ -3511,38 +3710,38 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
3511
3710
|
//
|
|
3512
3711
|
/* c8 ignore next */
|
|
3513
3712
|
protected _on_AddNodes(message: Message, channel: ServerSecureChannelLayer): void {
|
|
3514
|
-
|
|
3713
|
+
g_sendError(channel, message, AddNodesResponse, StatusCodes.BadServiceUnsupported);
|
|
3515
3714
|
}
|
|
3516
3715
|
|
|
3517
3716
|
/* c8 ignore next */
|
|
3518
3717
|
protected _on_AddReferences(message: Message, channel: ServerSecureChannelLayer): void {
|
|
3519
|
-
|
|
3718
|
+
g_sendError(channel, message, AddReferencesResponse, StatusCodes.BadServiceUnsupported);
|
|
3520
3719
|
}
|
|
3521
3720
|
|
|
3522
3721
|
/* c8 ignore next */
|
|
3523
3722
|
protected _on_DeleteNodes(message: Message, channel: ServerSecureChannelLayer): void {
|
|
3524
|
-
|
|
3723
|
+
g_sendError(channel, message, DeleteNodesResponse, StatusCodes.BadServiceUnsupported);
|
|
3525
3724
|
}
|
|
3526
3725
|
|
|
3527
3726
|
/* c8 ignore next */
|
|
3528
3727
|
protected _on_DeleteReferences(message: Message, channel: ServerSecureChannelLayer): void {
|
|
3529
|
-
|
|
3728
|
+
g_sendError(channel, message, DeleteReferencesResponse, StatusCodes.BadServiceUnsupported);
|
|
3530
3729
|
}
|
|
3531
3730
|
|
|
3532
3731
|
// Query Service
|
|
3533
3732
|
/* c8 ignore next */
|
|
3534
3733
|
protected _on_QueryFirst(message: Message, channel: ServerSecureChannelLayer): void {
|
|
3535
|
-
|
|
3734
|
+
g_sendError(channel, message, QueryFirstResponse, StatusCodes.BadServiceUnsupported);
|
|
3536
3735
|
}
|
|
3537
3736
|
|
|
3538
3737
|
/* c8 ignore next */
|
|
3539
3738
|
protected _on_QueryNext(message: Message, channel: ServerSecureChannelLayer): void {
|
|
3540
|
-
|
|
3739
|
+
g_sendError(channel, message, QueryNextResponse, StatusCodes.BadServiceUnsupported);
|
|
3541
3740
|
}
|
|
3542
3741
|
|
|
3543
3742
|
/* c8 ignore next */
|
|
3544
3743
|
protected _on_HistoryUpdate(message: Message, channel: ServerSecureChannelLayer): void {
|
|
3545
|
-
|
|
3744
|
+
g_sendError(channel, message, HistoryUpdateResponse, StatusCodes.BadServiceUnsupported);
|
|
3546
3745
|
}
|
|
3547
3746
|
|
|
3548
3747
|
private createEndpoint(
|
|
@@ -3588,8 +3787,8 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
3588
3787
|
|
|
3589
3788
|
/* c8 ignore next */
|
|
3590
3789
|
if (
|
|
3591
|
-
!Object.
|
|
3592
|
-
!isFinite(endpointOptions.port
|
|
3790
|
+
!Object.hasOwn(endpointOptions, "port") ||
|
|
3791
|
+
!Number.isFinite(endpointOptions.port) ||
|
|
3593
3792
|
typeof endpointOptions.port !== "number"
|
|
3594
3793
|
) {
|
|
3595
3794
|
throw new Error(
|
|
@@ -3602,10 +3801,9 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
3602
3801
|
const endPoint = this.createEndpoint(port, serverOption);
|
|
3603
3802
|
|
|
3604
3803
|
endpointOptions.alternateHostname = endpointOptions.alternateHostname || [];
|
|
3605
|
-
const alternateHostname =
|
|
3606
|
-
endpointOptions.alternateHostname
|
|
3607
|
-
|
|
3608
|
-
: [endpointOptions.alternateHostname];
|
|
3804
|
+
const alternateHostname = Array.isArray(endpointOptions.alternateHostname)
|
|
3805
|
+
? endpointOptions.alternateHostname
|
|
3806
|
+
: [endpointOptions.alternateHostname];
|
|
3609
3807
|
const allowAnonymous = endpointOptions.allowAnonymous === undefined ? true : !!endpointOptions.allowAnonymous;
|
|
3610
3808
|
|
|
3611
3809
|
endPoint.addStandardEndpointDescriptions({
|
|
@@ -3619,7 +3817,9 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
3619
3817
|
|
|
3620
3818
|
disableDiscovery: !!endpointOptions.disableDiscovery,
|
|
3621
3819
|
// xx hostname,
|
|
3622
|
-
resourcePath: serverOption.resourcePath || ""
|
|
3820
|
+
resourcePath: serverOption.resourcePath || "",
|
|
3821
|
+
|
|
3822
|
+
advertisedEndpoints: endpointOptions.advertisedEndpoints,
|
|
3623
3823
|
|
|
3624
3824
|
// TODO userTokenTypes: endpointOptions.userTokenTypes || undefined,
|
|
3625
3825
|
|
|
@@ -3632,6 +3832,90 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
3632
3832
|
await super.initializeCM();
|
|
3633
3833
|
await this.userCertificateManager.initialize();
|
|
3634
3834
|
}
|
|
3835
|
+
|
|
3836
|
+
public on(event: "create_session", eventHandler: (session: ServerSession) => void): this;
|
|
3837
|
+
|
|
3838
|
+
public on(event: "session_activated", eventHandler: (session: ServerSession) => void): this;
|
|
3839
|
+
|
|
3840
|
+
public on(event: "session_closed", eventHandler: (session: ServerSession, reason: string) => void): this;
|
|
3841
|
+
|
|
3842
|
+
public on(event: "post_initialize", eventHandler: () => void): this;
|
|
3843
|
+
|
|
3844
|
+
/**
|
|
3845
|
+
* emitted when the server is trying to registered the LDS
|
|
3846
|
+
* but when the connection to the lds has failed
|
|
3847
|
+
* serverRegistrationPending is sent when the backoff signal of the
|
|
3848
|
+
* connection process is raised
|
|
3849
|
+
* @event serverRegistrationPending
|
|
3850
|
+
*/
|
|
3851
|
+
public on(event: "serverRegistrationPending", eventHandler: () => void): this;
|
|
3852
|
+
|
|
3853
|
+
/**
|
|
3854
|
+
* event raised when server has been successfully registered on the local discovery server
|
|
3855
|
+
* @event serverRegistered
|
|
3856
|
+
*/
|
|
3857
|
+
public on(event: "serverRegistered", eventHandler: () => void): this;
|
|
3858
|
+
|
|
3859
|
+
/**
|
|
3860
|
+
* event raised when server registration has been successfully renewed on the local discovery server
|
|
3861
|
+
* @event serverRegistered
|
|
3862
|
+
*/
|
|
3863
|
+
public on(event: "serverRegistrationRenewed", eventHandler: () => void): this;
|
|
3864
|
+
|
|
3865
|
+
/**
|
|
3866
|
+
* event raised when server has been successfully unregistered from the local discovery server
|
|
3867
|
+
* @event serverUnregistered
|
|
3868
|
+
*/
|
|
3869
|
+
public on(event: "serverUnregistered", eventHandler: () => void): this;
|
|
3870
|
+
|
|
3871
|
+
/**
|
|
3872
|
+
* event raised after the server has raised an OPCUA event toward a client
|
|
3873
|
+
*/
|
|
3874
|
+
public on(event: "event", eventHandler: (eventData: unknown) => void): this;
|
|
3875
|
+
|
|
3876
|
+
/**
|
|
3877
|
+
* event raised when the server received a request from one of its connected client.
|
|
3878
|
+
* useful for trace purpose.
|
|
3879
|
+
*/
|
|
3880
|
+
public on(event: "request", eventHandler: (request: Request, channel: ServerSecureChannelLayer) => void): this;
|
|
3881
|
+
|
|
3882
|
+
/**
|
|
3883
|
+
* event raised when the server send an response to a request to one of its connected client.
|
|
3884
|
+
* useful for trace purpose.
|
|
3885
|
+
*/
|
|
3886
|
+
public on(event: "response", eventHandler: (request: Response, channel: ServerSecureChannelLayer) => void): this;
|
|
3887
|
+
|
|
3888
|
+
/**
|
|
3889
|
+
* event raised when a new secure channel is opened
|
|
3890
|
+
*/
|
|
3891
|
+
public on(event: "newChannel", eventHandler: (channel: ServerSecureChannelLayer, endpoint: OPCUAServerEndPoint) => void): this;
|
|
3892
|
+
|
|
3893
|
+
/**
|
|
3894
|
+
* event raised when a new secure channel is closed
|
|
3895
|
+
*/
|
|
3896
|
+
public on(
|
|
3897
|
+
event: "closeChannel",
|
|
3898
|
+
eventHandler: (channel: ServerSecureChannelLayer, endpoint: OPCUAServerEndPoint) => void
|
|
3899
|
+
): this;
|
|
3900
|
+
|
|
3901
|
+
/**
|
|
3902
|
+
* event raised when the server refused a tcp connection from a client. ( for instance because too any connections)
|
|
3903
|
+
*/
|
|
3904
|
+
public on(event: "connectionRefused", eventHandler: (socketData: ISocketData, endpoint: OPCUAServerEndPoint) => void): this;
|
|
3905
|
+
|
|
3906
|
+
/**
|
|
3907
|
+
* event raised when a OpenSecureChannel has failed, it could be a invalid certificate or malformed message
|
|
3908
|
+
*/
|
|
3909
|
+
public on(
|
|
3910
|
+
event: "openSecureChannelFailure",
|
|
3911
|
+
eventHandler: (socketData: ISocketData, channelData: IChannelData, endpoint: OPCUAServerEndPoint) => void
|
|
3912
|
+
): this;
|
|
3913
|
+
|
|
3914
|
+
public on(event: string, eventHandler: (...args: unknown[]) => void): this;
|
|
3915
|
+
// biome-ignore lint/suspicious/noExplicitAny: must match EventEmitter.on() signature
|
|
3916
|
+
public on(event: string, eventHandler: (...args: any[]) => void): this {
|
|
3917
|
+
return super.on(event, eventHandler);
|
|
3918
|
+
}
|
|
3635
3919
|
}
|
|
3636
3920
|
|
|
3637
3921
|
const userIdentityTokenPasswordRemoved = (userIdentityToken?: UserIdentityToken): UserIdentityToken => {
|
|
@@ -3688,7 +3972,10 @@ function raiseAuditActivateSessionEventType(this: OPCUAServer, session: ServerSe
|
|
|
3688
3972
|
// use the same identifier in all AuditEvents related to the Session Service Set
|
|
3689
3973
|
// (AuditCreateSessionEventType, AuditActivateSessionEventType and their subtypes) and
|
|
3690
3974
|
// the SecureChannel Service Set (AuditChannelEventType and its subtypes).
|
|
3691
|
-
secureChannelId: {
|
|
3975
|
+
secureChannelId: {
|
|
3976
|
+
dataType: "String",
|
|
3977
|
+
value: session.channel?.channelId?.toString() ?? ""
|
|
3978
|
+
}
|
|
3692
3979
|
});
|
|
3693
3980
|
}
|
|
3694
3981
|
}
|
|
@@ -3711,7 +3998,7 @@ export interface RaiseEventAuditEventData extends RaiseEventData {
|
|
|
3711
3998
|
|
|
3712
3999
|
export interface RaiseEventAuditUpdateMethodEventData extends RaiseEventAuditEventData {
|
|
3713
4000
|
methodId: PseudoVariantNodeId;
|
|
3714
|
-
inputArguments:
|
|
4001
|
+
inputArguments: PseudoVariant | Variant | UAEventType | undefined;
|
|
3715
4002
|
}
|
|
3716
4003
|
|
|
3717
4004
|
export interface RaiseEventAuditConditionCommentEventData extends RaiseEventAuditUpdateMethodEventData {
|
|
@@ -3727,6 +4014,7 @@ export interface RaiseEventAuditSessionEventData extends RaiseEventAuditEventDat
|
|
|
3727
4014
|
}
|
|
3728
4015
|
|
|
3729
4016
|
export interface RaiseEventAuditCreateSessionEventData extends RaiseEventAuditSessionEventData {
|
|
4017
|
+
sessionId: PseudoVariantNodeId;
|
|
3730
4018
|
/**
|
|
3731
4019
|
* part 5 - 6.4.8 AuditCreateSessionEventType
|
|
3732
4020
|
* SecureChannelId shall uniquely identify the SecureChannel.
|
|
@@ -3846,115 +4134,7 @@ export interface RaiseAuditCertificateRevokedEventData extends RaiseAuditCertifi
|
|
|
3846
4134
|
* There are no additional Properties defined for this EventType
|
|
3847
4135
|
*/
|
|
3848
4136
|
export interface RaiseAuditCertificateMismatchEventData extends RaiseAuditCertificateEventData { }
|
|
3849
|
-
export interface OPCUAServer {
|
|
3850
|
-
/**
|
|
3851
|
-
* @internal
|
|
3852
|
-
* @param eventType
|
|
3853
|
-
* @param options
|
|
3854
|
-
*/
|
|
3855
|
-
raiseEvent(eventType: "AuditSessionEventType", options: RaiseEventAuditSessionEventData): void;
|
|
3856
|
-
|
|
3857
|
-
raiseEvent(eventType: "AuditCreateSessionEventType", options: RaiseEventAuditCreateSessionEventData): void;
|
|
3858
|
-
|
|
3859
|
-
raiseEvent(eventType: "AuditActivateSessionEventType", options: RaiseEventAuditActivateSessionEventData): void;
|
|
3860
|
-
|
|
3861
|
-
raiseEvent(eventType: "AuditCreateSessionEventType", options: RaiseEventData): void;
|
|
3862
|
-
|
|
3863
|
-
raiseEvent(eventType: "AuditConditionCommentEventType", options: RaiseEventAuditConditionCommentEventData): void;
|
|
3864
|
-
|
|
3865
|
-
raiseEvent(eventType: "AuditUrlMismatchEventType", options: RaiseEventAuditUrlMismatchEventTypeData): void;
|
|
3866
|
-
|
|
3867
|
-
raiseEvent(eventType: "TransitionEventType", options: RaiseEventTransitionEventData): void;
|
|
3868
|
-
|
|
3869
|
-
raiseEvent(eventType: "AuditCertificateInvalidEventType", options: RaiseAuditCertificateInvalidEventData): void;
|
|
3870
|
-
raiseEvent(eventType: "AuditCertificateExpiredEventType", options: RaiseAuditCertificateExpiredEventData): void;
|
|
3871
|
-
raiseEvent(eventType: "AuditCertificateUntrustedEventType", options: RaiseAuditCertificateUntrustedEventData): void;
|
|
3872
|
-
raiseEvent(eventType: "AuditCertificateRevokedEventType", options: RaiseAuditCertificateRevokedEventData): void;
|
|
3873
|
-
raiseEvent(eventType: "AuditCertificateMismatchEventType", options: RaiseAuditCertificateMismatchEventData): void;
|
|
3874
|
-
raiseEvent(eventType: UAEventType, options: RaiseEventData): void;
|
|
3875
|
-
}
|
|
3876
|
-
|
|
3877
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
|
|
3878
|
-
export interface OPCUAServer extends EventEmitter {
|
|
3879
|
-
on(event: "create_session", eventHandler: (session: ServerSession) => void): this;
|
|
3880
|
-
|
|
3881
|
-
on(event: "session_activated", eventHandler: (session: ServerSession) => void): this;
|
|
3882
|
-
|
|
3883
|
-
on(event: "session_closed", eventHandler: (session: ServerSession, reason: string) => void): this;
|
|
3884
|
-
|
|
3885
|
-
on(event: "post_initialize", eventHandler: () => void): this;
|
|
3886
|
-
|
|
3887
|
-
/**
|
|
3888
|
-
* emitted when the server is trying to registered the LDS
|
|
3889
|
-
* but when the connection to the lds has failed
|
|
3890
|
-
* serverRegistrationPending is sent when the backoff signal of the
|
|
3891
|
-
* connection process is raised
|
|
3892
|
-
* @event serverRegistrationPending
|
|
3893
|
-
*/
|
|
3894
|
-
on(event: "serverRegistrationPending", eventHandler: () => void): this;
|
|
3895
|
-
|
|
3896
|
-
/**
|
|
3897
|
-
* event raised when server has been successfully registered on the local discovery server
|
|
3898
|
-
* @event serverRegistered
|
|
3899
|
-
*/
|
|
3900
|
-
on(event: "serverRegistered", eventHandler: () => void): this;
|
|
3901
|
-
|
|
3902
|
-
/**
|
|
3903
|
-
* event raised when server registration has been successfully renewed on the local discovery server
|
|
3904
|
-
* @event serverRegistered
|
|
3905
|
-
*/
|
|
3906
|
-
on(event: "serverRegistrationRenewed", eventHandler: () => void): this;
|
|
3907
|
-
|
|
3908
|
-
/**
|
|
3909
|
-
* event raised when server has been successfully unregistered from the local discovery server
|
|
3910
|
-
* @event serverUnregistered
|
|
3911
|
-
*/
|
|
3912
|
-
on(event: "serverUnregistered", eventHandler: () => void): this;
|
|
3913
|
-
|
|
3914
|
-
/**
|
|
3915
|
-
* event raised after the server has raised an OPCUA event toward a client
|
|
3916
|
-
*/
|
|
3917
|
-
on(event: "event", eventHandler: (eventData: any) => void): this;
|
|
3918
|
-
|
|
3919
|
-
/**
|
|
3920
|
-
* event raised when the server received a request from one of its connected client.
|
|
3921
|
-
* useful for trace purpose.
|
|
3922
|
-
*/
|
|
3923
|
-
on(event: "request", eventHandler: (request: Request, channel: ServerSecureChannelLayer) => void): this;
|
|
3924
|
-
|
|
3925
|
-
/**
|
|
3926
|
-
* event raised when the server send an response to a request to one of its connected client.
|
|
3927
|
-
* useful for trace purpose.
|
|
3928
|
-
*/
|
|
3929
|
-
on(event: "response", eventHandler: (request: Response, channel: ServerSecureChannelLayer) => void): this;
|
|
3930
|
-
|
|
3931
|
-
/**
|
|
3932
|
-
* event raised when a new secure channel is opened
|
|
3933
|
-
*/
|
|
3934
|
-
on(event: "newChannel", eventHandler: (channel: ServerSecureChannelLayer, endpoint: OPCUAServerEndPoint) => void): this;
|
|
3935
|
-
|
|
3936
|
-
/**
|
|
3937
|
-
* event raised when a new secure channel is closed
|
|
3938
|
-
*/
|
|
3939
|
-
on(event: "closeChannel", eventHandler: (channel: ServerSecureChannelLayer, endpoint: OPCUAServerEndPoint) => void): this;
|
|
3940
|
-
|
|
3941
|
-
/**
|
|
3942
|
-
* event raised when the server refused a tcp connection from a client. ( for instance because too any connections)
|
|
3943
|
-
*/
|
|
3944
|
-
on(event: "connectionRefused", eventHandler: (socketData: ISocketData, endpoint: OPCUAServerEndPoint) => void): this;
|
|
3945
|
-
|
|
3946
|
-
/**
|
|
3947
|
-
* event raised when a OpenSecureChannel has failed, it could be a invalid certificate or malformed message
|
|
3948
|
-
*/
|
|
3949
|
-
on(
|
|
3950
|
-
event: "openSecureChannelFailure",
|
|
3951
|
-
eventHandler: (socketData: ISocketData, channelData: IChannelData, endpoint: OPCUAServerEndPoint) => void
|
|
3952
|
-
): this;
|
|
3953
|
-
|
|
3954
|
-
on(event: string, eventHandler: (...args: [any?, ...any[]]) => void): this;
|
|
3955
|
-
}
|
|
3956
4137
|
|
|
3957
4138
|
const opts = { multiArgs: false };
|
|
3958
|
-
OPCUAServer.prototype.start = withCallback(OPCUAServer.prototype.start, opts);
|
|
3959
4139
|
OPCUAServer.prototype.initialize = withCallback(OPCUAServer.prototype.initialize, opts);
|
|
3960
4140
|
OPCUAServer.prototype.shutdown = withCallback(OPCUAServer.prototype.shutdown, opts);
|