node-opcua-server 2.51.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/.mocharc.yml +10 -0
  2. package/LICENSE +20 -0
  3. package/dist/base_server.d.ts +110 -0
  4. package/dist/base_server.js +476 -0
  5. package/dist/base_server.js.map +1 -0
  6. package/dist/factory.d.ts +10 -0
  7. package/dist/factory.js +24 -0
  8. package/dist/factory.js.map +1 -0
  9. package/dist/history_server_capabilities.d.ts +35 -0
  10. package/dist/history_server_capabilities.js +44 -0
  11. package/dist/history_server_capabilities.js.map +1 -0
  12. package/dist/i_channel_data.d.ts +13 -0
  13. package/dist/i_channel_data.js +3 -0
  14. package/dist/i_channel_data.js.map +1 -0
  15. package/dist/i_register_server_manager.d.ts +16 -0
  16. package/dist/i_register_server_manager.js +3 -0
  17. package/dist/i_register_server_manager.js.map +1 -0
  18. package/dist/i_server_side_publish_engine.d.ts +36 -0
  19. package/dist/i_server_side_publish_engine.js +50 -0
  20. package/dist/i_server_side_publish_engine.js.map +1 -0
  21. package/dist/i_socket_data.d.ts +11 -0
  22. package/dist/i_socket_data.js +3 -0
  23. package/dist/i_socket_data.js.map +1 -0
  24. package/dist/index.d.ts +14 -0
  25. package/dist/index.js +27 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/monitored_item.d.ts +173 -0
  28. package/dist/monitored_item.js +1006 -0
  29. package/dist/monitored_item.js.map +1 -0
  30. package/dist/node_sampler.d.ts +3 -0
  31. package/dist/node_sampler.js +76 -0
  32. package/dist/node_sampler.js.map +1 -0
  33. package/dist/opcua_server.d.ts +668 -0
  34. package/dist/opcua_server.js +2407 -0
  35. package/dist/opcua_server.js.map +1 -0
  36. package/dist/queue.d.ts +11 -0
  37. package/dist/queue.js +71 -0
  38. package/dist/queue.js.map +1 -0
  39. package/dist/register_server_manager.d.ts +92 -0
  40. package/dist/register_server_manager.js +574 -0
  41. package/dist/register_server_manager.js.map +1 -0
  42. package/dist/register_server_manager_hidden.d.ts +17 -0
  43. package/dist/register_server_manager_hidden.js +28 -0
  44. package/dist/register_server_manager_hidden.js.map +1 -0
  45. package/dist/register_server_manager_mdns_only.d.ts +19 -0
  46. package/dist/register_server_manager_mdns_only.js +58 -0
  47. package/dist/register_server_manager_mdns_only.js.map +1 -0
  48. package/dist/server_capabilities.d.ts +61 -0
  49. package/dist/server_capabilities.js +109 -0
  50. package/dist/server_capabilities.js.map +1 -0
  51. package/dist/server_end_point.d.ts +180 -0
  52. package/dist/server_end_point.js +825 -0
  53. package/dist/server_end_point.js.map +1 -0
  54. package/dist/server_engine.d.ts +311 -0
  55. package/dist/server_engine.js +1659 -0
  56. package/dist/server_engine.js.map +1 -0
  57. package/dist/server_publish_engine.d.ts +109 -0
  58. package/dist/server_publish_engine.js +531 -0
  59. package/dist/server_publish_engine.js.map +1 -0
  60. package/dist/server_publish_engine_for_orphan_subscriptions.d.ts +16 -0
  61. package/dist/server_publish_engine_for_orphan_subscriptions.js +50 -0
  62. package/dist/server_publish_engine_for_orphan_subscriptions.js.map +1 -0
  63. package/dist/server_session.d.ts +176 -0
  64. package/dist/server_session.js +734 -0
  65. package/dist/server_session.js.map +1 -0
  66. package/dist/server_subscription.d.ts +393 -0
  67. package/dist/server_subscription.js +1313 -0
  68. package/dist/server_subscription.js.map +1 -0
  69. package/dist/sessions_compatible_for_transfer.d.ts +2 -0
  70. package/dist/sessions_compatible_for_transfer.js +36 -0
  71. package/dist/sessions_compatible_for_transfer.js.map +1 -0
  72. package/dist/validate_filter.d.ts +5 -0
  73. package/dist/validate_filter.js +64 -0
  74. package/dist/validate_filter.js.map +1 -0
  75. package/package.json +88 -0
  76. package/source/base_server.ts +617 -0
  77. package/source/factory.ts +25 -0
  78. package/source/history_server_capabilities.ts +75 -0
  79. package/source/i_channel_data.ts +17 -0
  80. package/source/i_register_server_manager.ts +24 -0
  81. package/source/i_server_side_publish_engine.ts +77 -0
  82. package/source/i_socket_data.ts +11 -0
  83. package/source/index.ts +14 -0
  84. package/source/monitored_item.ts +1303 -0
  85. package/source/node_sampler.ts +82 -0
  86. package/source/opcua_server.ts +3742 -0
  87. package/source/queue.ts +73 -0
  88. package/source/register_server_manager.ts +744 -0
  89. package/source/register_server_manager_hidden.ts +33 -0
  90. package/source/register_server_manager_mdns_only.ts +69 -0
  91. package/source/server_capabilities.ts +177 -0
  92. package/source/server_end_point.ts +1182 -0
  93. package/source/server_engine.ts +2167 -0
  94. package/source/server_publish_engine.ts +657 -0
  95. package/source/server_publish_engine_for_orphan_subscriptions.ts +52 -0
  96. package/source/server_session.ts +931 -0
  97. package/source/server_subscription.ts +1792 -0
  98. package/source/sessions_compatible_for_transfer.ts +33 -0
  99. package/source/validate_filter.ts +86 -0
  100. package/test_helpers/create_certificates.js +1 -0
