From 09c07ba95ed7300207f1f75d505fb35090c091fa Mon Sep 17 00:00:00 2001 From: sinolover Date: Fri, 8 Jan 2021 17:52:24 +0800 Subject: [PATCH] The program will be blocked when we use the properties of isAlive ActiveID and InactiveID and all methods relatived to ping undering the case of connectivity of any remote client was terminated suddenly. That's a big problem when we use the software in real time data transferring and the network was interrupted unexpectedly, so I change them to asynchronous method with setting macro definition of __PING_ASYNC at property of project 1.Steps to reproduce: Start WebSocketServer Start two or more clients to connect to WebSocket Server Unplugged the net cable of one of clients' manually The property of isAlive ActiveID or InActiveID was used before sending message in WebSocketBehavior or the event of _sweepTimer was triggered at occasion of sending message. 2.Actual result: All other rest clients could not receiving any data from WebSocket Server in 10 to 30 seconds until the WebSocket Server receiving the event of that client was closed or offline. 3.Expected result: Send messages quickly and continusly 4.Proposed solution: Replacing the usage of the ping method with pingAsync --- .../Server/WebSocketSessionManager.cs | 3304 +++---- websocket-sharp/WebSocket.cs | 7563 +++++++++-------- 2 files changed, 5703 insertions(+), 5164 deletions(-) diff --git a/websocket-sharp/Server/WebSocketSessionManager.cs b/websocket-sharp/Server/WebSocketSessionManager.cs index f7144b0ce..9c23f3955 100644 --- a/websocket-sharp/Server/WebSocketSessionManager.cs +++ b/websocket-sharp/Server/WebSocketSessionManager.cs @@ -33,1663 +33,1807 @@ using System.Linq; using System.Text; using System.Threading; +using System.Threading.Tasks; using System.Timers; namespace WebSocketSharp.Server { - /// - /// Provides the management function for the sessions in a WebSocket service. - /// - /// - /// This class manages the sessions in a WebSocket service provided by - /// the or . - /// - public class WebSocketSessionManager - { - #region Private Fields - - private volatile bool _clean; - private object _forSweep; - private Logger _log; - private Dictionary _sessions; - private volatile ServerState _state; - private volatile bool _sweeping; - private System.Timers.Timer _sweepTimer; - private object _sync; - private TimeSpan _waitTime; - - #endregion - - #region Internal Constructors - - internal WebSocketSessionManager (Logger log) - { - _log = log; - - _clean = true; - _forSweep = new object (); - _sessions = new Dictionary (); - _state = ServerState.Ready; - _sync = ((ICollection) _sessions).SyncRoot; - _waitTime = TimeSpan.FromSeconds (1); - - setSweepTimer (60000); - } - - #endregion - - #region Internal Properties - - internal ServerState State { - get { - return _state; - } - } - - #endregion - - #region Public Properties - /// - /// Gets the IDs for the active sessions in the WebSocket service. + /// Provides the management function for the sessions in a WebSocket service. /// - /// - /// - /// An IEnumerable<string> instance. - /// - /// - /// It provides an enumerator which supports the iteration over - /// the collection of the IDs for the active sessions. - /// - /// - public IEnumerable ActiveIDs { - get { - foreach (var res in broadping (WebSocketFrame.EmptyPingBytes)) { - if (res.Value) - yield return res.Key; + /// + /// This class manages the sessions in a WebSocket service provided by + /// the or . + /// + public class WebSocketSessionManager + { + #region Private Fields + + private volatile bool _clean; + private object _forSweep; + private Logger _log; + private Dictionary _sessions; + private volatile ServerState _state; + private volatile bool _sweeping; + private System.Timers.Timer _sweepTimer; + private object _sync; + private TimeSpan _waitTime; +#if __PING_ASYNC + private System.Timers.Timer _broadTimer; +#endif + #endregion + + #region Internal Constructors + + internal WebSocketSessionManager(Logger log) + { + _log = log; + + _clean = true; + _forSweep = new object(); + _sessions = new Dictionary(); + _state = ServerState.Ready; + _sync = ((ICollection)_sessions).SyncRoot; + _waitTime = TimeSpan.FromSeconds(1); + + setSweepTimer(60000); +#if __PING_ASYNC + _broadTimer = new System.Timers.Timer(10000); + _broadTimer.Elapsed += (sender, e) => broadpingAsync(WebSocketFrame.EmptyPingBytes); + _broadTimer.Enabled = true; + _broadTimer.Start(); +#endif } - } - } - /// - /// Gets the number of the sessions in the WebSocket service. - /// - /// - /// An that represents the number of the sessions. - /// - public int Count { - get { - lock (_sync) - return _sessions.Count; - } - } + #endregion - /// - /// Gets the IDs for the sessions in the WebSocket service. - /// - /// - /// - /// An IEnumerable<string> instance. - /// - /// - /// It provides an enumerator which supports the iteration over - /// the collection of the IDs for the sessions. - /// - /// - public IEnumerable IDs { - get { - if (_state != ServerState.Start) - return Enumerable.Empty (); - - lock (_sync) { - if (_state != ServerState.Start) - return Enumerable.Empty (); - - return _sessions.Keys.ToList (); - } - } - } + #region Internal Properties - /// - /// Gets the IDs for the inactive sessions in the WebSocket service. - /// - /// - /// - /// An IEnumerable<string> instance. - /// - /// - /// It provides an enumerator which supports the iteration over - /// the collection of the IDs for the inactive sessions. - /// - /// - public IEnumerable InactiveIDs { - get { - foreach (var res in broadping (WebSocketFrame.EmptyPingBytes)) { - if (!res.Value) - yield return res.Key; + internal ServerState State + { + get + { + return _state; + } } - } - } - /// - /// Gets the session instance with . - /// - /// - /// - /// A instance or - /// if not found. - /// - /// - /// The session instance provides the function to access the information - /// in the session. - /// - /// - /// - /// A that represents the ID of the session to find. - /// - /// - /// is . - /// - /// - /// is an empty string. - /// - public IWebSocketSession this[string id] { - get { - if (id == null) - throw new ArgumentNullException ("id"); - - if (id.Length == 0) - throw new ArgumentException ("An empty string.", "id"); - - IWebSocketSession session; - tryGetSession (id, out session); - - return session; - } - } - - /// - /// Gets or sets a value indicating whether the inactive sessions in - /// the WebSocket service are cleaned up periodically. - /// - /// - /// The set operation does nothing if the service has already started or - /// it is shutting down. - /// - /// - /// true if the inactive sessions are cleaned up every 60 seconds; - /// otherwise, false. - /// - public bool KeepClean { - get { - return _clean; - } - - set { - string msg; - if (!canSet (out msg)) { - _log.Warn (msg); - return; + #endregion + + #region Public Properties + + /// + /// Gets the IDs for the active sessions in the WebSocket service. + /// + /// + /// + /// An IEnumerable<string> instance. + /// + /// + /// It provides an enumerator which supports the iteration over + /// the collection of the IDs for the active sessions. + /// + /// +#if __PING_ASYNC + public IEnumerable ActiveIDs + { + get + { + foreach (var res in Sessions) + { + if (res.Context.WebSocket.IsAlive) + yield return res.ID; + } + } } - - lock (_sync) { - if (!canSet (out msg)) { - _log.Warn (msg); - return; - } - - _clean = value; +#else + public IEnumerable ActiveIDs + { + get + { + foreach (var res in broadping(WebSocketFrame.EmptyPingBytes)) + { + if (res.Value) + yield return res.Key; + } + } } - } - } - - /// - /// Gets the session instances in the WebSocket service. - /// - /// - /// - /// An IEnumerable<IWebSocketSession> instance. - /// - /// - /// It provides an enumerator which supports the iteration over - /// the collection of the session instances. - /// - /// - public IEnumerable Sessions { - get { - if (_state != ServerState.Start) - return Enumerable.Empty (); - - lock (_sync) { - if (_state != ServerState.Start) - return Enumerable.Empty (); - - return _sessions.Values.ToList (); +#endif + /// + /// Gets the number of the sessions in the WebSocket service. + /// + /// + /// An that represents the number of the sessions. + /// + public int Count + { + get + { + lock (_sync) + return _sessions.Count; + } } - } - } - /// - /// Gets or sets the time to wait for the response to the WebSocket Ping or - /// Close. - /// - /// - /// The set operation does nothing if the service has already started or - /// it is shutting down. - /// - /// - /// A to wait for the response. - /// - /// - /// The value specified for a set operation is zero or less. - /// - public TimeSpan WaitTime { - get { - return _waitTime; - } - - set { - if (value <= TimeSpan.Zero) - throw new ArgumentOutOfRangeException ("value", "Zero or less."); - - string msg; - if (!canSet (out msg)) { - _log.Warn (msg); - return; + /// + /// Gets the IDs for the sessions in the WebSocket service. + /// + /// + /// + /// An IEnumerable<string> instance. + /// + /// + /// It provides an enumerator which supports the iteration over + /// the collection of the IDs for the sessions. + /// + /// + public IEnumerable IDs + { + get + { + if (_state != ServerState.Start) + return Enumerable.Empty(); + + lock (_sync) + { + if (_state != ServerState.Start) + return Enumerable.Empty(); + + return _sessions.Keys.ToList(); + } + } } - lock (_sync) { - if (!canSet (out msg)) { - _log.Warn (msg); - return; - } - - _waitTime = value; + /// + /// Gets the IDs for the inactive sessions in the WebSocket service. + /// + /// + /// + /// An IEnumerable<string> instance. + /// + /// + /// It provides an enumerator which supports the iteration over + /// the collection of the IDs for the inactive sessions. + /// + /// +#if __PING_ASYNC + public IEnumerable InactiveIDs + { + get + { + foreach (var res in Sessions) + { + if (!res.Context.WebSocket.IsAlive) + yield return res.ID; + } + } } - } - } - - #endregion - - #region Private Methods - - private void broadcast (Opcode opcode, byte[] data, Action completed) - { - var cache = new Dictionary (); - - try { - foreach (var session in Sessions) { - if (_state != ServerState.Start) { - _log.Error ("The service is shutting down."); - break; - } - - session.Context.WebSocket.Send (opcode, data, cache); +#else + public IEnumerable InactiveIDs + { + get + { + foreach (var res in broadping(WebSocketFrame.EmptyPingBytes)) + { + if (!res.Value) + yield return res.Key; + } + } } - - if (completed != null) - completed (); - } - catch (Exception ex) { - _log.Error (ex.Message); - _log.Debug (ex.ToString ()); - } - finally { - cache.Clear (); - } - } - - private void broadcast (Opcode opcode, Stream stream, Action completed) - { - var cache = new Dictionary (); - - try { - foreach (var session in Sessions) { - if (_state != ServerState.Start) { - _log.Error ("The service is shutting down."); - break; - } - - session.Context.WebSocket.Send (opcode, stream, cache); +#endif + /// + /// Gets the session instance with . + /// + /// + /// + /// A instance or + /// if not found. + /// + /// + /// The session instance provides the function to access the information + /// in the session. + /// + /// + /// + /// A that represents the ID of the session to find. + /// + /// + /// is . + /// + /// + /// is an empty string. + /// + public IWebSocketSession this[string id] + { + get + { + if (id == null) + // throw new ArgumentNullException("id"); + return null; + + if (id.Length == 0) + throw new ArgumentException("An empty string.", "id"); + + IWebSocketSession session; + tryGetSession(id, out session); + + return session; + } } - if (completed != null) - completed (); - } - catch (Exception ex) { - _log.Error (ex.Message); - _log.Debug (ex.ToString ()); - } - finally { - foreach (var cached in cache.Values) - cached.Dispose (); - - cache.Clear (); - } - } - - private void broadcastAsync (Opcode opcode, byte[] data, Action completed) - { - ThreadPool.QueueUserWorkItem ( - state => broadcast (opcode, data, completed) - ); - } - - private void broadcastAsync (Opcode opcode, Stream stream, Action completed) - { - ThreadPool.QueueUserWorkItem ( - state => broadcast (opcode, stream, completed) - ); - } - - private Dictionary broadping (byte[] frameAsBytes) - { - var ret = new Dictionary (); - - foreach (var session in Sessions) { - if (_state != ServerState.Start) { - _log.Error ("The service is shutting down."); - break; + /// + /// Gets or sets a value indicating whether the inactive sessions in + /// the WebSocket service are cleaned up periodically. + /// + /// + /// The set operation does nothing if the service has already started or + /// it is shutting down. + /// + /// + /// true if the inactive sessions are cleaned up every 60 seconds; + /// otherwise, false. + /// + public bool KeepClean + { + get + { + return _clean; + } + + set + { + string msg; + if (!canSet(out msg)) + { + _log.Warn(msg); + return; + } + + lock (_sync) + { + if (!canSet(out msg)) + { + _log.Warn(msg); + return; + } + + _clean = value; + } + } } - var res = session.Context.WebSocket.Ping (frameAsBytes, _waitTime); - ret.Add (session.ID, res); - } - - return ret; - } - - private bool canSet (out string message) - { - message = null; - - if (_state == ServerState.Start) { - message = "The service has already started."; - return false; - } - - if (_state == ServerState.ShuttingDown) { - message = "The service is shutting down."; - return false; - } - - return true; - } - - private static string createID () - { - return Guid.NewGuid ().ToString ("N"); - } - - private void setSweepTimer (double interval) - { - _sweepTimer = new System.Timers.Timer (interval); - _sweepTimer.Elapsed += (sender, e) => Sweep (); - } - - private void stop (PayloadData payloadData, bool send) - { - var bytes = send - ? WebSocketFrame.CreateCloseFrame (payloadData, false).ToArray () - : null; - - lock (_sync) { - _state = ServerState.ShuttingDown; - - _sweepTimer.Enabled = false; - foreach (var session in _sessions.Values.ToList ()) - session.Context.WebSocket.Close (payloadData, bytes); - - _state = ServerState.Stop; - } - } - - private bool tryGetSession (string id, out IWebSocketSession session) - { - session = null; - - if (_state != ServerState.Start) - return false; - - lock (_sync) { - if (_state != ServerState.Start) - return false; - - return _sessions.TryGetValue (id, out session); - } - } - - #endregion - - #region Internal Methods - - internal string Add (IWebSocketSession session) - { - lock (_sync) { - if (_state != ServerState.Start) - return null; - - var id = createID (); - _sessions.Add (id, session); - - return id; - } - } - - internal void Broadcast ( - Opcode opcode, byte[] data, Dictionary cache - ) - { - foreach (var session in Sessions) { - if (_state != ServerState.Start) { - _log.Error ("The service is shutting down."); - break; + /// + /// Gets the session instances in the WebSocket service. + /// + /// + /// + /// An IEnumerable<IWebSocketSession> instance. + /// + /// + /// It provides an enumerator which supports the iteration over + /// the collection of the session instances. + /// + /// + public IEnumerable Sessions + { + get + { + if (_state != ServerState.Start) + return Enumerable.Empty(); + + lock (_sync) + { + if (_state != ServerState.Start) + return Enumerable.Empty(); + + return _sessions.Values.ToList(); + } + } } - session.Context.WebSocket.Send (opcode, data, cache); - } - } - - internal void Broadcast ( - Opcode opcode, Stream stream, Dictionary cache - ) - { - foreach (var session in Sessions) { - if (_state != ServerState.Start) { - _log.Error ("The service is shutting down."); - break; + /// + /// Gets or sets the time to wait for the response to the WebSocket Ping or + /// Close. + /// + /// + /// The set operation does nothing if the service has already started or + /// it is shutting down. + /// + /// + /// A to wait for the response. + /// + /// + /// The value specified for a set operation is zero or less. + /// + public TimeSpan WaitTime + { + get + { + return _waitTime; + } + + set + { + if (value <= TimeSpan.Zero) + throw new ArgumentOutOfRangeException("value", "Zero or less."); + + string msg; + if (!canSet(out msg)) + { + _log.Warn(msg); + return; + } + + lock (_sync) + { + if (!canSet(out msg)) + { + _log.Warn(msg); + return; + } + + _waitTime = value; + } + } } - session.Context.WebSocket.Send (opcode, stream, cache); - } - } - - internal Dictionary Broadping ( - byte[] frameAsBytes, TimeSpan timeout - ) - { - var ret = new Dictionary (); - - foreach (var session in Sessions) { - if (_state != ServerState.Start) { - _log.Error ("The service is shutting down."); - break; + #endregion + + #region Private Methods + + private void broadcast(Opcode opcode, byte[] data, Action completed) + { + var cache = new Dictionary(); + + try + { + foreach (var session in Sessions) + { + if (_state != ServerState.Start) + { + _log.Error("The service is shutting down."); + break; + } + + session.Context.WebSocket.Send(opcode, data, cache); + } + + if (completed != null) + completed(); + } + catch (Exception ex) + { + _log.Error(ex.Message); + _log.Debug(ex.ToString()); + } + finally + { + cache.Clear(); + } } - var res = session.Context.WebSocket.Ping (frameAsBytes, timeout); - ret.Add (session.ID, res); - } + private void broadcast(Opcode opcode, Stream stream, Action completed) + { + var cache = new Dictionary(); + + try + { + foreach (var session in Sessions) + { + if (_state != ServerState.Start) + { + _log.Error("The service is shutting down."); + break; + } + + session.Context.WebSocket.Send(opcode, stream, cache); + } + + if (completed != null) + completed(); + } + catch (Exception ex) + { + _log.Error(ex.Message); + _log.Debug(ex.ToString()); + } + finally + { + foreach (var cached in cache.Values) + cached.Dispose(); + + cache.Clear(); + } + } - return ret; - } + private void broadcastAsync(Opcode opcode, byte[] data, Action completed) + { + ThreadPool.QueueUserWorkItem( + state => broadcast(opcode, data, completed) + ); + } - internal bool Remove (string id) - { - lock (_sync) - return _sessions.Remove (id); - } + private void broadcastAsync(Opcode opcode, Stream stream, Action completed) + { + ThreadPool.QueueUserWorkItem( + state => broadcast(opcode, stream, completed) + ); + } +#if __PING_ASYNC + private void broadpingAsync(byte[] frameAsBytes) + { + + foreach (var session in Sessions) + { + if (_state != ServerState.Start) + { + _log.Error("The service is shutting down."); + break; + } + + session.Context.WebSocket.PingAsync(frameAsBytes, _waitTime, null); + } + } +#endif + private Dictionary broadping(byte[] frameAsBytes) + { + var ret = new Dictionary(); + + foreach (var session in Sessions) + { + if (_state != ServerState.Start) + { + _log.Error("The service is shutting down."); + break; + } + + var res = session.Context.WebSocket.Ping(frameAsBytes, _waitTime); + ret.Add(session.ID, res); + } + + return ret; + } + private bool canSet(out string message) + { + message = null; + + if (_state == ServerState.Start) + { + message = "The service has already started."; + return false; + } + + if (_state == ServerState.ShuttingDown) + { + message = "The service is shutting down."; + return false; + } + + return true; + } - internal void Start () - { - lock (_sync) { - _sweepTimer.Enabled = _clean; - _state = ServerState.Start; - } - } + private static string createID() + { + return Guid.NewGuid().ToString("N"); - internal void Stop (ushort code, string reason) - { - if (code == 1005) { // == no status - stop (PayloadData.Empty, true); - return; - } + } - stop (new PayloadData (code, reason), !code.IsReserved ()); - } + private void setSweepTimer(double interval) + { + _sweepTimer = new System.Timers.Timer(interval); + _sweepTimer.Elapsed += (sender, e) => Sweep(); + } - #endregion + private void stop(PayloadData payloadData, bool send) + { + var bytes = send + ? WebSocketFrame.CreateCloseFrame(payloadData, false).ToArray() + : null; - #region Public Methods + lock (_sync) + { + _state = ServerState.ShuttingDown; - /// - /// Sends to every client in the WebSocket service. - /// - /// - /// An array of that represents the binary data to send. - /// - /// - /// The current state of the manager is not Start. - /// - /// - /// is . - /// - public void Broadcast (byte[] data) - { - if (_state != ServerState.Start) { - var msg = "The current state of the manager is not Start."; - throw new InvalidOperationException (msg); - } - - if (data == null) - throw new ArgumentNullException ("data"); - - if (data.LongLength <= WebSocket.FragmentLength) - broadcast (Opcode.Binary, data, null); - else - broadcast (Opcode.Binary, new MemoryStream (data), null); - } + _sweepTimer.Enabled = false; + foreach (var session in _sessions.Values.ToList()) + session.Context.WebSocket.Close(payloadData, bytes); - /// - /// Sends to every client in the WebSocket service. - /// - /// - /// A that represents the text data to send. - /// - /// - /// The current state of the manager is not Start. - /// - /// - /// is . - /// - /// - /// could not be UTF-8-encoded. - /// - public void Broadcast (string data) - { - if (_state != ServerState.Start) { - var msg = "The current state of the manager is not Start."; - 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"); - } - - if (bytes.LongLength <= WebSocket.FragmentLength) - broadcast (Opcode.Text, bytes, null); - else - broadcast (Opcode.Text, new MemoryStream (bytes), null); - } + _state = ServerState.Stop; + } + } - /// - /// Sends the data from to every client in - /// the WebSocket service. - /// - /// - /// The data is sent as the binary data. - /// - /// - /// A instance from which to read the data to send. - /// - /// - /// An that specifies the number of bytes to send. - /// - /// - /// The current state of the manager is not Start. - /// - /// - /// is . - /// - /// - /// - /// cannot be read. - /// - /// - /// -or- - /// - /// - /// is less than 1. - /// - /// - /// -or- - /// - /// - /// No data could be read from . - /// - /// - public void Broadcast (Stream stream, int length) - { - if (_state != ServerState.Start) { - var msg = "The current state of the manager is not Start."; - 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) { - _log.Warn ( - String.Format ( - "Only {0} byte(s) of data could be read from the stream.", - len - ) - ); - } - - if (len <= WebSocket.FragmentLength) - broadcast (Opcode.Binary, bytes, null); - else - broadcast (Opcode.Binary, new MemoryStream (bytes), null); - } + private bool tryGetSession(string id, out IWebSocketSession session) + { + session = null; - /// - /// Sends asynchronously to every client in - /// the WebSocket service. - /// - /// - /// This method does not wait for the send to be complete. - /// - /// - /// An array of that represents the binary data to send. - /// - /// - /// - /// An delegate or - /// if not needed. - /// - /// - /// The delegate invokes the method called when the send is complete. - /// - /// - /// - /// The current state of the manager is not Start. - /// - /// - /// is . - /// - public void BroadcastAsync (byte[] data, Action completed) - { - if (_state != ServerState.Start) { - var msg = "The current state of the manager is not Start."; - throw new InvalidOperationException (msg); - } - - if (data == null) - throw new ArgumentNullException ("data"); - - if (data.LongLength <= WebSocket.FragmentLength) - broadcastAsync (Opcode.Binary, data, completed); - else - broadcastAsync (Opcode.Binary, new MemoryStream (data), completed); - } + if (_state != ServerState.Start) + return false; - /// - /// Sends asynchronously to every client in - /// the WebSocket service. - /// - /// - /// This method does not wait for the send to be complete. - /// - /// - /// A that represents the text data to send. - /// - /// - /// - /// An delegate or - /// if not needed. - /// - /// - /// The delegate invokes the method called when the send is complete. - /// - /// - /// - /// The current state of the manager is not Start. - /// - /// - /// is . - /// - /// - /// could not be UTF-8-encoded. - /// - public void BroadcastAsync (string data, Action completed) - { - if (_state != ServerState.Start) { - var msg = "The current state of the manager is not Start."; - 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"); - } - - if (bytes.LongLength <= WebSocket.FragmentLength) - broadcastAsync (Opcode.Text, bytes, completed); - else - broadcastAsync (Opcode.Text, new MemoryStream (bytes), completed); - } + lock (_sync) + { + if (_state != ServerState.Start) + return false; - /// - /// Sends the data from asynchronously to - /// every client in the WebSocket service. - /// - /// - /// - /// The data is sent as the binary data. - /// - /// - /// This method does not wait for the send to be complete. - /// - /// - /// - /// A instance from which to read the data to send. - /// - /// - /// An that specifies the number of bytes to send. - /// - /// - /// - /// An delegate or - /// if not needed. - /// - /// - /// The delegate invokes the method called when the send is complete. - /// - /// - /// - /// The current state of the manager is not Start. - /// - /// - /// is . - /// - /// - /// - /// cannot be read. - /// - /// - /// -or- - /// - /// - /// is less than 1. - /// - /// - /// -or- - /// - /// - /// No data could be read from . - /// - /// - public void BroadcastAsync (Stream stream, int length, Action completed) - { - if (_state != ServerState.Start) { - var msg = "The current state of the manager is not Start."; - 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) { - _log.Warn ( - String.Format ( - "Only {0} byte(s) of data could be read from the stream.", - len - ) - ); - } - - if (len <= WebSocket.FragmentLength) - broadcastAsync (Opcode.Binary, bytes, completed); - else - broadcastAsync (Opcode.Binary, new MemoryStream (bytes), completed); - } + return _sessions.TryGetValue(id, out session); + } + } - /// - /// Sends a ping to every client in the WebSocket service. - /// - /// - /// - /// A Dictionary<string, bool>. - /// - /// - /// It represents a collection of pairs of a session ID and - /// a value indicating whether a pong has been received from - /// the client within a time. - /// - /// - /// - /// The current state of the manager is not Start. - /// - [Obsolete ("This method will be removed.")] - public Dictionary Broadping () - { - if (_state != ServerState.Start) { - var msg = "The current state of the manager is not Start."; - throw new InvalidOperationException (msg); - } + #endregion - return Broadping (WebSocketFrame.EmptyPingBytes, _waitTime); - } + #region Internal Methods - /// - /// Sends a ping with to every client in - /// the WebSocket service. - /// - /// - /// - /// A Dictionary<string, bool>. - /// - /// - /// It represents a collection of pairs of a session ID and - /// a value indicating whether a pong has been received from - /// the client within a time. - /// - /// - /// - /// - /// A that represents the message to send. - /// - /// - /// The size must be 125 bytes or less in UTF-8. - /// - /// - /// - /// The current state of the manager is not Start. - /// - /// - /// could not be UTF-8-encoded. - /// - /// - /// The size of is greater than 125 bytes. - /// - [Obsolete ("This method will be removed.")] - public Dictionary Broadping (string message) - { - if (_state != ServerState.Start) { - var msg = "The current state of the manager is not Start."; - throw new InvalidOperationException (msg); - } - - if (message.IsNullOrEmpty ()) - return Broadping (WebSocketFrame.EmptyPingBytes, _waitTime); - - 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); - } - - var frame = WebSocketFrame.CreatePingFrame (bytes, false); - return Broadping (frame.ToArray (), _waitTime); - } - - /// - /// Closes the specified session. - /// - /// - /// A that represents the ID of the session to close. - /// - /// - /// is . - /// - /// - /// is an empty string. - /// - /// - /// The session could not be found. - /// - public void CloseSession (string id) - { - IWebSocketSession session; - if (!TryGetSession (id, out session)) { - var msg = "The session could not be found."; - throw new InvalidOperationException (msg); - } + internal string Add(IWebSocketSession session) + { + lock (_sync) + { + if (_state != ServerState.Start) + return null; - session.Context.WebSocket.Close (); - } + var id = session.Context.QueryString["station_code"]; + if (string.IsNullOrEmpty(id)) id = createID(); + if (_sessions.ContainsKey(id)) _sessions[id] = session; + else _sessions.Add(id, session); + return id; + } + } - /// - /// Closes the specified session with and - /// . - /// - /// - /// A that represents the ID of the session to close. - /// - /// - /// - /// A that represents the status code indicating - /// the reason for the close. - /// - /// - /// The status codes are defined in - /// - /// Section 7.4 of RFC 6455. - /// - /// - /// - /// - /// A that represents the reason for the close. - /// - /// - /// The size must be 123 bytes or less in UTF-8. - /// - /// - /// - /// is . - /// - /// - /// - /// is an empty string. - /// - /// - /// -or- - /// - /// - /// is 1010 (mandatory extension). - /// - /// - /// -or- - /// - /// - /// is 1005 (no status) and there is - /// . - /// - /// - /// -or- - /// - /// - /// could not be UTF-8-encoded. - /// - /// - /// - /// The session could not be found. - /// - /// - /// - /// is less than 1000 or greater than 4999. - /// - /// - /// -or- - /// - /// - /// The size of is greater than 123 bytes. - /// - /// - public void CloseSession (string id, ushort code, string reason) - { - IWebSocketSession session; - if (!TryGetSession (id, out session)) { - var msg = "The session could not be found."; - throw new InvalidOperationException (msg); - } + internal void Broadcast( + Opcode opcode, byte[] data, Dictionary cache + ) + { + foreach (var session in Sessions) + { + if (_state != ServerState.Start) + { + _log.Error("The service is shutting down."); + break; + } + + session.Context.WebSocket.Send(opcode, data, cache); + } + } - session.Context.WebSocket.Close (code, reason); - } + internal void Broadcast( + Opcode opcode, Stream stream, Dictionary cache + ) + { + foreach (var session in Sessions) + { + if (_state != ServerState.Start) + { + _log.Error("The service is shutting down."); + break; + } + + session.Context.WebSocket.Send(opcode, stream, cache); + } + } - /// - /// Closes the specified session with and - /// . - /// - /// - /// A that represents the ID of the session to close. - /// - /// - /// - /// One of the enum values. - /// - /// - /// It represents the status code indicating the reason for the close. - /// - /// - /// - /// - /// A that represents the reason for the close. - /// - /// - /// The size must be 123 bytes or less in UTF-8. - /// - /// - /// - /// is . - /// - /// - /// - /// is an empty string. - /// - /// - /// -or- - /// - /// - /// is - /// . - /// - /// - /// -or- - /// - /// - /// is - /// and there is - /// . - /// - /// - /// -or- - /// - /// - /// could not be UTF-8-encoded. - /// - /// - /// - /// The session could not be found. - /// - /// - /// The size of is greater than 123 bytes. - /// - public void CloseSession (string id, CloseStatusCode code, string reason) - { - IWebSocketSession session; - if (!TryGetSession (id, out session)) { - var msg = "The session could not be found."; - throw new InvalidOperationException (msg); - } + internal Dictionary Broadping( + byte[] frameAsBytes, TimeSpan timeout + ) + { + var ret = new Dictionary(); + + foreach (var session in Sessions) + { + if (_state != ServerState.Start) + { + _log.Error("The service is shutting down."); + break; + } + + var res = session.Context.WebSocket.Ping(frameAsBytes, timeout); + ret.Add(session.ID, res); + } + + return ret; + } - session.Context.WebSocket.Close (code, reason); - } + internal bool Remove(string id) + { + lock (_sync) + return _sessions.Remove(id); + } - /// - /// Sends a ping to the client using the specified session. - /// - /// - /// true if the send has done with no error and a pong has been - /// received from the client within a time; otherwise, false. - /// - /// - /// A that represents the ID of the session. - /// - /// - /// is . - /// - /// - /// is an empty string. - /// - /// - /// The session could not be found. - /// - public bool PingTo (string id) - { - IWebSocketSession session; - if (!TryGetSession (id, out session)) { - var msg = "The session could not be found."; - throw new InvalidOperationException (msg); - } + internal void Start() + { + lock (_sync) + { + _sweepTimer.Enabled = _clean; + _state = ServerState.Start; + } + } - return session.Context.WebSocket.Ping (); - } + internal void Stop(ushort code, string reason) + { + if (code == 1005) + { // == no status + stop(PayloadData.Empty, true); + return; + } - /// - /// Sends a ping with to the client using - /// the specified session. - /// - /// - /// true if the send has done with no error and a pong has been - /// received from the client within a time; otherwise, false. - /// - /// - /// - /// A that represents the message to send. - /// - /// - /// The size must be 125 bytes or less in UTF-8. - /// - /// - /// - /// A that represents the ID of the session. - /// - /// - /// is . - /// - /// - /// - /// is an empty string. - /// - /// - /// -or- - /// - /// - /// could not be UTF-8-encoded. - /// - /// - /// - /// The session could not be found. - /// - /// - /// The size of is greater than 125 bytes. - /// - public bool PingTo (string message, string id) - { - IWebSocketSession session; - if (!TryGetSession (id, out session)) { - var msg = "The session could not be found."; - throw new InvalidOperationException (msg); - } + stop(new PayloadData(code, reason), !code.IsReserved()); + } - return session.Context.WebSocket.Ping (message); - } + #endregion + + #region Public Methods + + /// + /// Sends to every client in the WebSocket service. + /// + /// + /// An array of that represents the binary data to send. + /// + /// + /// The current state of the manager is not Start. + /// + /// + /// is . + /// + public void Broadcast(byte[] data) + { + if (_state != ServerState.Start) + { + var msg = "The current state of the manager is not Start."; + throw new InvalidOperationException(msg); + } + + if (data == null) + throw new ArgumentNullException("data"); + + if (data.LongLength <= WebSocket.FragmentLength) + broadcast(Opcode.Binary, data, null); + else + broadcast(Opcode.Binary, new MemoryStream(data), null); + } - /// - /// Sends to the client using the specified session. - /// - /// - /// An array of that represents the binary data to send. - /// - /// - /// A that represents the ID of the session. - /// - /// - /// - /// is . - /// - /// - /// -or- - /// - /// - /// is . - /// - /// - /// - /// is an empty string. - /// - /// - /// - /// The session could not be found. - /// - /// - /// -or- - /// - /// - /// The current state of the WebSocket connection is not Open. - /// - /// - public void SendTo (byte[] data, string id) - { - IWebSocketSession session; - if (!TryGetSession (id, out session)) { - var msg = "The session could not be found."; - throw new InvalidOperationException (msg); - } + /// + /// Sends to every client in the WebSocket service. + /// + /// + /// A that represents the text data to send. + /// + /// + /// The current state of the manager is not Start. + /// + /// + /// is . + /// + /// + /// could not be UTF-8-encoded. + /// + public void Broadcast(string data) + { + if (_state != ServerState.Start) + { + var msg = "The current state of the manager is not Start."; + 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"); + } + + if (bytes.LongLength <= WebSocket.FragmentLength) + broadcast(Opcode.Text, bytes, null); + else + broadcast(Opcode.Text, new MemoryStream(bytes), null); + } - session.Context.WebSocket.Send (data); - } + /// + /// Sends the data from to every client in + /// the WebSocket service. + /// + /// + /// The data is sent as the binary data. + /// + /// + /// A instance from which to read the data to send. + /// + /// + /// An that specifies the number of bytes to send. + /// + /// + /// The current state of the manager is not Start. + /// + /// + /// is . + /// + /// + /// + /// cannot be read. + /// + /// + /// -or- + /// + /// + /// is less than 1. + /// + /// + /// -or- + /// + /// + /// No data could be read from . + /// + /// + public void Broadcast(Stream stream, int length) + { + if (_state != ServerState.Start) + { + var msg = "The current state of the manager is not Start."; + 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) + { + _log.Warn( + String.Format( + "Only {0} byte(s) of data could be read from the stream.", + len + ) + ); + } + + if (len <= WebSocket.FragmentLength) + broadcast(Opcode.Binary, bytes, null); + else + broadcast(Opcode.Binary, new MemoryStream(bytes), null); + } - /// - /// Sends to the client using the specified session. - /// - /// - /// A that represents the text data to send. - /// - /// - /// A that represents the ID of the session. - /// - /// - /// - /// is . - /// - /// - /// -or- - /// - /// - /// is . - /// - /// - /// - /// - /// is an empty string. - /// - /// - /// -or- - /// - /// - /// could not be UTF-8-encoded. - /// - /// - /// - /// - /// The session could not be found. - /// - /// - /// -or- - /// - /// - /// The current state of the WebSocket connection is not Open. - /// - /// - public void SendTo (string data, string id) - { - IWebSocketSession session; - if (!TryGetSession (id, out session)) { - var msg = "The session could not be found."; - throw new InvalidOperationException (msg); - } + /// + /// Sends asynchronously to every client in + /// the WebSocket service. + /// + /// + /// This method does not wait for the send to be complete. + /// + /// + /// An array of that represents the binary data to send. + /// + /// + /// + /// An delegate or + /// if not needed. + /// + /// + /// The delegate invokes the method called when the send is complete. + /// + /// + /// + /// The current state of the manager is not Start. + /// + /// + /// is . + /// + public void BroadcastAsync(byte[] data, Action completed) + { + if (_state != ServerState.Start) + { + var msg = "The current state of the manager is not Start."; + throw new InvalidOperationException(msg); + } + + if (data == null) + throw new ArgumentNullException("data"); + + if (data.LongLength <= WebSocket.FragmentLength) + broadcastAsync(Opcode.Binary, data, completed); + else + broadcastAsync(Opcode.Binary, new MemoryStream(data), completed); + } - session.Context.WebSocket.Send (data); - } + /// + /// Sends asynchronously to every client in + /// the WebSocket service. + /// + /// + /// This method does not wait for the send to be complete. + /// + /// + /// A that represents the text data to send. + /// + /// + /// + /// An delegate or + /// if not needed. + /// + /// + /// The delegate invokes the method called when the send is complete. + /// + /// + /// + /// The current state of the manager is not Start. + /// + /// + /// is . + /// + /// + /// could not be UTF-8-encoded. + /// + public void BroadcastAsync(string data, Action completed) + { + if (_state != ServerState.Start) + { + var msg = "The current state of the manager is not Start."; + 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"); + } + + if (bytes.LongLength <= WebSocket.FragmentLength) + broadcastAsync(Opcode.Text, bytes, completed); + else + broadcastAsync(Opcode.Text, new MemoryStream(bytes), completed); + } - /// - /// Sends the data from to the client using - /// the specified session. - /// - /// - /// The data is sent as the binary data. - /// - /// - /// A instance from which to read the data to send. - /// - /// - /// An that specifies the number of bytes to send. - /// - /// - /// A that represents the ID of the session. - /// - /// - /// - /// is . - /// - /// - /// -or- - /// - /// - /// is . - /// - /// - /// - /// - /// is an empty string. - /// - /// - /// -or- - /// - /// - /// cannot be read. - /// - /// - /// -or- - /// - /// - /// is less than 1. - /// - /// - /// -or- - /// - /// - /// No data could be read from . - /// - /// - /// - /// - /// The session could not be found. - /// - /// - /// -or- - /// - /// - /// The current state of the WebSocket connection is not Open. - /// - /// - public void SendTo (Stream stream, int length, string id) - { - IWebSocketSession session; - if (!TryGetSession (id, out session)) { - var msg = "The session could not be found."; - throw new InvalidOperationException (msg); - } + /// + /// Sends the data from asynchronously to + /// every client in the WebSocket service. + /// + /// + /// + /// The data is sent as the binary data. + /// + /// + /// This method does not wait for the send to be complete. + /// + /// + /// + /// A instance from which to read the data to send. + /// + /// + /// An that specifies the number of bytes to send. + /// + /// + /// + /// An delegate or + /// if not needed. + /// + /// + /// The delegate invokes the method called when the send is complete. + /// + /// + /// + /// The current state of the manager is not Start. + /// + /// + /// is . + /// + /// + /// + /// cannot be read. + /// + /// + /// -or- + /// + /// + /// is less than 1. + /// + /// + /// -or- + /// + /// + /// No data could be read from . + /// + /// + public void BroadcastAsync(Stream stream, int length, Action completed) + { + if (_state != ServerState.Start) + { + var msg = "The current state of the manager is not Start."; + 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) + { + _log.Warn( + String.Format( + "Only {0} byte(s) of data could be read from the stream.", + len + ) + ); + } + + if (len <= WebSocket.FragmentLength) + broadcastAsync(Opcode.Binary, bytes, completed); + else + broadcastAsync(Opcode.Binary, new MemoryStream(bytes), completed); + } - session.Context.WebSocket.Send (stream, length); - } + /// + /// Sends a ping to every client in the WebSocket service. + /// + /// + /// + /// A Dictionary<string, bool>. + /// + /// + /// It represents a collection of pairs of a session ID and + /// a value indicating whether a pong has been received from + /// the client within a time. + /// + /// + /// + /// The current state of the manager is not Start. + /// + [Obsolete("This method will be removed.")] + public Dictionary Broadping() + { + if (_state != ServerState.Start) + { + var msg = "The current state of the manager is not Start."; + throw new InvalidOperationException(msg); + } + + return Broadping(WebSocketFrame.EmptyPingBytes, _waitTime); + } - /// - /// Sends asynchronously to the client using - /// the specified session. - /// - /// - /// This method does not wait for the send to be complete. - /// - /// - /// An array of that represents the binary data to send. - /// - /// - /// A that represents the ID of the session. - /// - /// - /// - /// An Action<bool> delegate or - /// if not needed. - /// - /// - /// The delegate invokes the method called when the send is complete. - /// - /// - /// true is passed to the method if the send has done with - /// no error; otherwise, false. - /// - /// - /// - /// - /// is . - /// - /// - /// -or- - /// - /// - /// is . - /// - /// - /// - /// is an empty string. - /// - /// - /// - /// The session could not be found. - /// - /// - /// -or- - /// - /// - /// The current state of the WebSocket connection is not Open. - /// - /// - public void SendToAsync (byte[] data, string id, Action completed) - { - IWebSocketSession session; - if (!TryGetSession (id, out session)) { - var msg = "The session could not be found."; - throw new InvalidOperationException (msg); - } + /// + /// Sends a ping with to every client in + /// the WebSocket service. + /// + /// + /// + /// A Dictionary<string, bool>. + /// + /// + /// It represents a collection of pairs of a session ID and + /// a value indicating whether a pong has been received from + /// the client within a time. + /// + /// + /// + /// + /// A that represents the message to send. + /// + /// + /// The size must be 125 bytes or less in UTF-8. + /// + /// + /// + /// The current state of the manager is not Start. + /// + /// + /// could not be UTF-8-encoded. + /// + /// + /// The size of is greater than 125 bytes. + /// + [Obsolete("This method will be removed.")] + public Dictionary Broadping(string message) + { + if (_state != ServerState.Start) + { + var msg = "The current state of the manager is not Start."; + throw new InvalidOperationException(msg); + } + + if (message.IsNullOrEmpty()) + return Broadping(WebSocketFrame.EmptyPingBytes, _waitTime); + + 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); + } + + var frame = WebSocketFrame.CreatePingFrame(bytes, false); + return Broadping(frame.ToArray(), _waitTime); + } - session.Context.WebSocket.SendAsync (data, completed); - } + /// + /// Closes the specified session. + /// + /// + /// A that represents the ID of the session to close. + /// + /// + /// is . + /// + /// + /// is an empty string. + /// + /// + /// The session could not be found. + /// + public void CloseSession(string id) + { + IWebSocketSession session; + if (!TryGetSession(id, out session)) + { + var msg = "The session could not be found."; + throw new InvalidOperationException(msg); + } + + session.Context.WebSocket.Close(); + } - /// - /// Sends asynchronously to the client using - /// the specified session. - /// - /// - /// This method does not wait for the send to be complete. - /// - /// - /// A that represents the text data to send. - /// - /// - /// A that represents the ID of the session. - /// - /// - /// - /// An Action<bool> delegate or - /// if not needed. - /// - /// - /// The delegate invokes the method called when the send is complete. - /// - /// - /// true is passed to the method if the send has done with - /// no error; otherwise, false. - /// - /// - /// - /// - /// is . - /// - /// - /// -or- - /// - /// - /// is . - /// - /// - /// - /// - /// is an empty string. - /// - /// - /// -or- - /// - /// - /// could not be UTF-8-encoded. - /// - /// - /// - /// - /// The session could not be found. - /// - /// - /// -or- - /// - /// - /// The current state of the WebSocket connection is not Open. - /// - /// - public void SendToAsync (string data, string id, Action completed) - { - IWebSocketSession session; - if (!TryGetSession (id, out session)) { - var msg = "The session could not be found."; - throw new InvalidOperationException (msg); - } + /// + /// Closes the specified session with and + /// . + /// + /// + /// A that represents the ID of the session to close. + /// + /// + /// + /// A that represents the status code indicating + /// the reason for the close. + /// + /// + /// The status codes are defined in + /// + /// Section 7.4 of RFC 6455. + /// + /// + /// + /// + /// A that represents the reason for the close. + /// + /// + /// The size must be 123 bytes or less in UTF-8. + /// + /// + /// + /// is . + /// + /// + /// + /// is an empty string. + /// + /// + /// -or- + /// + /// + /// is 1010 (mandatory extension). + /// + /// + /// -or- + /// + /// + /// is 1005 (no status) and there is + /// . + /// + /// + /// -or- + /// + /// + /// could not be UTF-8-encoded. + /// + /// + /// + /// The session could not be found. + /// + /// + /// + /// is less than 1000 or greater than 4999. + /// + /// + /// -or- + /// + /// + /// The size of is greater than 123 bytes. + /// + /// + public void CloseSession(string id, ushort code, string reason) + { + IWebSocketSession session; + if (!TryGetSession(id, out session)) + { + var msg = "The session could not be found."; + throw new InvalidOperationException(msg); + } + + session.Context.WebSocket.Close(code, reason); + } - session.Context.WebSocket.SendAsync (data, completed); - } + /// + /// Closes the specified session with and + /// . + /// + /// + /// A that represents the ID of the session to close. + /// + /// + /// + /// One of the enum values. + /// + /// + /// It represents the status code indicating the reason for the close. + /// + /// + /// + /// + /// A that represents the reason for the close. + /// + /// + /// The size must be 123 bytes or less in UTF-8. + /// + /// + /// + /// is . + /// + /// + /// + /// is an empty string. + /// + /// + /// -or- + /// + /// + /// is + /// . + /// + /// + /// -or- + /// + /// + /// is + /// and there is + /// . + /// + /// + /// -or- + /// + /// + /// could not be UTF-8-encoded. + /// + /// + /// + /// The session could not be found. + /// + /// + /// The size of is greater than 123 bytes. + /// + public void CloseSession(string id, CloseStatusCode code, string reason) + { + IWebSocketSession session; + if (!TryGetSession(id, out session)) + { + var msg = "The session could not be found."; + throw new InvalidOperationException(msg); + } + + session.Context.WebSocket.Close(code, reason); + } - /// - /// Sends the data from asynchronously to - /// the client using the specified session. - /// - /// - /// - /// The data is sent as the binary data. - /// - /// - /// This method does not wait for the send to be complete. - /// - /// - /// - /// A instance from which to read the data to send. - /// - /// - /// An that specifies the number of bytes to send. - /// - /// - /// A that represents the ID of the session. - /// - /// - /// - /// An Action<bool> delegate or - /// if not needed. - /// - /// - /// The delegate invokes the method called when the send is complete. - /// - /// - /// true is passed to the method if the send has done with - /// no error; otherwise, false. - /// - /// - /// - /// - /// is . - /// - /// - /// -or- - /// - /// - /// is . - /// - /// - /// - /// - /// is an empty string. - /// - /// - /// -or- - /// - /// - /// cannot be read. - /// - /// - /// -or- - /// - /// - /// is less than 1. - /// - /// - /// -or- - /// - /// - /// No data could be read from . - /// - /// - /// - /// - /// The session could not be found. - /// - /// - /// -or- - /// - /// - /// The current state of the WebSocket connection is not Open. - /// - /// - public void SendToAsync ( - Stream stream, int length, string id, Action completed - ) - { - IWebSocketSession session; - if (!TryGetSession (id, out session)) { - var msg = "The session could not be found."; - throw new InvalidOperationException (msg); - } + /// + /// Sends a ping to the client using the specified session. + /// + /// + /// true if the send has done with no error and a pong has been + /// received from the client within a time; otherwise, false. + /// + /// + /// A that represents the ID of the session. + /// + /// + /// is . + /// + /// + /// is an empty string. + /// + /// + /// The session could not be found. + /// + public bool PingTo(string id) + { + IWebSocketSession session; + if (!TryGetSession(id, out session)) + { + var msg = "The session could not be found."; + throw new InvalidOperationException(msg); + } + + return session.Context.WebSocket.Ping(); + } - session.Context.WebSocket.SendAsync (stream, length, completed); - } + /// + /// Sends a ping with to the client using + /// the specified session. + /// + /// + /// true if the send has done with no error and a pong has been + /// received from the client within a time; otherwise, false. + /// + /// + /// + /// A that represents the message to send. + /// + /// + /// The size must be 125 bytes or less in UTF-8. + /// + /// + /// + /// A that represents the ID of the session. + /// + /// + /// is . + /// + /// + /// + /// is an empty string. + /// + /// + /// -or- + /// + /// + /// could not be UTF-8-encoded. + /// + /// + /// + /// The session could not be found. + /// + /// + /// The size of is greater than 125 bytes. + /// + public bool PingTo(string message, string id) + { + IWebSocketSession session; + if (!TryGetSession(id, out session)) + { + var msg = "The session could not be found."; + throw new InvalidOperationException(msg); + } + + return session.Context.WebSocket.Ping(message); + } - /// - /// Cleans up the inactive sessions in the WebSocket service. - /// - public void Sweep () - { - if (_sweeping) { - _log.Info ("The sweeping is already in progress."); - return; - } - - lock (_forSweep) { - if (_sweeping) { - _log.Info ("The sweeping is already in progress."); - return; + /// + /// Sends to the client using the specified session. + /// + /// + /// An array of that represents the binary data to send. + /// + /// + /// A that represents the ID of the session. + /// + /// + /// + /// is . + /// + /// + /// -or- + /// + /// + /// is . + /// + /// + /// + /// is an empty string. + /// + /// + /// + /// The session could not be found. + /// + /// + /// -or- + /// + /// + /// The current state of the WebSocket connection is not Open. + /// + /// + public void SendTo(byte[] data, string id) + { + IWebSocketSession session; + if (!TryGetSession(id, out session)) + { + var msg = "The session could not be found."; + throw new InvalidOperationException(msg); + } + + session.Context.WebSocket.Send(data); } - _sweeping = true; - } + /// + /// Sends to the client using the specified session. + /// + /// + /// A that represents the text data to send. + /// + /// + /// A that represents the ID of the session. + /// + /// + /// + /// is . + /// + /// + /// -or- + /// + /// + /// is . + /// + /// + /// + /// + /// is an empty string. + /// + /// + /// -or- + /// + /// + /// could not be UTF-8-encoded. + /// + /// + /// + /// + /// The session could not be found. + /// + /// + /// -or- + /// + /// + /// The current state of the WebSocket connection is not Open. + /// + /// + public void SendTo(string data, string id) + { + IWebSocketSession session; + if (!TryGetSession(id, out session)) + { + var msg = "The session could not be found."; + throw new InvalidOperationException(msg); + } + + session.Context.WebSocket.Send(data); + } - foreach (var id in InactiveIDs) { - if (_state != ServerState.Start) - break; + /// + /// Sends the data from to the client using + /// the specified session. + /// + /// + /// The data is sent as the binary data. + /// + /// + /// A instance from which to read the data to send. + /// + /// + /// An that specifies the number of bytes to send. + /// + /// + /// A that represents the ID of the session. + /// + /// + /// + /// is . + /// + /// + /// -or- + /// + /// + /// is . + /// + /// + /// + /// + /// is an empty string. + /// + /// + /// -or- + /// + /// + /// cannot be read. + /// + /// + /// -or- + /// + /// + /// is less than 1. + /// + /// + /// -or- + /// + /// + /// No data could be read from . + /// + /// + /// + /// + /// The session could not be found. + /// + /// + /// -or- + /// + /// + /// The current state of the WebSocket connection is not Open. + /// + /// + public void SendTo(Stream stream, int length, string id) + { + IWebSocketSession session; + if (!TryGetSession(id, out session)) + { + var msg = "The session could not be found."; + throw new InvalidOperationException(msg); + } + + session.Context.WebSocket.Send(stream, length); + } - lock (_sync) { - if (_state != ServerState.Start) - break; + /// + /// Sends asynchronously to the client using + /// the specified session. + /// + /// + /// This method does not wait for the send to be complete. + /// + /// + /// An array of that represents the binary data to send. + /// + /// + /// A that represents the ID of the session. + /// + /// + /// + /// An Action<bool> delegate or + /// if not needed. + /// + /// + /// The delegate invokes the method called when the send is complete. + /// + /// + /// true is passed to the method if the send has done with + /// no error; otherwise, false. + /// + /// + /// + /// + /// is . + /// + /// + /// -or- + /// + /// + /// is . + /// + /// + /// + /// is an empty string. + /// + /// + /// + /// The session could not be found. + /// + /// + /// -or- + /// + /// + /// The current state of the WebSocket connection is not Open. + /// + /// + public void SendToAsync(byte[] data, string id, Action completed) + { + IWebSocketSession session; + if (!TryGetSession(id, out session)) + { + var msg = "The session could not be found."; + throw new InvalidOperationException(msg); + } + + session.Context.WebSocket.SendAsync(data, completed); + } - IWebSocketSession session; - if (_sessions.TryGetValue (id, out session)) { - var state = session.ConnectionState; - if (state == WebSocketState.Open) - session.Context.WebSocket.Close (CloseStatusCode.Abnormal); - else if (state == WebSocketState.Closing) - continue; - else - _sessions.Remove (id); - } + /// + /// Sends asynchronously to the client using + /// the specified session. + /// + /// + /// This method does not wait for the send to be complete. + /// + /// + /// A that represents the text data to send. + /// + /// + /// A that represents the ID of the session. + /// + /// + /// + /// An Action<bool> delegate or + /// if not needed. + /// + /// + /// The delegate invokes the method called when the send is complete. + /// + /// + /// true is passed to the method if the send has done with + /// no error; otherwise, false. + /// + /// + /// + /// + /// is . + /// + /// + /// -or- + /// + /// + /// is . + /// + /// + /// + /// + /// is an empty string. + /// + /// + /// -or- + /// + /// + /// could not be UTF-8-encoded. + /// + /// + /// + /// + /// The session could not be found. + /// + /// + /// -or- + /// + /// + /// The current state of the WebSocket connection is not Open. + /// + /// + public void SendToAsync(string data, string id, Action completed) + { + IWebSocketSession session; + if (!TryGetSession(id, out session)) + { + var msg = "The session could not be found."; + throw new InvalidOperationException(msg); + } + + session.Context.WebSocket.SendAsync(data, completed); } - } - _sweeping = false; - } + /// + /// Sends the data from asynchronously to + /// the client using the specified session. + /// + /// + /// + /// The data is sent as the binary data. + /// + /// + /// This method does not wait for the send to be complete. + /// + /// + /// + /// A instance from which to read the data to send. + /// + /// + /// An that specifies the number of bytes to send. + /// + /// + /// A that represents the ID of the session. + /// + /// + /// + /// An Action<bool> delegate or + /// if not needed. + /// + /// + /// The delegate invokes the method called when the send is complete. + /// + /// + /// true is passed to the method if the send has done with + /// no error; otherwise, false. + /// + /// + /// + /// + /// is . + /// + /// + /// -or- + /// + /// + /// is . + /// + /// + /// + /// + /// is an empty string. + /// + /// + /// -or- + /// + /// + /// cannot be read. + /// + /// + /// -or- + /// + /// + /// is less than 1. + /// + /// + /// -or- + /// + /// + /// No data could be read from . + /// + /// + /// + /// + /// The session could not be found. + /// + /// + /// -or- + /// + /// + /// The current state of the WebSocket connection is not Open. + /// + /// + public void SendToAsync( + Stream stream, int length, string id, Action completed + ) + { + IWebSocketSession session; + if (!TryGetSession(id, out session)) + { + var msg = "The session could not be found."; + throw new InvalidOperationException(msg); + } + + session.Context.WebSocket.SendAsync(stream, length, completed); + } - /// - /// Tries to get the session instance with . - /// - /// - /// true if the session is successfully found; otherwise, - /// false. - /// - /// - /// A that represents the ID of the session to find. - /// - /// - /// - /// When this method returns, a - /// instance or if not found. - /// - /// - /// The session instance provides the function to access - /// the information in the session. - /// - /// - /// - /// is . - /// - /// - /// is an empty string. - /// - public bool TryGetSession (string id, out IWebSocketSession session) - { - if (id == null) - throw new ArgumentNullException ("id"); + /// + /// Cleans up the inactive sessions in the WebSocket service. + /// + public void Sweep() + { + if (_sweeping) + { + _log.Info("The sweeping is already in progress."); + return; + } + + lock (_forSweep) + { + if (_sweeping) + { + _log.Info("The sweeping is already in progress."); + return; + } + + _sweeping = true; + } + + foreach (var id in InactiveIDs) + { + if (_state != ServerState.Start) + break; + + lock (_sync) + { + if (_state != ServerState.Start) + break; + + IWebSocketSession session; + if (_sessions.TryGetValue(id, out session)) + { + var state = session.ConnectionState; + if (state == WebSocketState.Open) + session.Context.WebSocket.Close(CloseStatusCode.Abnormal); + else if (state == WebSocketState.Closing) + continue; + else + _sessions.Remove(id); + } + } + } + + _sweeping = false; + } - if (id.Length == 0) - throw new ArgumentException ("An empty string.", "id"); + /// + /// Tries to get the session instance with . + /// + /// + /// true if the session is successfully found; otherwise, + /// false. + /// + /// + /// A that represents the ID of the session to find. + /// + /// + /// + /// When this method returns, a + /// instance or if not found. + /// + /// + /// The session instance provides the function to access + /// the information in the session. + /// + /// + /// + /// is . + /// + /// + /// is an empty string. + /// + public bool TryGetSession(string id, out IWebSocketSession session) + { + if (id == null) + throw new ArgumentNullException("id"); + + if (id.Length == 0) + throw new ArgumentException("An empty string.", "id"); + + return tryGetSession(id, out session); + } - return tryGetSession (id, out session); + #endregion } - - #endregion - } } diff --git a/websocket-sharp/WebSocket.cs b/websocket-sharp/WebSocket.cs index 011dee00d..56659fb02 100644 --- a/websocket-sharp/WebSocket.cs +++ b/websocket-sharp/WebSocket.cs @@ -50,4044 +50,4439 @@ using System.Security.Cryptography; using System.Text; using System.Threading; +using System.Threading.Tasks; using WebSocketSharp.Net; using WebSocketSharp.Net.WebSockets; namespace WebSocketSharp { - /// - /// Implements the WebSocket interface. - /// - /// - /// - /// This class provides a set of methods and properties for two-way - /// communication using the WebSocket protocol. - /// - /// - /// The WebSocket protocol is defined in - /// RFC 6455. - /// - /// - 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 _handshakeRequestChecker; - private bool _ignoreExtensions; - private bool _inContinuation; - private volatile bool _inMessage; - private volatile Logger _logger; - private static readonly int _maxRetryCountForConnect; - private Action _message; - private Queue _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 - - /// - /// Represents the empty array of used internally. - /// - internal static readonly byte[] EmptyBytes; - /// - /// Represents the length used to determine whether the data should be fragmented in sending. + /// Implements the WebSocket interface. /// /// /// - /// The data will be fragmented if that length is greater than the value of this field. + /// This class provides a set of methods and properties for two-way + /// communication using the WebSocket protocol. /// /// - /// If you would like to change the value, you must set it to a value between 125 and - /// Int32.MaxValue - 14 inclusive. + /// The WebSocket protocol is defined in + /// RFC 6455. /// /// - internal static readonly int FragmentLength; - - /// - /// Represents the random number generator used internally. - /// - internal static readonly RandomNumberGenerator RandomNumber; - - #endregion - - #region Static Constructor - - static WebSocket () + public class WebSocket : IDisposable { - _maxRetryCountForConnect = 10; - EmptyBytes = new byte[0]; - FragmentLength = 1016; - RandomNumber = new RNGCryptoServiceProvider (); - } - - #endregion - - #region Internal Constructors + #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 _handshakeRequestChecker; + private bool _ignoreExtensions; + private bool _inContinuation; + private volatile bool _inMessage; + private volatile Logger _logger; + private static readonly int _maxRetryCountForConnect; + private Action _message; + private Queue _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; +#if __PING_ASYNC + private bool _isAlive = true; + private bool _isPinged = true; +#endif + #endregion + + #region Internal Fields + + /// + /// Represents the empty array of used internally. + /// + internal static readonly byte[] EmptyBytes; + + /// + /// Represents the length used to determine whether the data should be fragmented in sending. + /// + /// + /// + /// The data will be fragmented if that length is greater than the value of this field. + /// + /// + /// If you would like to change the value, you must set it to a value between 125 and + /// Int32.MaxValue - 14 inclusive. + /// + /// + internal static readonly int FragmentLength; + + /// + /// Represents the random number generator used internally. + /// + internal static readonly RandomNumberGenerator RandomNumber; + + #endregion + + #region Static Constructor + + static WebSocket() + { + _maxRetryCountForConnect = 10; + EmptyBytes = new byte[0]; + FragmentLength = 1016; + RandomNumber = new RNGCryptoServiceProvider(); + } - // As server - internal WebSocket (HttpListenerWebSocketContext context, string protocol) - { - _context = context; - _protocol = protocol; + #endregion - _closeContext = context.Close; - _logger = context.Log; - _message = messages; - _secure = context.IsSecureConnection; - _stream = context.Stream; - _waitTime = TimeSpan.FromSeconds (1); + #region Internal Constructors - init (); - } + // As server + internal WebSocket(HttpListenerWebSocketContext context, string protocol) + { + _context = context; + _protocol = protocol; - // 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); - _closeContext = context.Close; - _logger = context.Log; - _message = messages; - _secure = context.IsSecureConnection; - _stream = context.Stream; - _waitTime = TimeSpan.FromSeconds (1); + init(); + } - init (); - } + // As server + internal WebSocket(TcpListenerWebSocketContext context, string protocol) + { + _context = context; + _protocol = protocol; - #endregion + _closeContext = context.Close; + _logger = context.Log; + _message = messages; + _secure = context.IsSecureConnection; + _stream = context.Stream; + _waitTime = TimeSpan.FromSeconds(1); - #region Public Constructors + init(); + } - /// - /// Initializes a new instance of the class with - /// and optionally . - /// - /// - /// - /// A that specifies the URL to which to connect. - /// - /// - /// The scheme of the URL must be ws or wss. - /// - /// - /// The new instance uses a secure connection if the scheme is wss. - /// - /// - /// - /// - /// An array of that specifies the names of - /// the subprotocols if necessary. - /// - /// - /// Each value of the array must be a token defined in - /// - /// RFC 2616. - /// - /// - /// - /// is . - /// - /// - /// - /// is an empty string. - /// - /// - /// -or- - /// - /// - /// is an invalid WebSocket URL string. - /// - /// - /// -or- - /// - /// - /// contains a value that is not a token. - /// - /// - /// -or- - /// - /// - /// contains a value twice. - /// - /// - public WebSocket (string url, params string[] protocols) - { - if (url == null) - throw new ArgumentNullException ("url"); + #endregion + + #region Public Constructors + + /// + /// Initializes a new instance of the class with + /// and optionally . + /// + /// + /// + /// A that specifies the URL to which to connect. + /// + /// + /// The scheme of the URL must be ws or wss. + /// + /// + /// The new instance uses a secure connection if the scheme is wss. + /// + /// + /// + /// + /// An array of that specifies the names of + /// the subprotocols if necessary. + /// + /// + /// Each value of the array must be a token defined in + /// + /// RFC 2616. + /// + /// + /// + /// is . + /// + /// + /// + /// is an empty string. + /// + /// + /// -or- + /// + /// + /// is an invalid WebSocket URL string. + /// + /// + /// -or- + /// + /// + /// contains a value that is not a token. + /// + /// + /// -or- + /// + /// + /// contains a value twice. + /// + /// + 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; + } - if (url.Length == 0) - throw new ArgumentException ("An empty string.", "url"); + _base64Key = CreateBase64Key(); + _client = true; + _logger = new Logger(); + _message = messagec; + _secure = _uri.Scheme == "wss"; + _waitTime = TimeSpan.FromSeconds(5); - string msg; - if (!url.TryCreateWebSocketUri (out _uri, out msg)) - throw new ArgumentException (msg, "url"); + init(); + } - if (protocols != null && protocols.Length > 0) { - if (!checkProtocols (protocols, out msg)) - throw new ArgumentException (msg, "protocols"); + #endregion - _protocols = protocols; - } + #region Internal Properties - _base64Key = CreateBase64Key (); - _client = true; - _logger = new Logger (); - _message = messagec; - _secure = _uri.Scheme == "wss"; - _waitTime = TimeSpan.FromSeconds (5); + internal CookieCollection CookieCollection + { + get + { + return _cookies; + } + } - init (); - } + // As server + internal Func CustomHandshakeRequestChecker + { + get + { + return _handshakeRequestChecker; + } - #endregion + set + { + _handshakeRequestChecker = value; + } + } - #region Internal Properties + internal bool HasMessage + { + get + { + lock (_forMessageEventQueue) + return _messageEventQueue.Count > 0; + } + } - internal CookieCollection CookieCollection { - get { - return _cookies; - } - } + // As server + internal bool IgnoreExtensions + { + get + { + return _ignoreExtensions; + } - // As server - internal Func CustomHandshakeRequestChecker { - get { - return _handshakeRequestChecker; - } + set + { + _ignoreExtensions = value; + } + } - set { - _handshakeRequestChecker = value; - } - } + internal bool IsConnected + { + get + { + return _readyState == WebSocketState.Open || _readyState == WebSocketState.Closing; + } + } - internal bool HasMessage { - get { - lock (_forMessageEventQueue) - return _messageEventQueue.Count > 0; - } - } + #endregion + + #region Public Properties + + /// + /// Gets or sets the compression method used to compress a message. + /// + /// + /// The set operation does nothing if the connection has already been + /// established or it is closing. + /// + /// + /// + /// One of the enum values. + /// + /// + /// It specifies the compression method used to compress a message. + /// + /// + /// The default value is . + /// + /// + /// + /// The set operation is not available if this instance is not a client. + /// + public CompressionMethod Compression + { + get + { + return _compression; + } - // As server - internal bool IgnoreExtensions { - get { - return _ignoreExtensions; - } + set + { + string msg = null; + + if (!_client) + { + msg = "This instance is not a client."; + throw new InvalidOperationException(msg); + } + + if (!canSet(out msg)) + { + _logger.Warn(msg); + return; + } + + lock (_forState) + { + if (!canSet(out msg)) + { + _logger.Warn(msg); + return; + } + + _compression = value; + } + } + } - set { - _ignoreExtensions = value; - } - } + /// + /// Gets the HTTP cookies included in the handshake request/response. + /// + /// + /// + /// An + /// instance. + /// + /// + /// It provides an enumerator which supports the iteration over + /// the collection of the cookies. + /// + /// + public IEnumerable Cookies + { + get + { + lock (_cookies.SyncRoot) + { + foreach (Cookie cookie in _cookies) + yield return cookie; + } + } + } - internal bool IsConnected { - get { - return _readyState == WebSocketState.Open || _readyState == WebSocketState.Closing; - } - } + /// + /// Gets the credentials for the HTTP authentication (Basic/Digest). + /// + /// + /// + /// A that represents the credentials + /// used to authenticate the client. + /// + /// + /// The default value is . + /// + /// + public NetworkCredential Credentials + { + get + { + return _credentials; + } + } - #endregion + /// + /// Gets or sets a value indicating whether a event + /// is emitted when a ping is received. + /// + /// + /// + /// true if this instance emits a event + /// when receives a ping; otherwise, false. + /// + /// + /// The default value is false. + /// + /// + public bool EmitOnPing + { + get + { + return _emitOnPing; + } - #region Public Properties + set + { + _emitOnPing = value; + } + } - /// - /// Gets or sets the compression method used to compress a message. - /// - /// - /// The set operation does nothing if the connection has already been - /// established or it is closing. - /// - /// - /// - /// One of the enum values. - /// - /// - /// It specifies the compression method used to compress a message. - /// - /// - /// The default value is . - /// - /// - /// - /// The set operation is not available if this instance is not a client. - /// - public CompressionMethod Compression { - get { - return _compression; - } + /// + /// Gets or sets a value indicating whether the URL redirection for + /// the handshake request is allowed. + /// + /// + /// The set operation does nothing if the connection has already been + /// established or it is closing. + /// + /// + /// + /// true if this instance allows the URL redirection for + /// the handshake request; otherwise, false. + /// + /// + /// The default value is false. + /// + /// + /// + /// The set operation is not available if this instance is not a client. + /// + public bool EnableRedirection + { + get + { + return _enableRedirection; + } - set { - string msg = null; + set + { + string msg = null; + + if (!_client) + { + msg = "This instance is not a client."; + throw new InvalidOperationException(msg); + } + + if (!canSet(out msg)) + { + _logger.Warn(msg); + return; + } + + lock (_forState) + { + if (!canSet(out msg)) + { + _logger.Warn(msg); + return; + } + + _enableRedirection = value; + } + } + } - if (!_client) { - msg = "This instance is not a client."; - throw new InvalidOperationException (msg); + /// + /// Gets the extensions selected by server. + /// + /// + /// A that will be a list of the extensions + /// negotiated between client and server, or an empty string if + /// not specified or selected. + /// + public string Extensions + { + get + { + return _extensions ?? String.Empty; + } } - if (!canSet (out msg)) { - _logger.Warn (msg); - return; + /// + /// Gets a value indicating whether the connection is alive. + /// + /// + /// The get operation returns the value by using a ping/pong + /// if the current state of the connection is Open. + /// + /// + /// true if the connection is alive; otherwise, false. + /// +#if __PING_ASYNC + public bool IsAlive + { + get + { + return _isAlive; + } + } +#else + public bool IsAlive + { + get + { + return ping(EmptyBytes); + } + } +#endif + /// + /// Gets a value indicating whether a secure connection is used. + /// + /// + /// true if this instance uses a secure connection; otherwise, + /// false. + /// + public bool IsSecure + { + get + { + return _secure; + } } - lock (_forState) { - if (!canSet (out msg)) { - _logger.Warn (msg); - return; - } + /// + /// Gets the logging function. + /// + /// + /// The default logging level is . + /// + /// + /// A that provides the logging function. + /// + public Logger Log + { + get + { + return _logger; + } - _compression = value; + internal set + { + _logger = value; + } } - } - } - /// - /// Gets the HTTP cookies included in the handshake request/response. - /// - /// - /// - /// An - /// instance. - /// - /// - /// It provides an enumerator which supports the iteration over - /// the collection of the cookies. - /// - /// - public IEnumerable Cookies { - get { - lock (_cookies.SyncRoot) { - foreach (Cookie cookie in _cookies) - yield return cookie; - } - } - } + /// + /// Gets or sets the value of the HTTP Origin header to send with + /// the handshake request. + /// + /// + /// + /// The HTTP Origin header is defined in + /// + /// Section 7 of RFC 6454. + /// + /// + /// This instance sends the Origin header if this property has any. + /// + /// + /// The set operation does nothing if the connection has already been + /// established or it is closing. + /// + /// + /// + /// + /// A that represents the value of the Origin + /// header to send. + /// + /// + /// The syntax is <scheme>://<host>[:<port>]. + /// + /// + /// The default value is . + /// + /// + /// + /// The set operation is not available if this instance is not a client. + /// + /// + /// + /// The value specified for a set operation is not an absolute URI string. + /// + /// + /// -or- + /// + /// + /// The value specified for a set operation includes the path segments. + /// + /// + public string Origin + { + get + { + return _origin; + } - /// - /// Gets the credentials for the HTTP authentication (Basic/Digest). - /// - /// - /// - /// A that represents the credentials - /// used to authenticate the client. - /// - /// - /// The default value is . - /// - /// - public NetworkCredential Credentials { - get { - return _credentials; - } - } + set + { + string msg = null; + + if (!_client) + { + msg = "This instance is not a client."; + throw new InvalidOperationException(msg); + } + + if (!value.IsNullOrEmpty()) + { + Uri uri; + if (!Uri.TryCreate(value, UriKind.Absolute, out uri)) + { + msg = "Not an absolute URI string."; + throw new ArgumentException(msg, "value"); + } + + if (uri.Segments.Length > 1) + { + msg = "It includes the path segments."; + throw new ArgumentException(msg, "value"); + } + } + + if (!canSet(out msg)) + { + _logger.Warn(msg); + return; + } + + lock (_forState) + { + if (!canSet(out msg)) + { + _logger.Warn(msg); + return; + } + + _origin = !value.IsNullOrEmpty() ? value.TrimEnd('/') : value; + } + } + } - /// - /// Gets or sets a value indicating whether a event - /// is emitted when a ping is received. - /// - /// - /// - /// true if this instance emits a event - /// when receives a ping; otherwise, false. - /// - /// - /// The default value is false. - /// - /// - public bool EmitOnPing { - get { - return _emitOnPing; - } - - set { - _emitOnPing = value; - } - } + /// + /// Gets the name of subprotocol selected by the server. + /// + /// + /// + /// A that will be one of the names of + /// subprotocols specified by client. + /// + /// + /// An empty string if not specified or selected. + /// + /// + public string Protocol + { + get + { + return _protocol ?? String.Empty; + } - /// - /// Gets or sets a value indicating whether the URL redirection for - /// the handshake request is allowed. - /// - /// - /// The set operation does nothing if the connection has already been - /// established or it is closing. - /// - /// - /// - /// true if this instance allows the URL redirection for - /// the handshake request; otherwise, false. - /// - /// - /// The default value is false. - /// - /// - /// - /// The set operation is not available if this instance is not a client. - /// - public bool EnableRedirection { - get { - return _enableRedirection; - } + internal set + { + _protocol = value; + } + } - set { - string msg = null; + /// + /// Gets the current state of the connection. + /// + /// + /// + /// One of the enum values. + /// + /// + /// It indicates the current state of the connection. + /// + /// + /// The default value is . + /// + /// + public WebSocketState ReadyState + { + get + { + return _readyState; + } + } - if (!_client) { - msg = "This instance is not a client."; - throw new InvalidOperationException (msg); + /// + /// Gets the configuration for secure connection. + /// + /// + /// This configuration will be referenced when attempts to connect, + /// so it must be configured before any connect method is called. + /// + /// + /// A that represents + /// the configuration used to establish a secure connection. + /// + /// + /// + /// This instance is not a client. + /// + /// + /// This instance does not use a secure connection. + /// + /// + public ClientSslConfiguration SslConfiguration + { + get + { + if (!_client) + { + var msg = "This instance is not a client."; + throw new InvalidOperationException(msg); + } + + if (!_secure) + { + var msg = "This instance does not use a secure connection."; + throw new InvalidOperationException(msg); + } + + return getSslConfiguration(); + } } - if (!canSet (out msg)) { - _logger.Warn (msg); - return; + /// + /// Gets the URL to which to connect. + /// + /// + /// A that represents the URL to which to connect. + /// + public Uri Url + { + get + { + return _client ? _uri : _context.RequestUri; + } } - lock (_forState) { - if (!canSet (out msg)) { - _logger.Warn (msg); - return; - } + /// + /// Gets or sets the time to wait for the response to the ping or close. + /// + /// + /// The set operation does nothing if the connection has already been + /// established or it is closing. + /// + /// + /// + /// A to wait for the response. + /// + /// + /// The default value is the same as 5 seconds if this instance is + /// a client. + /// + /// + /// + /// The value specified for a set operation is zero or less. + /// + public TimeSpan WaitTime + { + get + { + return _waitTime; + } - _enableRedirection = value; + set + { + if (value <= TimeSpan.Zero) + throw new ArgumentOutOfRangeException("value", "Zero or less."); + + string msg; + if (!canSet(out msg)) + { + _logger.Warn(msg); + return; + } + + lock (_forState) + { + if (!canSet(out msg)) + { + _logger.Warn(msg); + return; + } + + _waitTime = value; + } + } } - } - } - - /// - /// Gets the extensions selected by server. - /// - /// - /// A that will be a list of the extensions - /// negotiated between client and server, or an empty string if - /// not specified or selected. - /// - public string Extensions { - get { - return _extensions ?? String.Empty; - } - } - /// - /// Gets a value indicating whether the connection is alive. - /// - /// - /// The get operation returns the value by using a ping/pong - /// if the current state of the connection is Open. - /// - /// - /// true if the connection is alive; otherwise, false. - /// - public bool IsAlive { - get { - return ping (EmptyBytes); - } - } + #endregion - /// - /// Gets a value indicating whether a secure connection is used. - /// - /// - /// true if this instance uses a secure connection; otherwise, - /// false. - /// - public bool IsSecure { - get { - return _secure; - } - } + #region Public Events - /// - /// Gets the logging function. - /// - /// - /// The default logging level is . - /// - /// - /// A that provides the logging function. - /// - public Logger Log { - get { - return _logger; - } - - internal set { - _logger = value; - } - } + /// + /// Occurs when the WebSocket connection has been closed. + /// + public event EventHandler OnClose; - /// - /// Gets or sets the value of the HTTP Origin header to send with - /// the handshake request. - /// - /// - /// - /// The HTTP Origin header is defined in - /// - /// Section 7 of RFC 6454. - /// - /// - /// This instance sends the Origin header if this property has any. - /// - /// - /// The set operation does nothing if the connection has already been - /// established or it is closing. - /// - /// - /// - /// - /// A that represents the value of the Origin - /// header to send. - /// - /// - /// The syntax is <scheme>://<host>[:<port>]. - /// - /// - /// The default value is . - /// - /// - /// - /// The set operation is not available if this instance is not a client. - /// - /// - /// - /// The value specified for a set operation is not an absolute URI string. - /// - /// - /// -or- - /// - /// - /// The value specified for a set operation includes the path segments. - /// - /// - public string Origin { - get { - return _origin; - } + /// + /// Occurs when the gets an error. + /// + public event EventHandler OnError; - set { - string msg = null; + /// + /// Occurs when the receives a message. + /// + public event EventHandler OnMessage; - if (!_client) { - msg = "This instance is not a client."; - throw new InvalidOperationException (msg); - } + /// + /// Occurs when the WebSocket connection has been established. + /// + public event EventHandler OnOpen; - if (!value.IsNullOrEmpty ()) { - Uri uri; - if (!Uri.TryCreate (value, UriKind.Absolute, out uri)) { - msg = "Not an absolute URI string."; - throw new ArgumentException (msg, "value"); - } + #endregion - if (uri.Segments.Length > 1) { - msg = "It includes the path segments."; - throw new ArgumentException (msg, "value"); - } - } + #region Private Methods - if (!canSet (out msg)) { - _logger.Warn (msg); - return; - } + // As server + private bool accept() + { + if (_readyState == WebSocketState.Open) + { + var msg = "The handshake request has already been accepted."; + _logger.Warn(msg); - lock (_forState) { - if (!canSet (out msg)) { - _logger.Warn (msg); - return; - } + return false; + } - _origin = !value.IsNullOrEmpty () ? value.TrimEnd ('/') : value; - } - } - } + lock (_forState) + { + if (_readyState == WebSocketState.Open) + { + var msg = "The handshake request has already been accepted."; + _logger.Warn(msg); - /// - /// Gets the name of subprotocol selected by the server. - /// - /// - /// - /// A that will be one of the names of - /// subprotocols specified by client. - /// - /// - /// An empty string if not specified or selected. - /// - /// - public string Protocol { - get { - return _protocol ?? String.Empty; - } - - internal set { - _protocol = value; - } - } + return false; + } - /// - /// Gets the current state of the connection. - /// - /// - /// - /// One of the enum values. - /// - /// - /// It indicates the current state of the connection. - /// - /// - /// The default value is . - /// - /// - public WebSocketState ReadyState { - get { - return _readyState; - } - } + if (_readyState == WebSocketState.Closing) + { + var msg = "The close process has set in."; + _logger.Error(msg); - /// - /// Gets the configuration for secure connection. - /// - /// - /// This configuration will be referenced when attempts to connect, - /// so it must be configured before any connect method is called. - /// - /// - /// A that represents - /// the configuration used to establish a secure connection. - /// - /// - /// - /// This instance is not a client. - /// - /// - /// This instance does not use a secure connection. - /// - /// - public ClientSslConfiguration SslConfiguration { - get { - if (!_client) { - var msg = "This instance is not a client."; - throw new InvalidOperationException (msg); - } + msg = "An interruption has occurred while attempting to accept."; + error(msg, null); - if (!_secure) { - var msg = "This instance does not use a secure connection."; - throw new InvalidOperationException (msg); - } + return false; + } - return getSslConfiguration (); - } - } + if (_readyState == WebSocketState.Closed) + { + var msg = "The connection has been closed."; + _logger.Error(msg); - /// - /// Gets the URL to which to connect. - /// - /// - /// A that represents the URL to which to connect. - /// - public Uri Url { - get { - return _client ? _uri : _context.RequestUri; - } - } + msg = "An interruption has occurred while attempting to accept."; + error(msg, null); - /// - /// Gets or sets the time to wait for the response to the ping or close. - /// - /// - /// The set operation does nothing if the connection has already been - /// established or it is closing. - /// - /// - /// - /// A to wait for the response. - /// - /// - /// The default value is the same as 5 seconds if this instance is - /// a client. - /// - /// - /// - /// The value specified for a set operation is zero or less. - /// - public TimeSpan WaitTime { - get { - return _waitTime; - } - - set { - if (value <= TimeSpan.Zero) - throw new ArgumentOutOfRangeException ("value", "Zero or less."); - - string msg; - if (!canSet (out msg)) { - _logger.Warn (msg); - return; - } - - lock (_forState) { - if (!canSet (out msg)) { - _logger.Warn (msg); - return; - } - - _waitTime = value; - } - } - } + return false; + } - #endregion + try + { + if (!acceptHandshake()) + return false; + } + catch (Exception ex) + { + _logger.Fatal(ex.Message); + _logger.Debug(ex.ToString()); - #region Public Events + var msg = "An exception has occurred while attempting to accept."; + fatal(msg, ex); - /// - /// Occurs when the WebSocket connection has been closed. - /// - public event EventHandler OnClose; + return false; + } - /// - /// Occurs when the gets an error. - /// - public event EventHandler OnError; + _readyState = WebSocketState.Open; + return true; + } + } - /// - /// Occurs when the receives a message. - /// - public event EventHandler OnMessage; + // As server + private bool acceptHandshake() + { + _logger.Debug( + String.Format( + "A handshake request from {0}:\n{1}", _context.UserEndPoint, _context + ) + ); - /// - /// Occurs when the WebSocket connection has been established. - /// - public event EventHandler OnOpen; + string msg; + if (!checkHandshakeRequest(_context, out msg)) + { + _logger.Error(msg); - #endregion + refuseHandshake( + CloseStatusCode.ProtocolError, + "A handshake error has occurred while attempting to accept." + ); - #region Private Methods + return false; + } - // As server - private bool accept () - { - if (_readyState == WebSocketState.Open) { - var msg = "The handshake request has already been accepted."; - _logger.Warn (msg); + if (!customCheckHandshakeRequest(_context, out msg)) + { + _logger.Error(msg); - return false; - } + refuseHandshake( + CloseStatusCode.PolicyViolation, + "A handshake error has occurred while attempting to accept." + ); - lock (_forState) { - if (_readyState == WebSocketState.Open) { - var msg = "The handshake request has already been accepted."; - _logger.Warn (msg); + return false; + } - return false; - } + _base64Key = _context.Headers["Sec-WebSocket-Key"]; - if (_readyState == WebSocketState.Closing) { - var msg = "The close process has set in."; - _logger.Error (msg); + if (_protocol != null) + { + var vals = _context.SecWebSocketProtocols; + processSecWebSocketProtocolClientHeader(vals); + } - msg = "An interruption has occurred while attempting to accept."; - error (msg, null); + if (!_ignoreExtensions) + { + var val = _context.Headers["Sec-WebSocket-Extensions"]; + processSecWebSocketExtensionsClientHeader(val); + } - return false; + return sendHttpResponse(createHandshakeResponse()); } - if (_readyState == WebSocketState.Closed) { - var msg = "The connection has been closed."; - _logger.Error (msg); - - msg = "An interruption has occurred while attempting to accept."; - error (msg, null); - - return false; - } + private bool canSet(out string message) + { + message = null; - try { - if (!acceptHandshake ()) - return false; - } - catch (Exception ex) { - _logger.Fatal (ex.Message); - _logger.Debug (ex.ToString ()); + if (_readyState == WebSocketState.Open) + { + message = "The connection has already been established."; + return false; + } - var msg = "An exception has occurred while attempting to accept."; - fatal (msg, ex); + if (_readyState == WebSocketState.Closing) + { + message = "The connection is closing."; + return false; + } - return false; + return true; } - _readyState = WebSocketState.Open; - return true; - } - } - - // As server - private bool acceptHandshake () - { - _logger.Debug ( - String.Format ( - "A handshake request from {0}:\n{1}", _context.UserEndPoint, _context + // As server + private bool checkHandshakeRequest( + WebSocketContext context, out string message ) - ); + { + message = null; - string msg; - if (!checkHandshakeRequest (_context, out msg)) { - _logger.Error (msg); + if (!context.IsWebSocketRequest) + { + message = "Not a handshake request."; + return false; + } - refuseHandshake ( - CloseStatusCode.ProtocolError, - "A handshake error has occurred while attempting to accept." - ); + if (context.RequestUri == null) + { + message = "It specifies an invalid Request-URI."; + return false; + } - return false; - } + var headers = context.Headers; - if (!customCheckHandshakeRequest (_context, out msg)) { - _logger.Error (msg); + var key = headers["Sec-WebSocket-Key"]; + if (key == null) + { + message = "It includes no Sec-WebSocket-Key header."; + return false; + } - refuseHandshake ( - CloseStatusCode.PolicyViolation, - "A handshake error has occurred while attempting to accept." - ); + if (key.Length == 0) + { + message = "It includes an invalid Sec-WebSocket-Key header."; + return false; + } - return false; - } + var version = headers["Sec-WebSocket-Version"]; + if (version == null) + { + message = "It includes no Sec-WebSocket-Version header."; + return false; + } - _base64Key = _context.Headers["Sec-WebSocket-Key"]; - - if (_protocol != null) { - var vals = _context.SecWebSocketProtocols; - processSecWebSocketProtocolClientHeader (vals); - } + if (version != _version) + { + message = "It includes an invalid Sec-WebSocket-Version header."; + return false; + } - if (!_ignoreExtensions) { - var val = _context.Headers["Sec-WebSocket-Extensions"]; - processSecWebSocketExtensionsClientHeader (val); - } + var protocol = headers["Sec-WebSocket-Protocol"]; + if (protocol != null && protocol.Length == 0) + { + message = "It includes an invalid Sec-WebSocket-Protocol header."; + return false; + } - return sendHttpResponse (createHandshakeResponse ()); - } + if (!_ignoreExtensions) + { + var extensions = headers["Sec-WebSocket-Extensions"]; + if (extensions != null && extensions.Length == 0) + { + message = "It includes an invalid Sec-WebSocket-Extensions header."; + return false; + } + } - private bool canSet (out string message) - { - message = null; + return true; + } - if (_readyState == WebSocketState.Open) { - message = "The connection has already been established."; - return false; - } + // As client + private bool checkHandshakeResponse(HttpResponse response, out string message) + { + message = null; - if (_readyState == WebSocketState.Closing) { - message = "The connection is closing."; - return false; - } + if (response.IsRedirect) + { + message = "Indicates the redirection."; + return false; + } - return true; - } + if (response.IsUnauthorized) + { + message = "Requires the authentication."; + return false; + } - // As server - private bool checkHandshakeRequest ( - WebSocketContext context, out string message - ) - { - message = null; - - if (!context.IsWebSocketRequest) { - message = "Not a handshake request."; - return false; - } - - if (context.RequestUri == null) { - message = "It specifies an invalid Request-URI."; - return false; - } - - var headers = context.Headers; - - var key = headers["Sec-WebSocket-Key"]; - if (key == null) { - message = "It includes no Sec-WebSocket-Key header."; - return false; - } - - if (key.Length == 0) { - message = "It includes an invalid Sec-WebSocket-Key header."; - return false; - } - - var version = headers["Sec-WebSocket-Version"]; - if (version == null) { - message = "It includes no Sec-WebSocket-Version header."; - return false; - } - - if (version != _version) { - message = "It includes an invalid Sec-WebSocket-Version header."; - return false; - } - - var protocol = headers["Sec-WebSocket-Protocol"]; - if (protocol != null && protocol.Length == 0) { - message = "It includes an invalid Sec-WebSocket-Protocol header."; - return false; - } - - if (!_ignoreExtensions) { - var extensions = headers["Sec-WebSocket-Extensions"]; - if (extensions != null && extensions.Length == 0) { - message = "It includes an invalid Sec-WebSocket-Extensions header."; - return false; - } - } - - return true; - } + if (!response.IsWebSocketResponse) + { + message = "Not a WebSocket handshake response."; + return false; + } - // As client - private bool checkHandshakeResponse (HttpResponse response, out string message) - { - message = null; - - if (response.IsRedirect) { - message = "Indicates the redirection."; - return false; - } - - if (response.IsUnauthorized) { - message = "Requires the authentication."; - return false; - } - - if (!response.IsWebSocketResponse) { - message = "Not a WebSocket handshake response."; - return false; - } - - var headers = response.Headers; - if (!validateSecWebSocketAcceptHeader (headers["Sec-WebSocket-Accept"])) { - message = "Includes no Sec-WebSocket-Accept header, or it has an invalid value."; - return false; - } - - if (!validateSecWebSocketProtocolServerHeader (headers["Sec-WebSocket-Protocol"])) { - message = "Includes no Sec-WebSocket-Protocol header, or it has an invalid value."; - return false; - } - - if (!validateSecWebSocketExtensionsServerHeader (headers["Sec-WebSocket-Extensions"])) { - message = "Includes an invalid Sec-WebSocket-Extensions header."; - return false; - } - - if (!validateSecWebSocketVersionServerHeader (headers["Sec-WebSocket-Version"])) { - message = "Includes an invalid Sec-WebSocket-Version header."; - return false; - } - - return true; - } + var headers = response.Headers; + if (!validateSecWebSocketAcceptHeader(headers["Sec-WebSocket-Accept"])) + { + message = "Includes no Sec-WebSocket-Accept header, or it has an invalid value."; + return false; + } - private static bool checkProtocols (string[] protocols, out string message) - { - message = null; + if (!validateSecWebSocketProtocolServerHeader(headers["Sec-WebSocket-Protocol"])) + { + message = "Includes no Sec-WebSocket-Protocol header, or it has an invalid value."; + return false; + } - Func cond = protocol => protocol.IsNullOrEmpty () - || !protocol.IsToken (); + if (!validateSecWebSocketExtensionsServerHeader(headers["Sec-WebSocket-Extensions"])) + { + message = "Includes an invalid Sec-WebSocket-Extensions header."; + return false; + } - if (protocols.Contains (cond)) { - message = "It contains a value that is not a token."; - return false; - } + if (!validateSecWebSocketVersionServerHeader(headers["Sec-WebSocket-Version"])) + { + message = "Includes an invalid Sec-WebSocket-Version header."; + return false; + } - if (protocols.ContainsTwice ()) { - message = "It contains a value twice."; - return false; - } + return true; + } - return true; - } + private static bool checkProtocols(string[] protocols, out string message) + { + message = null; - 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 has been received while receiving continuation frames."; - return false; - } - - if (frame.IsCompressed && _compression == CompressionMethod.None) { - message = "A compressed frame has been 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; - } + Func cond = protocol => protocol.IsNullOrEmpty() + || !protocol.IsToken(); - private void close (ushort code, string reason) - { - if (_readyState == WebSocketState.Closing) { - _logger.Info ("The closing is already in progress."); - return; - } - - if (_readyState == WebSocketState.Closed) { - _logger.Info ("The connection has already been closed."); - return; - } - - if (code == 1005) { // == no status - close (PayloadData.Empty, true, true, false); - return; - } - - var send = !code.IsReserved (); - close (new PayloadData (code, reason), send, send, false); - } + if (protocols.Contains(cond)) + { + message = "It contains a value that is not a token."; + return false; + } - private void close ( - PayloadData payloadData, bool send, bool receive, bool received - ) - { - lock (_forState) { - if (_readyState == WebSocketState.Closing) { - _logger.Info ("The closing is already in progress."); - return; - } + if (protocols.ContainsTwice()) + { + message = "It contains a value twice."; + return false; + } - if (_readyState == WebSocketState.Closed) { - _logger.Info ("The connection has already been closed."); - return; + return true; } - send = send && _readyState == WebSocketState.Open; - receive = send && receive; + private bool checkReceivedFrame(WebSocketFrame frame, out string message) + { + message = null; - _readyState = WebSocketState.Closing; - } - - _logger.Trace ("Begin closing the connection."); + var masked = frame.IsMasked; + if (_client && masked) + { + message = "A frame from the server is masked."; + return false; + } - var res = closeHandshake (payloadData, send, receive, received); - releaseResources (); + if (!_client && !masked) + { + message = "A frame from a client is not masked."; + return false; + } - _logger.Trace ("End closing the connection."); + if (_inContinuation && frame.IsData) + { + message = "A data frame has been received while receiving continuation frames."; + return false; + } - _readyState = WebSocketState.Closed; + if (frame.IsCompressed && _compression == CompressionMethod.None) + { + message = "A compressed frame has been received without any agreement for it."; + return false; + } - var e = new CloseEventArgs (payloadData, res); + if (frame.Rsv2 == Rsv.On) + { + message = "The RSV2 of a frame is non-zero without any negotiation for it."; + return false; + } - try { - OnClose.Emit (this, e); - } - catch (Exception ex) { - _logger.Error (ex.Message); - _logger.Debug (ex.ToString ()); - } - } + if (frame.Rsv3 == Rsv.On) + { + message = "The RSV3 of a frame is non-zero without any negotiation for it."; + return false; + } - private void closeAsync (ushort code, string reason) - { - if (_readyState == WebSocketState.Closing) { - _logger.Info ("The closing is already in progress."); - return; - } - - if (_readyState == WebSocketState.Closed) { - _logger.Info ("The connection has already been closed."); - return; - } - - if (code == 1005) { // == no status - closeAsync (PayloadData.Empty, true, true, false); - return; - } - - var send = !code.IsReserved (); - closeAsync (new PayloadData (code, reason), send, send, false); - } + return true; + } - private void closeAsync ( - PayloadData payloadData, bool send, bool receive, bool received - ) - { - Action closer = close; - closer.BeginInvoke ( - payloadData, send, receive, received, ar => closer.EndInvoke (ar), null - ); - } + private void close(ushort code, string reason) + { + if (_readyState == WebSocketState.Closing) + { + _logger.Info("The closing is already in progress."); + return; + } - private bool closeHandshake (byte[] frameAsBytes, bool receive, bool received) - { - var sent = frameAsBytes != null && sendBytes (frameAsBytes); + if (_readyState == WebSocketState.Closed) + { + _logger.Info("The connection has already been closed."); + return; + } - var wait = !received && sent && receive && _receivingExited != null; - if (wait) - received = _receivingExited.WaitOne (_waitTime); + if (code == 1005) + { // == no status + close(PayloadData.Empty, true, true, false); + return; + } - var ret = sent && received; + var send = !code.IsReserved(); + close(new PayloadData(code, reason), send, send, false); + } - _logger.Debug ( - String.Format ( - "Was clean?: {0}\n sent: {1}\n received: {2}", ret, sent, received + private void close( + PayloadData payloadData, bool send, bool receive, bool received ) - ); + { + lock (_forState) + { + if (_readyState == WebSocketState.Closing) + { + _logger.Info("The closing is already in progress."); + return; + } + + if (_readyState == WebSocketState.Closed) + { + _logger.Info("The connection has already been closed."); + return; + } + + send = send && _readyState == WebSocketState.Open; + receive = send && receive; + + _readyState = WebSocketState.Closing; + } - return ret; - } + _logger.Trace("Begin closing the connection."); - private bool closeHandshake ( - PayloadData payloadData, bool send, bool receive, bool received - ) - { - var sent = false; - if (send) { - var frame = WebSocketFrame.CreateCloseFrame (payloadData, _client); - sent = sendBytes (frame.ToArray ()); + var res = closeHandshake(payloadData, send, receive, received); + releaseResources(); - if (_client) - frame.Unmask (); - } + _logger.Trace("End closing the connection."); - var wait = !received && sent && receive && _receivingExited != null; - if (wait) - received = _receivingExited.WaitOne (_waitTime); + _readyState = WebSocketState.Closed; - var ret = sent && received; + var e = new CloseEventArgs(payloadData, res); - _logger.Debug ( - String.Format ( - "Was clean?: {0}\n sent: {1}\n received: {2}", ret, sent, received - ) - ); + try + { + OnClose.Emit(this, e); + } + catch (Exception ex) + { + _logger.Error(ex.Message); + _logger.Debug(ex.ToString()); + } + } - return ret; - } + private void closeAsync(ushort code, string reason) + { + if (_readyState == WebSocketState.Closing) + { + _logger.Info("The closing is already in progress."); + return; + } - // As client - private bool connect () - { - if (_readyState == WebSocketState.Open) { - var msg = "The connection has already been established."; - _logger.Warn (msg); + if (_readyState == WebSocketState.Closed) + { + _logger.Info("The connection has already been closed."); + return; + } - return false; - } + if (code == 1005) + { // == no status + closeAsync(PayloadData.Empty, true, true, false); + return; + } - lock (_forState) { - if (_readyState == WebSocketState.Open) { - var msg = "The connection has already been established."; - _logger.Warn (msg); + var send = !code.IsReserved(); + closeAsync(new PayloadData(code, reason), send, send, false); + } - return false; + private void closeAsync( + PayloadData payloadData, bool send, bool receive, bool received + ) + { + Action closer = close; + closer.BeginInvoke( + payloadData, send, receive, received, ar => closer.EndInvoke(ar), null + ); } - if (_readyState == WebSocketState.Closing) { - var msg = "The close process has set in."; - _logger.Error (msg); + private bool closeHandshake(byte[] frameAsBytes, bool receive, bool received) + { + var sent = frameAsBytes != null && sendBytes(frameAsBytes); + + var wait = !received && sent && receive && _receivingExited != null; + if (wait) + received = _receivingExited.WaitOne(_waitTime); - msg = "An interruption has occurred while attempting to connect."; - error (msg, null); + var ret = sent && received; + + _logger.Debug( + String.Format( + "Was clean?: {0}\n sent: {1}\n received: {2}", ret, sent, received + ) + ); - return false; + return ret; } - if (_retryCountForConnect > _maxRetryCountForConnect) { - var msg = "An opportunity for reconnecting has been lost."; - _logger.Error (msg); + private bool closeHandshake( + PayloadData payloadData, bool send, bool receive, bool received + ) + { + var sent = false; + if (send) + { + var frame = WebSocketFrame.CreateCloseFrame(payloadData, _client); + sent = sendBytes(frame.ToArray()); + + if (_client) + frame.Unmask(); + } - msg = "An interruption has occurred while attempting to connect."; - error (msg, null); + var wait = !received && sent && receive && _receivingExited != null; + if (wait) + received = _receivingExited.WaitOne(_waitTime); - return false; - } + var ret = sent && received; - _readyState = WebSocketState.Connecting; + _logger.Debug( + String.Format( + "Was clean?: {0}\n sent: {1}\n received: {2}", ret, sent, received + ) + ); - try { - doHandshake (); + return ret; } - catch (Exception ex) { - _retryCountForConnect++; - _logger.Fatal (ex.Message); - _logger.Debug (ex.ToString ()); + // As client + private bool connect() + { + if (_readyState == WebSocketState.Open) + { + var msg = "The connection has already been established."; + _logger.Warn(msg); - var msg = "An exception has occurred while attempting to connect."; - fatal (msg, ex); + return false; + } - return false; - } + lock (_forState) + { + if (_readyState == WebSocketState.Open) + { + var msg = "The connection has already been established."; + _logger.Warn(msg); - _retryCountForConnect = 1; - _readyState = WebSocketState.Open; + return false; + } - return true; - } - } + if (_readyState == WebSocketState.Closing) + { + var msg = "The close process has set in."; + _logger.Error(msg); - // As client - private string createExtensions () - { - var buff = new StringBuilder (80); + msg = "An interruption has occurred while attempting to connect."; + error(msg, null); - if (_compression != CompressionMethod.None) { - var str = _compression.ToExtensionString ( - "server_no_context_takeover", "client_no_context_takeover"); + return false; + } - buff.AppendFormat ("{0}, ", str); - } + if (_retryCountForConnect > _maxRetryCountForConnect) + { + var msg = "An opportunity for reconnecting has been lost."; + _logger.Error(msg); - var len = buff.Length; - if (len > 2) { - buff.Length = len - 2; - return buff.ToString (); - } + msg = "An interruption has occurred while attempting to connect."; + error(msg, null); - return null; - } + return false; + } - // As server - private HttpResponse createHandshakeFailureResponse (HttpStatusCode code) - { - var ret = HttpResponse.CreateCloseResponse (code); - ret.Headers["Sec-WebSocket-Version"] = _version; + _readyState = WebSocketState.Connecting; - return ret; - } + try + { + doHandshake(); + } + catch (Exception ex) + { + //_retryCountForConnect++; - // As client - private HttpRequest createHandshakeRequest () - { - var ret = HttpRequest.CreateWebSocketRequest (_uri); + _logger.Fatal(ex.Message); + _logger.Debug(ex.ToString()); - var headers = ret.Headers; - if (!_origin.IsNullOrEmpty ()) - headers["Origin"] = _origin; + var msg = "An exception has occurred while attempting to connect."; + fatal(msg, ex); - headers["Sec-WebSocket-Key"] = _base64Key; + return false; + } - _protocolsRequested = _protocols != null; - if (_protocolsRequested) - headers["Sec-WebSocket-Protocol"] = _protocols.ToString (", "); + _retryCountForConnect = 1; + _readyState = WebSocketState.Open; - _extensionsRequested = _compression != CompressionMethod.None; - if (_extensionsRequested) - headers["Sec-WebSocket-Extensions"] = createExtensions (); + return true; + } + } - headers["Sec-WebSocket-Version"] = _version; + // As client + private string createExtensions() + { + var buff = new StringBuilder(80); - AuthenticationResponse authRes = null; - if (_authChallenge != null && _credentials != null) { - authRes = new AuthenticationResponse (_authChallenge, _credentials, _nonceCount); - _nonceCount = authRes.NonceCount; - } - else if (_preAuth) { - authRes = new AuthenticationResponse (_credentials); - } + if (_compression != CompressionMethod.None) + { + var str = _compression.ToExtensionString( + "server_no_context_takeover", "client_no_context_takeover"); - if (authRes != null) - headers["Authorization"] = authRes.ToString (); + buff.AppendFormat("{0}, ", str); + } - if (_cookies.Count > 0) - ret.SetCookies (_cookies); + var len = buff.Length; + if (len > 2) + { + buff.Length = len - 2; + return buff.ToString(); + } - return ret; - } + return null; + } - // As server - private HttpResponse createHandshakeResponse () - { - var ret = HttpResponse.CreateWebSocketResponse (); + // As server + private HttpResponse createHandshakeFailureResponse(HttpStatusCode code) + { + var ret = HttpResponse.CreateCloseResponse(code); + ret.Headers["Sec-WebSocket-Version"] = _version; - var headers = ret.Headers; - headers["Sec-WebSocket-Accept"] = CreateResponseKey (_base64Key); + return ret; + } - if (_protocol != null) - headers["Sec-WebSocket-Protocol"] = _protocol; + // As client + private HttpRequest createHandshakeRequest() + { + var ret = HttpRequest.CreateWebSocketRequest(_uri); - if (_extensions != null) - headers["Sec-WebSocket-Extensions"] = _extensions; + var headers = ret.Headers; + if (!_origin.IsNullOrEmpty()) + headers["Origin"] = _origin; - if (_cookies.Count > 0) - ret.SetCookies (_cookies); + headers["Sec-WebSocket-Key"] = _base64Key; - return ret; - } + _protocolsRequested = _protocols != null; + if (_protocolsRequested) + headers["Sec-WebSocket-Protocol"] = _protocols.ToString(", "); - // As server - private bool customCheckHandshakeRequest ( - WebSocketContext context, out string message - ) - { - message = null; + _extensionsRequested = _compression != CompressionMethod.None; + if (_extensionsRequested) + headers["Sec-WebSocket-Extensions"] = createExtensions(); - if (_handshakeRequestChecker == null) - return true; + headers["Sec-WebSocket-Version"] = _version; - message = _handshakeRequestChecker (context); - return message == null; - } + AuthenticationResponse authRes = null; + if (_authChallenge != null && _credentials != null) + { + authRes = new AuthenticationResponse(_authChallenge, _credentials, _nonceCount); + _nonceCount = authRes.NonceCount; + } + else if (_preAuth) + { + authRes = new AuthenticationResponse(_credentials); + } - private MessageEventArgs dequeueFromMessageEventQueue () - { - lock (_forMessageEventQueue) - return _messageEventQueue.Count > 0 ? _messageEventQueue.Dequeue () : null; - } + if (authRes != null) + headers["Authorization"] = authRes.ToString(); - // As client - private void doHandshake () - { - setClientStream (); - var res = sendHandshakeRequest (); + if (_cookies.Count > 0) + ret.SetCookies(_cookies); - string msg; - if (!checkHandshakeResponse (res, out msg)) - throw new WebSocketException (CloseStatusCode.ProtocolError, msg); + return ret; + } - if (_protocolsRequested) - _protocol = res.Headers["Sec-WebSocket-Protocol"]; + // As server + private HttpResponse createHandshakeResponse() + { + var ret = HttpResponse.CreateWebSocketResponse(); - if (_extensionsRequested) - processSecWebSocketExtensionsServerHeader (res.Headers["Sec-WebSocket-Extensions"]); + var headers = ret.Headers; + headers["Sec-WebSocket-Accept"] = CreateResponseKey(_base64Key); - processCookies (res.Cookies); - } + if (_protocol != null) + headers["Sec-WebSocket-Protocol"] = _protocol; - private void enqueueToMessageEventQueue (MessageEventArgs e) - { - lock (_forMessageEventQueue) - _messageEventQueue.Enqueue (e); - } + if (_extensions != null) + headers["Sec-WebSocket-Extensions"] = _extensions; - private void error (string message, Exception exception) - { - try { - OnError.Emit (this, new ErrorEventArgs (message, exception)); - } - catch (Exception ex) { - _logger.Error (ex.Message); - _logger.Debug (ex.ToString ()); - } - } + if (_cookies.Count > 0) + ret.SetCookies(_cookies); - private void fatal (string message, Exception exception) - { - var code = exception is WebSocketException - ? ((WebSocketException) exception).Code - : CloseStatusCode.Abnormal; + return ret; + } - fatal (message, (ushort) code); - } + // As server + private bool customCheckHandshakeRequest( + WebSocketContext context, out string message + ) + { + message = null; - private void fatal (string message, ushort code) - { - var payload = new PayloadData (code, message); - close (payload, !code.IsReserved (), false, false); - } + if (_handshakeRequestChecker == null) + return true; - private void fatal (string message, CloseStatusCode code) - { - fatal (message, (ushort) code); - } + message = _handshakeRequestChecker(context); + return message == null; + } - private ClientSslConfiguration getSslConfiguration () - { - if (_sslConfig == null) - _sslConfig = new ClientSslConfiguration (_uri.DnsSafeHost); + private MessageEventArgs dequeueFromMessageEventQueue() + { + lock (_forMessageEventQueue) + return _messageEventQueue.Count > 0 ? _messageEventQueue.Dequeue() : null; + } - return _sslConfig; - } + // As client + private void doHandshake() + { + setClientStream(); + var res = sendHandshakeRequest(); - private void init () - { - _compression = CompressionMethod.None; - _cookies = new CookieCollection (); - _forPing = new object (); - _forSend = new object (); - _forState = new object (); - _messageEventQueue = new Queue (); - _forMessageEventQueue = ((ICollection) _messageEventQueue).SyncRoot; - _readyState = WebSocketState.Connecting; - } + string msg; + if (!checkHandshakeResponse(res, out msg)) + throw new WebSocketException(CloseStatusCode.ProtocolError, msg); - private void message () - { - MessageEventArgs e = null; - lock (_forMessageEventQueue) { - if (_inMessage || _messageEventQueue.Count == 0 || _readyState != WebSocketState.Open) - return; + if (_protocolsRequested) + _protocol = res.Headers["Sec-WebSocket-Protocol"]; - _inMessage = true; - e = _messageEventQueue.Dequeue (); - } + if (_extensionsRequested) + processSecWebSocketExtensionsServerHeader(res.Headers["Sec-WebSocket-Extensions"]); - _message (e); - } + processCookies(res.Cookies); + } - private void messagec (MessageEventArgs e) - { - do { - try { - OnMessage.Emit (this, e); + private void enqueueToMessageEventQueue(MessageEventArgs e) + { + lock (_forMessageEventQueue) + _messageEventQueue.Enqueue(e); } - catch (Exception ex) { - _logger.Error (ex.ToString ()); - error ("An error has occurred during an OnMessage event.", ex); + + private void error(string message, Exception exception) + { + try + { + OnError.Emit(this, new ErrorEventArgs(message, exception)); + } + catch (Exception ex) + { + _logger.Error(ex.Message); + _logger.Debug(ex.ToString()); + } } - lock (_forMessageEventQueue) { - if (_messageEventQueue.Count == 0 || _readyState != WebSocketState.Open) { - _inMessage = false; - break; - } + private void fatal(string message, Exception exception) + { + var code = exception is WebSocketException + ? ((WebSocketException)exception).Code + : CloseStatusCode.Abnormal; - e = _messageEventQueue.Dequeue (); + fatal(message, (ushort)code); } - } - while (true); - } - - private void messages (MessageEventArgs e) - { - try { - OnMessage.Emit (this, e); - } - catch (Exception ex) { - _logger.Error (ex.ToString ()); - error ("An error has occurred during an OnMessage event.", ex); - } - lock (_forMessageEventQueue) { - if (_messageEventQueue.Count == 0 || _readyState != WebSocketState.Open) { - _inMessage = false; - return; + private void fatal(string message, ushort code) + { + var payload = new PayloadData(code, message); + close(payload, !code.IsReserved(), false, false); } - e = _messageEventQueue.Dequeue (); - } + private void fatal(string message, CloseStatusCode code) + { + fatal(message, (ushort)code); + } - ThreadPool.QueueUserWorkItem (state => messages (e)); - } + private ClientSslConfiguration getSslConfiguration() + { + if (_sslConfig == null) + _sslConfig = new ClientSslConfiguration(_uri.DnsSafeHost); - private void open () - { - _inMessage = true; - startReceiving (); - try { - OnOpen.Emit (this, EventArgs.Empty); - } - catch (Exception ex) { - _logger.Error (ex.ToString ()); - error ("An error has occurred during the OnOpen event.", ex); - } - - MessageEventArgs e = null; - lock (_forMessageEventQueue) { - if (_messageEventQueue.Count == 0 || _readyState != WebSocketState.Open) { - _inMessage = false; - return; - } - - e = _messageEventQueue.Dequeue (); - } - - _message.BeginInvoke (e, ar => _message.EndInvoke (ar), null); - } + return _sslConfig; + } - private bool ping (byte[] data) - { - if (_readyState != WebSocketState.Open) - return false; + private void init() + { + _compression = CompressionMethod.None; + _cookies = new CookieCollection(); + _forPing = new object(); + _forSend = new object(); + _forState = new object(); + _messageEventQueue = new Queue(); + _forMessageEventQueue = ((ICollection)_messageEventQueue).SyncRoot; + _readyState = WebSocketState.Connecting; +#if __PING_ASYNC + _isAlive = true; + _isPinged = true; +#endif + } - var pongReceived = _pongReceived; - if (pongReceived == null) - return false; + private void message() + { + MessageEventArgs e = null; + lock (_forMessageEventQueue) + { + if (_inMessage || _messageEventQueue.Count == 0 || _readyState != WebSocketState.Open) + return; - lock (_forPing) { - try { - pongReceived.Reset (); - if (!send (Fin.Final, Opcode.Ping, data, false)) - return false; + _inMessage = true; + e = _messageEventQueue.Dequeue(); + } - return pongReceived.WaitOne (_waitTime); - } - catch (ObjectDisposedException) { - return false; + _message(e); } - } - } - private bool processCloseFrame (WebSocketFrame frame) - { - var payload = frame.PayloadData; - close (payload, !payload.HasReservedCode, false, true); + private void messagec(MessageEventArgs e) + { + do + { + try + { + OnMessage.Emit(this, e); + } + catch (Exception ex) + { + _logger.Error(ex.ToString()); + error("An error has occurred during an OnMessage event.", ex); + } + + lock (_forMessageEventQueue) + { + if (_messageEventQueue.Count == 0 || _readyState != WebSocketState.Open) + { + _inMessage = false; + break; + } + + e = _messageEventQueue.Dequeue(); + } + } + while (true); + } - return false; - } + private void messages(MessageEventArgs e) + { + try + { + OnMessage.Emit(this, e); + } + catch (Exception ex) + { + _logger.Error(ex.ToString()); + error("An error has occurred during an OnMessage event.", ex); + } - // As client - private void processCookies (CookieCollection cookies) - { - if (cookies.Count == 0) - return; + lock (_forMessageEventQueue) + { + if (_messageEventQueue.Count == 0 || _readyState != WebSocketState.Open) + { + _inMessage = false; + return; + } - _cookies.SetOrRemove (cookies); - } + e = _messageEventQueue.Dequeue(); + } - private bool processDataFrame (WebSocketFrame frame) - { - enqueueToMessageEventQueue ( - frame.IsCompressed - ? new MessageEventArgs ( - frame.Opcode, frame.PayloadData.ApplicationData.Decompress (_compression)) - : new MessageEventArgs (frame)); + ThreadPool.QueueUserWorkItem(state => messages(e)); + } - return true; - } + private void open() + { + _inMessage = true; + startReceiving(); + try + { + OnOpen.Emit(this, EventArgs.Empty); + } + catch (Exception ex) + { + _logger.Error(ex.ToString()); + error("An error has occurred during the OnOpen event.", ex); + } - private bool processFragmentFrame (WebSocketFrame frame) - { - if (!_inContinuation) { - // Must process first fragment. - if (frame.IsContinuation) - return true; + MessageEventArgs e = null; + lock (_forMessageEventQueue) + { + if (_messageEventQueue.Count == 0 || _readyState != WebSocketState.Open) + { + _inMessage = false; + return; + } - _fragmentsOpcode = frame.Opcode; - _fragmentsCompressed = frame.IsCompressed; - _fragmentsBuffer = new MemoryStream (); - _inContinuation = true; - } + e = _messageEventQueue.Dequeue(); + } - _fragmentsBuffer.WriteBytes (frame.PayloadData.ApplicationData, 1024); - if (frame.IsFinal) { - using (_fragmentsBuffer) { - var data = _fragmentsCompressed - ? _fragmentsBuffer.DecompressToArray (_compression) - : _fragmentsBuffer.ToArray (); + _message.BeginInvoke(e, ar => _message.EndInvoke(ar), null); + } - enqueueToMessageEventQueue (new MessageEventArgs (_fragmentsOpcode, data)); + private bool ping(byte[] data) + { + if (_readyState != WebSocketState.Open) + return false; + + var pongReceived = _pongReceived; + if (pongReceived == null) + return false; + + lock (_forPing) + { + try + { + pongReceived.Reset(); + if (!send(Fin.Final, Opcode.Ping, data, false)) + return false; + + return pongReceived.WaitOne(_waitTime); + } + catch (ObjectDisposedException) + { + return false; + } + } } - _fragmentsBuffer = null; - _inContinuation = false; - } + private bool processCloseFrame(WebSocketFrame frame) + { + var payload = frame.PayloadData; + close(payload, !payload.HasReservedCode, false, true); - return true; - } + return false; + } - private bool processPingFrame (WebSocketFrame frame) - { - _logger.Trace ("A ping was received."); + // As client + private void processCookies(CookieCollection cookies) + { + if (cookies.Count == 0) + return; + + _cookies.SetOrRemove(cookies); + } - var pong = WebSocketFrame.CreatePongFrame (frame.PayloadData, _client); + private bool processDataFrame(WebSocketFrame frame) + { + enqueueToMessageEventQueue( + frame.IsCompressed + ? new MessageEventArgs( + frame.Opcode, frame.PayloadData.ApplicationData.Decompress(_compression)) + : new MessageEventArgs(frame)); - lock (_forState) { - if (_readyState != WebSocketState.Open) { - _logger.Error ("The connection is closing."); - return true; + return true; } - if (!sendBytes (pong.ToArray ())) - return false; - } + private bool processFragmentFrame(WebSocketFrame frame) + { + if (!_inContinuation) + { + // Must process first fragment. + if (frame.IsContinuation) + return true; + + _fragmentsOpcode = frame.Opcode; + _fragmentsCompressed = frame.IsCompressed; + _fragmentsBuffer = new MemoryStream(); + _inContinuation = true; + } - _logger.Trace ("A pong to this ping has been sent."); + _fragmentsBuffer.WriteBytes(frame.PayloadData.ApplicationData, 1024); + if (frame.IsFinal) + { + using (_fragmentsBuffer) + { + var data = _fragmentsCompressed + ? _fragmentsBuffer.DecompressToArray(_compression) + : _fragmentsBuffer.ToArray(); - if (_emitOnPing) { - if (_client) - pong.Unmask (); + enqueueToMessageEventQueue(new MessageEventArgs(_fragmentsOpcode, data)); + } - enqueueToMessageEventQueue (new MessageEventArgs (frame)); - } + _fragmentsBuffer = null; + _inContinuation = false; + } - return true; - } + return true; + } - private bool processPongFrame (WebSocketFrame frame) - { - _logger.Trace ("A pong was received."); + private bool processPingFrame(WebSocketFrame frame) + { + _logger.Trace("A ping was received."); - try { - _pongReceived.Set (); - } - catch (NullReferenceException ex) { - _logger.Error (ex.Message); - _logger.Debug (ex.ToString ()); + var pong = WebSocketFrame.CreatePongFrame(frame.PayloadData, _client); - return false; - } - catch (ObjectDisposedException ex) { - _logger.Error (ex.Message); - _logger.Debug (ex.ToString ()); + lock (_forState) + { + if (_readyState != WebSocketState.Open) + { + _logger.Error("The connection is closing."); + return true; + } - return false; - } + if (!sendBytes(pong.ToArray())) + return false; + } - _logger.Trace ("It has been signaled."); + _logger.Trace("A pong to this ping has been sent."); - return true; - } + if (_emitOnPing) + { + if (_client) + pong.Unmask(); - 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); - } + enqueueToMessageEventQueue(new MessageEventArgs(frame)); + } - // As server - private void processSecWebSocketExtensionsClientHeader (string value) - { - if (value == null) - return; + return true; + } - var buff = new StringBuilder (80); - var comp = false; + private bool processPongFrame(WebSocketFrame frame) + { + _logger.Trace("A pong was received."); - foreach (var elm in value.SplitHeaderValue (',')) { - var extension = elm.Trim (); - if (extension.Length == 0) - continue; + try + { + _pongReceived.Set(); + } + catch (NullReferenceException ex) + { + _logger.Error(ex.Message); + _logger.Debug(ex.ToString()); - if (!comp) { - if (extension.IsCompressionExtension (CompressionMethod.Deflate)) { - _compression = CompressionMethod.Deflate; + return false; + } + catch (ObjectDisposedException ex) + { + _logger.Error(ex.Message); + _logger.Debug(ex.ToString()); - buff.AppendFormat ( - "{0}, ", - _compression.ToExtensionString ( - "client_no_context_takeover", "server_no_context_takeover" - ) - ); + return false; + } - comp = true; - } + _logger.Trace("It has been signaled."); + + return true; } - } - var len = buff.Length; - if (len <= 2) - return; + 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); + } - buff.Length = len - 2; - _extensions = buff.ToString (); - } + // As server + private void processSecWebSocketExtensionsClientHeader(string value) + { + if (value == null) + return; - // As client - private void processSecWebSocketExtensionsServerHeader (string value) - { - if (value == null) { - _compression = CompressionMethod.None; - return; - } + var buff = new StringBuilder(80); + var comp = false; + + foreach (var elm in value.SplitHeaderValue(',')) + { + var extension = elm.Trim(); + if (extension.Length == 0) + continue; + + if (!comp) + { + if (extension.IsCompressionExtension(CompressionMethod.Deflate)) + { + _compression = CompressionMethod.Deflate; + + buff.AppendFormat( + "{0}, ", + _compression.ToExtensionString( + "client_no_context_takeover", "server_no_context_takeover" + ) + ); + + comp = true; + } + } + } - _extensions = value; - } + var len = buff.Length; + if (len <= 2) + return; - // As server - private void processSecWebSocketProtocolClientHeader ( - IEnumerable values - ) - { - if (values.Contains (val => val == _protocol)) - return; + buff.Length = len - 2; + _extensions = buff.ToString(); + } - _protocol = null; - } + // As client + private void processSecWebSocketExtensionsServerHeader(string value) + { + if (value == null) + { + _compression = CompressionMethod.None; + return; + } - private bool processUnsupportedFrame (WebSocketFrame frame) - { - _logger.Fatal ("An unsupported frame:" + frame.PrintToString (false)); - fatal ("There is no way to handle it.", CloseStatusCode.PolicyViolation); + _extensions = value; + } - return false; - } + // As server + private void processSecWebSocketProtocolClientHeader( + IEnumerable values + ) + { + if (values.Contains(val => val == _protocol)) + return; - // As server - private void refuseHandshake (CloseStatusCode code, string reason) - { - _readyState = WebSocketState.Closing; + _protocol = null; + } - var res = createHandshakeFailureResponse (HttpStatusCode.BadRequest); - sendHttpResponse (res); + private bool processUnsupportedFrame(WebSocketFrame frame) + { + _logger.Fatal("An unsupported frame:" + frame.PrintToString(false)); + fatal("There is no way to handle it.", CloseStatusCode.PolicyViolation); - releaseServerResources (); + return false; + } - _readyState = WebSocketState.Closed; + // As server + private void refuseHandshake(CloseStatusCode code, string reason) + { + _readyState = WebSocketState.Closing; - var e = new CloseEventArgs ((ushort) code, reason, false); + var res = createHandshakeFailureResponse(HttpStatusCode.BadRequest); + sendHttpResponse(res); - try { - OnClose.Emit (this, e); - } - catch (Exception ex) { - _logger.Error (ex.Message); - _logger.Debug (ex.ToString ()); - } - } + releaseServerResources(); - // As client - private void releaseClientResources () - { - if (_stream != null) { - _stream.Dispose (); - _stream = null; - } - - if (_tcpClient != null) { - _tcpClient.Close (); - _tcpClient = null; - } - } + _readyState = WebSocketState.Closed; - 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; - } - } + var e = new CloseEventArgs((ushort)code, reason, false); - private void releaseResources () - { - if (_client) - releaseClientResources (); - else - releaseServerResources (); + try + { + OnClose.Emit(this, e); + } + catch (Exception ex) + { + _logger.Error(ex.Message); + _logger.Debug(ex.ToString()); + } + } - releaseCommonResources (); - } + // As client + private void releaseClientResources() + { + if (_stream != null) + { + _stream.Dispose(); + _stream = null; + } - // As server - private void releaseServerResources () - { - if (_closeContext == null) - return; + if (_tcpClient != null) + { + _tcpClient.Close(); + _tcpClient = null; + } + } - _closeContext (); - _closeContext = null; - _stream = null; - _context = null; - } + private void releaseCommonResources() + { + if (_fragmentsBuffer != null) + { + _fragmentsBuffer.Dispose(); + _fragmentsBuffer = null; + _inContinuation = false; + } - 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; - } + if (_pongReceived != null) + { + _pongReceived.Close(); + _pongReceived = null; + } - sent = send (opcode, stream, compressed); - if (!sent) - error ("A send has been interrupted.", null); - } - catch (Exception ex) { - _logger.Error (ex.ToString ()); - error ("An error has occurred during a send.", ex); + if (_receivingExited != null) + { + _receivingExited.Close(); + _receivingExited = null; + } } - finally { - if (compressed) - stream.Dispose (); - src.Dispose (); + private void releaseResources() + { + if (_client) + releaseClientResources(); + else + releaseServerResources(); + + releaseCommonResources(); } - return sent; - } - } + // As server + private void releaseServerResources() + { + if (_closeContext == null) + return; - 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); - } + _closeContext(); + _closeContext = null; + //_stream.Dispose(); + _stream = null; + _context = null; + } - private bool send (Fin fin, Opcode opcode, byte[] data, bool compressed) - { - lock (_forState) { - if (_readyState != WebSocketState.Open) { - _logger.Error ("The connection is closing."); - return false; + 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.ToString()); + error("An error has occurred during a send.", ex); + } + finally + { + if (compressed) + stream.Dispose(); + + src.Dispose(); + } + + return sent; + } } - var frame = new WebSocketFrame (fin, opcode, data, compressed, _client); - return sendBytes (frame.ToArray ()); - } - } + 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); + } - private void sendAsync (Opcode opcode, Stream stream, Action completed) - { - Func sender = send; - sender.BeginInvoke ( - opcode, - stream, - ar => { - try { - var sent = sender.EndInvoke (ar); - if (completed != null) - completed (sent); - } - catch (Exception ex) { - _logger.Error (ex.ToString ()); - error ( - "An error has occurred during the callback for an async send.", - ex - ); - } - }, - null - ); - } + if (quo == 1 && rem == 0) + { + buff = new byte[FragmentLength]; + return stream.Read(buff, 0, FragmentLength) == FragmentLength + && send(Fin.Final, opcode, buff, compressed); + } - private bool sendBytes (byte[] bytes) - { - try { - _stream.Write (bytes, 0, bytes.Length); - } - catch (Exception ex) { - _logger.Error (ex.Message); - _logger.Debug (ex.ToString ()); + /* Send fragments */ - return false; - } + // Begin + buff = new byte[FragmentLength]; + var sent = stream.Read(buff, 0, FragmentLength) == FragmentLength + && send(Fin.More, opcode, buff, compressed); - return true; - } + if (!sent) + return false; - // As client - private HttpResponse sendHandshakeRequest () - { - var req = createHandshakeRequest (); - var res = sendHttpRequest (req, 90000); - if (res.IsUnauthorized) { - var chal = res.Headers["WWW-Authenticate"]; - _logger.Warn (String.Format ("Received an authentication requirement for '{0}'.", chal)); - if (chal.IsNullOrEmpty ()) { - _logger.Error ("No authentication challenge is specified."); - return res; - } - - _authChallenge = AuthenticationChallenge.Parse (chal); - if (_authChallenge == null) { - _logger.Error ("An invalid authentication challenge is specified."); - return res; - } - - if (_credentials != null && - (!_preAuth || _authChallenge.Scheme == AuthenticationSchemes.Digest)) { - if (res.HasConnectionClose) { - releaseClientResources (); - setClientStream (); - } - - var authRes = new AuthenticationResponse (_authChallenge, _credentials, _nonceCount); - _nonceCount = authRes.NonceCount; - req.Headers["Authorization"] = authRes.ToString (); - res = sendHttpRequest (req, 15000); - } - } - - if (res.IsRedirect) { - var url = res.Headers["Location"]; - _logger.Warn (String.Format ("Received a redirection to '{0}'.", url)); - if (_enableRedirection) { - if (url.IsNullOrEmpty ()) { - _logger.Error ("No url to redirect is located."); - return res; - } + 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); - Uri uri; - string msg; - if (!url.TryCreateWebSocketUri (out uri, out msg)) { - _logger.Error ("An invalid url to redirect is located: " + msg); - return res; - } + if (!sent) + return false; + } - releaseClientResources (); + // End + if (rem == 0) + rem = FragmentLength; + else + buff = new byte[rem]; - _uri = uri; - _secure = uri.Scheme == "wss"; + return stream.Read(buff, 0, rem) == rem + && send(Fin.Final, Opcode.Cont, buff, false); + } - setClientStream (); - return sendHandshakeRequest (); + private bool send(Fin fin, Opcode opcode, byte[] data, bool compressed) + { + lock (_forState) + { + if (_readyState != WebSocketState.Open) + { + _logger.Error("The connection is closing."); + return false; + } + + var frame = new WebSocketFrame(fin, opcode, data, compressed, _client); + return sendBytes(frame.ToArray()); + } } - } - return res; - } + private void sendAsync(Opcode opcode, Stream stream, Action completed) + { + Func sender = send; + sender.BeginInvoke( + opcode, + stream, + ar => + { + try + { + var sent = sender.EndInvoke(ar); + if (completed != null) + completed(sent); + } + catch (Exception ex) + { + _logger.Error(ex.ToString()); + error( + "An error has occurred during the callback for an async send.", + ex + ); + } + }, + null + ); + } - // As client - private HttpResponse sendHttpRequest (HttpRequest request, int millisecondsTimeout) - { - _logger.Debug ("A request to the server:\n" + request.ToString ()); - var res = request.GetResponse (_stream, millisecondsTimeout); - _logger.Debug ("A response to this request:\n" + res.ToString ()); + private bool sendBytes(byte[] bytes) + { + try + { + _stream.Write(bytes, 0, bytes.Length); + } + catch (Exception ex) + { + _logger.Error(ex.Message); + _logger.Debug(ex.ToString()); - return res; - } + return false; + } - // As server - private bool sendHttpResponse (HttpResponse response) - { - _logger.Debug ( - String.Format ( - "A response to {0}:\n{1}", _context.UserEndPoint, response - ) - ); + return true; + } - return sendBytes (response.ToByteArray ()); - } + // As client + private HttpResponse sendHandshakeRequest() + { + var req = createHandshakeRequest(); + var res = sendHttpRequest(req, 90000); + if (res.IsUnauthorized) + { + var chal = res.Headers["WWW-Authenticate"]; + _logger.Warn(String.Format("Received an authentication requirement for '{0}'.", chal)); + if (chal.IsNullOrEmpty()) + { + _logger.Error("No authentication challenge is specified."); + return res; + } + + _authChallenge = AuthenticationChallenge.Parse(chal); + if (_authChallenge == null) + { + _logger.Error("An invalid authentication challenge is specified."); + return res; + } + + if (_credentials != null && + (!_preAuth || _authChallenge.Scheme == AuthenticationSchemes.Digest)) + { + if (res.HasConnectionClose) + { + releaseClientResources(); + setClientStream(); + } + + var authRes = new AuthenticationResponse(_authChallenge, _credentials, _nonceCount); + _nonceCount = authRes.NonceCount; + req.Headers["Authorization"] = authRes.ToString(); + res = sendHttpRequest(req, 15000); + } + } - // As client - private void sendProxyConnectRequest () - { - var req = HttpRequest.CreateConnectRequest (_uri); - var res = sendHttpRequest (req, 90000); - if (res.IsProxyAuthenticationRequired) { - var chal = res.Headers["Proxy-Authenticate"]; - _logger.Warn ( - String.Format ("Received a proxy authentication requirement for '{0}'.", chal)); - - if (chal.IsNullOrEmpty ()) - throw new WebSocketException ("No proxy authentication challenge is specified."); - - var authChal = AuthenticationChallenge.Parse (chal); - if (authChal == null) - throw new WebSocketException ("An invalid proxy authentication challenge is specified."); - - if (_proxyCredentials != null) { - if (res.HasConnectionClose) { - releaseClientResources (); - _tcpClient = new TcpClient (_proxyUri.DnsSafeHost, _proxyUri.Port); - _stream = _tcpClient.GetStream (); - } - - var authRes = new AuthenticationResponse (authChal, _proxyCredentials, 0); - req.Headers["Proxy-Authorization"] = authRes.ToString (); - res = sendHttpRequest (req, 15000); - } - - if (res.IsProxyAuthenticationRequired) - throw new WebSocketException ("A proxy authentication is required."); - } - - if (res.StatusCode[0] != '2') - throw new WebSocketException ( - "The proxy has failed a connection to the requested host and port."); - } + if (res.IsRedirect) + { + var url = res.Headers["Location"]; + _logger.Warn(String.Format("Received a redirection to '{0}'.", url)); + if (_enableRedirection) + { + if (url.IsNullOrEmpty()) + { + _logger.Error("No url to redirect is located."); + return res; + } + + Uri uri; + string msg; + if (!url.TryCreateWebSocketUri(out uri, out msg)) + { + _logger.Error("An invalid url to redirect is located: " + msg); + return res; + } + + releaseClientResources(); + + _uri = uri; + _secure = uri.Scheme == "wss"; + + setClientStream(); + return sendHandshakeRequest(); + } + } - // 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) - throw new WebSocketException ( - CloseStatusCode.TlsHandshakeFailure, "An invalid host name is specified."); - - 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); - } - } - } + return res; + } - 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 => { - if (!processReceivedFrame (frame) || _readyState == WebSocketState.Closed) { - var exited = _receivingExited; - if (exited != null) - exited.Set (); + // As client + private HttpResponse sendHttpRequest(HttpRequest request, int millisecondsTimeout) + { + _logger.Debug("A request to the server:\n" + request.ToString()); + var res = request.GetResponse(_stream, millisecondsTimeout); + _logger.Debug("A response to this request:\n" + res.ToString()); - return; - } + return res; + } - // Receive next asap because the Ping or Close needs a response to it. - receive (); + // As server + private bool sendHttpResponse(HttpResponse response) + { + _logger.Debug( + String.Format( + "A response to {0}:\n{1}", _context.UserEndPoint, response + ) + ); - if (_inMessage || !HasMessage || _readyState != WebSocketState.Open) - return; + return sendBytes(response.ToByteArray()); + } - message (); - }, - ex => { - _logger.Fatal (ex.ToString ()); - fatal ("An exception has occurred while receiving.", ex); + // As client + private void sendProxyConnectRequest() + { + var req = HttpRequest.CreateConnectRequest(_uri); + var res = sendHttpRequest(req, 90000); + if (res.IsProxyAuthenticationRequired) + { + var chal = res.Headers["Proxy-Authenticate"]; + _logger.Warn( + String.Format("Received a proxy authentication requirement for '{0}'.", chal)); + + if (chal.IsNullOrEmpty()) + throw new WebSocketException("No proxy authentication challenge is specified."); + + var authChal = AuthenticationChallenge.Parse(chal); + if (authChal == null) + throw new WebSocketException("An invalid proxy authentication challenge is specified."); + + if (_proxyCredentials != null) + { + if (res.HasConnectionClose) + { + releaseClientResources(); + _tcpClient = new TcpClient(_proxyUri.DnsSafeHost, _proxyUri.Port); + _stream = _tcpClient.GetStream(); + } + + var authRes = new AuthenticationResponse(authChal, _proxyCredentials, 0); + req.Headers["Proxy-Authorization"] = authRes.ToString(); + res = sendHttpRequest(req, 15000); + } + + if (res.IsProxyAuthenticationRequired) + throw new WebSocketException("A proxy authentication is required."); } - ); - receive (); - } + if (res.StatusCode[0] != '2') + throw new WebSocketException( + "The proxy has failed a connection to the requested host and port."); + } - // As client - private bool validateSecWebSocketAcceptHeader (string value) - { - return value != null && value == CreateResponseKey (_base64Key); - } + // 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(); + } - // As client - private bool validateSecWebSocketExtensionsServerHeader (string value) - { - if (value == null) - return true; + if (_secure) + { + var conf = getSslConfiguration(); + var host = conf.TargetHost; + if (host != _uri.DnsSafeHost) + throw new WebSocketException( + CloseStatusCode.TlsHandshakeFailure, "An invalid host name is specified."); + + 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 => + { + if (!processReceivedFrame(frame) || _readyState == WebSocketState.Closed) + { + var exited = _receivingExited; + if (exited != null) + exited.Set(); + + return; + } + + // Receive next asap because the Ping or Close needs a response to it. + receive(); + + if (_inMessage || !HasMessage || _readyState != WebSocketState.Open) + return; + + message(); + }, + ex => + { + _logger.Error(ex.ToString()); + fatal("An exception has occurred while receiving.", ex); + } + ); + + receive(); + } - if (value.Length == 0) - return false; + // As client + private bool validateSecWebSocketAcceptHeader(string value) + { + return value != null && value == CreateResponseKey(_base64Key); + } - if (!_extensionsRequested) - return false; + // 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 e in value.SplitHeaderValue(',')) + { + var ext = e.Trim(); + if (comp && ext.IsCompressionExtension(_compression)) + { + if (!ext.Contains("server_no_context_takeover")) + { + _logger.Error("The server hasn't sent back 'server_no_context_takeover'."); + return false; + } + + if (!ext.Contains("client_no_context_takeover")) + _logger.Warn("The server hasn't sent back 'client_no_context_takeover'."); + + var method = _compression.ToExtensionString(); + var invalid = + ext.SplitHeaderValue(';').Contains( + t => + { + t = t.Trim(); + return t != method + && t != "server_no_context_takeover" + && t != "client_no_context_takeover"; + } + ); + + if (invalid) + return false; + } + else + { + return false; + } + } - var comp = _compression != CompressionMethod.None; - foreach (var e in value.SplitHeaderValue (',')) { - var ext = e.Trim (); - if (comp && ext.IsCompressionExtension (_compression)) { - if (!ext.Contains ("server_no_context_takeover")) { - _logger.Error ("The server hasn't sent back 'server_no_context_takeover'."); - return false; - } - - if (!ext.Contains ("client_no_context_takeover")) - _logger.Warn ("The server hasn't sent back 'client_no_context_takeover'."); - - var method = _compression.ToExtensionString (); - var invalid = - ext.SplitHeaderValue (';').Contains ( - t => { - t = t.Trim (); - return t != method - && t != "server_no_context_takeover" - && t != "client_no_context_takeover"; - } - ); + return true; + } - if (invalid) - return false; + // As client + private bool validateSecWebSocketProtocolServerHeader(string value) + { + if (value == null) + return !_protocolsRequested; + + if (value.Length == 0) + return false; + + return _protocolsRequested && _protocols.Contains(p => p == value); } - else { - return false; + + // As client + private bool validateSecWebSocketVersionServerHeader(string value) + { + return value == null || value == _version; } - } - return true; - } + #endregion - // As client - private bool validateSecWebSocketProtocolServerHeader (string value) - { - if (value == null) - return !_protocolsRequested; + #region Internal Methods - if (value.Length == 0) - return false; + // As server + internal void Close(HttpResponse response) + { + _readyState = WebSocketState.Closing; - return _protocolsRequested && _protocols.Contains (p => p == value); - } + sendHttpResponse(response); + releaseServerResources(); - // As client - private bool validateSecWebSocketVersionServerHeader (string value) - { - return value == null || value == _version; - } + _readyState = WebSocketState.Closed; + } + + // As server + internal void Close(HttpStatusCode code) + { + Close(createHandshakeFailureResponse(code)); + } - #endregion + // As server + internal void Close(PayloadData payloadData, byte[] frameAsBytes) + { + lock (_forState) + { + if (_readyState == WebSocketState.Closing) + { + _logger.Info("The closing is already in progress."); + return; + } + + if (_readyState == WebSocketState.Closed) + { + _logger.Info("The connection has already been closed."); + return; + } + + _readyState = WebSocketState.Closing; + } - #region Internal Methods + _logger.Trace("Begin closing the connection."); - // As server - internal void Close (HttpResponse response) - { - _readyState = WebSocketState.Closing; + var sent = frameAsBytes != null && sendBytes(frameAsBytes); + var received = sent && _receivingExited != null + ? _receivingExited.WaitOne(_waitTime) + : false; - sendHttpResponse (response); - releaseServerResources (); + var res = sent && received; - _readyState = WebSocketState.Closed; - } + _logger.Debug( + String.Format( + "Was clean?: {0}\n sent: {1}\n received: {2}", res, sent, received + ) + ); - // As server - internal void Close (HttpStatusCode code) - { - Close (createHandshakeFailureResponse (code)); - } + releaseServerResources(); + releaseCommonResources(); - // As server - internal void Close (PayloadData payloadData, byte[] frameAsBytes) - { - lock (_forState) { - if (_readyState == WebSocketState.Closing) { - _logger.Info ("The closing is already in progress."); - return; + _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); } - if (_readyState == WebSocketState.Closed) { - _logger.Info ("The connection has already been closed."); - return; + internal static string CreateResponseKey(string base64Key) + { + var buff = new StringBuilder(base64Key, 64); + buff.Append(_guid); + SHA1 sha1 = new SHA1CryptoServiceProvider(); + var src = sha1.ComputeHash(buff.ToString().GetUTF8EncodedBytes()); + + return Convert.ToBase64String(src); } - _readyState = WebSocketState.Closing; - } + // As server + internal void InternalAccept() + { + try + { + if (!acceptHandshake()) + return; + } + catch (Exception ex) + { + _logger.Fatal(ex.Message); + _logger.Debug(ex.ToString()); + + var msg = "An exception has occurred while attempting to accept."; + fatal(msg, ex); - _logger.Trace ("Begin closing the connection."); + return; + } - var sent = frameAsBytes != null && sendBytes (frameAsBytes); - var received = sent && _receivingExited != null - ? _receivingExited.WaitOne (_waitTime) - : false; + _readyState = WebSocketState.Open; + + open(); + } - var res = sent && received; + // As server +#if __PING_ASYNC + internal async void PingAsync(byte[] frameAsBytes, TimeSpan timeout, Action callback) + { + var b = await pingAsync(frameAsBytes, timeout); + if (b == true) _isAlive = true; + else b = false; + //Console.WriteLine("{0} iiiiiiiii{1}iiiiiiiiiiiiiiiiiiiii",DateTime.Now.ToString("hh:MM:ss"), _isAlive); + if (null != callback) callback(b); + } + private async Task pingAsync(byte[] frameAsBytes, TimeSpan timeout) + { + if (_readyState != WebSocketState.Open) + return false; + if (_isPinged == false) return false; + var pongReceived = _pongReceived; + if (pongReceived == null) + return false; + return await Task.Run(() => + { + lock (_forPing) + { + try + { + pongReceived.Reset(); + + lock (_forState) + { + if (_readyState != WebSocketState.Open) + return false; + + if (!sendBytes(frameAsBytes)) + return false; + } + + return pongReceived.WaitOne(timeout); + + } + catch (ObjectDisposedException) + { + return false; + } + finally + { + _isPinged = true; + } + } + }); - _logger.Debug ( - String.Format ( - "Was clean?: {0}\n sent: {1}\n received: {2}", res, sent, received + } +#endif + internal bool Ping(byte[] frameAsBytes, TimeSpan timeout) + { + if (_readyState != WebSocketState.Open) + return false; + + var pongReceived = _pongReceived; + if (pongReceived == null) + return false; + + lock (_forPing) + { + try + { + pongReceived.Reset(); + + lock (_forState) + { + if (_readyState != WebSocketState.Open) + return false; + + if (!sendBytes(frameAsBytes)) + return false; + } + + return pongReceived.WaitOne(timeout); + } + catch (ObjectDisposedException) + { + return false; + } + } + } + // As server + internal void Send( + Opcode opcode, byte[] data, Dictionary 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(); - releaseServerResources (); - releaseCommonResources (); + cache.Add(_compression, found); + } - _logger.Trace ("End closing the connection."); + sendBytes(found); + } + } + } - _readyState = WebSocketState.Closed; + // As server + internal void Send( + Opcode opcode, Stream stream, Dictionary 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); + } + } - var e = new CloseEventArgs (payloadData, res); + #endregion + + #region Public Methods + + /// + /// Accepts the handshake request. + /// + /// + /// This method does nothing if the handshake request has already been + /// accepted. + /// + /// + /// + /// This instance is a client. + /// + /// + /// -or- + /// + /// + /// The close process is in progress. + /// + /// + /// -or- + /// + /// + /// The connection has already been closed. + /// + /// + public void Accept() + { + if (_client) + { + var msg = "This instance is a client."; + throw new InvalidOperationException(msg); + } - try { - OnClose.Emit (this, e); - } - catch (Exception ex) { - _logger.Error (ex.Message); - _logger.Debug (ex.ToString ()); - } - } + if (_readyState == WebSocketState.Closing) + { + var msg = "The close process is in progress."; + throw new InvalidOperationException(msg); + } - // As client - internal static string CreateBase64Key () - { - var src = new byte[16]; - RandomNumber.GetBytes (src); + if (_readyState == WebSocketState.Closed) + { + var msg = "The connection has already been closed."; + throw new InvalidOperationException(msg); + } - return Convert.ToBase64String (src); - } + if (accept()) + open(); + } - internal static string CreateResponseKey (string base64Key) - { - var buff = new StringBuilder (base64Key, 64); - buff.Append (_guid); - SHA1 sha1 = new SHA1CryptoServiceProvider (); - var src = sha1.ComputeHash (buff.ToString ().GetUTF8EncodedBytes ()); + /// + /// Accepts the handshake request asynchronously. + /// + /// + /// + /// This method does not wait for the accept process to be complete. + /// + /// + /// This method does nothing if the handshake request has already been + /// accepted. + /// + /// + /// + /// + /// This instance is a client. + /// + /// + /// -or- + /// + /// + /// The close process is in progress. + /// + /// + /// -or- + /// + /// + /// The connection has already been closed. + /// + /// + public void AcceptAsync() + { + if (_client) + { + var msg = "This instance is a client."; + throw new InvalidOperationException(msg); + } - return Convert.ToBase64String (src); - } + if (_readyState == WebSocketState.Closing) + { + var msg = "The close process is in progress."; + throw new InvalidOperationException(msg); + } - // As server - internal void InternalAccept () - { - try { - if (!acceptHandshake ()) - return; - } - catch (Exception ex) { - _logger.Fatal (ex.Message); - _logger.Debug (ex.ToString ()); + if (_readyState == WebSocketState.Closed) + { + var msg = "The connection has already been closed."; + throw new InvalidOperationException(msg); + } - var msg = "An exception has occurred while attempting to accept."; - fatal (msg, ex); + Func acceptor = accept; + acceptor.BeginInvoke( + ar => + { + if (acceptor.EndInvoke(ar)) + open(); + }, + null + ); + } - return; - } + /// + /// Closes the connection. + /// + /// + /// This method does nothing if the current state of the connection is + /// Closing or Closed. + /// + public void Close() + { + close(1005, String.Empty); + } - _readyState = WebSocketState.Open; + /// + /// Closes the connection with the specified code. + /// + /// + /// This method does nothing if the current state of the connection is + /// Closing or Closed. + /// + /// + /// + /// A that represents the status code indicating + /// the reason for the close. + /// + /// + /// The status codes are defined in + /// + /// Section 7.4 of RFC 6455. + /// + /// + /// + /// is less than 1000 or greater than 4999. + /// + /// + /// + /// is 1011 (server error). + /// It cannot be used by clients. + /// + /// + /// -or- + /// + /// + /// is 1010 (mandatory extension). + /// It cannot be used by servers. + /// + /// + public void Close(ushort code) + { + if (!code.IsCloseStatusCode()) + { + var msg = "Less than 1000 or greater than 4999."; + throw new ArgumentOutOfRangeException("code", msg); + } - open (); - } + if (_client && code == 1011) + { + var msg = "1011 cannot be used."; + throw new ArgumentException(msg, "code"); + } - // As server - internal bool Ping (byte[] frameAsBytes, TimeSpan timeout) - { - if (_readyState != WebSocketState.Open) - return false; + if (!_client && code == 1010) + { + var msg = "1010 cannot be used."; + throw new ArgumentException(msg, "code"); + } - var pongReceived = _pongReceived; - if (pongReceived == null) - return false; + close(code, String.Empty); + } - lock (_forPing) { - try { - pongReceived.Reset (); + /// + /// Closes the connection with the specified code. + /// + /// + /// This method does nothing if the current state of the connection is + /// Closing or Closed. + /// + /// + /// + /// One of the enum values. + /// + /// + /// It represents the status code indicating the reason for the close. + /// + /// + /// + /// + /// is + /// . + /// It cannot be used by clients. + /// + /// + /// -or- + /// + /// + /// is + /// . + /// It cannot be used by servers. + /// + /// + public void Close(CloseStatusCode code) + { + if (_client && code == CloseStatusCode.ServerError) + { + var msg = "ServerError cannot be used."; + throw new ArgumentException(msg, "code"); + } - lock (_forState) { - if (_readyState != WebSocketState.Open) - return false; + if (!_client && code == CloseStatusCode.MandatoryExtension) + { + var msg = "MandatoryExtension cannot be used."; + throw new ArgumentException(msg, "code"); + } + + close((ushort)code, String.Empty); + } + + /// + /// Closes the connection with the specified code and reason. + /// + /// + /// This method does nothing if the current state of the connection is + /// Closing or Closed. + /// + /// + /// + /// A that represents the status code indicating + /// the reason for the close. + /// + /// + /// The status codes are defined in + /// + /// Section 7.4 of RFC 6455. + /// + /// + /// + /// + /// A that represents the reason for the close. + /// + /// + /// The size must be 123 bytes or less in UTF-8. + /// + /// + /// + /// + /// is less than 1000 or greater than 4999. + /// + /// + /// -or- + /// + /// + /// The size of is greater than 123 bytes. + /// + /// + /// + /// + /// is 1011 (server error). + /// It cannot be used by clients. + /// + /// + /// -or- + /// + /// + /// is 1010 (mandatory extension). + /// It cannot be used by servers. + /// + /// + /// -or- + /// + /// + /// is 1005 (no status) and there is reason. + /// + /// + /// -or- + /// + /// + /// could not be UTF-8-encoded. + /// + /// + 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); + } - if (!sendBytes (frameAsBytes)) - return false; - } + /// + /// Closes the connection with the specified code and reason. + /// + /// + /// This method does nothing if the current state of the connection is + /// Closing or Closed. + /// + /// + /// + /// One of the enum values. + /// + /// + /// It represents the status code indicating the reason for the close. + /// + /// + /// + /// + /// A that represents the reason for the close. + /// + /// + /// The size must be 123 bytes or less in UTF-8. + /// + /// + /// + /// + /// is + /// . + /// It cannot be used by clients. + /// + /// + /// -or- + /// + /// + /// is + /// . + /// It cannot be used by servers. + /// + /// + /// -or- + /// + /// + /// is + /// and there is reason. + /// + /// + /// -or- + /// + /// + /// could not be UTF-8-encoded. + /// + /// + /// + /// The size of is greater than 123 bytes. + /// + 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"); + } - return pongReceived.WaitOne (timeout); + 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); } - catch (ObjectDisposedException) { - return false; + + /// + /// Closes the connection asynchronously. + /// + /// + /// + /// This method does not wait for the close to be complete. + /// + /// + /// This method does nothing if the current state of the connection is + /// Closing or Closed. + /// + /// + public void CloseAsync() + { + closeAsync(1005, String.Empty); } - } - } - // As server - internal void Send ( - Opcode opcode, byte[] data, Dictionary 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); - } - } - } + /// + /// Closes the connection asynchronously with the specified code. + /// + /// + /// + /// This method does not wait for the close to be complete. + /// + /// + /// This method does nothing if the current state of the connection is + /// Closing or Closed. + /// + /// + /// + /// + /// A that represents the status code indicating + /// the reason for the close. + /// + /// + /// The status codes are defined in + /// + /// Section 7.4 of RFC 6455. + /// + /// + /// + /// is less than 1000 or greater than 4999. + /// + /// + /// + /// is 1011 (server error). + /// It cannot be used by clients. + /// + /// + /// -or- + /// + /// + /// is 1010 (mandatory extension). + /// It cannot be used by servers. + /// + /// + public void CloseAsync(ushort code) + { + if (!code.IsCloseStatusCode()) + { + var msg = "Less than 1000 or greater than 4999."; + throw new ArgumentOutOfRangeException("code", msg); + } - // As server - internal void Send ( - Opcode opcode, Stream stream, Dictionary cache - ) - { - lock (_forSend) { - Stream found; - if (!cache.TryGetValue (_compression, out found)) { - found = stream.Compress (_compression); - cache.Add (_compression, found); + 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"); + } + + closeAsync(code, String.Empty); } - else { - found.Position = 0; + + /// + /// Closes the connection asynchronously with the specified code. + /// + /// + /// + /// This method does not wait for the close to be complete. + /// + /// + /// This method does nothing if the current state of the connection is + /// Closing or Closed. + /// + /// + /// + /// + /// One of the enum values. + /// + /// + /// It represents the status code indicating the reason for the close. + /// + /// + /// + /// + /// is + /// . + /// It cannot be used by clients. + /// + /// + /// -or- + /// + /// + /// is + /// . + /// It cannot be used by servers. + /// + /// + public void CloseAsync(CloseStatusCode code) + { + 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"); + } + + closeAsync((ushort)code, String.Empty); } - send (opcode, found, _compression != CompressionMethod.None); - } - } + /// + /// Closes the connection asynchronously with the specified code and reason. + /// + /// + /// + /// This method does not wait for the close to be complete. + /// + /// + /// This method does nothing if the current state of the connection is + /// Closing or Closed. + /// + /// + /// + /// + /// A that represents the status code indicating + /// the reason for the close. + /// + /// + /// The status codes are defined in + /// + /// Section 7.4 of RFC 6455. + /// + /// + /// + /// + /// A that represents the reason for the close. + /// + /// + /// The size must be 123 bytes or less in UTF-8. + /// + /// + /// + /// + /// is less than 1000 or greater than 4999. + /// + /// + /// -or- + /// + /// + /// The size of is greater than 123 bytes. + /// + /// + /// + /// + /// is 1011 (server error). + /// It cannot be used by clients. + /// + /// + /// -or- + /// + /// + /// is 1010 (mandatory extension). + /// It cannot be used by servers. + /// + /// + /// -or- + /// + /// + /// is 1005 (no status) and there is reason. + /// + /// + /// -or- + /// + /// + /// could not be UTF-8-encoded. + /// + /// + public void CloseAsync(ushort code, string reason) + { + if (!code.IsCloseStatusCode()) + { + var msg = "Less than 1000 or greater than 4999."; + throw new ArgumentOutOfRangeException("code", msg); + } - #endregion + if (_client && code == 1011) + { + var msg = "1011 cannot be used."; + throw new ArgumentException(msg, "code"); + } - #region Public Methods + if (!_client && code == 1010) + { + var msg = "1010 cannot be used."; + throw new ArgumentException(msg, "code"); + } - /// - /// Accepts the handshake request. - /// - /// - /// This method does nothing if the handshake request has already been - /// accepted. - /// - /// - /// - /// This instance is a client. - /// - /// - /// -or- - /// - /// - /// The close process is in progress. - /// - /// - /// -or- - /// - /// - /// The connection has already been closed. - /// - /// - public void Accept () - { - if (_client) { - var msg = "This instance is a client."; - throw new InvalidOperationException (msg); - } - - if (_readyState == WebSocketState.Closing) { - var msg = "The close process is in progress."; - throw new InvalidOperationException (msg); - } - - if (_readyState == WebSocketState.Closed) { - var msg = "The connection has already been closed."; - throw new InvalidOperationException (msg); - } - - if (accept ()) - open (); - } + if (reason.IsNullOrEmpty()) + { + closeAsync(code, String.Empty); + return; + } - /// - /// Accepts the handshake request asynchronously. - /// - /// - /// - /// This method does not wait for the accept process to be complete. - /// - /// - /// This method does nothing if the handshake request has already been - /// accepted. - /// - /// - /// - /// - /// This instance is a client. - /// - /// - /// -or- - /// - /// - /// The close process is in progress. - /// - /// - /// -or- - /// - /// - /// The connection has already been closed. - /// - /// - public void AcceptAsync () - { - if (_client) { - var msg = "This instance is a client."; - throw new InvalidOperationException (msg); - } - - if (_readyState == WebSocketState.Closing) { - var msg = "The close process is in progress."; - throw new InvalidOperationException (msg); - } - - if (_readyState == WebSocketState.Closed) { - var msg = "The connection has already been closed."; - throw new InvalidOperationException (msg); - } - - Func acceptor = accept; - acceptor.BeginInvoke ( - ar => { - if (acceptor.EndInvoke (ar)) - open (); - }, - null - ); - } + if (code == 1005) + { + var msg = "1005 cannot be used."; + throw new ArgumentException(msg, "code"); + } - /// - /// Closes the connection. - /// - /// - /// This method does nothing if the current state of the connection is - /// Closing or Closed. - /// - public void Close () - { - close (1005, String.Empty); - } + byte[] bytes; + if (!reason.TryGetUTF8EncodedBytes(out bytes)) + { + var msg = "It could not be UTF-8-encoded."; + throw new ArgumentException(msg, "reason"); + } - /// - /// Closes the connection with the specified code. - /// - /// - /// This method does nothing if the current state of the connection is - /// Closing or Closed. - /// - /// - /// - /// A that represents the status code indicating - /// the reason for the close. - /// - /// - /// The status codes are defined in - /// - /// Section 7.4 of RFC 6455. - /// - /// - /// - /// is less than 1000 or greater than 4999. - /// - /// - /// - /// is 1011 (server error). - /// It cannot be used by clients. - /// - /// - /// -or- - /// - /// - /// is 1010 (mandatory extension). - /// It cannot be used by servers. - /// - /// - public void Close (ushort code) - { - 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"); - } - - close (code, String.Empty); - } + if (bytes.Length > 123) + { + var msg = "Its size is greater than 123 bytes."; + throw new ArgumentOutOfRangeException("reason", msg); + } - /// - /// Closes the connection with the specified code. - /// - /// - /// This method does nothing if the current state of the connection is - /// Closing or Closed. - /// - /// - /// - /// One of the enum values. - /// - /// - /// It represents the status code indicating the reason for the close. - /// - /// - /// - /// - /// is - /// . - /// It cannot be used by clients. - /// - /// - /// -or- - /// - /// - /// is - /// . - /// It cannot be used by servers. - /// - /// - public void Close (CloseStatusCode code) - { - if (_client && code == CloseStatusCode.ServerError) { - var msg = "ServerError cannot be used."; - throw new ArgumentException (msg, "code"); - } + closeAsync(code, reason); + } - if (!_client && code == CloseStatusCode.MandatoryExtension) { - var msg = "MandatoryExtension cannot be used."; - throw new ArgumentException (msg, "code"); - } + /// + /// Closes the connection asynchronously with the specified code and reason. + /// + /// + /// + /// This method does not wait for the close to be complete. + /// + /// + /// This method does nothing if the current state of the connection is + /// Closing or Closed. + /// + /// + /// + /// + /// One of the enum values. + /// + /// + /// It represents the status code indicating the reason for the close. + /// + /// + /// + /// + /// A that represents the reason for the close. + /// + /// + /// The size must be 123 bytes or less in UTF-8. + /// + /// + /// + /// + /// is + /// . + /// It cannot be used by clients. + /// + /// + /// -or- + /// + /// + /// is + /// . + /// It cannot be used by servers. + /// + /// + /// -or- + /// + /// + /// is + /// and there is reason. + /// + /// + /// -or- + /// + /// + /// could not be UTF-8-encoded. + /// + /// + /// + /// The size of is greater than 123 bytes. + /// + public void CloseAsync(CloseStatusCode code, string reason) + { + if (_client && code == CloseStatusCode.ServerError) + { + var msg = "ServerError cannot be used."; + throw new ArgumentException(msg, "code"); + } - close ((ushort) code, String.Empty); - } + if (!_client && code == CloseStatusCode.MandatoryExtension) + { + var msg = "MandatoryExtension cannot be used."; + throw new ArgumentException(msg, "code"); + } - /// - /// Closes the connection with the specified code and reason. - /// - /// - /// This method does nothing if the current state of the connection is - /// Closing or Closed. - /// - /// - /// - /// A that represents the status code indicating - /// the reason for the close. - /// - /// - /// The status codes are defined in - /// - /// Section 7.4 of RFC 6455. - /// - /// - /// - /// - /// A that represents the reason for the close. - /// - /// - /// The size must be 123 bytes or less in UTF-8. - /// - /// - /// - /// - /// is less than 1000 or greater than 4999. - /// - /// - /// -or- - /// - /// - /// The size of is greater than 123 bytes. - /// - /// - /// - /// - /// is 1011 (server error). - /// It cannot be used by clients. - /// - /// - /// -or- - /// - /// - /// is 1010 (mandatory extension). - /// It cannot be used by servers. - /// - /// - /// -or- - /// - /// - /// is 1005 (no status) and there is reason. - /// - /// - /// -or- - /// - /// - /// could not be UTF-8-encoded. - /// - /// - 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); - } + if (reason.IsNullOrEmpty()) + { + closeAsync((ushort)code, String.Empty); + return; + } - /// - /// Closes the connection with the specified code and reason. - /// - /// - /// This method does nothing if the current state of the connection is - /// Closing or Closed. - /// - /// - /// - /// One of the enum values. - /// - /// - /// It represents the status code indicating the reason for the close. - /// - /// - /// - /// - /// A that represents the reason for the close. - /// - /// - /// The size must be 123 bytes or less in UTF-8. - /// - /// - /// - /// - /// is - /// . - /// It cannot be used by clients. - /// - /// - /// -or- - /// - /// - /// is - /// . - /// It cannot be used by servers. - /// - /// - /// -or- - /// - /// - /// is - /// and there is reason. - /// - /// - /// -or- - /// - /// - /// could not be UTF-8-encoded. - /// - /// - /// - /// The size of is greater than 123 bytes. - /// - 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); - } + if (code == CloseStatusCode.NoStatus) + { + var msg = "NoStatus cannot be used."; + throw new ArgumentException(msg, "code"); + } - /// - /// Closes the connection asynchronously. - /// - /// - /// - /// This method does not wait for the close to be complete. - /// - /// - /// This method does nothing if the current state of the connection is - /// Closing or Closed. - /// - /// - public void CloseAsync () - { - closeAsync (1005, String.Empty); - } + byte[] bytes; + if (!reason.TryGetUTF8EncodedBytes(out bytes)) + { + var msg = "It could not be UTF-8-encoded."; + throw new ArgumentException(msg, "reason"); + } - /// - /// Closes the connection asynchronously with the specified code. - /// - /// - /// - /// This method does not wait for the close to be complete. - /// - /// - /// This method does nothing if the current state of the connection is - /// Closing or Closed. - /// - /// - /// - /// - /// A that represents the status code indicating - /// the reason for the close. - /// - /// - /// The status codes are defined in - /// - /// Section 7.4 of RFC 6455. - /// - /// - /// - /// is less than 1000 or greater than 4999. - /// - /// - /// - /// is 1011 (server error). - /// It cannot be used by clients. - /// - /// - /// -or- - /// - /// - /// is 1010 (mandatory extension). - /// It cannot be used by servers. - /// - /// - public void CloseAsync (ushort code) - { - 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"); - } - - closeAsync (code, String.Empty); - } + if (bytes.Length > 123) + { + var msg = "Its size is greater than 123 bytes."; + throw new ArgumentOutOfRangeException("reason", msg); + } - /// - /// Closes the connection asynchronously with the specified code. - /// - /// - /// - /// This method does not wait for the close to be complete. - /// - /// - /// This method does nothing if the current state of the connection is - /// Closing or Closed. - /// - /// - /// - /// - /// One of the enum values. - /// - /// - /// It represents the status code indicating the reason for the close. - /// - /// - /// - /// - /// is - /// . - /// It cannot be used by clients. - /// - /// - /// -or- - /// - /// - /// is - /// . - /// It cannot be used by servers. - /// - /// - public void CloseAsync (CloseStatusCode code) - { - if (_client && code == CloseStatusCode.ServerError) { - var msg = "ServerError cannot be used."; - throw new ArgumentException (msg, "code"); - } + closeAsync((ushort)code, reason); + } - if (!_client && code == CloseStatusCode.MandatoryExtension) { - var msg = "MandatoryExtension cannot be used."; - throw new ArgumentException (msg, "code"); - } + /// + /// Establishes a connection. + /// + /// + /// This method does nothing if the connection has already been established. + /// + /// + /// + /// This instance is not a client. + /// + /// + /// -or- + /// + /// + /// The close process is in progress. + /// + /// + /// -or- + /// + /// + /// A series of reconnecting has failed. + /// + /// + public void Connect() + { + if (!_client) + { + var msg = "This instance is not a client."; + throw new InvalidOperationException(msg); + } - closeAsync ((ushort) code, String.Empty); - } + if (_readyState == WebSocketState.Closing) + { + var msg = "The close process is in progress."; + throw new InvalidOperationException(msg); + } - /// - /// Closes the connection asynchronously with the specified code and reason. - /// - /// - /// - /// This method does not wait for the close to be complete. - /// - /// - /// This method does nothing if the current state of the connection is - /// Closing or Closed. - /// - /// - /// - /// - /// A that represents the status code indicating - /// the reason for the close. - /// - /// - /// The status codes are defined in - /// - /// Section 7.4 of RFC 6455. - /// - /// - /// - /// - /// A that represents the reason for the close. - /// - /// - /// The size must be 123 bytes or less in UTF-8. - /// - /// - /// - /// - /// is less than 1000 or greater than 4999. - /// - /// - /// -or- - /// - /// - /// The size of is greater than 123 bytes. - /// - /// - /// - /// - /// is 1011 (server error). - /// It cannot be used by clients. - /// - /// - /// -or- - /// - /// - /// is 1010 (mandatory extension). - /// It cannot be used by servers. - /// - /// - /// -or- - /// - /// - /// is 1005 (no status) and there is reason. - /// - /// - /// -or- - /// - /// - /// could not be UTF-8-encoded. - /// - /// - 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); - } + if (_retryCountForConnect > _maxRetryCountForConnect) + { + var msg = "A series of reconnecting has failed."; + throw new InvalidOperationException(msg); + } - /// - /// Closes the connection asynchronously with the specified code and reason. - /// - /// - /// - /// This method does not wait for the close to be complete. - /// - /// - /// This method does nothing if the current state of the connection is - /// Closing or Closed. - /// - /// - /// - /// - /// One of the enum values. - /// - /// - /// It represents the status code indicating the reason for the close. - /// - /// - /// - /// - /// A that represents the reason for the close. - /// - /// - /// The size must be 123 bytes or less in UTF-8. - /// - /// - /// - /// - /// is - /// . - /// It cannot be used by clients. - /// - /// - /// -or- - /// - /// - /// is - /// . - /// It cannot be used by servers. - /// - /// - /// -or- - /// - /// - /// is - /// and there is reason. - /// - /// - /// -or- - /// - /// - /// could not be UTF-8-encoded. - /// - /// - /// - /// The size of is greater than 123 bytes. - /// - 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); - } + if (connect()) + open(); + } - /// - /// Establishes a connection. - /// - /// - /// This method does nothing if the connection has already been established. - /// - /// - /// - /// This instance is not a client. - /// - /// - /// -or- - /// - /// - /// The close process is in progress. - /// - /// - /// -or- - /// - /// - /// A series of reconnecting has failed. - /// - /// - public void Connect () - { - if (!_client) { - var msg = "This instance is not a client."; - throw new InvalidOperationException (msg); - } - - if (_readyState == WebSocketState.Closing) { - var msg = "The close process is in progress."; - throw new InvalidOperationException (msg); - } - - if (_retryCountForConnect > _maxRetryCountForConnect) { - var msg = "A series of reconnecting has failed."; - throw new InvalidOperationException (msg); - } - - if (connect ()) - open (); - } + /// + /// Establishes a connection asynchronously. + /// + /// + /// + /// This method does not wait for the connect process to be complete. + /// + /// + /// This method does nothing if the connection has already been + /// established. + /// + /// + /// + /// + /// This instance is not a client. + /// + /// + /// -or- + /// + /// + /// The close process is in progress. + /// + /// + /// -or- + /// + /// + /// A series of reconnecting has failed. + /// + /// + public void ConnectAsync() + { + if (!_client) + { + var msg = "This instance is not a client."; + throw new InvalidOperationException(msg); + } - /// - /// Establishes a connection asynchronously. - /// - /// - /// - /// This method does not wait for the connect process to be complete. - /// - /// - /// This method does nothing if the connection has already been - /// established. - /// - /// - /// - /// - /// This instance is not a client. - /// - /// - /// -or- - /// - /// - /// The close process is in progress. - /// - /// - /// -or- - /// - /// - /// A series of reconnecting has failed. - /// - /// - public void ConnectAsync () - { - if (!_client) { - var msg = "This instance is not a client."; - throw new InvalidOperationException (msg); - } - - if (_readyState == WebSocketState.Closing) { - var msg = "The close process is in progress."; - throw new InvalidOperationException (msg); - } - - if (_retryCountForConnect > _maxRetryCountForConnect) { - var msg = "A series of reconnecting has failed."; - throw new InvalidOperationException (msg); - } - - Func connector = connect; - connector.BeginInvoke ( - ar => { - if (connector.EndInvoke (ar)) - open (); - }, - null - ); - } + if (_readyState == WebSocketState.Closing) + { + var msg = "The close process is in progress."; + throw new InvalidOperationException(msg); + } - /// - /// Sends a ping using the WebSocket connection. - /// - /// - /// true if the send has done with no error and a pong has been - /// received within a time; otherwise, false. - /// - public bool Ping () - { - return ping (EmptyBytes); - } + if (_retryCountForConnect > _maxRetryCountForConnect) + { + var msg = "A series of reconnecting has failed."; + throw new InvalidOperationException(msg); + } - /// - /// Sends a ping with using the WebSocket - /// connection. - /// - /// - /// true if the send has done with no error and a pong has been - /// received within a time; otherwise, false. - /// - /// - /// - /// A that represents the message to send. - /// - /// - /// The size must be 125 bytes or less in UTF-8. - /// - /// - /// - /// could not be UTF-8-encoded. - /// - /// - /// The size of is greater than 125 bytes. - /// - public bool Ping (string message) - { - if (message.IsNullOrEmpty ()) - return ping (EmptyBytes); + Func connector = connect; + connector.BeginInvoke( + ar => + { + if (connector.EndInvoke(ar)) + open(); + }, + null + ); + } - byte[] bytes; - if (!message.TryGetUTF8EncodedBytes (out bytes)) { - var msg = "It could not be UTF-8-encoded."; - throw new ArgumentException (msg, "message"); - } + /// + /// Sends a ping using the WebSocket connection. + /// + /// + /// true if the send has done with no error and a pong has been + /// received within a time; otherwise, false. + /// + public bool Ping() + { + return ping(EmptyBytes); + } - if (bytes.Length > 125) { - var msg = "Its size is greater than 125 bytes."; - throw new ArgumentOutOfRangeException ("message", msg); - } + /// + /// Sends a ping with using the WebSocket + /// connection. + /// + /// + /// true if the send has done with no error and a pong has been + /// received within a time; otherwise, false. + /// + /// + /// + /// A that represents the message to send. + /// + /// + /// The size must be 125 bytes or less in UTF-8. + /// + /// + /// + /// could not be UTF-8-encoded. + /// + /// + /// The size of is greater than 125 bytes. + /// + public bool Ping(string message) + { + 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"); + } - return ping (bytes); - } + if (bytes.Length > 125) + { + var msg = "Its size is greater than 125 bytes."; + throw new ArgumentOutOfRangeException("message", msg); + } - /// - /// Sends the specified data using the WebSocket connection. - /// - /// - /// An array of that represents the binary data to send. - /// - /// - /// The current state of the connection is not Open. - /// - /// - /// is . - /// - public void Send (byte[] data) - { - if (_readyState != WebSocketState.Open) { - var msg = "The current state of the connection is not Open."; - throw new InvalidOperationException (msg); - } + return ping(bytes); + } - if (data == null) - throw new ArgumentNullException ("data"); + /// + /// Sends the specified data using the WebSocket connection. + /// + /// + /// An array of that represents the binary data to send. + /// + /// + /// The current state of the connection is not Open. + /// + /// + /// is . + /// + public void Send(byte[] data) + { + if (_readyState != WebSocketState.Open) + { + var msg = "The current state of the connection is not Open."; + throw new InvalidOperationException(msg); + } - send (Opcode.Binary, new MemoryStream (data)); - } + if (data == null) + throw new ArgumentNullException("data"); - /// - /// Sends the specified file using the WebSocket connection. - /// - /// - /// - /// A that specifies the file to send. - /// - /// - /// The file is sent as the binary data. - /// - /// - /// - /// The current state of the connection is not Open. - /// - /// - /// is . - /// - /// - /// - /// The file does not exist. - /// - /// - /// -or- - /// - /// - /// The file could not be opened. - /// - /// - 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); - } + send(Opcode.Binary, new MemoryStream(data)); + } - /// - /// Sends the specified data using the WebSocket connection. - /// - /// - /// A that represents the text data to send. - /// - /// - /// The current state of the connection is not Open. - /// - /// - /// is . - /// - /// - /// could not be UTF-8-encoded. - /// - public void Send (string data) - { - if (_readyState != WebSocketState.Open) { - var msg = "The current state of the connection is not Open."; - throw new InvalidOperationException (msg); - } + /// + /// Sends the specified file using the WebSocket connection. + /// + /// + /// + /// A that specifies the file to send. + /// + /// + /// The file is sent as the binary data. + /// + /// + /// + /// The current state of the connection is not Open. + /// + /// + /// is . + /// + /// + /// + /// The file does not exist. + /// + /// + /// -or- + /// + /// + /// The file could not be opened. + /// + /// + 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 (data == null) - throw new ArgumentNullException ("data"); + if (fileInfo == null) + throw new ArgumentNullException("fileInfo"); - byte[] bytes; - if (!data.TryGetUTF8EncodedBytes (out bytes)) { - var msg = "It could not be UTF-8-encoded."; - throw new ArgumentException (msg, "data"); - } + if (!fileInfo.Exists) + { + var msg = "The file does not exist."; + throw new ArgumentException(msg, "fileInfo"); + } - send (Opcode.Text, new MemoryStream (bytes)); - } + FileStream stream; + if (!fileInfo.TryOpenRead(out stream)) + { + var msg = "The file could not be opened."; + throw new ArgumentException(msg, "fileInfo"); + } - /// - /// Sends the data from the specified stream using the WebSocket connection. - /// - /// - /// - /// A instance from which to read the data to send. - /// - /// - /// The data is sent as the binary data. - /// - /// - /// - /// An that specifies the number of bytes to send. - /// - /// - /// The current state of the connection is not Open. - /// - /// - /// is . - /// - /// - /// - /// cannot be read. - /// - /// - /// -or- - /// - /// - /// is less than 1. - /// - /// - /// -or- - /// - /// - /// No data could be read from . - /// - /// - 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)); - } + send(Opcode.Binary, stream); + } - /// - /// Sends the specified data asynchronously using the WebSocket connection. - /// - /// - /// This method does not wait for the send to be complete. - /// - /// - /// An array of that represents the binary data to send. - /// - /// - /// - /// An Action<bool> delegate or - /// if not needed. - /// - /// - /// The delegate invokes the method called when the send is complete. - /// - /// - /// true is passed to the method if the send has done with - /// no error; otherwise, false. - /// - /// - /// - /// The current state of the connection is not Open. - /// - /// - /// is . - /// - public void SendAsync (byte[] data, Action completed) - { - if (_readyState != WebSocketState.Open) { - var msg = "The current state of the connection is not Open."; - throw new InvalidOperationException (msg); - } + /// + /// Sends the specified data using the WebSocket connection. + /// + /// + /// A that represents the text data to send. + /// + /// + /// The current state of the connection is not Open. + /// + /// + /// is . + /// + /// + /// could not be UTF-8-encoded. + /// + 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"); + if (data == null) + throw new ArgumentNullException("data"); - sendAsync (Opcode.Binary, new MemoryStream (data), completed); - } + byte[] bytes; + if (!data.TryGetUTF8EncodedBytes(out bytes)) + { + var msg = "It could not be UTF-8-encoded."; + throw new ArgumentException(msg, "data"); + } - /// - /// Sends the specified file asynchronously using the WebSocket connection. - /// - /// - /// This method does not wait for the send to be complete. - /// - /// - /// - /// A that specifies the file to send. - /// - /// - /// The file is sent as the binary data. - /// - /// - /// - /// - /// An Action<bool> delegate or - /// if not needed. - /// - /// - /// The delegate invokes the method called when the send is complete. - /// - /// - /// true is passed to the method if the send has done with - /// no error; otherwise, false. - /// - /// - /// - /// The current state of the connection is not Open. - /// - /// - /// is . - /// - /// - /// - /// The file does not exist. - /// - /// - /// -or- - /// - /// - /// The file could not be opened. - /// - /// - public void SendAsync (FileInfo fileInfo, Action 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); - } + send(Opcode.Text, new MemoryStream(bytes)); + } - /// - /// Sends the specified data asynchronously using the WebSocket connection. - /// - /// - /// This method does not wait for the send to be complete. - /// - /// - /// A that represents the text data to send. - /// - /// - /// - /// An Action<bool> delegate or - /// if not needed. - /// - /// - /// The delegate invokes the method called when the send is complete. - /// - /// - /// true is passed to the method if the send has done with - /// no error; otherwise, false. - /// - /// - /// - /// The current state of the connection is not Open. - /// - /// - /// is . - /// - /// - /// could not be UTF-8-encoded. - /// - public void SendAsync (string data, Action completed) - { - if (_readyState != WebSocketState.Open) { - var msg = "The current state of the connection is not Open."; - throw new InvalidOperationException (msg); - } + /// + /// Sends the data from the specified stream using the WebSocket connection. + /// + /// + /// + /// A instance from which to read the data to send. + /// + /// + /// The data is sent as the binary data. + /// + /// + /// + /// An that specifies the number of bytes to send. + /// + /// + /// The current state of the connection is not Open. + /// + /// + /// is . + /// + /// + /// + /// cannot be read. + /// + /// + /// -or- + /// + /// + /// is less than 1. + /// + /// + /// -or- + /// + /// + /// No data could be read from . + /// + /// + 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 (data == null) - throw new ArgumentNullException ("data"); + if (stream == null) + throw new ArgumentNullException("stream"); - byte[] bytes; - if (!data.TryGetUTF8EncodedBytes (out bytes)) { - var msg = "It could not be UTF-8-encoded."; - throw new ArgumentException (msg, "data"); - } + if (!stream.CanRead) + { + var msg = "It cannot be read."; + throw new ArgumentException(msg, "stream"); + } - sendAsync (Opcode.Text, new MemoryStream (bytes), completed); - } + if (length < 1) + { + var msg = "Less than 1."; + throw new ArgumentException(msg, "length"); + } - /// - /// Sends the data from the specified stream asynchronously using - /// the WebSocket connection. - /// - /// - /// This method does not wait for the send to be complete. - /// - /// - /// - /// A instance from which to read the data to send. - /// - /// - /// The data is sent as the binary data. - /// - /// - /// - /// An that specifies the number of bytes to send. - /// - /// - /// - /// An Action<bool> delegate or - /// if not needed. - /// - /// - /// The delegate invokes the method called when the send is complete. - /// - /// - /// true is passed to the method if the send has done with - /// no error; otherwise, false. - /// - /// - /// - /// The current state of the connection is not Open. - /// - /// - /// is . - /// - /// - /// - /// cannot be read. - /// - /// - /// -or- - /// - /// - /// is less than 1. - /// - /// - /// -or- - /// - /// - /// No data could be read from . - /// - /// - public void SendAsync (Stream stream, int length, Action 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); - } + var bytes = stream.ReadBytes(length); - /// - /// Sets an HTTP cookie to send with the handshake request. - /// - /// - /// This method does nothing if the connection has already been - /// established or it is closing. - /// - /// - /// A that represents the cookie to send. - /// - /// - /// This instance is not a client. - /// - /// - /// is . - /// - public void SetCookie (Cookie cookie) - { - string msg = null; + 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 + ) + ); + } - if (!_client) { - msg = "This instance is not a client."; - throw new InvalidOperationException (msg); - } + send(Opcode.Binary, new MemoryStream(bytes)); + } - if (cookie == null) - throw new ArgumentNullException ("cookie"); + /// + /// Sends the specified data asynchronously using the WebSocket connection. + /// + /// + /// This method does not wait for the send to be complete. + /// + /// + /// An array of that represents the binary data to send. + /// + /// + /// + /// An Action<bool> delegate or + /// if not needed. + /// + /// + /// The delegate invokes the method called when the send is complete. + /// + /// + /// true is passed to the method if the send has done with + /// no error; otherwise, false. + /// + /// + /// + /// The current state of the connection is not Open. + /// + /// + /// is . + /// + public void SendAsync(byte[] data, Action completed) + { + if (_readyState != WebSocketState.Open) + { + var msg = "The current state of the connection is not Open."; + throw new InvalidOperationException(msg); + } - if (!canSet (out msg)) { - _logger.Warn (msg); - return; - } + if (data == null) + throw new ArgumentNullException("data"); - lock (_forState) { - if (!canSet (out msg)) { - _logger.Warn (msg); - return; + sendAsync(Opcode.Binary, new MemoryStream(data), completed); } - lock (_cookies.SyncRoot) - _cookies.SetOrRemove (cookie); - } - } + /// + /// Sends the specified file asynchronously using the WebSocket connection. + /// + /// + /// This method does not wait for the send to be complete. + /// + /// + /// + /// A that specifies the file to send. + /// + /// + /// The file is sent as the binary data. + /// + /// + /// + /// + /// An Action<bool> delegate or + /// if not needed. + /// + /// + /// The delegate invokes the method called when the send is complete. + /// + /// + /// true is passed to the method if the send has done with + /// no error; otherwise, false. + /// + /// + /// + /// The current state of the connection is not Open. + /// + /// + /// is . + /// + /// + /// + /// The file does not exist. + /// + /// + /// -or- + /// + /// + /// The file could not be opened. + /// + /// + public void SendAsync(FileInfo fileInfo, Action completed) + { + if (_readyState != WebSocketState.Open) + { + var msg = "The current state of the connection is not Open."; + throw new InvalidOperationException(msg); + } - /// - /// Sets the credentials for the HTTP authentication (Basic/Digest). - /// - /// - /// This method does nothing if the connection has already been - /// established or it is closing. - /// - /// - /// - /// A that represents the username associated with - /// the credentials. - /// - /// - /// or an empty string if initializes - /// the credentials. - /// - /// - /// - /// - /// A that represents the password for the username - /// associated with the credentials. - /// - /// - /// or an empty string if not necessary. - /// - /// - /// - /// true if sends the credentials for the Basic authentication in - /// advance with the first handshake request; otherwise, false. - /// - /// - /// This instance is not a client. - /// - /// - /// - /// contains an invalid character. - /// - /// - /// -or- - /// - /// - /// contains an invalid character. - /// - /// - public void SetCredentials (string username, string password, bool preAuth) - { - string msg = null; + if (fileInfo == null) + throw new ArgumentNullException("fileInfo"); - if (!_client) { - msg = "This instance is not a client."; - throw new InvalidOperationException (msg); - } + if (!fileInfo.Exists) + { + var msg = "The file does not exist."; + throw new ArgumentException(msg, "fileInfo"); + } - if (!username.IsNullOrEmpty ()) { - if (username.Contains (':') || !username.IsText ()) { - msg = "It contains an invalid character."; - throw new ArgumentException (msg, "username"); - } - } + FileStream stream; + if (!fileInfo.TryOpenRead(out stream)) + { + var msg = "The file could not be opened."; + throw new ArgumentException(msg, "fileInfo"); + } - if (!password.IsNullOrEmpty ()) { - if (!password.IsText ()) { - msg = "It contains an invalid character."; - throw new ArgumentException (msg, "password"); + sendAsync(Opcode.Binary, stream, completed); } - } - if (!canSet (out msg)) { - _logger.Warn (msg); - return; - } + /// + /// Sends the specified data asynchronously using the WebSocket connection. + /// + /// + /// This method does not wait for the send to be complete. + /// + /// + /// A that represents the text data to send. + /// + /// + /// + /// An Action<bool> delegate or + /// if not needed. + /// + /// + /// The delegate invokes the method called when the send is complete. + /// + /// + /// true is passed to the method if the send has done with + /// no error; otherwise, false. + /// + /// + /// + /// The current state of the connection is not Open. + /// + /// + /// is . + /// + /// + /// could not be UTF-8-encoded. + /// + public void SendAsync(string data, Action completed) + { + if (_readyState != WebSocketState.Open) + { + var msg = "The current state of the connection is not Open."; + throw new InvalidOperationException(msg); + } - lock (_forState) { - if (!canSet (out msg)) { - _logger.Warn (msg); - return; - } + if (data == null) + throw new ArgumentNullException("data"); - if (username.IsNullOrEmpty ()) { - _credentials = null; - _preAuth = false; + byte[] bytes; + if (!data.TryGetUTF8EncodedBytes(out bytes)) + { + var msg = "It could not be UTF-8-encoded."; + throw new ArgumentException(msg, "data"); + } - return; + sendAsync(Opcode.Text, new MemoryStream(bytes), completed); } - _credentials = new NetworkCredential ( - username, password, _uri.PathAndQuery - ); + /// + /// Sends the data from the specified stream asynchronously using + /// the WebSocket connection. + /// + /// + /// This method does not wait for the send to be complete. + /// + /// + /// + /// A instance from which to read the data to send. + /// + /// + /// The data is sent as the binary data. + /// + /// + /// + /// An that specifies the number of bytes to send. + /// + /// + /// + /// An Action<bool> delegate or + /// if not needed. + /// + /// + /// The delegate invokes the method called when the send is complete. + /// + /// + /// true is passed to the method if the send has done with + /// no error; otherwise, false. + /// + /// + /// + /// The current state of the connection is not Open. + /// + /// + /// is . + /// + /// + /// + /// cannot be read. + /// + /// + /// -or- + /// + /// + /// is less than 1. + /// + /// + /// -or- + /// + /// + /// No data could be read from . + /// + /// + public void SendAsync(Stream stream, int length, Action completed) + { + if (_readyState != WebSocketState.Open) + { + var msg = "The current state of the connection is not Open."; + throw new InvalidOperationException(msg); + } - _preAuth = preAuth; - } - } + if (stream == null) + throw new ArgumentNullException("stream"); - /// - /// Sets the URL of the HTTP proxy server through which to connect and - /// the credentials for the HTTP proxy authentication (Basic/Digest). - /// - /// - /// This method does nothing if the connection has already been - /// established or it is closing. - /// - /// - /// - /// A that represents the URL of the proxy server - /// through which to connect. - /// - /// - /// The syntax is http://<host>[:<port>]. - /// - /// - /// or an empty string if initializes the URL and - /// the credentials. - /// - /// - /// - /// - /// A that represents the username associated with - /// the credentials. - /// - /// - /// or an empty string if the credentials are not - /// necessary. - /// - /// - /// - /// - /// A that represents the password for the username - /// associated with the credentials. - /// - /// - /// or an empty string if not necessary. - /// - /// - /// - /// This instance is not a client. - /// - /// - /// - /// is not an absolute URI string. - /// - /// - /// -or- - /// - /// - /// The scheme of is not http. - /// - /// - /// -or- - /// - /// - /// includes the path segments. - /// - /// - /// -or- - /// - /// - /// contains an invalid character. - /// - /// - /// -or- - /// - /// - /// contains an invalid character. - /// - /// - public void SetProxy (string url, string username, string password) - { - string msg = null; + if (!stream.CanRead) + { + var msg = "It cannot be read."; + throw new ArgumentException(msg, "stream"); + } - if (!_client) { - msg = "This instance is not a client."; - throw new InvalidOperationException (msg); - } + if (length < 1) + { + var msg = "Less than 1."; + throw new ArgumentException(msg, "length"); + } - Uri uri = null; + var bytes = stream.ReadBytes(length); - if (!url.IsNullOrEmpty ()) { - if (!Uri.TryCreate (url, UriKind.Absolute, out uri)) { - msg = "Not an absolute URI string."; - throw new ArgumentException (msg, "url"); - } + var len = bytes.Length; + if (len == 0) + { + var msg = "No data could be read from it."; + throw new ArgumentException(msg, "stream"); + } - if (uri.Scheme != "http") { - msg = "The scheme part is not http."; - throw new ArgumentException (msg, "url"); - } + if (len < length) + { + _logger.Warn( + String.Format( + "Only {0} byte(s) of data could be read from the stream.", + len + ) + ); + } - if (uri.Segments.Length > 1) { - msg = "It includes the path segments."; - throw new ArgumentException (msg, "url"); + sendAsync(Opcode.Binary, new MemoryStream(bytes), completed); } - } - if (!username.IsNullOrEmpty ()) { - if (username.Contains (':') || !username.IsText ()) { - msg = "It contains an invalid character."; - throw new ArgumentException (msg, "username"); - } - } + /// + /// Sets an HTTP cookie to send with the handshake request. + /// + /// + /// This method does nothing if the connection has already been + /// established or it is closing. + /// + /// + /// A that represents the cookie to send. + /// + /// + /// This instance is not a client. + /// + /// + /// is . + /// + public void SetCookie(Cookie cookie) + { + string msg = null; + + if (!_client) + { + msg = "This instance is not a client."; + throw new InvalidOperationException(msg); + } - if (!password.IsNullOrEmpty ()) { - if (!password.IsText ()) { - msg = "It contains an invalid character."; - throw new ArgumentException (msg, "password"); - } - } + if (cookie == null) + throw new ArgumentNullException("cookie"); + + if (!canSet(out msg)) + { + _logger.Warn(msg); + return; + } - if (!canSet (out msg)) { - _logger.Warn (msg); - return; - } + lock (_forState) + { + if (!canSet(out msg)) + { + _logger.Warn(msg); + return; + } - lock (_forState) { - if (!canSet (out msg)) { - _logger.Warn (msg); - return; + lock (_cookies.SyncRoot) + _cookies.SetOrRemove(cookie); + } } - if (url.IsNullOrEmpty ()) { - _proxyUri = null; - _proxyCredentials = null; + /// + /// Sets the credentials for the HTTP authentication (Basic/Digest). + /// + /// + /// This method does nothing if the connection has already been + /// established or it is closing. + /// + /// + /// + /// A that represents the username associated with + /// the credentials. + /// + /// + /// or an empty string if initializes + /// the credentials. + /// + /// + /// + /// + /// A that represents the password for the username + /// associated with the credentials. + /// + /// + /// or an empty string if not necessary. + /// + /// + /// + /// true if sends the credentials for the Basic authentication in + /// advance with the first handshake request; otherwise, false. + /// + /// + /// This instance is not a client. + /// + /// + /// + /// contains an invalid character. + /// + /// + /// -or- + /// + /// + /// contains an invalid character. + /// + /// + public void SetCredentials(string username, string password, bool preAuth) + { + string msg = null; + + if (!_client) + { + msg = "This instance is not a client."; + throw new InvalidOperationException(msg); + } + + if (!username.IsNullOrEmpty()) + { + if (username.Contains(':') || !username.IsText()) + { + msg = "It contains an invalid character."; + throw new ArgumentException(msg, "username"); + } + } + + if (!password.IsNullOrEmpty()) + { + if (!password.IsText()) + { + msg = "It contains an invalid character."; + throw new ArgumentException(msg, "password"); + } + } + + if (!canSet(out msg)) + { + _logger.Warn(msg); + return; + } - return; + lock (_forState) + { + if (!canSet(out msg)) + { + _logger.Warn(msg); + return; + } + + if (username.IsNullOrEmpty()) + { + _credentials = null; + _preAuth = false; + + return; + } + + _credentials = new NetworkCredential( + username, password, _uri.PathAndQuery + ); + + _preAuth = preAuth; + } } - _proxyUri = uri; - _proxyCredentials = !username.IsNullOrEmpty () - ? new NetworkCredential ( - username, - password, - String.Format ( - "{0}:{1}", _uri.DnsSafeHost, _uri.Port - ) - ) - : null; - } - } + /// + /// Sets the URL of the HTTP proxy server through which to connect and + /// the credentials for the HTTP proxy authentication (Basic/Digest). + /// + /// + /// This method does nothing if the connection has already been + /// established or it is closing. + /// + /// + /// + /// A that represents the URL of the proxy server + /// through which to connect. + /// + /// + /// The syntax is http://<host>[:<port>]. + /// + /// + /// or an empty string if initializes the URL and + /// the credentials. + /// + /// + /// + /// + /// A that represents the username associated with + /// the credentials. + /// + /// + /// or an empty string if the credentials are not + /// necessary. + /// + /// + /// + /// + /// A that represents the password for the username + /// associated with the credentials. + /// + /// + /// or an empty string if not necessary. + /// + /// + /// + /// This instance is not a client. + /// + /// + /// + /// is not an absolute URI string. + /// + /// + /// -or- + /// + /// + /// The scheme of is not http. + /// + /// + /// -or- + /// + /// + /// includes the path segments. + /// + /// + /// -or- + /// + /// + /// contains an invalid character. + /// + /// + /// -or- + /// + /// + /// contains an invalid character. + /// + /// + public void SetProxy(string url, string username, string password) + { + string msg = null; + + if (!_client) + { + msg = "This instance is not a client."; + throw new InvalidOperationException(msg); + } - #endregion + Uri uri = null; + + if (!url.IsNullOrEmpty()) + { + if (!Uri.TryCreate(url, UriKind.Absolute, out uri)) + { + msg = "Not an absolute URI string."; + throw new ArgumentException(msg, "url"); + } + + if (uri.Scheme != "http") + { + msg = "The scheme part is not http."; + throw new ArgumentException(msg, "url"); + } + + if (uri.Segments.Length > 1) + { + msg = "It includes the path segments."; + throw new ArgumentException(msg, "url"); + } + } - #region Explicit Interface Implementations + if (!username.IsNullOrEmpty()) + { + if (username.Contains(':') || !username.IsText()) + { + msg = "It contains an invalid character."; + throw new ArgumentException(msg, "username"); + } + } - /// - /// Closes the connection and releases all associated resources. - /// - /// - /// - /// This method closes the connection with close status 1001 (going away). - /// - /// - /// And this method does nothing if the current state of the connection is - /// Closing or Closed. - /// - /// - void IDisposable.Dispose () - { - close (1001, String.Empty); - } + if (!password.IsNullOrEmpty()) + { + if (!password.IsText()) + { + msg = "It contains an invalid character."; + throw new ArgumentException(msg, "password"); + } + } - #endregion - } + if (!canSet(out msg)) + { + _logger.Warn(msg); + return; + } + + lock (_forState) + { + if (!canSet(out msg)) + { + _logger.Warn(msg); + 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 + + /// + /// Closes the connection and releases all associated resources. + /// + /// + /// + /// This method closes the connection with close status 1001 (going away). + /// + /// + /// And this method does nothing if the current state of the connection is + /// Closing or Closed. + /// + /// + void IDisposable.Dispose() + { + close(1001, String.Empty); + } + + #endregion + } }