From 4867a7cbbf7302845fff031db5eae6fbf93bf26b Mon Sep 17 00:00:00 2001
From: teravus
Date: Thu, 7 Feb 2013 10:26:48 -0500
Subject: This is the final commit that enables the Websocket handler

---
 .../Framework/Servers/HttpServer/BaseHttpServer.cs |   12 +-
 .../Servers/HttpServer/WebsocketServerHandler.cs   | 1085 ++++++++++++++++++++
 2 files changed, 1095 insertions(+), 2 deletions(-)
 create mode 100644 OpenSim/Framework/Servers/HttpServer/WebsocketServerHandler.cs

(limited to 'OpenSim/Framework')

diff --git a/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs b/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs
index dcfe99a..70c531c 100644
--- a/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs
+++ b/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs
@@ -54,7 +54,15 @@ namespace OpenSim.Framework.Servers.HttpServer
         private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
         private HttpServerLogWriter httpserverlog = new HttpServerLogWriter();
 
-        public delegate void WebSocketRequestDelegate(string servicepath, WebSocketHTTPServerHandler handler);
+
+        /// <summary>
+        /// This is a pending websocket request before it got an sucessful upgrade response.
+        /// The consumer must call handler.HandshakeAndUpgrade() to signal to the handler to 
+        /// start the connection and optionally provide an origin authentication method.
+        /// </summary>
+        /// <param name="servicepath"></param>
+        /// <param name="handler"></param>
+        public delegate void WebSocketRequestDelegate(string servicepath, WebSocketHttpServerHandler handler);
 
         /// <summary>
         /// Gets or sets the debug level.
@@ -440,7 +448,7 @@ namespace OpenSim.Framework.Servers.HttpServer
             }
             if (dWebSocketRequestDelegate != null)
             {
-                dWebSocketRequestDelegate(req.Url.AbsolutePath, new WebSocketHTTPServerHandler(req, context, 16384));
+                dWebSocketRequestDelegate(req.Url.AbsolutePath, new WebSocketHttpServerHandler(req, context, 8192));
                 return;
             }
             
