context: #region MIT License
  /*
   * WebSocket.cs
   *
   * A C# implementation of the WebSocket interface.
   * This code derived from WebSocket.java (http://github.com/adamac/Java-WebSocket-client).
   *
   * The MIT License
   *
   * Copyright (c) 2009 Adam MacBeth
   * Copyright (c) 2010-2013 sta.blockhead
   * 
   * Permission is hereby granted, free of charge, to any person obtaining a copy
   * of this software and associated documentation files (the "Software"), to deal
   * in the Software without restriction, including without limitation the rights
   * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
   * copies of the Software, and to permit persons to whom the Software is
   * furnished to do so, subject to the following conditions:
   *
   * The above copyright notice and this permission notice shall be included in
   * all copies or substantial portions of the Software.
   * 
   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
   * THE SOFTWARE.
   */
  #endregion
  
  using System;
  using System.Collections;
  using System.Collections.Generic;
  using System.Collections.Specialized;
  using System.Diagnostics;
  using System.IO;
  using System.Linq;
  using System.Net.Sockets;
  using System.Security.Cryptography;
  using System.Text;
  using System.Threading;
  using WebSocketSharp.Net;
  using WebSocketSharp.Net.WebSockets;
  
  namespace WebSocketSharp {
  
    /// <summary>
    /// Implements the WebSocket interface.
    /// </summary>
    /// <remarks>
    /// The WebSocket class provides methods and properties for two-way communication using the WebSocket protocol (RFC 6455).
    /// </remarks>
    public class WebSocket : IDisposable
    {
      #region Private Const Fields
  
      private const int    _fragmentLen = 1016; // Max value is int.MaxValue - 14.
      private const string _guid        = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
      private const string _version     = "13";
  
      #endregion
  
      #region Private Fields
  
      private string                _base64key;
      private HttpListenerContext   _httpContext;
      private WebSocketContext      _context;
      private System.Net.IPEndPoint _endPoint;
      private string                _extensions;
      private AutoResetEvent        _exitMessageLoop;
      private Object                _forClose;
      private Object                _forSend;
      private bool                  _isClient;
      private bool                  _isSecure;
      private string                _protocol;
      private string                _protocols;
      private NameValueCollection   _queryString;
      private volatile WsState      _readyState;
      private AutoResetEvent        _receivePong;
      private TcpClient             _tcpClient;
      private Uri                   _uri;
      private WsStream              _wsStream;
  
      #endregion
  
      #region Private Constructor
  
      private WebSocket()
      {
        _extensions = String.Empty;
        _forClose   = new Object();
        _forSend    = new Object();
        _protocol   = String.Empty;
        _readyState = WsState.CONNECTING;
      }
  
      #endregion
  
      #region Internal Constructors
  
      internal WebSocket(HttpListenerWebSocketContext context)
        : this()
      {
        _uri         = context.Path.ToUri();
        _context     = context;
        _httpContext = context.BaseContext;
        _wsStream    = context.Stream;
        _endPoint    = context.ServerEndPoint;
        _isClient    = false;
        _isSecure    = context.IsSecureConnection;
      }
  
      internal WebSocket(TcpListenerWebSocketContext context)
        : this()
      {
        _uri       = context.Path.ToUri();
        _context   = context;
        _tcpClient = context.Client;
        _wsStream  = context.Stream;
        _endPoint  = context.ServerEndPoint;
        _isClient  = false;
        _isSecure  = context.IsSecureConnection;
      }
  
      #endregion
  
      #region Public Constructors
  
      /// <summary>
      /// Initializes a new instance of the <see cref="WebSocketSharp.WebSocket"/> class with the specified WebSocket URL and subprotocols.
      /// </summary>
      /// <param name="url">
      /// A <see cref="string"/> that contains the WebSocket URL.
      /// </param>
      /// <param name="protocols">
      /// An array of <see cref="string"/> that contains the WebSocket subprotocols if any.
      /// </param>
      /// <exception cref="ArgumentNullException">
      /// <paramref name="url"/> is <see langword="null"/>.
      /// </exception>
      /// <exception cref="ArgumentException">
      /// <paramref name="url"/> is not valid WebSocket URL.
      /// </exception>
      public WebSocket(string url, params string[] protocols)
        : this()
      {
        if (url.IsNull())
          throw new ArgumentNullException("url");
  
        Uri    uri;
        string msg;
        if (!tryCreateUri(url, out uri, out msg))
          throw new ArgumentException(msg, "url");
  
        _uri       = uri;
        _protocols = protocols.ToString(", ");
        _base64key = createBase64Key();
        _isClient  = true;
        _isSecure  = uri.Scheme == "wss" ? true : false;
      }
  
      /// <summary>
      /// Initializes a new instance of the <see cref="WebSocketSharp.WebSocket"/> class with the specified WebSocket URL, OnOpen, OnMessage, OnError, OnClose event handlers and subprotocols.
      /// </summary>
      /// <param name="url">
      /// A <see cref="string"/> that contains the WebSocket URL.
      /// </param>
      /// <param name="onOpen">
      /// An OnOpen event handler.
      /// </param>
      /// <param name="onMessage">
      /// An OnMessage event handler.
      /// </param>
      /// <param name="onError">
      /// An OnError event handler.
      /// </param>
      /// <param name="onClose">
      /// An OnClose event handler.
      /// </param>
      /// <param name="protocols">
      /// An array of <see cref="string"/> that contains the WebSocket subprotocols if any.
      /// </param>
      /// <exception cref="ArgumentNullException">
      /// <paramref name="url"/> is <see langword="null"/>.
      /// </exception>
      /// <exception cref="ArgumentException">
      /// <paramref name="url"/> is not valid WebSocket URL.
      /// </exception>
      public WebSocket(
        string                         url,
        EventHandler                   onOpen,
        EventHandler<MessageEventArgs> onMessage,
        EventHandler<ErrorEventArgs>   onError,
        EventHandler<CloseEventArgs>   onClose,
        params string[]                protocols)
        : this(url, protocols)
      {
        OnOpen    = onOpen;
        OnMessage = onMessage;
        OnError   = onError;
        OnClose   = onClose;
  
        Connect();
      }
  
      #endregion
  
      #region Internal Property
  
      internal NameValueCollection QueryString {
        get {
          return _queryString;
        }
      }
  
      #endregion
  
      #region Public Properties
  
      /// <summary>
      /// Gets the extensions selected by the server.
      /// </summary>
      /// <value>
      /// A <see cref="string"/> that contains the extensions if any. By default, <c>String.Empty</c>. (Currently this will only ever be the <c>String.Empty</c>.)
      /// </value>
      public string Extensions {
        get {
          return _extensions;
        }
      }
  
      /// <summary>
      /// Gets a value indicating whether a connection is alive.
      /// </summary>
      /// <value>
      /// <c>true</c> if the connection is alive; otherwise, <c>false</c>.
      /// </value>
      public bool IsAlive {
        get {
          if (_readyState != WsState.OPEN)
            return false;
  
          return Ping();
        }
      }
  
      /// <summary>
      /// Gets a value indicating whether a connection is secure.
      /// </summary>
      /// <value>
      /// <c>true</c> if the connection is secure; otherwise, <c>false</c>.
      /// </value>
      public bool IsSecure {
        get {
          return _isSecure;
        }
      }
  
      /// <summary>
      /// Gets the subprotocol selected by the server.
      /// </summary>
      /// <value>
      /// A <see cref="string"/> that contains the subprotocol if any. By default, <c>String.Empty</c>.
      /// </value>
      public string Protocol {
        get {
          return _protocol;
        }
      }
  
      /// <summary>
      /// Gets the state of the connection.
      /// </summary>
      /// <value>
      /// One of the <see cref="WebSocketSharp.WsState"/>. By default, <c>WsState.CONNECTING</c>.
      /// </value>
      public WsState ReadyState {
        get {
          return _readyState;
        }
      }
  
      /// <summary>
      /// Gets the WebSocket URL.
      /// </summary>
      /// <value>
      /// A <see cref="Uri"/> that contains the WebSocket URL.
      /// </value>
      public Uri Url {
        get {
          return _uri;
        }
        internal set {
          if (_readyState == WsState.CONNECTING && !_isClient)
            _uri = value;
        }
      }
  
      #endregion
  
      #region Events
  
      /// <summary>
      /// Occurs when the WebSocket receives a Close frame or the Close method is called.
      /// </summary>
      public event EventHandler<CloseEventArgs> OnClose;
  
      /// <summary>
      /// Occurs when the WebSocket gets an error.
      /// </summary>
      public event EventHandler<ErrorEventArgs> OnError;
  
      /// <summary>
      /// Occurs when the WebSocket receives a data frame.
      /// </summary>
      public event EventHandler<MessageEventArgs> OnMessage;
  
      /// <summary>
      /// Occurs when the WebSocket connection has been established.
      /// </summary>
      public event EventHandler OnOpen;
  
      #endregion
  
      #region Private Methods
  
      // As Server
      private void acceptHandshake()
      {
        var req = receiveOpeningHandshake();
  
        string msg;
        if (!isValidRequest(req, out msg))
        {
          onError(msg);
          close(CloseStatusCode.ABNORMAL, msg);
          return;
        }
  
        sendResponseHandshake();
        onOpen();
      }
  
      private bool canSendAsCloseFrame(PayloadData data)
      {
        if (data.Length >= 2)
        {
          var code = data.ToBytes().SubArray(0, 2).To<ushort>(ByteOrder.BIG);
          if (code == (ushort)CloseStatusCode.NO_STATUS_CODE ||
              code == (ushort)CloseStatusCode.ABNORMAL       ||
              code == (ushort)CloseStatusCode.TLS_HANDSHAKE_FAILURE)
            return false;
        }
  
        return true;
      }
  
      private void close(HttpStatusCode code)
      {
        if (_readyState != WsState.CONNECTING || _isClient)
          return;
  
        sendResponseHandshake(code);
        closeConnection();
      }
  
      private void close(PayloadData data)
      {
        #if DEBUG
        Console.WriteLine("WS: Info@close: Current thread IsBackground ?: {0}", Thread.CurrentThread.IsBackground);
        #endif
        lock(_forClose)
        {
          // Whether the closing handshake has been started already ?
          if (_readyState == WsState.CLOSING ||
              _readyState == WsState.CLOSED)
            return;
  
          // Whether the closing handshake as server is started before the connection has been established ?
          if (_readyState == WsState.CONNECTING && !_isClient)
          {
            sendResponseHandshake(HttpStatusCode.BadRequest);
            onClose(new CloseEventArgs(data));
            
            return;
          }
  
          _readyState = WsState.CLOSING;
        }
  
        // Whether a close status code that must not be set for send is used ?
        if (!canSendAsCloseFrame(data))
        {
          onClose(new CloseEventArgs(data));
          return;
        }
  
        closeHandshake(data);
        #if DEBUG
        Console.WriteLine("WS: Info@close: Exits close method.");
        #endif
      }
  
      private void close(CloseStatusCode code, string reason)
      {
        close((ushort)code, reason);
      }
  
      private void close(ushort code, string reason)
      {
        var data = new List<byte>(code.ToBytes(ByteOrder.BIG));
        if (!reason.IsNullOrEmpty())
        {
          var buffer = Encoding.UTF8.GetBytes(reason);
          data.AddRange(buffer);
        }
  
        var payloadData = new PayloadData(data.ToArray());
        if (payloadData.Length > 125)
        {
          var msg = "A Close frame must have a payload length of 125 bytes or less.";
          onError(msg);
          return;
        }
  
        close(payloadData);
      }
  
      private bool closeConnection()
      {
        _readyState = WsState.CLOSED;
  
        try
        {
          if (!_httpContext.IsNull())
          {
            _httpContext.Response.Close();
            _wsStream    = null;
            _httpContext = null;
          }
  
          if (!_wsStream.IsNull())
          {
            _wsStream.Dispose();
            _wsStream = null;
          }
  
          if (!_tcpClient.IsNull())
          {
            _tcpClient.Close();
            _tcpClient = null;
          }
  
          return true;
        }
        catch (Exception ex)
        {
          onError(ex.Message);
          return false;
        }
      }
  
      private void closeHandshake(PayloadData data)
      {
        var args  = new CloseEventArgs(data);
        var frame = createFrame(Fin.FINAL, Opcode.CLOSE, data);
        send(frame);
        onClose(args);
      }
  
      // As Client
      private string createBase64Key()
      {
        var src  = new byte[16];
        var rand = new Random();
        rand.NextBytes(src);
  
        return Convert.ToBase64String(src);
      }
  
      // As Client
      private void createClientStream()
      {
        var host = _uri.DnsSafeHost;
        var port = _uri.Port > 0
                 ? _uri.Port
                 : _isSecure ? 443 : 80;
  
        _tcpClient = new TcpClient(host, port);
        _wsStream  = WsStream.CreateClientStream(_tcpClient, host, _isSecure);
      }
  
      private WsFrame createFrame(Fin fin, Opcode opcode, PayloadData payloadData)
      {
        return _isClient
               ? new WsFrame(fin, opcode, payloadData)
               : new WsFrame(fin, opcode, Mask.UNMASK, payloadData);
      }
  
      // As Client
      private RequestHandshake createOpeningHandshake()
      {
        var path = _uri.PathAndQuery;
        var host = _uri.DnsSafeHost;
        var port = ((System.Net.IPEndPoint)_tcpClient.Client.RemoteEndPoint).Port;
        if (port != 80)
          host += ":" + port;
  
        var req = new RequestHandshake(path);
        req.AddHeader("Host", host);
        req.AddHeader("Sec-WebSocket-Key", _base64key);
        if (!_protocols.IsNullOrEmpty())
          req.AddHeader("Sec-WebSocket-Protocol", _protocols);
        req.AddHeader("Sec-WebSocket-Version", _version);
  
        return req;
      }
  
      // As Server
      private ResponseHandshake createResponseHandshake()
      {
        var res = new ResponseHandshake();
        res.AddHeader("Sec-WebSocket-Accept", createResponseKey());
  
        return res;
      }
  
      // As Server
      private ResponseHandshake createResponseHandshake(HttpStatusCode code)
      {
        var res = ResponseHandshake.CreateCloseResponse(code);
        res.AddHeader("Sec-WebSocket-Version", _version);
  
        return res;
      }
  
      private string createResponseKey()
      {
        SHA1 sha1 = new SHA1CryptoServiceProvider();
        var  sb   = new StringBuilder(_base64key);
        sb.Append(_guid);
        var  src  = sha1.ComputeHash(Encoding.UTF8.GetBytes(sb.ToString()));
  
        return Convert.ToBase64String(src);
      }
  
      // As Client
      private void doHandshake()
      {
        var res = sendOpeningHandshake();
  
        string msg;
        if (!isValidResponse(res, out msg))
        {
          onError(msg);
          close(CloseStatusCode.ABNORMAL, msg);
          return;
        }
  
        onOpen();
      }
  
      private bool isValidCloseStatusCode(ushort code, out string message)
      {
        if (code < 1000)
        {
          message = "Close status codes in the range 0-999 are not used: " + code;
          return false;
        }
  
        if (code > 4999)
        {
          message = "Out of reserved close status code range: " + code;
          return false;
        }
  
        message = String.Empty;
        return true;
      }
  
      private bool isValidFrame(WsFrame frame)
      {
        if (frame.IsNull())
        {
          var msg = "The WebSocket frame can not be read from the network stream.";
          close(CloseStatusCode.ABNORMAL, msg);
  
          return false;
        }
  
        return true;
      }
  
      // As Server
      private bool isValidRequest(RequestHandshake request, out string message)
      {
        if (!request.IsWebSocketRequest)
        {
          message = "Invalid WebSocket request.";
          return false;
        }
  
        if (_uri.IsAbsoluteUri && !isValidRequestHost(request.Headers["Host"], out message))
          return false;
  
        if (!request.HeaderExists("Sec-WebSocket-Version", _version))
        {
          message = "Unsupported Sec-WebSocket-Version.";
          return false;
        }
  
        _base64key = request.Headers["Sec-WebSocket-Key"];
  
        if (request.HeaderExists("Sec-WebSocket-Protocol"))
          _protocols = request.Headers["Sec-WebSocket-Protocol"];
  
        if (request.HeaderExists("Sec-WebSocket-Extensions"))
          _extensions = request.Headers["Sec-WebSocket-Extensions"];
  
        _queryString = request.QueryString;
  
        message = String.Empty;
        return true;
      }
  
      // As Server
      private bool isValidRequestHost(string value, out string message)
      {
        var host    = _uri.DnsSafeHost;
        var type    = Uri.CheckHostName(host);
        var address = _endPoint.Address;
        var port    = _endPoint.Port;
  
        var expectedHost1 = host;
        var expectedHost2 = type == UriHostNameType.Dns
                          ? address.ToString()
                          : System.Net.Dns.GetHostEntry(address).HostName;
  
  
gt:       if (port != 80)