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.
- package/.mocharc.yml +10 -0
- package/LICENSE +20 -0
- package/dist/base_server.d.ts +110 -0
- package/dist/base_server.js +476 -0
- package/dist/base_server.js.map +1 -0
- package/dist/factory.d.ts +10 -0
- package/dist/factory.js +24 -0
- package/dist/factory.js.map +1 -0
- package/dist/history_server_capabilities.d.ts +35 -0
- package/dist/history_server_capabilities.js +44 -0
- package/dist/history_server_capabilities.js.map +1 -0
- package/dist/i_channel_data.d.ts +13 -0
- package/dist/i_channel_data.js +3 -0
- package/dist/i_channel_data.js.map +1 -0
- package/dist/i_register_server_manager.d.ts +16 -0
- package/dist/i_register_server_manager.js +3 -0
- package/dist/i_register_server_manager.js.map +1 -0
- package/dist/i_server_side_publish_engine.d.ts +36 -0
- package/dist/i_server_side_publish_engine.js +50 -0
- package/dist/i_server_side_publish_engine.js.map +1 -0
- package/dist/i_socket_data.d.ts +11 -0
- package/dist/i_socket_data.js +3 -0
- package/dist/i_socket_data.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/dist/monitored_item.d.ts +173 -0
- package/dist/monitored_item.js +1006 -0
- package/dist/monitored_item.js.map +1 -0
- package/dist/node_sampler.d.ts +3 -0
- package/dist/node_sampler.js +76 -0
- package/dist/node_sampler.js.map +1 -0
- package/dist/opcua_server.d.ts +668 -0
- package/dist/opcua_server.js +2407 -0
- package/dist/opcua_server.js.map +1 -0
- package/dist/queue.d.ts +11 -0
- package/dist/queue.js +71 -0
- package/dist/queue.js.map +1 -0
- package/dist/register_server_manager.d.ts +92 -0
- package/dist/register_server_manager.js +574 -0
- package/dist/register_server_manager.js.map +1 -0
- package/dist/register_server_manager_hidden.d.ts +17 -0
- package/dist/register_server_manager_hidden.js +28 -0
- package/dist/register_server_manager_hidden.js.map +1 -0
- package/dist/register_server_manager_mdns_only.d.ts +19 -0
- package/dist/register_server_manager_mdns_only.js +58 -0
- package/dist/register_server_manager_mdns_only.js.map +1 -0
- package/dist/server_capabilities.d.ts +61 -0
- package/dist/server_capabilities.js +109 -0
- package/dist/server_capabilities.js.map +1 -0
- package/dist/server_end_point.d.ts +180 -0
- package/dist/server_end_point.js +825 -0
- package/dist/server_end_point.js.map +1 -0
- package/dist/server_engine.d.ts +311 -0
- package/dist/server_engine.js +1659 -0
- package/dist/server_engine.js.map +1 -0
- package/dist/server_publish_engine.d.ts +109 -0
- package/dist/server_publish_engine.js +531 -0
- package/dist/server_publish_engine.js.map +1 -0
- package/dist/server_publish_engine_for_orphan_subscriptions.d.ts +16 -0
- package/dist/server_publish_engine_for_orphan_subscriptions.js +50 -0
- package/dist/server_publish_engine_for_orphan_subscriptions.js.map +1 -0
- package/dist/server_session.d.ts +176 -0
- package/dist/server_session.js +734 -0
- package/dist/server_session.js.map +1 -0
- package/dist/server_subscription.d.ts +393 -0
- package/dist/server_subscription.js +1313 -0
- package/dist/server_subscription.js.map +1 -0
- package/dist/sessions_compatible_for_transfer.d.ts +2 -0
- package/dist/sessions_compatible_for_transfer.js +36 -0
- package/dist/sessions_compatible_for_transfer.js.map +1 -0
- package/dist/validate_filter.d.ts +5 -0
- package/dist/validate_filter.js +64 -0
- package/dist/validate_filter.js.map +1 -0
- package/package.json +88 -0
- package/source/base_server.ts +617 -0
- package/source/factory.ts +25 -0
- package/source/history_server_capabilities.ts +75 -0
- package/source/i_channel_data.ts +17 -0
- package/source/i_register_server_manager.ts +24 -0
- package/source/i_server_side_publish_engine.ts +77 -0
- package/source/i_socket_data.ts +11 -0
- package/source/index.ts +14 -0
- package/source/monitored_item.ts +1303 -0
- package/source/node_sampler.ts +82 -0
- package/source/opcua_server.ts +3742 -0
- package/source/queue.ts +73 -0
- package/source/register_server_manager.ts +744 -0
- package/source/register_server_manager_hidden.ts +33 -0
- package/source/register_server_manager_mdns_only.ts +69 -0
- package/source/server_capabilities.ts +177 -0
- package/source/server_end_point.ts +1182 -0
- package/source/server_engine.ts +2167 -0
- package/source/server_publish_engine.ts +657 -0
- package/source/server_publish_engine_for_orphan_subscriptions.ts +52 -0
- package/source/server_session.ts +931 -0
- package/source/server_subscription.ts +1792 -0
- package/source/sessions_compatible_for_transfer.ts +33 -0
- package/source/validate_filter.ts +86 -0
- package/test_helpers/create_certificates.js +1 -0
|
@@ -0,0 +1,931 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module node-opcua-server
|
|
3
|
+
*/
|
|
4
|
+
// tslint:disable:no-console
|
|
5
|
+
|
|
6
|
+
import * as crypto from "crypto";
|
|
7
|
+
import { EventEmitter } from "events";
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
addElement,
|
|
11
|
+
AddressSpace,
|
|
12
|
+
ContinuationPointManager,
|
|
13
|
+
createExtObjArrayNode,
|
|
14
|
+
ensureObjectIsSecure,
|
|
15
|
+
ISessionBase,
|
|
16
|
+
removeElement,
|
|
17
|
+
UADynamicVariableArray,
|
|
18
|
+
UAObject,
|
|
19
|
+
UASessionDiagnosticsVariable,
|
|
20
|
+
UASessionSecurityDiagnostics,
|
|
21
|
+
DTSessionDiagnostics,
|
|
22
|
+
DTSessionSecurityDiagnostics
|
|
23
|
+
} from "node-opcua-address-space";
|
|
24
|
+
|
|
25
|
+
import { assert } from "node-opcua-assert";
|
|
26
|
+
import { randomGuid } from "node-opcua-basic-types";
|
|
27
|
+
import { SessionDiagnosticsDataType, SessionSecurityDiagnosticsDataType, SubscriptionDiagnosticsDataType } from "node-opcua-common";
|
|
28
|
+
import { QualifiedName, NodeClass } from "node-opcua-data-model";
|
|
29
|
+
import { checkDebugFlag, make_debugLog } from "node-opcua-debug";
|
|
30
|
+
import { makeNodeId, NodeId, NodeIdType, sameNodeId } from "node-opcua-nodeid";
|
|
31
|
+
import { ObjectRegistry } from "node-opcua-object-registry";
|
|
32
|
+
import { StatusCode, StatusCodes } from "node-opcua-status-code";
|
|
33
|
+
import { WatchDog } from "node-opcua-utils";
|
|
34
|
+
import { lowerFirstLetter } from "node-opcua-utils";
|
|
35
|
+
import { ISubscriber, IWatchdogData2 } from "node-opcua-utils";
|
|
36
|
+
|
|
37
|
+
import { IServerSession, ServerSecureChannelLayer } from "node-opcua-secure-channel";
|
|
38
|
+
import { ApplicationDescription, UserIdentityToken, CreateSubscriptionRequestOptions, EndpointDescription } from "node-opcua-types";
|
|
39
|
+
|
|
40
|
+
import { ServerSidePublishEngine } from "./server_publish_engine";
|
|
41
|
+
import { Subscription } from "./server_subscription";
|
|
42
|
+
import { SubscriptionState } from "./server_subscription";
|
|
43
|
+
import { ServerEngine } from "./server_engine";
|
|
44
|
+
|
|
45
|
+
const debugLog = make_debugLog(__filename);
|
|
46
|
+
const doDebug = checkDebugFlag(__filename);
|
|
47
|
+
const theWatchDog = new WatchDog();
|
|
48
|
+
|
|
49
|
+
const registeredNodeNameSpace = 9999;
|
|
50
|
+
|
|
51
|
+
function compareSessionId(
|
|
52
|
+
sessionDiagnostics1: SessionDiagnosticsDataType | SessionSecurityDiagnosticsDataType,
|
|
53
|
+
sessionDiagnostics2: SessionDiagnosticsDataType | SessionSecurityDiagnosticsDataType
|
|
54
|
+
) {
|
|
55
|
+
return sessionDiagnostics1.sessionId.toString() === sessionDiagnostics2.sessionId.toString();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function on_channel_abort(this: ServerSession) {
|
|
59
|
+
debugLog("ON CHANNEL ABORT ON SESSION!!!");
|
|
60
|
+
/**
|
|
61
|
+
* @event channel_aborted
|
|
62
|
+
*/
|
|
63
|
+
this.emit("channel_aborted");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface SessionDiagnosticsDataTypeEx extends SessionDiagnosticsDataType {
|
|
67
|
+
$session: any;
|
|
68
|
+
}
|
|
69
|
+
interface SessionSecurityDiagnosticsDataTypeEx extends SessionSecurityDiagnosticsDataType {
|
|
70
|
+
$session: any;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
*
|
|
75
|
+
* A Server session object.
|
|
76
|
+
*
|
|
77
|
+
* **from OPCUA Spec 1.02:**
|
|
78
|
+
*
|
|
79
|
+
* * Sessions are created to be independent of the underlying communications connection. Therefore, if a communication
|
|
80
|
+
* connection fails, the Session is not immediately affected. The exact mechanism to recover from an underlying
|
|
81
|
+
* communication connection error depends on the SecureChannel mapping as described in Part 6.
|
|
82
|
+
*
|
|
83
|
+
* * Sessions are terminated by the Server automatically if the Client fails to issue a Service request on the Session
|
|
84
|
+
* within the timeout period negotiated by the Server in the CreateSession Service response. This protects the Server
|
|
85
|
+
* against Client failures and against situations where a failed underlying connection cannot be re-established.
|
|
86
|
+
*
|
|
87
|
+
* * Clients shall be prepared to submit requests in a timely manner to prevent the Session from closing automatically.
|
|
88
|
+
*
|
|
89
|
+
* * Clients may explicitly terminate Sessions using the CloseSession Service.
|
|
90
|
+
*
|
|
91
|
+
* * When a Session is terminated, all outstanding requests on the Session are aborted and BadSessionClosed StatusCodes
|
|
92
|
+
* are returned to the Client. In addition, the Server deletes the entry for the Client from its
|
|
93
|
+
* SessionDiagnosticsArray Variable and notifies any other Clients who were subscribed to this entry.
|
|
94
|
+
*
|
|
95
|
+
*/
|
|
96
|
+
export class ServerSession extends EventEmitter implements ISubscriber, ISessionBase, IServerSession {
|
|
97
|
+
public static registry = new ObjectRegistry();
|
|
98
|
+
public static maxPublishRequestInQueue: number = 100;
|
|
99
|
+
|
|
100
|
+
public __status: string = "";
|
|
101
|
+
public parent: ServerEngine;
|
|
102
|
+
public authenticationToken: NodeId;
|
|
103
|
+
public nodeId: NodeId;
|
|
104
|
+
public sessionName: string = "";
|
|
105
|
+
|
|
106
|
+
public publishEngine: ServerSidePublishEngine;
|
|
107
|
+
public sessionObject: any;
|
|
108
|
+
public readonly creationDate: Date;
|
|
109
|
+
public sessionTimeout: number;
|
|
110
|
+
public sessionDiagnostics?: UASessionDiagnosticsVariable<DTSessionDiagnostics>;
|
|
111
|
+
public sessionSecurityDiagnostics?: UASessionSecurityDiagnostics<DTSessionSecurityDiagnostics>;
|
|
112
|
+
public subscriptionDiagnosticsArray?: UADynamicVariableArray<SubscriptionDiagnosticsDataType>;
|
|
113
|
+
public channel?: ServerSecureChannelLayer;
|
|
114
|
+
public nonce?: Buffer;
|
|
115
|
+
public userIdentityToken?: UserIdentityToken;
|
|
116
|
+
public clientDescription?: ApplicationDescription;
|
|
117
|
+
public channelId?: number | null;
|
|
118
|
+
public continuationPointManager: ContinuationPointManager;
|
|
119
|
+
|
|
120
|
+
// ISubscriber
|
|
121
|
+
public _watchDog?: WatchDog;
|
|
122
|
+
public _watchDogData?: IWatchdogData2;
|
|
123
|
+
keepAlive: () => void = WatchDog.emptyKeepAlive;
|
|
124
|
+
|
|
125
|
+
private _registeredNodesCounter: number;
|
|
126
|
+
private _registeredNodes: any;
|
|
127
|
+
private _registeredNodesInv: any;
|
|
128
|
+
private _cumulatedSubscriptionCount: number;
|
|
129
|
+
private _sessionDiagnostics?: SessionDiagnosticsDataTypeEx;
|
|
130
|
+
private _sessionSecurityDiagnostics?: SessionSecurityDiagnosticsDataTypeEx;
|
|
131
|
+
|
|
132
|
+
private channel_abort_event_handler: any;
|
|
133
|
+
|
|
134
|
+
constructor(parent: ServerEngine, sessionTimeout: number) {
|
|
135
|
+
super();
|
|
136
|
+
|
|
137
|
+
this.parent = parent; // SessionEngine
|
|
138
|
+
|
|
139
|
+
ServerSession.registry.register(this);
|
|
140
|
+
|
|
141
|
+
assert(isFinite(sessionTimeout));
|
|
142
|
+
assert(sessionTimeout >= 0, " sessionTimeout");
|
|
143
|
+
this.sessionTimeout = sessionTimeout;
|
|
144
|
+
|
|
145
|
+
const authenticationTokenBuf = crypto.randomBytes(16);
|
|
146
|
+
this.authenticationToken = new NodeId(NodeIdType.BYTESTRING, authenticationTokenBuf);
|
|
147
|
+
|
|
148
|
+
// the sessionId
|
|
149
|
+
const ownNamespaceIndex = 1; // addressSpace.getOwnNamespace().index;
|
|
150
|
+
this.nodeId = new NodeId(NodeIdType.GUID, randomGuid(), ownNamespaceIndex);
|
|
151
|
+
|
|
152
|
+
assert(this.authenticationToken instanceof NodeId);
|
|
153
|
+
assert(this.nodeId instanceof NodeId);
|
|
154
|
+
|
|
155
|
+
this._cumulatedSubscriptionCount = 0;
|
|
156
|
+
|
|
157
|
+
this.publishEngine = new ServerSidePublishEngine({
|
|
158
|
+
maxPublishRequestInQueue: ServerSession.maxPublishRequestInQueue
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
this.publishEngine.setMaxListeners(100);
|
|
162
|
+
|
|
163
|
+
theWatchDog.addSubscriber(this, this.sessionTimeout);
|
|
164
|
+
|
|
165
|
+
this.__status = "new";
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* the continuation point manager for this session
|
|
169
|
+
* @property continuationPointManager
|
|
170
|
+
* @type {ContinuationPointManager}
|
|
171
|
+
*/
|
|
172
|
+
this.continuationPointManager = new ContinuationPointManager();
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* @property creationDate
|
|
176
|
+
* @type {Date}
|
|
177
|
+
*/
|
|
178
|
+
this.creationDate = new Date();
|
|
179
|
+
|
|
180
|
+
this._registeredNodesCounter = 0;
|
|
181
|
+
this._registeredNodes = {};
|
|
182
|
+
this._registeredNodesInv = {};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
public getSessionId(): NodeId {
|
|
186
|
+
return this.nodeId;
|
|
187
|
+
}
|
|
188
|
+
public endpoint?: EndpointDescription;
|
|
189
|
+
public getEndpointDescription(): EndpointDescription {
|
|
190
|
+
return this.endpoint!;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
public dispose() {
|
|
194
|
+
debugLog("ServerSession#dispose()");
|
|
195
|
+
|
|
196
|
+
assert(!this.sessionObject, " sessionObject has not been cleared !");
|
|
197
|
+
|
|
198
|
+
this.parent = (null as any) as ServerEngine;
|
|
199
|
+
this.authenticationToken = NodeId.nullNodeId;
|
|
200
|
+
|
|
201
|
+
if (this.publishEngine) {
|
|
202
|
+
this.publishEngine.dispose();
|
|
203
|
+
(this as any).publishEngine = null;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
this._sessionDiagnostics = undefined;
|
|
207
|
+
|
|
208
|
+
this._registeredNodesCounter = 0;
|
|
209
|
+
this._registeredNodes = null;
|
|
210
|
+
this._registeredNodesInv = null;
|
|
211
|
+
(this as any).continuationPointManager = null;
|
|
212
|
+
this.removeAllListeners();
|
|
213
|
+
this.__status = "disposed";
|
|
214
|
+
|
|
215
|
+
ServerSession.registry.unregister(this);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
public get clientConnectionTime() {
|
|
219
|
+
return this.creationDate;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
public get clientLastContactTime() {
|
|
223
|
+
return this._watchDogData!.lastSeen;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
public get status(): string {
|
|
227
|
+
return this.__status;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
public set status(value: string) {
|
|
231
|
+
if (value === "active") {
|
|
232
|
+
this._createSessionObjectInAddressSpace();
|
|
233
|
+
}
|
|
234
|
+
this.__status = value;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
get addressSpace(): AddressSpace | null {
|
|
238
|
+
if (this.parent && this.parent.addressSpace) {
|
|
239
|
+
return this.parent.addressSpace!;
|
|
240
|
+
}
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
get currentPublishRequestInQueue(): number {
|
|
245
|
+
return this.publishEngine ? this.publishEngine.pendingPublishRequestCount : 0;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
public updateClientLastContactTime() {
|
|
249
|
+
const session = this;
|
|
250
|
+
if (session._sessionDiagnostics && session._sessionDiagnostics.clientLastContactTime) {
|
|
251
|
+
const currentTime = new Date();
|
|
252
|
+
// do not record all ticks as this may be overwhelming,
|
|
253
|
+
if (currentTime.getTime() - 250 >= session._sessionDiagnostics.clientLastContactTime.getTime()) {
|
|
254
|
+
session._sessionDiagnostics.clientLastContactTime = currentTime;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* @method onClientSeen
|
|
261
|
+
* required for watch dog
|
|
262
|
+
* @param currentTime {DateTime}
|
|
263
|
+
* @private
|
|
264
|
+
*/
|
|
265
|
+
public onClientSeen() {
|
|
266
|
+
this.updateClientLastContactTime();
|
|
267
|
+
|
|
268
|
+
if (this._sessionDiagnostics) {
|
|
269
|
+
// see https://opcfoundation-onlineapplications.org/mantis/view.php?id=4111
|
|
270
|
+
assert(this._sessionDiagnostics.hasOwnProperty("currentMonitoredItemsCount"));
|
|
271
|
+
assert(this._sessionDiagnostics.hasOwnProperty("currentSubscriptionsCount"));
|
|
272
|
+
assert(this._sessionDiagnostics.hasOwnProperty("currentPublishRequestsInQueue"));
|
|
273
|
+
|
|
274
|
+
// note : https://opcfoundation-onlineapplications.org/mantis/view.php?id=4111
|
|
275
|
+
// sessionDiagnostics extension object uses a different spelling
|
|
276
|
+
// here with an S !!!!
|
|
277
|
+
if (this._sessionDiagnostics.currentMonitoredItemsCount !== this.currentMonitoredItemCount) {
|
|
278
|
+
this._sessionDiagnostics.currentMonitoredItemsCount = this.currentMonitoredItemCount;
|
|
279
|
+
}
|
|
280
|
+
if (this._sessionDiagnostics.currentSubscriptionsCount !== this.currentSubscriptionCount) {
|
|
281
|
+
this._sessionDiagnostics.currentSubscriptionsCount = this.currentSubscriptionCount;
|
|
282
|
+
}
|
|
283
|
+
if (this._sessionDiagnostics.currentPublishRequestsInQueue !== this.currentPublishRequestInQueue) {
|
|
284
|
+
this._sessionDiagnostics.currentPublishRequestsInQueue = this.currentPublishRequestInQueue;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
public incrementTotalRequestCount() {
|
|
290
|
+
if (this._sessionDiagnostics && this._sessionDiagnostics.totalRequestCount) {
|
|
291
|
+
this._sessionDiagnostics.totalRequestCount.totalCount += 1;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
public incrementRequestTotalCounter(counterName: string) {
|
|
296
|
+
if (this._sessionDiagnostics) {
|
|
297
|
+
const propName = lowerFirstLetter(counterName + "Count");
|
|
298
|
+
if (!this._sessionDiagnostics.hasOwnProperty(propName)) {
|
|
299
|
+
console.log(" cannot find", propName);
|
|
300
|
+
// xx return;
|
|
301
|
+
} else {
|
|
302
|
+
// console.log(self._sessionDiagnostics.toString());
|
|
303
|
+
(this._sessionDiagnostics as any)[propName].totalCount += 1;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
public incrementRequestErrorCounter(counterName: string) {
|
|
309
|
+
this.parent?.incrementRejectedRequestsCount();
|
|
310
|
+
if (this._sessionDiagnostics) {
|
|
311
|
+
const propName = lowerFirstLetter(counterName + "Count");
|
|
312
|
+
if (!this._sessionDiagnostics.hasOwnProperty(propName)) {
|
|
313
|
+
console.log(" cannot find", propName);
|
|
314
|
+
// xx return;
|
|
315
|
+
} else {
|
|
316
|
+
(this._sessionDiagnostics as any)[propName].errorCount += 1;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* returns rootFolder.objects.server.serverDiagnostics.sessionsDiagnosticsSummary.sessionDiagnosticsArray
|
|
323
|
+
*/
|
|
324
|
+
public getSessionDiagnosticsArray(): UADynamicVariableArray<SessionDiagnosticsDataType> {
|
|
325
|
+
const server = this.addressSpace!.rootFolder.objects.server;
|
|
326
|
+
return server.serverDiagnostics.sessionsDiagnosticsSummary.sessionDiagnosticsArray as any;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* returns rootFolder.objects.server.serverDiagnostics.sessionsDiagnosticsSummary.sessionSecurityDiagnosticsArray
|
|
331
|
+
*/
|
|
332
|
+
public getSessionSecurityDiagnosticsArray(): UADynamicVariableArray<SessionSecurityDiagnosticsDataType> {
|
|
333
|
+
const server = this.addressSpace!.rootFolder.objects.server;
|
|
334
|
+
return server.serverDiagnostics.sessionsDiagnosticsSummary.sessionSecurityDiagnosticsArray as any;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* number of active subscriptions
|
|
339
|
+
*/
|
|
340
|
+
public get currentSubscriptionCount(): number {
|
|
341
|
+
return this.publishEngine ? this.publishEngine.subscriptionCount : 0;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* number of subscriptions ever created since this object is live
|
|
346
|
+
*/
|
|
347
|
+
public get cumulatedSubscriptionCount(): number {
|
|
348
|
+
return this._cumulatedSubscriptionCount;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* number of monitored items
|
|
353
|
+
*/
|
|
354
|
+
public get currentMonitoredItemCount(): number {
|
|
355
|
+
const self = this;
|
|
356
|
+
return self.publishEngine ? self.publishEngine.currentMonitoredItemCount : 0;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* retrieve an existing subscription by subscriptionId
|
|
361
|
+
* @method getSubscription
|
|
362
|
+
* @param subscriptionId {Number}
|
|
363
|
+
* @return {Subscription}
|
|
364
|
+
*/
|
|
365
|
+
public getSubscription(subscriptionId: number): Subscription | null {
|
|
366
|
+
const subscription = this.publishEngine.getSubscriptionById(subscriptionId);
|
|
367
|
+
if (subscription && subscription.state === SubscriptionState.CLOSED) {
|
|
368
|
+
// subscription is CLOSED but has not been notified yet
|
|
369
|
+
// it should be considered as excluded
|
|
370
|
+
return null;
|
|
371
|
+
}
|
|
372
|
+
assert(
|
|
373
|
+
!subscription || subscription.state !== SubscriptionState.CLOSED,
|
|
374
|
+
"CLOSED subscription shall not be managed by publish engine anymore"
|
|
375
|
+
);
|
|
376
|
+
return subscription;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* @method deleteSubscription
|
|
381
|
+
* @param subscriptionId {Number}
|
|
382
|
+
* @return {StatusCode}
|
|
383
|
+
*/
|
|
384
|
+
public deleteSubscription(subscriptionId: number): StatusCode {
|
|
385
|
+
const session = this;
|
|
386
|
+
const subscription = session.getSubscription(subscriptionId);
|
|
387
|
+
if (!subscription) {
|
|
388
|
+
return StatusCodes.BadSubscriptionIdInvalid;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// xx this.publishEngine.remove_subscription(subscription);
|
|
392
|
+
subscription.terminate();
|
|
393
|
+
|
|
394
|
+
if (session.currentSubscriptionCount === 0) {
|
|
395
|
+
const local_publishEngine = session.publishEngine;
|
|
396
|
+
local_publishEngine.cancelPendingPublishRequest();
|
|
397
|
+
}
|
|
398
|
+
return StatusCodes.Good;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* close a ServerSession, this will also delete the subscriptions if the flag is set.
|
|
403
|
+
*
|
|
404
|
+
* Spec extract:
|
|
405
|
+
*
|
|
406
|
+
* If a Client invokes the CloseSession Service then all Subscriptions associated with the Session are also deleted
|
|
407
|
+
* if the deleteSubscriptions flag is set to TRUE. If a Server terminates a Session for any other reason,
|
|
408
|
+
* Subscriptions associated with the Session, are not deleted. Each Subscription has its own lifetime to protect
|
|
409
|
+
* against data loss in the case of a Session termination. In these cases, the Subscription can be reassigned to
|
|
410
|
+
* another Client before its lifetime expires.
|
|
411
|
+
*
|
|
412
|
+
* @method close
|
|
413
|
+
* @param deleteSubscriptions : should we delete subscription ?
|
|
414
|
+
* @param [reason = "CloseSession"] the reason for closing the session
|
|
415
|
+
* (shall be "Timeout", "Terminated" or "CloseSession")
|
|
416
|
+
*
|
|
417
|
+
*/
|
|
418
|
+
public close(deleteSubscriptions: boolean, reason: string): void {
|
|
419
|
+
debugLog(" closing session deleteSubscriptions = ", deleteSubscriptions);
|
|
420
|
+
if (this.publishEngine) {
|
|
421
|
+
this.publishEngine.onSessionClose();
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
theWatchDog.removeSubscriber(this);
|
|
425
|
+
// --------------- delete associated subscriptions ---------------------
|
|
426
|
+
|
|
427
|
+
if (!deleteSubscriptions && this.currentSubscriptionCount !== 0) {
|
|
428
|
+
// I don't know what to do yet if deleteSubscriptions is false
|
|
429
|
+
console.log("TO DO : Closing session without deleting subscription not yet implemented");
|
|
430
|
+
// to do: Put subscriptions in safe place for future transfer if any
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
this._deleteSubscriptions();
|
|
434
|
+
|
|
435
|
+
assert(this.currentSubscriptionCount === 0);
|
|
436
|
+
|
|
437
|
+
// Post-Conditions
|
|
438
|
+
assert(this.currentSubscriptionCount === 0);
|
|
439
|
+
|
|
440
|
+
this.status = "closed";
|
|
441
|
+
/**
|
|
442
|
+
* @event session_closed
|
|
443
|
+
* @param deleteSubscriptions {Boolean}
|
|
444
|
+
* @param reason {String}
|
|
445
|
+
*/
|
|
446
|
+
this.emit("session_closed", this, deleteSubscriptions, reason);
|
|
447
|
+
|
|
448
|
+
// ---------------- shut down publish engine
|
|
449
|
+
if (this.publishEngine) {
|
|
450
|
+
// remove subscription
|
|
451
|
+
this.publishEngine.shutdown();
|
|
452
|
+
|
|
453
|
+
assert(this.publishEngine.subscriptionCount === 0);
|
|
454
|
+
this.publishEngine.dispose();
|
|
455
|
+
this.publishEngine = (null as any) as ServerSidePublishEngine;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
this._removeSessionObjectFromAddressSpace();
|
|
459
|
+
|
|
460
|
+
assert(!this.sessionDiagnostics, "ServerSession#_removeSessionObjectFromAddressSpace must be called");
|
|
461
|
+
assert(!this.sessionObject, "ServerSession#_removeSessionObjectFromAddressSpace must be called");
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
public registerNode(nodeId: NodeId) {
|
|
465
|
+
assert(nodeId instanceof NodeId);
|
|
466
|
+
const session = this;
|
|
467
|
+
|
|
468
|
+
if (nodeId.namespace === 0 && nodeId.identifierType === NodeIdType.NUMERIC) {
|
|
469
|
+
return nodeId;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const key = nodeId.toString();
|
|
473
|
+
|
|
474
|
+
const registeredNode = session._registeredNodes[key];
|
|
475
|
+
if (registeredNode) {
|
|
476
|
+
// already registered
|
|
477
|
+
return registeredNode;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
const node = session.addressSpace!.findNode(nodeId);
|
|
481
|
+
if (!node) {
|
|
482
|
+
return nodeId;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
session._registeredNodesCounter += 1;
|
|
486
|
+
|
|
487
|
+
const aliasNodeId = makeNodeId(session._registeredNodesCounter, registeredNodeNameSpace);
|
|
488
|
+
session._registeredNodes[key] = aliasNodeId;
|
|
489
|
+
session._registeredNodesInv[aliasNodeId.toString()] = node;
|
|
490
|
+
return aliasNodeId;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
public unRegisterNode(aliasNodeId: NodeId): void {
|
|
494
|
+
assert(aliasNodeId instanceof NodeId);
|
|
495
|
+
if (aliasNodeId.namespace !== registeredNodeNameSpace) {
|
|
496
|
+
return; // not a registered Node
|
|
497
|
+
}
|
|
498
|
+
const session = this;
|
|
499
|
+
|
|
500
|
+
const node = session._registeredNodesInv[aliasNodeId.toString()];
|
|
501
|
+
if (!node) {
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
session._registeredNodesInv[aliasNodeId.toString()] = null;
|
|
505
|
+
session._registeredNodes[node.nodeId.toString()] = null;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
public resolveRegisteredNode(aliasNodeId: NodeId): NodeId {
|
|
509
|
+
if (aliasNodeId.namespace !== registeredNodeNameSpace) {
|
|
510
|
+
return aliasNodeId; // not a registered Node
|
|
511
|
+
}
|
|
512
|
+
const node = this._registeredNodesInv[aliasNodeId.toString()];
|
|
513
|
+
if (!node) {
|
|
514
|
+
return aliasNodeId;
|
|
515
|
+
}
|
|
516
|
+
return node.nodeId;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* true if the underlying channel has been closed or aborted...
|
|
521
|
+
*/
|
|
522
|
+
public get aborted() {
|
|
523
|
+
if (!this.channel) {
|
|
524
|
+
return true;
|
|
525
|
+
}
|
|
526
|
+
return this.channel.aborted;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
public createSubscription(parameters: CreateSubscriptionRequestOptions): Subscription {
|
|
530
|
+
const subscription = this.parent._createSubscriptionOnSession(this, parameters);
|
|
531
|
+
assert(!parameters.hasOwnProperty("id"));
|
|
532
|
+
this.assignSubscription(subscription);
|
|
533
|
+
assert(subscription.$session === this);
|
|
534
|
+
assert(subscription.sessionId instanceof NodeId);
|
|
535
|
+
assert(sameNodeId(subscription.sessionId, this.nodeId));
|
|
536
|
+
return subscription;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
public _attach_channel(channel: ServerSecureChannelLayer) {
|
|
540
|
+
assert(this.nonce && this.nonce instanceof Buffer);
|
|
541
|
+
this.channel = channel;
|
|
542
|
+
this.channelId = channel.channelId;
|
|
543
|
+
const key = this.authenticationToken.toString();
|
|
544
|
+
assert(!channel.sessionTokens.hasOwnProperty(key), "channel has already a session");
|
|
545
|
+
|
|
546
|
+
channel.sessionTokens[key] = this;
|
|
547
|
+
|
|
548
|
+
// when channel is aborting
|
|
549
|
+
this.channel_abort_event_handler = on_channel_abort.bind(this);
|
|
550
|
+
channel.on("abort", this.channel_abort_event_handler);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
public _detach_channel() {
|
|
554
|
+
const channel = this.channel;
|
|
555
|
+
if (!channel) {
|
|
556
|
+
throw new Error("expecting a valid channel");
|
|
557
|
+
}
|
|
558
|
+
assert(this.nonce && this.nonce instanceof Buffer);
|
|
559
|
+
assert(this.authenticationToken);
|
|
560
|
+
const key = this.authenticationToken.toString();
|
|
561
|
+
assert(channel.sessionTokens.hasOwnProperty(key));
|
|
562
|
+
assert(this.channel);
|
|
563
|
+
assert(typeof this.channel_abort_event_handler === "function");
|
|
564
|
+
channel.removeListener("abort", this.channel_abort_event_handler);
|
|
565
|
+
|
|
566
|
+
delete channel.sessionTokens[key];
|
|
567
|
+
this.channel = undefined;
|
|
568
|
+
this.channelId = undefined;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
public _exposeSubscriptionDiagnostics(subscription: Subscription) {
|
|
572
|
+
debugLog("ServerSession#_exposeSubscriptionDiagnostics");
|
|
573
|
+
assert(subscription.$session === this);
|
|
574
|
+
const subscriptionDiagnosticsArray = this._getSubscriptionDiagnosticsArray();
|
|
575
|
+
const subscriptionDiagnostics = subscription.subscriptionDiagnostics;
|
|
576
|
+
assert(subscriptionDiagnostics.$subscription === subscription);
|
|
577
|
+
|
|
578
|
+
if (subscriptionDiagnostics && subscriptionDiagnosticsArray) {
|
|
579
|
+
// subscription.id,"on session", session.nodeId.toString());
|
|
580
|
+
addElement(subscriptionDiagnostics, subscriptionDiagnosticsArray);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
public _unexposeSubscriptionDiagnostics(subscription: Subscription) {
|
|
585
|
+
const subscriptionDiagnosticsArray = this._getSubscriptionDiagnosticsArray();
|
|
586
|
+
const subscriptionDiagnostics = subscription.subscriptionDiagnostics;
|
|
587
|
+
assert(subscriptionDiagnostics instanceof SubscriptionDiagnosticsDataType);
|
|
588
|
+
if (subscriptionDiagnostics && subscriptionDiagnosticsArray) {
|
|
589
|
+
// console.log("GG => ServerSession **Unexposing** subscription diagnostics =>",
|
|
590
|
+
// subscription.id,"on session", session.nodeId.toString());
|
|
591
|
+
removeElement(subscriptionDiagnosticsArray, subscriptionDiagnostics);
|
|
592
|
+
}
|
|
593
|
+
debugLog("ServerSession#_unexposeSubscriptionDiagnostics");
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* @method watchdogReset
|
|
597
|
+
* used as a callback for the Watchdog
|
|
598
|
+
* @private
|
|
599
|
+
*/
|
|
600
|
+
public watchdogReset() {
|
|
601
|
+
debugLog("Session#watchdogReset: the server session has expired and must be removed from the server");
|
|
602
|
+
// the server session has expired and must be removed from the server
|
|
603
|
+
this.emit("timeout");
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
private _createSessionObjectInAddressSpace() {
|
|
607
|
+
if (this.sessionObject) {
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
assert(!this.sessionObject, "ServerSession#_createSessionObjectInAddressSpace already called ?");
|
|
611
|
+
|
|
612
|
+
this.sessionObject = null;
|
|
613
|
+
if (!this.addressSpace) {
|
|
614
|
+
debugLog("ServerSession#_createSessionObjectInAddressSpace : no addressSpace");
|
|
615
|
+
return; // no addressSpace
|
|
616
|
+
}
|
|
617
|
+
const root = this.addressSpace.rootFolder;
|
|
618
|
+
assert(root, "expecting a root object");
|
|
619
|
+
|
|
620
|
+
if (!root.objects) {
|
|
621
|
+
debugLog("ServerSession#_createSessionObjectInAddressSpace : no object folder");
|
|
622
|
+
return false;
|
|
623
|
+
}
|
|
624
|
+
if (!root.objects.server) {
|
|
625
|
+
debugLog("ServerSession#_createSessionObjectInAddressSpace : no server object");
|
|
626
|
+
return false;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// self.addressSpace.findNode(makeNodeId(ObjectIds.Server_ServerDiagnostics));
|
|
630
|
+
const serverDiagnosticsNode = root.objects.server.serverDiagnostics;
|
|
631
|
+
|
|
632
|
+
if (!serverDiagnosticsNode || !serverDiagnosticsNode.sessionsDiagnosticsSummary) {
|
|
633
|
+
debugLog("ServerSession#_createSessionObjectInAddressSpace :" + " no serverDiagnostics.sessionsDiagnosticsSummary");
|
|
634
|
+
return false;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
const sessionDiagnosticsObjectType = this.addressSpace.findObjectType("SessionDiagnosticsObjectType");
|
|
638
|
+
|
|
639
|
+
const sessionDiagnosticsDataType = this.addressSpace.findDataType("SessionDiagnosticsDataType");
|
|
640
|
+
const sessionDiagnosticsVariableType = this.addressSpace.findVariableType("SessionDiagnosticsVariableType");
|
|
641
|
+
|
|
642
|
+
const sessionSecurityDiagnosticsDataType = this.addressSpace.findDataType("SessionSecurityDiagnosticsDataType");
|
|
643
|
+
const sessionSecurityDiagnosticsType = this.addressSpace.findVariableType("SessionSecurityDiagnosticsType");
|
|
644
|
+
|
|
645
|
+
const namespace = this.addressSpace.getOwnNamespace();
|
|
646
|
+
|
|
647
|
+
function createSessionDiagnosticsStuff(this: ServerSession) {
|
|
648
|
+
if (sessionDiagnosticsDataType && sessionDiagnosticsVariableType) {
|
|
649
|
+
// the extension object
|
|
650
|
+
this._sessionDiagnostics = this.addressSpace!.constructExtensionObject(
|
|
651
|
+
sessionDiagnosticsDataType,
|
|
652
|
+
{}
|
|
653
|
+
)! as SessionDiagnosticsDataTypeEx;
|
|
654
|
+
this._sessionDiagnostics.$session = this;
|
|
655
|
+
|
|
656
|
+
// install property getter on property that are unlikely to change
|
|
657
|
+
if (this.parent.clientDescription) {
|
|
658
|
+
this._sessionDiagnostics.clientDescription = this.parent.clientDescription;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
Object.defineProperty(this._sessionDiagnostics, "clientConnectionTime", {
|
|
662
|
+
get(this: any) {
|
|
663
|
+
return this.$session.clientConnectionTime;
|
|
664
|
+
}
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
Object.defineProperty(this._sessionDiagnostics, "actualSessionTimeout", {
|
|
668
|
+
get(this: SessionDiagnosticsDataTypeEx) {
|
|
669
|
+
return this.$session?.sessionTimeout;
|
|
670
|
+
}
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
Object.defineProperty(this._sessionDiagnostics, "sessionId", {
|
|
674
|
+
get(this: SessionDiagnosticsDataTypeEx) {
|
|
675
|
+
return this.$session?.nodeId;
|
|
676
|
+
}
|
|
677
|
+
});
|
|
678
|
+
|
|
679
|
+
Object.defineProperty(this._sessionDiagnostics, "sessionName", {
|
|
680
|
+
get(this: SessionDiagnosticsDataTypeEx) {
|
|
681
|
+
return this.$session?.sessionName.toString();
|
|
682
|
+
}
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
this.sessionDiagnostics = sessionDiagnosticsVariableType.instantiate({
|
|
686
|
+
browseName: new QualifiedName({ name: "SessionDiagnostics", namespaceIndex: 0 }),
|
|
687
|
+
componentOf: this.sessionObject,
|
|
688
|
+
extensionObject: this._sessionDiagnostics,
|
|
689
|
+
minimumSamplingInterval: 2000 // 2 seconds
|
|
690
|
+
}) as UASessionDiagnosticsVariable<DTSessionDiagnostics>;
|
|
691
|
+
|
|
692
|
+
this._sessionDiagnostics = this.sessionDiagnostics.$extensionObject as SessionDiagnosticsDataTypeEx;
|
|
693
|
+
assert(this._sessionDiagnostics.$session === this);
|
|
694
|
+
|
|
695
|
+
const sessionDiagnosticsArray = this.getSessionDiagnosticsArray();
|
|
696
|
+
|
|
697
|
+
// add sessionDiagnostics into sessionDiagnosticsArray
|
|
698
|
+
addElement<SessionDiagnosticsDataType>(this._sessionDiagnostics, sessionDiagnosticsArray);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
function createSessionSecurityDiagnosticsStuff(this: ServerSession) {
|
|
702
|
+
if (sessionSecurityDiagnosticsDataType && sessionSecurityDiagnosticsType) {
|
|
703
|
+
// the extension object
|
|
704
|
+
this._sessionSecurityDiagnostics = this.addressSpace!.constructExtensionObject(
|
|
705
|
+
sessionSecurityDiagnosticsDataType,
|
|
706
|
+
{}
|
|
707
|
+
)! as SessionSecurityDiagnosticsDataTypeEx;
|
|
708
|
+
this._sessionSecurityDiagnostics.$session = this;
|
|
709
|
+
|
|
710
|
+
/*
|
|
711
|
+
sessionId: NodeId;
|
|
712
|
+
clientUserIdOfSession: UAString;
|
|
713
|
+
clientUserIdHistory: UAString[] | null;
|
|
714
|
+
authenticationMechanism: UAString;
|
|
715
|
+
encoding: UAString;
|
|
716
|
+
transportProtocol: UAString;
|
|
717
|
+
securityMode: MessageSecurityMode;
|
|
718
|
+
securityPolicyUri: UAString;
|
|
719
|
+
clientCertificate: ByteString;
|
|
720
|
+
*/
|
|
721
|
+
Object.defineProperty(this._sessionSecurityDiagnostics, "sessionId", {
|
|
722
|
+
get(this: any) {
|
|
723
|
+
return this.$session?.nodeId;
|
|
724
|
+
}
|
|
725
|
+
});
|
|
726
|
+
|
|
727
|
+
Object.defineProperty(this._sessionSecurityDiagnostics, "clientUserIdOfSession", {
|
|
728
|
+
get(this: any) {
|
|
729
|
+
return ""; // UAString // TO DO : implement
|
|
730
|
+
}
|
|
731
|
+
});
|
|
732
|
+
|
|
733
|
+
Object.defineProperty(this._sessionSecurityDiagnostics, "clientUserIdHistory", {
|
|
734
|
+
get(this: any) {
|
|
735
|
+
return []; // UAString[] | null
|
|
736
|
+
}
|
|
737
|
+
});
|
|
738
|
+
|
|
739
|
+
Object.defineProperty(this._sessionSecurityDiagnostics, "authenticationMechanism", {
|
|
740
|
+
get(this: any) {
|
|
741
|
+
return "";
|
|
742
|
+
}
|
|
743
|
+
});
|
|
744
|
+
Object.defineProperty(this._sessionSecurityDiagnostics, "encoding", {
|
|
745
|
+
get(this: any) {
|
|
746
|
+
return "";
|
|
747
|
+
}
|
|
748
|
+
});
|
|
749
|
+
Object.defineProperty(this._sessionSecurityDiagnostics, "transportProtocol", {
|
|
750
|
+
get(this: any) {
|
|
751
|
+
return "opc.tcp";
|
|
752
|
+
}
|
|
753
|
+
});
|
|
754
|
+
Object.defineProperty(this._sessionSecurityDiagnostics, "securityMode", {
|
|
755
|
+
get(this: any) {
|
|
756
|
+
const session: ServerSession = this.$session;
|
|
757
|
+
return session?.channel?.securityMode;
|
|
758
|
+
}
|
|
759
|
+
});
|
|
760
|
+
Object.defineProperty(this._sessionSecurityDiagnostics, "securityPolicyUri", {
|
|
761
|
+
get(this: any) {
|
|
762
|
+
const session: ServerSession = this.$session;
|
|
763
|
+
return session?.channel?.securityPolicy;
|
|
764
|
+
}
|
|
765
|
+
});
|
|
766
|
+
Object.defineProperty(this._sessionSecurityDiagnostics, "clientCertificate", {
|
|
767
|
+
get(this: any) {
|
|
768
|
+
const session: ServerSession = this.$session;
|
|
769
|
+
return session?.channel!.clientCertificate;
|
|
770
|
+
}
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
this.sessionSecurityDiagnostics = sessionSecurityDiagnosticsType.instantiate({
|
|
774
|
+
browseName: new QualifiedName({ name: "SessionSecurityDiagnostics", namespaceIndex: 0 }),
|
|
775
|
+
componentOf: this.sessionObject,
|
|
776
|
+
extensionObject: this._sessionSecurityDiagnostics,
|
|
777
|
+
minimumSamplingInterval: 2000 // 2 seconds
|
|
778
|
+
}) as UASessionSecurityDiagnostics<DTSessionSecurityDiagnostics>;
|
|
779
|
+
|
|
780
|
+
ensureObjectIsSecure(this.sessionSecurityDiagnostics);
|
|
781
|
+
|
|
782
|
+
this._sessionSecurityDiagnostics = this.sessionSecurityDiagnostics
|
|
783
|
+
.$extensionObject as SessionSecurityDiagnosticsDataTypeEx;
|
|
784
|
+
assert(this._sessionSecurityDiagnostics.$session === this);
|
|
785
|
+
|
|
786
|
+
const sessionSecurityDiagnosticsArray = this.getSessionSecurityDiagnosticsArray();
|
|
787
|
+
|
|
788
|
+
// add sessionDiagnostics into sessionDiagnosticsArray
|
|
789
|
+
const node = addElement<SessionSecurityDiagnosticsDataType>(
|
|
790
|
+
this._sessionSecurityDiagnostics,
|
|
791
|
+
sessionSecurityDiagnosticsArray
|
|
792
|
+
);
|
|
793
|
+
ensureObjectIsSecure(node);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
function createSessionDiagnosticSummaryUAObject(this: ServerSession) {
|
|
798
|
+
const references: any[] = [];
|
|
799
|
+
if (sessionDiagnosticsObjectType) {
|
|
800
|
+
references.push({
|
|
801
|
+
isForward: true,
|
|
802
|
+
nodeId: sessionDiagnosticsObjectType,
|
|
803
|
+
referenceType: "HasTypeDefinition"
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
this.sessionObject = namespace.createNode({
|
|
808
|
+
browseName: this.sessionName || "Session-" + this.nodeId.toString(),
|
|
809
|
+
componentOf: serverDiagnosticsNode.sessionsDiagnosticsSummary,
|
|
810
|
+
nodeClass: NodeClass.Object,
|
|
811
|
+
nodeId: this.nodeId,
|
|
812
|
+
references,
|
|
813
|
+
typeDefinition: sessionDiagnosticsObjectType
|
|
814
|
+
}) as UAObject;
|
|
815
|
+
|
|
816
|
+
createSessionDiagnosticsStuff.call(this);
|
|
817
|
+
createSessionSecurityDiagnosticsStuff.call(this);
|
|
818
|
+
}
|
|
819
|
+
function createSubscriptionDiagnosticsArray(this: ServerSession) {
|
|
820
|
+
const subscriptionDiagnosticsArrayType = this.addressSpace!.findVariableType("SubscriptionDiagnosticsArrayType")!;
|
|
821
|
+
assert(subscriptionDiagnosticsArrayType.nodeId.toString() === "ns=0;i=2171");
|
|
822
|
+
|
|
823
|
+
this.subscriptionDiagnosticsArray = createExtObjArrayNode<SubscriptionDiagnosticsDataType>(this.sessionObject, {
|
|
824
|
+
browseName: { namespaceIndex: 0, name: "SubscriptionDiagnosticsArray" },
|
|
825
|
+
complexVariableType: "SubscriptionDiagnosticsArrayType",
|
|
826
|
+
indexPropertyName: "subscriptionId",
|
|
827
|
+
minimumSamplingInterval: 2000, // 2 seconds
|
|
828
|
+
variableType: "SubscriptionDiagnosticsType"
|
|
829
|
+
});
|
|
830
|
+
}
|
|
831
|
+
createSessionDiagnosticSummaryUAObject.call(this);
|
|
832
|
+
createSubscriptionDiagnosticsArray.call(this);
|
|
833
|
+
return this.sessionObject;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
/**
|
|
837
|
+
*
|
|
838
|
+
* @private
|
|
839
|
+
*/
|
|
840
|
+
private _removeSessionObjectFromAddressSpace() {
|
|
841
|
+
// todo : dump session statistics in a file or somewhere for deeper diagnostic analysis on closed session
|
|
842
|
+
|
|
843
|
+
if (!this.addressSpace) {
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
846
|
+
if (this.sessionDiagnostics) {
|
|
847
|
+
const sessionDiagnosticsArray = this.getSessionDiagnosticsArray()!;
|
|
848
|
+
removeElement(sessionDiagnosticsArray, this.sessionDiagnostics.$extensionObject);
|
|
849
|
+
|
|
850
|
+
this.addressSpace.deleteNode(this.sessionDiagnostics);
|
|
851
|
+
|
|
852
|
+
assert(this._sessionDiagnostics!.$session === this);
|
|
853
|
+
this._sessionDiagnostics!.$session = null;
|
|
854
|
+
|
|
855
|
+
this._sessionDiagnostics = undefined;
|
|
856
|
+
this.sessionDiagnostics = undefined;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
if (this.sessionSecurityDiagnostics) {
|
|
860
|
+
const sessionSecurityDiagnosticsArray = this.getSessionSecurityDiagnosticsArray()!;
|
|
861
|
+
removeElement(sessionSecurityDiagnosticsArray, this.sessionSecurityDiagnostics.$extensionObject);
|
|
862
|
+
|
|
863
|
+
this.addressSpace.deleteNode(this.sessionSecurityDiagnostics);
|
|
864
|
+
|
|
865
|
+
assert(this._sessionSecurityDiagnostics!.$session === this);
|
|
866
|
+
this._sessionSecurityDiagnostics!.$session = null;
|
|
867
|
+
|
|
868
|
+
this._sessionSecurityDiagnostics = undefined;
|
|
869
|
+
this.sessionSecurityDiagnostics = undefined;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
if (this.sessionObject) {
|
|
873
|
+
this.addressSpace.deleteNode(this.sessionObject);
|
|
874
|
+
this.sessionObject = null;
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
/**
|
|
879
|
+
*
|
|
880
|
+
* @private
|
|
881
|
+
*/
|
|
882
|
+
private _getSubscriptionDiagnosticsArray() {
|
|
883
|
+
if (!this.addressSpace) {
|
|
884
|
+
if (doDebug) {
|
|
885
|
+
console.warn("ServerSession#_getSubscriptionDiagnosticsArray : no addressSpace");
|
|
886
|
+
}
|
|
887
|
+
return null; // no addressSpace
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
const subscriptionDiagnosticsArray = this.subscriptionDiagnosticsArray;
|
|
891
|
+
if (!subscriptionDiagnosticsArray) {
|
|
892
|
+
return null; // no subscriptionDiagnosticsArray
|
|
893
|
+
}
|
|
894
|
+
assert(subscriptionDiagnosticsArray.browseName.toString() === "SubscriptionDiagnosticsArray");
|
|
895
|
+
return subscriptionDiagnosticsArray;
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
private assignSubscription(subscription: Subscription) {
|
|
899
|
+
assert(!subscription.$session);
|
|
900
|
+
assert(this.nodeId instanceof NodeId);
|
|
901
|
+
|
|
902
|
+
subscription.$session = this;
|
|
903
|
+
|
|
904
|
+
subscription.sessionId = this.nodeId;
|
|
905
|
+
|
|
906
|
+
this._cumulatedSubscriptionCount += 1;
|
|
907
|
+
|
|
908
|
+
// Notify the owner that a new subscription has been created
|
|
909
|
+
// @event new_subscription
|
|
910
|
+
// @param {Subscription} subscription
|
|
911
|
+
this.emit("new_subscription", subscription);
|
|
912
|
+
|
|
913
|
+
// add subscription diagnostics to SubscriptionDiagnosticsArray
|
|
914
|
+
this._exposeSubscriptionDiagnostics(subscription);
|
|
915
|
+
|
|
916
|
+
subscription.once("terminated", () => {
|
|
917
|
+
// Notify the owner that a new subscription has been terminated
|
|
918
|
+
// @event subscription_terminated
|
|
919
|
+
// @param {Subscription} subscription
|
|
920
|
+
this.emit("subscription_terminated", subscription);
|
|
921
|
+
});
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
private _deleteSubscriptions() {
|
|
925
|
+
assert(this.publishEngine);
|
|
926
|
+
const subscriptions = this.publishEngine.subscriptions;
|
|
927
|
+
for (const subscription of subscriptions) {
|
|
928
|
+
this.deleteSubscription(subscription.id);
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
}
|