diff --git a/OpenSim/Framework/Servers/HttpServer/WebsocketServerHandler.cs b/OpenSim/Framework/Servers/HttpServer/WebsocketServerHandler.cs
new file mode 100644
index 0000000..cfb1605
--- /dev/null
+++ b/OpenSim/Framework/Servers/HttpServer/WebsocketServerHandler.cs
@@ -0,0 +1,1085 @@
+/*
+ * Copyright (c) Contributors, http://opensimulator.org/
+ * See CONTRIBUTORS.TXT for a full list of copyright holders.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ *     * Neither the name of the OpenSimulator Project nor the
+ *       names of its contributors may be used to endorse or promote products
+ *       derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Security.Cryptography;
+using System.Text;
+using HttpServer;
+
+namespace OpenSim.Framework.Servers.HttpServer
+{
+    // Sealed class.  If you're going to unseal it, implement IDisposable.
+    /// <summary>
+    /// This class implements websockets.    It grabs the network context from C#Webserver and utilizes it directly as a tcp streaming service
+    /// </summary>
+    public sealed class WebSocketHttpServerHandler : BaseRequestHandler
+    {
+
+        private class WebSocketState
+        {
+            public List<byte> ReceivedBytes;
+            public int ExpectedBytes;
+            public WebsocketFrameHeader Header;
+            public bool FrameComplete;
+            public WebSocketFrame ContinuationFrame;
+        }
+
+        /// <summary>
+        /// Binary Data will trigger this event
+        /// </summary>
+        public event DataDelegate OnData;
+
+        /// <summary>
+        /// Textual Data will trigger this event
+        /// </summary>
+        public event TextDelegate OnText;
+
+        /// <summary>
+        /// A ping request form the other side will trigger this event.
+        /// This class responds to the ping automatically.  You shouldn't send a pong.
+        /// it's informational.
+        /// </summary>
+        public event PingDelegate OnPing;
+
+        /// <summary>
+        /// This is a response to a ping you sent.
+        /// </summary>
+        public event PongDelegate OnPong;
+
+        /// <summary>
+        /// This is a regular HTTP Request...    This may be removed in the future.   
+        /// </summary>
+        public event RegularHttpRequestDelegate OnRegularHttpRequest;
+
+        /// <summary>
+        /// When the upgrade from a HTTP request to a Websocket is completed, this will be fired
+        /// </summary>
+        public event UpgradeCompletedDelegate OnUpgradeCompleted;
+
+        /// <summary>
+        /// If the upgrade failed, this will be fired
+        /// </summary>
+        public event UpgradeFailedDelegate OnUpgradeFailed;
+
+        /// <summary>
+        /// When the websocket is closed, this will be fired.
+        /// </summary>
+        public event CloseDelegate OnClose;
+        
+        /// <summary>
+        /// Set this delegate to allow your module to validate the origin of the 
+        /// Websocket request.  Primary line of defense against cross site scripting
+        /// </summary>
+        public ValidateHandshake HandshakeValidateMethodOverride = null;
+
+        private OSHttpRequest _request;
+        private HTTPNetworkContext _networkContext;
+        private IHttpClientContext _clientContext;
+
+        private int _pingtime = 0;
+        private byte[] _buffer;
+        private int _bufferPosition;
+        private int _bufferLength;
+        private bool _closing;
+        private bool _upgraded;
+
+        private const string HandshakeAcceptText =
+            "HTTP/1.1 101 Switching Protocols\r\n" +
+            "upgrade: websocket\r\n" +
+            "Connection: Upgrade\r\n" +
+            "sec-websocket-accept: {0}\r\n\r\n";// +
+           //"{1}";
+
+        private const string HandshakeDeclineText =
+            "HTTP/1.1 {0} {1}\r\n" +
+            "Connection: close\r\n\r\n";
+
+        /// <summary>
+        /// Mysterious constant defined in RFC6455 to append to the client provided security key
+        /// </summary>
+        private const string WebsocketHandshakeAcceptHashConstant = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
+
+        public WebSocketHttpServerHandler(OSHttpRequest preq, IHttpClientContext pContext, int bufferlen)
+            : base(preq.HttpMethod, preq.Url.OriginalString)
+        {
+            _request = preq;
+            _networkContext = pContext.GiveMeTheNetworkStreamIKnowWhatImDoing();
+            _clientContext = pContext;
+            _bufferLength = bufferlen;
+            _buffer = new byte[_bufferLength];
+        }
+
+        // Sealed class implments destructor and an internal dispose method. complies with C# unmanaged resource best practices.
+        ~WebSocketHttpServerHandler()
+        {
+            Dispose();
+
+        }
+
+        /// <summary>
+        /// Sets the length of the stream buffer
+        /// </summary>
+        /// <param name="pChunk">Byte length.</param>
+        public void SetChunksize(int pChunk)
+        {
+            if (!_upgraded)
+            {
+                _buffer = new byte[pChunk];
+            }
+            else
+            {
+                throw new InvalidOperationException("You must set the chunksize before the connection is upgraded");
+            }
+        }
+
+        /// <summary>
+        /// This is the famous nagle.
+        /// </summary>
+        public bool NoDelay_TCP_Nagle
+        {
+            get
+            {
+                if (_networkContext != null && _networkContext.Socket != null)
+                {
+                    return _networkContext.Socket.NoDelay;
+                }
+                else
+                {
+                    throw new InvalidOperationException("The socket has been shutdown");
+                }
+            } 
+            set
+            {
+                if (_networkContext != null && _networkContext.Socket != null)
+                    _networkContext.Socket.NoDelay = value;
+                else
+                {
+                    throw new InvalidOperationException("The socket has been shutdown");
+                }
+            }
+        }
+
+        /// <summary>
+        /// This triggers the websocket to start the upgrade process...  
+        /// This is a Generalized Networking 'common sense' helper method.  Some people expect to call Start() instead 
+        /// of the more context appropriate HandshakeAndUpgrade()
+        /// </summary>
+        public void Start()
+        {
+            HandshakeAndUpgrade();
+        }
+
+        /// <summary>
+        /// This triggers the websocket start the upgrade process
+        /// </summary>
+        public void HandshakeAndUpgrade()
+        {
+            string webOrigin = string.Empty;
+            string websocketKey = string.Empty;
+            string acceptKey = string.Empty;
+            string accepthost = string.Empty;
+            if (!string.IsNullOrEmpty(_request.Headers["origin"]))
+                webOrigin = _request.Headers["origin"];
+
+            if (!string.IsNullOrEmpty(_request.Headers["sec-websocket-key"]))
+                websocketKey = _request.Headers["sec-websocket-key"];
+
+            if (!string.IsNullOrEmpty(_request.Headers["host"]))
+                accepthost = _request.Headers["host"];
+
+            if (string.IsNullOrEmpty(_request.Headers["upgrade"]))
+            {
+                FailUpgrade(OSHttpStatusCode.ClientErrorBadRequest, "no upgrade request submitted");
+            }
+
+            string connectionheader = _request.Headers["upgrade"];
+            if (connectionheader.ToLower() != "websocket")
+            {
+                FailUpgrade(OSHttpStatusCode.ClientErrorBadRequest, "no connection upgrade request submitted");
+            }
+
+            // If the object consumer provided a method to validate the origin, we should call it and give the client a success or fail.
+            // If not..  we should accept any.   The assumption here is that there would be no Websocket handlers registered in baseHTTPServer unless
+            // Something asked for it...
+            if (HandshakeValidateMethodOverride != null)
+            {
+                if (HandshakeValidateMethodOverride(webOrigin, websocketKey, accepthost))
+                {
+                    acceptKey = GenerateAcceptKey(websocketKey);
+                    string rawaccept = string.Format(HandshakeAcceptText, acceptKey);
+                    SendUpgradeSuccess(rawaccept);
+                    
+                }
+                else
+                {
+                    FailUpgrade(OSHttpStatusCode.ClientErrorForbidden, "Origin Validation Failed");
+                }
+            }
+            else
+            {
+                acceptKey = GenerateAcceptKey(websocketKey);
+                string rawaccept = string.Format(HandshakeAcceptText, acceptKey);
+                SendUpgradeSuccess(rawaccept);
+            }
+        }
+
+        /// <summary>
+        /// Generates a handshake response key string based on the client's 
+        /// provided key to prove to the client that we're allowing the Websocket
+        /// upgrade of our own free will and we were not coerced into doing it.
+        /// </summary>
+        /// <param name="key">Client provided security key</param>
+        /// <returns></returns>
+        private static string GenerateAcceptKey(string key)
+        {
+            if (string.IsNullOrEmpty(key))
+                return string.Empty;
+
+            string acceptkey = key + WebsocketHandshakeAcceptHashConstant;
+
+            SHA1 hashobj = SHA1.Create();
+            string ret = Convert.ToBase64String(hashobj.ComputeHash(Encoding.UTF8.GetBytes(acceptkey)));
+            hashobj.Clear();
+            
+            return ret;
+        }
+
+        /// <summary>
+        /// Informs the otherside that we accepted their upgrade request
+        /// </summary>
+        /// <param name="pHandshakeResponse">The HTTP 1.1 101 response that says Yay \o/ </param>
+        private void SendUpgradeSuccess(string pHandshakeResponse)
+        {
+            // Create a new websocket state so we can keep track of data in between network reads.
+            WebSocketState socketState = new WebSocketState() { ReceivedBytes = new List<byte>(), Header = WebsocketFrameHeader.HeaderDefault(), FrameComplete = true};
+            
+            byte[] bhandshakeResponse = Encoding.UTF8.GetBytes(pHandshakeResponse);
+            try
+            {
+                
+                // Begin reading the TCP stream before writing the Upgrade success message to the other side of the stream.
+                _networkContext.Stream.BeginRead(_buffer, 0, _bufferLength, OnReceive, socketState);
+                
+                // Write the upgrade handshake success message
+                _networkContext.Stream.Write(bhandshakeResponse, 0, bhandshakeResponse.Length);
+                _networkContext.Stream.Flush();
+                _upgraded = true;
+                UpgradeCompletedDelegate d = OnUpgradeCompleted;
+                if (d != null)
+                    d(this, new UpgradeCompletedEventArgs());
+            }
+            catch (IOException fail)
+            {
+                Close(string.Empty);
+            }
+            catch (ObjectDisposedException fail)
+            {
+                Close(string.Empty);
+            }
+            
+        }
+
+        /// <summary>
+        /// The server has decided not to allow the upgrade to a websocket for some reason.  The Http 1.1 response that says Nay >:(
+        /// </summary>
+        /// <param name="pCode">HTTP Status reflecting the reason why</param>
+        /// <param name="pMessage">Textual reason for the upgrade fail</param>
+        private void FailUpgrade(OSHttpStatusCode pCode, string pMessage )
+        {
+            string handshakeResponse = string.Format(HandshakeDeclineText, (int)pCode, pMessage.Replace("\n", string.Empty).Replace("\r", string.Empty));
+            byte[] bhandshakeResponse = Encoding.UTF8.GetBytes(handshakeResponse);
+            _networkContext.Stream.Write(bhandshakeResponse, 0, bhandshakeResponse.Length);
+            _networkContext.Stream.Flush();
+            _networkContext.Stream.Dispose();
+
+            UpgradeFailedDelegate d = OnUpgradeFailed;
+            if (d != null)
+                d(this,new UpgradeFailedEventArgs());
+        }
+
+
+        /// <summary>
+        /// This is our ugly Async OnReceive event handler.
+        /// This chunks the input stream based on the length of the provided buffer and processes out 
+        /// as many frames as it can.   It then moves the unprocessed data to the beginning of the buffer.
+        /// </summary>
+        /// <param name="ar">Our Async State from beginread</param>
+        private void OnReceive(IAsyncResult ar)
+        {
+            WebSocketState _socketState = ar.AsyncState as WebSocketState;
+            try
+            {
+                int bytesRead = _networkContext.Stream.EndRead(ar);
+                if (bytesRead == 0)
+                {
+                    // Do Disconnect
+                    _networkContext.Stream.Dispose();
+                    _networkContext = null;
+                    return;
+                }
+                _bufferPosition += bytesRead;
+
+                if (_bufferPosition > _bufferLength)
+                {
+                    // Message too big for chunksize..   not sure how this happened...  
+                    //Close(string.Empty);
+                }
+
+                int offset = 0;
+                bool headerread = true;
+                int headerforwardposition = 0;
+                while (headerread && offset < bytesRead)
+                {
+                    if (_socketState.FrameComplete)
+                    {
+                        WebsocketFrameHeader pheader = WebsocketFrameHeader.ZeroHeader;
+
+                        headerread = WebSocketReader.TryReadHeader(_buffer, offset, _bufferPosition - offset, out pheader,
+                                                                   out headerforwardposition);
+                        offset += headerforwardposition;
+
+                        if (headerread)
+                        {
+                            _socketState.FrameComplete = false;
+
+                            if (pheader.PayloadLen > 0)
+                            {
+                                if ((int) pheader.PayloadLen > _bufferPosition - offset)
+                                {
+                                    byte[] writebytes = new byte[_bufferPosition - offset];
+
+                                    Buffer.BlockCopy(_buffer, offset, writebytes, 0, (int) _bufferPosition - offset);
+                                    _socketState.ExpectedBytes = (int) pheader.PayloadLen;
+                                    _socketState.ReceivedBytes.AddRange(writebytes);
+                                    _socketState.Header = pheader; // We need to add the header so that we can unmask it
+                                    offset += (int) _bufferPosition - offset;
+                                }
+                                else
+                                {
+                                    byte[] writebytes = new byte[pheader.PayloadLen];
+                                    Buffer.BlockCopy(_buffer, offset, writebytes, 0, (int) pheader.PayloadLen);
+                                    WebSocketReader.Mask(pheader.Mask, writebytes);
+                                    pheader.IsMasked = false;
+                                    _socketState.FrameComplete = true;
+                                    _socketState.ReceivedBytes.AddRange(writebytes);
+                                    _socketState.Header = pheader;
+                                    offset += (int) pheader.PayloadLen;
+                                }
+                            }
+                            else
+                            {
+                                pheader.Mask = 0;
+                                _socketState.FrameComplete = true;
+                                _socketState.Header = pheader;
+                            }
+
+
+
+                            if (_socketState.FrameComplete)
+                            {
+                                ProcessFrame(_socketState);
+                                _socketState.Header.SetDefault();
+                                _socketState.ReceivedBytes.Clear();
+                                _socketState.ExpectedBytes = 0;
+
+                            }
+
+                        }
+                    }
+                    else
+                    {
+                        WebsocketFrameHeader frameHeader = _socketState.Header;
+                        int bytesleft = _socketState.ExpectedBytes - _socketState.ReceivedBytes.Count;
+
+                        if (bytesleft > _bufferPosition)
+                        {
+                            byte[] writebytes = new byte[_bufferPosition];
+
+                            Buffer.BlockCopy(_buffer, offset, writebytes, 0, (int) _bufferPosition);
+                            _socketState.ReceivedBytes.AddRange(writebytes);
+                            _socketState.Header = frameHeader; // We need to add the header so that we can unmask it
+                            offset += (int) _bufferPosition;
+                        }
+                        else
+                        {
+                            byte[] writebytes = new byte[_bufferPosition];
+                            Buffer.BlockCopy(_buffer, offset, writebytes, 0, (int) _bufferPosition);
+                            _socketState.FrameComplete = true;
+                            _socketState.ReceivedBytes.AddRange(writebytes);
+                            _socketState.Header = frameHeader;
+                            offset += (int) _bufferPosition;
+                        }
+                        if (_socketState.FrameComplete)
+                        {
+                            ProcessFrame(_socketState);
+                            _socketState.Header.SetDefault();
+                            _socketState.ReceivedBytes.Clear();
+                            _socketState.ExpectedBytes = 0;
+                            // do some processing
+                        }
+
+                    }
+                }
+                if (offset > 0)
+                {
+                    // If the buffer is maxed out..  we can just move the cursor.   Nothing to move to the beginning.
+                    if (offset <_buffer.Length)
+                        Buffer.BlockCopy(_buffer, offset, _buffer, 0, _bufferPosition - offset);
+                    _bufferPosition -= offset;
+                }
+                if (_networkContext.Stream != null && _networkContext.Stream.CanRead && !_closing)
+                {
+                    _networkContext.Stream.BeginRead(_buffer, _bufferPosition, _bufferLength - _bufferPosition, OnReceive,
+                                                    _socketState);
+                }
+                else
+                {
+                    // We can't read the stream anymore...  
+                }
+
+            }
+            catch (IOException fail)
+            {
+                Close(string.Empty);
+            }
+            catch (ObjectDisposedException fail)
+            {
+                Close(string.Empty);
+            }
+        }
+
+        /// <summary>
+        /// Sends a string to the other side
+        /// </summary>
+        /// <param name="message">the string message that is to be sent</param>
+        public void SendMessage(string message)
+        {
+            byte[] messagedata = Encoding.UTF8.GetBytes(message);
+            WebSocketFrame textMessageFrame = new WebSocketFrame() { Header = WebsocketFrameHeader.HeaderDefault(), WebSocketPayload = messagedata };
+            textMessageFrame.Header.Opcode = WebSocketReader.OpCode.Text;
+            textMessageFrame.Header.IsEnd = true;
+            SendSocket(textMessageFrame.ToBytes());
+            
+        }
+
+        public void SendData(byte[] data)
+        {
+            WebSocketFrame dataMessageFrame = new WebSocketFrame() { Header = WebsocketFrameHeader.HeaderDefault(), WebSocketPayload = data};
+            dataMessageFrame.Header.IsEnd = true;
+            dataMessageFrame.Header.Opcode = WebSocketReader.OpCode.Binary;
+            SendSocket(dataMessageFrame.ToBytes());
+
+        }
+
+        /// <summary>
+        /// Writes raw bytes to the websocket.   Unframed data will cause disconnection
+        /// </summary>
+        /// <param name="data"></param>
+        private void SendSocket(byte[] data)
+        {
+            if (!_closing)
+            {
+                try
+                {
+
+                    _networkContext.Stream.Write(data, 0, data.Length);
+                }
+                catch (IOException)
+                {
+
+                }
+            }
+        }
+
+        /// <summary>
+        /// Sends a Ping check to the other side.  The other side SHOULD respond as soon as possible with a pong frame.   This interleaves with incoming fragmented frames.
+        /// </summary>
+        public void SendPingCheck()
+        {
+            WebSocketFrame pingFrame = new WebSocketFrame() { Header = WebsocketFrameHeader.HeaderDefault(), WebSocketPayload = new byte[0] };
+            pingFrame.Header.Opcode = WebSocketReader.OpCode.Ping;
+            pingFrame.Header.IsEnd = true;
+            _pingtime = Util.EnvironmentTickCount();
+            SendSocket(pingFrame.ToBytes());
+        }
+
+        /// <summary>
+        /// Closes the websocket connection.   Sends a close message to the other side if it hasn't already done so.
+        /// </summary>
+        /// <param name="message"></param>
+        public void Close(string message)
+        {
+            if (_networkContext.Stream != null)
+            {
+                if (_networkContext.Stream.CanWrite)
+            {
+                byte[] messagedata = Encoding.UTF8.GetBytes(message);
+                WebSocketFrame closeResponseFrame = new WebSocketFrame()
+                                                        {
+                                                            Header = WebsocketFrameHeader.HeaderDefault(),
+                                                            WebSocketPayload = messagedata
+                                                        };
+                closeResponseFrame.Header.Opcode = WebSocketReader.OpCode.Close;
+                closeResponseFrame.Header.PayloadLen = (ulong) messagedata.Length;
+                closeResponseFrame.Header.IsEnd = true;
+                SendSocket(closeResponseFrame.ToBytes());
+            }
+                }
+            CloseDelegate closeD = OnClose;
+            if (closeD != null)
+            {
+                closeD(this, new CloseEventArgs());
+            }
+
+            _closing = true;
+        }
+
+        /// <summary>
+        /// Processes a websocket frame and triggers consumer events
+        /// </summary>
+        /// <param name="psocketState">We need to modify the websocket state here depending on the frame</param>
+        private void ProcessFrame(WebSocketState psocketState)
+        {
+            if (psocketState.Header.IsMasked)
+            {
+                byte[] unmask = psocketState.ReceivedBytes.ToArray();
+                WebSocketReader.Mask(psocketState.Header.Mask, unmask);
+                psocketState.ReceivedBytes = new List<byte>(unmask);
+            }
+            
+            switch (psocketState.Header.Opcode)
+            {
+                case WebSocketReader.OpCode.Ping:
+                    PingDelegate pingD = OnPing;
+                    if (pingD != null)
+                    {
+                        pingD(this, new PingEventArgs());
+                    }
+
+                    WebSocketFrame pongFrame = new WebSocketFrame(){Header = WebsocketFrameHeader.HeaderDefault(),WebSocketPayload = new byte[0]};
+                    pongFrame.Header.Opcode = WebSocketReader.OpCode.Pong;
+                    pongFrame.Header.IsEnd = true;
+                    SendSocket(pongFrame.ToBytes());
+                    break;
+                case WebSocketReader.OpCode.Pong:
+                 
+                    PongDelegate pongD = OnPong;
+                    if (pongD != null)
+                    {
+                        pongD(this, new PongEventArgs(){PingResponseMS = Util.EnvironmentTickCountSubtract(Util.EnvironmentTickCount(),_pingtime)});
+                    }
+                    break;
+                case WebSocketReader.OpCode.Binary:
+                    if (!psocketState.Header.IsEnd) // Not done, so we need to store this and wait for the end frame.
+                    {
+                        psocketState.ContinuationFrame = new WebSocketFrame
+                                                             {
+                                                                 Header = psocketState.Header,
+                                                                 WebSocketPayload =
+                                                                     psocketState.ReceivedBytes.ToArray()
+                                                             };
+                    }
+                    else
+                    {
+                        // Send Done Event!
+                        DataDelegate dataD = OnData;
+                        if (dataD != null)
+                        {
+                            dataD(this,new WebsocketDataEventArgs(){Data = psocketState.ReceivedBytes.ToArray()});
+                        }
+                    }
+                    break;
+                case WebSocketReader.OpCode.Text:
+                    if (!psocketState.Header.IsEnd) // Not done, so we need to store this and wait for the end frame.
+                    {
+                        psocketState.ContinuationFrame = new WebSocketFrame
+                                                             {
+                                                                 Header = psocketState.Header,
+                                                                 WebSocketPayload =
+                                                                     psocketState.ReceivedBytes.ToArray()
+                                                             };
+                    }
+                    else
+                    {
+                        TextDelegate textD = OnText;
+                        if (textD != null)
+                        {
+                            textD(this, new WebsocketTextEventArgs() { Data = Encoding.UTF8.GetString(psocketState.ReceivedBytes.ToArray()) });
+                        }
+                        
+                        // Send Done Event!
+                    }
+                    break;
+                case WebSocketReader.OpCode.Continue:  // Continuation.  Multiple frames worth of data for one message.   Only valid when not using Control Opcodes
+                    //Console.WriteLine("currhead " + psocketState.Header.IsEnd);
+                    //Console.WriteLine("Continuation! " + psocketState.ContinuationFrame.Header.IsEnd);
+                    byte[] combineddata = new byte[psocketState.ReceivedBytes.Count+psocketState.ContinuationFrame.WebSocketPayload.Length];
+                    byte[] newdata = psocketState.ReceivedBytes.ToArray();
+                    Buffer.BlockCopy(psocketState.ContinuationFrame.WebSocketPayload, 0, combineddata, 0, psocketState.ContinuationFrame.WebSocketPayload.Length);
+                    Buffer.BlockCopy(newdata, 0, combineddata,
+                                        psocketState.ContinuationFrame.WebSocketPayload.Length, newdata.Length);
+                    psocketState.ContinuationFrame.WebSocketPayload = combineddata;
+                    psocketState.Header.PayloadLen = (ulong)combineddata.Length;
+                    if (psocketState.Header.IsEnd)
+                    {
+                        if (psocketState.ContinuationFrame.Header.Opcode == WebSocketReader.OpCode.Text)
+                        {
+                            // Send Done event    
+                            TextDelegate textD = OnText;
+                            if (textD != null)
+                            {
+                                textD(this, new WebsocketTextEventArgs() { Data = Encoding.UTF8.GetString(combineddata) });
+                            }
+                        }
+                        else if (psocketState.ContinuationFrame.Header.Opcode == WebSocketReader.OpCode.Binary)
+                        {
+                            // Send Done event
+                            DataDelegate dataD = OnData;
+                            if (dataD != null)
+                            {
+                                dataD(this, new WebsocketDataEventArgs() { Data = combineddata });
+                            }
+                        }
+                        else
+                        {
+                            // protocol violation
+                        }
+                        psocketState.ContinuationFrame = null;
+                    }
+                    break;
+                case WebSocketReader.OpCode.Close:
+                    Close(string.Empty);
+                    
+                    break;
+               
+            }
+            psocketState.Header.SetDefault();
+            psocketState.ReceivedBytes.Clear();
+            psocketState.ExpectedBytes = 0;
+        }
+        public void Dispose()
+        {
+            if (_networkContext != null && _networkContext.Stream != null)
+            {
+                if (_networkContext.Stream.CanWrite)
+                    _networkContext.Stream.Flush();
+                _networkContext.Stream.Close();
+                _networkContext.Stream.Dispose();
+                _networkContext.Stream = null;
+            }
+
+            if (_request != null && _request.InputStream != null)
+            {
+                _request.InputStream.Close();
+                _request.InputStream.Dispose();
+                _request = null;
+            }
+
+            if (_clientContext != null)
+            {
+                _clientContext.Close();
+                _clientContext = null;
+            }
+        }
+    }
+
+    /// <summary>
+    /// Reads a byte stream and returns Websocket frames.
+    /// </summary>
+    public class WebSocketReader
+    {
+        /// <summary>
+        /// Bit to determine if the frame read on the stream is the last frame in a sequence of fragmented frames
+        /// </summary>
+        private const byte EndBit = 0x80;
+
+        /// <summary>
+        /// These are the Frame Opcodes
+        /// </summary>
+        public enum OpCode
+        {
+            // Data Opcodes
+            Continue = 0x0,
+            Text = 0x1,
+            Binary = 0x2,
+
+            // Control flow Opcodes
+            Close = 0x8,
+            Ping = 0x9,
+            Pong = 0xA
+        }
+
+        /// <summary>
+        /// Masks and Unmasks data using the frame mask.  Mask is applied per octal
+        /// Note: Frames from clients MUST be masked
+        /// Note: Frames from servers MUST NOT be masked
+        /// </summary>
+        /// <param name="pMask">Int representing 32 bytes of mask data.  Mask is applied per octal</param>
+        /// <param name="pBuffer"></param>
+        public static void Mask(int pMask, byte[] pBuffer)
+        {
+            byte[] maskKey = BitConverter.GetBytes(pMask);
+            int currentMaskIndex = 0;
+            for (int i = 0; i < pBuffer.Length; i++)
+            {
+                pBuffer[i] = (byte)(pBuffer[i] ^ maskKey[currentMaskIndex]);
+                if (currentMaskIndex == 3)
+                {
+                    currentMaskIndex = 0;
+                }
+                else
+                {
+                    currentMaskIndex++;
+
+                }
+
+            }
+        }
+
+        /// <summary>
+        /// Attempts to read a header off the provided buffer.  Returns true, exports a WebSocketFrameheader, 
+        /// and an int to move the buffer forward when it reads a header.  False when it can't read a header
+        /// </summary>
+        /// <param name="pBuffer">Bytes read from the stream</param>
+        /// <param name="pOffset">Starting place in the stream to begin trying to read from</param>
+        /// <param name="length">Lenth in the stream to try and read from.  Provided for cases where the 
+        /// buffer's length is larger then the data in it</param>
+        /// <param name="oHeader">Outputs the read WebSocket frame header</param>
+        /// <param name="moveBuffer">Informs the calling stream to move the buffer forward</param>
+        /// <returns>True if it got a header, False if it didn't get a header</returns>
+        public static bool TryReadHeader(byte[] pBuffer, int pOffset, int length, out WebsocketFrameHeader oHeader,
+                                         out int moveBuffer)
+        {
+            oHeader = WebsocketFrameHeader.ZeroHeader;
+            int minumheadersize = 2;
+            if (length > pBuffer.Length - pOffset)
+                throw new ArgumentOutOfRangeException("The Length specified was larger the byte array supplied");
+            if (length < minumheadersize)
+            {
+                moveBuffer = 0;
+                return false;
+            }
+
+            byte nibble1 = (byte)(pBuffer[pOffset] & 0xF0);  //FIN/RSV1/RSV2/RSV3
+            byte nibble2 = (byte)(pBuffer[pOffset] & 0x0F); // Opcode block
+
+            oHeader = new WebsocketFrameHeader();
+            oHeader.SetDefault();
+
+            if ((nibble1 & WebSocketReader.EndBit) == WebSocketReader.EndBit)
+            {
+                oHeader.IsEnd = true;
+            }
+            else
+            {
+                oHeader.IsEnd = false;
+            }
+            //Opcode
+            oHeader.Opcode = (WebSocketReader.OpCode)nibble2;
+            //Mask
+            oHeader.IsMasked = Convert.ToBoolean((pBuffer[pOffset + 1] & 0x80) >> 7);
+
+            // Payload length
+            oHeader.PayloadLen = (byte)(pBuffer[pOffset + 1] & 0x7F);
+
+            int index = 2; // LargerPayload length starts at byte 3
+
+            switch (oHeader.PayloadLen)
+            {
+                case 126:
+                    minumheadersize += 2;
+                    if (length < minumheadersize)
+                    {
+                        moveBuffer = 0;
+                        return false;
+                    }
+                    Array.Reverse(pBuffer, pOffset + index, 2);  // two bytes
+                    oHeader.PayloadLen = BitConverter.ToUInt16(pBuffer, pOffset + index);
+                    index += 2;
+                    break;
+                case 127:   // we got more this is a bigger frame  
+                    // 8 bytes - uint64 - most significant bit 0  network byte order
+                    minumheadersize += 8;
+                    if (length < minumheadersize)
+                    {
+                        moveBuffer = 0;
+                        return false;
+                    }
+                    Array.Reverse(pBuffer, pOffset + index, 8);
+                    oHeader.PayloadLen = BitConverter.ToUInt64(pBuffer, pOffset + index);
+                    index += 8;
+                    break;
+                
+            }
+            //oHeader.PayloadLeft = oHeader.PayloadLen; // Start the count in case it's chunked over the network.  This is different then frame fragmentation
+            if (oHeader.IsMasked)
+            {
+                minumheadersize += 4;
+                if (length < minumheadersize)
+                {
+                    moveBuffer = 0;
+                    return false;
+                }
+                oHeader.Mask = BitConverter.ToInt32(pBuffer, pOffset + index);
+                index += 4;
+            }
+            moveBuffer = index;
+            return true;
+
+        }
+    }
+
+    /// <summary>
+    /// RFC6455 Websocket Frame
+    /// </summary>
+    public class WebSocketFrame
+    {
+        /*
+         * RFC6455
+nib   0       1       2       3       4       5       6       7
+byt   0               1               2               3               
+dec   0                   1                   2                   3   
+      0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 
+     +-+-+-+-+-------+-+-------------+-------------------------------+
+     |F|R|R|R| opcode|M| Payload len |    Extended payload length    |
+     |I|S|S|S|  (4)  |A|     (7)     |             (16/64)           +
+     |N|V|V|V|       |S|             |   (if payload len==126/127)   |
+     | |1|2|3|       |K|             |                               +
+     +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
+     |     Extended payload length continued, if payload len == 127  |
+     + - - - - - - - - - - - - - - - +-------------------------------+
+     |                               |Masking-key, if MASK set to 1  |
+     +-------------------------------+-------------------------------+
+     | Masking-key (continued)       |          Payload Data         |
+     +-------------------------------- - - - - - - - - - - - - - - - +
+     :                     Payload Data continued ...                :
+     + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
+     |                     Payload Data continued ...                |
+     +---------------------------------------------------------------+
+
+         *   When reading these, the frames are possibly fragmented and interleaved with control frames
+         *   the fragmented frames are not interleaved with data frames.  Just control frames
+         */
+        public static readonly WebSocketFrame DefaultFrame = new WebSocketFrame(){Header = new WebsocketFrameHeader(),WebSocketPayload = new byte[0]};
+        public WebsocketFrameHeader Header;
+        public byte[] WebSocketPayload;
+        
+        public byte[] ToBytes()
+        {
+            Header.PayloadLen = (ulong)WebSocketPayload.Length;
+            return Header.ToBytes(WebSocketPayload);
+        }
+
+    }
+
+    public struct WebsocketFrameHeader
+    {
+        //public byte CurrentMaskIndex;
+        /// <summary>
+        /// The last frame in a sequence of fragmented frames or the one and only frame for this message.
+        /// </summary>
+        public bool IsEnd;
+
+        /// <summary>
+        /// Returns whether the payload data is masked or not.  Data from Clients MUST be masked, Data from Servers MUST NOT be masked
+        /// </summary>
+        public bool IsMasked;
+
+        /// <summary>
+        /// A set of cryptologically sound random bytes XoR-ed against the payload octally.  Looped
+        /// </summary>
+        public int Mask;
+        /*
+byt   0               1               2               3
+      0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 
+     +---------------+---------------+---------------+---------------+
+     |    Octal 1    |    Octal 2    |    Octal 3    |    Octal 4    |
+     +---------------+---------------+---------------+---------------+
+*/
+
+
+        public WebSocketReader.OpCode Opcode;
+
+        public UInt64 PayloadLen;
+        //public UInt64 PayloadLeft;
+        // Payload is X + Y   
+        //public UInt64 ExtensionDataLength;
+        //public UInt64 ApplicationDataLength;
+        public static readonly WebsocketFrameHeader ZeroHeader = WebsocketFrameHeader.HeaderDefault();
+       
+        public void SetDefault()
+        {
+
+            //CurrentMaskIndex = 0;
+            IsEnd = true;
+            IsMasked = true;
+            Mask = 0;
+            Opcode = WebSocketReader.OpCode.Close;
+  //        PayloadLeft = 0;
+            PayloadLen = 0;
+  //        ExtensionDataLength = 0;
+  //        ApplicationDataLength = 0;
+
+        }
+
+        /// <summary>
+        /// Returns a byte array representing the Frame header
+        /// </summary>
+        /// <param name="payload">This is the frame data payload.  The header describes the size of the payload. 
+        /// If payload is null, a Zero sized payload is assumed</param>
+        /// <returns>Returns a byte array representing the frame header</returns>
+        public byte[] ToBytes(byte[] payload)
+        {
+            List<byte> result = new List<byte>();
+           
+            // Squeeze in our opcode and our ending bit.
+            result.Add((byte)((byte)Opcode | (IsEnd?0x80:0x00) ));
+           
+            // Again with the three different byte interpretations of size..
+
+            //bytesize
+            if (PayloadLen <= 125)
+            {
+                result.Add((byte) PayloadLen);
+            } //Uint16
+            else if (PayloadLen <= ushort.MaxValue)
+            {
+                result.Add(126);
+                byte[] payloadLengthByte = BitConverter.GetBytes(Convert.ToUInt16(PayloadLen));
+                Array.Reverse(payloadLengthByte);
+                result.AddRange(payloadLengthByte);
+            } //UInt64
+            else
+            {
+                result.Add(127);
+                byte[] payloadLengthByte = BitConverter.GetBytes(PayloadLen);
+                Array.Reverse(payloadLengthByte);
+                result.AddRange(payloadLengthByte);
+            }
+            
+            // Only add a payload if it's not null
+            if (payload != null)
+            {
+                result.AddRange(payload);
+            }
+            return result.ToArray();
+        }
+
+        /// <summary>
+        /// A Helper method to define the defaults
+        /// </summary>
+        /// <returns></returns>
+
+        public static WebsocketFrameHeader HeaderDefault()
+        {
+            return new WebsocketFrameHeader
+                       {
+                           //CurrentMaskIndex = 0,
+                           IsEnd = false,
+                           IsMasked = true,
+                           Mask = 0,
+                           Opcode = WebSocketReader.OpCode.Close,
+                           //PayloadLeft = 0,
+                           PayloadLen = 0,
+ //                          ExtensionDataLength = 0,
+ //                          ApplicationDataLength = 0
+                       };
+        }
+    }
+
+    public delegate void DataDelegate(object sender, WebsocketDataEventArgs data);
+
+    public delegate void TextDelegate(object sender, WebsocketTextEventArgs text);
+
+    public delegate void PingDelegate(object sender, PingEventArgs pingdata);
+
+    public delegate void PongDelegate(object sender, PongEventArgs pongdata);
+
+    public delegate void RegularHttpRequestDelegate(object sender, RegularHttpRequestEvnetArgs request);
+
+    public delegate void UpgradeCompletedDelegate(object sender, UpgradeCompletedEventArgs completeddata);
+
+    public delegate void UpgradeFailedDelegate(object sender, UpgradeFailedEventArgs faileddata);
+
+    public delegate void CloseDelegate(object sender, CloseEventArgs closedata);
+
+    public delegate bool ValidateHandshake(string pWebOrigin, string pWebSocketKey, string pHost);
+
+
+    public class WebsocketDataEventArgs : EventArgs
+    {
+        public byte[] Data;
+    }
+
+    public class WebsocketTextEventArgs : EventArgs
+    {
+        public string Data;
+    }
+
+    public class PingEventArgs : EventArgs
+    {
+        /// <summary>
+        /// The ping event can arbitrarily contain data
+        /// </summary>
+        public byte[] Data;
+    }
+
+    public class PongEventArgs : EventArgs
+    {
+        /// <summary>
+        /// The pong event can arbitrarily contain data
+        /// </summary>
+        public byte[] Data;
+
+        public int PingResponseMS;
+
+    }
+
+    public class RegularHttpRequestEvnetArgs : EventArgs
+    {
+
+    }
+
+    public class UpgradeCompletedEventArgs : EventArgs
+    {
+
+    }
+
+    public class UpgradeFailedEventArgs : EventArgs
+    {
+
+    }
+
+    public class CloseEventArgs : EventArgs
+    {
+
+    }
+
+   
+}
-- 
cgit v1.1