"use strict";
/**
* @module opcua.server
*/
const _ = require("underscore");
const crypto = require("crypto");
const NodeId = require("node-opcua-nodeid").NodeId;
const sameNodeId = require("node-opcua-nodeid").sameNodeId;
const NodeIdType = require("node-opcua-nodeid").NodeIdType;
const ServerSidePublishEngine = require("./server_publish_engine").ServerSidePublishEngine;
const StatusCodes = require("node-opcua-status-code").StatusCodes;
const NodeClass = require("node-opcua-data-model").NodeClass;
const QualifiedName = require("node-opcua-data-model").QualifiedName;
const DataValue = require("node-opcua-data-value").DataValue;
const VariableIds = require("node-opcua-constants").VariableIds;
const ec = require("node-opcua-basic-types");
const assert = require("node-opcua-assert").assert;
const util = require("util");
const EventEmitter = require("events").EventEmitter;
const eoan = require("node-opcua-address-space");
const makeNodeId = require("node-opcua-nodeid").makeNodeId;
const ObjectIds = require("node-opcua-constants").ObjectIds;
const WatchDog = require("node-opcua-utils").WatchDog;
const theWatchDog = new WatchDog();
const ContinuationPointManager = require("./continuation_point_manager").ContinuationPointManager;
const utils = require("node-opcua-utils");
const debugLog = require("node-opcua-debug").make_debugLog(__filename);
const doDebug = require("node-opcua-debug").checkDebugFlag(__filename);
/**
*
* A Server session object.
*
* **from OPCUA Spec 1.02:**
*
* * Sessions are created to be independent of the underlying communications connection. Therefore, if a communication
* connection fails, the Session is not immediately affected. The exact mechanism to recover from an underlying
* communication connection error depends on the SecureChannel mapping as described in Part 6.
*
* * Sessions are terminated by the Server automatically if the Client fails to issue a Service request on the Session
* within the timeout period negotiated by the Server in the CreateSession Service response. This protects the Server
* against Client failures and against situations where a failed underlying connection cannot be re-established.
*
* * Clients shall be prepared to submit requests in a timely manner to prevent the Session from closing automatically.
*
* * Clients may explicitly terminate Sessions using the CloseSession Service.
*
* * When a Session is terminated, all outstanding requests on the Session are aborted and Bad_SessionClosed StatusCodes
* are returned to the Client. In addition, the Server deletes the entry for the Client from its
* SessionDiagnosticsArray Variable and notifies any other Clients who were subscribed to this entry.
*
* @class ServerSession
*
* @param parent {SessionEngine}
* @param sessionTimeout {Number}
* @private
* @constructor
*/
function ServerSession(parent, sessionTimeout) {
const session = this;
session.parent = parent; // SessionEngine
EventEmitter.apply(session, arguments);
ServerSession.registry.register(session);
assert(_.isFinite(sessionTimeout));
assert(sessionTimeout >= 0, " sessionTimeout");
session.sessionTimeout = sessionTimeout;
const authenticationTokenBuf = crypto.randomBytes(16);
session.authenticationToken = new NodeId(NodeIdType.BYTESTRING, authenticationTokenBuf);
// the sessionId
const ownNamespaceIndex = 1; // addressSpace.getOwnNamespace().index;
session.nodeId = new NodeId(NodeIdType.GUID, ec.randomGuid(), ownNamespaceIndex);
assert(session.authenticationToken instanceof NodeId);
assert(session.nodeId instanceof NodeId);
session._cumulatedSubscriptionCount = 0;
session.publishEngine = new ServerSidePublishEngine({
maxPublishRequestInQueue: ServerSession.maxPublishRequestInQueue
});
session.publishEngine.setMaxListeners(100);
theWatchDog.addSubscriber(session, session.sessionTimeout);
session.__status = "new";
/**
* the continuation point manager for this session
* @property continuationPointManager
* @type {ContinuationPointManager}
*/
session.continuationPointManager = new ContinuationPointManager();
/**
* @property creationDate
* @type {Date}
*/
session.creationDate = new Date();
session._registeredNodesCounter = 0;
session._registeredNodes = {};
session._registeredNodesInv = {};
}
util.inherits(ServerSession, EventEmitter);
const ObjectRegistry = require("node-opcua-object-registry").ObjectRegistry;
ServerSession.registry = new ObjectRegistry();
ServerSession.prototype.dispose = function() {
debugLog("ServerSession#dispose()");
const self = this;
assert(!self.sessionObject," sessionObject has not been cleared !");
self.parent = null;
self.authenticationToken = null;
if (self.publishEngine) {
self.publishEngine.dispose();
self.publishEngine = null;
}
self._sessionDiagnostics = null;
self._registeredNodesCounter = 0;
self._registeredNodes = null;
self._registeredNodesInv = null;
self.continuationPointManager = null;
self.removeAllListeners();
self.__status = "disposed";
ServerSession.registry.unregister(self);
};
ServerSession.maxPublishRequestInQueue = 100;
Object.defineProperty(ServerSession.prototype, "clientConnectionTime", {
get: function () {
const self = this;
return self.creationDate;
}
});
Object.defineProperty(ServerSession.prototype, "clientLastContactTime", {
get: function () {
const self = this;
return self._watchDogData.last_seen;
}
});
Object.defineProperty(ServerSession.prototype, "status", {
get: function () {
return this.__status;
},
set: function (value) {
if (value === "active") {
assert(this.__value !== "active");
this._createSessionObjectInAddressSpace();
}
this.__status = value;
}
});
ServerSession.prototype.__defineGetter__("addressSpace", function () {
return this.parent ? this.parent.addressSpace : null;
});
ServerSession.prototype.__defineGetter__("currentPublishRequestInQueue", function () {
const self = this;
return self.publishEngine ? self.publishEngine.pendingPublishRequestCount : 0;
});
//xx ServerSession.prototype.__defineGetter__("serverDiagnostics")
const ServiceCounter = require("node-opcua-common").ServiceCounter;
const SessionDiagnostics = require("node-opcua-common").SessionDiagnostics;
const SubscriptionDiagnostics = require("node-opcua-common").SubscriptionDiagnostics;
ServerSession.prototype.updateClientLastContactTime = function (currentTime) {
const session = this;
if (session._sessionDiagnostics && session._sessionDiagnostics.clientLastContactTime) {
currentTime = currentTime || new Date();
// do not record all ticks as this may be overwhelming,
if (currentTime.getTime() - 250 >= session._sessionDiagnostics.clientLastContactTime.getTime()) {
session._sessionDiagnostics.clientLastContactTime = currentTime;
}
}
};
/**
* @method onClientSeen
* required for watch dog
* @param currentTime {DateTime}
* @private
*/
ServerSession.prototype.onClientSeen = function (currentTime) {
const session = this;
session.updateClientLastContactTime(currentTime);
if (session._sessionDiagnostics) {
// see https://opcfoundation-onlineapplications.org/mantis/view.php?id=4111
assert(session._sessionDiagnostics.hasOwnProperty("currentMonitoredItemsCount"));
assert(session._sessionDiagnostics.hasOwnProperty("currentSubscriptionsCount"));
assert(session._sessionDiagnostics.hasOwnProperty("currentPublishRequestsInQueue"));
// note : https://opcfoundation-onlineapplications.org/mantis/view.php?id=4111
// sessionDiagnostics extension object uses a different spelling
// here with an S !!!!
session._sessionDiagnostics.currentMonitoredItemsCount = session.currentMonitoredItemCount;
session._sessionDiagnostics.currentSubscriptionsCount = session.currentSubscriptionCount;
session._sessionDiagnostics.currentPublishRequestsInQueue = session.currentPublishRequestInQueue;
}
};
ServerSession.prototype.incrementTotalRequestCount = function () {
const session = this;
if (session._sessionDiagnostics && session._sessionDiagnostics.totalRequestCount) {
session._sessionDiagnostics.totalRequestCount.totalCount += 1;
}
};
const lowerFirstLetter = require("node-opcua-utils").lowerFirstLetter;
ServerSession.prototype.incrementRequestTotalCounter = function (counterName) {
const session = this;
if (session._sessionDiagnostics) {
const propName = lowerFirstLetter(counterName + "Count");
if (!session._sessionDiagnostics.hasOwnProperty(propName)) {
console.log(" cannot find", propName);
//xx return;
}
// console.log(self._sessionDiagnostics.toString());
session._sessionDiagnostics[propName].totalCount = session._sessionDiagnostics[propName].totalCount + 1;
}
};
ServerSession.prototype.incrementRequestErrorCounter = function (counterName) {
const session = this;
if (session._sessionDiagnostics) {
const propName = lowerFirstLetter(counterName + "Count");
if (!session._sessionDiagnostics.hasOwnProperty(propName)) {
console.log(" cannot find", propName);
//xx return;
}
session._sessionDiagnostics[propName].errorCount += 1;
}
};
/**
* return rootFolder.objects.server.serverDiagnostics.sessionsDiagnosticsSummary
* @returns {UAObject}
*/
ServerSession.prototype.getSessionDiagnosticsArray = function () {
const self = this;
return self.addressSpace.rootFolder.objects.server.serverDiagnostics.sessionsDiagnosticsSummary.sessionDiagnosticsArray
};
ServerSession.prototype._createSessionObjectInAddressSpace = function () {
const session = this;
if (session.sessionObject) {
return;
}
assert(!session.sessionObject, "ServerSession#_createSessionObjectInAddressSpace already called ?");
session.sessionObject = null;
if (!session.addressSpace) {
debugLog("ServerSession#_createSessionObjectInAddressSpace : no addressSpace");
return; // no addressSpace
}
const root = session.addressSpace.findNode(makeNodeId(ObjectIds.RootFolder));
assert(root, "expecting a root object");
if (!root.objects) {
debugLog("ServerSession#_createSessionObjectInAddressSpace : no object folder");
return false;
}
if (!root.objects.server) {
debugLog("ServerSession#_createSessionObjectInAddressSpace : no server object");
return false;
}
// self.addressSpace.findNode(makeNodeId(ObjectIds.Server_ServerDiagnostics));
const serverDiagnosticsNode = root.objects.server.serverDiagnostics;
if (!serverDiagnosticsNode || !serverDiagnosticsNode.sessionsDiagnosticsSummary) {
debugLog("ServerSession#_createSessionObjectInAddressSpace : no serverDiagnostics.sessionsDiagnosticsSummary");
return false;
}
const sessionDiagnosticsDataType = session.addressSpace.findDataType("SessionDiagnosticsDataType");
const sessionDiagnosticsObjectType = session.addressSpace.findObjectType("SessionDiagnosticsObjectType");
const sessionDiagnosticsVariableType = session.addressSpace.findVariableType("SessionDiagnosticsVariableType");
const references = [];
if (sessionDiagnosticsObjectType) {
references.push({referenceType: "HasTypeDefinition", isForward: true, nodeId: sessionDiagnosticsObjectType});
}
const namespace = session.addressSpace.getOwnNamespace();
session.sessionObject = namespace.createNode({
nodeId: session.nodeId,
nodeClass: NodeClass.Object,
browseName: session.sessionName || "Session-" + session.nodeId.toString(),
componentOf: serverDiagnosticsNode.sessionsDiagnosticsSummary,
typeDefinition: sessionDiagnosticsObjectType,
references: references
});
if (sessionDiagnosticsDataType && sessionDiagnosticsVariableType) {
// the extension object
session._sessionDiagnostics = session.addressSpace.constructExtensionObject(sessionDiagnosticsDataType, {});
session._sessionDiagnostics.session = session;
// install property getter on property that are unlikely to change
if (session.parent.clientDescription) {
session._sessionDiagnostics.clientDescription = session.parent.clientDescription;
}
Object.defineProperty(session._sessionDiagnostics, "clientConnectionTime", {
get: function () {
return this.session.clientConnectionTime;
}
});
Object.defineProperty(session._sessionDiagnostics, "actualSessionTimeout", {
get: function () {
return this.session.sessionTimeout;
}
});
Object.defineProperty(session._sessionDiagnostics, "sessionId", {
get: function () {
return this.session.nodeId;
}
});
Object.defineProperty(session._sessionDiagnostics, "sessionName", {
get: function () {
assert(_.isString(session.sessionName));
return this.session.sessionName.toString();
}
});
session.sessionDiagnostics = sessionDiagnosticsVariableType.instantiate({
browseName: new QualifiedName({ name: "SessionDiagnostics", namespaceIndex: 0}),
componentOf: session.sessionObject,
extensionObject: session._sessionDiagnostics,
minimumSamplingInterval: 2000 // 2 seconds
});
session._sessionDiagnostics = session.sessionDiagnostics.$extensionObject;
assert(session._sessionDiagnostics.session === session);
const sessionDiagnosticsArray = session.getSessionDiagnosticsArray();
// add sessionDiagnostics into sessionDiagnoticsArray
eoan.addElement(session._sessionDiagnostics, sessionDiagnosticsArray);
}
const subscriptionDiagnosticsArrayType = session.addressSpace.findVariableType("SubscriptionDiagnosticsArrayType");
assert(subscriptionDiagnosticsArrayType.nodeId.toString() === "ns=0;i=2171");
session.subscriptionDiagnosticsArray=
eoan.createExtObjArrayNode(session.sessionObject, {
browseName: { namespaceIndex: 0,name:"SubscriptionDiagnosticsArray" },
complexVariableType: "SubscriptionDiagnosticsArrayType",
variableType: "SubscriptionDiagnosticsType",
indexPropertyName: "subscriptionId",
minimumSamplingInterval: 2000 // 2 seconds
});
return session.sessionObject;
};
ServerSession.prototype._removeSessionObjectFromAddressSpace = function () {
const session = this;
// todo : dump session statistics in a file or somewhere for deeper diagnostic analysis on closed session
if (!session.addressSpace) {
return;
}
if (session.sessionDiagnostics) {
const sessionDiagnosticsArray = session.getSessionDiagnosticsArray();
eoan.removeElement(sessionDiagnosticsArray, session.sessionDiagnostics.$extensionObject);
session.addressSpace.deleteNode(session.sessionDiagnostics);
assert(session._sessionDiagnostics.session === session);
session._sessionDiagnostics.session = null;
session._sessionDiagnostics = null;
session.sessionDiagnostics = null;
}
if (session.sessionObject) {
session.addressSpace.deleteNode(session.sessionObject);
session.sessionObject = null;
}
};
const Subscription = require("./server_subscription").Subscription;
ServerSession.prototype._getSubscriptionDiagnosticsArray = function () {
const session = this;
if (!session.addressSpace) {
if (doDebug) {
console.warn("ServerSession#_getSubscriptionDiagnosticsArray : no addressSpace");
}
return null; // no addressSpace
}
const subscriptionDiagnosticsArray = session.subscriptionDiagnosticsArray;
if (!subscriptionDiagnosticsArray) {
return null; // no subscriptionDiagnosticsArray
}
assert(subscriptionDiagnosticsArray.browseName.toString() === "SubscriptionDiagnosticsArray");
return subscriptionDiagnosticsArray;
};
ServerSession.prototype._exposeSubscriptionDiagnostics = function (subscription) {
const session = this;
debugLog("ServerSession#_exposeSubscriptionDiagnostics");
assert(subscription.$session === session);
const subscriptionDiagnosticsArray = session._getSubscriptionDiagnosticsArray();
const subscriptionDiagnostics = subscription.subscriptionDiagnostics;
assert(subscriptionDiagnostics.$subscription == subscription);
if (subscriptionDiagnostics && subscriptionDiagnosticsArray) {
//xx console.log("GGGGGGGGGGGGGGGG => ServerSession Exposing subscription diagnostics =>",subscription.id,"on session", session.nodeId.toString());
eoan.addElement(subscriptionDiagnostics, subscriptionDiagnosticsArray);
}
};
function compareSessionId(sessionDiagnostics1, sessionDiagnostics2) {
return sessionDiagnostics1.sessionId.toString() == sessionDiagnostics2.sessionId.toString();
}
ServerSession.prototype._unexposeSubscriptionDiagnostics = function (subscription) {
const session = this;
const subscriptionDiagnosticsArray = session._getSubscriptionDiagnosticsArray();
const subscriptionDiagnostics = subscription.subscriptionDiagnostics;
assert(subscriptionDiagnostics instanceof SubscriptionDiagnostics);
if (subscriptionDiagnostics && subscriptionDiagnosticsArray) {
//xx console.log("GGGGGGGGGGGGGGGG => ServerSession **Unexposing** subscription diagnostics =>",subscription.id,"on session", session.nodeId.toString());
eoan.removeElement(subscriptionDiagnosticsArray, subscriptionDiagnostics);
}
debugLog("ServerSession#_unexposeSubscriptionDiagnostics");
};
ServerSession.prototype.assignSubscription = function (subscription) {
const session = this;
assert(!subscription.$session);
assert(session.nodeId instanceof NodeId);
subscription.$session = session;
subscription.sessionId = session.nodeId;
session._cumulatedSubscriptionCount += 1;
// Notify the owner that a new subscription has been created
// @event new_subscription
// @param {Subscription} subscription
session.emit("new_subscription", subscription);
// add subscription diagnostics to SubscriptionDiagnosticsArray
session._exposeSubscriptionDiagnostics(subscription);
subscription.once("terminated", function () {
//Xx session._unexposeSubscriptionDiagnostics(subscription);
// Notify the owner that a new subscription has been terminated
// @event subscription_terminated
// @param {Subscription} subscription
session.emit("subscription_terminated", subscription);
});
};
ServerSession.prototype.createSubscription = function(parameters) {
const session = this;
const subscription = session.parent._createSubscriptionOnSession(session,parameters);
session.assignSubscription(subscription);
assert(subscription.$session === session);
assert(subscription.sessionId instanceof NodeId);
assert(sameNodeId(subscription.sessionId,session.nodeId));
return subscription;
};
/**
* number of active subscriptions
* @property currentSubscriptionCount
* @type {Number}
*/
ServerSession.prototype.__defineGetter__("currentSubscriptionCount", function () {
const self = this;
return self.publishEngine ? self.publishEngine.subscriptionCount : 0;
});
/**
* number of subscriptions ever created since this object is live
* @property cumulatedSubscriptionCount
* @type {Number}
*/
ServerSession.prototype.__defineGetter__("cumulatedSubscriptionCount", function () {
return this._cumulatedSubscriptionCount;
});
/**
* number of monitored items
* @property currentMonitoredItemCount
* @type {Number}
*/
ServerSession.prototype.__defineGetter__("currentMonitoredItemCount", function () {
const self = this;
return self.publishEngine ? self.publishEngine.currentMonitoredItemCount : 0;
});
const SubscriptionState = require("./server_subscription").SubscriptionState;
/**
* retrieve an existing subscription by subscriptionId
* @method getSubscription
* @param subscriptionId {Number}
* @return {Subscription}
*/
ServerSession.prototype.getSubscription = function (subscriptionId) {
const subscription = this.publishEngine.getSubscriptionById(subscriptionId);
if (subscription && subscription.state === SubscriptionState.CLOSED) {
// subscription is CLOSED but has not been notified yet
// it should be considered as excluded
return null;
}
assert(!subscription || subscription.state !== SubscriptionState.CLOSED, "CLOSED subscription shall not be managed by publish engine anymore");
return subscription;
};
/**
* @method deleteSubscription
* @param subscriptionId {Number}
* @return {StatusCode}
*/
ServerSession.prototype.deleteSubscription = function (subscriptionId) {
const session = this;
const subscription = session.getSubscription(subscriptionId);
if (!subscription) {
return StatusCodes.BadSubscriptionIdInvalid;
}
//xx this.publishEngine.remove_subscription(subscription);
subscription.terminate();
if (session.currentSubscriptionCount === 0) {
const local_publishEngine = session.publishEngine;
local_publishEngine.cancelPendingPublishRequest();
}
return StatusCodes.Good;
};
ServerSession.prototype._deleteSubscriptions = function () {
const session = this;
assert(session.publishEngine);
const subscriptions = session.publishEngine.subscriptions;
subscriptions.forEach(function (subscription) {
session.deleteSubscription(subscription.id);
});
};
/**
* close a ServerSession, this will also delete the subscriptions if the flag is set.
*
* Spec extract:
*
* If a Client invokes the CloseSession Service then all Subscriptions associated with the Session are also deleted
* if the deleteSubscriptions flag is set to TRUE. If a Server terminates a Session for any other reason,
* Subscriptions associated with the Session, are not deleted. Each Subscription has its own lifetime to protect
* against data loss in the case of a Session termination. In these cases, the Subscription can be reassigned to
* another Client before its lifetime expires.
*
* @method close
* @param {Boolean} deleteSubscriptions : should we delete subscription ?
* @param {String} [reason = "CloseSession"] the reason for closing the session (shall be "Timeout", "Terminated" or "CloseSession")
*
*/
ServerSession.prototype.close = function (deleteSubscriptions, reason) {
//xx console.log("xxxxxxxxxxxxxxxxxxxxxxxxxxx => ServerSession.close() ");
const session = this;
if (session.publishEngine) {
session.publishEngine.onSessionClose();
}
theWatchDog.removeSubscriber(session);
// --------------- delete associated subscriptions ---------------------
if (!deleteSubscriptions && session.currentSubscriptionCount !== 0) {
// I don't know what to do yet if deleteSubscriptions is false
console.log("TO DO : Closing session without deleting subscription not yet implemented");
//to do: Put subscriptions in safe place for future transfer if any
}
session._deleteSubscriptions();
assert(session.currentSubscriptionCount === 0);
// Post-Conditions
assert(session.currentSubscriptionCount === 0);
session.status = "closed";
/**
* @event session_closed
* @param deleteSubscriptions {Boolean}
* @param reason {String}
*/
session.emit("session_closed", session, deleteSubscriptions, reason);
// ---------------- shut down publish engine
if (session.publishEngine) {
// remove subscription
session.publishEngine.shutdown();
assert(session.publishEngine.subscriptionCount === 0);
session.publishEngine.dispose();
session.publishEngine = null;
}
session._removeSessionObjectFromAddressSpace();
assert(!session.sessionDiagnostics, "ServerSession#_removeSessionObjectFromAddressSpace must be called");
assert(!session.sessionObject, "ServerSession#_removeSessionObjectFromAddressSpace must be called");
};
/**
* @method watchdogReset
* used as a callback for the Watchdog
* @private
*/
ServerSession.prototype.watchdogReset = function () {
const self = this;
// the server session has expired and must be removed from the server
self.emit("timeout");
};
const registeredNodeNameSpace = 9999 ;
ServerSession.prototype.registerNode = function(nodeId) {
assert(nodeId instanceof NodeId);
const session = this;
if (nodeId.namespace === 0 && nodeId.identifierType.value === NodeIdType.NUMERIC.value) {
return nodeId;
}
const key = nodeId.toString();
const registeredNode = session._registeredNodes[key];
if (registeredNode) {
// already registered
return registeredNode;
}
const node = session.addressSpace.findNode(nodeId);
if (!node) {
return nodeId;
}
session._registeredNodesCounter +=1;
const aliasNodeId = makeNodeId(session._registeredNodesCounter,registeredNodeNameSpace);
session._registeredNodes[key] = aliasNodeId;
session._registeredNodesInv[aliasNodeId.toString()] = node;
return aliasNodeId;
};
ServerSession.prototype.unRegisterNode = function(aliasNodeId) {
assert(aliasNodeId instanceof NodeId);
if (aliasNodeId.namespace !== registeredNodeNameSpace) {
return aliasNodeId; // not a registered Node
}
const session = this;
const node = session._registeredNodesInv[aliasNodeId.toString()];
if (!node) {
return ;
}
session._registeredNodesInv[aliasNodeId.toString()] = null;
session._registeredNodes[node.nodeId.toString()] = null;
};
ServerSession.prototype.resolveRegisteredNode = function(aliasNodeId) {
const session = this;
if (aliasNodeId.namespace !== registeredNodeNameSpace) {
return aliasNodeId; // not a registered Node
}
const node = session._registeredNodesInv[aliasNodeId.toString()];
if (!node) {
return aliasNodeId;
}
return node.nodeId;
};
/**
* true if the underlying channel has been closed or aborted...
*/
ServerSession.prototype.__defineGetter__("aborted", function () {
const session = this;
if (!session.channel) {
return true;
}
return session.channel.aborted;
});
function on_channel_abort()
{
const session = this;
debugLog("ON CHANNEL ABORT ON SESSION!!!");
/**
* @event channel_aborted
*/
session.emit("channel_aborted");
}
ServerSession.prototype._attach_channel = function(channel) {
const session = this;
assert(session.nonce && session.nonce instanceof Buffer);
session.channel = channel;
session.secureChannelId = channel.secureChannelId;
const key = session.authenticationToken.toString("hex");
assert(!channel.sessionTokens.hasOwnProperty(key), "channel has already a session");
channel.sessionTokens[key] = session;
// when channel is aborting
session.channel_abort_event_handler = on_channel_abort.bind(session);
channel.on("abort", session.channel_abort_event_handler);
};
ServerSession.prototype._detach_channel = function() {
const session = this;
const channel = session.channel;
assert(channel,"expecting a valid channel");
assert(session.nonce && session.nonce instanceof Buffer);
assert(session.authenticationToken);
const key = session.authenticationToken.toString("hex");
assert(channel.sessionTokens.hasOwnProperty(key));
assert(session.channel);
assert(_.isFunction(session.channel_abort_event_handler));
channel.removeListener("abort", session.channel_abort_event_handler);
delete channel.sessionTokens[key];
session.channel = null;
session.secureChannelId = null;
};
exports.ServerSession = ServerSession;