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 + } }