@@ -0,0 +1,3742 @@
1
+ /**
2
+ * @module node-opcua-server
3
+ */
4
+ // tslint:disable:no-console
5
+ // tslint:disable:max-line-length
6
+ // tslint:disable:unified-signatures
7
+
8
+ import * as crypto from "crypto";
9
+ import { EventEmitter } from "events";
10
+ import { callbackify } from "util";
11
+
12
+ import * as async from "async";
13
+ import * as chalk from "chalk";
14
+
15
+ import { extractFullyQualifiedDomainName, getFullyQualifiedDomainName } from "node-opcua-hostname";
16
+
17
+ import { assert } from "node-opcua-assert";
18
+ import * as utils from "node-opcua-utils";
19
+
20
+ import {
21
+ AddressSpace,
22
+ callMethodHelper,
23
+ ContinuationPoint,
24
+ IUserManager,
25
+ PseudoVariantBoolean,
26
+ PseudoVariantByteString,
27
+ PseudoVariantDateTime,
28
+ PseudoVariantDuration,
29
+ PseudoVariantExtensionObject,
30
+ PseudoVariantExtensionObjectArray,
31
+ PseudoVariantLocalizedText,
32
+ PseudoVariantNodeId,
33
+ PseudoVariantString,
34
+ RaiseEventData,
35
+ SessionContext,
36
+ UAObject,
37
+ UAVariable,
38
+ ISessionContext,
39
+ UAView
40
+ } from "node-opcua-address-space";
41
+ import { getDefaultCertificateManager, OPCUACertificateManager } from "node-opcua-certificate-manager";
42
+ import { ServerState } from "node-opcua-common";
43
+ import { Certificate, exploreCertificate, makeSHA1Thumbprint, Nonce, toPem } from "node-opcua-crypto";
44
+ import { AttributeIds, LocalizedText, NodeClass } from "node-opcua-data-model";
45
+ import { DataValue } from "node-opcua-data-value";
46
+ import { dump, make_debugLog, make_errorLog, make_warningLog } from "node-opcua-debug";
47
+ import { NodeId } from "node-opcua-nodeid";
48
+ import { ObjectRegistry } from "node-opcua-object-registry";
49
+ import {
50
+ AsymmetricAlgorithmSecurityHeader,
51
+ computeSignature,
52
+ fromURI,
53
+ getCryptoFactory,
54
+ Message,
55
+ MessageSecurityMode,
56
+ nonceAlreadyBeenUsed,
57
+ Request,
58
+ Response,
59
+ SecurityPolicy,
60
+ ServerSecureChannelLayer,
61
+ SignatureData,
62
+ verifySignature
63
+ } from "node-opcua-secure-channel";
64
+ import { BrowseNextRequest, BrowseNextResponse, BrowseRequest, BrowseResponse } from "node-opcua-service-browse";
65
+ import { CallRequest, CallResponse } from "node-opcua-service-call";
66
+ import { ApplicationType, UserTokenType } from "node-opcua-service-endpoints";
67
+ import { HistoryReadRequest, HistoryReadResponse, HistoryReadResult, HistoryUpdateResponse } from "node-opcua-service-history";
68
+ import {
69
+ AddNodesResponse,
70
+ AddReferencesResponse,
71
+ DeleteNodesResponse,
72
+ DeleteReferencesResponse
73
+ } from "node-opcua-service-node-management";
74
+ import { QueryFirstResponse, QueryNextResponse } from "node-opcua-service-query";
75
+ import { ReadRequest, ReadResponse, ReadValueId, TimestampsToReturn } from "node-opcua-service-read";
76
+ import {
77
+ RegisterNodesRequest,
78
+ RegisterNodesResponse,
79
+ UnregisterNodesRequest,
80
+ UnregisterNodesResponse
81
+ } from "node-opcua-service-register-node";
82
+ import {
83
+ ActivateSessionRequest,
84
+ ActivateSessionResponse,
85
+ AnonymousIdentityToken,
86
+ CloseSessionRequest,
87
+ CloseSessionResponse,
88
+ CreateSessionRequest,
89
+ CreateSessionResponse,
90
+ UserNameIdentityToken,
91
+ X509IdentityToken
92
+ } from "node-opcua-service-session";
93
+ import {
94
+ CreateMonitoredItemsRequest,
95
+ CreateMonitoredItemsResponse,
96
+ CreateSubscriptionRequest,
97
+ CreateSubscriptionResponse,
98
+ DeleteMonitoredItemsRequest,
99
+ DeleteMonitoredItemsResponse,
100
+ DeleteSubscriptionsRequest,
101
+ DeleteSubscriptionsResponse,
102
+ ModifyMonitoredItemsRequest,
103
+ ModifyMonitoredItemsResponse,
104
+ ModifySubscriptionRequest,
105
+ ModifySubscriptionResponse,
106
+ MonitoredItemModifyResult,
107
+ PublishRequest,
108
+ PublishResponse,
109
+ RepublishRequest,
110
+ RepublishResponse,
111
+ SetMonitoringModeRequest,
112
+ SetMonitoringModeResponse,
113
+ SetPublishingModeRequest,
114
+ SetPublishingModeResponse,
115
+ SetTriggeringRequestOptions,
116
+ SetTriggeringRequest,
117
+ SetTriggeringResponse,
118
+ TransferSubscriptionsRequest,
119
+ TransferSubscriptionsResponse
120
+ } from "node-opcua-service-subscription";
121
+ import {
122
+ TranslateBrowsePathsToNodeIdsRequest,
123
+ TranslateBrowsePathsToNodeIdsResponse
124
+ } from "node-opcua-service-translate-browse-path";
125
+ import { WriteRequest, WriteResponse } from "node-opcua-service-write";
126
+ import { ErrorCallback, StatusCode, StatusCodes } from "node-opcua-status-code";
127
+ import {
128
+ ApplicationDescriptionOptions,
129
+ BrowseResult,
130
+ BuildInfo,
131
+ CallMethodResultOptions,
132
+ CancelResponse,
133
+ EndpointDescription,
134
+ MonitoredItemModifyRequest,
135
+ MonitoringMode,
136
+ UserIdentityToken,
137
+ UserTokenPolicy,
138
+ BrowseDescription,
139
+ BuildInfoOptions,
140
+ MonitoredItemCreateResult,
141
+ IssuedIdentityToken
142
+ } from "node-opcua-types";
143
+ import { DataType } from "node-opcua-variant";
144
+ import { VariantArrayType } from "node-opcua-variant";
145
+ import { matchUri } from "node-opcua-utils";
146
+
147
+ import { OPCUABaseServer, OPCUABaseServerOptions } from "./base_server";
148
+ import { Factory } from "./factory";
149
+ import { IRegisterServerManager } from "./i_register_server_manager";
150
+ import { MonitoredItem } from "./monitored_item";
151
+ import { RegisterServerManager } from "./register_server_manager";
152
+ import { RegisterServerManagerHidden } from "./register_server_manager_hidden";
153
+ import { RegisterServerManagerMDNSONLY } from "./register_server_manager_mdns_only";
154
+ import { ServerCapabilitiesOptions } from "./server_capabilities";
155
+ import { OPCUAServerEndPoint } from "./server_end_point";
156
+ import { ClosingReason, ServerEngine } from "./server_engine";
157
+ import { ServerSession } from "./server_session";
158
+ import { CreateMonitoredItemHook, DeleteMonitoredItemHook, Subscription } from "./server_subscription";
159
+ import { ISocketData } from "./i_socket_data";
160
+ import { IChannelData } from "./i_channel_data";
161
+
162
+ function isSubscriptionIdInvalid(subscriptionId: number): boolean {
163
+ return subscriptionId < 0 || subscriptionId >= 0xffffffff;
164
+ }
165
+
166
+ export type ValidUserFunc = (this: ServerSession, username: string, password: string) => boolean;
167
+ export type ValidUserAsyncFunc = (
168
+ this: ServerSession,
169
+ username: string,
170
+ password: string,
171
+ callback: (err: Error | null, isAuthorized?: boolean) => void
172
+ ) => void;
173
+
174
+ export interface UserManagerOptions extends IUserManager {
175
+ /** synchronous function to check the credentials - can be overruled by isValidUserAsync */
176
+ isValidUser?: ValidUserFunc;
177
+ /** asynchronous function to check if the credentials - overrules isValidUser */
178
+ isValidUserAsync?: ValidUserAsyncFunc;
179
+ }
180
+
181
+ // tslint:disable-next-line:no-var-requires
182
+ const package_info = require("../package.json");
183
+ const debugLog = make_debugLog(__filename);
184
+ const errorLog = make_errorLog(__filename);
185
+ const warningLog = make_warningLog(__filename);
186
+
187
+ const default_maxAllowedSessionNumber = 10;
188
+ const default_maxConnectionsPerEndpoint = 10;
189
+
190
+ function g_sendError(channel: ServerSecureChannelLayer, message: Message, ResponseClass: any, statusCode: StatusCode): void {
191
+ const response = new ResponseClass({
192
+ responseHeader: { serviceResult: statusCode }
193
+ });
194
+ return channel.send_response("MSG", response, message);
195
+ }
196
+
197
+ const default_build_info: BuildInfoOptions = {
198
+ manufacturerName: "NodeOPCUA : MIT Licence ( see http://node-opcua.github.io/)",
199
+ productName: "NodeOPCUA-Server",
200
+ productUri: null, // << should be same as default_server_info.productUri?
201
+ softwareVersion: package_info.version,
202
+ buildNumber: "0",
203
+ buildDate: new Date(2020, 1, 1)
204
+ // xx buildDate: fs.statSync(package_json_file).mtime
205
+ };
206
+
207
+ const minSessionTimeout = 100; // 100 milliseconds
208
+ const defaultSessionTimeout = 1000 * 30; // 30 seconds
209
+ const maxSessionTimeout = 1000 * 60 * 50; // 50 minutes
210
+
211
+ function _adjust_session_timeout(sessionTimeout: number) {
212
+ let revisedSessionTimeout = sessionTimeout || defaultSessionTimeout;
213
+ revisedSessionTimeout = Math.min(revisedSessionTimeout, maxSessionTimeout);
214
+ revisedSessionTimeout = Math.max(revisedSessionTimeout, minSessionTimeout);
215
+ return revisedSessionTimeout;
216
+ }
217
+
218
+ function channel_has_session(channel: ServerSecureChannelLayer, session: ServerSession): boolean {
219
+ if (session.channel === channel) {
220
+ assert(channel.sessionTokens.hasOwnProperty(session.authenticationToken.toString()));
221
+ return true;
222
+ }
223
+ return false;
224
+ }
225
+
226
+ function moveSessionToChannel(session: ServerSession, channel: ServerSecureChannelLayer) {
227
+ debugLog("moveSessionToChannel sessionId", session.nodeId, " channelId=", channel.channelId);
228
+ if (session.publishEngine) {
229
+ session.publishEngine.cancelPendingPublishRequestBeforeChannelChange();
230
+ }
231
+
232
+ session._detach_channel();
233
+ session._attach_channel(channel);
234
+
235
+ assert(session.channel!.channelId === channel.channelId);
236
+ }
237
+
238
+ async function _attempt_to_close_some_old_unactivated_session(server: OPCUAServer) {
239
+ const session = server.engine!.getOldestUnactivatedSession();
240
+ if (session) {
241
+ await server.engine!.closeSession(session.authenticationToken, false, "Forcing");
242
+ }
243
+ }
244
+
245
+ function getRequiredEndpointInfo(endpoint: EndpointDescription) {
246
+ assert(endpoint instanceof EndpointDescription);
247
+ // It is recommended that Servers only include the server.applicationUri, endpointUrl, securityMode,
248
+ // securityPolicyUri, userIdentityTokens, transportProfileUri and securityLevel with all
249
+ // other parameters set to null. Only the recommended parameters shall be verified by
250
+ // the client.
251
+
252
+ const e = new EndpointDescription({
253
+ endpointUrl: endpoint.endpointUrl,
254
+ securityLevel: endpoint.securityLevel,
255
+ securityMode: endpoint.securityMode,
256
+ securityPolicyUri: endpoint.securityPolicyUri,
257
+ server: {
258
+ applicationUri: endpoint.server.applicationUri,
259
+ applicationType: endpoint.server.applicationType,
260
+ applicationName: endpoint.server.applicationName
261
+ // ... to be continued after verifying what fields are actually needed
262
+ },
263
+ transportProfileUri: endpoint.transportProfileUri,
264
+ userIdentityTokens: endpoint.userIdentityTokens
265
+ });
266
+ // reduce even further by explicitly setting unwanted members to null
267
+ e.server.productUri = null;
268
+ e.server.applicationName = null as any;
269
+ // xx e.server.applicationType = null as any;
270
+ e.server.gatewayServerUri = null;
271
+ e.server.discoveryProfileUri = null;
272
+ e.server.discoveryUrls = null;
273
+ e.serverCertificate = null as any;
274
+ return e;
275
+ }
276
+
277
+ // serverUri String This value is only specified if the EndpointDescription has a gatewayServerUri.
278
+ // This value is the applicationUri from the EndpointDescription which is the applicationUri for the
279
+ // underlying Server. The type EndpointDescription is defined in 7.10.
280
+
281
+ function _serverEndpointsForCreateSessionResponse(server: OPCUAServer, endpointUrl: string | null, serverUri: string | null) {
282
+ serverUri = null; // unused then
283
+
284
+ // The Server shall return a set of EndpointDescriptions available for the serverUri specified in the request.
285
+ // It is recommended that Servers only include the endpointUrl, securityMode,
286
+ // securityPolicyUri, userIdentityTokens, transportProfileUri and securityLevel with all other parameters
287
+ // set to null. Only the recommended parameters shall be verified by the client.
288
+ return server
289
+ ._get_endpoints(endpointUrl)
290
+ .filter((e) => !(e as any).restricted) // remove restricted endpoints
291
+ .filter((e) => matchUri(e.endpointUrl, endpointUrl))
292
+ .map(getRequiredEndpointInfo);
293
+ }
294
+
295
+ function adjustSecurityPolicy(
296
+ channel: ServerSecureChannelLayer,
297
+ userTokenPolicy_securityPolicyUri: SecurityPolicy
298
+ ): SecurityPolicy {
299
+ // check that userIdentityToken
300
+ let securityPolicy = fromURI(userTokenPolicy_securityPolicyUri);
301
+
302
+ // if the security policy is not specified we use the session security policy
303
+ if (securityPolicy === SecurityPolicy.Invalid) {
304
+ securityPolicy = fromURI((channel.clientSecurityHeader! as AsymmetricAlgorithmSecurityHeader).securityPolicyUri);
305
+ assert(securityPolicy !== SecurityPolicy.Invalid);
306
+ }
307
+ return securityPolicy;
308
+ }
309
+
310
+ function findUserTokenByPolicy(
311
+ endpoint_description: EndpointDescription,
312
+ userTokenType: UserTokenType,
313
+ policyId: SecurityPolicy | string | null
314
+ ): UserTokenPolicy | null {
315
+ assert(endpoint_description instanceof EndpointDescription);
316
+ const r = endpoint_description.userIdentityTokens!.filter(
317
+ (userIdentity: UserTokenPolicy) =>
318
+ userIdentity.tokenType === userTokenType && (!policyId || userIdentity.policyId === policyId)
319
+ );
320
+ return r.length === 0 ? null : r[0];
321
+ }
322
+
323
+ function findUserTokenPolicy(endpoint_description: EndpointDescription, userTokenType: UserTokenType): UserTokenPolicy | null {
324
+ assert(endpoint_description instanceof EndpointDescription);
325
+ const r = endpoint_description.userIdentityTokens!.filter((userIdentity: UserTokenPolicy) => {
326
+ assert(userIdentity.tokenType !== undefined);
327
+ return userIdentity.tokenType === userTokenType;
328
+ });
329
+ return r.length === 0 ? null : r[0];
330
+ }
331
+
332
+ function createAnonymousIdentityToken(endpoint_desc: EndpointDescription) {
333
+ assert(endpoint_desc instanceof EndpointDescription);
334
+ const userTokenPolicy = findUserTokenPolicy(endpoint_desc, UserTokenType.Anonymous);
335
+ if (!userTokenPolicy) {
336
+ throw new Error("Cannot find ANONYMOUS user token policy in end point description");
337
+ }
338
+ return new AnonymousIdentityToken({ policyId: userTokenPolicy.policyId });
339
+ }
340
+
341
+ function sameIdentityToken(token1: UserIdentityToken, token2: UserIdentityToken): boolean {
342
+ if (token1 instanceof UserNameIdentityToken) {
343
+ if (!(token2 instanceof UserNameIdentityToken)) {
344
+ return false;
345
+ }
346
+ if (token1.userName !== token2.userName) {
347
+ return false;
348
+ }
349
+ if (token1.password.toString("hex") !== token2.password.toString("hex")) {
350
+ return false;
351
+ }
352
+ } else if (token1 instanceof AnonymousIdentityToken) {
353
+ if (!(token2 instanceof AnonymousIdentityToken)) {
354
+ return false;
355
+ }
356
+ if (token1.policyId !== token2.policyId) {
357
+ return false;
358
+ }
359
+ return true;
360
+ }
361
+ assert(false, " Not implemented yet");
362
+ return false;
363
+ }
364
+ function getTokenType(userIdentityToken: UserIdentityToken): UserTokenType {
365
+ if (userIdentityToken instanceof AnonymousIdentityToken) {
366
+ return UserTokenType.Anonymous;
367
+ } else if (userIdentityToken instanceof UserNameIdentityToken) {
368
+ return UserTokenType.UserName;
369
+ } else if (userIdentityToken instanceof IssuedIdentityToken) {
370
+ return UserTokenType.IssuedToken;
371
+ } else if (userIdentityToken instanceof X509IdentityToken) {
372
+ return UserTokenType.Certificate;
373
+ }
374
+ return UserTokenType.Invalid;
375
+ }
376
+ function thumbprint(certificate?: Certificate): string {
377
+ return certificate ? certificate.toString("base64") : "";
378
+ }
379
+
380
+ /*=== private
381
+ *
382
+ * perform the read operation on a given node for a monitored item.
383
+ * this method DOES NOT apply to Variable Values attribute
384
+ *
385
+ * @param self
386
+ * @param oldValue
387
+ * @param node
388
+ * @param itemToMonitor
389
+ * @private
390
+ */
391
+ function monitoredItem_read_and_record_value(
392
+ self: MonitoredItem,
393
+ context: ISessionContext | null,
394
+ oldValue: DataValue,
395
+ node: UAVariable,
396
+ itemToMonitor: any,
397
+ callback: (err: Error | null, dataValue?: DataValue) => void
398
+ ) {
399
+ assert(self instanceof MonitoredItem);
400
+ assert(oldValue instanceof DataValue);
401
+ assert(itemToMonitor.attributeId === AttributeIds.Value);
402
+
403
+ const dataValue = node.readAttribute(context, itemToMonitor.attributeId, itemToMonitor.indexRange, itemToMonitor.dataEncoding);
404
+
405
+ callback(null, dataValue);
406
+ }
407
+
408
+ /*== private
409
+ * @method monitoredItem_read_and_record_value_async
410
+ * this method applies to Variable Values attribute
411
+ * @param self
412
+ * @param oldValue
413
+ * @param node
414
+ * @param itemToMonitor
415
+ * @private
416
+ */
417
+ function monitoredItem_read_and_record_value_async(
418
+ self: MonitoredItem,
419
+ context: ISessionContext,
420
+ oldValue: DataValue,
421
+ node: UAVariable,
422
+ itemToMonitor: any,
423
+ callback: (err: Error | null, dataValue?: DataValue) => void
424
+ ) {
425
+ assert(context instanceof SessionContext);
426
+ assert(itemToMonitor.attributeId === AttributeIds.Value);
427
+ assert(self instanceof MonitoredItem);
428
+ assert(oldValue instanceof DataValue);
429
+ // do it asynchronously ( this is only valid for value attributes )
430
+ assert(itemToMonitor.attributeId === AttributeIds.Value);
431
+
432
+ node.readValueAsync(context, (err: Error | null, dataValue?: DataValue) => {
433
+ callback(err, dataValue);
434
+ });
435
+ }
436
+
437
+ function build_scanning_node_function(
438
+ context: ISessionContext,
439
+ addressSpace: AddressSpace,
440
+ monitoredItem: MonitoredItem,
441
+ itemToMonitor: any
442
+ ): (dataValue: DataValue, callback: (err: Error | null, dataValue?: DataValue) => void) => void {
443
+ assert(context instanceof SessionContext);
444
+ assert(itemToMonitor instanceof ReadValueId);
445
+
446
+ const node = addressSpace.findNode(itemToMonitor.nodeId) as UAVariable;
447
+
448
+ /* istanbul ignore next */
449
+ if (!node) {
450
+ errorLog(" INVALID NODE ID , ", itemToMonitor.nodeId.toString());
451
+ dump(itemToMonitor);
452
+ return (oldData: DataValue, callback: (err: Error | null, dataValue?: DataValue) => void) => {
453
+ callback(
454
+ null,
455
+ new DataValue({
456
+ statusCode: StatusCodes.BadNodeIdUnknown,
457
+ value: { dataType: DataType.Null, value: 0 }
458
+ })
459
+ );
460
+ };
461
+ }
462
+
463
+ ///// !!monitoredItem.setNode(node);
464
+
465
+ if (itemToMonitor.attributeId === AttributeIds.Value) {
466
+ const monitoredItem_read_and_record_value_func =
467
+ itemToMonitor.attributeId === AttributeIds.Value && typeof node.readValueAsync === "function"
468
+ ? monitoredItem_read_and_record_value_async
469
+ : monitoredItem_read_and_record_value;
470
+
471
+ return function func(
472
+ this: MonitoredItem,
473
+ oldDataValue: DataValue,
474
+ callback: (err: Error | null, dataValue?: DataValue) => void
475
+ ) {
476
+ assert(this instanceof MonitoredItem);
477
+ assert(oldDataValue instanceof DataValue);
478
+ assert(typeof callback === "function");
479
+ monitoredItem_read_and_record_value_func(this, context, oldDataValue, node, itemToMonitor, callback);
480
+ };
481
+ } else {
482
+ // Attributes, other than the Value Attribute, are only monitored for a change in value.
483
+ // The filter is not used for these Attributes. Any change in value for these Attributes
484
+ // causes a Notification to be generated.
485
+
486
+ // only record value when it has changed
487
+ return function func(
488
+ this: MonitoredItem,
489
+ oldDataValue: DataValue,
490
+ callback: (err: Error | null, dataValue?: DataValue) => void
491
+ ) {
492
+ const self = this;
493
+ assert(self instanceof MonitoredItem);
494
+ assert(oldDataValue instanceof DataValue);
495
+ assert(typeof callback === "function");
496
+ const newDataValue = node.readAttribute(null, itemToMonitor.attributeId);
497
+ callback(null, newDataValue);
498
+ };
499
+ }
500
+ }
501
+
502
+ function prepareMonitoredItem(context: ISessionContext, addressSpace: AddressSpace, monitoredItem: MonitoredItem) {
503
+ const itemToMonitor = monitoredItem.itemToMonitor;
504
+ const readNodeFunc = build_scanning_node_function(context, addressSpace, monitoredItem, itemToMonitor);
505
+ monitoredItem.samplingFunc = readNodeFunc;
506
+ }
507
+
508
+ function isMonitoringModeValid(monitoringMode: MonitoringMode): boolean {
509
+ assert(MonitoringMode.Invalid !== undefined);
510
+ return monitoringMode !== MonitoringMode.Invalid && monitoringMode <= MonitoringMode.Reporting;
511
+ }
512
+
513
+ function _installRegisterServerManager(self: OPCUAServer) {
514
+ assert(self instanceof OPCUAServer);
515
+ assert(!self.registerServerManager);
516
+
517
+ /* istanbul ignore next */
518
+ if (!self.registerServerMethod) {
519
+ throw new Error("Internal Error");
520
+ }
521
+
522
+ switch (self.registerServerMethod) {
523
+ case RegisterServerMethod.HIDDEN:
524
+ self.registerServerManager = new RegisterServerManagerHidden({
525
+ server: self
526
+ });
527
+ break;
528
+ case RegisterServerMethod.MDNS:
529
+ self.registerServerManager = new RegisterServerManagerMDNSONLY({
530
+ server: self
531
+ });
532
+ break;
533
+ case RegisterServerMethod.LDS:
534
+ self.registerServerManager = new RegisterServerManager({
535
+ discoveryServerEndpointUrl: self.discoveryServerEndpointUrl,
536
+ server: self
537
+ });
538
+ break;
539
+ /* istanbul ignore next */
540
+ default:
541
+ throw new Error("Invalid switch");
542
+ }
543
+
544
+ self.registerServerManager.on("serverRegistrationPending", () => {
545
+ /**
546
+ * emitted when the server is trying to registered the LDS
547
+ * but when the connection to the lds has failed
548
+ * serverRegistrationPending is sent when the backoff signal of the
549
+ * connection process is raised
550
+ * @event serverRegistrationPending
551
+ */
552
+ debugLog("serverRegistrationPending");
553
+ self.emit("serverRegistrationPending");
554
+ });
555
+ self.registerServerManager.on("serverRegistered", () => {
556
+ /**
557
+ * emitted when the server is successfully registered to the LDS
558
+ * @event serverRegistered
559
+ */
560
+ debugLog("serverRegistered");
561
+ self.emit("serverRegistered");
562
+ });
563
+ self.registerServerManager.on("serverRegistrationRenewed", () => {
564
+ /**
565
+ * emitted when the server has successfully renewed its registration to the LDS
566
+ * @event serverRegistrationRenewed
567
+ */
568
+ debugLog("serverRegistrationRenewed");
569
+ self.emit("serverRegistrationRenewed");
570
+ });
571
+
572
+ self.registerServerManager.on("serverUnregistered", () => {
573
+ debugLog("serverUnregistered");
574
+ /**
575
+ * emitted when the server is successfully unregistered to the LDS
576
+ * ( for instance during shutdown)
577
+ * @event serverUnregistered
578
+ */
579
+ self.emit("serverUnregistered");
580
+ });
581
+ }
582
+
583
+ export enum RegisterServerMethod {
584
+ HIDDEN = 1, // the server doesn't expose itself to the external world
585
+ MDNS = 2, // the server publish itself to the mDNS Multicast network directly
586
+ LDS = 3 // the server registers itself to the LDS or LDS-ME (Local Discovery Server)
587
+ }
588
+
589
+ export interface OPCUAServerEndpointOptions {
590
+ /**
591
+ * the primary hostname of the endpoint.
592
+ * @default getFullyQualifiedDomainName()
593
+ */
594
+ hostname?: string;
595
+
596
+ /**
597
+ * the TCP port to listen to.
598
+ * @default 26543
599
+ */
600
+ port?: number;
601
+ /**
602
+ * the possible security policies that the server will expose
603
+ * @default [SecurityPolicy.None, SecurityPolicy.Basic128Rsa15, SecurityPolicy.Basic256Sha256]
604
+ */
605
+ securityPolicies?: SecurityPolicy[];
606
+ /**
607
+ * the possible security mode that the server will expose
608
+ * @default [MessageSecurityMode.None, MessageSecurityMode.Sign, MessageSecurityMode.SignAndEncrypt]
609
+ */
610
+ securityModes?: MessageSecurityMode[];
611
+ /**
612
+ * tells if the server default endpoints should allow anonymous connection.
613
+ * @default true
614
+ */
615
+ allowAnonymous?: boolean;
616
+
617
+ /** alternate hostname or IP to use */
618
+ alternateHostname?: string | string[];
619
+
620
+ /**
621
+ * true, if discovery service on secure channel shall be disabled
622
+ */
623
+ disableDiscovery?: boolean;
624
+ }
625
+
626
+ export interface OPCUAServerOptions extends OPCUABaseServerOptions, OPCUAServerEndpointOptions {
627
+ alternateEndpoints?: OPCUAServerEndpointOptions[];
628
+
629
+ /**
630
+ * the server certificate full path filename
631
+ *
632
+ * the certificate should be in PEM format
633
+ */
634
+ certificateFile?: string;
635
+ /**
636
+ * the server private key full path filename
637
+ *
638
+ * This file should contains the private key that has been used to generate
639
+ * the server certificate file.
640
+ *
641
+ * the private key should be in PEM format
642
+ *
643
+ */
644
+ privateKeyFile?: string;
645
+
646
+ /**
647
+ * the default secure token life time in ms.
648
+ */
649
+ defaultSecureTokenLifetime?: number;
650
+ /**
651
+ * the HEL/ACK transaction timeout in ms.
652
+ *
653
+ * Use a large value ( i.e 15000 ms) for slow connections or embedded devices.
654
+ * @default 10000
655
+ */
656
+ timeout?: number;
657
+
658
+ /**
659
+ * the maximum number of simultaneous sessions allowed.
660
+ * @default 10
661
+ */
662
+ maxAllowedSessionNumber?: number;
663
+
664
+ /**
665
+ * the maximum number authorized simultaneous connections per endpoint
666
+ * @default 10
667
+ */
668
+ maxConnectionsPerEndpoint?: number;
669
+
670
+ /**
671
+ * the nodeset.xml file(s) to load
672
+ *
673
+ * node-opcua comes with pre-installed node-set files that can be used
674
+ *
675
+ * example:
676
+ *
677
+ * ``` javascript
678
+ *
679
+ * ```
680
+ */
681
+ nodeset_filename?: string[] | string;
682
+
683
+ /**
684
+ * the server Info
685
+ *
686
+ * this object contains the value that will populate the
687
+ * Root/ObjectS/Server/ServerInfo OPCUA object in the address space.
688
+ */
689
+ serverInfo?: ApplicationDescriptionOptions;
690
+ /*{
691
+ applicationUri?: string;
692
+ productUri?: string;
693
+ applicationName?: LocalizedTextLike | string;
694
+ gatewayServerUri?: string | null;
695
+ discoveryProfileUri?: string | null;
696
+ discoveryUrls?: string[];
697
+ };
698
+ */
699
+ buildInfo?: {
700
+ productName?: string;
701
+ productUri?: string | null; // << should be same as default_server_info.productUri?
702
+ manufacturerName?: string;
703
+ softwareVersion?: string;
704
+ buildNumber?: string;
705
+ buildDate?: Date;
706
+ };
707
+
708
+ /**
709
+ * an object that implements user authentication methods
710
+ */
711
+ userManager?: UserManagerOptions;
712
+
713
+ /** resource Path is a string added at the end of the url such as "/UA/Server" */
714
+ resourcePath?: string;
715
+
716
+ /**
717
+ *
718
+ */
719
+ serverCapabilities?: ServerCapabilitiesOptions;
720
+ /**
721
+ * if server shall raise AuditingEvent
722
+ * @default true
723
+ */
724
+ isAuditing?: boolean;
725
+
726
+ /**
727
+ * strategy used by the server to declare itself to a discovery server
728
+ *
729
+ * - HIDDEN: the server doesn't expose itself to the external world
730
+ * - MDNS: the server publish itself to the mDNS Multicast network directly
731
+ * - LDS: the server registers itself to the LDS or LDS-ME (Local Discovery Server)
732
+ *
733
+ * @default RegisterServerMethod.HIDDEN - by default the server
734
+ * will not register itself to the local discovery server
735
+ *
736
+ */
737
+ registerServerMethod?: RegisterServerMethod;
738
+ /**
739
+ *
740
+ * @default "opc.tcp://localhost:4840"]
741
+ */
742
+ discoveryServerEndpointUrl?: string;
743
+ /**
744
+ *
745
+ * supported server capabilities for the Multicast (mDNS)
746
+ * @default ["NA"]
747
+ * the possible values are any of node-opcua-discovery.serverCapabilities)
748
+ *
749
+ */
750
+ capabilitiesForMDNS?: string[];
751
+
752
+ /**
753
+ * user Certificate Manager
754
+ * this certificate manager holds the X509 certificates used
755
+ * by client that uses X509 certificate token to impersonate a user
756
+ */
757
+ userCertificateManager?: OPCUACertificateManager;
758
+ /**
759
+ * Server Certificate Manager
760
+ *
761
+ * this certificate manager will be used by the server to access
762
+ * and store certificates from the connecting clients
763
+ */
764
+ serverCertificateManager?: OPCUACertificateManager;
765
+
766
+ /**
767
+ *
768
+ */
769
+ onCreateMonitoredItem?: CreateMonitoredItemHook;
770
+ onDeleteMonitoredItem?: DeleteMonitoredItemHook;
771
+ }
772
+
773
+ export interface OPCUAServer {
774
+ /**
775
+ *
776
+ */
777
+ engine: ServerEngine;
778
+ /**
779
+ *
780
+ */
781
+ registerServerMethod: RegisterServerMethod;
782
+ /**
783
+ *
784
+ */
785
+ discoveryServerEndpointUrl: string;
786
+ /**
787
+ *
788
+ */
789
+ registerServerManager?: IRegisterServerManager;
790
+ /**
791
+ *
792
+ */
793
+ capabilitiesForMDNS: string[];
794
+ /**
795
+ *
796
+ */
797
+ userCertificateManager: OPCUACertificateManager;
798
+ }
799
+
800
+ const g_requestExactEndpointUrl: boolean = !!process.env.NODEOPCUA_SERVER_REQUEST_EXACT_ENDPOINT_URL;
801
+ /**
802
+ *
803
+ */
804
+ export class OPCUAServer extends OPCUABaseServer {
805
+ static defaultShutdownTimeout: number = 100; // 250 ms
806
+ /**
807
+ * if requestExactEndpointUrl is set to true the server will only accept createSession that have a endpointUrl that strictly matches
808
+ * one of the provided endpoint.
809
+ * This mean that if the server expose a endpoint with url such as opc.tcp://MYHOSTNAME:1234, client will not be able to reach the server
810
+ * with the ip address of the server.
811
+ * requestExactEndpointUrl = true => emulates the Prosys Server behavior
812
+ * requestExactEndpointUrl = false => emulates the Unified Automation behavior.
813
+ */
814
+ static requestExactEndpointUrl: boolean = g_requestExactEndpointUrl;
815
+ /**
816
+ * total number of bytes written by the server since startup
817
+ */
818
+ public get bytesWritten(): number {
819
+ return this.endpoints.reduce((accumulated: number, endpoint: OPCUAServerEndPoint) => {
820
+ return accumulated + endpoint.bytesWritten;
821
+ }, 0);
822
+ }
823
+
824
+ /**
825
+ * total number of bytes read by the server since startup
826
+ */
827
+ public get bytesRead(): number {
828
+ return this.endpoints.reduce((accumulated: number, endpoint: OPCUAServerEndPoint) => {
829
+ return accumulated + endpoint.bytesRead;
830
+ }, 0);
831
+ }
832
+
833
+ /**
834
+ * Number of transactions processed by the server since startup
835
+ */
836
+ public get transactionsCount(): number {
837
+ return this.endpoints.reduce((accumulated: number, endpoint: OPCUAServerEndPoint) => {
838
+ return accumulated + endpoint.transactionsCount;
839
+ }, 0);
840
+ }
841
+
842
+ /**
843
+ * The server build info
844
+ */
845
+ public get buildInfo(): BuildInfo {
846
+ return this.engine.buildInfo;
847
+ }
848
+
849
+ /**
850
+ * the number of connected channel on all existing end points
851
+ */
852
+ public get currentChannelCount(): number {
853
+ // TODO : move to base
854
+ const self = this;
855
+ return self.endpoints.reduce((currentValue: number, endPoint: OPCUAServerEndPoint) => {
856
+ return currentValue + endPoint.currentChannelCount;
857
+ }, 0);
858
+ }
859
+
860
+ /**
861
+ * The number of active subscriptions from all sessions
862
+ */
863
+ public get currentSubscriptionCount(): number {
864
+ return this.engine ? this.engine.currentSubscriptionCount : 0;
865
+ }
866
+
867
+ /**
868
+ * the number of session activation requests that have been rejected
869
+ */
870
+ public get rejectedSessionCount(): number {
871
+ return this.engine ? this.engine.rejectedSessionCount : 0;
872
+ }
873
+
874
+ /**
875
+ * the number of request that have been rejected
876
+ */
877
+ public get rejectedRequestsCount(): number {
878
+ return this.engine ? this.engine.rejectedRequestsCount : 0;
879
+ }
880
+
881
+ /**
882
+ * the number of sessions that have been aborted
883
+ */
884
+ public get sessionAbortCount(): number {
885
+ return this.engine ? this.engine.sessionAbortCount : 0;
886
+ }
887
+
888
+ /**
889
+ * the publishing interval count
890
+ */
891
+ public get publishingIntervalCount(): number {
892
+ return this.engine ? this.engine.publishingIntervalCount : 0;
893
+ }
894
+
895
+ /**
896
+ * the number of sessions currently active
897
+ */
898
+ public get currentSessionCount(): number {
899
+ return this.engine ? this.engine.currentSessionCount : 0;
900
+ }
901
+
902
+ /**
903
+ * true if the server has been initialized
904
+ *
905
+ */
906
+ public get initialized(): boolean {
907
+ return this.engine && this.engine.addressSpace !== null;
908
+ }
909
+
910
+ /**
911
+ * is the server auditing ?
912
+ */
913
+ public get isAuditing(): boolean {
914
+ return this.engine ? this.engine.isAuditing : false;
915
+ }
916
+
917
+ public static registry = new ObjectRegistry();
918
+ public static fallbackSessionName = "Client didn't provide a meaningful sessionName ...";
919
+ /**
920
+ * the maximum number of subscription that can be created per server
921
+ */
922
+ public static MAX_SUBSCRIPTION = 50;
923
+ /**
924
+ * the maximum number of concurrent sessions allowed on the server
925
+ */
926
+ public maxAllowedSessionNumber: number;
927
+ /**
928
+ * the maximum number for concurrent connection per end point
929
+ */
930
+ public maxConnectionsPerEndpoint: number;
931
+
932
+ /**
933
+ * false if anonymous connection are not allowed
934
+ */
935
+ public allowAnonymous: boolean = false;
936
+
937
+ /**
938
+ * the user manager
939
+ */
940
+ public userManager: UserManagerOptions;
941
+ public readonly options: OPCUAServerOptions;
942
+
943
+ private objectFactory?: Factory;
944
+
945
+ private _delayInit?: () => Promise<void>;
946
+
947
+ constructor(options?: OPCUAServerOptions) {
948
+ super(options);
949
+
950
+ options = options || {};
951
+
952
+ this.options = options;
953
+
954
+ /**
955
+ * @property maxAllowedSessionNumber
956
+ */
957
+ this.maxAllowedSessionNumber = options.maxAllowedSessionNumber || default_maxAllowedSessionNumber;
958
+ /**
959
+ * @property maxConnectionsPerEndpoint
960
+ */
961
+ this.maxConnectionsPerEndpoint = options.maxConnectionsPerEndpoint || default_maxConnectionsPerEndpoint;
962
+
963
+ // build Info
964
+ const buildInfo: BuildInfoOptions = {
965
+ ...default_build_info,
966
+ ...options.buildInfo
967
+ };
968
+
969
+ // repair product name
970
+ buildInfo.productUri = buildInfo.productUri || this.serverInfo.productUri;
971
+ this.serverInfo.productUri = this.serverInfo.productUri || buildInfo.productUri;
972
+
973
+ this.userManager = options.userManager || {};
974
+ if (typeof this.userManager.isValidUser !== "function") {
975
+ this.userManager.isValidUser = (/*userName,password*/) => {
976
+ return false;
977
+ };
978
+ }
979
+
980
+ options.allowAnonymous = options.allowAnonymous === undefined ? true : !!options.allowAnonymous;
981
+ /**
982
+ * @property allowAnonymous
983
+ */
984
+ this.allowAnonymous = options.allowAnonymous;
985
+
986
+ this.discoveryServerEndpointUrl = options.discoveryServerEndpointUrl || "opc.tcp://%FQDN%:4840";
987
+ assert(typeof this.discoveryServerEndpointUrl === "string");
988
+
989
+ this.serverInfo.applicationType = ApplicationType.Server;
990
+ this.capabilitiesForMDNS = options.capabilitiesForMDNS || ["NA"];
991
+ this.registerServerMethod = options.registerServerMethod || RegisterServerMethod.HIDDEN;
992
+ _installRegisterServerManager(this);
993
+
994
+ if (!options.userCertificateManager) {
995
+ this.userCertificateManager = getDefaultCertificateManager("UserPKI");
996
+ } else {
997
+ this.userCertificateManager = options.userCertificateManager;
998
+ }
999
+
1000
+ // note: we need to delay initialization of endpoint as certain resources
1001
+ // such as %FQDN% might not be ready yet at this stage
1002
+ this._delayInit = async () => {
1003
+ /* istanbul ignore next */
1004
+ if (!options) {
1005
+ throw new Error("Internal Error");
1006
+ }
1007
+ // to check => this.serverInfo.applicationName = this.serverInfo.productName || buildInfo.productName;
1008
+
1009
+ // note: applicationUri is handled in a special way
1010
+ this.engine = new ServerEngine({
1011
+ applicationUri: () => this.serverInfo.applicationUri!,
1012
+ buildInfo,
1013
+ isAuditing: options.isAuditing,
1014
+ serverCapabilities: options.serverCapabilities
1015
+ });
1016
+ this.objectFactory = new Factory(this.engine);
1017
+
1018
+ const endpointDefinitions = options.alternateEndpoints || [];
1019
+ const hostname = getFullyQualifiedDomainName();
1020
+
1021
+ endpointDefinitions.push({
1022
+ port: options.port || 26543,
1023
+
1024
+ allowAnonymous: options.allowAnonymous,
1025
+ alternateHostname: options.alternateHostname,
1026
+ disableDiscovery: options.disableDiscovery,
1027
+ hostname: options.hostname || hostname,
1028
+ securityModes: options.securityModes,
1029
+ securityPolicies: options.securityPolicies
1030
+ });
1031
+
1032
+ // todo should self.serverInfo.productUri match self.engine.buildInfo.productUri ?
1033
+ for (const endpointOptions of endpointDefinitions) {
1034
+ const endPoint = this.createEndpointDescriptions(options!, endpointOptions);
1035
+ this.endpoints.push(endPoint);
1036
+ endPoint.on("message", (message: Message, channel: ServerSecureChannelLayer) => {
1037
+ this.on_request(message, channel);
1038
+ });
1039
+
1040
+ endPoint.on("error", (err: Error) => {
1041
+ errorLog("OPCUAServer endpoint error", err);
1042
+ // set serverState to ServerState.Failed;
1043
+ this.engine.setServerState(ServerState.Failed);
1044
+ this.shutdown(() => {
1045
+ /* empty */
1046
+ });
1047
+ });
1048
+ }
1049
+ };
1050
+ }
1051
+
1052
+ /**
1053
+ * Initialize the server by installing default node set.
1054
+ *
1055
+ * and instruct the server to listen to its endpoints.
1056
+ *
1057
+ * ```javascript
1058
+ * const server = new OPCUAServer();
1059
+ * await server.initialize();
1060
+ *
1061
+ * // default server namespace is now initialized
1062
+ * // it is a good time to create life instance objects
1063
+ * const namespace = server.engine.addressSpace.getOwnNamespace();
1064
+ * namespace.addObject({
1065
+ * browseName: "SomeObject",
1066
+ * organizedBy: server.engine.addressSpace.rootFolder.objects
1067
+ * });
1068
+ *
1069
+ * // the addressSpace is now complete
1070
+ * // let's now start listening to clients
1071
+ * await server.start();
1072
+ * ```
1073
+ */
1074
+ public initialize(): Promise<void>;
1075
+ public initialize(done: () => void): void;
1076
+ public initialize(...args: [any?, ...any[]]): any {
1077
+ const done = args[0] as (err?: Error) => void;
1078
+ assert(!this.initialized, "server is already initialized"); // already initialized ?
1079
+
1080
+ this._preInitTask.push(async () => {
1081
+ /* istanbul ignore else */
1082
+ if (this._delayInit) {
1083
+ await this._delayInit();
1084
+ this._delayInit = undefined;
1085
+ }
1086
+ });
1087
+
1088
+ this.performPreInitialization()
1089
+ .then(() => {
1090
+ OPCUAServer.registry.register(this);
1091
+ this.engine.initialize(this.options, () => {
1092
+ setImmediate(() => {
1093
+ this.emit("post_initialize");
1094
+ done();
1095
+ });
1096
+ });
1097
+ })
1098
+ .catch((err) => {
1099
+ done(err);
1100
+ });
1101
+ }
1102
+
1103
+ /**
1104
+ * Initiate the server by starting all its endpoints
1105
+ * @async
1106
+ */
1107
+ public start(): Promise<void>;
1108
+ public start(done: () => void): void;
1109
+ public start(...args: [any?, ...any[]]): any {
1110
+ const done = args[0] as () => void;
1111
+ const tasks: any[] = [];
1112
+
1113
+ tasks.push(callbackify(extractFullyQualifiedDomainName));
1114
+
1115
+ if (!this.initialized) {
1116
+ tasks.push((callback: ErrorCallback) => {
1117
+ this.initialize(callback);
1118
+ });
1119
+ }
1120
+ tasks.push((callback: ErrorCallback) => {
1121
+ super.start((err?: Error | null) => {
1122
+ if (err) {
1123
+ this.shutdown((/*err2*/ err2?: Error) => {
1124
+ callback(err);
1125
+ });
1126
+ } else {
1127
+ // we start the registration process asynchronously
1128
+ // as we want to make server immediately available
1129
+ this.registerServerManager!.start(() => {
1130
+ /* empty */
1131
+ });
1132
+
1133
+ setImmediate(callback);
1134
+ }
1135
+ });
1136
+ });
1137
+
1138
+ async.series(tasks, done);
1139
+ }
1140
+
1141
+ /**
1142
+ * shutdown all server endpoints
1143
+ * @method shutdown
1144
+ * @async
1145
+ * @param timeout the timeout (in ms) before the server is actually shutdown
1146
+ *
1147
+ * @example
1148
+ *
1149
+ * ```javascript
1150
+ * // shutdown immediately
1151
+ * server.shutdown(function(err) {
1152
+ * });
1153
+ * ```
1154
+ * ```ts
1155
+ * // in typescript with promises
1156
+ * server.shutdown(10000).then(()=>{
1157
+ * console.log("Server has shutdown");
1158
+ * });
1159
+ * ```
1160
+ * ```javascript
1161
+ * // shutdown within 10 seconds
1162
+ * server.engine .shutdownReason = coerceLocalizedText("Shutdown for maintenance");
1163
+ * server.shutdown(10000,function(err) {
1164
+ * });
1165
+ * ```
1166
+ */
1167
+ public shutdown(timeout?: number): Promise<void>;
1168
+ public shutdown(callback: (err?: Error) => void): void;
1169
+ public shutdown(timeout: number, callback: (err?: Error) => void): void;
1170
+ public shutdown(...args: [any?, ...any[]]): any {
1171
+ const timeout = args.length === 1 ? OPCUAServer.defaultShutdownTimeout : (args[0] as number);
1172
+ const callback = (args.length === 1 ? args[0] : args[1]) as (err?: Error) => void;
1173
+ assert(typeof callback === "function");
1174
+ debugLog("OPCUAServer#shutdown (timeout = ", timeout, ")");
1175
+
1176
+ /* istanbul ignore next */
1177
+ if (!this.engine) {
1178
+ return callback();
1179
+ }
1180
+ assert(this.engine);
1181
+ if (!this.engine.isStarted()) {
1182
+ // server may have been shot down already , or may have fail to start !!
1183
+ const err = new Error("OPCUAServer#shutdown failure ! server doesn't seems to be started yet");
1184
+ return callback(err);
1185
+ }
1186
+
1187
+ this.userCertificateManager.dispose();
1188
+
1189
+ this.engine.setServerState(ServerState.Shutdown);
1190
+
1191
+ const shutdownTime = new Date(Date.now() + timeout);
1192
+ this.engine.setShutdownTime(shutdownTime);
1193
+
1194
+ debugLog("OPCUAServer is now unregistering itself from the discovery server " + this.buildInfo);
1195
+ this.registerServerManager!.stop((err?: Error | null) => {
1196
+ debugLog("OPCUAServer unregistered from discovery server", err);
1197
+ setTimeout(() => {
1198
+ this.engine.shutdown();
1199
+
1200
+ debugLog("OPCUAServer#shutdown: started");
1201
+ OPCUABaseServer.prototype.shutdown.call(this, (err1?: Error) => {
1202
+ debugLog("OPCUAServer#shutdown: completed");
1203
+
1204
+ this.dispose();
1205
+ callback(err1);
1206
+ });
1207
+ }, timeout);
1208
+ });
1209
+ }
1210
+
1211
+ public dispose() {
1212
+ for (const endpoint of this.endpoints) {
1213
+ endpoint.dispose();
1214
+ }
1215
+ this.endpoints = [];
1216
+
1217
+ this.removeAllListeners();
1218
+
1219
+ if (this.registerServerManager) {
1220
+ this.registerServerManager.dispose();
1221
+ this.registerServerManager = undefined;
1222
+ }
1223
+ OPCUAServer.registry.unregister(this);
1224
+
1225
+ /* istanbul ignore next */
1226
+ if (this.engine) {
1227
+ this.engine.dispose();
1228
+ }
1229
+ }
1230
+
1231
+ public raiseEvent(eventType: any, options: any): void {
1232
+ /* istanbul ignore next */
1233
+ if (!this.engine.addressSpace) {
1234
+ errorLog("addressSpace missing");
1235
+ return;
1236
+ }
1237
+
1238
+ const server = this.engine.addressSpace.findNode("Server") as UAObject;
1239
+
1240
+ /* istanbul ignore next */
1241
+ if (!server) {
1242
+ // xx throw new Error("OPCUAServer#raiseEvent : cannot find Server object");
1243
+ return;
1244
+ }
1245
+
1246
+ let eventTypeNode = eventType;
1247
+ if (typeof eventType === "string") {
1248
+ eventTypeNode = this.engine.addressSpace.findEventType(eventType);
1249
+ }
1250
+
1251
+ /* istanbul ignore else */
1252
+ if (eventTypeNode) {
1253
+ return server.raiseEvent(eventTypeNode, options);
1254
+ } else {
1255
+ console.warn(" cannot find event type ", eventType);
1256
+ }
1257
+ }
1258
+
1259
+ /**
1260
+ * create and register a new session
1261
+ * @internal
1262
+ */
1263
+ protected createSession(options: any): ServerSession {
1264
+ /* istanbul ignore next */
1265
+ if (!this.engine) {
1266
+ throw new Error("Internal Error");
1267
+ }
1268
+ return this.engine.createSession(options);
1269
+ }
1270
+
1271
+ /**
1272
+ * retrieve a session by authentication token
1273
+ * @internal
1274
+ */
1275
+ protected getSession(authenticationToken: NodeId, activeOnly?: boolean): ServerSession | null {
1276
+ return this.engine ? this.engine.getSession(authenticationToken, activeOnly) : null;
1277
+ }
1278
+
1279
+ /**
1280
+ *
1281
+ * @param channel
1282
+ * @param clientCertificate
1283
+ * @param clientNonce
1284
+ * @internal
1285
+ */
1286
+ protected computeServerSignature(
1287
+ channel: ServerSecureChannelLayer,
1288
+ clientCertificate: Certificate,
1289
+ clientNonce: Nonce
1290
+ ): SignatureData | undefined {
1291
+ return computeSignature(clientCertificate, clientNonce, this.getPrivateKey(), channel.messageBuilder.securityPolicy);
1292
+ }
1293
+
1294
+ /**
1295
+ *
1296
+ * @param session
1297
+ * @param channel
1298
+ * @param clientSignature
1299
+ * @internal
1300
+ */
1301
+ protected verifyClientSignature(
1302
+ session: ServerSession,
1303
+ channel: ServerSecureChannelLayer,
1304
+ clientSignature: SignatureData
1305
+ ): boolean {
1306
+ const clientCertificate = channel.receiverCertificate!;
1307
+ const securityPolicy = channel.messageBuilder.securityPolicy;
1308
+ const serverCertificate = this.getCertificate();
1309
+
1310
+ const result = verifySignature(serverCertificate, session.nonce!, clientSignature, clientCertificate, securityPolicy);
1311
+
1312
+ return result;
1313
+ }
1314
+
1315
+ protected isValidUserNameIdentityToken(
1316
+ channel: ServerSecureChannelLayer,
1317
+ session: ServerSession,
1318
+ userTokenPolicy: any,
1319
+ userIdentityToken: UserNameIdentityToken,
1320
+ userTokenSignature: any,
1321
+ callback: (err: Error | null, statusCode?: StatusCode) => void
1322
+ ) {
1323
+ assert(userIdentityToken instanceof UserNameIdentityToken);
1324
+
1325
+ const securityPolicy = adjustSecurityPolicy(channel, userTokenPolicy.securityPolicyUri);
1326
+ if (securityPolicy === SecurityPolicy.None) {
1327
+ return callback(null, StatusCodes.Good);
1328
+ }
1329
+ const cryptoFactory = getCryptoFactory(securityPolicy);
1330
+
1331
+ /* istanbul ignore next */
1332
+ if (!cryptoFactory) {
1333
+ return callback(null, StatusCodes.BadSecurityPolicyRejected);
1334
+ }
1335
+
1336
+ /* istanbul ignore next */
1337
+ if (userIdentityToken.encryptionAlgorithm !== cryptoFactory.asymmetricEncryptionAlgorithm) {
1338
+ errorLog("invalid encryptionAlgorithm");
1339
+ errorLog("userTokenPolicy", userTokenPolicy.toString());
1340
+ errorLog("userTokenPolicy", userIdentityToken.toString());
1341
+ return callback(null, StatusCodes.BadIdentityTokenInvalid);
1342
+ }
1343
+ const userName = userIdentityToken.userName;
1344
+ const password = userIdentityToken.password;
1345
+ if (!userName || !password) {
1346
+ return callback(null, StatusCodes.BadIdentityTokenInvalid);
1347
+ }
1348
+ return callback(null, StatusCodes.Good);
1349
+ }
1350
+
1351
+ protected isValidX509IdentityToken(
1352
+ channel: ServerSecureChannelLayer,
1353
+ session: ServerSession,
1354
+ userTokenPolicy: any,
1355
+ userIdentityToken: X509IdentityToken,
1356
+ userTokenSignature: any,
1357
+ callback: (err: Error | null, statusCode?: StatusCode) => void
1358
+ ) {
1359
+ assert(userIdentityToken instanceof X509IdentityToken);
1360
+ assert(callback instanceof Function);
1361
+
1362
+ const securityPolicy = adjustSecurityPolicy(channel, userTokenPolicy.securityPolicyUri);
1363
+
1364
+ const cryptoFactory = getCryptoFactory(securityPolicy);
1365
+ /* istanbul ignore next */
1366
+ if (!cryptoFactory) {
1367
+ return callback(null, StatusCodes.BadSecurityPolicyRejected);
1368
+ }
1369
+
1370
+ if (!userTokenSignature || !userTokenSignature.signature) {
1371
+ return callback(null, StatusCodes.BadUserSignatureInvalid);
1372
+ }
1373
+
1374
+ if (userIdentityToken.policyId !== userTokenPolicy.policyId) {
1375
+ errorLog("invalid encryptionAlgorithm");
1376
+ errorLog("userTokenPolicy", userTokenPolicy.toString());
1377
+ errorLog("userTokenPolicy", userIdentityToken.toString());
1378
+ return callback(null, StatusCodes.BadSecurityPolicyRejected);
1379
+ }
1380
+ const certificate = userIdentityToken.certificateData; /* as Certificate*/
1381
+ const nonce = session.nonce!;
1382
+ const serverCertificate = this.getCertificate();
1383
+
1384
+ assert(serverCertificate instanceof Buffer);
1385
+ assert(certificate instanceof Buffer, "expecting certificate to be a Buffer");
1386
+ assert(nonce instanceof Buffer, "expecting nonce to be a Buffer");
1387
+ assert(userTokenSignature.signature instanceof Buffer, "expecting userTokenSignature to be a Buffer");
1388
+
1389
+ // verify proof of possession by checking certificate signature & server nonce correctness
1390
+ if (!verifySignature(serverCertificate, nonce, userTokenSignature, certificate, securityPolicy)) {
1391
+ return callback(null, StatusCodes.BadUserSignatureInvalid);
1392
+ }
1393
+
1394
+ // verify if certificate is Valid
1395
+ this.userCertificateManager!.checkCertificate(certificate, (err, certificateStatus) => {
1396
+ /* istanbul ignore next */
1397
+ if (err) {
1398
+ return callback(err);
1399
+ }
1400
+ if (
1401
+ StatusCodes.BadCertificateUntrusted === certificateStatus ||
1402
+ StatusCodes.BadCertificateTimeInvalid === certificateStatus ||
1403
+ StatusCodes.BadCertificateIssuerTimeInvalid === certificateStatus ||
1404
+ StatusCodes.BadCertificateIssuerUseNotAllowed === certificateStatus ||
1405
+ StatusCodes.BadCertificateIssuerRevocationUnknown === certificateStatus ||
1406
+ StatusCodes.BadCertificateRevocationUnknown === certificateStatus ||
1407
+ StatusCodes.BadCertificateRevoked === certificateStatus ||
1408
+ StatusCodes.BadCertificateUseNotAllowed === certificateStatus ||
1409
+ StatusCodes.BadSecurityChecksFailed === certificateStatus ||
1410
+ StatusCodes.Good !== certificateStatus
1411
+ ) {
1412
+ debugLog("isValidX509IdentityToken => certificateStatus = ", certificateStatus?.toString());
1413
+ return callback(null, StatusCodes.BadIdentityTokenRejected);
1414
+ }
1415
+ if (StatusCodes.Good !== certificateStatus) {
1416
+ assert(certificateStatus instanceof StatusCode);
1417
+ return callback(null, certificateStatus);
1418
+ // return callback(null, StatusCodes.BadIdentityTokenInvalid);
1419
+ }
1420
+
1421
+ // verify if certificate is truster or rejected
1422
+ // todo: StatusCodes.BadCertificateUntrusted
1423
+
1424
+ // store untrusted certificate to rejected folder
1425
+ // todo:
1426
+ return callback(null, StatusCodes.Good);
1427
+ });
1428
+ }
1429
+
1430
+ /**
1431
+ * @internal
1432
+ */
1433
+ protected userNameIdentityTokenAuthenticateUser(
1434
+ channel: ServerSecureChannelLayer,
1435
+ session: ServerSession,
1436
+ userTokenPolicy: any,
1437
+ userIdentityToken: UserNameIdentityToken,
1438
+ callback: (err: Error | null, isAuthorized?: boolean) => void
1439
+ ): void {
1440
+ assert(userIdentityToken instanceof UserNameIdentityToken);
1441
+ // assert(this.isValidUserNameIdentityToken(channel, session, userTokenPolicy, userIdentityToken));
1442
+
1443
+ const securityPolicy = adjustSecurityPolicy(channel, userTokenPolicy.securityPolicyUri);
1444
+
1445
+ const userName = userIdentityToken.userName!;
1446
+ let password: any = userIdentityToken.password;
1447
+
1448
+ // decrypt password if necessary
1449
+ if (securityPolicy === SecurityPolicy.None) {
1450
+ // not good, password was sent in clear text ...
1451
+ password = password.toString();
1452
+ } else {
1453
+ const serverPrivateKey = this.getPrivateKey();
1454
+
1455
+ const serverNonce = session.nonce!;
1456
+ assert(serverNonce instanceof Buffer);
1457
+
1458
+ const cryptoFactory = getCryptoFactory(securityPolicy);
1459
+ /* istanbul ignore next */
1460
+ if (!cryptoFactory) {
1461
+ return callback(new Error(" Unsupported security Policy"));
1462
+ }
1463
+
1464
+ const buff = cryptoFactory.asymmetricDecrypt(password, serverPrivateKey);
1465
+
1466
+ // server certificate may be invalid and asymmetricDecrypt may fail
1467
+ if (!buff || buff.length < 4) {
1468
+ async.setImmediate(() => callback(null, false));
1469
+ return;
1470
+ }
1471
+
1472
+ const length = buff.readUInt32LE(0) - serverNonce.length;
1473
+ password = buff.slice(4, 4 + length).toString("utf-8");
1474
+ }
1475
+
1476
+ if (typeof this.userManager.isValidUserAsync === "function") {
1477
+ this.userManager.isValidUserAsync.call(session, userName, password, callback);
1478
+ } else {
1479
+ const authorized = this.userManager.isValidUser!.call(session, userName, password);
1480
+ async.setImmediate(() => callback(null, authorized));
1481
+ }
1482
+ }
1483
+
1484
+ /**
1485
+ * @internal
1486
+ */
1487
+ protected isValidUserIdentityToken(
1488
+ channel: ServerSecureChannelLayer,
1489
+ session: ServerSession,
1490
+ userIdentityToken: UserIdentityToken,
1491
+ userTokenSignature: any,
1492
+ endpointDescription: EndpointDescription,
1493
+ callback: (err: Error | null, statusCode?: StatusCode) => void
1494
+ ): void {
1495
+ assert(callback instanceof Function);
1496
+ /* istanbul ignore next */
1497
+ if (!userIdentityToken) {
1498
+ throw new Error("Invalid token");
1499
+ }
1500
+
1501
+ const userTokenType = getTokenType(userIdentityToken);
1502
+
1503
+ const userTokenPolicy = findUserTokenByPolicy(endpointDescription, userTokenType, userIdentityToken.policyId!);
1504
+ if (!userTokenPolicy) {
1505
+ // cannot find token with this policyId
1506
+ return callback(null, StatusCodes.BadIdentityTokenInvalid);
1507
+ }
1508
+ //
1509
+ if (userIdentityToken instanceof UserNameIdentityToken) {
1510
+ return this.isValidUserNameIdentityToken(
1511
+ channel,
1512
+ session,
1513
+ userTokenPolicy,
1514
+ userIdentityToken,
1515
+ userTokenSignature,
1516
+ callback
1517
+ );
1518
+ }
1519
+ if (userIdentityToken instanceof X509IdentityToken) {
1520
+ return this.isValidX509IdentityToken(
1521
+ channel,
1522
+ session,
1523
+ userTokenPolicy,
1524
+ userIdentityToken,
1525
+ userTokenSignature,
1526
+ callback
1527
+ );
1528
+ }
1529
+
1530
+ return callback(null, StatusCodes.Good);
1531
+ }
1532
+
1533
+ /**
1534
+ *
1535
+ * @internal
1536
+ * @param channel
1537
+ * @param session
1538
+ * @param userIdentityToken
1539
+ * @param callback
1540
+ * @returns {*}
1541
+ */
1542
+ protected isUserAuthorized(
1543
+ channel: ServerSecureChannelLayer,
1544
+ session: ServerSession,
1545
+ userIdentityToken: UserIdentityToken,
1546
+ callback: (err: Error | null, isAuthorized?: boolean) => void
1547
+ ) {
1548
+ assert(userIdentityToken);
1549
+ assert(typeof callback === "function");
1550
+
1551
+ const userTokenType = getTokenType(userIdentityToken);
1552
+ const userTokenPolicy = findUserTokenByPolicy(session.getEndpointDescription(), userTokenType, userIdentityToken.policyId!);
1553
+ assert(userTokenPolicy);
1554
+ // find if a userToken exists
1555
+ if (userIdentityToken instanceof UserNameIdentityToken) {
1556
+ return this.userNameIdentityTokenAuthenticateUser(channel, session, userTokenPolicy, userIdentityToken, callback);
1557
+ }
1558
+ async.setImmediate(callback.bind(null, null, true));
1559
+ }
1560
+
1561
+ protected makeServerNonce(): Nonce {
1562
+ return crypto.randomBytes(32);
1563
+ }
1564
+
1565
+ // session services
1566
+ protected async _on_CreateSessionRequest(message: Message, channel: ServerSecureChannelLayer) {
1567
+ const server = this;
1568
+ const request = message.request as CreateSessionRequest;
1569
+ assert(request instanceof CreateSessionRequest);
1570
+
1571
+ function rejectConnection(statusCode: StatusCode): void {
1572
+ server.engine.incrementSecurityRejectedSessionCount();
1573
+
1574
+ const response1 = new CreateSessionResponse({
1575
+ responseHeader: { serviceResult: statusCode }
1576
+ });
1577
+ channel.send_response("MSG", response1, message);
1578
+ // and close !
1579
+ }
1580
+
1581
+ // From OPCUA V1.03 Part 4 5.6.2 CreateSession
1582
+ // A Server application should limit the number of Sessions. To protect against misbehaving Clients and denial
1583
+ // of service attacks, the Server shall close the oldest Session that is not activated before reaching the
1584
+ // maximum number of supported Sessions
1585
+ if (server.currentSessionCount >= server.maxAllowedSessionNumber) {
1586
+ await _attempt_to_close_some_old_unactivated_session(server);
1587
+ }
1588
+
1589
+ // check if session count hasn't reach the maximum allowed sessions
1590
+ if (server.currentSessionCount >= server.maxAllowedSessionNumber) {
1591
+ return rejectConnection(StatusCodes.BadTooManySessions);
1592
+ }
1593
+
1594
+ // Release 1.03 OPC Unified Architecture, Part 4 page 24 - CreateSession Parameters
1595
+ // client should prove a sessionName
1596
+ // Session name is a Human readable string that identifies the Session. The Server makes this name and the
1597
+ // sessionId visible in its AddressSpace for diagnostic purposes. The Client should provide a name that is
1598
+ // unique for the instance of the Client.
1599
+ // If this parameter is not specified the Server shall assign a value.
1600
+
1601
+ if (utils.isNullOrUndefined(request.sessionName)) {
1602
+ // see also #198
1603
+ // let's the server assign a sessionName for this lazy client.
1604
+
1605
+ debugLog(
1606
+ "assigning OPCUAServer.fallbackSessionName because client's sessionName is null ",
1607
+ OPCUAServer.fallbackSessionName
1608
+ );
1609
+
1610
+ request.sessionName = OPCUAServer.fallbackSessionName;
1611
+ }
1612
+
1613
+ // Duration Requested maximum number of milliseconds that a Session should remain open without activity.
1614
+ // If the Client fails to issue a Service request within this interval, then the Server shall automatically
1615
+ // terminate the Client Session.
1616
+ const revisedSessionTimeout = _adjust_session_timeout(request.requestedSessionTimeout);
1617
+
1618
+ // Release 1.02 page 27 OPC Unified Architecture, Part 4: CreateSession.clientNonce
1619
+ // A random number that should never be used in any other request. This number shall have a minimum length of 32
1620
+ // bytes. Profiles may increase the required length. The Server shall use this value to prove possession of
1621
+ // its application instance Certificate in the response.
1622
+ if (!request.clientNonce || request.clientNonce.length < 32) {
1623
+ if (channel.securityMode !== MessageSecurityMode.None) {
1624
+ errorLog(
1625
+ chalk.red("SERVER with secure connection: Missing or invalid client Nonce "),
1626
+ request.clientNonce && request.clientNonce.toString("hex")
1627
+ );
1628
+
1629
+ return rejectConnection(StatusCodes.BadNonceInvalid);
1630
+ }
1631
+ }
1632
+ if (nonceAlreadyBeenUsed(request.clientNonce)) {
1633
+ errorLog(
1634
+ chalk.red("SERVER with secure connection: None has already been used"),
1635
+ request.clientNonce && request.clientNonce.toString("hex")
1636
+ );
1637
+
1638
+ return rejectConnection(StatusCodes.BadNonceInvalid);
1639
+ }
1640
+
1641
+ function validate_applicationUri(applicationUri: string, clientCertificate: Certificate): boolean {
1642
+ // if session is insecure there is no need to check certificate information
1643
+ if (channel.securityMode === MessageSecurityMode.None) {
1644
+ return true; // assume correct
1645
+ }
1646
+ if (!clientCertificate || clientCertificate.length === 0) {
1647
+ return true; // can't check
1648
+ }
1649
+ const e = exploreCertificate(clientCertificate);
1650
+ const applicationUriFromCert = e.tbsCertificate.extensions!.subjectAltName.uniformResourceIdentifier[0];
1651
+
1652
+ /* istanbul ignore next */
1653
+ if (applicationUriFromCert !== applicationUri) {
1654
+ errorLog("BadCertificateUriInvalid!");
1655
+ errorLog("applicationUri = ", applicationUri);
1656
+ errorLog("applicationUriFromCert = ", applicationUriFromCert);
1657
+ }
1658
+
1659
+ return applicationUriFromCert === applicationUri;
1660
+ }
1661
+
1662
+ // check application spoofing
1663
+ // check if applicationUri in createSessionRequest matches applicationUri in client Certificate
1664
+ if (!validate_applicationUri(request.clientDescription.applicationUri!, request.clientCertificate)) {
1665
+ return rejectConnection(StatusCodes.BadCertificateUriInvalid);
1666
+ }
1667
+
1668
+ function validate_security_endpoint(channel1: ServerSecureChannelLayer): {
1669
+ errCode: StatusCode;
1670
+ endpoint?: EndpointDescription;
1671
+ } {
1672
+ debugLog("validate_security_endpoint = ", request.endpointUrl);
1673
+ let endpoints = server._get_endpoints(request.endpointUrl);
1674
+ // endpointUrl String The network address that the Client used to access the Session Endpoint.
1675
+ // The HostName portion of the URL should be one of the HostNames for the application that are
1676
+ // specified in the Server’s ApplicationInstanceCertificate (see 7.2). The Server shall raise an
1677
+ // AuditUrlMismatchEventType event if the URL does not match the Server’s HostNames.
1678
+ // AuditUrlMismatchEventType event type is defined in Part 5.
1679
+ // The Server uses this information for diagnostics and to determine the set of
1680
+ // EndpointDescriptions to return in the response.
1681
+ // ToDo: check endpointUrl validity and emit an AuditUrlMismatchEventType event if not
1682
+ if (endpoints.length === 0) {
1683
+ // we have a UrlMismatch here
1684
+ const ua_server = server.engine.addressSpace!.rootFolder.objects.server;
1685
+ ua_server.raiseEvent("AuditUrlMismatchEventType", {
1686
+ endpointUrl: { dataType: DataType.String, value: request.endpointUrl }
1687
+ });
1688
+ debugLog("Cannot find endpoint in available endpoints with endpointUri", request.endpointUrl);
1689
+ if (OPCUAServer.requestExactEndpointUrl) {
1690
+ return { errCode: StatusCodes.BadServiceUnsupported };
1691
+ } else {
1692
+ endpoints = server._get_endpoints(null);
1693
+ }
1694
+ }
1695
+ // ignore restricted endpoints
1696
+ endpoints = endpoints.filter((e: EndpointDescription) => !(e as any).restricted);
1697
+
1698
+ const endpoints_matching_security_mode = endpoints.filter((e: EndpointDescription) => {
1699
+ return e.securityMode === channel1.securityMode;
1700
+ });
1701
+
1702
+ if (endpoints_matching_security_mode.length === 0) {
1703
+ return { errCode: StatusCodes.BadSecurityModeRejected };
1704
+ }
1705
+ const endpoints_matching_security_policy = endpoints_matching_security_mode.filter((e: EndpointDescription) => {
1706
+ return e.securityPolicyUri === channel1.securityHeader!.securityPolicyUri;
1707
+ });
1708
+
1709
+ if (endpoints_matching_security_policy.length === 0) {
1710
+ return { errCode: StatusCodes.BadSecurityPolicyRejected };
1711
+ }
1712
+ if (endpoints_matching_security_policy.length !== 1) {
1713
+ debugLog("endpoints_matching_security_policy= ", endpoints_matching_security_policy.length);
1714
+ }
1715
+ return { errCode: StatusCodes.Good, endpoint: endpoints_matching_security_policy[0] };
1716
+ }
1717
+
1718
+ const { errCode, endpoint } = validate_security_endpoint(channel);
1719
+ if (errCode !== StatusCodes.Good) {
1720
+ return rejectConnection(errCode);
1721
+ }
1722
+
1723
+ // see Release 1.02 27 OPC Unified Architecture, Part 4
1724
+ const session = server.createSession({
1725
+ clientDescription: request.clientDescription,
1726
+ sessionTimeout: revisedSessionTimeout
1727
+ });
1728
+ session.endpoint = endpoint;
1729
+
1730
+ assert(session);
1731
+ assert(session.sessionTimeout === revisedSessionTimeout);
1732
+
1733
+ session.clientDescription = request.clientDescription;
1734
+ session.sessionName = request.sessionName || "<unknown session name>";
1735
+
1736
+ // Depending upon on the SecurityPolicy and the SecurityMode of the SecureChannel, the exchange of
1737
+ // ApplicationInstanceCertificates and Nonces may be optional and the signatures may be empty. See
1738
+ // Part 7 for the definition of SecurityPolicies and the handling of these parameters
1739
+
1740
+ // serverNonce:
1741
+ // A random number that should never be used in any other request.
1742
+ // This number shall have a minimum length of 32 bytes.
1743
+ // The Client shall use this value to prove possession of its application instance
1744
+ // Certificate in the ActivateSession request.
1745
+ // This value may also be used to prove possession of the userIdentityToken it
1746
+ // specified in the ActivateSession request.
1747
+ //
1748
+ // ( this serverNonce will only be used up to the _on_ActivateSessionRequest
1749
+ // where a new nonce will be created)
1750
+ session.nonce = server.makeServerNonce();
1751
+ session.channelId = channel.channelId;
1752
+
1753
+ session._attach_channel(channel);
1754
+
1755
+ const serverCertificateChain = server.getCertificateChain();
1756
+
1757
+ const hasEncryption = true;
1758
+ // If the securityPolicyUri is None and none of the UserTokenPolicies requires encryption
1759
+ if (session.channel!.securityMode === MessageSecurityMode.None) {
1760
+ // ToDo: Check that none of our insecure endpoint has a a UserTokenPolicy that require encryption
1761
+ // and set hasEncryption = false under this condition
1762
+ }
1763
+
1764
+ const response = new CreateSessionResponse({
1765
+ // A identifier which uniquely identifies the session.
1766
+ sessionId: session.nodeId,
1767
+
1768
+ // A unique identifier assigned by the Server to the Session.
1769
+ // The token used to authenticate the client in subsequent requests.
1770
+ authenticationToken: session.authenticationToken,
1771
+
1772
+ revisedSessionTimeout,
1773
+
1774
+ serverNonce: session.nonce,
1775
+
1776
+ // serverCertificate: type ApplicationServerCertificate
1777
+ // The application instance Certificate issued to the Server.
1778
+ // A Server shall prove possession by using the private key to sign the Nonce provided
1779
+ // by the Client in the request. The Client shall verify that this Certificate is the same as
1780
+ // the one it used to create the SecureChannel.
1781
+ // The ApplicationInstanceCertificate type is defined in OpCUA 1.03 part 4 - $7.2 page 108
1782
+ // If the securityPolicyUri is None and none of the UserTokenPolicies requires
1783
+ // encryption, the Server shall not send an ApplicationInstanceCertificate and the Client
1784
+ // shall ignore the ApplicationInstanceCertificate.
1785
+ serverCertificate: hasEncryption ? serverCertificateChain : undefined,
1786
+
1787
+ // The endpoints provided by the server.
1788
+ // The Server shall return a set of EndpointDescriptions available for the serverUri
1789
+ // specified in the request.[...]
1790
+ // The Client shall verify this list with the list from a Discovery Endpoint if it used a Discovery
1791
+ // Endpoint to fetch the EndpointDescriptions.
1792
+ // It is recommended that Servers only include the endpointUrl, securityMode,
1793
+ // securityPolicyUri, userIdentityTokens, transportProfileUri and securityLevel with all
1794
+ // other parameters set to null. Only the recommended parameters shall be verified by
1795
+ // the client.
1796
+ serverEndpoints: _serverEndpointsForCreateSessionResponse(server, session.endpoint!.endpointUrl, request.serverUri),
1797
+
1798
+ // This parameter is deprecated and the array shall be empty.
1799
+ serverSoftwareCertificates: null,
1800
+
1801
+ // This is a signature generated with the private key associated with the
1802
+ // serverCertificate. This parameter is calculated by appending the clientNonce to the
1803
+ // clientCertificate and signing the resulting sequence of bytes.
1804
+ // The SignatureAlgorithm shall be the AsymmetricSignatureAlgorithm specified in the
1805
+ // SecurityPolicy for the Endpoint.
1806
+ // The SignatureData type is defined in 7.30.
1807
+ serverSignature: server.computeServerSignature(channel, request.clientCertificate, request.clientNonce),
1808
+
1809
+ // The maximum message size accepted by the server
1810
+ // The Client Communication Stack should return a Bad_RequestTooLarge error to the
1811
+ // application if a request message exceeds this limit.
1812
+ // The value zero indicates that this parameter is not used.
1813
+ maxRequestMessageSize: 0x4000000
1814
+ });
1815
+
1816
+ server.emit("create_session", session);
1817
+
1818
+ session.on("session_closed", (session1: ServerSession, deleteSubscriptions: boolean, reason: string) => {
1819
+ assert(typeof reason === "string");
1820
+ if (server.isAuditing) {
1821
+ assert(reason === "Timeout" || reason === "Terminated" || reason === "CloseSession" || reason === "Forcing");
1822
+ const sourceName = "Session/" + reason;
1823
+
1824
+ server.raiseEvent("AuditSessionEventType", {
1825
+ /* part 5 - 6.4.3 AuditEventType */
1826
+ actionTimeStamp: { dataType: "DateTime", value: new Date() },
1827
+ status: { dataType: "Boolean", value: true },
1828
+
1829
+ serverId: { dataType: "String", value: "" },
1830
+
1831
+ // ClientAuditEntryId contains the human-readable AuditEntryId defined in Part 3.
1832
+ clientAuditEntryId: { dataType: "String", value: "" },
1833
+
1834
+ // The ClientUserId identifies the user of the client requesting an action. The ClientUserId can be
1835
+ // obtained from the UserIdentityToken passed in the ActivateSession call.
1836
+ clientUserId: { dataType: "String", value: "" },
1837
+
1838
+ sourceName: { dataType: "String", value: sourceName },
1839
+
1840
+ /* part 5 - 6.4.7 AuditSessionEventType */
1841
+ sessionId: { dataType: "NodeId", value: session1.nodeId }
1842
+ });
1843
+ }
1844
+
1845
+ server.emit("session_closed", session1, deleteSubscriptions);
1846
+ });
1847
+
1848
+ if (server.isAuditing) {
1849
+ // ------------------------------------------------------------------------------------------------------
1850
+ server.raiseEvent("AuditCreateSessionEventType", {
1851
+ /* part 5 - 6.4.3 AuditEventType */
1852
+ actionTimeStamp: { dataType: "DateTime", value: new Date() },
1853
+ status: { dataType: "Boolean", value: true },
1854
+
1855
+ serverId: { dataType: "String", value: "" },
1856
+
1857
+ // ClientAuditEntryId contains the human-readable AuditEntryId defined in Part 3.
1858
+ clientAuditEntryId: { dataType: "String", value: "" },
1859
+
1860
+ // The ClientUserId identifies the user of the client requesting an action. The ClientUserId can be
1861
+ // obtained from the UserIdentityToken passed in the ActivateSession call.
1862
+ clientUserId: { dataType: "String", value: "" },
1863
+
1864
+ sourceName: { dataType: "String", value: "Session/CreateSession" },
1865
+
1866
+ /* part 5 - 6.4.7 AuditSessionEventType */
1867
+ sessionId: { dataType: "NodeId", value: session.nodeId },
1868
+
1869
+ /* part 5 - 6.4.8 AuditCreateSessionEventType */
1870
+ // SecureChannelId shall uniquely identify the SecureChannel. The application shall use the same
1871
+ // identifier in all AuditEvents related to the Session Service Set (AuditCreateSessionEventType,
1872
+ // AuditActivateSessionEventType and their subtypes) and the SecureChannel Service Set
1873
+ // (AuditChannelEventType and its subtypes
1874
+ secureChannelId: { dataType: "String", value: session.channel!.channelId!.toString() },
1875
+
1876
+ // Duration
1877
+ revisedSessionTimeout: { dataType: "Duration", value: session.sessionTimeout },
1878
+
1879
+ // clientCertificate
1880
+ clientCertificate: { dataType: "ByteString", value: session.channel!.clientCertificate },
1881
+
1882
+ // clientCertificateThumbprint
1883
+ clientCertificateThumbprint: {
1884
+ dataType: "ByteString",
1885
+ value: thumbprint(session.channel!.clientCertificate!)
1886
+ }
1887
+ });
1888
+ }
1889
+ // -----------------------------------------------------------------------------------------------------------
1890
+
1891
+ assert(response.authenticationToken);
1892
+ channel.send_response("MSG", response, message);
1893
+ }
1894
+
1895
+ // TODO : implement this:
1896
+ //
1897
+ // When the ActivateSession Service is called for the first time then the Server shall reject the request
1898
+ // if the SecureChannel is not same as the one associated with the CreateSession request.
1899
+ // Subsequent calls to ActivateSession may be associated with different SecureChannels. If this is the
1900
+ // case then the Server shall verify that the Certificate the Client used to create the new
1901
+ // SecureChannel is the same as the Certificate used to create the original SecureChannel. In addition,
1902
+ // the Server shall verify that the Client supplied a UserIdentityToken that is identical to the token
1903
+ // currently associated with the Session. Once the Server accepts the new SecureChannel it shall
1904
+ // reject requests sent via the old SecureChannel.
1905
+ /**
1906
+ *
1907
+ * @method _on_ActivateSessionRequest
1908
+ * @private
1909
+ *
1910
+ *
1911
+ */
1912
+ protected _on_ActivateSessionRequest(message: Message, channel: ServerSecureChannelLayer) {
1913
+ const server = this;
1914
+ const request = message.request as ActivateSessionRequest;
1915
+ assert(request instanceof ActivateSessionRequest);
1916
+
1917
+ // get session from authenticationToken
1918
+ const authenticationToken = request.requestHeader.authenticationToken;
1919
+
1920
+ const session = server.getSession(authenticationToken);
1921
+
1922
+ function rejectConnection(statusCode: StatusCode): void {
1923
+ if (statusCode === StatusCodes.BadSessionIdInvalid) {
1924
+ server.engine.incrementRejectedSessionCount();
1925
+ } else {
1926
+ server.engine.incrementRejectedSessionCount();
1927
+ server.engine.incrementSecurityRejectedSessionCount();
1928
+ }
1929
+
1930
+ const response1 = new ActivateSessionResponse({ responseHeader: { serviceResult: statusCode } });
1931
+
1932
+ channel.send_response("MSG", response1, message);
1933
+ // and close !
1934
+ }
1935
+
1936
+ let response;
1937
+
1938
+ /* istanbul ignore next */
1939
+ if (!session) {
1940
+ // this may happen when the server has been restarted and a client tries to reconnect, thinking
1941
+ // that the previous session may still be active
1942
+ debugLog(chalk.yellow.bold(" Bad Session in _on_ActivateSessionRequest"), authenticationToken.toString());
1943
+
1944
+ return rejectConnection(StatusCodes.BadSessionIdInvalid);
1945
+ }
1946
+
1947
+ // tslint:disable-next-line: no-unused-expression
1948
+ session.keepAlive ? session.keepAlive() : void 0;
1949
+
1950
+ // OpcUA 1.02 part 3 $5.6.3.1 ActiveSession Set page 29
1951
+ // When the ActivateSession Service is called f or the first time then the Server shall reject the request
1952
+ // if the SecureChannel is not same as the one associated with the CreateSession request.
1953
+ if (session.status === "new") {
1954
+ // xx if (channel.session_nonce !== session.nonce) {
1955
+ if (!channel_has_session(channel, session)) {
1956
+ // it looks like session activation is being using a channel that is not the
1957
+ // one that have been used to create the session
1958
+ errorLog(" channel.sessionTokens === " + Object.keys(channel.sessionTokens).join(" "));
1959
+ return rejectConnection(StatusCodes.BadSessionNotActivated);
1960
+ }
1961
+ }
1962
+
1963
+ // OpcUA 1.02 part 3 $5.6.3.1 ActiveSession Set page 29
1964
+ // ... Subsequent calls to ActivateSession may be associated with different SecureChannels. If this is the
1965
+ // case then the Server shall verify that the Certificate the Client used to create the new
1966
+ // SecureChannel is the same as the Certificate used to create the original SecureChannel.
1967
+
1968
+ if (session.status === "active") {
1969
+ if (session.channel!.channelId !== channel.channelId) {
1970
+ warningLog(
1971
+ " Session ",
1972
+ session.sessionName,
1973
+ " is being transferred from channel",
1974
+ chalk.cyan(session.channel!.channelId!.toString()),
1975
+ " to channel ",
1976
+ chalk.cyan(channel.channelId!.toString())
1977
+ );
1978
+
1979
+ // session is being reassigned to a new Channel,
1980
+ // we shall verify that the certificate used to create the Session is the same as the current
1981
+ // channel certificate.
1982
+ const old_channel_cert_thumbprint = thumbprint(session.channel!.clientCertificate!);
1983
+ const new_channel_cert_thumbprint = thumbprint(channel.clientCertificate!);
1984
+
1985
+ if (old_channel_cert_thumbprint !== new_channel_cert_thumbprint) {
1986
+ return rejectConnection(StatusCodes.BadNoValidCertificates); // not sure about this code !
1987
+ }
1988
+
1989
+ // ... In addition the Server shall verify that the Client supplied a UserIdentityToken that is
1990
+ // identical to the token currently associated with the Session reassign session to new channel.
1991
+ if (!sameIdentityToken(session.userIdentityToken!, request.userIdentityToken as UserIdentityToken)) {
1992
+ return rejectConnection(StatusCodes.BadIdentityChangeNotSupported); // not sure about this code !
1993
+ }
1994
+ }
1995
+
1996
+ moveSessionToChannel(session, channel);
1997
+ } else if (session.status === "screwed") {
1998
+ // session has been used before being activated => this should be detected and session should be dismissed.
1999
+ return rejectConnection(StatusCodes.BadSessionClosed);
2000
+ } else if (session.status === "closed") {
2001
+ warningLog(chalk.yellow.bold(" Bad Session Closed in _on_ActivateSessionRequest"), authenticationToken.toString());
2002
+ return rejectConnection(StatusCodes.BadSessionClosed);
2003
+ }
2004
+
2005
+ // verify clientSignature provided by the client
2006
+ if (!server.verifyClientSignature(session, channel, request.clientSignature)) {
2007
+ return rejectConnection(StatusCodes.BadApplicationSignatureInvalid);
2008
+ }
2009
+
2010
+ // userIdentityToken may be missing , assume anonymous access then
2011
+ request.userIdentityToken = request.userIdentityToken || createAnonymousIdentityToken(session.endpoint!);
2012
+
2013
+ // check request.userIdentityToken is correct ( expected type and correctly formed)
2014
+ server.isValidUserIdentityToken(
2015
+ channel,
2016
+ session,
2017
+ request.userIdentityToken as UserIdentityToken,
2018
+ request.userTokenSignature,
2019
+ session.endpoint!,
2020
+ (err: Error | null, statusCode?: StatusCode) => {
2021
+ if (statusCode !== StatusCodes.Good) {
2022
+ /* istanbul ignore next */
2023
+ if (!(statusCode && statusCode instanceof StatusCode)) {
2024
+ const a = 23;
2025
+ }
2026
+ assert(statusCode && statusCode instanceof StatusCode, "expecting statusCode");
2027
+ return rejectConnection(statusCode!);
2028
+ }
2029
+ session.userIdentityToken = request.userIdentityToken as UserIdentityToken;
2030
+
2031
+ // check if user access is granted
2032
+ server.isUserAuthorized(
2033
+ channel,
2034
+ session,
2035
+ request.userIdentityToken as UserIdentityToken,
2036
+ (err1: Error | null, authorized?: boolean) => {
2037
+ /* istanbul ignore next */
2038
+ if (err1) {
2039
+ return rejectConnection(StatusCodes.BadInternalError);
2040
+ }
2041
+
2042
+ if (!authorized) {
2043
+ return rejectConnection(StatusCodes.BadUserAccessDenied);
2044
+ } else {
2045
+ // extract : OPC UA part 4 - 5.6.3
2046
+ // Once used, a serverNonce cannot be used again. For that reason, the Server returns a new
2047
+ // serverNonce each time the ActivateSession Service is called.
2048
+ session.nonce = server.makeServerNonce();
2049
+
2050
+ session.status = "active";
2051
+
2052
+ response = new ActivateSessionResponse({ serverNonce: session.nonce });
2053
+ channel.send_response("MSG", response, message);
2054
+
2055
+ const userIdentityTokenPasswordRemoved = (userIdentityToken: any) => {
2056
+ const a = userIdentityToken.clone();
2057
+ // remove password
2058
+ a.password = "*************";
2059
+ return a;
2060
+ };
2061
+
2062
+ // send OPCUA Event Notification
2063
+ // see part 5 : 6.4.3 AuditEventType
2064
+ // 6.4.7 AuditSessionEventType
2065
+ // 6.4.10 AuditActivateSessionEventType
2066
+ assert(session.nodeId); // sessionId
2067
+ // xx assert(session.channel.clientCertificate instanceof Buffer);
2068
+ assert(session.sessionTimeout > 0);
2069
+
2070
+ if (server.isAuditing) {
2071
+ server.raiseEvent("AuditActivateSessionEventType", {
2072
+ /* part 5 - 6.4.3 AuditEventType */
2073
+ actionTimeStamp: { dataType: "DateTime", value: new Date() },
2074
+ status: { dataType: "Boolean", value: true },
2075
+
2076
+ serverId: { dataType: "String", value: "" },
2077
+
2078
+ // ClientAuditEntryId contains the human-readable AuditEntryId defined in Part 3.
2079
+ clientAuditEntryId: { dataType: "String", value: "" },
2080
+
2081
+ // The ClientUserId identifies the user of the client requesting an action.
2082
+ // The ClientUserId can be obtained from the UserIdentityToken passed in the
2083
+ // ActivateSession call.
2084
+ clientUserId: { dataType: "String", value: "cc" },
2085
+
2086
+ sourceName: { dataType: "String", value: "Session/ActivateSession" },
2087
+
2088
+ /* part 5 - 6.4.7 AuditSessionEventType */
2089
+ sessionId: { dataType: "NodeId", value: session.nodeId },
2090
+
2091
+ /* part 5 - 6.4.10 AuditActivateSessionEventType */
2092
+ clientSoftwareCertificates: {
2093
+ arrayType: VariantArrayType.Array,
2094
+ dataType: "ExtensionObject" /* SignedSoftwareCertificate */,
2095
+ value: []
2096
+ },
2097
+ // UserIdentityToken reflects the userIdentityToken parameter of the ActivateSession
2098
+ // Service call.
2099
+ // For Username/Password tokens the password should NOT be included.
2100
+ userIdentityToken: {
2101
+ dataType: "ExtensionObject" /* UserIdentityToken */,
2102
+ value: userIdentityTokenPasswordRemoved(session.userIdentityToken)
2103
+ },
2104
+
2105
+ // SecureChannelId shall uniquely identify the SecureChannel. The application shall
2106
+ // use the same identifier in all AuditEvents related to the Session Service Set
2107
+ // (AuditCreateSessionEventType, AuditActivateSessionEventType and their subtypes) and
2108
+ // the SecureChannel Service Set (AuditChannelEventType and its subtypes).
2109
+ secureChannelId: { dataType: "String", value: session.channel!.channelId!.toString() }
2110
+ });
2111
+ }
2112
+ server.emit("session_activated", session, userIdentityTokenPasswordRemoved);
2113
+ }
2114
+ }
2115
+ );
2116
+ }
2117
+ );
2118
+ }
2119
+
2120
+ protected prepare(message: Message, channel: ServerSecureChannelLayer) {
2121
+ const server = this;
2122
+ const request = message.request;
2123
+
2124
+ // --- check that session is correct
2125
+ const authenticationToken = request.requestHeader.authenticationToken;
2126
+ const session = server.getSession(authenticationToken, /*activeOnly*/ true);
2127
+ if (!session) {
2128
+ message.session_statusCode = StatusCodes.BadSessionIdInvalid;
2129
+ return;
2130
+ }
2131
+ message.session = session;
2132
+
2133
+ // --- check that provided session matches session attached to channel
2134
+ if (channel.channelId !== session.channelId) {
2135
+ if (!(request instanceof ActivateSessionRequest)) {
2136
+ errorLog(chalk.red.bgWhite("ERROR: channel.channelId !== session.channelId"), channel.channelId, session.channelId);
2137
+ }
2138
+ message.session_statusCode = StatusCodes.BadSecureChannelIdInvalid;
2139
+ } else if (channel_has_session(channel, session)) {
2140
+ message.session_statusCode = StatusCodes.Good;
2141
+ } else {
2142
+ // session ma y have been moved to a different channel
2143
+ message.session_statusCode = StatusCodes.BadSecureChannelIdInvalid;
2144
+ }
2145
+ }
2146
+
2147
+ /**
2148
+ * ensure that action is performed on a valid session object,
2149
+ * @method _apply_on_SessionObject
2150
+ * @param ResponseClass the constructor of the response Class
2151
+ * @param message
2152
+ * @param channel
2153
+ * @param action_to_perform
2154
+ * @param action_to_perform.session {ServerSession}
2155
+ * @param action_to_perform.sendResponse
2156
+ * @param action_to_perform.sendResponse.response
2157
+ * @param action_to_perform.sendError
2158
+ * @param action_to_perform.sendError.statusCode
2159
+ * @param action_to_perform.sendError.diagnostics
2160
+ *
2161
+ * @private
2162
+ */
2163
+ protected async _apply_on_SessionObject(
2164
+ ResponseClass: any,
2165
+ message: Message,
2166
+ channel: ServerSecureChannelLayer,
2167
+ action_to_perform: (
2168
+ session: ServerSession,
2169
+ sendResponse: (response: Response) => void,
2170
+ sendError: (statusCode: StatusCode) => void
2171
+ ) => void | Promise<void>
2172
+ ) {
2173
+ assert(typeof action_to_perform === "function");
2174
+
2175
+ function sendResponse(response1: Response) {
2176
+ try {
2177
+ assert(response1 instanceof ResponseClass);
2178
+ if (message.session) {
2179
+ const counterName = ResponseClass.name.replace("Response", "");
2180
+ message.session.incrementRequestTotalCounter(counterName);
2181
+ }
2182
+ return channel.send_response("MSG", response1, message);
2183
+ } catch (err) {
2184
+ if (err instanceof Error) {
2185
+ // istanbul ignore next
2186
+ errorLog(
2187
+ "Internal error in issuing response\nplease contact support@sterfive.com",
2188
+ message.request.toString(),
2189
+ "\n",
2190
+ response1.toString()
2191
+ );
2192
+ }
2193
+ // istanbul ignore next
2194
+ throw err;
2195
+ }
2196
+ }
2197
+
2198
+ function sendError(statusCode: StatusCode) {
2199
+ if (message.session) {
2200
+ message.session.incrementRequestErrorCounter(ResponseClass.name.replace("Response", ""));
2201
+ }
2202
+ return g_sendError(channel, message, ResponseClass, statusCode);
2203
+ }
2204
+
2205
+ let response: any;
2206
+ /* istanbul ignore next */
2207
+ if (!message.session || message.session_statusCode !== StatusCodes.Good) {
2208
+ const errMessage = "INVALID SESSION !! ";
2209
+ response = new ResponseClass({ responseHeader: { serviceResult: message.session_statusCode } });
2210
+ debugLog(chalk.red.bold(errMessage), chalk.yellow(message.session_statusCode!.toString()), response.constructor.name);
2211
+ return sendResponse(response);
2212
+ }
2213
+
2214
+ assert(message.session_statusCode === StatusCodes.Good);
2215
+
2216
+ // OPC UA Specification 1.02 part 4 page 26
2217
+ // When a Session is terminated, all outstanding requests on the Session are aborted and
2218
+ // Bad_SessionClosed StatusCodes are returned to the Client. In addition, the Server deletes the entry
2219
+ // for the Client from its SessionDiagnostics Array Variable and notifies any other Clients who were
2220
+ // subscribed to this entry.
2221
+ if (message.session.status === "closed") {
2222
+ // note : use StatusCodes.BadSessionClosed , for pending message for this session
2223
+ return sendError(StatusCodes.BadSessionIdInvalid);
2224
+ }
2225
+
2226
+ if (message.session.status === "new") {
2227
+ // mark session as being screwed ! so it cannot be activated anymore
2228
+ message.session.status = "screwed";
2229
+
2230
+ return sendError(StatusCodes.BadSessionNotActivated);
2231
+ }
2232
+
2233
+ if (message.session.status !== "active") {
2234
+ // mark session as being screwed ! so it cannot be activated anymore
2235
+ message.session.status = "screwed";
2236
+
2237
+ // note : use StatusCodes.BadSessionClosed , for pending message for this session
2238
+ return sendError(StatusCodes.BadSessionIdInvalid);
2239
+ }
2240
+
2241
+ // lets also reset the session watchdog so it doesn't
2242
+ // (Sessions are terminated by the Server automatically if the Client fails to issue a Service
2243
+ // request on the Session within the timeout period negotiated by the Server in the
2244
+ // CreateSession Service response. )
2245
+ if (message.session.keepAlive) {
2246
+ assert(typeof message.session.keepAlive === "function");
2247
+ message.session.keepAlive();
2248
+ }
2249
+ message.session.incrementTotalRequestCount();
2250
+ await action_to_perform(message.session as ServerSession, sendResponse, sendError);
2251
+ }
2252
+
2253
+ /**
2254
+ * @method _apply_on_Subscription
2255
+ * @param ResponseClass
2256
+ * @param message
2257
+ * @param channel
2258
+ * @param action_to_perform
2259
+ * @private
2260
+ */
2261
+ protected async _apply_on_Subscription(
2262
+ ResponseClass: any,
2263
+ message: Message,
2264
+ channel: ServerSecureChannelLayer,
2265
+ action_to_perform: (
2266
+ session: ServerSession,
2267
+ subscription: Subscription,
2268
+ sendResponse: (response: Response) => void,
2269
+ sendError: (statusCode: StatusCode) => void
2270
+ ) => Promise<void>
2271
+ ) {
2272
+ assert(typeof action_to_perform === "function");
2273
+ const request = message.request as unknown as { subscriptionId: number };
2274
+ assert(request.hasOwnProperty("subscriptionId"));
2275
+
2276
+ this._apply_on_SessionObject(
2277
+ ResponseClass,
2278
+ message,
2279
+ channel,
2280
+ async (
2281
+ session: ServerSession,
2282
+ sendResponse: (response: Response) => void,
2283
+ sendError: (statusCode: StatusCode) => void
2284
+ ) => {
2285
+ const subscription = session.getSubscription(request.subscriptionId);
2286
+ if (!subscription) {
2287
+ return sendError(StatusCodes.BadSubscriptionIdInvalid);
2288
+ }
2289
+ subscription.resetLifeTimeAndKeepAliveCounters();
2290
+ await action_to_perform(session, subscription, sendResponse, sendError);
2291
+ }
2292
+ );
2293
+ }
2294
+
2295
+ /**
2296
+ * @method _apply_on_SubscriptionIds
2297
+ * @param ResponseClass
2298
+ * @param message
2299
+ * @param channel
2300
+ * @param action_to_perform
2301
+ * @private
2302
+ */
2303
+ protected _apply_on_SubscriptionIds<T>(
2304
+ ResponseClass: typeof SetPublishingModeResponse | typeof TransferSubscriptionsResponse | typeof DeleteSubscriptionsResponse,
2305
+ message: Message,
2306
+ channel: ServerSecureChannelLayer,
2307
+ action_to_perform: (session: ServerSession, subscriptionId: number) => Promise<T>
2308
+ ) {
2309
+ assert(typeof action_to_perform === "function");
2310
+ const request = message.request as unknown as { subscriptionIds: number[] };
2311
+ assert(request.hasOwnProperty("subscriptionIds"));
2312
+
2313
+ this._apply_on_SessionObject(
2314
+ ResponseClass,
2315
+ message,
2316
+ channel,
2317
+ async (
2318
+ session: ServerSession,
2319
+ sendResponse: (response: Response) => void,
2320
+ sendError: (statusCode: StatusCode) => void
2321
+ ) => {
2322
+ const subscriptionIds = request.subscriptionIds;
2323
+
2324
+ if (!request.subscriptionIds || request.subscriptionIds.length === 0) {
2325
+ return sendError(StatusCodes.BadNothingToDo);
2326
+ }
2327
+
2328
+ // if (request.subscriptionIds.length > OPCUAServer.MAX_SUBSCRIPTION) {
2329
+ // return sendError(StatusCodes.BadTooManyOperations);
2330
+ // }
2331
+
2332
+ const results: any[] = subscriptionIds.map((subscriptionId: number) => action_to_perform(session, subscriptionId));
2333
+
2334
+ // resolve potential pending promises ....
2335
+ for (let i = 0; i < results.length; i++) {
2336
+ if (results[i].then) {
2337
+ results[i] = await results[i];
2338
+ }
2339
+ }
2340
+
2341
+ const response = new ResponseClass({
2342
+ responseHeader: {
2343
+ serviceResult:
2344
+ request.subscriptionIds.length > OPCUAServer.MAX_SUBSCRIPTION
2345
+ ? StatusCodes.BadTooManyOperations
2346
+ : StatusCodes.Good
2347
+ },
2348
+ results
2349
+ });
2350
+ sendResponse(response);
2351
+ }
2352
+ );
2353
+ }
2354
+
2355
+ /**
2356
+ * @method _apply_on_Subscriptions
2357
+ * @param ResponseClass
2358
+ * @param message
2359
+ * @param channel
2360
+ * @param action_to_perform
2361
+ * @private
2362
+ */
2363
+ protected _apply_on_Subscriptions(
2364
+ ResponseClass: typeof SetPublishingModeResponse,
2365
+ message: Message,
2366
+ channel: ServerSecureChannelLayer,
2367
+ action_to_perform: (session: ServerSession, subscription: Subscription) => Promise<StatusCode>
2368
+ ) {
2369
+ this._apply_on_SubscriptionIds<StatusCode>(
2370
+ ResponseClass,
2371
+ message,
2372
+ channel,
2373
+ async (session: ServerSession, subscriptionId: number) => {
2374
+ /* istanbul ignore next */
2375
+ if (isSubscriptionIdInvalid(subscriptionId)) {
2376
+ return StatusCodes.BadSubscriptionIdInvalid;
2377
+ }
2378
+ const subscription = session.getSubscription(subscriptionId);
2379
+ if (!subscription) {
2380
+ return StatusCodes.BadSubscriptionIdInvalid;
2381
+ }
2382
+ return action_to_perform(session, subscription);
2383
+ }
2384
+ );
2385
+ }
2386
+
2387
+ private async _closeSession(authenticationToken: NodeId, deleteSubscriptions: boolean, reason: ClosingReason) {
2388
+ const server = this;
2389
+
2390
+ //
2391
+ if (deleteSubscriptions && this.options.onDeleteMonitoredItem) {
2392
+ const session = this.getSession(authenticationToken);
2393
+ if (session) {
2394
+ const subscriptions = session.publishEngine.subscriptions;
2395
+ for (const subscription of subscriptions) {
2396
+ await subscription.applyOnMonitoredItem(this.options.onDeleteMonitoredItem.bind(null, subscription) as any);
2397
+ }
2398
+ }
2399
+ }
2400
+ await server.engine.closeSession(authenticationToken, deleteSubscriptions, reason);
2401
+ }
2402
+ /**
2403
+ * @method _on_CloseSessionRequest
2404
+ * @param message
2405
+ * @param channel
2406
+ * @private
2407
+ */
2408
+ protected _on_CloseSessionRequest(message: Message, channel: ServerSecureChannelLayer) {
2409
+ const server = this;
2410
+
2411
+ const request = message.request as CloseSessionRequest;
2412
+ assert(request instanceof CloseSessionRequest);
2413
+
2414
+ let response;
2415
+
2416
+ message.session_statusCode = StatusCodes.Good;
2417
+
2418
+ function sendError(statusCode: StatusCode) {
2419
+ return g_sendError(channel, message, CloseSessionResponse, statusCode);
2420
+ }
2421
+
2422
+ function sendResponse(response1: CloseSessionResponse) {
2423
+ channel.send_response("MSG", response1, message);
2424
+ }
2425
+
2426
+ // do not use _apply_on_SessionObject
2427
+ // this._apply_on_SessionObject(CloseSessionResponse, message, channel, function (session) {
2428
+ // });
2429
+
2430
+ const session = message.session;
2431
+ if (!session) {
2432
+ return sendError(StatusCodes.BadSessionIdInvalid);
2433
+ }
2434
+
2435
+ // session has been created but not activated !
2436
+ const wasNotActivated = session.status === "new";
2437
+
2438
+ (async () => {
2439
+ await this._closeSession(request.requestHeader.authenticationToken, request.deleteSubscriptions, "CloseSession");
2440
+
2441
+ // if (false && wasNotActivated) {
2442
+ // return sendError(StatusCodes.BadSessionNotActivated);
2443
+ // }
2444
+
2445
+ response = new CloseSessionResponse({});
2446
+ sendResponse(response);
2447
+ })();
2448
+ }
2449
+
2450
+ // browse services
2451
+ /**
2452
+ * @method _on_BrowseRequest
2453
+ * @param message
2454
+ * @param channel
2455
+ * @private
2456
+ */
2457
+ protected _on_BrowseRequest(message: Message, channel: ServerSecureChannelLayer) {
2458
+ const server = this;
2459
+ const request = message.request as BrowseRequest;
2460
+ assert(request instanceof BrowseRequest);
2461
+ const diagnostic: any = {};
2462
+
2463
+ this._apply_on_SessionObject(
2464
+ BrowseResponse,
2465
+ message,
2466
+ channel,
2467
+ (session: ServerSession, sendResponse: (response: Response) => void, sendError: (statusCode: StatusCode) => void) => {
2468
+ let response: BrowseResponse;
2469
+ // test view
2470
+ if (request.view && !request.view.viewId.isEmpty()) {
2471
+ let theView: UAView | null = server.engine!.addressSpace!.findNode(request.view.viewId) as UAView;
2472
+ if (theView && theView.nodeClass !== NodeClass.View) {
2473
+ // Error: theView is not a View
2474
+ diagnostic.localizedText = { text: "Expecting a view here" };
2475
+ theView = null;
2476
+ }
2477
+ if (!theView) {
2478
+ return sendError(StatusCodes.BadViewIdUnknown);
2479
+ }
2480
+ }
2481
+
2482
+ if (!request.nodesToBrowse || request.nodesToBrowse.length === 0) {
2483
+ return sendError(StatusCodes.BadNothingToDo);
2484
+ }
2485
+
2486
+ if (server.engine.serverCapabilities.operationLimits.maxNodesPerBrowse > 0) {
2487
+ if (request.nodesToBrowse.length > server.engine.serverCapabilities.operationLimits.maxNodesPerBrowse) {
2488
+ return sendError(StatusCodes.BadTooManyOperations);
2489
+ }
2490
+ }
2491
+
2492
+ // limit results to requestedMaxReferencesPerNode further so it never exceed a too big number
2493
+ const requestedMaxReferencesPerNode = Math.min(9876, request.requestedMaxReferencesPerNode);
2494
+ assert(request.nodesToBrowse[0].schema.name === "BrowseDescription");
2495
+
2496
+ const context = new SessionContext({ session, server });
2497
+
2498
+ const f = callbackify(server.engine.browseWithAutomaticExpansion).bind(server.engine);
2499
+ (f as any)(request.nodesToBrowse, context, (err?: Error | null, results?: BrowseResult[]) => {
2500
+ // istanbul ignore next
2501
+ if (!results) {
2502
+ throw new Error("internal error : " + err?.message);
2503
+ }
2504
+
2505
+ assert(results[0].schema.name === "BrowseResult");
2506
+
2507
+ // handle continuation point and requestedMaxReferencesPerNode
2508
+ const maxBrowseContinuationPoints = server.engine.serverCapabilities.maxBrowseContinuationPoints;
2509
+ results = results.map((result: BrowseResult) => {
2510
+ assert(!result.continuationPoint);
2511
+
2512
+ // istanbul ignore next
2513
+ if (!session.continuationPointManager) {
2514
+ return new BrowseResult({ statusCode: StatusCodes.BadNoContinuationPoints });
2515
+ }
2516
+
2517
+ if (session.continuationPointManager.hasReachMaximum(maxBrowseContinuationPoints)) {
2518
+ return new BrowseResult({ statusCode: StatusCodes.BadNoContinuationPoints });
2519
+ }
2520
+ const truncatedResult = session.continuationPointManager.register(
2521
+ requestedMaxReferencesPerNode,
2522
+ result.references || []
2523
+ );
2524
+ assert(truncatedResult.statusCode === StatusCodes.Good);
2525
+ truncatedResult.statusCode = result.statusCode;
2526
+ return new BrowseResult(truncatedResult);
2527
+ });
2528
+
2529
+ response = new BrowseResponse({
2530
+ diagnosticInfos: undefined,
2531
+ results
2532
+ });
2533
+ sendResponse(response);
2534
+ });
2535
+ }
2536
+ );
2537
+ }
2538
+
2539
+ /**
2540
+ * @method _on_BrowseNextRequest
2541
+ * @param message
2542
+ * @param channel
2543
+ * @private
2544
+ */
2545
+ protected _on_BrowseNextRequest(message: Message, channel: ServerSecureChannelLayer) {
2546
+ const request = message.request as BrowseNextRequest;
2547
+ assert(request instanceof BrowseNextRequest);
2548
+ this._apply_on_SessionObject(
2549
+ BrowseNextResponse,
2550
+ message,
2551
+ channel,
2552
+ (session: ServerSession, sendResponse: (response: Response) => void, sendError: (statusCode: StatusCode) => void) => {
2553
+ let response;
2554
+
2555
+ if (!request.continuationPoints || request.continuationPoints.length === 0) {
2556
+ return sendError(StatusCodes.BadNothingToDo);
2557
+ }
2558
+
2559
+ // A Boolean parameter with the following values:
2560
+
2561
+ let results;
2562
+ if (request.releaseContinuationPoints) {
2563
+ // releaseContinuationPoints = TRUE
2564
+ // passed continuationPoints shall be reset to free resources in
2565
+ // the Server. The continuation points are released and the results
2566
+ // and diagnosticInfos arrays are empty.
2567
+ results = request.continuationPoints.map((continuationPoint: ContinuationPoint) => {
2568
+ return session.continuationPointManager.cancel(continuationPoint);
2569
+ });
2570
+ } else {
2571
+ // let extract data from continuation points
2572
+
2573
+ // releaseContinuationPoints = FALSE
2574
+ // passed continuationPoints shall be used to get the next set of
2575
+ // browse information.
2576
+ results = request.continuationPoints.map((continuationPoint: ContinuationPoint) => {
2577
+ return session.continuationPointManager.getNext(continuationPoint);
2578
+ });
2579
+ }
2580
+
2581
+ response = new BrowseNextResponse({
2582
+ diagnosticInfos: undefined,
2583
+ results
2584
+ });
2585
+ sendResponse(response);
2586
+ }
2587
+ );
2588
+ }
2589
+
2590
+ // read services
2591
+ protected _on_ReadRequest(message: Message, channel: ServerSecureChannelLayer) {
2592
+ const server = this;
2593
+ const request = message.request as ReadRequest;
2594
+ assert(request instanceof ReadRequest);
2595
+
2596
+ this._apply_on_SessionObject(
2597
+ ReadResponse,
2598
+ message,
2599
+ channel,
2600
+ (session: ServerSession, sendResponse: (response: Response) => void, sendError: (statusCode: StatusCode) => void) => {
2601
+ const context = new SessionContext({ session, server });
2602
+
2603
+ let response;
2604
+
2605
+ let results = [];
2606
+
2607
+ const timestampsToReturn = request.timestampsToReturn;
2608
+
2609
+ if (timestampsToReturn === TimestampsToReturn.Invalid) {
2610
+ return sendError(StatusCodes.BadTimestampsToReturnInvalid);
2611
+ }
2612
+
2613
+ if (request.maxAge < 0) {
2614
+ return sendError(StatusCodes.BadMaxAgeInvalid);
2615
+ }
2616
+
2617
+ request.nodesToRead = request.nodesToRead || [];
2618
+
2619
+ if (!request.nodesToRead || request.nodesToRead.length <= 0) {
2620
+ return sendError(StatusCodes.BadNothingToDo);
2621
+ }
2622
+
2623
+ assert(request.nodesToRead[0].schema.name === "ReadValueId");
2624
+
2625
+ // limit size of nodesToRead array to maxNodesPerRead
2626
+ if (server.engine.serverCapabilities.operationLimits.maxNodesPerRead > 0) {
2627
+ if (request.nodesToRead.length > server.engine.serverCapabilities.operationLimits.maxNodesPerRead) {
2628
+ return sendError(StatusCodes.BadTooManyOperations);
2629
+ }
2630
+ }
2631
+
2632
+ // proceed with registered nodes alias resolution
2633
+ for (const nodeToRead of request.nodesToRead) {
2634
+ nodeToRead.nodeId = session.resolveRegisteredNode(nodeToRead.nodeId);
2635
+ }
2636
+
2637
+ // ask for a refresh of asynchronous variables
2638
+ server.engine.refreshValues(request.nodesToRead, request.maxAge, (err?: Error | null) => {
2639
+ assert(!err, " error not handled here , fix me");
2640
+
2641
+ results = server.engine.read(context, request);
2642
+
2643
+ assert(results[0].schema.name === "DataValue");
2644
+ assert(results.length === request.nodesToRead!.length);
2645
+
2646
+ response = new ReadResponse({
2647
+ diagnosticInfos: undefined,
2648
+ results: undefined
2649
+ });
2650
+ // set it here for performance
2651
+ response.results = results;
2652
+ assert(response.diagnosticInfos!.length === 0);
2653
+ sendResponse(response);
2654
+ });
2655
+ }
2656
+ );
2657
+ }
2658
+
2659
+ // read services
2660
+ protected _on_HistoryReadRequest(message: Message, channel: ServerSecureChannelLayer) {
2661
+ const server = this;
2662
+ const request = message.request as HistoryReadRequest;
2663
+
2664
+ assert(request instanceof HistoryReadRequest);
2665
+
2666
+ this._apply_on_SessionObject(
2667
+ HistoryReadResponse,
2668
+ message,
2669
+ channel,
2670
+ (session: ServerSession, sendResponse: (response: Response) => void, sendError: (statsCode: StatusCode) => void) => {
2671
+ let response;
2672
+
2673
+ const timestampsToReturn = request.timestampsToReturn;
2674
+
2675
+ if (timestampsToReturn === TimestampsToReturn.Invalid) {
2676
+ return sendError(StatusCodes.BadTimestampsToReturnInvalid);
2677
+ }
2678
+
2679
+ request.nodesToRead = request.nodesToRead || [];
2680
+
2681
+ if (!request.nodesToRead || request.nodesToRead.length <= 0) {
2682
+ return sendError(StatusCodes.BadNothingToDo);
2683
+ }
2684
+
2685
+ assert(request.nodesToRead[0].schema.name === "HistoryReadValueId");
2686
+
2687
+ // limit size of nodesToRead array to maxNodesPerRead
2688
+ if (server.engine.serverCapabilities.operationLimits.maxNodesPerRead > 0) {
2689
+ if (request.nodesToRead.length > server.engine.serverCapabilities.operationLimits.maxNodesPerRead) {
2690
+ return sendError(StatusCodes.BadTooManyOperations);
2691
+ }
2692
+ }
2693
+ // todo : handle
2694
+ if (server.engine.serverCapabilities.operationLimits.maxNodesPerHistoryReadData > 0) {
2695
+ if (request.nodesToRead.length > server.engine.serverCapabilities.operationLimits.maxNodesPerHistoryReadData) {
2696
+ return sendError(StatusCodes.BadTooManyOperations);
2697
+ }
2698
+ }
2699
+ if (server.engine.serverCapabilities.operationLimits.maxNodesPerHistoryReadEvents > 0) {
2700
+ if (
2701
+ request.nodesToRead.length > server.engine.serverCapabilities.operationLimits.maxNodesPerHistoryReadEvents
2702
+ ) {
2703
+ return sendError(StatusCodes.BadTooManyOperations);
2704
+ }
2705
+ }
2706
+
2707
+ const context = new SessionContext({ session, server });
2708
+
2709
+ // ask for a refresh of asynchronous variables
2710
+ server.engine.refreshValues(request.nodesToRead, 0, (err?: Error | null) => {
2711
+ assert(!err, " error not handled here , fix me"); // TODO
2712
+
2713
+ server.engine.historyRead(context, request, (err1: Error | null, results?: HistoryReadResult[]) => {
2714
+ if (err1) {
2715
+ return sendError(StatusCodes.BadHistoryOperationInvalid);
2716
+ }
2717
+ if (!results) {
2718
+ return sendError(StatusCodes.BadHistoryOperationInvalid);
2719
+ }
2720
+
2721
+ assert(results[0].schema.name === "HistoryReadResult");
2722
+ assert(results.length === request.nodesToRead!.length);
2723
+
2724
+ response = new HistoryReadResponse({
2725
+ diagnosticInfos: undefined,
2726
+ results
2727
+ });
2728
+
2729
+ assert(response.diagnosticInfos!.length === 0);
2730
+ sendResponse(response);
2731
+ });
2732
+ });
2733
+ }
2734
+ );
2735
+ }
2736
+
2737
+ /*
2738
+ // write services
2739
+ // OPCUA Specification 1.02 Part 3 : 5.10.4 Write
2740
+ // This Service is used to write values to one or more Attributes of one or more Nodes. For constructed
2741
+ // Attribute values whose elements are indexed, such as an array, this Service allows Clients to write
2742
+ // the entire set of indexed values as a composite, to write individual elements or to write ranges of
2743
+ // elements of the composite.
2744
+ // The values are written to the data source, such as a device, and the Service does not return until it writes
2745
+ // the values or determines that the value cannot be written. In certain cases, the Server will successfully
2746
+ // to an intermediate system or Server, and will not know if the data source was updated properly. In these cases,
2747
+ // the Server should report a success code that indicates that the write was not verified.
2748
+ // In the cases where the Server is able to verify that it has successfully written to the data source,
2749
+ // it reports an unconditional success.
2750
+ */
2751
+ protected _on_WriteRequest(message: Message, channel: ServerSecureChannelLayer) {
2752
+ const server = this;
2753
+ const request = message.request as WriteRequest;
2754
+ assert(request instanceof WriteRequest);
2755
+ assert(!request.nodesToWrite || Array.isArray(request.nodesToWrite));
2756
+
2757
+ this._apply_on_SessionObject(
2758
+ WriteResponse,
2759
+ message,
2760
+ channel,
2761
+ (session: ServerSession, sendResponse: (response: Response) => void, sendError: (statusCode: StatusCode) => void) => {
2762
+ let response;
2763
+
2764
+ if (!request.nodesToWrite || request.nodesToWrite.length === 0) {
2765
+ return sendError(StatusCodes.BadNothingToDo);
2766
+ }
2767
+
2768
+ if (server.engine.serverCapabilities.operationLimits.maxNodesPerWrite > 0) {
2769
+ if (request.nodesToWrite.length > server.engine.serverCapabilities.operationLimits.maxNodesPerWrite) {
2770
+ return sendError(StatusCodes.BadTooManyOperations);
2771
+ }
2772
+ }
2773
+
2774
+ // proceed with registered nodes alias resolution
2775
+ for (const nodeToWrite of request.nodesToWrite) {
2776
+ nodeToWrite.nodeId = session.resolveRegisteredNode(nodeToWrite.nodeId);
2777
+ }
2778
+
2779
+ const context = new SessionContext({ session, server });
2780
+
2781
+ assert(request.nodesToWrite[0].schema.name === "WriteValue");
2782
+ server.engine.write(context, request.nodesToWrite, (err: Error | null, results?: StatusCode[]) => {
2783
+ assert(!err);
2784
+ assert(Array.isArray(results));
2785
+ assert(results!.length === request.nodesToWrite!.length);
2786
+ response = new WriteResponse({
2787
+ diagnosticInfos: undefined,
2788
+ results
2789
+ });
2790
+ sendResponse(response);
2791
+ });
2792
+ }
2793
+ );
2794
+ }
2795
+
2796
+ // subscription services
2797
+ protected _on_CreateSubscriptionRequest(message: Message, channel: ServerSecureChannelLayer) {
2798
+ const server = this;
2799
+ const engine = server.engine;
2800
+ const addressSpace = engine.addressSpace!;
2801
+
2802
+ const request = message.request as CreateSubscriptionRequest;
2803
+ assert(request instanceof CreateSubscriptionRequest);
2804
+
2805
+ this._apply_on_SessionObject(
2806
+ CreateSubscriptionResponse,
2807
+ message,
2808
+ channel,
2809
+ (session: ServerSession, sendResponse: (response: Response) => void, sendError: (statusCode: StatusCode) => void) => {
2810
+ const context = new SessionContext({ session, server });
2811
+
2812
+ if (session.currentSubscriptionCount >= OPCUAServer.MAX_SUBSCRIPTION) {
2813
+ return sendError(StatusCodes.BadTooManySubscriptions);
2814
+ }
2815
+
2816
+ const subscription = session.createSubscription(request);
2817
+
2818
+ subscription.on("monitoredItem", (monitoredItem: MonitoredItem) => {
2819
+ prepareMonitoredItem(context, addressSpace, monitoredItem);
2820
+ });
2821
+
2822
+ const response = new CreateSubscriptionResponse({
2823
+ revisedLifetimeCount: subscription.lifeTimeCount,
2824
+ revisedMaxKeepAliveCount: subscription.maxKeepAliveCount,
2825
+ revisedPublishingInterval: subscription.publishingInterval,
2826
+ subscriptionId: subscription.id
2827
+ });
2828
+ sendResponse(response);
2829
+ }
2830
+ );
2831
+ }
2832
+
2833
+ protected _on_DeleteSubscriptionsRequest(message: Message, channel: ServerSecureChannelLayer) {
2834
+ const server = this;
2835
+ const request = message.request as DeleteSubscriptionsRequest;
2836
+ assert(request instanceof DeleteSubscriptionsRequest);
2837
+ this._apply_on_SubscriptionIds(
2838
+ DeleteSubscriptionsResponse,
2839
+ message,
2840
+ channel,
2841
+ async (session: ServerSession, subscriptionId: number) => {
2842
+ let subscription = server.engine.findOrphanSubscription(subscriptionId);
2843
+ // istanbul ignore next
2844
+ if (subscription) {
2845
+ warningLog("Deleting an orphan subscription", subscriptionId);
2846
+
2847
+ await this._beforeDeleteSubscription(subscription);
2848
+ return server.engine.deleteOrphanSubscription(subscription);
2849
+ }
2850
+
2851
+ subscription = session.getSubscription(subscriptionId);
2852
+ if (subscription) {
2853
+ await this._beforeDeleteSubscription(subscription);
2854
+ }
2855
+
2856
+ return session.deleteSubscription(subscriptionId);
2857
+ }
2858
+ );
2859
+ }
2860
+
2861
+ protected _on_TransferSubscriptionsRequest(message: Message, channel: ServerSecureChannelLayer) {
2862
+ //
2863
+ // sendInitialValue Boolean
2864
+ // A Boolean parameter with the following values:
2865
+ // TRUE the first Publish response(s) after the TransferSubscriptions call shall
2866
+ // contain the current values of all Monitored Items in the Subscription where
2867
+ // the Monitoring Mode is set to Reporting.
2868
+ // FALSE the first Publish response after the TransferSubscriptions call shall contain only the value
2869
+ // changes since the last Publish response was sent.
2870
+ // This parameter only applies to MonitoredItems used for monitoring Attribute changes.
2871
+ //
2872
+
2873
+ const server = this;
2874
+ const engine = server.engine;
2875
+
2876
+ const request = message.request as TransferSubscriptionsRequest;
2877
+ assert(request instanceof TransferSubscriptionsRequest);
2878
+ this._apply_on_SubscriptionIds(
2879
+ TransferSubscriptionsResponse,
2880
+ message,
2881
+ channel,
2882
+ async (session: ServerSession, subscriptionId: number) =>
2883
+ await engine.transferSubscription(session, subscriptionId, request.sendInitialValues)
2884
+ );
2885
+ }
2886
+
2887
+ protected _on_CreateMonitoredItemsRequest(message: Message, channel: ServerSecureChannelLayer) {
2888
+ const server = this;
2889
+ const engine = server.engine;
2890
+ const addressSpace = engine.addressSpace!;
2891
+
2892
+ const request = message.request as CreateMonitoredItemsRequest;
2893
+ assert(request instanceof CreateMonitoredItemsRequest);
2894
+
2895
+ this._apply_on_Subscription(
2896
+ CreateMonitoredItemsResponse,
2897
+ message,
2898
+ channel,
2899
+ async (
2900
+ session: ServerSession,
2901
+ subscription: Subscription,
2902
+ sendResponse: (response: Response) => void,
2903
+ sendError: (statusCode: StatusCode) => void
2904
+ ): Promise<void> => {
2905
+ const timestampsToReturn = request.timestampsToReturn;
2906
+ if (timestampsToReturn === TimestampsToReturn.Invalid) {
2907
+ return sendError(StatusCodes.BadTimestampsToReturnInvalid);
2908
+ }
2909
+
2910
+ if (!request.itemsToCreate || request.itemsToCreate.length === 0) {
2911
+ return sendError(StatusCodes.BadNothingToDo);
2912
+ }
2913
+ if (server.engine.serverCapabilities.operationLimits.maxMonitoredItemsPerCall > 0) {
2914
+ if (request.itemsToCreate.length > server.engine.serverCapabilities.operationLimits.maxMonitoredItemsPerCall) {
2915
+ return sendError(StatusCodes.BadTooManyOperations);
2916
+ }
2917
+ }
2918
+
2919
+ const options = this.options as OPCUAServerOptions;
2920
+ let results: MonitoredItemCreateResult[] = [];
2921
+ if (options.onCreateMonitoredItem) {
2922
+ const resultsPromise = request.itemsToCreate.map(async (monitoredItemCreateRequest) => {
2923
+ const { monitoredItem, createResult } = subscription.preCreateMonitoredItem(
2924
+ addressSpace,
2925
+ timestampsToReturn,
2926
+ monitoredItemCreateRequest
2927
+ );
2928
+ if (monitoredItem) {
2929
+ await options.onCreateMonitoredItem!(subscription, monitoredItem);
2930
+ subscription.postCreateMonitoredItem(monitoredItem, monitoredItemCreateRequest, createResult);
2931
+ }
2932
+ return createResult;
2933
+ });
2934
+ results = await Promise.all(resultsPromise);
2935
+ } else {
2936
+ results = request.itemsToCreate.map((monitoredItemCreateRequest) => {
2937
+ const { monitoredItem, createResult } = subscription.preCreateMonitoredItem(
2938
+ addressSpace,
2939
+ timestampsToReturn,
2940
+ monitoredItemCreateRequest
2941
+ );
2942
+ if (monitoredItem) {
2943
+ subscription.postCreateMonitoredItem(monitoredItem, monitoredItemCreateRequest, createResult);
2944
+ }
2945
+ return createResult;
2946
+ });
2947
+ }
2948
+ const response = new CreateMonitoredItemsResponse({
2949
+ responseHeader: { serviceResult: StatusCodes.Good },
2950
+ results
2951
+ // ,diagnosticInfos: []
2952
+ });
2953
+ sendResponse(response);
2954
+ }
2955
+ );
2956
+ }
2957
+
2958
+ protected _on_ModifySubscriptionRequest(message: Message, channel: ServerSecureChannelLayer) {
2959
+ const request = message.request as ModifySubscriptionRequest;
2960
+ assert(request instanceof ModifySubscriptionRequest);
2961
+
2962
+ this._apply_on_Subscription(
2963
+ ModifySubscriptionResponse,
2964
+ message,
2965
+ channel,
2966
+ async (
2967
+ session: ServerSession,
2968
+ subscription: Subscription,
2969
+ sendResponse: (response: ModifySubscriptionResponse) => void,
2970
+ sendError: (statusCode: StatusCode) => void
2971
+ ) => {
2972
+ subscription.modify(request);
2973
+
2974
+ const response = new ModifySubscriptionResponse({
2975
+ revisedLifetimeCount: subscription.lifeTimeCount,
2976
+ revisedMaxKeepAliveCount: subscription.maxKeepAliveCount,
2977
+ revisedPublishingInterval: subscription.publishingInterval
2978
+ });
2979
+
2980
+ sendResponse(response);
2981
+ }
2982
+ );
2983
+ }
2984
+
2985
+ protected _on_ModifyMonitoredItemsRequest(message: Message, channel: ServerSecureChannelLayer) {
2986
+ const server = this;
2987
+ const request = message.request as ModifyMonitoredItemsRequest;
2988
+
2989
+ assert(request instanceof ModifyMonitoredItemsRequest);
2990
+ this._apply_on_Subscription(
2991
+ ModifyMonitoredItemsResponse,
2992
+ message,
2993
+ channel,
2994
+ async (
2995
+ session: ServerSession,
2996
+ subscription: Subscription,
2997
+ sendResponse: (response: ModifyMonitoredItemsResponse) => void,
2998
+ sendError: (statusCode: StatusCode) => void
2999
+ ) => {
3000
+ const timestampsToReturn = request.timestampsToReturn;
3001
+ if (timestampsToReturn === TimestampsToReturn.Invalid) {
3002
+ return sendError(StatusCodes.BadTimestampsToReturnInvalid);
3003
+ }
3004
+
3005
+ if (!request.itemsToModify || request.itemsToModify.length === 0) {
3006
+ return sendError(StatusCodes.BadNothingToDo);
3007
+ }
3008
+
3009
+ /* istanbul ignore next */
3010
+ if (server.engine.serverCapabilities.operationLimits.maxMonitoredItemsPerCall > 0) {
3011
+ if (request.itemsToModify.length > server.engine.serverCapabilities.operationLimits.maxMonitoredItemsPerCall) {
3012
+ return sendError(StatusCodes.BadTooManyOperations);
3013
+ }
3014
+ }
3015
+
3016
+ const itemsToModify = request.itemsToModify; // MonitoredItemModifyRequest
3017
+
3018
+ function modifyMonitoredItem(item: MonitoredItemModifyRequest) {
3019
+ const monitoredItemId = item.monitoredItemId;
3020
+ const monitoredItem = subscription.getMonitoredItem(monitoredItemId);
3021
+ if (!monitoredItem) {
3022
+ return new MonitoredItemModifyResult({ statusCode: StatusCodes.BadMonitoredItemIdInvalid });
3023
+ }
3024
+
3025
+ // adjust samplingInterval if === -1
3026
+ if (item.requestedParameters.samplingInterval === -1) {
3027
+ item.requestedParameters.samplingInterval = subscription.publishingInterval;
3028
+ }
3029
+ return monitoredItem.modify(timestampsToReturn, item.requestedParameters);
3030
+ }
3031
+
3032
+ const results = itemsToModify.map(modifyMonitoredItem);
3033
+
3034
+ const response = new ModifyMonitoredItemsResponse({
3035
+ results
3036
+ });
3037
+ sendResponse(response);
3038
+ }
3039
+ );
3040
+ }
3041
+
3042
+ protected _on_PublishRequest(message: Message, channel: ServerSecureChannelLayer) {
3043
+ const request = message.request as PublishRequest;
3044
+ assert(request instanceof PublishRequest);
3045
+
3046
+ this._apply_on_SessionObject(
3047
+ PublishResponse,
3048
+ message,
3049
+ channel,
3050
+ (session: ServerSession, sendResponse: (response: Response) => void, sendError: (statusCode: StatusCode) => void) => {
3051
+ assert(session);
3052
+ assert(session.publishEngine); // server.publishEngine doesn't exists, OPCUAServer has probably shut down already
3053
+ session.publishEngine._on_PublishRequest(request, (request1: any, response: any) => {
3054
+ sendResponse(response);
3055
+ });
3056
+ }
3057
+ );
3058
+ }
3059
+
3060
+ protected _on_SetPublishingModeRequest(message: Message, channel: ServerSecureChannelLayer) {
3061
+ const request = message.request as SetPublishingModeRequest;
3062
+ assert(request instanceof SetPublishingModeRequest);
3063
+ const publishingEnabled = request.publishingEnabled;
3064
+ this._apply_on_Subscriptions(
3065
+ SetPublishingModeResponse,
3066
+ message,
3067
+ channel,
3068
+ async (session: ServerSession, subscription: Subscription) => {
3069
+ return subscription.setPublishingMode(publishingEnabled);
3070
+ }
3071
+ );
3072
+ }
3073
+
3074
+ protected _on_DeleteMonitoredItemsRequest(message: Message, channel: ServerSecureChannelLayer) {
3075
+ const server = this;
3076
+ const request = message.request as DeleteMonitoredItemsRequest;
3077
+ assert(request instanceof DeleteMonitoredItemsRequest);
3078
+
3079
+ this._apply_on_Subscription(
3080
+ DeleteMonitoredItemsResponse,
3081
+ message,
3082
+ channel,
3083
+ async (
3084
+ session: ServerSession,
3085
+ subscription: Subscription,
3086
+ sendResponse: (response: Response) => void,
3087
+ sendError: (statusCode: StatusCode) => void
3088
+ ) => {
3089
+ /* istanbul ignore next */
3090
+ if (!request.monitoredItemIds || request.monitoredItemIds.length === 0) {
3091
+ return sendError(StatusCodes.BadNothingToDo);
3092
+ }
3093
+
3094
+ /* istanbul ignore next */
3095
+ if (server.engine.serverCapabilities.operationLimits.maxMonitoredItemsPerCall > 0) {
3096
+ if (
3097
+ request.monitoredItemIds.length > server.engine.serverCapabilities.operationLimits.maxMonitoredItemsPerCall
3098
+ ) {
3099
+ return sendError(StatusCodes.BadTooManyOperations);
3100
+ }
3101
+ }
3102
+
3103
+ const resultsPromises = request.monitoredItemIds.map(async (monitoredItemId: number) => {
3104
+ if (this.options.onDeleteMonitoredItem) {
3105
+ const monitoredItem = subscription.getMonitoredItem(monitoredItemId);
3106
+ if (monitoredItem) {
3107
+ await this.options.onDeleteMonitoredItem(subscription, monitoredItem);
3108
+ }
3109
+ }
3110
+ return subscription.removeMonitoredItem(monitoredItemId);
3111
+ });
3112
+
3113
+ try {
3114
+ const results = await Promise.all(resultsPromises);
3115
+
3116
+ const response = new DeleteMonitoredItemsResponse({
3117
+ diagnosticInfos: undefined,
3118
+ results
3119
+ });
3120
+
3121
+ sendResponse(response);
3122
+ } catch (err) {
3123
+ console.log(err);
3124
+ return sendError(StatusCodes.BadInternalError);
3125
+ }
3126
+ }
3127
+ );
3128
+ }
3129
+ protected _on_SetTriggeringRequest(message: Message, channel: ServerSecureChannelLayer) {
3130
+ const server = this;
3131
+ const request = message.request as SetTriggeringRequest;
3132
+ assert(request instanceof SetTriggeringRequest);
3133
+
3134
+ this._apply_on_Subscription(
3135
+ SetTriggeringResponse,
3136
+ message,
3137
+ channel,
3138
+ async (
3139
+ session: ServerSession,
3140
+ subscription: Subscription,
3141
+ sendResponse: (response: Response) => void,
3142
+ sendError: (statusCode: StatusCode) => void
3143
+ ) => {
3144
+ /* */
3145
+ const { triggeringItemId, linksToAdd, linksToRemove } = request;
3146
+
3147
+ /**
3148
+ * The MaxMonitoredItemsPerCall Property indicates
3149
+ * [...]
3150
+ * • the maximum size of the sum of the linksToAdd and linksToRemove arrays when a
3151
+ * Client calls the SetTriggering Service.
3152
+ *
3153
+ */
3154
+ const maxElements = (linksToAdd ? linksToAdd.length : 0) + (linksToRemove ? linksToRemove.length : 0);
3155
+ if (server.engine.serverCapabilities.operationLimits.maxMonitoredItemsPerCall > 0) {
3156
+ if (maxElements > server.engine.serverCapabilities.operationLimits.maxMonitoredItemsPerCall) {
3157
+ return sendError(StatusCodes.BadTooManyOperations);
3158
+ }
3159
+ }
3160
+
3161
+ const { addResults, removeResults, statusCode } = subscription.setTriggering(
3162
+ triggeringItemId,
3163
+ linksToAdd,
3164
+ linksToRemove
3165
+ );
3166
+ const response = new SetTriggeringResponse({
3167
+ responseHeader: { serviceResult: statusCode },
3168
+
3169
+ addResults,
3170
+ removeResults,
3171
+ addDiagnosticInfos: null,
3172
+ removeDiagnosticInfos: null
3173
+ });
3174
+
3175
+ sendResponse(response);
3176
+ }
3177
+ );
3178
+ }
3179
+
3180
+ protected async _beforeDeleteSubscription(subscription: Subscription) {
3181
+ if (!this.options.onDeleteMonitoredItem) {
3182
+ return;
3183
+ }
3184
+ await subscription.applyOnMonitoredItem(this.options.onDeleteMonitoredItem.bind(null, subscription) as any);
3185
+ }
3186
+ protected _on_RepublishRequest(message: Message, channel: ServerSecureChannelLayer) {
3187
+ const request = message.request as RepublishRequest;
3188
+ assert(request instanceof RepublishRequest);
3189
+
3190
+ this._apply_on_Subscription(
3191
+ RepublishResponse,
3192
+ message,
3193
+ channel,
3194
+ async (
3195
+ session: ServerSession,
3196
+ subscription: Subscription,
3197
+ sendResponse: (response: Response) => void,
3198
+ sendError: (statusCode: StatusCode) => void
3199
+ ) => {
3200
+ // update diagnostic counter
3201
+ subscription.subscriptionDiagnostics.republishRequestCount += 1;
3202
+ subscription.subscriptionDiagnostics.republishMessageRequestCount += 1;
3203
+
3204
+ const retransmitSequenceNumber = request.retransmitSequenceNumber;
3205
+ const notificationMessage = subscription.getMessageForSequenceNumber(retransmitSequenceNumber);
3206
+
3207
+ if (!notificationMessage) {
3208
+ return sendError(StatusCodes.BadMessageNotAvailable);
3209
+ }
3210
+ const response = new RepublishResponse({
3211
+ notificationMessage,
3212
+ responseHeader: {
3213
+ serviceResult: StatusCodes.Good
3214
+ }
3215
+ });
3216
+ // update diagnostic counter
3217
+ subscription.subscriptionDiagnostics.republishMessageCount += 1;
3218
+
3219
+ sendResponse(response);
3220
+ }
3221
+ );
3222
+ }
3223
+
3224
+ // Bad_NothingToDo
3225
+ // Bad_TooManyOperations
3226
+ // Bad_SubscriptionIdInvalid
3227
+ // Bad_MonitoringModeInvalid
3228
+ protected _on_SetMonitoringModeRequest(message: Message, channel: ServerSecureChannelLayer) {
3229
+ const server = this;
3230
+ const request = message.request as SetMonitoringModeRequest;
3231
+ assert(request instanceof SetMonitoringModeRequest);
3232
+
3233
+ this._apply_on_Subscription(
3234
+ SetMonitoringModeResponse,
3235
+ message,
3236
+ channel,
3237
+ async (
3238
+ session: ServerSession,
3239
+ subscription: Subscription,
3240
+ sendResponse: (response: Response) => void,
3241
+ sendError: (statusCode: StatusCode) => void
3242
+ ) => {
3243
+ /* istanbul ignore next */
3244
+ if (!request.monitoredItemIds || request.monitoredItemIds.length === 0) {
3245
+ return sendError(StatusCodes.BadNothingToDo);
3246
+ }
3247
+
3248
+ /* istanbul ignore next */
3249
+ if (server.engine.serverCapabilities.operationLimits.maxMonitoredItemsPerCall > 0) {
3250
+ if (
3251
+ request.monitoredItemIds.length > server.engine.serverCapabilities.operationLimits.maxMonitoredItemsPerCall
3252
+ ) {
3253
+ return sendError(StatusCodes.BadTooManyOperations);
3254
+ }
3255
+ }
3256
+ const monitoringMode = request.monitoringMode;
3257
+
3258
+ if (!isMonitoringModeValid(monitoringMode)) {
3259
+ return sendError(StatusCodes.BadMonitoringModeInvalid);
3260
+ }
3261
+
3262
+ const results = request.monitoredItemIds.map((monitoredItemId) => {
3263
+ const monitoredItem = subscription.getMonitoredItem(monitoredItemId);
3264
+ if (!monitoredItem) {
3265
+ return StatusCodes.BadMonitoredItemIdInvalid;
3266
+ }
3267
+ monitoredItem.setMonitoringMode(monitoringMode);
3268
+ return StatusCodes.Good;
3269
+ });
3270
+
3271
+ const response = new SetMonitoringModeResponse({
3272
+ results
3273
+ });
3274
+ sendResponse(response);
3275
+ }
3276
+ );
3277
+ }
3278
+
3279
+ // _on_TranslateBrowsePathsToNodeIds service
3280
+ protected _on_TranslateBrowsePathsToNodeIdsRequest(message: Message, channel: ServerSecureChannelLayer) {
3281
+ const request = message.request as TranslateBrowsePathsToNodeIdsRequest;
3282
+ assert(request instanceof TranslateBrowsePathsToNodeIdsRequest);
3283
+ const server = this;
3284
+
3285
+ this._apply_on_SessionObject(
3286
+ TranslateBrowsePathsToNodeIdsResponse,
3287
+ message,
3288
+ channel,
3289
+ async (
3290
+ session: ServerSession,
3291
+ sendResponse: (response: Response) => void,
3292
+ sendError: (statusCode: StatusCode) => void
3293
+ ) => {
3294
+ if (!request.browsePaths || request.browsePaths.length === 0) {
3295
+ return sendError(StatusCodes.BadNothingToDo);
3296
+ }
3297
+ if (server.engine.serverCapabilities.operationLimits.maxNodesPerTranslateBrowsePathsToNodeIds > 0) {
3298
+ if (
3299
+ request.browsePaths.length >
3300
+ server.engine.serverCapabilities.operationLimits.maxNodesPerTranslateBrowsePathsToNodeIds
3301
+ ) {
3302
+ return sendError(StatusCodes.BadTooManyOperations);
3303
+ }
3304
+ }
3305
+
3306
+ const browsePathsResults = request.browsePaths.map((browsePath) => server.engine.browsePath(browsePath));
3307
+
3308
+ const response = new TranslateBrowsePathsToNodeIdsResponse({
3309
+ diagnosticInfos: null,
3310
+ results: browsePathsResults
3311
+ });
3312
+
3313
+ sendResponse(response);
3314
+ }
3315
+ );
3316
+ }
3317
+
3318
+ // Call Service Result Codes
3319
+ // Symbolic Id Description
3320
+ // Bad_NothingToDo See Table 165 for the description of this result code.
3321
+ // Bad_TooManyOperations See Table 165 for the description of this result code.
3322
+ //
3323
+ protected _on_CallRequest(message: Message, channel: ServerSecureChannelLayer) {
3324
+ const server = this;
3325
+ const request = message.request as CallRequest;
3326
+ assert(request instanceof CallRequest);
3327
+
3328
+ this._apply_on_SessionObject(
3329
+ CallResponse,
3330
+ message,
3331
+ channel,
3332
+ (session: ServerSession, sendResponse: (response: Response) => void, sendError: (statusCode: StatusCode) => void) => {
3333
+ let response;
3334
+
3335
+ if (!request.methodsToCall || request.methodsToCall.length === 0) {
3336
+ return sendError(StatusCodes.BadNothingToDo);
3337
+ }
3338
+
3339
+ // the MaxNodesPerMethodCall Property indicates the maximum size of the methodsToCall array when
3340
+ // a Client calls the Call Service.
3341
+ let maxNodesPerMethodCall = server.engine.serverCapabilities.operationLimits.maxNodesPerMethodCall;
3342
+ maxNodesPerMethodCall = maxNodesPerMethodCall <= 0 ? 1000 : maxNodesPerMethodCall;
3343
+ if (request.methodsToCall.length > maxNodesPerMethodCall) {
3344
+ return sendError(StatusCodes.BadTooManyOperations);
3345
+ }
3346
+
3347
+ /* jshint validthis: true */
3348
+ const addressSpace = server.engine.addressSpace!;
3349
+
3350
+ const context = new SessionContext({ session, server });
3351
+
3352
+ async.map(
3353
+ request.methodsToCall,
3354
+ callMethodHelper.bind(null, context, addressSpace),
3355
+ (err?: Error | null, results?: (CallMethodResultOptions | undefined)[]) => {
3356
+ /* istanbul ignore next */
3357
+ if (err) {
3358
+ errorLog("ERROR in method Call !! ", err);
3359
+ }
3360
+ assert(Array.isArray(results));
3361
+ response = new CallResponse({
3362
+ results: results as CallMethodResultOptions[]
3363
+ });
3364
+ sendResponse(response);
3365
+ }
3366
+ );
3367
+ }
3368
+ );
3369
+ }
3370
+
3371
+ protected _on_RegisterNodesRequest(message: Message, channel: ServerSecureChannelLayer) {
3372
+ const server = this;
3373
+ const request = message.request as RegisterNodesRequest;
3374
+ assert(request instanceof RegisterNodesRequest);
3375
+
3376
+ this._apply_on_SessionObject(
3377
+ RegisterNodesResponse,
3378
+ message,
3379
+ channel,
3380
+ (session: ServerSession, sendResponse: (response: Response) => void, sendError: (statusCode: StatusCode) => void) => {
3381
+ let response;
3382
+
3383
+ if (!request.nodesToRegister || request.nodesToRegister.length === 0) {
3384
+ response = new RegisterNodesResponse({ responseHeader: { serviceResult: StatusCodes.BadNothingToDo } });
3385
+ return sendResponse(response);
3386
+ }
3387
+ if (server.engine.serverCapabilities.operationLimits.maxNodesPerRegisterNodes > 0) {
3388
+ if (
3389
+ request.nodesToRegister.length > server.engine.serverCapabilities.operationLimits.maxNodesPerRegisterNodes
3390
+ ) {
3391
+ return sendError(StatusCodes.BadTooManyOperations);
3392
+ }
3393
+ }
3394
+ // A list of NodeIds which the Client shall use for subsequent access operations. The
3395
+ // size and order of this list matches the size and order of the nodesToRegister
3396
+ // request parameter.
3397
+ // The Server may return the NodeId from the request or a new (an alias) NodeId. It
3398
+ // is recommended that the Server return a numeric NodeIds for aliasing.
3399
+ // In case no optimization is supported for a Node, the Server shall return the
3400
+ // NodeId from the request.
3401
+ const registeredNodeIds = request.nodesToRegister.map((nodeId) => session.registerNode(nodeId));
3402
+
3403
+ response = new RegisterNodesResponse({
3404
+ registeredNodeIds
3405
+ });
3406
+ sendResponse(response);
3407
+ }
3408
+ );
3409
+ }
3410
+
3411
+ protected _on_UnregisterNodesRequest(message: Message, channel: ServerSecureChannelLayer) {
3412
+ const server = this;
3413
+ const request = message.request as UnregisterNodesRequest;
3414
+ assert(request instanceof UnregisterNodesRequest);
3415
+
3416
+ this._apply_on_SessionObject(
3417
+ UnregisterNodesResponse,
3418
+ message,
3419
+ channel,
3420
+ (session: ServerSession, sendResponse: (response: Response) => void, sendError: (statusCode: StatusCode) => void) => {
3421
+ let response;
3422
+
3423
+ request.nodesToUnregister = request.nodesToUnregister || [];
3424
+
3425
+ if (!request.nodesToUnregister || request.nodesToUnregister.length === 0) {
3426
+ response = new UnregisterNodesResponse({ responseHeader: { serviceResult: StatusCodes.BadNothingToDo } });
3427
+ return sendResponse(response);
3428
+ }
3429
+
3430
+ if (server.engine.serverCapabilities.operationLimits.maxNodesPerRegisterNodes > 0) {
3431
+ if (
3432
+ request.nodesToUnregister.length > server.engine.serverCapabilities.operationLimits.maxNodesPerRegisterNodes
3433
+ ) {
3434
+ return sendError(StatusCodes.BadTooManyOperations);
3435
+ }
3436
+ }
3437
+
3438
+ request.nodesToUnregister.map((nodeId: NodeId) => session.unRegisterNode(nodeId));
3439
+
3440
+ response = new UnregisterNodesResponse({});
3441
+ sendResponse(response);
3442
+ }
3443
+ );
3444
+ }
3445
+
3446
+ /* istanbul ignore next */
3447
+ protected _on_Cancel(message: Message, channel: ServerSecureChannelLayer) {
3448
+ return g_sendError(channel, message, CancelResponse, StatusCodes.BadServiceUnsupported);
3449
+ }
3450
+
3451
+ // NodeManagement Service Set Overview
3452
+ // This Service Set defines Services to add and delete AddressSpace Nodes and References between them. All added
3453
+ // Nodes continue to exist in the AddressSpace even if the Client that created them disconnects from the Server.
3454
+ //
3455
+ /* istanbul ignore next */
3456
+ protected _on_AddNodes(message: Message, channel: ServerSecureChannelLayer) {
3457
+ return g_sendError(channel, message, AddNodesResponse, StatusCodes.BadServiceUnsupported);
3458
+ }
3459
+
3460
+ /* istanbul ignore next */
3461
+ protected _on_AddReferences(message: Message, channel: ServerSecureChannelLayer) {
3462
+ return g_sendError(channel, message, AddReferencesResponse, StatusCodes.BadServiceUnsupported);
3463
+ }
3464
+
3465
+ /* istanbul ignore next */
3466
+ protected _on_DeleteNodes(message: Message, channel: ServerSecureChannelLayer) {
3467
+ return g_sendError(channel, message, DeleteNodesResponse, StatusCodes.BadServiceUnsupported);
3468
+ }
3469
+
3470
+ /* istanbul ignore next */
3471
+ protected _on_DeleteReferences(message: Message, channel: ServerSecureChannelLayer) {
3472
+ return g_sendError(channel, message, DeleteReferencesResponse, StatusCodes.BadServiceUnsupported);
3473
+ }
3474
+
3475
+ // Query Service
3476
+ /* istanbul ignore next */
3477
+ protected _on_QueryFirst(message: Message, channel: ServerSecureChannelLayer) {
3478
+ return g_sendError(channel, message, QueryFirstResponse, StatusCodes.BadServiceUnsupported);
3479
+ }
3480
+
3481
+ /* istanbul ignore next */
3482
+ protected _on_QueryNext(message: Message, channel: ServerSecureChannelLayer) {
3483
+ return g_sendError(channel, message, QueryNextResponse, StatusCodes.BadServiceUnsupported);
3484
+ }
3485
+
3486
+ /* istanbul ignore next */
3487
+ protected _on_HistoryUpdate(message: Message, channel: ServerSecureChannelLayer) {
3488
+ return g_sendError(channel, message, HistoryUpdateResponse, StatusCodes.BadServiceUnsupported);
3489
+ }
3490
+
3491
+ private createEndpoint(port1: number, serverOptions: OPCUAServerOptions): OPCUAServerEndPoint {
3492
+ // add the tcp/ip endpoint with no security
3493
+ const endPoint = new OPCUAServerEndPoint({
3494
+ port: port1,
3495
+
3496
+ certificateManager: this.serverCertificateManager,
3497
+
3498
+ certificateChain: this.getCertificateChain(),
3499
+ privateKey: this.getPrivateKey(),
3500
+
3501
+ defaultSecureTokenLifetime: serverOptions.defaultSecureTokenLifetime || 600000,
3502
+ timeout: serverOptions.timeout || 3 * 60 * 1000,
3503
+
3504
+ maxConnections: this.maxConnectionsPerEndpoint,
3505
+ objectFactory: this.objectFactory,
3506
+ serverInfo: this.serverInfo
3507
+ });
3508
+ return endPoint;
3509
+ }
3510
+
3511
+ private createEndpointDescriptions(
3512
+ serverOption: OPCUAServerOptions,
3513
+ endpointOptions: OPCUAServerEndpointOptions
3514
+ ): OPCUAServerEndPoint {
3515
+ /* istanbul ignore next */
3516
+ if (!endpointOptions) {
3517
+ throw new Error("internal error");
3518
+ }
3519
+ const hostname = getFullyQualifiedDomainName();
3520
+ endpointOptions.hostname = endpointOptions.hostname || hostname;
3521
+ endpointOptions.port = endpointOptions.port || 26543;
3522
+
3523
+ /* istanbul ignore next */
3524
+ if (
3525
+ !Object.prototype.hasOwnProperty.call(endpointOptions,"port") ||
3526
+ !isFinite(endpointOptions.port!) ||
3527
+ typeof endpointOptions.port !== "number"
3528
+ ) {
3529
+ throw new Error("expecting a valid port (number)");
3530
+ }
3531
+
3532
+ const port = Number(endpointOptions.port || 0);
3533
+
3534
+ const endPoint = this.createEndpoint(port, serverOption);
3535
+
3536
+ endpointOptions.alternateHostname = endpointOptions.alternateHostname || [];
3537
+ const alternateHostname =
3538
+ endpointOptions.alternateHostname instanceof Array
3539
+ ? endpointOptions.alternateHostname
3540
+ : [endpointOptions.alternateHostname];
3541
+ const allowAnonymous = endpointOptions.allowAnonymous === undefined ? true : !!endpointOptions.allowAnonymous;
3542
+
3543
+ endPoint.addStandardEndpointDescriptions({
3544
+ allowAnonymous,
3545
+ securityModes: endpointOptions.securityModes,
3546
+ securityPolicies: endpointOptions.securityPolicies,
3547
+
3548
+ hostname: endpointOptions.hostname,
3549
+
3550
+ alternateHostname,
3551
+
3552
+ disableDiscovery: !!endpointOptions.disableDiscovery,
3553
+ // xx hostname,
3554
+ resourcePath: serverOption.resourcePath || ""
3555
+ });
3556
+ return endPoint;
3557
+ }
3558
+
3559
+ protected async initializeCM(): Promise<void> {
3560
+ await super.initializeCM();
3561
+ await this.userCertificateManager.initialize();
3562
+ }
3563
+ }
3564
+
3565
+ export interface RaiseEventAuditEventData extends RaiseEventData {
3566
+ actionTimeStamp: PseudoVariantDateTime;
3567
+ status: PseudoVariantBoolean;
3568
+ serverId: PseudoVariantString;
3569
+ /**
3570
+ * ClientAuditEntryId contains the human-readable AuditEntryId defined in Part 3.
3571
+ */
3572
+ clientAuditEntryId: PseudoVariantString;
3573
+ /**
3574
+ * The ClientUserId identifies the user of the client requesting an action. The ClientUserId can be
3575
+ * obtained from the UserIdentityToken passed in the ActivateSession call.
3576
+ */
3577
+ clientUserId: PseudoVariantString;
3578
+ sourceName: PseudoVariantString;
3579
+ }
3580
+
3581
+ export interface RaiseEventAuditUpdateMethodEventData extends RaiseEventAuditEventData {
3582
+ methodId: PseudoVariantNodeId;
3583
+ inputArguments: any;
3584
+ }
3585
+
3586
+ export interface RaiseEventAuditConditionCommentEventData extends RaiseEventAuditUpdateMethodEventData {
3587
+ eventId: PseudoVariantByteString;
3588
+ comment: PseudoVariantLocalizedText;
3589
+ }
3590
+
3591
+ export interface RaiseEventAuditSessionEventData extends RaiseEventAuditEventData {
3592
+ /**
3593
+ * part 5 - 6.4.7 AuditSessionEventType
3594
+ */
3595
+ sessionId: PseudoVariantNodeId;
3596
+ }
3597
+
3598
+ export interface RaiseEventAuditCreateSessionEventData extends RaiseEventAuditSessionEventData {
3599
+ /**
3600
+ * part 5 - 6.4.8 AuditCreateSessionEventType
3601
+ * SecureChannelId shall uniquely identify the SecureChannel.
3602
+ * The application shall use the same identifier in
3603
+ * all AuditEvents related to the Session Service Set (AuditCreateSessionEventType, AuditActivateSessionEventType
3604
+ * and their subtypes) and the SecureChannel Service Set (AuditChannelEventType and its subtype
3605
+ */
3606
+ secureChannelId: PseudoVariantString;
3607
+ revisedSessionTimeout: PseudoVariantDuration;
3608
+ clientCertificate: PseudoVariantByteString;
3609
+ clientCertificateThumbprint: PseudoVariantByteString;
3610
+ }
3611
+
3612
+ export interface RaiseEventAuditActivateSessionEventData extends RaiseEventAuditSessionEventData {
3613
+ /**
3614
+ * part 5 - 6.4.10 AuditActivateSessionEventType
3615
+ */
3616
+ clientSoftwareCertificates: PseudoVariantExtensionObjectArray;
3617
+ /**
3618
+ * UserIdentityToken reflects the userIdentityToken parameter of the ActivateSession Service call.
3619
+ * For Username/Password tokens the password should NOT be included.
3620
+ */
3621
+ userIdentityToken: PseudoVariantExtensionObject;
3622
+ /**
3623
+ * SecureChannelId shall uniquely identify the SecureChannel. The application shall use the same identifier
3624
+ * in all AuditEvents related to the Session Service Set (AuditCreateSessionEventType,
3625
+ * AuditActivateSessionEventType and their subtypes) and the SecureChannel Service Set
3626
+ * (AuditChannelEventType and its subtypes).
3627
+ */
3628
+ secureChannelId: PseudoVariantString;
3629
+ }
3630
+
3631
+ // tslint:disable:no-empty-interface
3632
+ export interface RaiseEventTransitionEventData extends RaiseEventData {}
3633
+
3634
+ export interface RaiseEventAuditUrlMismatchEventTypeData extends RaiseEventData {
3635
+ endpointUrl: PseudoVariantString;
3636
+ }
3637
+ export interface OPCUAServer {
3638
+ /**
3639
+ * @internal
3640
+ * @param eventType
3641
+ * @param options
3642
+ */
3643
+ raiseEvent(eventType: "AuditSessionEventType", options: RaiseEventAuditSessionEventData): void;
3644
+
3645
+ raiseEvent(eventType: "AuditCreateSessionEventType", options: RaiseEventAuditCreateSessionEventData): void;
3646
+
3647
+ raiseEvent(eventType: "AuditActivateSessionEventType", options: RaiseEventAuditActivateSessionEventData): void;
3648
+
3649
+ raiseEvent(eventType: "AuditCreateSessionEventType", options: RaiseEventData): void;
3650
+
3651
+ raiseEvent(eventType: "AuditConditionCommentEventType", options: RaiseEventAuditConditionCommentEventData): void;
3652
+
3653
+ raiseEvent(eventType: "AuditUrlMismatchEventType", options: RaiseEventAuditUrlMismatchEventTypeData): void;
3654
+
3655
+ raiseEvent(eventType: "TransitionEventType", options: RaiseEventTransitionEventData): void;
3656
+ }
3657
+
3658
+ export interface OPCUAServer extends EventEmitter {
3659
+ on(event: "create_session", eventHandler: (session: ServerSession) => void): this;
3660
+
3661
+ on(event: "session_activated", eventHandler: (session: ServerSession) => void): this;
3662
+
3663
+ on(event: "session_closed", eventHandler: (session: ServerSession, reason: string) => void): this;
3664
+
3665
+ on(event: "post_initialize", eventHandler: () => void): this;
3666
+
3667
+ /**
3668
+ * emitted when the server is trying to registered the LDS
3669
+ * but when the connection to the lds has failed
3670
+ * serverRegistrationPending is sent when the backoff signal of the
3671
+ * connection process is raised
3672
+ * @event serverRegistrationPending
3673
+ */
3674
+ on(event: "serverRegistrationPending", eventHandler: () => void): this;
3675
+
3676
+ /**
3677
+ * event raised when server has been successfully registered on the local discovery server
3678
+ * @event serverRegistered
3679
+ */
3680
+ on(event: "serverRegistered", eventHandler: () => void): this;
3681
+
3682
+ /**
3683
+ * event raised when server registration has been successfully renewed on the local discovery server
3684
+ * @event serverRegistered
3685
+ */
3686
+ on(event: "serverRegistrationRenewed", eventHandler: () => void): this;
3687
+
3688
+ /**
3689
+ * event raised when server has been successfully unregistered from the local discovery server
3690
+ * @event serverUnregistered
3691
+ */
3692
+ on(event: "serverUnregistered", eventHandler: () => void): this;
3693
+
3694
+ /**
3695
+ * event raised after the server has raised an OPCUA event toward a client
3696
+ */
3697
+ on(event: "event", eventHandler: (eventData: any) => void): this;
3698
+
3699
+ /**
3700
+ * event raised when the server received a request from one of its connected client.
3701
+ * useful for trace purpose.
3702
+ */
3703
+ on(event: "request", eventHandler: (request: Request, channel: ServerSecureChannelLayer) => void): this;
3704
+
3705
+ /**
3706
+ * event raised when the server send an response to a request to one of its connected client.
3707
+ * useful for trace purpose.
3708
+ */
3709
+ on(event: "response", eventHandler: (request: Response, channel: ServerSecureChannelLayer) => void): this;
3710
+
3711
+ /**
3712
+ * event raised when a new secure channel is opened
3713
+ */
3714
+ on(event: "newChannel", eventHandler: (channel: ServerSecureChannelLayer, endpoint: OPCUAServerEndPoint) => void): this;
3715
+
3716
+ /**
3717
+ * event raised when a new secure channel is closed
3718
+ */
3719
+ on(event: "closeChannel", eventHandler: (channel: ServerSecureChannelLayer, endpoint: OPCUAServerEndPoint) => void): this;
3720
+
3721
+ /**
3722
+ * event raised when the server refused a tcp connection from a client. ( for instance because too any connections)
3723
+ */
3724
+ on(event: "connectionRefused", eventHandler: (socketData: ISocketData, endpoint: OPCUAServerEndPoint) => void): this;
3725
+
3726
+ /**
3727
+ * event raised when a OpenSecureChannel has failed, it could be a invalid certificate or malformed message
3728
+ */
3729
+ on(
3730
+ event: "openSecureChannelFailure",
3731
+ eventHandler: (socketData: ISocketData, channelData: IChannelData, endpoint: OPCUAServerEndPoint) => void
3732
+ ): this;
3733
+
3734
+ on(event: string, eventHandler: (...args: [any?, ...any[]]) => void): this;
3735
+ }
3736
+
3737
+ // tslint:disable:no-var-requires
3738
+ const thenify = require("thenify");
3739
+ const opts = { multiArgs: false };
3740
+ OPCUAServer.prototype.start = thenify.withCallback(OPCUAServer.prototype.start, opts);
3741
+ OPCUAServer.prototype.initialize = thenify.withCallback(OPCUAServer.prototype.initialize, opts);
3742
+ OPCUAServer.prototype.shutdown = thenify.withCallback(OPCUAServer.prototype.shutdown, opts);