#region License
/*
 * WebSocket.cs
 *
 * This code is derived from WebSocket.java
 * (http://github.com/adamac/Java-WebSocket-client).
 *
 * The MIT License
 *
 * Copyright (c) 2009 Adam MacBeth
 * Copyright (c) 2010-2022 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

#region Contributors
/*
 * Contributors:
 * - Frank Razenberg <frank@zzattack.org>
 * - David Wood <dpwood@gmail.com>
 * - Liryna <liryna.stark@gmail.com>
 */
#endregion

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.IO;
using System.Net.Security;
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>
    ///   <para>
    ///   This class provides a set of methods and properties for two-way
    ///   communication using the WebSocket protocol.
    ///   </para>
    ///   <para>
    ///   The WebSocket protocol is defined in
    ///   <see href="http://tools.ietf.org/html/rfc6455">RFC 6455</see>.
    ///   </para>
    /// </remarks>
    public class WebSocket : IDisposable
    {
        #region Private Fields

        private AuthenticationChallenge _authChallenge;
        private string _base64Key;
        private bool _client;
        private Action _closeContext;
        private CompressionMethod _compression;
        private WebSocketContext _context;
        private CookieCollection _cookies;
        private NetworkCredential _credentials;
        private bool _emitOnPing;
        private bool _enableRedirection;
        private string _extensions;
        private bool _extensionsRequested;
        private object _forMessageEventQueue;
        private object _forPing;
        private object _forSend;
        private object _forState;
        private MemoryStream _fragmentsBuffer;
        private bool _fragmentsCompressed;
        private Opcode _fragmentsOpcode;
        private const string _guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
        private Func<WebSocketContext, string> _handshakeRequestChecker;
        private bool _ignoreExtensions;
        private bool _inContinuation;
        private volatile bool _inMessage;
        private volatile Logger _logger;
        private static readonly int _maxRetryCountForConnect;
        private Action<MessageEventArgs> _message;
        private Queue<MessageEventArgs> _messageEventQueue;
        private uint _nonceCount;
        private string _origin;
        private ManualResetEvent _pongReceived;
        private bool _preAuth;
        private string _protocol;
        private string[] _protocols;
        private bool _protocolsRequested;
        private NetworkCredential _proxyCredentials;
        private Uri _proxyUri;
        private volatile WebSocketState _readyState;
        private ManualResetEvent _receivingExited;
        private int _retryCountForConnect;
        private bool _secure;
        private ClientSslConfiguration _sslConfig;
        private Stream _stream;
        private TcpClient _tcpClient;
        private Uri _uri;
        private const string _version = "13";
        private TimeSpan _waitTime;

        #endregion

        #region Internal Fields

        /// <summary>
        /// Represents the empty array of <see cref="byte"/> used internally.
        /// </summary>
        internal static readonly byte[] EmptyBytes;

        /// <summary>
        /// Represents the length used to determine whether the data should
        /// be fragmented in sending.
        /// </summary>
        /// <remarks>
        ///   <para>
        ///   The data will be fragmented if its length is greater than
        ///   the value of this field.
        ///   </para>
        ///   <para>
        ///   If you would like to change the value, you must set it to
        ///   a value between 125 and <c>Int32.MaxValue - 14</c> inclusive.
        ///   </para>
        /// </remarks>
        internal static readonly int FragmentLength;

        /// <summary>
        /// Represents the random number generator used internally.
        /// </summary>
        internal static readonly RandomNumberGenerator RandomNumber;

        #endregion

        #region Static Constructor

        static WebSocket()
        {
            _maxRetryCountForConnect = 10;
            EmptyBytes = new byte[0];
            FragmentLength = 1016;
            RandomNumber = new RNGCryptoServiceProvider();
        }

        #endregion

        #region Internal Constructors

        // As server
        internal WebSocket(HttpListenerWebSocketContext context, string protocol)
        {
            _context = context;
            _protocol = protocol;

            _closeContext = context.Close;
            _logger = context.Log;
            _message = messages;
            _secure = context.IsSecureConnection;
            _stream = context.Stream;
            _waitTime = TimeSpan.FromSeconds(1);

            init();
        }

        // As server
        internal WebSocket(TcpListenerWebSocketContext context, string protocol)
        {
            _context = context;
            _protocol = protocol;

            _closeContext = context.Close;
            _logger = context.Log;
            _message = messages;
            _secure = context.IsSecureConnection;
            _stream = context.Stream;
            _waitTime = TimeSpan.FromSeconds(1);

            init();
        }

        #endregion

        #region Public Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="WebSocket"/> class with
        /// the specified URL and optionally subprotocols.
        /// </summary>
        /// <param name="url">
        ///   <para>
        ///   A <see cref="string"/> that specifies the URL to which to connect.
        ///   </para>
        ///   <para>
        ///   The scheme of the URL must be ws or wss.
        ///   </para>
        ///   <para>
        ///   The new instance uses a secure connection if the scheme is wss.
        ///   </para>
        /// </param>
        /// <param name="protocols">
        ///   <para>
        ///   An array of <see cref="string"/> that specifies the names of
        ///   the subprotocols if necessary.
        ///   </para>
        ///   <para>
        ///   Each value of the array must be a token defined in
        ///   <see href="http://tools.ietf.org/html/rfc2616#section-2.2">
        ///   RFC 2616</see>.
        ///   </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="url"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ArgumentException">
        ///   <para>
        ///   <paramref name="url"/> is an empty string.
        ///   </para>
        ///   <para>
        ///   -or-
        ///   </para>
        ///   <para>
        ///   <paramref name="url"/> is an invalid WebSocket URL string.
        ///   </para>
        ///   <para>
        ///   -or-
        ///   </para>
        ///   <para>
        ///   <paramref name="protocols"/> contains a value that is not a token.
        ///   </para>
        ///   <para>
        ///   -or-
        ///   </para>
        ///   <para>
        ///   <paramref name="protocols"/> contains a value twice.
        ///   </para>
        /// </exception>
        public WebSocket(string url, params string[] protocols)
        {
            if (url == null)
                throw new ArgumentNullException("url");

            if (url.Length == 0)
                throw new ArgumentException("An empty string.", "url");

            string msg;

            if (!url.TryCreateWebSocketUri(out _uri, out msg))
                throw new ArgumentException(msg, "url");

            if (protocols != null && protocols.Length > 0)
            {
                if (!checkProtocols(protocols, out msg))
                    throw new ArgumentException(msg, "protocols");

                _protocols = protocols;
            }

            _base64Key = CreateBase64Key();
            _client = true;
            _logger = new Logger();
            _message = messagec;
            _retryCountForConnect = -1;
            _secure = _uri.Scheme == "wss";
            _waitTime = TimeSpan.FromSeconds(5);

            init();
        }

        #endregion

        #region Internal Properties

        internal CookieCollection CookieCollection
        {
            get
            {
                return _cookies;
            }
        }

        // As server
        internal Func<WebSocketContext, string> CustomHandshakeRequestChecker
        {
            get
            {
                return _handshakeRequestChecker;
            }

            set
            {
                _handshakeRequestChecker = value;
            }
        }

        // As server
        internal bool IgnoreExtensions
        {
            get
            {
                return _ignoreExtensions;
            }

            set
            {
                _ignoreExtensions = value;
            }
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets or sets the compression method used to compress a message.
        /// </summary>
        /// <remarks>
        /// The set operation works if the current state of the interface is
        /// New or Closed.
        /// </remarks>
        /// <value>
        ///   <para>
        ///   One of the <see cref="CompressionMethod"/> enum values.
        ///   </para>
        ///   <para>
        ///   It indicates the compression method used to compress a message.
        ///   </para>
        ///   <para>
        ///   The default value is <see cref="CompressionMethod.None"/>.
        ///   </para>
        /// </value>
        /// <exception cref="InvalidOperationException">
        /// The set operation is not available if the interface is not for
        /// the client.
        /// </exception>
        public CompressionMethod Compression
        {
            get
            {
                return _compression;
            }

            set
            {
                if (!_client)
                {
                    var msg = "The interface is not for the client.";

                    throw new InvalidOperationException(msg);
                }

                lock (_forState)
                {
                    if (!canSet())
                        return;

                    _compression = value;
                }
            }
        }

        /// <summary>
        /// Gets the HTTP cookies included in the handshake request/response.
        /// </summary>
        /// <value>
        ///   <para>
        ///   An <see cref="T:System.Collections.Generic.IEnumerable{WebSocketSharp.Net.Cookie}"/>
        ///   instance.
        ///   </para>
        ///   <para>
        ///   It provides an enumerator which supports the iteration over
        ///   the collection of the cookies.
        ///   </para>
        /// </value>
        public IEnumerable<Cookie> Cookies
        {
            get
            {
                lock (_cookies.SyncRoot)
                {
                    foreach (Cookie cookie in _cookies)
                        yield return cookie;
                }
            }
        }

        /// <summary>
        /// Gets the credentials for the HTTP authentication (Basic/Digest).
        /// </summary>
        /// <value>
        ///   <para>
        ///   A <see cref="NetworkCredential"/> that represents the credentials
        ///   used to authenticate the client.
        ///   </para>
        ///   <para>
        ///   The default value is <see langword="null"/>.
        ///   </para>
        /// </value>
        public NetworkCredential Credentials
        {
            get
            {
                return _credentials;
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the message event is
        /// emitted when the interface receives a ping.
        /// </summary>
        /// <value>
        ///   <para>
        ///   <c>true</c> if the interface emits the message event when
        ///   receives a ping; otherwise, <c>false</c>.
        ///   </para>
        ///   <para>
        ///   The default value is <c>false</c>.
        ///   </para>
        /// </value>
        public bool EmitOnPing
        {
            get
            {
                return _emitOnPing;
            }

            set
            {
                _emitOnPing = value;
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the URL redirection for
        /// the handshake request is allowed.
        /// </summary>
        /// <remarks>
        /// The set operation works if the current state of the interface is
        /// New or Closed.
        /// </remarks>
        /// <value>
        ///   <para>
        ///   <c>true</c> if the interface allows the URL redirection for
        ///   the handshake request; otherwise, <c>false</c>.
        ///   </para>
        ///   <para>
        ///   The default value is <c>false</c>.
        ///   </para>
        /// </value>
        /// <exception cref="InvalidOperationException">
        /// The set operation is not available if the interface is not for
        /// the client.
        /// </exception>
        public bool EnableRedirection
        {
            get
            {
                return _enableRedirection;
            }

            set
            {
                if (!_client)
                {
                    var msg = "The interface is not for the client.";

                    throw new InvalidOperationException(msg);
                }

                lock (_forState)
                {
                    if (!canSet())
                        return;

                    _enableRedirection = value;
                }
            }
        }

        /// <summary>
        /// Gets the extensions selected by the server.
        /// </summary>
        /// <value>
        /// A <see cref="string"/> that will be a list of the extensions
        /// negotiated between the client and server, or an empty string
        /// if not specified or selected.
        /// </value>
        public string Extensions
        {
            get
            {
                return _extensions ?? String.Empty;
            }
        }

        /// <summary>
        /// Gets a value indicating whether the communication is possible.
        /// </summary>
        /// <value>
        /// <c>true</c> if the communication is possible; otherwise, <c>false</c>.
        /// </value>
        public bool IsAlive
        {
            get
            {
                return ping(EmptyBytes);
            }
        }

        /// <summary>
        /// Gets a value indicating whether the connection is secure.
        /// </summary>
        /// <value>
        /// <c>true</c> if the connection is secure; otherwise, <c>false</c>.
        /// </value>
        public bool IsSecure
        {
            get
            {
                return _secure;
            }
        }

        /// <summary>
        /// Gets the logging function.
        /// </summary>
        /// <remarks>
        /// The default logging level is <see cref="LogLevel.Error"/>.
        /// </remarks>
        /// <value>
        /// A <see cref="Logger"/> that provides the logging function.
        /// </value>
        public Logger Log
        {
            get
            {
                return _logger;
            }

            internal set
            {
                _logger = value;
            }
        }

        /// <summary>
        /// Gets or sets the value of the HTTP Origin header to send with
        /// the handshake request.
        /// </summary>
        /// <remarks>
        ///   <para>
        ///   The HTTP Origin header is defined in
        ///   <see href="http://tools.ietf.org/html/rfc6454#section-7">
        ///   Section 7 of RFC 6454</see>.
        ///   </para>
        ///   <para>
        ///   The interface sends the Origin header if this property has any.
        ///   </para>
        ///   <para>
        ///   The set operation works if the current state of the interface is
        ///   New or Closed.
        ///   </para>
        /// </remarks>
        /// <value>
        ///   <para>
        ///   A <see cref="string"/> that represents the value of the Origin
        ///   header to send.
        ///   </para>
        ///   <para>
        ///   The syntax is &lt;scheme&gt;://&lt;host&gt;[:&lt;port&gt;].
        ///   </para>
        ///   <para>
        ///   The default value is <see langword="null"/>.
        ///   </para>
        /// </value>
        /// <exception cref="InvalidOperationException">
        /// The set operation is not available if the interface is not for
        /// the client.
        /// </exception>
        /// <exception cref="ArgumentException">
        ///   <para>
        ///   The value specified for a set operation is not an absolute URI string.
        ///   </para>
        ///   <para>
        ///   -or-
        ///   </para>
        ///   <para>
        ///   The value specified for a set operation includes the path segments.
        ///   </para>
        /// </exception>
        public string Origin
        {
            get
            {
                return _origin;
            }

            set
            {
                if (!_client)
                {
                    var msg = "The interface is not for the client.";

                    throw new InvalidOperationException(msg);
                }

                if (!value.IsNullOrEmpty())
                {
                    Uri uri;

                    if (!Uri.TryCreate(value, UriKind.Absolute, out uri))
                    {
                        var msg = "Not an absolute URI string.";

                        throw new ArgumentException(msg, "value");
                    }

                    if (uri.Segments.Length > 1)
                    {
                        var msg = "It includes the path segments.";

                        throw new ArgumentException(msg, "value");
                    }
                }

                lock (_forState)
                {
                    if (!canSet())
                        return;

                    _origin = !value.IsNullOrEmpty() ? value.TrimEnd('/') : value;
                }
            }
        }

        /// <summary>
        /// Gets the name of subprotocol selected by the server.
        /// </summary>
        /// <value>
        ///   <para>
        ///   A <see cref="string"/> that will be one of the names of
        ///   subprotocols specified by client.
        ///   </para>
        ///   <para>
        ///   An empty string if not specified or selected.
        ///   </para>
        /// </value>
        public string Protocol
        {
            get
            {
                return _protocol ?? String.Empty;
            }

            internal set
            {
                _protocol = value;
            }
        }

        /// <summary>
        /// Gets the current state of the interface.
        /// </summary>
        /// <value>
        ///   <para>
        ///   One of the <see cref="WebSocketState"/> enum values.
        ///   </para>
        ///   <para>
        ///   It indicates the current state of the interface.
        ///   </para>
        ///   <para>
        ///   The default value is <see cref="WebSocketState.New"/>.
        ///   </para>
        /// </value>
        public WebSocketState ReadyState
        {
            get
            {
                return _readyState;
            }
        }

        /// <summary>
        /// Gets the configuration for secure connection.
        /// </summary>
        /// <remarks>
        /// The configuration is used when the interface attempts to connect,
        /// so it must be configured before any connect method is called.
        /// </remarks>
        /// <value>
        /// A <see cref="ClientSslConfiguration"/> that represents the
        /// configuration used to establish a secure connection.
        /// </value>
        /// <exception cref="InvalidOperationException">
        ///   <para>
        ///   The interface is not for the client.
        ///   </para>
        ///   <para>
        ///   -or-
        ///   </para>
        ///   <para>
        ///   The interface does not use a secure connection.
        ///   </para>
        /// </exception>
        public ClientSslConfiguration SslConfiguration
        {
            get
            {
                if (!_client)
                {
                    var msg = "The interface is not for the client.";

                    throw new InvalidOperationException(msg);
                }

                if (!_secure)
                {
                    var msg = "The interface does not use a secure connection.";

                    throw new InvalidOperationException(msg);
                }

                return getSslConfiguration();
            }
        }

        /// <summary>
        /// Gets the URL to which to connect.
        /// </summary>
        /// <value>
        ///   <para>
        ///   A <see cref="Uri"/> that represents the URL to which to connect.
        ///   </para>
        ///   <para>
        ///   Also it represents the URL requested by the client if the interface
        ///   is for the server.
        ///   </para>
        /// </value>
        public Uri Url
        {
            get
            {
                return _client ? _uri : _context.RequestUri;
            }
        }

        /// <summary>
        /// Gets or sets the time to wait for the response to the ping or close.
        /// </summary>
        /// <remarks>
        /// The set operation works if the current state of the interface is
        /// New or Closed.
        /// </remarks>
        /// <value>
        ///   <para>
        ///   A <see cref="TimeSpan"/> that represents the time to wait for
        ///   the response.
        ///   </para>
        ///   <para>
        ///   The default value is the same as 5 seconds if the interface is
        ///   for the client.
        ///   </para>
        /// </value>
        /// <exception cref="ArgumentOutOfRangeException">
        /// The value specified for a set operation is zero or less.
        /// </exception>
        public TimeSpan WaitTime
        {
            get
            {
                return _waitTime;
            }

            set
            {
                if (value <= TimeSpan.Zero)
                {
                    var msg = "Zero or less.";

                    throw new ArgumentOutOfRangeException("value", msg);
                }

                lock (_forState)
                {
                    if (!canSet())
                        return;

                    _waitTime = value;
                }
            }
        }

        #endregion

        #region Public Events

        /// <summary>
        /// Occurs when the connection has been closed.
        /// </summary>
        public event EventHandler<CloseEventArgs> OnClose;

        /// <summary>
        /// Occurs when the interface gets an error.
        /// </summary>
        public event EventHandler<ErrorEventArgs> OnError;

        /// <summary>
        /// Occurs when the interface receives a message.
        /// </summary>
        public event EventHandler<MessageEventArgs> OnMessage;

        /// <summary>
        /// Occurs when the connection has been established.
        /// </summary>
        public event EventHandler OnOpen;

        #endregion

        #region Private Methods

        private void abort(string reason, Exception exception)
        {
            var code = exception is WebSocketException
                       ? ((WebSocketException)exception).Code
                       : CloseStatusCode.Abnormal;

            abort((ushort)code, reason);
        }

        private void abort(ushort code, string reason)
        {
            var data = new PayloadData(code, reason);

            close(data, false, false);
        }

        // As server
        private bool accept()
        {
            lock (_forState)
            {
                if (_readyState == WebSocketState.Open)
                {
                    _logger.Trace("The connection has already been established.");

                    return false;
                }

                if (_readyState == WebSocketState.Closing)
                {
                    _logger.Error("The close process is in progress.");

                    error("An error has occurred while accepting.", null);

                    return false;
                }

                if (_readyState == WebSocketState.Closed)
                {
                    _logger.Error("The connection has been closed.");

                    error("An error has occurred while accepting.", null);

                    return false;
                }

                _readyState = WebSocketState.Connecting;

                var accepted = false;

                try
                {
                    accepted = acceptHandshake();
                }
                catch (Exception ex)
                {
                    _logger.Fatal(ex.Message);
                    _logger.Fatal(ex.ToString());
                    _logger.Debug(ex.ToString());

                    abort(1011, "An exception has occurred while accepting.");
                }

                if (!accepted)
                    return false;

                _readyState = WebSocketState.Open;

                return true;
            }
        }

        // As server
        private bool acceptHandshake()
        {
            string msg;

            if (!checkHandshakeRequest(_context, out msg))
            {
                _logger.Error(msg);
                _logger.Debug(_context.ToString());

                refuseHandshake(1002, "A handshake error has occurred.");

                return false;
            }

            if (!customCheckHandshakeRequest(_context, out msg))
            {
                _logger.Error(msg);
                _logger.Debug(_context.ToString());

                refuseHandshake(1002, "A handshake error has occurred.");

                return false;
            }

            _base64Key = _context.Headers["Sec-WebSocket-Key"];

            if (_protocol != null)
            {
                var vals = _context.SecWebSocketProtocols;

                processSecWebSocketProtocolClientHeader(vals);
            }

            if (!_ignoreExtensions)
            {
                var val = _context.Headers["Sec-WebSocket-Extensions"];

                processSecWebSocketExtensionsClientHeader(val);
            }

            var res = createHandshakeResponse();

            sendHttpResponse(res);

            return true;
        }

        private bool canSet()
        {
            return _readyState == WebSocketState.New
                   || _readyState == WebSocketState.Closed;
        }

        // As server
        private bool checkHandshakeRequest(
          WebSocketContext context, out string message
        )
        {
            message = null;

            if (!context.IsWebSocketRequest)
            {
                message = "Not a WebSocket handshake request.";

                return false;
            }

            if (context.RequestUri == null)
            {
                message = "The Request-URI is invalid.";

                return false;
            }

            var headers = context.Headers;

            var key = headers["Sec-WebSocket-Key"];

            if (key == null)
            {
                message = "The Sec-WebSocket-Key header is non-existent.";

                return false;
            }

            if (key.Length == 0)
            {
                message = "The Sec-WebSocket-Key header is invalid.";

                return false;
            }

            var ver = headers["Sec-WebSocket-Version"];

            if (ver == null)
            {
                message = "The Sec-WebSocket-Version header is non-existent.";

                return false;
            }

            if (ver != _version)
            {
                message = "The Sec-WebSocket-Version header is invalid.";

                return false;
            }

            var subps = headers["Sec-WebSocket-Protocol"];

            if (subps != null && subps.Length == 0)
            {
                message = "The Sec-WebSocket-Protocol header is invalid.";

                return false;
            }

            if (!_ignoreExtensions)
            {
                var exts = headers["Sec-WebSocket-Extensions"];

                if (exts != null && exts.Length == 0)
                {
                    message = "The Sec-WebSocket-Extensions header is invalid.";

                    return false;
                }
            }

            return true;
        }

        // As client
        private bool checkHandshakeResponse(
          HttpResponse response, out string message
        )
        {
            message = null;

            if (response.IsRedirect)
            {
                message = "The redirection is indicated.";

                return false;
            }

            if (response.IsUnauthorized)
            {
                message = "The authentication is required.";

                return false;
            }

            if (!response.IsWebSocketResponse)
            {
                message = "Not a WebSocket handshake response.";

                return false;
            }

            var headers = response.Headers;

            var key = headers["Sec-WebSocket-Accept"];

            if (key == null)
            {
                message = "The Sec-WebSocket-Accept header is non-existent.";

                return false;
            }

            if (key != CreateResponseKey(_base64Key))
            {
                message = "The Sec-WebSocket-Accept header is invalid.";

                return false;
            }

            var ver = headers["Sec-WebSocket-Version"];

            if (ver != null && ver != _version)
            {
                message = "The Sec-WebSocket-Version header is invalid.";

                return false;
            }

            var subp = headers["Sec-WebSocket-Protocol"];

            if (subp == null)
            {
                if (_protocolsRequested)
                {
                    message = "The Sec-WebSocket-Protocol header is non-existent.";

                    return false;
                }
            }
            else
            {
                var valid = subp.Length > 0
                            && _protocolsRequested
                            && _protocols.Contains(p => p == subp);

                if (!valid)
                {
                    message = "The Sec-WebSocket-Protocol header is invalid.";

                    return false;
                }
            }

            var exts = headers["Sec-WebSocket-Extensions"];

            if (!validateSecWebSocketExtensionsServerHeader(exts))
            {
                message = "The Sec-WebSocket-Extensions header is invalid.";

                return false;
            }

            return true;
        }

        private static bool checkProtocols(string[] protocols, out string message)
        {
            message = null;

            Func<string, bool> cond = protocol => protocol.IsNullOrEmpty()
                                                  || !protocol.IsToken();

            if (protocols.Contains(cond))
            {
                message = "It contains a value that is not a token.";
                return false;
            }

            if (protocols.ContainsTwice())
            {
                message = "It contains a value twice.";
                return false;
            }

            return true;
        }

        private bool checkReceivedFrame(WebSocketFrame frame, out string message)
        {
            message = null;

            var masked = frame.IsMasked;

            if (_client && masked)
            {
                message = "A frame from the server is masked.";

                return false;
            }

            if (!_client && !masked)
            {
                message = "A frame from a client is not masked.";

                return false;
            }

            if (_inContinuation && frame.IsData)
            {
                message = "A data frame was received while receiving continuation frames.";

                return false;
            }

            if (frame.IsCompressed && _compression == CompressionMethod.None)
            {
                message = "A compressed frame was received without any agreement for it.";

                return false;
            }

            if (frame.Rsv2 == Rsv.On)
            {
                message = "The RSV2 of a frame is non-zero without any negotiation for it.";

                return false;
            }

            if (frame.Rsv3 == Rsv.On)
            {
                message = "The RSV3 of a frame is non-zero without any negotiation for it.";

                return false;
            }

            return true;
        }

        private void close(ushort code, string reason)
        {
            if (_readyState == WebSocketState.Closing)
            {
                _logger.Trace("The close process is already in progress.");

                return;
            }

            if (_readyState == WebSocketState.Closed)
            {
                _logger.Trace("The connection has already been closed.");

                return;
            }

            if (code == 1005)
            {
                close(PayloadData.Empty, true, false);

                return;
            }

            var data = new PayloadData(code, reason);
            var send = !code.IsReserved();

            close(data, send, false);
        }

        private void close(PayloadData payloadData, bool send, bool received)
        {
            lock (_forState)
            {
                if (_readyState == WebSocketState.Closing)
                {
                    _logger.Trace("The close process is already in progress.");

                    return;
                }

                if (_readyState == WebSocketState.Closed)
                {
                    _logger.Trace("The connection has already been closed.");

                    return;
                }

                send = send && _readyState == WebSocketState.Open;

                _readyState = WebSocketState.Closing;
            }

            _logger.Trace("Begin closing the connection.");

            var res = closeHandshake(payloadData, send, received);

            releaseResources();

            _logger.Trace("End closing the connection.");

            _readyState = WebSocketState.Closed;

            var e = new CloseEventArgs(payloadData, res);

            try
            {
                OnClose.Emit(this, e);
            }
            catch (Exception ex)
            {
                _logger.Error(ex.Message);
                _logger.Debug(ex.ToString());
            }
        }

        private void closeAsync(ushort code, string reason)
        {
            if (_readyState == WebSocketState.Closing)
            {
                _logger.Trace("The close process is already in progress.");

                return;
            }

            if (_readyState == WebSocketState.Closed)
            {
                _logger.Trace("The connection has already been closed.");

                return;
            }

            if (code == 1005)
            {
                closeAsync(PayloadData.Empty, true, false);

                return;
            }

            var data = new PayloadData(code, reason);
            var send = !code.IsReserved();

            closeAsync(data, send, false);
        }

        private void closeAsync(PayloadData payloadData, bool send, bool received)
        {
            Action<PayloadData, bool, bool> closer = close;

            closer.BeginInvoke(
              payloadData, send, received, ar => closer.EndInvoke(ar), null
            );
        }

        private bool closeHandshake(
          PayloadData payloadData, bool send, bool received
        )
        {
            var sent = false;

            if (send)
            {
                var frame = WebSocketFrame.CreateCloseFrame(payloadData, _client);
                var bytes = frame.ToArray();

                sent = sendBytes(bytes);

                if (_client)
                    frame.Unmask();
            }

            var wait = !received && sent && _receivingExited != null;

            if (wait)
                received = _receivingExited.WaitOne(_waitTime);

            var ret = sent && received;

            var msg = String.Format(
                        "The closing was clean? {0} (sent: {1} received: {2})",
                        ret,
                        sent,
                        received
                      );

            _logger.Debug(msg);

            return ret;
        }

        // As client
        private bool connect()
        {
            lock (_forState)
            {
                if (_readyState == WebSocketState.Open)
                {
                    _logger.Trace("The connection has already been established.");

                    return false;
                }

                if (_readyState == WebSocketState.Closing)
                {
                    _logger.Error("The close process is in progress.");

                    error("An error has occurred while connecting.", null);

                    return false;
                }

                if (_retryCountForConnect >= _maxRetryCountForConnect)
                {
                    _logger.Error("An opportunity for reconnecting has been lost.");

                    error("An error has occurred while connecting.", null);

                    return false;
                }

                _retryCountForConnect++;

                _readyState = WebSocketState.Connecting;

                var done = false;

                try
                {
                    done = doHandshake();
                }
                catch (Exception ex)
                {
                    _logger.Fatal(ex.Message);
                    _logger.Fatal(ex.ToString());
                    _logger.Debug(ex.ToString());

                    abort("An exception has occurred while connecting.", ex);
                }

                if (!done)
                    return false;

                _retryCountForConnect = -1;

                _readyState = WebSocketState.Open;

                return true;
            }
        }

        // As client
        private AuthenticationResponse createAuthenticationResponse()
        {
            if (_credentials == null)
                return null;

            if (_authChallenge != null)
            {
                var ret = new AuthenticationResponse(
                            _authChallenge, _credentials, _nonceCount
                          );

                _nonceCount = ret.NonceCount;

                return ret;
            }

            return _preAuth ? new AuthenticationResponse(_credentials) : null;
        }

        // As client
        private string createExtensions()
        {
            var buff = new StringBuilder(80);

            if (_compression != CompressionMethod.None)
            {
                var str = _compression.ToExtensionString(
                            "server_no_context_takeover", "client_no_context_takeover"
                          );

                buff.AppendFormat("{0}, ", str);
            }

            var len = buff.Length;

            if (len <= 2)
                return null;

            buff.Length = len - 2;

            return buff.ToString();
        }

        // As server
        private HttpResponse createHandshakeFailureResponse()
        {
            var ret = HttpResponse.CreateCloseResponse(HttpStatusCode.BadRequest);

            ret.Headers["Sec-WebSocket-Version"] = _version;

            return ret;
        }

        // As client
        private HttpRequest createHandshakeRequest()
        {
            var ret = HttpRequest.CreateWebSocketHandshakeRequest(_uri);

            var headers = ret.Headers;

            headers["Sec-WebSocket-Key"] = _base64Key;
            headers["Sec-WebSocket-Version"] = _version;

            if (!_origin.IsNullOrEmpty())
                headers["Origin"] = _origin;

            if (_protocols != null)
            {
                headers["Sec-WebSocket-Protocol"] = _protocols.ToString(", ");

                _protocolsRequested = true;
            }

            var exts = createExtensions();

            if (exts != null)
            {
                headers["Sec-WebSocket-Extensions"] = exts;

                _extensionsRequested = true;
            }

            var ares = createAuthenticationResponse();

            if (ares != null)
                headers["Authorization"] = ares.ToString();

            if (_cookies.Count > 0)
                ret.SetCookies(_cookies);

            return ret;
        }

        // As server
        private HttpResponse createHandshakeResponse()
        {
            var ret = HttpResponse.CreateWebSocketHandshakeResponse();

            var headers = ret.Headers;

            headers["Sec-WebSocket-Accept"] = CreateResponseKey(_base64Key);

            if (_protocol != null)
                headers["Sec-WebSocket-Protocol"] = _protocol;

            if (_extensions != null)
                headers["Sec-WebSocket-Extensions"] = _extensions;

            if (_cookies.Count > 0)
                ret.SetCookies(_cookies);

            return ret;
        }

        // As server
        private bool customCheckHandshakeRequest(
          WebSocketContext context, out string message
        )
        {
            message = null;

            if (_handshakeRequestChecker == null)
                return true;

            message = _handshakeRequestChecker(context);

            return message == null;
        }

        private MessageEventArgs dequeueFromMessageEventQueue()
        {
            lock (_forMessageEventQueue)
            {
                return _messageEventQueue.Count > 0
                       ? _messageEventQueue.Dequeue()
                       : null;
            }
        }

        // As client
        private bool doHandshake()
        {
            setClientStream();

            var res = sendHandshakeRequest();

            string msg;

            if (!checkHandshakeResponse(res, out msg))
            {
                _logger.Error(msg);
                _logger.Debug(res.ToString());

                abort(1002, "A handshake error has occurred.");

                return false;
            }

            if (_protocolsRequested)
                _protocol = res.Headers["Sec-WebSocket-Protocol"];

            if (_extensionsRequested)
            {
                var val = res.Headers["Sec-WebSocket-Extensions"];

                processSecWebSocketExtensionsServerHeader(val);
            }

            processCookies(res.Cookies);

            return true;
        }

        private void enqueueToMessageEventQueue(MessageEventArgs e)
        {
            lock (_forMessageEventQueue)
                _messageEventQueue.Enqueue(e);
        }

        private void error(string message, Exception exception)
        {
            var e = new ErrorEventArgs(message, exception);

            try
            {
                OnError.Emit(this, e);
            }
            catch (Exception ex)
            {
                _logger.Error(ex.Message);
                _logger.Debug(ex.ToString());
            }
        }

        private ClientSslConfiguration getSslConfiguration()
        {
            if (_sslConfig == null)
                _sslConfig = new ClientSslConfiguration(_uri.DnsSafeHost);

            return _sslConfig;
        }

        private void init()
        {
            _compression = CompressionMethod.None;
            _cookies = new CookieCollection();
            _forPing = new object();
            _forSend = new object();
            _forState = new object();
            _messageEventQueue = new Queue<MessageEventArgs>();
            _forMessageEventQueue = ((ICollection)_messageEventQueue).SyncRoot;
            _readyState = WebSocketState.New;
        }

        private void message()
        {
            MessageEventArgs e = null;

            lock (_forMessageEventQueue)
            {
                if (_inMessage)
                    return;

                if (_messageEventQueue.Count == 0)
                    return;

                if (_readyState != WebSocketState.Open)
                    return;

                e = _messageEventQueue.Dequeue();

                _inMessage = true;
            }

            _message(e);
        }

        private void messagec(MessageEventArgs e)
        {
            do
            {
                try
                {
                    OnMessage.Emit(this, e);
                }
                catch (Exception ex)
                {
                    _logger.Error(ex.Message);
                    _logger.Debug(ex.ToString());

                    error("An exception has occurred during an OnMessage event.", ex);
                }

                lock (_forMessageEventQueue)
                {
                    if (_messageEventQueue.Count == 0)
                    {
                        _inMessage = false;

                        break;
                    }

                    if (_readyState != WebSocketState.Open)
                    {
                        _inMessage = false;

                        break;
                    }

                    e = _messageEventQueue.Dequeue();
                }
            }
            while (true);
        }

        private void messages(MessageEventArgs e)
        {
            try
            {
                OnMessage.Emit(this, e);
            }
            catch (Exception ex)
            {
                _logger.Error(ex.Message);
                _logger.Debug(ex.ToString());

                error("An exception has occurred during an OnMessage event.", ex);
            }

            lock (_forMessageEventQueue)
            {
                if (_messageEventQueue.Count == 0)
                {
                    _inMessage = false;

                    return;
                }

                if (_readyState != WebSocketState.Open)
                {
                    _inMessage = false;

                    return;
                }

                e = _messageEventQueue.Dequeue();
            }

            ThreadPool.QueueUserWorkItem(state => messages(e));
        }

        private void open()
        {
            _inMessage = true;

            startReceiving();

            try
            {
                OnOpen.Emit(this, EventArgs.Empty);
            }
            catch (Exception ex)
            {
                _logger.Error(ex.Message);
                _logger.Debug(ex.ToString());

                error("An exception has occurred during the OnOpen event.", ex);
            }

            MessageEventArgs e = null;

            lock (_forMessageEventQueue)
            {
                if (_messageEventQueue.Count == 0)
                {
                    _inMessage = false;

                    return;
                }

                if (_readyState != WebSocketState.Open)
                {
                    _inMessage = false;

                    return;
                }

                e = _messageEventQueue.Dequeue();
            }

            _message.BeginInvoke(e, ar => _message.EndInvoke(ar), null);
        }

        private bool ping(byte[] data)
        {
            return true;
            if (_readyState != WebSocketState.Open)
                return false;

            var received = _pongReceived;

            if (received == null)
                return false;

            lock (_forPing)
            {
                try
                {
                    received.Reset();

                    var sent = send(Fin.Final, Opcode.Ping, data, false);

                    if (!sent)
                        return false;

                    return received.WaitOne(_waitTime);
                }
                catch (ObjectDisposedException)
                {
                    return false;
                }
            }
        }

        private bool processCloseFrame(WebSocketFrame frame)
        {
            var data = frame.PayloadData;
            var send = !data.HasReservedCode;

            close(data, send, true);

            return false;
        }

        // As client
        private void processCookies(CookieCollection cookies)
        {
            if (cookies.Count == 0)
                return;

            _cookies.SetOrRemove(cookies);
        }

        private bool processDataFrame(WebSocketFrame frame)
        {
            var e = frame.IsCompressed
                    ? new MessageEventArgs(
                        frame.Opcode,
                        frame.PayloadData.ApplicationData.Decompress(_compression)
                      )
                    : new MessageEventArgs(frame);

            enqueueToMessageEventQueue(e);

            return true;
        }

        private bool processFragmentFrame(WebSocketFrame frame)
        {
            if (!_inContinuation)
            {
                if (frame.IsContinuation)
                    return true;

                _fragmentsOpcode = frame.Opcode;
                _fragmentsCompressed = frame.IsCompressed;
                _fragmentsBuffer = new MemoryStream();
                _inContinuation = true;
            }

            _fragmentsBuffer.WriteBytes(frame.PayloadData.ApplicationData, 1024);

            if (frame.IsFinal)
            {
                using (_fragmentsBuffer)
                {
                    var data = _fragmentsCompressed
                               ? _fragmentsBuffer.DecompressToArray(_compression)
                               : _fragmentsBuffer.ToArray();

                    var e = new MessageEventArgs(_fragmentsOpcode, data);

                    enqueueToMessageEventQueue(e);
                }

                _fragmentsBuffer = null;
                _inContinuation = false;
            }

            return true;
        }

        private bool processPingFrame(WebSocketFrame frame)
        {
            _logger.Trace("A ping was received.");

            var pong = WebSocketFrame.CreatePongFrame(frame.PayloadData, _client);

            lock (_forState)
            {
                if (_readyState != WebSocketState.Open)
                {
                    _logger.Trace("A pong to this ping cannot be sent.");

                    return true;
                }

                var bytes = pong.ToArray();
                var sent = sendBytes(bytes);

                if (!sent)
                    return false;
            }

            _logger.Trace("A pong to this ping has been sent.");

            if (_emitOnPing)
            {
                if (_client)
                    pong.Unmask();

                var e = new MessageEventArgs(frame);

                enqueueToMessageEventQueue(e);
            }

            return true;
        }

        private bool processPongFrame(WebSocketFrame frame)
        {
            _logger.Trace("A pong was received.");

            try
            {
                _pongReceived.Set();
            }
            catch (NullReferenceException)
            {
                return false;
            }
            catch (ObjectDisposedException)
            {
                return false;
            }

            _logger.Trace("It has been signaled.");

            return true;
        }

        private bool processReceivedFrame(WebSocketFrame frame)
        {
            string msg;

            if (!checkReceivedFrame(frame, out msg))
                throw new WebSocketException(CloseStatusCode.ProtocolError, msg);

            frame.Unmask();

            return frame.IsFragment
                   ? processFragmentFrame(frame)
                   : frame.IsData
                     ? processDataFrame(frame)
                     : frame.IsPing
                       ? processPingFrame(frame)
                       : frame.IsPong
                         ? processPongFrame(frame)
                         : frame.IsClose
                           ? processCloseFrame(frame)
                           : processUnsupportedFrame(frame);
        }

        // As server
        private void processSecWebSocketExtensionsClientHeader(string value)
        {
            if (value == null)
                return;

            var buff = new StringBuilder(80);

            var comp = false;

            foreach (var elm in value.SplitHeaderValue(','))
            {
                var ext = elm.Trim();

                if (ext.Length == 0)
                    continue;

                if (!comp)
                {
                    if (ext.IsCompressionExtension(CompressionMethod.Deflate))
                    {
                        _compression = CompressionMethod.Deflate;

                        var str = _compression.ToExtensionString(
                                    "client_no_context_takeover",
                                    "server_no_context_takeover"
                                  );

                        buff.AppendFormat("{0}, ", str);

                        comp = true;
                    }
                }
            }

            var len = buff.Length;

            if (len <= 2)
                return;

            buff.Length = len - 2;

            _extensions = buff.ToString();
        }

        // As client
        private void processSecWebSocketExtensionsServerHeader(string value)
        {
            if (value == null)
            {
                _compression = CompressionMethod.None;

                return;
            }

            _extensions = value;
        }

        // As server
        private void processSecWebSocketProtocolClientHeader(
          IEnumerable<string> values
        )
        {
            if (values.Contains(val => val == _protocol))
                return;

            _protocol = null;
        }

        private bool processUnsupportedFrame(WebSocketFrame frame)
        {
            _logger.Fatal("An unsupported frame was received.");
            _logger.Debug("The frame is" + frame.PrintToString(false));

            abort(1003, "There is no way to handle it.");

            return false;
        }

        // As server
        private void refuseHandshake(ushort code, string reason)
        {
            var res = createHandshakeFailureResponse();

            sendHttpResponse(res);

            abort(code, reason);
        }

        // As client
        private void releaseClientResources()
        {
            if (_stream != null)
            {
                _stream.Dispose();

                _stream = null;
            }

            if (_tcpClient != null)
            {
                _tcpClient.Close();

                _tcpClient = null;
            }
        }

        private void releaseCommonResources()
        {
            if (_fragmentsBuffer != null)
            {
                _fragmentsBuffer.Dispose();

                _fragmentsBuffer = null;
                _inContinuation = false;
            }

            if (_pongReceived != null)
            {
                _pongReceived.Close();

                _pongReceived = null;
            }

            if (_receivingExited != null)
            {
                _receivingExited.Close();

                _receivingExited = null;
            }
        }

        private void releaseResources()
        {
            if (_client)
                releaseClientResources();
            else
                releaseServerResources();

            releaseCommonResources();
        }

        // As server
        private void releaseServerResources()
        {
            if (_closeContext != null)
            {
                _closeContext();

                _closeContext = null;
            }

            _stream = null;
            _context = null;
        }

        private bool send(Opcode opcode, Stream stream)
        {
            lock (_forSend)
            {
                var src = stream;
                var compressed = false;
                var sent = false;

                try
                {
                    if (_compression != CompressionMethod.None)
                    {
                        stream = stream.Compress(_compression);
                        compressed = true;
                    }

                    sent = send(opcode, stream, compressed);

                    if (!sent)
                        error("A send has been interrupted.", null);
                }
                catch (Exception ex)
                {
                    _logger.Error(ex.Message);
                    _logger.Debug(ex.ToString());

                    error("An exception has occurred during a send.", ex);
                }
                finally
                {
                    if (compressed)
                        stream.Dispose();

                    src.Dispose();
                }

                return sent;
            }
        }

        private bool send(Opcode opcode, Stream stream, bool compressed)
        {
            var len = stream.Length;

            if (len == 0)
                return send(Fin.Final, opcode, EmptyBytes, false);

            var quo = len / FragmentLength;
            var rem = (int)(len % FragmentLength);

            byte[] buff = null;

            if (quo == 0)
            {
                buff = new byte[rem];

                return stream.Read(buff, 0, rem) == rem
                       && send(Fin.Final, opcode, buff, compressed);
            }

            if (quo == 1 && rem == 0)
            {
                buff = new byte[FragmentLength];

                return stream.Read(buff, 0, FragmentLength) == FragmentLength
                       && send(Fin.Final, opcode, buff, compressed);
            }

            /* Send fragments */

            // Begin

            buff = new byte[FragmentLength];

            var sent = stream.Read(buff, 0, FragmentLength) == FragmentLength
                       && send(Fin.More, opcode, buff, compressed);

            if (!sent)
                return false;

            var n = rem == 0 ? quo - 2 : quo - 1;

            for (long i = 0; i < n; i++)
            {
                sent = stream.Read(buff, 0, FragmentLength) == FragmentLength
                       && send(Fin.More, Opcode.Cont, buff, false);

                if (!sent)
                    return false;
            }

            // End

            if (rem == 0)
                rem = FragmentLength;
            else
                buff = new byte[rem];

            return stream.Read(buff, 0, rem) == rem
                   && send(Fin.Final, Opcode.Cont, buff, false);
        }

        private bool send(Fin fin, Opcode opcode, byte[] data, bool compressed)
        {
            lock (_forState)
            {
                if (_readyState != WebSocketState.Open)
                {
                    _logger.Trace("The connection is closing.");

                    return false;
                }

                var frame = new WebSocketFrame(fin, opcode, data, compressed, _client);
                var bytes = frame.ToArray();

                return sendBytes(bytes);
            }
        }

        private void sendAsync(
          Opcode opcode, Stream stream, Action<bool> completed
        )
        {
            Func<Opcode, Stream, bool> sender = send;

            sender.BeginInvoke(
              opcode,
              stream,
              ar =>
              {
                  try
                  {
                      var sent = sender.EndInvoke(ar);

                      if (completed != null)
                          completed(sent);
                  }
                  catch (Exception ex)
                  {
                      _logger.Error(ex.Message);
                      _logger.Debug(ex.ToString());

                      error(
                  "An exception has occurred during the callback for an async send.",
                  ex
                );
                  }
              },
              null
            );
        }

        private bool sendBytes(byte[] bytes)
        {
            try
            {
                _stream.Write(bytes, 0, bytes.Length);
            }
            catch (Exception ex)
            {
                _logger.Error(ex.Message);
                _logger.Debug(ex.ToString());

                return false;
            }

            return true;
        }

        // As client
        private HttpResponse sendHandshakeRequest()
        {
            var req = createHandshakeRequest();
            var res = sendHttpRequest(req, 90000);

            if (res.IsUnauthorized)
            {
                if (_credentials == null)
                {
                    _logger.Error("No credential is specified.");

                    return res;
                }

                var val = res.Headers["WWW-Authenticate"];

                if (val.IsNullOrEmpty())
                {
                    _logger.Error("No authentication challenge is specified.");

                    return res;
                }

                var achal = AuthenticationChallenge.Parse(val);

                if (achal == null)
                {
                    _logger.Error("An invalid authentication challenge is specified.");

                    return res;
                }

                _authChallenge = achal;

                var failed = _preAuth
                             && _authChallenge.Scheme == AuthenticationSchemes.Basic;

                if (failed)
                {
                    _logger.Error("The authentication has failed.");

                    return res;
                }

                var ares = new AuthenticationResponse(
                             _authChallenge, _credentials, _nonceCount
                           );

                _nonceCount = ares.NonceCount;

                req.Headers["Authorization"] = ares.ToString();

                if (res.CloseConnection)
                {
                    releaseClientResources();
                    setClientStream();
                }

                res = sendHttpRequest(req, 15000);
            }

            if (res.IsRedirect)
            {
                if (!_enableRedirection)
                    return res;

                var val = res.Headers["Location"];

                if (val.IsNullOrEmpty())
                {
                    _logger.Error("No url to redirect is located.");

                    return res;
                }

                Uri uri;
                string msg;

                if (!val.TryCreateWebSocketUri(out uri, out msg))
                {
                    _logger.Error("An invalid url to redirect is located.");

                    return res;
                }

                releaseClientResources();

                _uri = uri;
                _secure = uri.Scheme == "wss";

                setClientStream();

                return sendHandshakeRequest();
            }

            return res;
        }

        // As client
        private HttpResponse sendHttpRequest(
          HttpRequest request, int millisecondsTimeout
        )
        {
            return request.GetResponse(_stream, millisecondsTimeout);
        }

        // As server
        private void sendHttpResponse(HttpResponse response)
        {
            response.WriteTo(_stream);
        }

        // As client
        private void sendProxyConnectRequest()
        {
            var req = HttpRequest.CreateConnectRequest(_uri);
            var res = sendHttpRequest(req, 90000);

            if (res.IsProxyAuthenticationRequired)
            {
                if (_proxyCredentials == null)
                {
                    var msg = "No credential for the proxy is specified.";

                    throw new WebSocketException(msg);
                }

                var val = res.Headers["Proxy-Authenticate"];

                if (val.IsNullOrEmpty())
                {
                    var msg = "No proxy authentication challenge is specified.";

                    throw new WebSocketException(msg);
                }

                var achal = AuthenticationChallenge.Parse(val);

                if (achal == null)
                {
                    var msg = "An invalid proxy authentication challenge is specified.";

                    throw new WebSocketException(msg);
                }

                var ares = new AuthenticationResponse(achal, _proxyCredentials, 0);

                req.Headers["Proxy-Authorization"] = ares.ToString();

                if (res.CloseConnection)
                {
                    releaseClientResources();

                    _tcpClient = new TcpClient(_proxyUri.DnsSafeHost, _proxyUri.Port);
                    _stream = _tcpClient.GetStream();
                }

                res = sendHttpRequest(req, 15000);

                if (res.IsProxyAuthenticationRequired)
                {
                    var msg = "The proxy authentication has failed.";

                    throw new WebSocketException(msg);
                }
            }

            if (!res.IsSuccess)
            {
                var msg = "The proxy has failed a connection to the requested URL.";

                throw new WebSocketException(msg);
            }
        }

        // As client
        private void setClientStream()
        {
            if (_proxyUri != null)
            {
                _tcpClient = new TcpClient(_proxyUri.DnsSafeHost, _proxyUri.Port);
                _stream = _tcpClient.GetStream();

                sendProxyConnectRequest();
            }
            else
            {
                _tcpClient = new TcpClient(_uri.DnsSafeHost, _uri.Port);
                _stream = _tcpClient.GetStream();
            }

            if (_secure)
            {
                var conf = getSslConfiguration();
                var host = conf.TargetHost;

                if (host != _uri.DnsSafeHost)
                {
                    var msg = "An invalid host name is specified.";

                    throw new WebSocketException(
                            CloseStatusCode.TlsHandshakeFailure, msg
                          );
                }

                try
                {
                    var sslStream = new SslStream(
                                      _stream,
                                      false,
                                      conf.ServerCertificateValidationCallback,
                                      conf.ClientCertificateSelectionCallback
                                    );

                    sslStream.AuthenticateAsClient(
                      host,
                      conf.ClientCertificates,
                      conf.EnabledSslProtocols,
                      conf.CheckCertificateRevocation
                    );

                    _stream = sslStream;
                }
                catch (Exception ex)
                {
                    throw new WebSocketException(
                            CloseStatusCode.TlsHandshakeFailure, ex
                          );
                }
            }
        }

        private void startReceiving()
        {
            if (_messageEventQueue.Count > 0)
                _messageEventQueue.Clear();

            _pongReceived = new ManualResetEvent(false);
            _receivingExited = new ManualResetEvent(false);

            Action receive = null;
            receive =
              () =>
                WebSocketFrame.ReadFrameAsync(
                  _stream,
                  false,
                  frame =>
                  {
                      var cont = processReceivedFrame(frame)
                           && _readyState != WebSocketState.Closed;

                      if (!cont)
                      {
                          var exited = _receivingExited;

                          if (exited != null)
                              exited.Set();

                          return;
                      }

                      receive();

                      if (_inMessage)
                          return;

                      message();
                  },
                  ex =>
                  {
                      Console.WriteLine(ex);
                      _logger.Fatal(ex.Message);
                      _logger.Fatal(ex.ToString());

                      _logger.Debug(ex.ToString());

                      abort("An exception has occurred while receiving.", ex);
                  }
                );

            receive();
        }

        // As client
        private bool validateSecWebSocketExtensionsServerHeader(string value)
        {
            if (value == null)
                return true;

            if (value.Length == 0)
                return false;

            if (!_extensionsRequested)
                return false;

            var comp = _compression != CompressionMethod.None;

            foreach (var elm in value.SplitHeaderValue(','))
            {
                var ext = elm.Trim();

                if (comp && ext.IsCompressionExtension(_compression))
                {
                    var param1 = "server_no_context_takeover";
                    var param2 = "client_no_context_takeover";

                    if (!ext.Contains(param1))
                    {
                        var fmt = "The server did not send back '{0}'.";
                        var msg = String.Format(fmt, param1);

                        _logger.Error(msg);

                        return false;
                    }

                    var name = _compression.ToExtensionString();
                    var invalid = ext.SplitHeaderValue(';').Contains(
                                    t =>
                                    {
                                        t = t.Trim();

                                        var valid = t == name
                                          || t == param1
                                          || t == param2;

                                        return !valid;
                                    }
                                  );

                    if (invalid)
                        return false;

                    comp = false;
                }
                else
                {
                    return false;
                }
            }

            return true;
        }

        #endregion

        #region Internal Methods

        // As server
        internal void Accept()
        {
            var accepted = accept();

            if (!accepted)
                return;

            open();
        }

        // As server
        internal void AcceptAsync()
        {
            Func<bool> acceptor = accept;

            acceptor.BeginInvoke(
              ar =>
              {
                  var accepted = acceptor.EndInvoke(ar);

                  if (!accepted)
                      return;

                  open();
              },
              null
            );
        }

        // As server
        internal void Close(PayloadData payloadData, byte[] frameAsBytes)
        {
            lock (_forState)
            {
                if (_readyState == WebSocketState.Closing)
                {
                    _logger.Trace("The close process is already in progress.");

                    return;
                }

                if (_readyState == WebSocketState.Closed)
                {
                    _logger.Trace("The connection has already been closed.");

                    return;
                }

                _readyState = WebSocketState.Closing;
            }

            _logger.Trace("Begin closing the connection.");

            var sent = frameAsBytes != null && sendBytes(frameAsBytes);
            var received = sent && _receivingExited != null
                           ? _receivingExited.WaitOne(_waitTime)
                           : false;

            var res = sent && received;

            var msg = String.Format(
                        "The closing was clean? {0} (sent: {1} received: {2})",
                        res,
                        sent,
                        received
                      );

            _logger.Debug(msg);

            releaseServerResources();
            releaseCommonResources();

            _logger.Trace("End closing the connection.");

            _readyState = WebSocketState.Closed;

            var e = new CloseEventArgs(payloadData, res);

            try
            {
                OnClose.Emit(this, e);
            }
            catch (Exception ex)
            {
                _logger.Error(ex.Message);
                _logger.Debug(ex.ToString());
            }
        }

        // As client
        internal static string CreateBase64Key()
        {
            var src = new byte[16];

            RandomNumber.GetBytes(src);

            return Convert.ToBase64String(src);
        }

        internal static string CreateResponseKey(string base64Key)
        {
            SHA1 sha1 = new SHA1CryptoServiceProvider();

            var data = base64Key + _guid;
            var bytes = data.GetUTF8EncodedBytes();
            var src = sha1.ComputeHash(bytes);

            return Convert.ToBase64String(src);
        }

        // As server
        internal bool Ping(byte[] frameAsBytes, TimeSpan timeout)
        {
            if (_readyState != WebSocketState.Open)
                return false;

            var received = _pongReceived;

            if (received == null)
                return false;

            lock (_forPing)
            {
                try
                {
                    received.Reset();

                    lock (_forState)
                    {
                        if (_readyState != WebSocketState.Open)
                            return false;

                        var sent = sendBytes(frameAsBytes);

                        if (!sent)
                            return false;
                    }

                    return received.WaitOne(timeout);
                }
                catch (ObjectDisposedException)
                {
                    return false;
                }
            }
        }

        // As server
        internal void Send(
          Opcode opcode, byte[] data, Dictionary<CompressionMethod, byte[]> cache
        )
        {
            lock (_forSend)
            {
                lock (_forState)
                {
                    if (_readyState != WebSocketState.Open)
                    {
                        _logger.Error("The connection is closing.");
                        return;
                    }

                    byte[] found;
                    if (!cache.TryGetValue(_compression, out found))
                    {
                        found = new WebSocketFrame(
                                  Fin.Final,
                                  opcode,
                                  data.Compress(_compression),
                                  _compression != CompressionMethod.None,
                                  false
                                )
                                .ToArray();

                        cache.Add(_compression, found);
                    }

                    sendBytes(found);
                }
            }
        }

        // As server
        internal void Send(
          Opcode opcode, Stream stream, Dictionary<CompressionMethod, Stream> cache
        )
        {
            lock (_forSend)
            {
                Stream found;
                if (!cache.TryGetValue(_compression, out found))
                {
                    found = stream.Compress(_compression);
                    cache.Add(_compression, found);
                }
                else
                {
                    found.Position = 0;
                }

                send(opcode, found, _compression != CompressionMethod.None);
            }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Closes the connection.
        /// </summary>
        /// <remarks>
        /// This method does nothing if the current state of the interface is
        /// Closing or Closed.
        /// </remarks>
        public void Close()
        {
            close(1005, String.Empty);
        }

        /// <summary>
        /// Closes the connection with the specified code.
        /// </summary>
        /// <remarks>
        /// This method does nothing if the current state of the interface is
        /// Closing or Closed.
        /// </remarks>
        /// <param name="code">
        ///   <para>
        ///   A <see cref="ushort"/> that specifies the status code indicating
        ///   the reason for the close.
        ///   </para>
        ///   <para>
        ///   The status codes are defined in
        ///   <see href="http://tools.ietf.org/html/rfc6455#section-7.4">
        ///   Section 7.4</see> of RFC 6455.
        ///   </para>
        /// </param>
        /// <exception cref="ArgumentOutOfRangeException">
        /// <paramref name="code"/> is less than 1000 or greater than 4999.
        /// </exception>
        /// <exception cref="ArgumentException">
        ///   <para>
        ///   <paramref name="code"/> is 1011 (server error).
        ///   It cannot be used by a client.
        ///   </para>
        ///   <para>
        ///   -or-
        ///   </para>
        ///   <para>
        ///   <paramref name="code"/> is 1010 (mandatory extension).
        ///   It cannot be used by a server.
        ///   </para>
        /// </exception>
        public void Close(ushort code)
        {
            Close(code, String.Empty);
        }

        /// <summary>
        /// Closes the connection with the specified code.
        /// </summary>
        /// <remarks>
        /// This method does nothing if the current state of the interface is
        /// Closing or Closed.
        /// </remarks>
        /// <param name="code">
        ///   <para>
        ///   One of the <see cref="CloseStatusCode"/> enum values.
        ///   </para>
        ///   <para>
        ///   It specifies the status code indicating the reason for the close.
        ///   </para>
        /// </param>
        /// <exception cref="ArgumentException">
        ///   <para>
        ///   <paramref name="code"/> is <see cref="CloseStatusCode.ServerError"/>.
        ///   It cannot be used by a client.
        ///   </para>
        ///   <para>
        ///   -or-
        ///   </para>
        ///   <para>
        ///   <paramref name="code"/> is <see cref="CloseStatusCode.MandatoryExtension"/>.
        ///   It cannot be used by a server.
        ///   </para>
        /// </exception>
        public void Close(CloseStatusCode code)
        {
            Close(code, String.Empty);
        }

        /// <summary>
        /// Closes the connection with the specified code and reason.
        /// </summary>
        /// <remarks>
        /// This method does nothing if the current state of the interface is
        /// Closing or Closed.
        /// </remarks>
        /// <param name="code">
        ///   <para>
        ///   A <see cref="ushort"/> that specifies the status code indicating
        ///   the reason for the close.
        ///   </para>
        ///   <para>
        ///   The status codes are defined in
        ///   <see href="http://tools.ietf.org/html/rfc6455#section-7.4">
        ///   Section 7.4</see> of RFC 6455.
        ///   </para>
        /// </param>
        /// <param name="reason">
        ///   <para>
        ///   A <see cref="string"/> that specifies the reason for the close.
        ///   </para>
        ///   <para>
        ///   Its size must be 123 bytes or less in UTF-8.
        ///   </para>
        /// </param>
        /// <exception cref="ArgumentOutOfRangeException">
        ///   <para>
        ///   <paramref name="code"/> is less than 1000 or greater than 4999.
        ///   </para>
        ///   <para>
        ///   -or-
        ///   </para>
        ///   <para>
        ///   The size of <paramref name="reason"/> is greater than 123 bytes.
        ///   </para>
        /// </exception>
        /// <exception cref="ArgumentException">
        ///   <para>
        ///   <paramref name="code"/> is 1011 (server error).
        ///   It cannot be used by a client.
        ///   </para>
        ///   <para>
        ///   -or-
        ///   </para>
        ///   <para>
        ///   <paramref name="code"/> is 1010 (mandatory extension).
        ///   It cannot be used by a server.
        ///   </para>
        ///   <para>
        ///   -or-
        ///   </para>
        ///   <para>
        ///   <paramref name="code"/> is 1005 (no status) and
        ///   <paramref name="reason"/> is specified.
        ///   </para>
        ///   <para>
        ///   -or-
        ///   </para>
        ///   <para>
        ///   <paramref name="reason"/> could not be UTF-8-encoded.
        ///   </para>
        /// </exception>
        public void Close(ushort code, string reason)
        {
            if (!code.IsCloseStatusCode())
            {
                var msg = "Less than 1000 or greater than 4999.";

                throw new ArgumentOutOfRangeException("code", msg);
            }

            if (_client && code == 1011)
            {
                var msg = "1011 cannot be used.";

                throw new ArgumentException(msg, "code");
            }

            if (!_client && code == 1010)
            {
                var msg = "1010 cannot be used.";

                throw new ArgumentException(msg, "code");
            }

            if (reason.IsNullOrEmpty())
            {
                close(code, String.Empty);

                return;
            }

            if (code == 1005)
            {
                var msg = "1005 cannot be used.";

                throw new ArgumentException(msg, "code");
            }

            byte[] bytes;

            if (!reason.TryGetUTF8EncodedBytes(out bytes))
            {
                var msg = "It could not be UTF-8-encoded.";

                throw new ArgumentException(msg, "reason");
            }

            if (bytes.Length > 123)
            {
                var msg = "Its size is greater than 123 bytes.";

                throw new ArgumentOutOfRangeException("reason", msg);
            }

            close(code, reason);
        }

        /// <summary>
        /// Closes the connection with the specified code and reason.
        /// </summary>
        /// <remarks>
        /// This method does nothing if the current state of the interface is
        /// Closing or Closed.
        /// </remarks>
        /// <param name="code">
        ///   <para>
        ///   One of the <see cref="CloseStatusCode"/> enum values.
        ///   </para>
        ///   <para>
        ///   It specifies the status code indicating the reason for the close.
        ///   </para>
        /// </param>
        /// <param name="reason">
        ///   <para>
        ///   A <see cref="string"/> that specifies the reason for the close.
        ///   </para>
        ///   <para>
        ///   Its size must be 123 bytes or less in UTF-8.
        ///   </para>
        /// </param>
        /// <exception cref="ArgumentException">
        ///   <para>
        ///   <paramref name="code"/> is <see cref="CloseStatusCode.ServerError"/>.
        ///   It cannot be used by a client.
        ///   </para>
        ///   <para>
        ///   -or-
        ///   </para>
        ///   <para>
        ///   <paramref name="code"/> is <see cref="CloseStatusCode.MandatoryExtension"/>.
        ///   It cannot be used by a server.
        ///   </para>
        ///   <para>
        ///   -or-
        ///   </para>
        ///   <para>
        ///   <paramref name="code"/> is <see cref="CloseStatusCode.NoStatus"/> and
        ///   <paramref name="reason"/> is specified.
        ///   </para>
        ///   <para>
        ///   -or-
        ///   </para>
        ///   <para>
        ///   <paramref name="reason"/> could not be UTF-8-encoded.
        ///   </para>
        /// </exception>
        /// <exception cref="ArgumentOutOfRangeException">
        /// The size of <paramref name="reason"/> is greater than 123 bytes.
        /// </exception>
        public void Close(CloseStatusCode code, string reason)
        {
            if (_client && code == CloseStatusCode.ServerError)
            {
                var msg = "ServerError cannot be used.";

                throw new ArgumentException(msg, "code");
            }

            if (!_client && code == CloseStatusCode.MandatoryExtension)
            {
                var msg = "MandatoryExtension cannot be used.";

                throw new ArgumentException(msg, "code");
            }

            if (reason.IsNullOrEmpty())
            {
                close((ushort)code, String.Empty);

                return;
            }

            if (code == CloseStatusCode.NoStatus)
            {
                var msg = "NoStatus cannot be used.";

                throw new ArgumentException(msg, "code");
            }

            byte[] bytes;

            if (!reason.TryGetUTF8EncodedBytes(out bytes))
            {
                var msg = "It could not be UTF-8-encoded.";

                throw new ArgumentException(msg, "reason");
            }

            if (bytes.Length > 123)
            {
                var msg = "Its size is greater than 123 bytes.";

                throw new ArgumentOutOfRangeException("reason", msg);
            }

            close((ushort)code, reason);
        }

        /// <summary>
        /// Closes the connection asynchronously.
        /// </summary>
        /// <remarks>
        ///   <para>
        ///   This method does not wait for the close to be complete.
        ///   </para>
        ///   <para>
        ///   This method does nothing if the current state of the interface is
        ///   Closing or Closed.
        ///   </para>
        /// </remarks>
        public void CloseAsync()
        {
            closeAsync(1005, String.Empty);
        }

        /// <summary>
        /// Closes the connection asynchronously with the specified code.
        /// </summary>
        /// <remarks>
        ///   <para>
        ///   This method does not wait for the close to be complete.
        ///   </para>
        ///   <para>
        ///   This method does nothing if the current state of the interface is
        ///   Closing or Closed.
        ///   </para>
        /// </remarks>
        /// <param name="code">
        ///   <para>
        ///   A <see cref="ushort"/> that specifies the status code indicating
        ///   the reason for the close.
        ///   </para>
        ///   <para>
        ///   The status codes are defined in
        ///   <see href="http://tools.ietf.org/html/rfc6455#section-7.4">
        ///   Section 7.4</see> of RFC 6455.
        ///   </para>
        /// </param>
        /// <exception cref="ArgumentOutOfRangeException">
        /// <paramref name="code"/> is less than 1000 or greater than 4999.
        /// </exception>
        /// <exception cref="ArgumentException">
        ///   <para>
        ///   <paramref name="code"/> is 1011 (server error).
        ///   It cannot be used by a client.
        ///   </para>
        ///   <para>
        ///   -or-
        ///   </para>
        ///   <para>
        ///   <paramref name="code"/> is 1010 (mandatory extension).
        ///   It cannot be used by a server.
        ///   </para>
        /// </exception>
        public void CloseAsync(ushort code)
        {
            CloseAsync(code, String.Empty);
        }

        /// <summary>
        /// Closes the connection asynchronously with the specified code.
        /// </summary>
        /// <remarks>
        ///   <para>
        ///   This method does not wait for the close to be complete.
        ///   </para>
        ///   <para>
        ///   This method does nothing if the current state of the interface is
        ///   Closing or Closed.
        ///   </para>
        /// </remarks>
        /// <param name="code">
        ///   <para>
        ///   One of the <see cref="CloseStatusCode"/> enum values.
        ///   </para>
        ///   <para>
        ///   It specifies the status code indicating the reason for the close.
        ///   </para>
        /// </param>
        /// <exception cref="ArgumentException">
        ///   <para>
        ///   <paramref name="code"/> is <see cref="CloseStatusCode.ServerError"/>.
        ///   It cannot be used by a client.
        ///   </para>
        ///   <para>
        ///   -or-
        ///   </para>
        ///   <para>
        ///   <paramref name="code"/> is <see cref="CloseStatusCode.MandatoryExtension"/>.
        ///   It cannot be used by a server.
        ///   </para>
        /// </exception>
        public void CloseAsync(CloseStatusCode code)
        {
            CloseAsync(code, String.Empty);
        }

        /// <summary>
        /// Closes the connection asynchronously with the specified code and reason.
        /// </summary>
        /// <remarks>
        ///   <para>
        ///   This method does not wait for the close to be complete.
        ///   </para>
        ///   <para>
        ///   This method does nothing if the current state of the interface is
        ///   Closing or Closed.
        ///   </para>
        /// </remarks>
        /// <param name="code">
        ///   <para>
        ///   A <see cref="ushort"/> that specifies the status code indicating
        ///   the reason for the close.
        ///   </para>
        ///   <para>
        ///   The status codes are defined in
        ///   <see href="http://tools.ietf.org/html/rfc6455#section-7.4">
        ///   Section 7.4</see> of RFC 6455.
        ///   </para>
        /// </param>
        /// <param name="reason">
        ///   <para>
        ///   A <see cref="string"/> that specifies the reason for the close.
        ///   </para>
        ///   <para>
        ///   Its size must be 123 bytes or less in UTF-8.
        ///   </para>
        /// </param>
        /// <exception cref="ArgumentOutOfRangeException">
        ///   <para>
        ///   <paramref name="code"/> is less than 1000 or greater than 4999.
        ///   </para>
        ///   <para>
        ///   -or-
        ///   </para>
        ///   <para>
        ///   The size of <paramref name="reason"/> is greater than 123 bytes.
        ///   </para>
        /// </exception>
        /// <exception cref="ArgumentException">
        ///   <para>
        ///   <paramref name="code"/> is 1011 (server error).
        ///   It cannot be used by a client.
        ///   </para>
        ///   <para>
        ///   -or-
        ///   </para>
        ///   <para>
        ///   <paramref name="code"/> is 1010 (mandatory extension).
        ///   It cannot be used by a server.
        ///   </para>
        ///   <para>
        ///   -or-
        ///   </para>
        ///   <para>
        ///   <paramref name="code"/> is 1005 (no status) and
        ///   <paramref name="reason"/> is specified.
        ///   </para>
        ///   <para>
        ///   -or-
        ///   </para>
        ///   <para>
        ///   <paramref name="reason"/> could not be UTF-8-encoded.
        ///   </para>
        /// </exception>
        public void CloseAsync(ushort code, string reason)
        {
            if (!code.IsCloseStatusCode())
            {
                var msg = "Less than 1000 or greater than 4999.";

                throw new ArgumentOutOfRangeException("code", msg);
            }

            if (_client && code == 1011)
            {
                var msg = "1011 cannot be used.";

                throw new ArgumentException(msg, "code");
            }

            if (!_client && code == 1010)
            {
                var msg = "1010 cannot be used.";

                throw new ArgumentException(msg, "code");
            }

            if (reason.IsNullOrEmpty())
            {
                closeAsync(code, String.Empty);

                return;
            }

            if (code == 1005)
            {
                var msg = "1005 cannot be used.";

                throw new ArgumentException(msg, "code");
            }

            byte[] bytes;

            if (!reason.TryGetUTF8EncodedBytes(out bytes))
            {
                var msg = "It could not be UTF-8-encoded.";

                throw new ArgumentException(msg, "reason");
            }

            if (bytes.Length > 123)
            {
                var msg = "Its size is greater than 123 bytes.";

                throw new ArgumentOutOfRangeException("reason", msg);
            }

            closeAsync(code, reason);
        }

        /// <summary>
        /// Closes the connection asynchronously with the specified code and reason.
        /// </summary>
        /// <remarks>
        ///   <para>
        ///   This method does not wait for the close to be complete.
        ///   </para>
        ///   <para>
        ///   This method does nothing if the current state of the interface is
        ///   Closing or Closed.
        ///   </para>
        /// </remarks>
        /// <param name="code">
        ///   <para>
        ///   One of the <see cref="CloseStatusCode"/> enum values.
        ///   </para>
        ///   <para>
        ///   It specifies the status code indicating the reason for the close.
        ///   </para>
        /// </param>
        /// <param name="reason">
        ///   <para>
        ///   A <see cref="string"/> that specifies the reason for the close.
        ///   </para>
        ///   <para>
        ///   Its size must be 123 bytes or less in UTF-8.
        ///   </para>
        /// </param>
        /// <exception cref="ArgumentException">
        ///   <para>
        ///   <paramref name="code"/> is <see cref="CloseStatusCode.ServerError"/>.
        ///   It cannot be used by a client.
        ///   </para>
        ///   <para>
        ///   -or-
        ///   </para>
        ///   <para>
        ///   <paramref name="code"/> is <see cref="CloseStatusCode.MandatoryExtension"/>.
        ///   It cannot be used by a server.
        ///   </para>
        ///   <para>
        ///   -or-
        ///   </para>
        ///   <para>
        ///   <paramref name="code"/> is <see cref="CloseStatusCode.NoStatus"/> and
        ///   <paramref name="reason"/> is specified.
        ///   </para>
        ///   <para>
        ///   -or-
        ///   </para>
        ///   <para>
        ///   <paramref name="reason"/> could not be UTF-8-encoded.
        ///   </para>
        /// </exception>
        /// <exception cref="ArgumentOutOfRangeException">
        /// The size of <paramref name="reason"/> is greater than 123 bytes.
        /// </exception>
        public void CloseAsync(CloseStatusCode code, string reason)
        {
            if (_client && code == CloseStatusCode.ServerError)
            {
                var msg = "ServerError cannot be used.";

                throw new ArgumentException(msg, "code");
            }

            if (!_client && code == CloseStatusCode.MandatoryExtension)
            {
                var msg = "MandatoryExtension cannot be used.";

                throw new ArgumentException(msg, "code");
            }

            if (reason.IsNullOrEmpty())
            {
                closeAsync((ushort)code, String.Empty);

                return;
            }

            if (code == CloseStatusCode.NoStatus)
            {
                var msg = "NoStatus cannot be used.";

                throw new ArgumentException(msg, "code");
            }

            byte[] bytes;

            if (!reason.TryGetUTF8EncodedBytes(out bytes))
            {
                var msg = "It could not be UTF-8-encoded.";

                throw new ArgumentException(msg, "reason");
            }

            if (bytes.Length > 123)
            {
                var msg = "Its size is greater than 123 bytes.";

                throw new ArgumentOutOfRangeException("reason", msg);
            }

            closeAsync((ushort)code, reason);
        }

        /// <summary>
        /// Establishes a connection.
        /// </summary>
        /// <remarks>
        /// This method does nothing if the connection has already been
        /// established.
        /// </remarks>
        /// <exception cref="InvalidOperationException">
        ///   <para>
        ///   The interface is not for the client.
        ///   </para>
        ///   <para>
        ///   -or-
        ///   </para>
        ///   <para>
        ///   A series of reconnecting has failed.
        ///   </para>
        /// </exception>
        public void Connect()
        {
            if (!_client)
            {
                var msg = "The interface is not for the client.";

                throw new InvalidOperationException(msg);
            }

            if (_retryCountForConnect >= _maxRetryCountForConnect)
            {
                var msg = "A series of reconnecting has failed.";

                throw new InvalidOperationException(msg);
            }

            var connected = connect();

            if (!connected)
                return;

            open();
        }

        /// <summary>
        /// Establishes a connection asynchronously.
        /// </summary>
        /// <remarks>
        ///   <para>
        ///   This method does not wait for the connect process to be complete.
        ///   </para>
        ///   <para>
        ///   This method does nothing if the connection has already been
        ///   established.
        ///   </para>
        /// </remarks>
        /// <exception cref="InvalidOperationException">
        ///   <para>
        ///   The interface is not for the client.
        ///   </para>
        ///   <para>
        ///   -or-
        ///   </para>
        ///   <para>
        ///   A series of reconnecting has failed.
        ///   </para>
        /// </exception>
        public void ConnectAsync()
        {
            if (!_client)
            {
                var msg = "The interface is not for the client.";

                throw new InvalidOperationException(msg);
            }

            if (_retryCountForConnect >= _maxRetryCountForConnect)
            {
                var msg = "A series of reconnecting has failed.";

                throw new InvalidOperationException(msg);
            }

            Func<bool> connector = connect;

            connector.BeginInvoke(
              ar =>
              {
                  var connected = connector.EndInvoke(ar);

                  if (!connected)
                      return;

                  open();
              },
              null
            );
        }

        /// <summary>
        /// Sends a ping to the remote endpoint.
        /// </summary>
        /// <returns>
        /// <c>true</c> if the send has successfully done and a pong has been
        /// received within a time; otherwise, <c>false</c>.
        /// </returns>
        public bool Ping()
        {
            return true;
            return ping(EmptyBytes);
        }

        /// <summary>
        /// Sends a ping with the specified message to the remote endpoint.
        /// </summary>
        /// <returns>
        /// <c>true</c> if the send has successfully done and a pong has been
        /// received within a time; otherwise, <c>false</c>.
        /// </returns>
        /// <param name="message">
        ///   <para>
        ///   A <see cref="string"/> that specifies the message to send.
        ///   </para>
        ///   <para>
        ///   Its size must be 125 bytes or less in UTF-8.
        ///   </para>
        /// </param>
        /// <exception cref="ArgumentException">
        /// <paramref name="message"/> could not be UTF-8-encoded.
        /// </exception>
        /// <exception cref="ArgumentOutOfRangeException">
        /// The size of <paramref name="message"/> is greater than 125 bytes.
        /// </exception>
        public bool Ping(string message)
        {
            return true;
            if (message.IsNullOrEmpty())
                return ping(EmptyBytes);

            byte[] bytes;

            if (!message.TryGetUTF8EncodedBytes(out bytes))
            {
                var msg = "It could not be UTF-8-encoded.";

                throw new ArgumentException(msg, "message");
            }

            if (bytes.Length > 125)
            {
                var msg = "Its size is greater than 125 bytes.";

                throw new ArgumentOutOfRangeException("message", msg);
            }

            return ping(bytes);
        }

        /// <summary>
        /// Sends the specified data using the WebSocket connection.
        /// </summary>
        /// <param name="data">
        /// An array of <see cref="byte"/> that represents the binary data to send.
        /// </param>
        /// <exception cref="InvalidOperationException">
        /// The current state of the connection is not Open.
        /// </exception>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="data"/> is <see langword="null"/>.
        /// </exception>
        public void Send(byte[] data)
        {
            if (_readyState != WebSocketState.Open)
            {
                var msg = "The current state of the connection is not Open.";
                throw new InvalidOperationException(msg);
            }

            if (data == null)
                throw new ArgumentNullException("data");

            send(Opcode.Binary, new MemoryStream(data));
        }

        /// <summary>
        /// Sends the specified file using the WebSocket connection.
        /// </summary>
        /// <param name="fileInfo">
        ///   <para>
        ///   A <see cref="FileInfo"/> that specifies the file to send.
        ///   </para>
        ///   <para>
        ///   The file is sent as the binary data.
        ///   </para>
        /// </param>
        /// <exception cref="InvalidOperationException">
        /// The current state of the connection is not Open.
        /// </exception>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="fileInfo"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ArgumentException">
        ///   <para>
        ///   The file does not exist.
        ///   </para>
        ///   <para>
        ///   -or-
        ///   </para>
        ///   <para>
        ///   The file could not be opened.
        ///   </para>
        /// </exception>
        public void Send(FileInfo fileInfo)
        {
            if (_readyState != WebSocketState.Open)
            {
                var msg = "The current state of the connection is not Open.";
                throw new InvalidOperationException(msg);
            }

            if (fileInfo == null)
                throw new ArgumentNullException("fileInfo");

            if (!fileInfo.Exists)
            {
                var msg = "The file does not exist.";
                throw new ArgumentException(msg, "fileInfo");
            }

            FileStream stream;
            if (!fileInfo.TryOpenRead(out stream))
            {
                var msg = "The file could not be opened.";
                throw new ArgumentException(msg, "fileInfo");
            }

            send(Opcode.Binary, stream);
        }

        /// <summary>
        /// Sends the specified data using the WebSocket connection.
        /// </summary>
        /// <param name="data">
        /// A <see cref="string"/> that represents the text data to send.
        /// </param>
        /// <exception cref="InvalidOperationException">
        /// The current state of the connection is not Open.
        /// </exception>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="data"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// <paramref name="data"/> could not be UTF-8-encoded.
        /// </exception>
        public void Send(string data)
        {
            if (_readyState != WebSocketState.Open)
            {
                var msg = "The current state of the connection is not Open.";
                throw new InvalidOperationException(msg);
            }

            if (data == null)
                throw new ArgumentNullException("data");

            byte[] bytes;
            if (!data.TryGetUTF8EncodedBytes(out bytes))
            {
                var msg = "It could not be UTF-8-encoded.";
                throw new ArgumentException(msg, "data");
            }

            send(Opcode.Text, new MemoryStream(bytes));
        }

        /// <summary>
        /// Sends the data from the specified stream using the WebSocket connection.
        /// </summary>
        /// <param name="stream">
        ///   <para>
        ///   A <see cref="Stream"/> instance from which to read the data to send.
        ///   </para>
        ///   <para>
        ///   The data is sent as the binary data.
        ///   </para>
        /// </param>
        /// <param name="length">
        /// An <see cref="int"/> that specifies the number of bytes to send.
        /// </param>
        /// <exception cref="InvalidOperationException">
        /// The current state of the connection is not Open.
        /// </exception>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="stream"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ArgumentException">
        ///   <para>
        ///   <paramref name="stream"/> cannot be read.
        ///   </para>
        ///   <para>
        ///   -or-
        ///   </para>
        ///   <para>
        ///   <paramref name="length"/> is less than 1.
        ///   </para>
        ///   <para>
        ///   -or-
        ///   </para>
        ///   <para>
        ///   No data could be read from <paramref name="stream"/>.
        ///   </para>
        /// </exception>
        public void Send(Stream stream, int length)
        {
            if (_readyState != WebSocketState.Open)
            {
                var msg = "The current state of the connection is not Open.";
                throw new InvalidOperationException(msg);
            }

            if (stream == null)
                throw new ArgumentNullException("stream");

            if (!stream.CanRead)
            {
                var msg = "It cannot be read.";
                throw new ArgumentException(msg, "stream");
            }

            if (length < 1)
            {
                var msg = "Less than 1.";
                throw new ArgumentException(msg, "length");
            }

            var bytes = stream.ReadBytes(length);

            var len = bytes.Length;
            if (len == 0)
            {
                var msg = "No data could be read from it.";
                throw new ArgumentException(msg, "stream");
            }

            if (len < length)
            {
                _logger.Warn(
                  String.Format(
                    "Only {0} byte(s) of data could be read from the stream.",
                    len
                  )
                );
            }

            send(Opcode.Binary, new MemoryStream(bytes));
        }

        /// <summary>
        /// Sends the specified data asynchronously using the WebSocket connection.
        /// </summary>
        /// <remarks>
        /// This method does not wait for the send to be complete.
        /// </remarks>
        /// <param name="data">
        /// An array of <see cref="byte"/> that represents the binary data to send.
        /// </param>
        /// <param name="completed">
        ///   <para>
        ///   An <c>Action&lt;bool&gt;</c> delegate or <see langword="null"/>
        ///   if not needed.
        ///   </para>
        ///   <para>
        ///   The delegate invokes the method called when the send is complete.
        ///   </para>
        ///   <para>
        ///   <c>true</c> is passed to the method if the send has done with
        ///   no error; otherwise, <c>false</c>.
        ///   </para>
        /// </param>
        /// <exception cref="InvalidOperationException">
        /// The current state of the connection is not Open.
        /// </exception>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="data"/> is <see langword="null"/>.
        /// </exception>
        public void SendAsync(byte[] data, Action<bool> completed)
        {
            if (_readyState != WebSocketState.Open)
            {
                var msg = "The current state of the connection is not Open.";
                throw new InvalidOperationException(msg);
            }

            if (data == null)
                throw new ArgumentNullException("data");

            sendAsync(Opcode.Binary, new MemoryStream(data), completed);
        }

        /// <summary>
        /// Sends the specified file asynchronously using the WebSocket connection.
        /// </summary>
        /// <remarks>
        /// This method does not wait for the send to be complete.
        /// </remarks>
        /// <param name="fileInfo">
        ///   <para>
        ///   A <see cref="FileInfo"/> that specifies the file to send.
        ///   </para>
        ///   <para>
        ///   The file is sent as the binary data.
        ///   </para>
        /// </param>
        /// <param name="completed">
        ///   <para>
        ///   An <c>Action&lt;bool&gt;</c> delegate or <see langword="null"/>
        ///   if not needed.
        ///   </para>
        ///   <para>
        ///   The delegate invokes the method called when the send is complete.
        ///   </para>
        ///   <para>
        ///   <c>true</c> is passed to the method if the send has done with
        ///   no error; otherwise, <c>false</c>.
        ///   </para>
        /// </param>
        /// <exception cref="InvalidOperationException">
        /// The current state of the connection is not Open.
        /// </exception>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="fileInfo"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ArgumentException">
        ///   <para>
        ///   The file does not exist.
        ///   </para>
        ///   <para>
        ///   -or-
        ///   </para>
        ///   <para>
        ///   The file could not be opened.
        ///   </para>
        /// </exception>
        public void SendAsync(FileInfo fileInfo, Action<bool> completed)
        {
            if (_readyState != WebSocketState.Open)
            {
                var msg = "The current state of the connection is not Open.";
                throw new InvalidOperationException(msg);
            }

            if (fileInfo == null)
                throw new ArgumentNullException("fileInfo");

            if (!fileInfo.Exists)
            {
                var msg = "The file does not exist.";
                throw new ArgumentException(msg, "fileInfo");
            }

            FileStream stream;
            if (!fileInfo.TryOpenRead(out stream))
            {
                var msg = "The file could not be opened.";
                throw new ArgumentException(msg, "fileInfo");
            }

            sendAsync(Opcode.Binary, stream, completed);
        }

        /// <summary>
        /// Sends the specified data asynchronously using the WebSocket connection.
        /// </summary>
        /// <remarks>
        /// This method does not wait for the send to be complete.
        /// </remarks>
        /// <param name="data">
        /// A <see cref="string"/> that represents the text data to send.
        /// </param>
        /// <param name="completed">
        ///   <para>
        ///   An <c>Action&lt;bool&gt;</c> delegate or <see langword="null"/>
        ///   if not needed.
        ///   </para>
        ///   <para>
        ///   The delegate invokes the method called when the send is complete.
        ///   </para>
        ///   <para>
        ///   <c>true</c> is passed to the method if the send has done with
        ///   no error; otherwise, <c>false</c>.
        ///   </para>
        /// </param>
        /// <exception cref="InvalidOperationException">
        /// The current state of the connection is not Open.
        /// </exception>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="data"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// <paramref name="data"/> could not be UTF-8-encoded.
        /// </exception>
        public void SendAsync(string data, Action<bool> completed)
        {
            if (_readyState != WebSocketState.Open)
            {
                var msg = "The current state of the connection is not Open.";
                throw new InvalidOperationException(msg);
            }

            if (data == null)
                throw new ArgumentNullException("data");

            byte[] bytes;
            if (!data.TryGetUTF8EncodedBytes(out bytes))
            {
                var msg = "It could not be UTF-8-encoded.";
                throw new ArgumentException(msg, "data");
            }

            sendAsync(Opcode.Text, new MemoryStream(bytes), completed);
        }

        /// <summary>
        /// Sends the data from the specified stream asynchronously using
        /// the WebSocket connection.
        /// </summary>
        /// <remarks>
        /// This method does not wait for the send to be complete.
        /// </remarks>
        /// <param name="stream">
        ///   <para>
        ///   A <see cref="Stream"/> instance from which to read the data to send.
        ///   </para>
        ///   <para>
        ///   The data is sent as the binary data.
        ///   </para>
        /// </param>
        /// <param name="length">
        /// An <see cref="int"/> that specifies the number of bytes to send.
        /// </param>
        /// <param name="completed">
        ///   <para>
        ///   An <c>Action&lt;bool&gt;</c> delegate or <see langword="null"/>
        ///   if not needed.
        ///   </para>
        ///   <para>
        ///   The delegate invokes the method called when the send is complete.
        ///   </para>
        ///   <para>
        ///   <c>true</c> is passed to the method if the send has done with
        ///   no error; otherwise, <c>false</c>.
        ///   </para>
        /// </param>
        /// <exception cref="InvalidOperationException">
        /// The current state of the connection is not Open.
        /// </exception>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="stream"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ArgumentException">
        ///   <para>
        ///   <paramref name="stream"/> cannot be read.
        ///   </para>
        ///   <para>
        ///   -or-
        ///   </para>
        ///   <para>
        ///   <paramref name="length"/> is less than 1.
        ///   </para>
        ///   <para>
        ///   -or-
        ///   </para>
        ///   <para>
        ///   No data could be read from <paramref name="stream"/>.
        ///   </para>
        /// </exception>
        public void SendAsync(Stream stream, int length, Action<bool> completed)
        {
            if (_readyState != WebSocketState.Open)
            {
                var msg = "The current state of the connection is not Open.";
                throw new InvalidOperationException(msg);
            }

            if (stream == null)
                throw new ArgumentNullException("stream");

            if (!stream.CanRead)
            {
                var msg = "It cannot be read.";
                throw new ArgumentException(msg, "stream");
            }

            if (length < 1)
            {
                var msg = "Less than 1.";
                throw new ArgumentException(msg, "length");
            }

            var bytes = stream.ReadBytes(length);

            var len = bytes.Length;
            if (len == 0)
            {
                var msg = "No data could be read from it.";
                throw new ArgumentException(msg, "stream");
            }

            if (len < length)
            {
                _logger.Warn(
                  String.Format(
                    "Only {0} byte(s) of data could be read from the stream.",
                    len
                  )
                );
            }

            sendAsync(Opcode.Binary, new MemoryStream(bytes), completed);
        }

        /// <summary>
        /// Sets an HTTP cookie to send with the handshake request.
        /// </summary>
        /// <remarks>
        /// This method works if the current state of the interface is
        /// New or Closed.
        /// </remarks>
        /// <param name="cookie">
        /// A <see cref="Cookie"/> that specifies the cookie to send.
        /// </param>
        /// <exception cref="InvalidOperationException">
        /// The interface is not for the client.
        /// </exception>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="cookie"/> is <see langword="null"/>.
        /// </exception>
        public void SetCookie(Cookie cookie)
        {
            if (!_client)
            {
                var msg = "The interface is not for the client.";

                throw new InvalidOperationException(msg);
            }

            if (cookie == null)
                throw new ArgumentNullException("cookie");

            lock (_forState)
            {
                if (!canSet())
                    return;

                lock (_cookies.SyncRoot)
                    _cookies.SetOrRemove(cookie);
            }
        }

        /// <summary>
        /// Sets the credentials for the HTTP authentication (Basic/Digest).
        /// </summary>
        /// <remarks>
        /// This method works if the current state of the interface is
        /// New or Closed.
        /// </remarks>
        /// <param name="username">
        ///   <para>
        ///   A <see cref="string"/> that specifies the username associated
        ///   with the credentials.
        ///   </para>
        ///   <para>
        ///   <see langword="null"/> or an empty string if initializes
        ///   the credentials.
        ///   </para>
        /// </param>
        /// <param name="password">
        ///   <para>
        ///   A <see cref="string"/> that specifies the password for the
        ///   username associated with the credentials.
        ///   </para>
        ///   <para>
        ///   <see langword="null"/> or an empty string if not necessary.
        ///   </para>
        /// </param>
        /// <param name="preAuth">
        /// A <see cref="bool"/>: <c>true</c> if sends the credentials for
        /// the Basic authentication in advance with the first handshake
        /// request; otherwise, <c>false</c>.
        /// </param>
        /// <exception cref="InvalidOperationException">
        /// The interface is not for the client.
        /// </exception>
        /// <exception cref="ArgumentException">
        ///   <para>
        ///   <paramref name="username"/> contains an invalid character.
        ///   </para>
        ///   <para>
        ///   -or-
        ///   </para>
        ///   <para>
        ///   <paramref name="password"/> contains an invalid character.
        ///   </para>
        /// </exception>
        public void SetCredentials(string username, string password, bool preAuth)
        {
            if (!_client)
            {
                var msg = "The interface is not for the client.";

                throw new InvalidOperationException(msg);
            }

            if (!username.IsNullOrEmpty())
            {
                if (username.Contains(':') || !username.IsText())
                {
                    var msg = "It contains an invalid character.";

                    throw new ArgumentException(msg, "username");
                }
            }

            if (!password.IsNullOrEmpty())
            {
                if (!password.IsText())
                {
                    var msg = "It contains an invalid character.";

                    throw new ArgumentException(msg, "password");
                }
            }

            lock (_forState)
            {
                if (!canSet())
                    return;

                if (username.IsNullOrEmpty())
                {
                    _credentials = null;
                    _preAuth = false;

                    return;
                }

                _credentials = new NetworkCredential(
                                 username, password, _uri.PathAndQuery
                               );

                _preAuth = preAuth;
            }
        }

        /// <summary>
        /// Sets the URL of the HTTP proxy server through which to connect and
        /// the credentials for the HTTP proxy authentication (Basic/Digest).
        /// </summary>
        /// <remarks>
        /// This method works if the current state of the interface is
        /// New or Closed.
        /// </remarks>
        /// <param name="url">
        ///   <para>
        ///   A <see cref="string"/> that specifies the URL of the proxy
        ///   server through which to connect.
        ///   </para>
        ///   <para>
        ///   The syntax is http://&lt;host&gt;[:&lt;port&gt;].
        ///   </para>
        ///   <para>
        ///   <see langword="null"/> or an empty string if initializes
        ///   the URL and the credentials.
        ///   </para>
        /// </param>
        /// <param name="username">
        ///   <para>
        ///   A <see cref="string"/> that specifies the username associated
        ///   with the credentials.
        ///   </para>
        ///   <para>
        ///   <see langword="null"/> or an empty string if the credentials
        ///   are not necessary.
        ///   </para>
        /// </param>
        /// <param name="password">
        ///   <para>
        ///   A <see cref="string"/> that specifies the password for the
        ///   username associated with the credentials.
        ///   </para>
        ///   <para>
        ///   <see langword="null"/> or an empty string if not necessary.
        ///   </para>
        /// </param>
        /// <exception cref="InvalidOperationException">
        /// The interface is not for the client.
        /// </exception>
        /// <exception cref="ArgumentException">
        ///   <para>
        ///   <paramref name="url"/> is not an absolute URI string.
        ///   </para>
        ///   <para>
        ///   -or-
        ///   </para>
        ///   <para>
        ///   The scheme of <paramref name="url"/> is not http.
        ///   </para>
        ///   <para>
        ///   -or-
        ///   </para>
        ///   <para>
        ///   <paramref name="url"/> includes the path segments.
        ///   </para>
        ///   <para>
        ///   -or-
        ///   </para>
        ///   <para>
        ///   <paramref name="username"/> contains an invalid character.
        ///   </para>
        ///   <para>
        ///   -or-
        ///   </para>
        ///   <para>
        ///   <paramref name="password"/> contains an invalid character.
        ///   </para>
        /// </exception>
        public void SetProxy(string url, string username, string password)
        {
            if (!_client)
            {
                var msg = "The interface is not for the client.";

                throw new InvalidOperationException(msg);
            }

            Uri uri = null;

            if (!url.IsNullOrEmpty())
            {
                if (!Uri.TryCreate(url, UriKind.Absolute, out uri))
                {
                    var msg = "Not an absolute URI string.";

                    throw new ArgumentException(msg, "url");
                }

                if (uri.Scheme != "http")
                {
                    var msg = "The scheme part is not http.";

                    throw new ArgumentException(msg, "url");
                }

                if (uri.Segments.Length > 1)
                {
                    var msg = "It includes the path segments.";

                    throw new ArgumentException(msg, "url");
                }
            }

            if (!username.IsNullOrEmpty())
            {
                if (username.Contains(':') || !username.IsText())
                {
                    var msg = "It contains an invalid character.";

                    throw new ArgumentException(msg, "username");
                }
            }

            if (!password.IsNullOrEmpty())
            {
                if (!password.IsText())
                {
                    var msg = "It contains an invalid character.";

                    throw new ArgumentException(msg, "password");
                }
            }

            lock (_forState)
            {
                if (!canSet())
                    return;

                if (url.IsNullOrEmpty())
                {
                    _proxyUri = null;
                    _proxyCredentials = null;

                    return;
                }

                _proxyUri = uri;
                _proxyCredentials = !username.IsNullOrEmpty()
                                    ? new NetworkCredential(
                                        username,
                                        password,
                                        String.Format(
                                          "{0}:{1}", _uri.DnsSafeHost, _uri.Port
                                        )
                                      )
                                    : null;
            }
        }

        #endregion

        #region Explicit Interface Implementations

        /// <summary>
        /// Closes the connection and releases all associated resources.
        /// </summary>
        /// <remarks>
        ///   <para>
        ///   This method closes the connection with close status 1001 (going away).
        ///   </para>
        ///   <para>
        ///   This method does nothing if the current state of the interface is
        ///   Closing or Closed.
        ///   </para>
        /// </remarks>
        void IDisposable.Dispose()
        {
            close(1001, String.Empty);
        }

        #endregion
    }
}
