text: "use strict";
  /**
   * @module opcua.server
   * @type {async|exports}
   */
  
  var net = require("net");
  var https = require("https");
  var fs = require("fs");
  var util = require("util");
  var path = require("path");
  var assert = require("node-opcua-assert");
  var async = require("async");
  var _ = require("underscore");
  var EventEmitter = require("events").EventEmitter;
  var WebSocket = require('ws');
  
  var UserIdentityTokenType = require("node-opcua-service-endpoints").UserIdentityTokenType;
  var MessageSecurityMode = require("node-opcua-service-secure-channel").MessageSecurityMode;
  var fromURI = require("node-opcua-secure-channel").fromURI;
  
  var debugLog = require("node-opcua-debug").make_debugLog(__filename);
  var doDebug = require("node-opcua-debug").checkDebugFlag(__filename);
  
  var ServerSecureChannelLayer = require("node-opcua-secure-channel/src/server/server_secure_channel_layer").ServerSecureChannelLayer;
  var toURI = require("node-opcua-secure-channel").toURI;
  
  
  var EndpointDescription = require("node-opcua-service-endpoints").EndpointDescription;
  
  var crypto_utils = require("node-opcua-crypto").crypto_utils;
  var split_der = require("node-opcua-crypto").crypto_explore_certificate.split_der;
  
  /**
   * OPCUAServerEndPoint a Server EndPoint.
   * @class OPCUAServerEndPoint
   *
   * A sever end point is listening to one port
   *
   * @param options {Object}
   * @param options.protocol                              {String} the endpoint protocol
   * @param options.port                                  {Number} the endpoint port
   * @param options.certificateChain                      {Buffer} the DER certificate chain
   * @param options.privateKey                            {String} PEM string of the private key
   * @param [options.defaultSecureTokenLifetime=600000]   {Number} the default secure token lifetime
   * @param [options.timeout=30000]                       {Number} the  timeout for the TCP HEL/ACK transaction (in ms)
   * @param [options.maxConnections = 20 ]                {Number} the maximum number of connection allowed on the TCP server socket
   * @param options.serverInfo                            {ApplicationDescription}
   * @param [options.serverInfo.applicationUri]           {String}
   * @param [options.serverInfo.productUri]               {String}
   * @param [options.serverInfo.applicationName]          {LocalizedText}
   * @param [options.serverInfo.gatewayServerUri]         {String|null}
   * @param [options.serverInfo.discoveryProfileUri]      {String|null}
   * @param [options.serverInfo.discoveryUrls]            {String[]}
   * @param options.objectFactory
   * @constructor
   *
   * note:
   *   see OPCUA Release 1.03 part 4 page 108 7.1 ApplicationDescription
   */
  function OPCUAServerEndPoint(options) {
  
      var self = this;
  
      assert(!options.hasOwnProperty("certificate"), "expecting a certificateChain instead");
      assert(options.hasOwnProperty("certificateChain"), "expecting a certificateChain");
      assert(options.hasOwnProperty("privateKey"));
      assert(typeof(options.privateKey) === "string");
  
      options = options || {};
      options.port = options.port || 0;
  
      self.port = parseInt(options.port, 10);
      assert(_.isNumber(self.port));
  
      self.protocol = options.protocol || "opc.tcp";
  
      self._certificateChain = options.certificateChain;
      self._privateKey = options.privateKey;
  
      self.sslCertificateFile = options.sslCertificateFile || path.join(__dirname,"../","certificates/server_selfsigned_cert_2048.pem");
      self.sslKeyFile = options.sslKeyFile || path.join(__dirname,"../","certificates/PKI/own/private/private_key.pem");
  
      self._channels = {};
  
      self.defaultSecureTokenLifetime = options.defaultSecureTokenLifetime || 600000;
  
      self.maxConnections = options.maxConnections || 20;
  
      self.timeout = options.timeout || 30000;
  
      self._server = null;
  
      self._setup_server();
  
      self._endpoints = [];
      self._reverseConnections = [];
      self._reverseSockets = [];
  
      self.objectFactory = options.objectFactory;
  
      this.bytesWrittenInOldChannels = 0;
      this.bytesReadInOldChannels = 0;
      this.transactionsCountOldChannels = 0;
      this.securityTokenCountOldChannels = 0;
  
      this.serverInfo = options.serverInfo;
      assert(_.isObject(this.serverInfo));
  
  }
  
  util.inherits(OPCUAServerEndPoint, EventEmitter);
  
  OPCUAServerEndPoint.prototype._dump_statistics = function () {
  
      var self = this;
  
      switch (self.protocol) {
          case "opc.tcp":
              self._server.getConnections(function (err, count) {
                  debugLog("CONCURRENT CONNECTION = ".cyan, count);
                  debugLog("MAX CONNECTIONS = ".cyan, self._server.maxConnections);
              });
              break;
          case "opc.wss":
              debugLog("CONCURRENT CONNECTION = ".cyan, self._server.clients.size);
              debugLog("MAX CONNECTIONS = ".cyan, self._server._server.maxConnections);
              break;
          case "fake":
          case "http":
          case "https":
          default:
              throw new Error("this transport protocol is currently not supported :" + self.protocol);
              return null;
      }           
  };
  
  OPCUAServerEndPoint.prototype.addOutgoingConnection = function (clientUrl,backoffTime){
      var self = this;
  
      // prevent illegal call to connect
      if (self._reverseConnections.includes(clientUrl)) {
          setImmediate(function () {
              callback(new Error("reverse connection already exists!"), null);
          });
          return;
      }else{
          self._reverseConnections.push(clientUrl);
      }
  
      function _reverse_connect(clientUrl, backoffTime){
          switch (self.protocol) {
              case "opc.wss":
                  //load ssl credentials
                  var sslCertificate = fs.readFileSync(self.sslCertificateFile);
                  var sslKey = fs.readFileSync(self.sslKeyFile);
      
                  //connect to the Websocket.Server at the client
                  //workaround since ws does not recognize opc.wss as secure 
                  var wsocket = new WebSocket(clientUrl.substring(4),{rejectUnauthorized: false});
                  //var socket = new WebSocket(endpointUrl,{rejectUnauthorized: false});
      
                  wsocket.on("open", function () {
                      wsocket.isAlive = true;
                      wsocket.haderror =false;
  
                      //second listener
                      self._on_client_connection(wsocket, true);
      
                      // istanbul ignore next
                      if (doDebug) {
                          self._dump_statistics();
                          debugLog("WS Client connected: "+clientUrl);
                      }                    
      
                      wsocket.on('pong', heartbeat);
      
                      self._reverseSockets.push(wsocket);
              
                  }).on("close", function () {
                      debugLog("WS server outgoing closed : "+clientUrl);
                      var index=self._reverseConnections.indexOf(clientUrl);
                      if(index>=0){
                          self._reverseSockets[index]=null;
                          wsocket.terminate();
                          if(wsocket.haderror !== false && wsocket.haderror.message.match(/ECONNREFUSED/)){
                              setTimeout(function(){_reverse_connect(clientUrl, backoffTime)}, backoffTime);
                          }else{
                              _reverse_connect(clientUrl, backoffTime);
                          }                        
                      }
                      //trigger backoff
                  }).on("error", function (err) {
                      // this could be because the port is already in use
                      wsocket.haderror = err;
                      debugLog("WS server outgoing error: ".red.bold, err.message,clientUrl);
                  });
      
                  //timeout with heartbeat
                  function heartbeat() {
                      this.isAlive = true;
                      console.log("its alive!");
                  }
                  break;
              case "opc.tcp":
              case "fake":
              case "http":
              case "https":
              default:
                  throw new Error("this transport protocol is currently not supported :" + self.protocol);
                  return null;
          }
      };
  
      _reverse_connect(clientUrl, backoffTime);
  };
  
  OPCUAServerEndPoint.prototype._setup_server = function () {
      var self = this;
  
      assert(self._server === null);
  
      switch (self.protocol) {
          case "opc.tcp":
              self._server = net.createServer(self._on_client_connection.bind(self));
  
              //xx console.log(" Server with max connections ", self.maxConnections);
              self._server.maxConnections = self.maxConnections + 1; // plus one extra
          
              self._listen_callback = null;
              
              self._server.on("connection", function (socket) {
  
                  // istanbul ignore next
                  if (doDebug) {
                      self._dump_statistics();
                      debugLog("TCP server connected  with : " + socket.remoteAddress + ":" + socket.remotePort);
                  }
          
              }).on("close", function () {
                  debugLog("TCP server closed : all connections have ended");
              }).on("error", function (err) {
                  // this could be because the port is already in use
                  debugLog("TCP server error: ".red.bold, err.message);
              });
  
              break;
          case "opc.wss":
              //load ssl credentials
              var sslCertificate = fs.readFileSync(self.sslCertificateFile);
              var sslKey = fs.readFileSync(self.sslKeyFile);
  
              //create a https server
              var httpsServer = https.createServer({
                  cert: sslCertificate,
                  key: sslKey
              });
              //httpsServer.listen(self.port);
  
              //create WebsocketServer
              self._server = new WebSocket.Server({ server: httpsServer});
              //self._server = new WebSocket.Server({ port: self.port});
  
              //xx console.log(" Server with max connections ", self.maxConnections);
              self._server._server.maxConnections = self.maxConnections + 1; // plus one extra
  
              self._listen_callback = null;
  
              self._server.on("connection", function (socket, req) {
  
                  //second listener
                  self._on_client_connection(socket);
  
                  // istanbul ignore next
                  if (doDebug) {
                      self._dump_statistics();
                      debugLog("WS server connected: "+req.connection.remoteAddress);
                  }
  
                  socket.isAlive = true;
                  socket.on('pong', heartbeat);
          
              }).on("close", function () {
                  debugLog("WS server closed : all connections have ended");
              }).on("error", function (err) {
                  // this could be because the port is already in use
                  debugLog("WS server error: ".red.bold, err.message);
              });
  
              //timeout with heartbeat
              function noop() {}
  
              function heartbeat() {
                  this.isAlive = true;
                  console.log("its alive!");
              }
              
              // for normal endpoint connections
              var interval = setInterval(function ping() {
                  self._server.clients.forEach(function each(ws) {
                  if (ws.isAlive === false) return ws.terminate();
              
                  ws.isAlive = false;
                  ws.ping(noop);
                  });
              }, self.timeout);
  
              // for outgoing connections
              var interval2 = setInterval(function ping() {
                  self._reverseSockets.forEach(function each(ws) {
                      if(ws !== null){
                          if (ws.isAlive === false) return ws.terminate();
  
                          ws.isAlive = false;
                          ws.ping(noop);
                      }
                  });
              }, self.timeout);
  
              break;
          case "fake":
          case "http":
          case "https":
          default:
              throw new Error("this transport protocol is currently not supported :" + self.protocol);
              return null;
      }    
  };
  
  function dumpChannelInfo(channels) {
  
      function dumpChannel(channel) {
  
          console.log("------------------------------------------------------");
          console.log("      secureChannelId = ", channel.secureChannelId);
          console.log("             timeout  = ", channel.timeout);
          console.log("        remoteAddress = ", channel.remoteAddress);
          console.log("        remotePort    = ", channel.remotePort);
          console.log("");
          console.log("        bytesWritten  = ", channel.bytesWritten);
          console.log("        bytesRead     = ", channel.bytesRead);
          
          var socket = channel.transport._socket;
          if (!socket) {
              console.log(" SOCKET IS CLOSED");
          } else {
          }
          //xx channel._dump_transaction_statistics();
      }
  
      _.forEach(channels, dumpChannel);
  }
  
  /**
   * @method _prevent_DOS_Attack
   * @async
   * @param self
   * @param establish_connection
   * @private
   */
  function _prevent_DOS_Attack(self, establish_connection) {
  
      var nbConnections = self.activeChannelCount;
  
      if (nbConnections >= self.maxConnections) {
          // istanbul ignore next
          if (doDebug) {
              console.log(" PREVENTING DOS ATTACK => Closing unused channels".bgRed.white);
          }
          var unused_channels = _.filter(self._channels, function (channel) {
              return !channel.isOpened && !channel.hasSession;
          });
          if (unused_channels.length === 0) {
              // all channels are in used , we cannot get any
  
              // istanbul ignore next
              if (doDebug) {
                  console.log("  - all channel are used !!!!");
                  dumpChannelInfo(self._channels);
              }
              setImmediate(establish_connection);
              return;
          }
          // istanbul ignore next
          if (doDebug) {
              console.log("   - Unused channels that can be clobbered", _.map(unused_channels, function (channel) {
                  return channel.hashKey;
              }).join(" "));
          }
          var channel = unused_channels[0];
          channel.close(function () {
              // istanbul ignore next
              if (doDebug) {
                  console.log("   _ Unused channel has been closed ", channel.hashKey);
              }
              self._unregisterChannel(channel);
  
              establish_connection();
          });
      } else {
          setImmediate(establish_connection);
      }
  }
  OPCUAServerEndPoint.prototype._on_client_connection = function (socket, isPassive) {
  
      // a client is attempting a connection on the socket
      var self = this;
  
      if(self.protocol === "opc.tcp"){
          socket.setNoDelay(true);
      }
      debugLog("this: "+JSON.stringify(this.protocol));
      debugLog("OPCUAServerEndPoint#_on_client_connection", self._started);
      if (!self._started) {
          debugLog("OPCUAServerEndPoint#_on_client_connection SERVER END POINT IS PROBABLY SHUTTING DOWN !!! - Connection is refused".bgWhite.cyan);
          
          switch (self.protocol) {
              case "opc.tcp":
                  socket.end();
              break;
              case "opc.wss":
                  socket.close();
              break;
              case "fake":
              case "http":
              case "https":
              default:
                  throw new Error("this transport protocol is currently not supported :" + self.protocol);
                  return;
          }   
          return;
      }
  
      // Each SecureChannel exists until it is explicitly closed or until the last token has expired and the overlap
      // period has elapsed. A Server application should limit the number of SecureChannels.
      // To protect against misbehaving Clients and denial of service attacks, the Server shall close the oldest
      // SecureChannel that has no Session assigned before reaching the maximum number of supported SecureChannels.
      _prevent_DOS_Attack(self, establish_connection);
  
      function establish_connection() {
  
          var nbConnections = Object.keys(self._channels).length;
  
          switch (self.protocol) {
              case "opc.tcp":
                  debugLog(" nbConnections ", nbConnections, " self._server.maxConnections", self._server.maxConnections, self.maxConnections);
              break;
              case "opc.wss":
              debugLog(" nbConnections ", nbConnections, " self._server.maxConnections", self._server._server.maxConnections, self.maxConnections);
              break;
              case "fake":
              case "http":
              case "https":
              default:
                  throw new Error("this transport protocol is currently not supported :" + self.protocol);
                  return;
          }
          
          if (nbConnections >= self.maxConnections) {
              debugLog("OPCUAServerEndPoint#_on_client_connection The maximum number of connection has been reached - Connection is refused".bgWhite.bold.cyan);
              switch (self.protocol) {
                  case "opc.tcp":
                      socket.end();
                  break;
                  case "opc.wss":
                      socket.close();
                  break;
                  case "fake":
                  case "http":
                  case "https":
                  default:
                      throw new Error("this transport protocol is currently not supported :" + self.protocol);
                      return;
              }   
              return;
          }
  
          debugLog("OPCUAServerEndPoint._on_client_connection successful => New Channel");
  
          var channel = new ServerSecureChannelLayer({
              parent: self,
              timeout: self.timeout,
              defaultSecureTokenLifetime: self.defaultSecureTokenLifetime,
              objectFactory: self.objectFactory,
              isPassive: isPassive || false
          });
  
          channel.init(socket, function (err) {
              if (err) {
                  switch (self.protocol) {
                      case "opc.tcp":
                          socket.end();
                      break;
                      case "opc.wss":
                          socket.close();
                      break;
                      case "fake":
                      case "http":
                      case "https":
                      default:
                          throw new Error("this transport protocol is currently not supported :" + self.protocol);
                          return;
                  }   
              } else {
                  self._registerChannel(channel);
                  debugLog("server receiving a client connection");
              }
          });
  
          channel.on("message", function (message) {
              // forward
              self.emit("message", message, channel, self);
          });
      }
  };
  
  /**
   * @method getCertificate
   * Returns the X509 DER form of the server certificate
   * @return {Buffer}
   */
  OPCUAServerEndPoint.prototype.getCertificate1 = function () {
      return split_der(this._certificateChain)[0];
  };
  /**
   * @method getCertificateChain
   * Returns the X509 DER form of the server certificate
   * @return {Buffer}
   */
  OPCUAServerEndPoint.prototype.getCertificateChain = function () {
      return this._certificateChain;
  };
  /**
   *
   * @method getPrivateKey
   * @return {Buffer} the privateKey
   */
  OPCUAServerEndPoint.prototype.getPrivateKey = function () {
      return this._privateKey;
  };
  
  /**
   * The number of active channel on this end point.
   * @property currentChannelCount
   * @type {Number}
   */
  OPCUAServerEndPoint.prototype.__defineGetter__("currentChannelCount", function () {
      return Object.keys(this._channels).length;
  });
  
  var SecurityPolicy = require("node-opcua-secure-channel").SecurityPolicy;
  
  
  var get_fully_qualified_domain_name = require("node-opcua-hostname").get_fully_qualified_domain_name;
  
  var default_transportProfileUri = "http://opcfoundation.org/UA-Profile/Transport/uatcp-uasc-uabinary";
  
  /**
   * @method _makeEndpointDescription
   * @param options.port
   * @param options.protocol
   * @param options.serverCertificate
   * @param options.securityMode
   * @param options.securityPolicy
   * @param options.securityLevel              {Number}
   * @param options.server.applicationUri      {String}
   * @param options.server.productUri          {String}
   * @param options.server.applicationName     {LocalizedText} // {text: "SampleServer", locale: null},
   * @param options.server.applicationType     {ApplicationType}
   * @param options.server.gatewayServerUri    {String}
   * @param options.server.discoveryProfileUri {String}
   * @param options.server.discoveryUrls       {String}
   * @param [options.resourcePath=""]          {String} resource Path is a string added at the end of the url such as "/UA/Server"
   * @param [options.hostname=get_fully_qualified_domain_name()] {string} default hostname
   * @return {EndpointDescription}
   * @private
   */
  function _makeEndpointDescription(options) {
  
      assert(_.isFinite(options.port), "expecting a valid port number");
      assert(options.hasOwnProperty("serverCertificateChain"));
      assert(!options.hasOwnProperty("serverCertificate"));
      assert(options.securityMode); // s.MessageSecurityMode
      assert(options.securityPolicy);
      assert(_.isObject(options.securityPolicy));
      assert(_.isObject(options.server));
      assert(options.hostname && (typeof options.hostname === "string"));
      assert(_.isBoolean(options.restricted));
      assert(options.hasOwnProperty("protocol"));
  
      options.securityLevel = (options.securityLevel === undefined) ? 3 : options.securityLevel;
      assert(_.isFinite(options.securityLevel), "expecting a valid securityLevel");
  
      var securityPolicyUri = toURI(options.securityPolicy);
  
      // resource Path is a string added at the end of the url such as "/UA/Server"
      var resourcePath = options.resourcePath || "";
  
      var userIdentityTokens = [];
  
      if (options.securityPolicy === SecurityPolicy.None) {
  
          userIdentityTokens.push({
              policyId: "username_basic256",
              tokenType: UserIdentityTokenType.USERNAME,
              issuedTokenType: null,
              issuerEndpointUrl: null,
              securityPolicyUri: SecurityPolicy.Basic256.value
          });
  
          userIdentityTokens.push({
              policyId: "username_basic128",
              tokenType: UserIdentityTokenType.USERNAME,
              issuedTokenType: null,
              issuerEndpointUrl: null,
              securityPolicyUri: SecurityPolicy.Basic128Rsa15.value
          });
  
          userIdentityTokens.push({
              policyId: "username_basic256Sha256",
              tokenType: UserIdentityTokenType.USERNAME,
              issuedTokenType: null,
              issuerEndpointUrl: null,
              securityPolicyUri: SecurityPolicy.Basic256Sha256.value
          });
  
      } else {
          // note:
          //  when channel session security is not NONE,
          //  userIdentityTokens can be left to null.
          //  in this case this mean that secure policy will be the same as connection security policy
          userIdentityTokens.push({
              policyId: "usernamePassword",
              tokenType: UserIdentityTokenType.USERNAME,
              issuedTokenType: null,
              issuerEndpointUrl: null,
              securityPolicyUri: null
          });
      }
  
      if (options.allowAnonymous) {
  
          userIdentityTokens.push({
              policyId: "anonymous",
              tokenType: UserIdentityTokenType.ANONYMOUS,
              issuedTokenType: null,
              issuerEndpointUrl: null,
              securityPolicyUri: null
          });
      }
  
      switch (options.protocol) {
          case "opc.tcp":
              var endpointUrl = "opc.tcp://" + options.hostname + ":" + path.join("" + options.port, resourcePath).replace(/\\/g, "/");
          break;
          case "opc.wss":
              var endpointUrl = "opc.wss://" + options.hostname + ":" + path.join("" + options.port, resourcePath).replace(/\\/g, "/");
          break;
          case "fake":
          case "http":
          case "https":
          default:
              throw new Error("this transport protocol is currently not supported :" + options.protocol);
              return;
      }   
      
      // return the endpoint object
      var endpoint = new EndpointDescription({
  
          endpointUrl: endpointUrl,
          server: options.server,
          serverCertificate: options.serverCertificateChain,
          securityMode: options.securityMode,
          securityPolicyUri: securityPolicyUri,
          userIdentityTokens: userIdentityTokens,
          transportProfileUri: default_transportProfileUri,
          securityLevel: options.securityLevel
      });
  
      endpoint.restricted = options.restricted;
  
      return endpoint;
  
  }
  
  /**
   * @method matching_endpoint
   * @param endpoint
   * @param securityMode
   * @param securityPolicy
   * @return {Boolean}
   *
   */
  function matching_endpoint(securityMode, securityPolicy, endpoint) {
  
      assert(endpoint instanceof EndpointDescription);
      assert(_.isObject(securityMode));
      assert(_.isObject(securityPolicy));
      var endpoint_securityPolicy = fromURI(endpoint.securityPolicyUri);
      return (endpoint.securityMode.value === securityMode.value && endpoint_securityPolicy.value === securityPolicy.value);
  }
  
  /**
   * @method getEndpointDescription
   * @param securityMode
   * @param securityPolicy
   * @return endpoint_description {EndpointDescription|null}
   */
  OPCUAServerEndPoint.prototype.getEndpointDescription = function (securityMode, securityPolicy) {
  
      var self = this;
      var endpoints = self.endpointDescriptions();
      var arr = _.filter(endpoints, matching_endpoint.bind(this, securityMode, securityPolicy));
      assert(arr.length === 0 || arr.length === 1);
      return arr.length === 0 ? null : arr[0];
  };
  
  
  OPCUAServerEndPoint.prototype.addEndpointDescription = function (securityMode, securityPolicy, options) {
  
      var self = this;
  
      options = options || {};
      options.allowAnonymous = (options.allowAnonymous === undefined) ? true : options.allowAnonymous;
  
      assert(_.isObject(securityMode));
      assert(_.isObject(securityPolicy));
  
      if (securityPolicy !== SecurityPolicy.None) {
      }
      if (securityMode === MessageSecurityMode.NONE && securityPolicy !== SecurityPolicy.None) {
          throw new Error(" invalid security ");
      }
      if (securityMode !== MessageSecurityMode.NONE && securityPolicy === SecurityPolicy.None) {
          throw new Error(" invalid security ");
      }
      //
      var endpoint_desc = self.getEndpointDescription(securityMode, securityPolicy);
      if (endpoint_desc) {
          throw new Error(" endpoint already exist");
      }
      var port = self.port;
  
      options.hostname = options.hostname || get_fully_qualified_domain_name();
  
      self._endpoints.push(_makeEndpointDescription({
          port: port,
          protocol: self.protocol,
          server: self.serverInfo,
          serverCertificateChain: self.getCertificateChain(),
          securityMode: securityMode,
          securityPolicy: securityPolicy,
          allowAnonymous: options.allowAnonymous,
          resourcePath: options.resourcePath,
          hostname: options.hostname,
          restricted: !!options.restricted
      }));
  };
  
  OPCUAServerEndPoint.prototype.addRestrictedEndpointDescription = function (options) {
      var self = this;
      options = _.clone(options);
      options.restricted = true;
      return self.addEndpointDescription(MessageSecurityMode.NONE, SecurityPolicy.None, options);
  };
  
  var defaultSecurityModes = [
      MessageSecurityMode.NONE,
      MessageSecurityMode.SIGN,
      MessageSecurityMode.SIGNANDENCRYPT
  ];
  var defaultSecurityPolicies = [
      SecurityPolicy.Basic128Rsa15,
      SecurityPolicy.Basic256,
  //xx UNUSED!!    SecurityPolicy.Basic256Rsa15,
      SecurityPolicy.Basic256Sha256
  ];
  
  OPCUAServerEndPoint.prototype.addStandardEndpointDescriptions = function (options) {
  
      var self = this;
  
      options = options || {};
  
      options.securityModes = options.securityModes || defaultSecurityModes;
      options.securityPolicies = options.securityPolicies || defaultSecurityPolicies;
  
      if (options.securityModes.indexOf(MessageSecurityMode.NONE) >= 0) {
          self.addEndpointDescription(MessageSecurityMode.NONE, SecurityPolicy.None, options);
      } else {
          if (!options.disableDiscovery) {
              self.addRestrictedEndpointDescription(options);
          }
      }
  
  
          for (var i = 0; i < options.securityModes.length; i++) {
              var securityMode = options.securityModes[i];
              if (securityMode === MessageSecurityMode.NONE) {
                  continue;
              }
  
              for (var j = 0; j < options.securityPolicies.length; j++) {
                  var securityPolicy = options.securityPolicies[j];
                  if (securityPolicy === SecurityPolicy.None) {
                      continue;
                  }
                  self.addEndpointDescription(securityMode, securityPolicy, options);
              }
          }
  };
  
  /**
   * returns the list of end point descriptions.
   * @method endpointDescriptions
   * @return {Array}
   */
  OPCUAServerEndPoint.prototype.endpointDescriptions = function () {
      var self = this;
      return self._endpoints;
  };
  
  
  /**
   * @method _registerChannel
   * @param channel
   * @private
   */
  OPCUAServerEndPoint.prototype._registerChannel = function (channel) {
  
      var self = this;
  
  
      if (self._started) {
  
          debugLog("_registerChannel = ".red, "channel.hashKey = ", channel.hashKey);
  
          assert(self._channels[channel.hashKey] === undefined);
          self._channels[channel.hashKey] = channel;
  
          channel._rememberClientAddressAndPort();
          /**
           * @event newChannel
           * @param channel
           */
          self.emit("newChannel", channel);
  
          channel.on("abort", function () {
              self._unregisterChannel(channel);
          });
  
      } else {
          debugLog("OPCUAServerEndPoint#_registerChannel called when end point is shutdown !");
          debugLog("  -> channel will be forcefully terminated");
          channel.close();
      }
  };
  
  /**
   * @method _unregisterChannel
   * @param channel
   * @private
   */
  OPCUAServerEndPoint.prototype._unregisterChannel = function (channel) {
  
      var self = this;
      debugLog("_un-registerChannel channel.hashKey", channel.hashKey);
  
      if (!self._channels.hasOwnProperty(channel.hashKey)) {
          return;
      }
  
      assert(self._channels.hasOwnProperty(channel.hashKey), "channel is not registered");
  
      /**
       * @event closeChannel
       * @param channel
       */
      self.emit("closeChannel", channel);
  
      // keep trace of statistics data from old channel for our own accumulated stats.
      self.bytesWrittenInOldChannels += channel.bytesWritten;
      self.bytesReadInOldChannels += channel.bytesRead;
      self.transactionsCountOldChannels += channel.transactionsCount;
      delete self._channels[channel.hashKey];
  
      //istanbul ignore next
      if (doDebug) {
          self._dump_statistics();
          debugLog("un-registering channel  - Count = ", self.currentChannelCount);
      }
  };
  
  OPCUAServerEndPoint.prototype._end_listen = function (err) {
  
      var self = this;
      assert(_.isFunction(self._listen_callback));
      self._listen_callback(err);
      self._listen_callback = null;
  };
  
  /**
   * @method listen
   * @async
   */
  OPCUAServerEndPoint.prototype.listen = function (callback) {
  
      debugLog("listen!!");
      var self = this;
      assert(_.isFunction(callback));
      assert(!self._started, "OPCUAServerEndPoint is already listening");
  
      self._listen_callback = callback;
  
      self._server.on("error", function (err) {
          debugLog(" error".red.bold + " port = " + self.port, err);
          self._started = false;
          self._end_listen(err);
      });
      self._server.on("listening",function() {
      });
  
      switch (self.protocol) {
          case "opc.tcp":
              self._server.listen(self.port, "::", function (err) { //'listening' listener
                  debugLog("TCP LISTENING TO PORT ".green.bold, self.port, "err  ", err);
                  assert(!err, " cannot listen to port ");
                  self._started = true;
                  self._end_listen();
              });
          break;
          case "opc.wss":
              self._server._server.listen(self.port, "::", function (err) { //'listening' listener
                  debugLog("WSS LISTENING TO PORT ".green.bold, self.port, "err  ", err);
                  assert(!err, " cannot listen to port ");
                  self._started = true;
                  self._end_listen();
              });
          break;
          case "fake":
          case "http":
          case "https":
          default:
              throw new Error("this transport protocol is currently not supported :" + self.protocol);
              return;
      }      
  };
  
  OPCUAServerEndPoint.prototype.killClientSockets = function (callback) {
  
      var self = this;
      var chnls = _.values(this._channels);
      chnls.forEach(function(channel){
          if (channel._transport && channel._transport._socket) {
              switch (self.protocol) {
                  case "opc.tcp":
                  channel._transport._socket.close();
                  channel._transport._socket.destroy();
                  break;
                  case "opc.wss":
                  channel._transport._socket.close();
                  channel._transport._socket.terminate();
                  break;
                  case "fake":
                  case "http":
                  case "https":
                  default:
                      throw new Error("this transport protocol is currently not supported :" + self.protocol);
                      return;
              }  
              
              channel._transport._socket.emit("error", new Error("EPIPE"));
          }
  
      });
      callback();
  };
  
  OPCUAServerEndPoint.prototype.suspendConnection = function (callback) {
  
      var self = this;
      assert(self._started);
      self._server.close(function () {
  //xx        assert(self._started);
          self._started = false;
      });
      self._started = false;
      callback();
  };
  
  OPCUAServerEndPoint.prototype.restoreConnection = function (callback) {
      var self = this;
      self.listen(callback);
  };
  
  /**
   * @method shutdown_channel
   * @param channel
   * @param inner_callback
   */
  function shutdown_channel(channel, inner_callback) {
      var self = this;
      assert(_.isFunction(inner_callback));
  
      channel.on("close", function () {
          //xx console.log(" ON CLOSED !!!!");
       });
  
      channel.close(function () {
          inner_callback();
          self._unregisterChannel(channel);
      });
  }
  
  /**
   * @method shutdown
   * @async
   * @param callback {Function}
   */
  OPCUAServerEndPoint.prototype.shutdown = function (callback) {
      var self = this;
  
      if (self._started) {
          // make sure we don't accept new connection any more ...
          self.suspendConnection(function () {
              // shutdown all opened channels ...
              var _channels = _.values(self._channels);
              async.each(_channels, shutdown_channel.bind(self), function (err) {
                  assert(Object.keys(self._channels).length === 0, "channel must have unregistered themselves");
                  callback(err);
              });
          });
      } else {
          callback();
      }
  };
  
  /**
   * @method start
   * @async
   * @param callback {Function}
   */
  OPCUAServerEndPoint.prototype.start = function (callback) {
      assert(_.isFunction(callback));
  
      var self=this;
      switch (self.protocol) {
          case "opc.tcp":
              this.listen(callback);
              break;
          case "opc.wss":
              debugLog("WSS endpoint started!");
              this.listen(callback);
              //callback();
              break;
          case "fake":
          case "http":
          case "https":
          default:
              throw new Error("this transport protocol is currently not supported :" + self.protocol);
              return;
      }   
      
  };
  
  OPCUAServerEndPoint.prototype.__defineGetter__("bytesWritten", function () {
      var chnls = _.values(this._channels);
      return this.bytesWrittenInOldChannels + chnls.reduce(
        function (accumulated, channel) {
            return accumulated + channel.bytesWritten;
        }, 0);
  });
  
  OPCUAServerEndPoint.prototype.__defineGetter__("bytesRead", function () {
      var chnls = _.values(this._channels);
      return this.bytesReadInOldChannels + chnls.reduce(
        function (accumulated, channel) {
            return accumulated + channel.bytesRead;
        }, 0);
  });
  
  OPCUAServerEndPoint.prototype.__defineGetter__("transactionsCount", function () {
      var chnls = _.values(this._channels);
      return this.transactionsCountOldChannels + chnls.reduce(
        function (accumulated, channel) {
            return accumulated + channel.transactionsCount;
        }, 0);
  });
  
  OPCUAServerEndPoint.prototype.__defineGetter__("securityTokenCount", function () {
      var chnls = _.values(this._channels);
      return this.securityTokenCountOldChannels + chnls.reduce(
        function (accumulated, channel) {
            return accumulated + channel.securityTokenCount;
        }, 0);
  });
  
  OPCUAServerEndPoint.prototype.__defineGetter__("activeChannelCount", function () {
      var self = this;
      return Object.keys(self._channels).length;
  });
  
  exports.OPCUAServerEndPoint = OPCUAServerEndPoint;
  