node-opcua-server 2.56.3 → 2.60.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/LICENSE +20 -20
- package/dist/i_socket_data.d.ts +2 -2
- package/dist/opcua_server.d.ts +1 -1
- package/dist/opcua_server.js +1 -0
- package/dist/opcua_server.js.map +1 -1
- package/dist/server_end_point.js +31 -17
- package/dist/server_end_point.js.map +1 -1
- package/dist/server_engine.js +31 -23
- package/dist/server_engine.js.map +1 -1
- package/dist/server_session.d.ts +6 -2
- package/dist/server_session.js +12 -3
- package/dist/server_session.js.map +1 -1
- package/dist/server_subscription.d.ts +2 -2
- package/dist/server_subscription.js +11 -7
- package/dist/server_subscription.js.map +1 -1
- package/package.json +43 -43
- package/source/i_socket_data.ts +2 -2
- package/source/index.ts +14 -14
- package/source/opcua_server.ts +2 -2
- package/source/server_end_point.ts +49 -30
- package/source/server_engine.ts +40 -31
- package/source/server_session.ts +18 -6
- package/source/server_subscription.ts +13 -9
- package/test_helpers/create_certificates.js +1 -1
|
@@ -20,7 +20,8 @@ import {
|
|
|
20
20
|
ServerSecureChannelLayer,
|
|
21
21
|
ServerSecureChannelParent,
|
|
22
22
|
toURI,
|
|
23
|
-
AsymmetricAlgorithmSecurityHeader
|
|
23
|
+
AsymmetricAlgorithmSecurityHeader,
|
|
24
|
+
IServerSessionBase
|
|
24
25
|
} from "node-opcua-secure-channel";
|
|
25
26
|
import { UserTokenType } from "node-opcua-service-endpoints";
|
|
26
27
|
import { EndpointDescription } from "node-opcua-service-endpoints";
|
|
@@ -78,6 +79,11 @@ function extractChannelData(channel: ServerSecureChannelLayer): IChannelData {
|
|
|
78
79
|
}
|
|
79
80
|
|
|
80
81
|
function dumpChannelInfo(channels: ServerSecureChannelLayer[]): void {
|
|
82
|
+
function d(s: IServerSessionBase) {
|
|
83
|
+
return `[ status=${s.status} lastSeen=${s.clientLastContactTime.toFixed(0)}ms sessionName=${s.sessionName} timeout=${
|
|
84
|
+
s.sessionTimeout
|
|
85
|
+
} ]`;
|
|
86
|
+
}
|
|
81
87
|
function dumpChannel(channel: ServerSecureChannelLayer): void {
|
|
82
88
|
console.log("------------------------------------------------------");
|
|
83
89
|
console.log(" channelId = ", channel.channelId);
|
|
@@ -87,6 +93,8 @@ function dumpChannelInfo(channels: ServerSecureChannelLayer[]): void {
|
|
|
87
93
|
console.log("");
|
|
88
94
|
console.log(" bytesWritten = ", channel.bytesWritten);
|
|
89
95
|
console.log(" bytesRead = ", channel.bytesRead);
|
|
96
|
+
console.log(" sessions = ", Object.keys(channel.sessionTokens).length);
|
|
97
|
+
console.log(Object.values(channel.sessionTokens).map(d).join("\n"));
|
|
90
98
|
|
|
91
99
|
const socket = (channel as any).transport?._socket;
|
|
92
100
|
if (!socket) {
|
|
@@ -181,6 +189,10 @@ function getUniqueName(name: string, collection: { [key: string]: number }) {
|
|
|
181
189
|
return name;
|
|
182
190
|
}
|
|
183
191
|
}
|
|
192
|
+
|
|
193
|
+
interface ServerSecureChannelLayerPriv extends ServerSecureChannelLayer {
|
|
194
|
+
_unpreregisterChannelEvent?: () => void;
|
|
195
|
+
}
|
|
184
196
|
/**
|
|
185
197
|
* OPCUAServerEndPoint a Server EndPoint.
|
|
186
198
|
* A sever end point is listening to one port
|
|
@@ -264,6 +276,7 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
|
|
|
264
276
|
this._privateKey = emptyPrivateKeyPEM;
|
|
265
277
|
|
|
266
278
|
assert(Object.keys(this._channels).length === 0, "OPCUAServerEndPoint channels must have been deleted");
|
|
279
|
+
|
|
267
280
|
this._channels = {};
|
|
268
281
|
this.serverInfo = new ApplicationDescription({});
|
|
269
282
|
|
|
@@ -660,7 +673,7 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
|
|
|
660
673
|
|
|
661
674
|
private _on_client_connection(socket: Socket) {
|
|
662
675
|
// a client is attempting a connection on the socket
|
|
663
|
-
|
|
676
|
+
socket.setNoDelay(true);
|
|
664
677
|
|
|
665
678
|
debugLog("OPCUAServerEndPoint#_on_client_connection", this._started);
|
|
666
679
|
if (!this._started) {
|
|
@@ -673,6 +686,20 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
|
|
|
673
686
|
socket.end();
|
|
674
687
|
return;
|
|
675
688
|
}
|
|
689
|
+
const deny_connection = () => {
|
|
690
|
+
console.log(
|
|
691
|
+
chalk.bgWhite.cyan(
|
|
692
|
+
"OPCUAServerEndPoint#_on_client_connection " +
|
|
693
|
+
"The maximum number of connection has been reached - Connection is refused"
|
|
694
|
+
)
|
|
695
|
+
);
|
|
696
|
+
const reason = "maxConnections reached (" + this.maxConnections + ")";
|
|
697
|
+
const socketData = extractSocketData(socket, reason);
|
|
698
|
+
this.emit("connectionRefused", socketData);
|
|
699
|
+
|
|
700
|
+
socket.end();
|
|
701
|
+
socket.destroy();
|
|
702
|
+
};
|
|
676
703
|
|
|
677
704
|
const establish_connection = () => {
|
|
678
705
|
const nbConnections = Object.keys(this._channels).length;
|
|
@@ -684,18 +711,7 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
|
|
|
684
711
|
this.maxConnections
|
|
685
712
|
);
|
|
686
713
|
if (nbConnections >= this.maxConnections) {
|
|
687
|
-
|
|
688
|
-
chalk.bgWhite.cyan(
|
|
689
|
-
"OPCUAServerEndPoint#_on_client_connection " +
|
|
690
|
-
"The maximum number of connection has been reached - Connection is refused"
|
|
691
|
-
)
|
|
692
|
-
);
|
|
693
|
-
const reason = "maxConnections reached (" + this.maxConnections + ")";
|
|
694
|
-
const socketData = extractSocketData(socket, reason);
|
|
695
|
-
this.emit("connectionRefused", socketData);
|
|
696
|
-
|
|
697
|
-
socket.end();
|
|
698
|
-
(socket as any).destroy();
|
|
714
|
+
deny_connection();
|
|
699
715
|
return;
|
|
700
716
|
}
|
|
701
717
|
|
|
@@ -708,6 +724,8 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
|
|
|
708
724
|
timeout: this.timeout
|
|
709
725
|
});
|
|
710
726
|
|
|
727
|
+
debugLog("channel Timeout = >", channel.timeout);
|
|
728
|
+
|
|
711
729
|
socket.resume();
|
|
712
730
|
|
|
713
731
|
this._preregisterChannel(channel);
|
|
@@ -722,7 +740,7 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
|
|
|
722
740
|
this.emit("openSecureChannelFailure", socketData, channelData);
|
|
723
741
|
|
|
724
742
|
socket.end();
|
|
725
|
-
|
|
743
|
+
socket.destroy();
|
|
726
744
|
} else {
|
|
727
745
|
debugLog("server receiving a client connection");
|
|
728
746
|
this._registerChannel(channel);
|
|
@@ -734,13 +752,13 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
|
|
|
734
752
|
this.emit("message", message, channel, this);
|
|
735
753
|
});
|
|
736
754
|
};
|
|
755
|
+
|
|
737
756
|
// Each SecureChannel exists until it is explicitly closed or until the last token has expired and the overlap
|
|
738
757
|
// period has elapsed. A Server application should limit the number of SecureChannels.
|
|
739
758
|
// To protect against misbehaving Clients and denial of service attacks, the Server shall close the oldest
|
|
740
759
|
// SecureChannel that has no Session assigned before reaching the maximum number of supported SecureChannels.
|
|
741
|
-
this._prevent_DDOS_Attack(establish_connection);
|
|
760
|
+
this._prevent_DDOS_Attack(establish_connection, deny_connection);
|
|
742
761
|
}
|
|
743
|
-
|
|
744
762
|
private _preregisterChannel(channel: ServerSecureChannelLayer) {
|
|
745
763
|
// _preregisterChannel is used to keep track of channel for which
|
|
746
764
|
// that are in early stage of the hand shaking process.
|
|
@@ -750,14 +768,14 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
|
|
|
750
768
|
|
|
751
769
|
assert(!Object.prototype.hasOwnProperty.call(this._channels, channel.hashKey), " channel already preregistered!");
|
|
752
770
|
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
771
|
+
const channelPriv = <ServerSecureChannelLayerPriv>channel;
|
|
772
|
+
this._channels[channel.hashKey] = channelPriv;
|
|
773
|
+
channelPriv._unpreregisterChannelEvent = () => {
|
|
756
774
|
debugLog("Channel received an abort event during the preregistration phase");
|
|
757
775
|
this._un_pre_registerChannel(channel);
|
|
758
776
|
channel.dispose();
|
|
759
777
|
};
|
|
760
|
-
channel.on("abort",
|
|
778
|
+
channel.on("abort", channelPriv._unpreregisterChannelEvent);
|
|
761
779
|
}
|
|
762
780
|
|
|
763
781
|
private _un_pre_registerChannel(channel: ServerSecureChannelLayer) {
|
|
@@ -765,11 +783,12 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
|
|
|
765
783
|
debugLog("Already un preregistered ?", channel.hashKey);
|
|
766
784
|
return;
|
|
767
785
|
}
|
|
768
|
-
|
|
769
786
|
delete this._channels[channel.hashKey];
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
787
|
+
const channelPriv = <ServerSecureChannelLayerPriv>channel;
|
|
788
|
+
if (typeof channelPriv._unpreregisterChannelEvent === "function") {
|
|
789
|
+
channel.removeListener("abort", channelPriv._unpreregisterChannelEvent!);
|
|
790
|
+
channelPriv._unpreregisterChannelEvent = undefined;
|
|
791
|
+
}
|
|
773
792
|
}
|
|
774
793
|
|
|
775
794
|
/**
|
|
@@ -861,23 +880,23 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
|
|
|
861
880
|
/**
|
|
862
881
|
* @private
|
|
863
882
|
*/
|
|
864
|
-
private _prevent_DDOS_Attack(establish_connection: () => void) {
|
|
883
|
+
private _prevent_DDOS_Attack(establish_connection: () => void, deny_connection: () => void) {
|
|
865
884
|
const nbConnections = this.activeChannelCount;
|
|
866
885
|
|
|
867
886
|
if (nbConnections >= this.maxConnections) {
|
|
868
887
|
// istanbul ignore next
|
|
869
|
-
|
|
888
|
+
errorLog(chalk.bgRed.white("PREVENTING DDOS ATTACK => maxConnection =" + this.maxConnections));
|
|
870
889
|
|
|
871
890
|
const unused_channels: ServerSecureChannelLayer[] = this.getChannels().filter((channel1: ServerSecureChannelLayer) => {
|
|
872
891
|
return !channel1.isOpened && !channel1.hasSession;
|
|
873
892
|
});
|
|
874
893
|
if (unused_channels.length === 0) {
|
|
875
894
|
// all channels are in used , we cannot get any
|
|
876
|
-
|
|
895
|
+
errorLog("All Channel are in used ! let cancel this one");
|
|
877
896
|
// istanbul ignore next
|
|
878
897
|
console.log(" - all channel are used !!!!");
|
|
879
898
|
dumpChannelInfo(this.getChannels());
|
|
880
|
-
|
|
899
|
+
setTimeout(deny_connection, 10);
|
|
881
900
|
return;
|
|
882
901
|
}
|
|
883
902
|
// istanbul ignore next
|
|
@@ -888,7 +907,7 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
|
|
|
888
907
|
);
|
|
889
908
|
}
|
|
890
909
|
const channel = unused_channels[0];
|
|
891
|
-
|
|
910
|
+
errorLog("Closing channel ", channel.hashKey);
|
|
892
911
|
channel.close(() => {
|
|
893
912
|
// istanbul ignore next
|
|
894
913
|
if (doDebug) {
|
package/source/server_engine.ts
CHANGED
|
@@ -26,7 +26,6 @@ import {
|
|
|
26
26
|
UAVariable,
|
|
27
27
|
UAServerDiagnostics,
|
|
28
28
|
BindVariableOptions,
|
|
29
|
-
MethodFunctorCallback,
|
|
30
29
|
ISessionContext,
|
|
31
30
|
DTServerStatus,
|
|
32
31
|
resolveOpaqueOnAddressSpace,
|
|
@@ -81,7 +80,8 @@ import {
|
|
|
81
80
|
WriteValue,
|
|
82
81
|
ReadValueId,
|
|
83
82
|
TimeZoneDataType,
|
|
84
|
-
ProgramDiagnosticDataType
|
|
83
|
+
ProgramDiagnosticDataType,
|
|
84
|
+
CallMethodResultOptions
|
|
85
85
|
} from "node-opcua-types";
|
|
86
86
|
import { DataType, isValidVariant, Variant, VariantArrayType } from "node-opcua-variant";
|
|
87
87
|
|
|
@@ -116,7 +116,7 @@ function setSubscriptionDurable(
|
|
|
116
116
|
this: ServerEngine,
|
|
117
117
|
inputArguments: Variant[],
|
|
118
118
|
context: ISessionContext,
|
|
119
|
-
callback:
|
|
119
|
+
callback: CallbackT<CallMethodResultOptions>
|
|
120
120
|
) {
|
|
121
121
|
// see https://reference.opcfoundation.org/v104/Core/docs/Part5/9.3/
|
|
122
122
|
// https://reference.opcfoundation.org/v104/Core/docs/Part4/6.8/
|
|
@@ -201,7 +201,7 @@ function getMonitoredItemsId(
|
|
|
201
201
|
this: ServerEngine,
|
|
202
202
|
inputArguments: Variant[],
|
|
203
203
|
context: ISessionContext,
|
|
204
|
-
callback:
|
|
204
|
+
callback: CallbackT<CallMethodResultOptions>
|
|
205
205
|
) {
|
|
206
206
|
assert(Array.isArray(inputArguments));
|
|
207
207
|
assert(typeof callback === "function");
|
|
@@ -229,8 +229,8 @@ function getMonitoredItemsId(
|
|
|
229
229
|
const callMethodResult = new CallMethodResult({
|
|
230
230
|
statusCode: result.statusCode,
|
|
231
231
|
outputArguments: [
|
|
232
|
-
{ dataType: DataType.UInt32, arrayType: VariantArrayType.Array, value:
|
|
233
|
-
{ dataType: DataType.UInt32, arrayType: VariantArrayType.Array, value:
|
|
232
|
+
{ dataType: DataType.UInt32, arrayType: VariantArrayType.Array, value: result.serverHandles },
|
|
233
|
+
{ dataType: DataType.UInt32, arrayType: VariantArrayType.Array, value: result.clientHandles }
|
|
234
234
|
]
|
|
235
235
|
});
|
|
236
236
|
callback(null, callMethodResult);
|
|
@@ -473,8 +473,7 @@ export class ServerEngine extends EventEmitter {
|
|
|
473
473
|
this._orphanPublishEngine.shutdown();
|
|
474
474
|
}
|
|
475
475
|
|
|
476
|
-
|
|
477
|
-
|
|
476
|
+
|
|
478
477
|
for (const token of tokens) {
|
|
479
478
|
this.closeSession(token, true, "Terminated");
|
|
480
479
|
}
|
|
@@ -642,7 +641,7 @@ export class ServerEngine extends EventEmitter {
|
|
|
642
641
|
public setServerState(serverState: ServerState): void {
|
|
643
642
|
assert(serverState !== null && serverState !== undefined);
|
|
644
643
|
this.addressSpace?.rootFolder?.objects?.server?.serverStatus?.state?.setValueFromSource({
|
|
645
|
-
dataType: DataType.
|
|
644
|
+
dataType: DataType.Int32,
|
|
646
645
|
value: serverState
|
|
647
646
|
});
|
|
648
647
|
}
|
|
@@ -817,8 +816,7 @@ export class ServerEngine extends EventEmitter {
|
|
|
817
816
|
|
|
818
817
|
// TimeZoneDataType
|
|
819
818
|
const timeZoneDataType = addressSpace.findDataType(resolveNodeId(DataTypeIds.TimeZoneDataType))!;
|
|
820
|
-
|
|
821
|
-
|
|
819
|
+
|
|
822
820
|
const timeZone = new TimeZoneDataType({
|
|
823
821
|
daylightSavingInOffset: /* boolean*/ false,
|
|
824
822
|
offset: /* int16 */ 0
|
|
@@ -1076,36 +1074,36 @@ export class ServerEngine extends EventEmitter {
|
|
|
1076
1074
|
// mainly for compliance
|
|
1077
1075
|
|
|
1078
1076
|
// The version number for the data type description. i=104
|
|
1079
|
-
bindStandardScalar(VariableIds.DataTypeDescriptionType_DataTypeVersion, DataType.
|
|
1080
|
-
return 0
|
|
1077
|
+
bindStandardScalar(VariableIds.DataTypeDescriptionType_DataTypeVersion, DataType.String, () => {
|
|
1078
|
+
return "0";
|
|
1081
1079
|
});
|
|
1082
1080
|
|
|
1083
1081
|
const namingRuleDataTypeNode = addressSpace.findDataType(resolveNodeId(DataTypeIds.NamingRuleType))! as UADataType;
|
|
1084
|
-
|
|
1082
|
+
|
|
1085
1083
|
if (namingRuleDataTypeNode) {
|
|
1086
1084
|
const namingRuleType = (namingRuleDataTypeNode as any)._getEnumerationInfo().nameIndex; // getEnumeration("NamingRuleType");
|
|
1087
1085
|
if (!namingRuleType) {
|
|
1088
1086
|
throw new Error("Cannot find Enumeration definition for NamingRuleType");
|
|
1089
1087
|
}
|
|
1090
1088
|
// i=111
|
|
1091
|
-
bindStandardScalar(VariableIds.ModellingRuleType_NamingRule, DataType.
|
|
1089
|
+
bindStandardScalar(VariableIds.ModellingRuleType_NamingRule, DataType.Int32, () => {
|
|
1092
1090
|
return 0;
|
|
1093
1091
|
});
|
|
1094
1092
|
|
|
1095
1093
|
// i=112
|
|
1096
|
-
bindStandardScalar(VariableIds.ModellingRule_Mandatory_NamingRule, DataType.
|
|
1094
|
+
bindStandardScalar(VariableIds.ModellingRule_Mandatory_NamingRule, DataType.Int32, () => {
|
|
1097
1095
|
return namingRuleType.Mandatory ? namingRuleType.Mandatory.value : 0;
|
|
1098
1096
|
});
|
|
1099
1097
|
|
|
1100
1098
|
// i=113
|
|
1101
|
-
bindStandardScalar(VariableIds.ModellingRule_Optional_NamingRule, DataType.
|
|
1099
|
+
bindStandardScalar(VariableIds.ModellingRule_Optional_NamingRule, DataType.Int32, () => {
|
|
1102
1100
|
return namingRuleType.Optional ? namingRuleType.Optional.value : 0;
|
|
1103
1101
|
});
|
|
1104
1102
|
// i=114
|
|
1105
|
-
bindStandardScalar(VariableIds.ModellingRule_ExposesItsArray_NamingRule, DataType.
|
|
1103
|
+
bindStandardScalar(VariableIds.ModellingRule_ExposesItsArray_NamingRule, DataType.Int32, () => {
|
|
1106
1104
|
return namingRuleType.ExposesItsArray ? namingRuleType.ExposesItsArray.value : 0;
|
|
1107
1105
|
});
|
|
1108
|
-
bindStandardScalar(VariableIds.ModellingRule_MandatoryPlaceholder_NamingRule, DataType.
|
|
1106
|
+
bindStandardScalar(VariableIds.ModellingRule_MandatoryPlaceholder_NamingRule, DataType.Int32, () => {
|
|
1109
1107
|
return namingRuleType.MandatoryPlaceholder ? namingRuleType.MandatoryPlaceholder.value : 0;
|
|
1110
1108
|
});
|
|
1111
1109
|
}
|
|
@@ -1162,7 +1160,6 @@ export class ServerEngine extends EventEmitter {
|
|
|
1162
1160
|
if (samplingIntervalDiagnosticsArray) {
|
|
1163
1161
|
addressSpace.deleteNode(samplingIntervalDiagnosticsArray);
|
|
1164
1162
|
const s = serverDiagnosticsNode.getComponents();
|
|
1165
|
-
// xx console.log(s.map((x) => x.browseName.toString()).join(" "));
|
|
1166
1163
|
}
|
|
1167
1164
|
|
|
1168
1165
|
const subscriptionDiagnosticsArrayNode = serverDiagnosticsNode.getComponentByName(
|
|
@@ -1233,9 +1230,9 @@ export class ServerEngine extends EventEmitter {
|
|
|
1233
1230
|
node.onFirstBrowseAction = undefined;
|
|
1234
1231
|
} catch (err) {
|
|
1235
1232
|
if (err instanceof Error) {
|
|
1236
|
-
|
|
1233
|
+
errorLog("onFirstBrowseAction method has failed", err.message);
|
|
1237
1234
|
}
|
|
1238
|
-
|
|
1235
|
+
errorLog(err);
|
|
1239
1236
|
}
|
|
1240
1237
|
assert(node.onFirstBrowseAction === undefined, "expansion can only be made once");
|
|
1241
1238
|
}
|
|
@@ -1720,7 +1717,6 @@ export class ServerEngine extends EventEmitter {
|
|
|
1720
1717
|
}
|
|
1721
1718
|
const subscription = session.publishEngine.getSubscriptionById(subscriptionId);
|
|
1722
1719
|
if (subscription) {
|
|
1723
|
-
// xx console.log("foundSubscription ", subscriptionId, " in session", session.sessionName);
|
|
1724
1720
|
subscriptions.push(subscription);
|
|
1725
1721
|
}
|
|
1726
1722
|
});
|
|
@@ -1880,7 +1876,7 @@ export class ServerEngine extends EventEmitter {
|
|
|
1880
1876
|
const referenceTime = new Date(Date.now() - maxAge);
|
|
1881
1877
|
|
|
1882
1878
|
assert(callback instanceof Function);
|
|
1883
|
-
const objectMap:
|
|
1879
|
+
const objectMap: Record<string, BaseNode> = {};
|
|
1884
1880
|
for (const nodeToRefresh of nodesToRefresh) {
|
|
1885
1881
|
// only consider node for which the caller wants to read the Value attribute
|
|
1886
1882
|
// assuming that Value is requested if attributeId is missing,
|
|
@@ -1903,13 +1899,15 @@ export class ServerEngine extends EventEmitter {
|
|
|
1903
1899
|
|
|
1904
1900
|
objectMap[key] = obj;
|
|
1905
1901
|
}
|
|
1906
|
-
|
|
1902
|
+
|
|
1903
|
+
const objectArray = Object.values(objectMap);
|
|
1904
|
+
if (objectArray.length === 0) {
|
|
1907
1905
|
// nothing to do
|
|
1908
1906
|
return callback(null, []);
|
|
1909
1907
|
}
|
|
1910
1908
|
// perform all asyncRefresh in parallel
|
|
1911
1909
|
async.map(
|
|
1912
|
-
|
|
1910
|
+
objectArray,
|
|
1913
1911
|
(obj: BaseNode, inner_callback: DataValueCallback) => {
|
|
1914
1912
|
if (obj.nodeClass !== NodeClass.Variable) {
|
|
1915
1913
|
inner_callback(
|
|
@@ -1920,7 +1918,19 @@ export class ServerEngine extends EventEmitter {
|
|
|
1920
1918
|
);
|
|
1921
1919
|
return;
|
|
1922
1920
|
}
|
|
1923
|
-
|
|
1921
|
+
try {
|
|
1922
|
+
(obj as UAVariable).asyncRefresh(referenceTime, (err, dataValue) => {
|
|
1923
|
+
inner_callback(err, dataValue);
|
|
1924
|
+
});
|
|
1925
|
+
} catch (err) {
|
|
1926
|
+
|
|
1927
|
+
// istanbul ignore next
|
|
1928
|
+
if (!(err instanceof Error)) {
|
|
1929
|
+
throw new Error("internal error");
|
|
1930
|
+
}
|
|
1931
|
+
errorLog("asyncRefresh internal error", err);
|
|
1932
|
+
inner_callback(err);
|
|
1933
|
+
}
|
|
1924
1934
|
},
|
|
1925
1935
|
(err?: Error | null, arrResult?: (DataValue | undefined)[]) => {
|
|
1926
1936
|
callback(err || null, arrResult as DataValue[]);
|
|
@@ -2087,7 +2097,7 @@ export class ServerEngine extends EventEmitter {
|
|
|
2087
2097
|
"HistoryReadDetails " +
|
|
2088
2098
|
historyReadDetails.toString();
|
|
2089
2099
|
if (doDebug) {
|
|
2090
|
-
|
|
2100
|
+
debugLog(chalk.cyan("ServerEngine#_historyReadSingleNode "), chalk.white.bold(msg));
|
|
2091
2101
|
}
|
|
2092
2102
|
const err = new Error(msg);
|
|
2093
2103
|
// object has no historyRead method
|
|
@@ -2132,13 +2142,12 @@ export class ServerEngine extends EventEmitter {
|
|
|
2132
2142
|
if (methodNode && methodNode.bindMethod) {
|
|
2133
2143
|
methodNode.bindMethod(func);
|
|
2134
2144
|
} else {
|
|
2135
|
-
|
|
2145
|
+
warningLog(
|
|
2136
2146
|
chalk.yellow("WARNING: cannot bind a method with id ") +
|
|
2137
2147
|
chalk.cyan(nodeId.toString()) +
|
|
2138
2148
|
chalk.yellow(". please check your nodeset.xml file or add this node programmatically")
|
|
2139
2149
|
);
|
|
2140
|
-
|
|
2141
|
-
console.log(traceFromThisProjectOnly());
|
|
2150
|
+
warningLog(traceFromThisProjectOnly());
|
|
2142
2151
|
}
|
|
2143
2152
|
}
|
|
2144
2153
|
|
package/source/server_session.ts
CHANGED
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
} from "node-opcua-address-space";
|
|
24
24
|
|
|
25
25
|
import { assert } from "node-opcua-assert";
|
|
26
|
-
import { randomGuid } from "node-opcua-basic-types";
|
|
26
|
+
import { minOPCUADate, randomGuid } from "node-opcua-basic-types";
|
|
27
27
|
import { SessionDiagnosticsDataType, SessionSecurityDiagnosticsDataType, SubscriptionDiagnosticsDataType } from "node-opcua-common";
|
|
28
28
|
import { QualifiedName, NodeClass } from "node-opcua-data-model";
|
|
29
29
|
import { checkDebugFlag, make_debugLog } from "node-opcua-debug";
|
|
@@ -34,7 +34,7 @@ import { WatchDog } from "node-opcua-utils";
|
|
|
34
34
|
import { lowerFirstLetter } from "node-opcua-utils";
|
|
35
35
|
import { ISubscriber, IWatchdogData2 } from "node-opcua-utils";
|
|
36
36
|
|
|
37
|
-
import { IServerSession, ServerSecureChannelLayer } from "node-opcua-secure-channel";
|
|
37
|
+
import { IServerSession, IServerSessionBase, ServerSecureChannelLayer } from "node-opcua-secure-channel";
|
|
38
38
|
import { ApplicationDescription, UserIdentityToken, CreateSubscriptionRequestOptions, EndpointDescription } from "node-opcua-types";
|
|
39
39
|
|
|
40
40
|
import { ServerSidePublishEngine } from "./server_publish_engine";
|
|
@@ -93,7 +93,7 @@ interface SessionSecurityDiagnosticsDataTypeEx extends SessionSecurityDiagnostic
|
|
|
93
93
|
* SessionDiagnosticsArray Variable and notifies any other Clients who were subscribed to this entry.
|
|
94
94
|
*
|
|
95
95
|
*/
|
|
96
|
-
export class ServerSession extends EventEmitter implements ISubscriber, ISessionBase, IServerSession {
|
|
96
|
+
export class ServerSession extends EventEmitter implements ISubscriber, ISessionBase, IServerSession, IServerSessionBase {
|
|
97
97
|
public static registry = new ObjectRegistry();
|
|
98
98
|
public static maxPublishRequestInQueue = 100;
|
|
99
99
|
|
|
@@ -196,7 +196,7 @@ export class ServerSession extends EventEmitter implements ISubscriber, ISession
|
|
|
196
196
|
assert(!this.sessionObject, " sessionObject has not been cleared !");
|
|
197
197
|
|
|
198
198
|
this.parent = null as any as ServerEngine;
|
|
199
|
-
this.authenticationToken = NodeId
|
|
199
|
+
this.authenticationToken = new NodeId();
|
|
200
200
|
|
|
201
201
|
if (this.publishEngine) {
|
|
202
202
|
this.publishEngine.dispose();
|
|
@@ -219,8 +219,13 @@ export class ServerSession extends EventEmitter implements ISubscriber, ISession
|
|
|
219
219
|
return this.creationDate;
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
+
/**
|
|
223
|
+
* return the number of milisecond since last session transaction occurs from client
|
|
224
|
+
* the first transaction is the creation of the session
|
|
225
|
+
*/
|
|
222
226
|
public get clientLastContactTime(): number {
|
|
223
|
-
|
|
227
|
+
const lastSeen = this._watchDogData ? this._watchDogData.lastSeen : minOPCUADate.getTime();
|
|
228
|
+
return WatchDog.lastSeenToDuration(lastSeen);
|
|
224
229
|
}
|
|
225
230
|
|
|
226
231
|
public get status(): string {
|
|
@@ -435,6 +440,9 @@ export class ServerSession extends EventEmitter implements ISubscriber, ISession
|
|
|
435
440
|
assert(this.currentSubscriptionCount === 0);
|
|
436
441
|
|
|
437
442
|
this.status = "closed";
|
|
443
|
+
|
|
444
|
+
this._detach_channel();
|
|
445
|
+
|
|
438
446
|
/**
|
|
439
447
|
* @event session_closed
|
|
440
448
|
* @param deleteSubscriptions {Boolean}
|
|
@@ -547,8 +555,12 @@ export class ServerSession extends EventEmitter implements ISubscriber, ISession
|
|
|
547
555
|
|
|
548
556
|
public _detach_channel(): void {
|
|
549
557
|
const channel = this.channel;
|
|
558
|
+
|
|
559
|
+
// istanbul ignore next
|
|
550
560
|
if (!channel) {
|
|
551
|
-
|
|
561
|
+
return;
|
|
562
|
+
// already detached !
|
|
563
|
+
// throw new Error("expecting a valid channel");
|
|
552
564
|
}
|
|
553
565
|
assert(this.nonce && this.nonce instanceof Buffer);
|
|
554
566
|
assert(this.authenticationToken);
|
|
@@ -407,12 +407,12 @@ export interface GetMonitoredItemsResult {
|
|
|
407
407
|
* array of serverHandles for all MonitoredItems of the subscription
|
|
408
408
|
* identified by subscriptionId.
|
|
409
409
|
*/
|
|
410
|
-
serverHandles:
|
|
410
|
+
serverHandles: Uint32Array;
|
|
411
411
|
/**
|
|
412
412
|
* array of clientHandles for all MonitoredItems of the subscription
|
|
413
413
|
* identified by subscriptionId.
|
|
414
414
|
*/
|
|
415
|
-
clientHandles:
|
|
415
|
+
clientHandles: Uint32Array;
|
|
416
416
|
statusCode: StatusCode;
|
|
417
417
|
}
|
|
418
418
|
|
|
@@ -790,7 +790,7 @@ export class Subscription extends EventEmitter {
|
|
|
790
790
|
this._pending_notifications.clear();
|
|
791
791
|
this._sent_notification_messages = [];
|
|
792
792
|
|
|
793
|
-
this.sessionId = NodeId
|
|
793
|
+
this.sessionId = new NodeId();
|
|
794
794
|
|
|
795
795
|
this.$session = undefined;
|
|
796
796
|
this.removeAllListeners();
|
|
@@ -1143,19 +1143,23 @@ export class Subscription extends EventEmitter {
|
|
|
1143
1143
|
*
|
|
1144
1144
|
*/
|
|
1145
1145
|
public getMonitoredItems(): GetMonitoredItemsResult {
|
|
1146
|
+
const monitoredItems = Object.keys(this.monitoredItems);
|
|
1147
|
+
const monitoredItemCount = monitoredItems.length;
|
|
1146
1148
|
const result: GetMonitoredItemsResult = {
|
|
1147
|
-
clientHandles:
|
|
1148
|
-
serverHandles:
|
|
1149
|
+
clientHandles: new Uint32Array(monitoredItemCount),
|
|
1150
|
+
serverHandles: new Uint32Array(monitoredItemCount),
|
|
1149
1151
|
statusCode: StatusCodes.Good
|
|
1150
1152
|
};
|
|
1151
|
-
for (
|
|
1152
|
-
const
|
|
1153
|
-
|
|
1153
|
+
for (let index = 0; index < monitoredItemCount; index++) {
|
|
1154
|
+
const monitoredItemId = monitoredItems[index];
|
|
1155
|
+
const serverHandle = parseInt(monitoredItemId, 10);
|
|
1156
|
+
const monitoredItem = this.getMonitoredItem(serverHandle)!;
|
|
1157
|
+
result.clientHandles[index] = monitoredItem.clientHandle;
|
|
1154
1158
|
// TODO: serverHandle is defined anywhere in the OPCUA Specification 1.02
|
|
1155
1159
|
// I am not sure what shall be reported for serverHandle...
|
|
1156
1160
|
// using monitoredItem.monitoredItemId instead...
|
|
1157
1161
|
// May be a clarification in the OPCUA Spec is required.
|
|
1158
|
-
result.serverHandles
|
|
1162
|
+
result.serverHandles[index] = serverHandle;
|
|
1159
1163
|
}
|
|
1160
1164
|
return result;
|
|
1161
1165
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
require("node-opcua-pki/bin/crypto_create_CA");
|
|
1
|
+
require("node-opcua-pki/bin/crypto_create_CA");
|