node-opcua-server 2.77.0 → 2.79.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/base_server.js +0 -11
- package/dist/base_server.js.map +1 -1
- package/dist/monitored_item.d.ts +8 -2
- package/dist/monitored_item.js +24 -6
- package/dist/monitored_item.js.map +1 -1
- package/dist/node_sampler.js.map +1 -1
- package/dist/opcua_server.js +66 -60
- package/dist/opcua_server.js.map +1 -1
- package/dist/sampling_func.d.ts +3 -0
- package/dist/sampling_func.js +3 -0
- package/dist/sampling_func.js.map +1 -0
- package/dist/server_engine.d.ts +2 -1
- package/dist/server_engine.js +49 -8
- package/dist/server_engine.js.map +1 -1
- package/dist/server_session.d.ts +5 -2
- package/dist/server_session.js +22 -2
- package/dist/server_session.js.map +1 -1
- package/dist/server_subscription.js +1 -1
- package/dist/server_subscription.js.map +1 -1
- package/dist/user_manager.js +27 -3
- package/dist/user_manager.js.map +1 -1
- package/package.json +34 -34
- package/source/base_server.ts +0 -11
- package/source/monitored_item.ts +36 -13
- package/source/node_sampler.ts +5 -2
- package/source/opcua_server.ts +84 -74
- package/source/sampling_func.ts +8 -0
- package/source/server_engine.ts +66 -14
- package/source/server_session.ts +18 -4
- package/source/server_subscription.ts +1 -1
- package/source/user_manager.ts +28 -4
package/source/opcua_server.ts
CHANGED
|
@@ -173,6 +173,7 @@ import { ISocketData } from "./i_socket_data";
|
|
|
173
173
|
import { IChannelData } from "./i_channel_data";
|
|
174
174
|
import { UAUserManagerBase, makeUserManager, UserManagerOptions } from "./user_manager";
|
|
175
175
|
import { bindRoleSet } from "./user_manager_ua";
|
|
176
|
+
import { SamplingFunc } from "./sampling_func";
|
|
176
177
|
|
|
177
178
|
function isSubscriptionIdInvalid(subscriptionId: number): boolean {
|
|
178
179
|
return subscriptionId < 0 || subscriptionId >= 0xffffffff;
|
|
@@ -403,7 +404,7 @@ function thumbprint(certificate?: Certificate): string {
|
|
|
403
404
|
*/
|
|
404
405
|
function monitoredItem_read_and_record_value(
|
|
405
406
|
self: MonitoredItem,
|
|
406
|
-
context: ISessionContext
|
|
407
|
+
context: ISessionContext,
|
|
407
408
|
oldValue: DataValue,
|
|
408
409
|
node: UAVariable,
|
|
409
410
|
itemToMonitor: any,
|
|
@@ -447,13 +448,7 @@ function monitoredItem_read_and_record_value_async(
|
|
|
447
448
|
});
|
|
448
449
|
}
|
|
449
450
|
|
|
450
|
-
function build_scanning_node_function(
|
|
451
|
-
context: ISessionContext,
|
|
452
|
-
addressSpace: AddressSpace,
|
|
453
|
-
monitoredItem: MonitoredItem,
|
|
454
|
-
itemToMonitor: any
|
|
455
|
-
): (dataValue: DataValue, callback: (err: Error | null, dataValue?: DataValue) => void) => void {
|
|
456
|
-
assert(context instanceof SessionContext);
|
|
451
|
+
function build_scanning_node_function(addressSpace: AddressSpace, itemToMonitor: any): SamplingFunc {
|
|
457
452
|
assert(itemToMonitor instanceof ReadValueId);
|
|
458
453
|
|
|
459
454
|
const node = addressSpace.findNode(itemToMonitor.nodeId) as UAVariable;
|
|
@@ -462,7 +457,11 @@ function build_scanning_node_function(
|
|
|
462
457
|
if (!node) {
|
|
463
458
|
errorLog(" INVALID NODE ID , ", itemToMonitor.nodeId.toString());
|
|
464
459
|
dump(itemToMonitor);
|
|
465
|
-
return (
|
|
460
|
+
return (
|
|
461
|
+
_sessionContext: ISessionContext,
|
|
462
|
+
_oldData: DataValue,
|
|
463
|
+
callback: (err: Error | null, dataValue?: DataValue) => void
|
|
464
|
+
) => {
|
|
466
465
|
callback(
|
|
467
466
|
null,
|
|
468
467
|
new DataValue({
|
|
@@ -483,13 +482,14 @@ function build_scanning_node_function(
|
|
|
483
482
|
|
|
484
483
|
return function func(
|
|
485
484
|
this: MonitoredItem,
|
|
485
|
+
sessionContext: ISessionContext,
|
|
486
486
|
oldDataValue: DataValue,
|
|
487
487
|
callback: (err: Error | null, dataValue?: DataValue) => void
|
|
488
488
|
) {
|
|
489
489
|
assert(this instanceof MonitoredItem);
|
|
490
490
|
assert(oldDataValue instanceof DataValue);
|
|
491
491
|
assert(typeof callback === "function");
|
|
492
|
-
monitoredItem_read_and_record_value_func(this,
|
|
492
|
+
monitoredItem_read_and_record_value_func(this, sessionContext, oldDataValue, node, itemToMonitor, callback);
|
|
493
493
|
};
|
|
494
494
|
} else {
|
|
495
495
|
// Attributes, other than the Value Attribute, are only monitored for a change in value.
|
|
@@ -499,13 +499,14 @@ function build_scanning_node_function(
|
|
|
499
499
|
// only record value when it has changed
|
|
500
500
|
return function func(
|
|
501
501
|
this: MonitoredItem,
|
|
502
|
+
sessionContext: ISessionContext,
|
|
502
503
|
oldDataValue: DataValue,
|
|
503
504
|
callback: (err: Error | null, dataValue?: DataValue) => void
|
|
504
505
|
) {
|
|
505
506
|
assert(this instanceof MonitoredItem);
|
|
506
507
|
assert(oldDataValue instanceof DataValue);
|
|
507
508
|
assert(typeof callback === "function");
|
|
508
|
-
const newDataValue = node.readAttribute(
|
|
509
|
+
const newDataValue = node.readAttribute(sessionContext, itemToMonitor.attributeId);
|
|
509
510
|
callback(null, newDataValue);
|
|
510
511
|
};
|
|
511
512
|
}
|
|
@@ -513,7 +514,7 @@ function build_scanning_node_function(
|
|
|
513
514
|
|
|
514
515
|
function prepareMonitoredItem(context: ISessionContext, addressSpace: AddressSpace, monitoredItem: MonitoredItem) {
|
|
515
516
|
const itemToMonitor = monitoredItem.itemToMonitor;
|
|
516
|
-
const readNodeFunc = build_scanning_node_function(
|
|
517
|
+
const readNodeFunc = build_scanning_node_function(addressSpace, itemToMonitor);
|
|
517
518
|
monitoredItem.samplingFunc = readNodeFunc;
|
|
518
519
|
}
|
|
519
520
|
|
|
@@ -1574,7 +1575,6 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
1574
1575
|
comment: { dataType: DataType.String, value: certificateStatus.toString() }
|
|
1575
1576
|
});
|
|
1576
1577
|
break;
|
|
1577
|
-
|
|
1578
1578
|
}
|
|
1579
1579
|
}
|
|
1580
1580
|
if (
|
|
@@ -1651,7 +1651,7 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
1651
1651
|
}
|
|
1652
1652
|
|
|
1653
1653
|
const length = buff.readUInt32LE(0) - serverNonce.length;
|
|
1654
|
-
password = buff.
|
|
1654
|
+
password = buff.subarray(4, 4 + length).toString("utf-8");
|
|
1655
1655
|
}
|
|
1656
1656
|
|
|
1657
1657
|
this.userManager
|
|
@@ -1833,7 +1833,8 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
1833
1833
|
// see Release 1.02 27 OPC Unified Architecture, Part 4
|
|
1834
1834
|
const session = this.createSession({
|
|
1835
1835
|
clientDescription: request.clientDescription,
|
|
1836
|
-
sessionTimeout: revisedSessionTimeout
|
|
1836
|
+
sessionTimeout: revisedSessionTimeout,
|
|
1837
|
+
server: this
|
|
1837
1838
|
});
|
|
1838
1839
|
session.endpoint = endpoint;
|
|
1839
1840
|
|
|
@@ -2101,8 +2102,6 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
2101
2102
|
return rejectConnection(this, StatusCodes.BadIdentityChangeNotSupported); // not sure about this code !
|
|
2102
2103
|
}
|
|
2103
2104
|
}
|
|
2104
|
-
|
|
2105
|
-
moveSessionToChannel(session, channel);
|
|
2106
2105
|
} else if (session.status === "screwed") {
|
|
2107
2106
|
// session has been used before being activated => this should be detected and session should be dismissed.
|
|
2108
2107
|
return rejectConnection(this, StatusCodes.BadSessionClosed);
|
|
@@ -2134,7 +2133,6 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
2134
2133
|
}
|
|
2135
2134
|
return rejectConnection(this, statusCode);
|
|
2136
2135
|
}
|
|
2137
|
-
session.userIdentityToken = request.userIdentityToken as UserIdentityToken;
|
|
2138
2136
|
|
|
2139
2137
|
// check if user access is granted
|
|
2140
2138
|
this.isUserAuthorized(
|
|
@@ -2150,6 +2148,11 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
2150
2148
|
if (!authorized) {
|
|
2151
2149
|
return rejectConnection(this, StatusCodes.BadUserAccessDenied);
|
|
2152
2150
|
} else {
|
|
2151
|
+
if (session.status === "active") {
|
|
2152
|
+
moveSessionToChannel(session, channel);
|
|
2153
|
+
}
|
|
2154
|
+
session.userIdentityToken = request.userIdentityToken as UserIdentityToken;
|
|
2155
|
+
|
|
2153
2156
|
// extract : OPC UA part 4 - 5.6.3
|
|
2154
2157
|
// Once used, a serverNonce cannot be used again. For that reason, the Server returns a new
|
|
2155
2158
|
// serverNonce each time the ActivateSession Service is called.
|
|
@@ -2160,13 +2163,6 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
2160
2163
|
response = new ActivateSessionResponse({ serverNonce: session.nonce });
|
|
2161
2164
|
channel.send_response("MSG", response, message);
|
|
2162
2165
|
|
|
2163
|
-
const userIdentityTokenPasswordRemoved = (userIdentityToken: any) => {
|
|
2164
|
-
const a = userIdentityToken.clone();
|
|
2165
|
-
// remove password
|
|
2166
|
-
a.password = "*************";
|
|
2167
|
-
return a;
|
|
2168
|
-
};
|
|
2169
|
-
|
|
2170
2166
|
// send OPCUA Event Notification
|
|
2171
2167
|
// see part 5 : 6.4.3 AuditEventType
|
|
2172
2168
|
// 6.4.7 AuditSessionEventType
|
|
@@ -2175,49 +2171,11 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
2175
2171
|
// xx assert(session.channel.clientCertificate instanceof Buffer);
|
|
2176
2172
|
assert(session.sessionTimeout > 0);
|
|
2177
2173
|
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
serverId: { dataType: "String", value: "" },
|
|
2185
|
-
|
|
2186
|
-
// ClientAuditEntryId contains the human-readable AuditEntryId defined in Part 3.
|
|
2187
|
-
clientAuditEntryId: { dataType: "String", value: "" },
|
|
2188
|
-
|
|
2189
|
-
// The ClientUserId identifies the user of the client requesting an action.
|
|
2190
|
-
// The ClientUserId can be obtained from the UserIdentityToken passed in the
|
|
2191
|
-
// ActivateSession call.
|
|
2192
|
-
clientUserId: { dataType: "String", value: "cc" },
|
|
2193
|
-
|
|
2194
|
-
sourceName: { dataType: "String", value: "Session/ActivateSession" },
|
|
2195
|
-
|
|
2196
|
-
/* part 5 - 6.4.7 AuditSessionEventType */
|
|
2197
|
-
sessionId: { dataType: "NodeId", value: session.nodeId },
|
|
2198
|
-
|
|
2199
|
-
/* part 5 - 6.4.10 AuditActivateSessionEventType */
|
|
2200
|
-
clientSoftwareCertificates: {
|
|
2201
|
-
arrayType: VariantArrayType.Array,
|
|
2202
|
-
dataType: "ExtensionObject" /* SignedSoftwareCertificate */,
|
|
2203
|
-
value: []
|
|
2204
|
-
},
|
|
2205
|
-
// UserIdentityToken reflects the userIdentityToken parameter of the ActivateSession
|
|
2206
|
-
// Service call.
|
|
2207
|
-
// For Username/Password tokens the password should NOT be included.
|
|
2208
|
-
userIdentityToken: {
|
|
2209
|
-
dataType: "ExtensionObject" /* UserIdentityToken */,
|
|
2210
|
-
value: userIdentityTokenPasswordRemoved(session.userIdentityToken)
|
|
2211
|
-
},
|
|
2212
|
-
|
|
2213
|
-
// SecureChannelId shall uniquely identify the SecureChannel. The application shall
|
|
2214
|
-
// use the same identifier in all AuditEvents related to the Session Service Set
|
|
2215
|
-
// (AuditCreateSessionEventType, AuditActivateSessionEventType and their subtypes) and
|
|
2216
|
-
// the SecureChannel Service Set (AuditChannelEventType and its subtypes).
|
|
2217
|
-
secureChannelId: { dataType: "String", value: session.channel!.channelId!.toString() }
|
|
2218
|
-
});
|
|
2219
|
-
}
|
|
2220
|
-
this.emit("session_activated", session, userIdentityTokenPasswordRemoved);
|
|
2174
|
+
raiseAuditActivateSessionEventType.call(this, session);
|
|
2175
|
+
|
|
2176
|
+
this.emit("session_activated", session, userIdentityTokenPasswordRemoved(session.userIdentityToken));
|
|
2177
|
+
|
|
2178
|
+
session.resendMonitoredItemInitialValues();
|
|
2221
2179
|
}
|
|
2222
2180
|
}
|
|
2223
2181
|
);
|
|
@@ -2574,7 +2532,7 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
2574
2532
|
const requestedMaxReferencesPerNode = Math.min(9876, request.requestedMaxReferencesPerNode);
|
|
2575
2533
|
assert(request.nodesToBrowse[0].schema.name === "BrowseDescription");
|
|
2576
2534
|
|
|
2577
|
-
const context =
|
|
2535
|
+
const context = session.sessionContext;
|
|
2578
2536
|
|
|
2579
2537
|
const f = callbackify(this.engine.browseWithAutomaticExpansion).bind(this.engine);
|
|
2580
2538
|
(f as any)(request.nodesToBrowse, context, (err?: Error | null, results?: BrowseResult[]) => {
|
|
@@ -2676,7 +2634,7 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
2676
2634
|
message,
|
|
2677
2635
|
channel,
|
|
2678
2636
|
(session: ServerSession, sendResponse: (response: Response) => void, sendError: (statusCode: StatusCode) => void) => {
|
|
2679
|
-
const context =
|
|
2637
|
+
const context = session.sessionContext;
|
|
2680
2638
|
|
|
2681
2639
|
let response;
|
|
2682
2640
|
|
|
@@ -2778,7 +2736,7 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
2778
2736
|
}
|
|
2779
2737
|
}
|
|
2780
2738
|
|
|
2781
|
-
const context =
|
|
2739
|
+
const context = session.sessionContext;
|
|
2782
2740
|
|
|
2783
2741
|
// ask for a refresh of asynchronous variables
|
|
2784
2742
|
this.engine.refreshValues(request.nodesToRead, 0, (err?: Error | null) => {
|
|
@@ -2849,7 +2807,7 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
2849
2807
|
nodeToWrite.nodeId = session.resolveRegisteredNode(nodeToWrite.nodeId);
|
|
2850
2808
|
}
|
|
2851
2809
|
|
|
2852
|
-
const context =
|
|
2810
|
+
const context = session.sessionContext;
|
|
2853
2811
|
|
|
2854
2812
|
assert(request.nodesToWrite[0].schema.name === "WriteValue");
|
|
2855
2813
|
this.engine.write(context, request.nodesToWrite, (err: Error | null, results?: StatusCode[]) => {
|
|
@@ -2882,7 +2840,7 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
2882
2840
|
message,
|
|
2883
2841
|
channel,
|
|
2884
2842
|
(session: ServerSession, sendResponse: (response: Response) => void, sendError: (statusCode: StatusCode) => void) => {
|
|
2885
|
-
const context =
|
|
2843
|
+
const context = session.sessionContext;
|
|
2886
2844
|
|
|
2887
2845
|
if (session.currentSubscriptionCount >= this.engine.serverCapabilities.maxSubscriptionsPerSession) {
|
|
2888
2846
|
return sendError(StatusCodes.BadTooManySubscriptions);
|
|
@@ -3417,7 +3375,7 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
3417
3375
|
/* jshint validthis: true */
|
|
3418
3376
|
const addressSpace = this.engine.addressSpace!;
|
|
3419
3377
|
|
|
3420
|
-
const context =
|
|
3378
|
+
const context = session.sessionContext;
|
|
3421
3379
|
|
|
3422
3380
|
async.map(
|
|
3423
3381
|
request.methodsToCall,
|
|
@@ -3623,6 +3581,58 @@ export class OPCUAServer extends OPCUABaseServer {
|
|
|
3623
3581
|
}
|
|
3624
3582
|
}
|
|
3625
3583
|
|
|
3584
|
+
const userIdentityTokenPasswordRemoved = (userIdentityToken: any) => {
|
|
3585
|
+
const a = userIdentityToken.clone();
|
|
3586
|
+
// remove password
|
|
3587
|
+
a.password = "*************";
|
|
3588
|
+
return a;
|
|
3589
|
+
};
|
|
3590
|
+
|
|
3591
|
+
function raiseAuditActivateSessionEventType(this: OPCUAServer, session: ServerSession) {
|
|
3592
|
+
if (this.isAuditing) {
|
|
3593
|
+
this.raiseEvent("AuditActivateSessionEventType", {
|
|
3594
|
+
/* part 5 - 6.4.3 AuditEventType */
|
|
3595
|
+
actionTimeStamp: { dataType: "DateTime", value: new Date() },
|
|
3596
|
+
status: { dataType: "Boolean", value: true },
|
|
3597
|
+
|
|
3598
|
+
serverId: { dataType: "String", value: "" },
|
|
3599
|
+
|
|
3600
|
+
// ClientAuditEntryId contains the human-readable AuditEntryId defined in Part 3.
|
|
3601
|
+
clientAuditEntryId: { dataType: "String", value: "" },
|
|
3602
|
+
|
|
3603
|
+
// The ClientUserId identifies the user of the client requesting an action.
|
|
3604
|
+
// The ClientUserId can be obtained from the UserIdentityToken passed in the
|
|
3605
|
+
// ActivateSession call.
|
|
3606
|
+
clientUserId: { dataType: "String", value: "cc" },
|
|
3607
|
+
|
|
3608
|
+
sourceName: { dataType: "String", value: "Session/ActivateSession" },
|
|
3609
|
+
|
|
3610
|
+
/* part 5 - 6.4.7 AuditSessionEventType */
|
|
3611
|
+
sessionId: { dataType: "NodeId", value: session.nodeId },
|
|
3612
|
+
|
|
3613
|
+
/* part 5 - 6.4.10 AuditActivateSessionEventType */
|
|
3614
|
+
clientSoftwareCertificates: {
|
|
3615
|
+
arrayType: VariantArrayType.Array,
|
|
3616
|
+
dataType: "ExtensionObject" /* SignedSoftwareCertificate */,
|
|
3617
|
+
value: []
|
|
3618
|
+
},
|
|
3619
|
+
// UserIdentityToken reflects the userIdentityToken parameter of the ActivateSession
|
|
3620
|
+
// Service call.
|
|
3621
|
+
// For Username/Password tokens the password should NOT be included.
|
|
3622
|
+
userIdentityToken: {
|
|
3623
|
+
dataType: "ExtensionObject" /* UserIdentityToken */,
|
|
3624
|
+
value: userIdentityTokenPasswordRemoved(session.userIdentityToken)
|
|
3625
|
+
},
|
|
3626
|
+
|
|
3627
|
+
// SecureChannelId shall uniquely identify the SecureChannel. The application shall
|
|
3628
|
+
// use the same identifier in all AuditEvents related to the Session Service Set
|
|
3629
|
+
// (AuditCreateSessionEventType, AuditActivateSessionEventType and their subtypes) and
|
|
3630
|
+
// the SecureChannel Service Set (AuditChannelEventType and its subtypes).
|
|
3631
|
+
secureChannelId: { dataType: "String", value: session.channel!.channelId!.toString() }
|
|
3632
|
+
});
|
|
3633
|
+
}
|
|
3634
|
+
}
|
|
3635
|
+
|
|
3626
3636
|
export interface RaiseEventAuditEventData extends RaiseEventData {
|
|
3627
3637
|
actionTimeStamp: PseudoVariantDateTime;
|
|
3628
3638
|
status: PseudoVariantBoolean;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ISessionContext } from "node-opcua-address-space-base";
|
|
2
|
+
import { DataValue } from "node-opcua-data-value";
|
|
3
|
+
|
|
4
|
+
export type SamplingFunc = (
|
|
5
|
+
context: ISessionContext,
|
|
6
|
+
dataValue: DataValue,
|
|
7
|
+
callback: (err: Error | null, dataValue?: DataValue) => void
|
|
8
|
+
) => void;
|
package/source/server_engine.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { EventEmitter } from "events";
|
|
|
5
5
|
import * as async from "async";
|
|
6
6
|
import * as chalk from "chalk";
|
|
7
7
|
import { assert } from "node-opcua-assert";
|
|
8
|
-
import { BinaryStream} from "node-opcua-binary-stream";
|
|
8
|
+
import { BinaryStream } from "node-opcua-binary-stream";
|
|
9
9
|
import {
|
|
10
10
|
addElement,
|
|
11
11
|
AddressSpace,
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
DTServerStatus,
|
|
30
30
|
resolveOpaqueOnAddressSpace,
|
|
31
31
|
ContinuationData,
|
|
32
|
+
IServerBase,
|
|
32
33
|
UARole
|
|
33
34
|
} from "node-opcua-address-space";
|
|
34
35
|
import { generateAddressSpace } from "node-opcua-address-space/nodeJS";
|
|
@@ -81,7 +82,8 @@ import {
|
|
|
81
82
|
ReadValueId,
|
|
82
83
|
TimeZoneDataType,
|
|
83
84
|
ProgramDiagnosticDataType,
|
|
84
|
-
CallMethodResultOptions
|
|
85
|
+
CallMethodResultOptions,
|
|
86
|
+
AggregateConfiguration
|
|
85
87
|
} from "node-opcua-types";
|
|
86
88
|
import { DataType, isValidVariant, Variant, VariantArrayType } from "node-opcua-variant";
|
|
87
89
|
|
|
@@ -94,6 +96,7 @@ import { ServerSession } from "./server_session";
|
|
|
94
96
|
import { Subscription } from "./server_subscription";
|
|
95
97
|
import { sessionsCompatibleForTransfer } from "./sessions_compatible_for_transfer";
|
|
96
98
|
import { OPCUAServerOptions } from "./opcua_server";
|
|
99
|
+
import { IUserManager } from "node-opcua-address-space/source";
|
|
97
100
|
|
|
98
101
|
const debugLog = make_debugLog(__filename);
|
|
99
102
|
const errorLog = make_errorLog(__filename);
|
|
@@ -266,6 +269,44 @@ function _get_next_subscriptionId() {
|
|
|
266
269
|
return next_subscriptionId++;
|
|
267
270
|
}
|
|
268
271
|
|
|
272
|
+
function checkReadProcessedDetails(historyReadDetails: ReadProcessedDetails): StatusCode {
|
|
273
|
+
if (!historyReadDetails.aggregateConfiguration) {
|
|
274
|
+
historyReadDetails.aggregateConfiguration = new AggregateConfiguration({
|
|
275
|
+
useServerCapabilitiesDefaults: true
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
if (historyReadDetails.aggregateConfiguration.useServerCapabilitiesDefaults) {
|
|
279
|
+
return StatusCodes.Good;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// The PercentDataGood and PercentDataBad shall follow the following relationship
|
|
283
|
+
// PercentDataGood ≥ (100 – PercentDataBad).
|
|
284
|
+
// If they are equal the result of the PercentDataGood calculation is used.
|
|
285
|
+
// If the values entered for PercentDataGood and PercentDataBad do not result in a valid calculation
|
|
286
|
+
// (e.g. Bad = 80; Good = 0) the result will have a StatusCode of Bad_AggregateInvalidInputs.
|
|
287
|
+
if (
|
|
288
|
+
historyReadDetails.aggregateConfiguration.percentDataGood <
|
|
289
|
+
100 - historyReadDetails.aggregateConfiguration.percentDataBad
|
|
290
|
+
) {
|
|
291
|
+
return StatusCodes.BadAggregateInvalidInputs;
|
|
292
|
+
}
|
|
293
|
+
// The StatusCode Bad_AggregateInvalidInputs will be returned if the value of PercentDataGood
|
|
294
|
+
// or PercentDataBad exceed 100.
|
|
295
|
+
if (
|
|
296
|
+
historyReadDetails.aggregateConfiguration.percentDataGood > 100 ||
|
|
297
|
+
historyReadDetails.aggregateConfiguration.percentDataGood < 0
|
|
298
|
+
) {
|
|
299
|
+
return StatusCodes.BadAggregateInvalidInputs;
|
|
300
|
+
}
|
|
301
|
+
if (
|
|
302
|
+
historyReadDetails.aggregateConfiguration.percentDataBad > 100 ||
|
|
303
|
+
historyReadDetails.aggregateConfiguration.percentDataBad < 0
|
|
304
|
+
) {
|
|
305
|
+
return StatusCodes.BadAggregateInvalidInputs;
|
|
306
|
+
}
|
|
307
|
+
return StatusCodes.Good;
|
|
308
|
+
}
|
|
309
|
+
|
|
269
310
|
export type StringGetter = () => string;
|
|
270
311
|
|
|
271
312
|
export interface ServerEngineOptions {
|
|
@@ -284,6 +325,7 @@ export interface ServerEngineOptions {
|
|
|
284
325
|
export interface CreateSessionOption {
|
|
285
326
|
clientDescription?: ApplicationDescription;
|
|
286
327
|
sessionTimeout?: number;
|
|
328
|
+
server: IServerBase;
|
|
287
329
|
}
|
|
288
330
|
|
|
289
331
|
export type ClosingReason = "Timeout" | "Terminated" | "CloseSession" | "Forcing";
|
|
@@ -400,7 +442,7 @@ export class ServerEngine extends EventEmitter {
|
|
|
400
442
|
counter += session.currentSubscriptionCount;
|
|
401
443
|
});
|
|
402
444
|
// we also need to add the orphan subscriptions
|
|
403
|
-
counter += this._orphanPublishEngine ? this._orphanPublishEngine
|
|
445
|
+
counter += this._orphanPublishEngine ? this._orphanPublishEngine.subscriptions.length : 0;
|
|
404
446
|
return counter;
|
|
405
447
|
});
|
|
406
448
|
|
|
@@ -1556,6 +1598,12 @@ export class ServerEngine extends EventEmitter {
|
|
|
1556
1598
|
if (!historyReadDetails.aggregateType || historyReadDetails.aggregateType.length !== nodesToRead.length) {
|
|
1557
1599
|
return callback(null, [new HistoryReadResult({ statusCode: StatusCodes.BadInvalidArgument })]);
|
|
1558
1600
|
}
|
|
1601
|
+
|
|
1602
|
+
// chkec parameters
|
|
1603
|
+
const parameterStatus = checkReadProcessedDetails(historyReadDetails);
|
|
1604
|
+
if (parameterStatus !== StatusCodes.Good) {
|
|
1605
|
+
return callback(null, [new HistoryReadResult({ statusCode: parameterStatus })]);
|
|
1606
|
+
}
|
|
1559
1607
|
const promises: Promise<HistoryReadResult>[] = [];
|
|
1560
1608
|
let index = 0;
|
|
1561
1609
|
for (const nodeToRead of nodesToRead) {
|
|
@@ -1632,7 +1680,7 @@ export class ServerEngine extends EventEmitter {
|
|
|
1632
1680
|
*/
|
|
1633
1681
|
public createSession(options: CreateSessionOption): ServerSession {
|
|
1634
1682
|
options = options || {};
|
|
1635
|
-
|
|
1683
|
+
options.server = options.server || {};
|
|
1636
1684
|
debugLog("createSession : increasing serverDiagnosticsSummary cumulatedSessionCount/currentSessionCount ");
|
|
1637
1685
|
this.serverDiagnosticsSummary.cumulatedSessionCount += 1;
|
|
1638
1686
|
this.serverDiagnosticsSummary.currentSessionCount += 1;
|
|
@@ -1642,7 +1690,7 @@ export class ServerEngine extends EventEmitter {
|
|
|
1642
1690
|
const sessionTimeout = options.sessionTimeout || 1000;
|
|
1643
1691
|
assert(typeof sessionTimeout === "number");
|
|
1644
1692
|
|
|
1645
|
-
const session = new ServerSession(this, sessionTimeout);
|
|
1693
|
+
const session = new ServerSession(this, options.server.userManager!, sessionTimeout);
|
|
1646
1694
|
|
|
1647
1695
|
debugLog("createSession :sessionTimeout = ", session.sessionTimeout);
|
|
1648
1696
|
|
|
@@ -1851,7 +1899,7 @@ export class ServerEngine extends EventEmitter {
|
|
|
1851
1899
|
if (subscription.$session) {
|
|
1852
1900
|
subscription.$session._unexposeSubscriptionDiagnostics(subscription);
|
|
1853
1901
|
}
|
|
1854
|
-
|
|
1902
|
+
|
|
1855
1903
|
await ServerSidePublishEngine.transferSubscription(subscription, session.publishEngine, sendInitialValues);
|
|
1856
1904
|
subscription.$session = session;
|
|
1857
1905
|
|
|
@@ -1987,14 +2035,18 @@ export class ServerEngine extends EventEmitter {
|
|
|
1987
2035
|
}
|
|
1988
2036
|
|
|
1989
2037
|
private _exposeSubscriptionDiagnostics(subscription: Subscription): void {
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
2038
|
+
try {
|
|
2039
|
+
debugLog("ServerEngine#_exposeSubscriptionDiagnostics", subscription.subscriptionId);
|
|
2040
|
+
const subscriptionDiagnosticsArray = this._getServerSubscriptionDiagnosticsArrayNode();
|
|
2041
|
+
const subscriptionDiagnostics = subscription.subscriptionDiagnostics;
|
|
2042
|
+
assert((subscriptionDiagnostics as any).$subscription === subscription);
|
|
2043
|
+
assert(subscriptionDiagnostics instanceof SubscriptionDiagnosticsDataType);
|
|
2044
|
+
|
|
2045
|
+
if (subscriptionDiagnostics && subscriptionDiagnosticsArray) {
|
|
2046
|
+
addElement(subscriptionDiagnostics, subscriptionDiagnosticsArray);
|
|
2047
|
+
}
|
|
2048
|
+
} catch (err) {
|
|
2049
|
+
console.log("err", err);
|
|
1998
2050
|
}
|
|
1999
2051
|
}
|
|
2000
2052
|
|
package/source/server_session.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import * as crypto from "crypto";
|
|
7
7
|
import { EventEmitter } from "events";
|
|
8
8
|
|
|
9
|
+
import { assert } from "node-opcua-assert";
|
|
9
10
|
import {
|
|
10
11
|
addElement,
|
|
11
12
|
AddressSpace,
|
|
@@ -19,10 +20,11 @@ import {
|
|
|
19
20
|
UASessionDiagnosticsVariable,
|
|
20
21
|
UASessionSecurityDiagnostics,
|
|
21
22
|
DTSessionDiagnostics,
|
|
22
|
-
DTSessionSecurityDiagnostics
|
|
23
|
+
DTSessionSecurityDiagnostics,
|
|
24
|
+
SessionContext,
|
|
25
|
+
IUserManager
|
|
23
26
|
} from "node-opcua-address-space";
|
|
24
|
-
|
|
25
|
-
import { assert } from "node-opcua-assert";
|
|
27
|
+
import { ISessionContext } from "node-opcua-address-space-base";
|
|
26
28
|
import { minOPCUADate, randomGuid } from "node-opcua-basic-types";
|
|
27
29
|
import { SessionDiagnosticsDataType, SessionSecurityDiagnosticsDataType, SubscriptionDiagnosticsDataType } from "node-opcua-common";
|
|
28
30
|
import { QualifiedName, NodeClass } from "node-opcua-data-model";
|
|
@@ -119,6 +121,7 @@ export class ServerSession extends EventEmitter implements ISubscriber, ISession
|
|
|
119
121
|
public clientDescription?: ApplicationDescription;
|
|
120
122
|
public channelId?: number | null;
|
|
121
123
|
public continuationPointManager: ContinuationPointManager;
|
|
124
|
+
public sessionContext: ISessionContext;
|
|
122
125
|
|
|
123
126
|
// ISubscriber
|
|
124
127
|
public _watchDog?: WatchDog;
|
|
@@ -134,13 +137,18 @@ export class ServerSession extends EventEmitter implements ISubscriber, ISession
|
|
|
134
137
|
|
|
135
138
|
private channel_abort_event_handler: any;
|
|
136
139
|
|
|
137
|
-
constructor(parent: ServerEngine, sessionTimeout: number) {
|
|
140
|
+
constructor(parent: ServerEngine, userManager: IUserManager, sessionTimeout: number) {
|
|
138
141
|
super();
|
|
139
142
|
|
|
140
143
|
this.parent = parent; // SessionEngine
|
|
141
144
|
|
|
142
145
|
ServerSession.registry.register(this);
|
|
143
146
|
|
|
147
|
+
this.sessionContext = new SessionContext({
|
|
148
|
+
session: this,
|
|
149
|
+
server: { userManager }
|
|
150
|
+
});
|
|
151
|
+
|
|
144
152
|
assert(isFinite(sessionTimeout));
|
|
145
153
|
assert(sessionTimeout >= 0, " sessionTimeout");
|
|
146
154
|
this.sessionTimeout = sessionTimeout;
|
|
@@ -846,6 +854,12 @@ export class ServerSession extends EventEmitter implements ISubscriber, ISession
|
|
|
846
854
|
return this.sessionObject;
|
|
847
855
|
}
|
|
848
856
|
|
|
857
|
+
public async resendMonitoredItemInitialValues(): Promise<void> {
|
|
858
|
+
for (const subscription of this.publishEngine.subscriptions) {
|
|
859
|
+
await subscription.resendInitialValues();
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
|
|
849
863
|
/**
|
|
850
864
|
*
|
|
851
865
|
* @private
|
|
@@ -783,7 +783,7 @@ export class Subscription extends EventEmitter {
|
|
|
783
783
|
}
|
|
784
784
|
assert(this.monitoredItemCount === 0);
|
|
785
785
|
|
|
786
|
-
if (this.$session) {
|
|
786
|
+
if (this.$session && this.$session._unexposeSubscriptionDiagnostics) {
|
|
787
787
|
this.$session._unexposeSubscriptionDiagnostics(this);
|
|
788
788
|
}
|
|
789
789
|
this.state = SubscriptionState.CLOSED;
|
package/source/user_manager.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { IUserManager, UARoleSet } from "node-opcua-address-space";
|
|
2
2
|
import { NodeId } from "node-opcua-nodeid";
|
|
3
3
|
import { IdentityMappingRuleType } from "node-opcua-types";
|
|
4
|
+
import { make_errorLog } from "node-opcua-debug";
|
|
4
5
|
import { ServerSession } from "./server_session";
|
|
5
6
|
|
|
7
|
+
const errorLog = make_errorLog(__filename);
|
|
8
|
+
|
|
6
9
|
export type ValidUserFunc = (this: ServerSession, username: string, password: string) => boolean;
|
|
7
10
|
export type ValidUserAsyncFunc = (
|
|
8
11
|
this: ServerSession,
|
|
@@ -45,7 +48,18 @@ export class UAUserManager1 extends UAUserManagerBase {
|
|
|
45
48
|
super();
|
|
46
49
|
}
|
|
47
50
|
getUserRoles(user: string): NodeId[] {
|
|
48
|
-
|
|
51
|
+
if (!this.options.getUserRoles) return [];
|
|
52
|
+
try {
|
|
53
|
+
return this.options.getUserRoles(user);
|
|
54
|
+
} catch (err) {
|
|
55
|
+
if (err instanceof Error) {
|
|
56
|
+
errorLog(
|
|
57
|
+
"[NODE-OPCUA-E27] userManager provided getUserRoles method has thrown an exception, please fix your code! "
|
|
58
|
+
);
|
|
59
|
+
errorLog(err.message, "\n", (err as Error).stack?.split("\n").slice(0,2).join("\n"));
|
|
60
|
+
}
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
49
63
|
}
|
|
50
64
|
|
|
51
65
|
async isValidUser(session: ServerSession, username: string, password: string): Promise<boolean> {
|
|
@@ -56,9 +70,19 @@ export class UAUserManager1 extends UAUserManagerBase {
|
|
|
56
70
|
resolve(isAuthorized!);
|
|
57
71
|
});
|
|
58
72
|
});
|
|
59
|
-
} else if(typeof this.options.isValidUser === "function") {
|
|
60
|
-
|
|
61
|
-
|
|
73
|
+
} else if (typeof this.options.isValidUser === "function") {
|
|
74
|
+
try {
|
|
75
|
+
const authorized = this.options.isValidUser!.call(session, username, password);
|
|
76
|
+
return authorized;
|
|
77
|
+
} catch (err) {
|
|
78
|
+
if (err instanceof Error) {
|
|
79
|
+
errorLog(
|
|
80
|
+
"[NODE-OPCUA-E26] userManager provided isValidUser method has thrown an exception, please fix your code!"
|
|
81
|
+
);
|
|
82
|
+
errorLog(err.message, "\n", (err as Error).stack?.split("\n").slice(0,2).join("\n"));
|
|
83
|
+
}
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
62
86
|
} else {
|
|
63
87
|
return false;
|
|
64
88
|
}
|