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-2012 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.Generic;
  using System.Collections.Specialized;
  using System.Diagnostics;
  using System.IO;
  using System.Net.Sockets;
  using System.Security.Cryptography;
  using System.Text;
  using System.Threading;
  using WebSocketSharp.Frame;
  using WebSocketSharp.Net;
  using WebSocketSharp.Net.Sockets;
  
  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 SynchronizedCollection<WsFrame> _unTransmittedBuffer;
          private WsStream _wsStream;
          private string _origin;
  
          #endregion
  
          #region Private Constructor
  
          private WebSocket()
          {
              _extensions = String.Empty;
              _forClose = new Object();
              _forSend = new Object();
              _protocol = String.Empty;
              _readyState = WsState.CONNECTING;
              _unTransmittedBuffer = new SynchronizedCollection<WsFrame>();
          }
  
          #endregion
  
          #region Internal Constructor
  
          internal WebSocket(HttpListenerWebSocketContext context)
              : this()
          {
              _uri = Ext.ToUri(context.Path);
              _context = context;
              _httpContext = context.BaseContext;
              _wsStream = context.Stream;
              _endPoint = context.ServerEndPoint;
              _isClient = false;
              _isSecure = context.IsSecureConnection;
          }
  
          internal WebSocket(TcpListenerWebSocketContext context)
              : this()
          {
              _uri = Ext.ToUri(context.Path);
              _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 == null)
                  throw new ArgumentNullException("url");
  
              Uri uri;
              string msg;
              if (!tryCreateUri(url, out uri, out msg))
                  throw new ArgumentException(msg, "url");
  
              _uri = uri;
              _protocols = Ext.ToString(protocols, ", ");
              _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 => _queryString;
  
          #endregion
  
          #region Public Properties
  
          /// <summary>
          /// Gets the amount of untransmitted data.
          /// </summary>
          /// <value>
          /// The number of bytes of untransmitted data.
          /// </value>
          public ulong BufferedAmount
          {
              get
              {
                  lock (_unTransmittedBuffer.SyncRoot)
                  {
                      ulong bufferedAmount = 0;
                      foreach (WsFrame frame in _unTransmittedBuffer)
                          bufferedAmount += frame.PayloadLength;
  
                      return bufferedAmount;
                  }
              }
          }
  
          /// <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 => _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 => _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 => _protocol;
  
          /// <summary>
          /// Gets the state of the connection.
          /// </summary>
          /// <value>
          /// A <see cref="WebSocketSharp.WsState"/>. By default, <c>WsState.CONNECTING</c>.
          /// </value>
          public WsState ReadyState => _readyState;
  
          /// <summary>
          /// Gets the untransmitted WebSocket frames.
          /// </summary>
          /// <value>
          /// A <c>IList&lt;WsFrame&gt;</c> that contains the untransmitted WebSocket frames.
          /// </value>
          public IList<WsFrame> UnTransmittedBuffer => _unTransmittedBuffer;
  
          /// <summary>
          /// Gets or sets the WebSocket URL.
          /// </summary>
          /// <value>
          /// A <see cref="Uri"/> that contains the WebSocket URL.
          /// </value>
          public Uri Url
          {
              get { return _uri; }
              set
              {
                  if (_readyState == WsState.CONNECTING && !_isClient)
                      _uri = value;
              }
          }
  
          /// <summary>
          /// Gets or sets the WebSocket Origin.
          /// </summary>
          public string Origin
          {
              get { return this._origin; }
              set { this._origin = value; }
          }
  
          /// <summary>
          /// Gets or sets the extra WebSocket handshake headers.
          /// </summary>
          public IDictionary<string, string> ExtraHeaders { get; set; }
  
          #endregion
  
          #region Events
  
          /// <summary>
          /// Occurs when the WebSocket connection has been established.
          /// </summary>
          public event EventHandler OnOpen;
  
          /// <summary>
          /// Occurs when the WebSocket receives a data frame.
          /// </summary>
          public event EventHandler<MessageEventArgs> OnMessage;
  
          /// <summary>
          /// Occurs when the WebSocket gets an error.
          /// </summary>
          public event EventHandler<ErrorEventArgs> OnError;
  
          /// <summary>
          /// Occurs when the WebSocket receives a Close frame or the Close method is called.
          /// </summary>
          public event EventHandler<CloseEventArgs> OnClose;
  
          #endregion
  
          #region Private Methods
  
          // As Server
          private void acceptHandshake()
          {
              var req = receiveOpeningHandshake();
  
              string msg;
              if (!isValidRequest(req, out msg))
              {
                  onError(msg);
                  close(CloseStatusCode.HANDSHAKE_FAILURE, msg);
                  return;
              }
  
              sendResponseHandshake();
              onOpen();
          }
  
          private bool canSendAsCloseFrame(PayloadData data)
          {
              if (data.Length >= 2)
              {
                  var code = Ext.To<ushort>(Ext.SubArray(data.ToBytes(), 0, 2), ByteOrder.BIG);
                  if (code == (ushort)CloseStatusCode.NO_STATUS_CODE ||
                      code == (ushort)CloseStatusCode.ABNORMAL ||
                      code == (ushort)CloseStatusCode.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>(Ext.ToBytes(code, ByteOrder.BIG));
              if (!Ext.IsNullOrEmpty(reason))
              {
                  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 != null)
                  {
                      _httpContext.Response.Close();
                      _wsStream = null;
                      _httpContext = null;
                  }
  
                  if (_wsStream != null)
                  {
                      _wsStream.Dispose();
                      _wsStream = null;
                  }
  
                  if (_tcpClient != null)
                  {
                      _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 (!Ext.IsNullOrEmpty(_protocols))
                  req.AddHeader("Sec-WebSocket-Protocol", _protocols);
              req.AddHeader("Sec-WebSocket-Version", _version);
              if (!string.IsNullOrEmpty(this._origin))
                  req.AddHeader("Origin", this._origin);
              //extra headers
              if (this.ExtraHeaders != null)
                  foreach (var i in this.ExtraHeaders)
                      req.AddHeader(i.Key, i.Value);
  
              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.HANDSHAKE_FAILURE, 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 == null)
              {
                  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;
  
              if (port != 80)
              {
                  expectedHost1 += ":" + port;
                  expectedHost2 += ":" + port;
              }
  
              if (Ext.NotEqual(expectedHost1, value, false) &&
                 Ext.NotEqual(expectedHost2, value, false))
              {
                  message = "Invalid Host.";
                  return false;
              }
  
              message = String.Empty;
              return true;
          }
  
          // As Client
          private bool isValidResponse(ResponseHandshake response, out string message)
          {
              if (!response.IsWebSocketResponse)
              {
                  message = "Invalid WebSocket response.";
                  return false;
              }
  
              if (!response.HeaderExists("Sec-WebSocket-Accept", createResponseKey()))
              {
  
gt:                 message = "Invalid Sec-WebSocket-Accept.";