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);
+
+ ///
+ /// 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.
+ ///
+ ///
+ ///
+ public delegate void WebSocketRequestDelegate(string servicepath, WebSocketHttpServerHandler handler);
///
/// 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.
+ ///
+ /// This class implements websockets. It grabs the network context from C#Webserver and utilizes it directly as a tcp streaming service
+ ///
+ public sealed class WebSocketHttpServerHandler : BaseRequestHandler
+ {
+
+ private class WebSocketState
+ {
+ public List ReceivedBytes;
+ public int ExpectedBytes;
+ public WebsocketFrameHeader Header;
+ public bool FrameComplete;
+ public WebSocketFrame ContinuationFrame;
+ }
+
+ ///
+ /// Binary Data will trigger this event
+ ///
+ public event DataDelegate OnData;
+
+ ///
+ /// Textual Data will trigger this event
+ ///
+ public event TextDelegate OnText;
+
+ ///
+ /// 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.
+ ///
+ public event PingDelegate OnPing;
+
+ ///
+ /// This is a response to a ping you sent.
+ ///
+ public event PongDelegate OnPong;
+
+ ///
+ /// This is a regular HTTP Request... This may be removed in the future.
+ ///
+ public event RegularHttpRequestDelegate OnRegularHttpRequest;
+
+ ///
+ /// When the upgrade from a HTTP request to a Websocket is completed, this will be fired
+ ///
+ public event UpgradeCompletedDelegate OnUpgradeCompleted;
+
+ ///
+ /// If the upgrade failed, this will be fired
+ ///
+ public event UpgradeFailedDelegate OnUpgradeFailed;
+
+ ///
+ /// When the websocket is closed, this will be fired.
+ ///
+ public event CloseDelegate OnClose;
+
+ ///
+ /// Set this delegate to allow your module to validate the origin of the
+ /// Websocket request. Primary line of defense against cross site scripting
+ ///
+ 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";
+
+ ///
+ /// Mysterious constant defined in RFC6455 to append to the client provided security key
+ ///
+ 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();
+
+ }
+
+ ///
+ /// Sets the length of the stream buffer
+ ///
+ /// Byte length.
+ 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");
+ }
+ }
+
+ ///
+ /// This is the famous nagle.
+ ///
+ 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");
+ }
+ }
+ }
+
+ ///
+ /// 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()
+ ///
+ public void Start()
+ {
+ HandshakeAndUpgrade();
+ }
+
+ ///
+ /// This triggers the websocket start the upgrade process
+ ///
+ 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);
+ }
+ }
+
+ ///
+ /// 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.
+ ///
+ /// Client provided security key
+ ///
+ 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;
+ }
+
+ ///
+ /// Informs the otherside that we accepted their upgrade request
+ ///
+ /// The HTTP 1.1 101 response that says Yay \o/
+ 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(), 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);
+ }
+
+ }
+
+ ///
+ /// The server has decided not to allow the upgrade to a websocket for some reason. The Http 1.1 response that says Nay >:(
+ ///
+ /// HTTP Status reflecting the reason why
+ /// Textual reason for the upgrade fail
+ 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());
+ }
+
+
+ ///
+ /// 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.
+ ///
+ /// Our Async State from beginread
+ 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);
+ }
+ }
+
+ ///
+ /// Sends a string to the other side
+ ///
+ /// the string message that is to be sent
+ 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());
+
+ }
+
+ ///
+ /// Writes raw bytes to the websocket. Unframed data will cause disconnection
+ ///
+ ///
+ private void SendSocket(byte[] data)
+ {
+ if (!_closing)
+ {
+ try
+ {
+
+ _networkContext.Stream.Write(data, 0, data.Length);
+ }
+ catch (IOException)
+ {
+
+ }
+ }
+ }
+
+ ///
+ /// 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.
+ ///
+ 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());
+ }
+
+ ///
+ /// Closes the websocket connection. Sends a close message to the other side if it hasn't already done so.
+ ///
+ ///
+ 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;
+ }
+
+ ///
+ /// Processes a websocket frame and triggers consumer events
+ ///
+ /// We need to modify the websocket state here depending on the frame
+ private void ProcessFrame(WebSocketState psocketState)
+ {
+ if (psocketState.Header.IsMasked)
+ {
+ byte[] unmask = psocketState.ReceivedBytes.ToArray();
+ WebSocketReader.Mask(psocketState.Header.Mask, unmask);
+ psocketState.ReceivedBytes = new List(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;
+ }
+ }
+ }
+
+ ///
+ /// Reads a byte stream and returns Websocket frames.
+ ///
+ public class WebSocketReader
+ {
+ ///
+ /// Bit to determine if the frame read on the stream is the last frame in a sequence of fragmented frames
+ ///
+ private const byte EndBit = 0x80;
+
+ ///
+ /// These are the Frame Opcodes
+ ///
+ public enum OpCode
+ {
+ // Data Opcodes
+ Continue = 0x0,
+ Text = 0x1,
+ Binary = 0x2,
+
+ // Control flow Opcodes
+ Close = 0x8,
+ Ping = 0x9,
+ Pong = 0xA
+ }
+
+ ///
+ /// 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
+ ///
+ /// Int representing 32 bytes of mask data. Mask is applied per octal
+ ///
+ 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++;
+
+ }
+
+ }
+ }
+
+ ///
+ /// 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
+ ///
+ /// Bytes read from the stream
+ /// Starting place in the stream to begin trying to read from
+ /// Lenth in the stream to try and read from. Provided for cases where the
+ /// buffer's length is larger then the data in it
+ /// Outputs the read WebSocket frame header
+ /// Informs the calling stream to move the buffer forward
+ /// True if it got a header, False if it didn't get a header
+ 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;
+
+ }
+ }
+
+ ///
+ /// RFC6455 Websocket Frame
+ ///
+ 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;
+ ///
+ /// The last frame in a sequence of fragmented frames or the one and only frame for this message.
+ ///
+ public bool IsEnd;
+
+ ///
+ /// Returns whether the payload data is masked or not. Data from Clients MUST be masked, Data from Servers MUST NOT be masked
+ ///
+ public bool IsMasked;
+
+ ///
+ /// A set of cryptologically sound random bytes XoR-ed against the payload octally. Looped
+ ///
+ 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;
+
+ }
+
+ ///
+ /// Returns a byte array representing the Frame header
+ ///
+ /// This is the frame data payload. The header describes the size of the payload.
+ /// If payload is null, a Zero sized payload is assumed
+ /// Returns a byte array representing the frame header
+ public byte[] ToBytes(byte[] payload)
+ {
+ List result = new List();
+
+ // 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();
+ }
+
+ ///
+ /// A Helper method to define the defaults
+ ///
+ ///
+
+ 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
+ {
+ ///
+ /// The ping event can arbitrarily contain data
+ ///
+ public byte[] Data;
+ }
+
+ public class PongEventArgs : EventArgs
+ {
+ ///
+ /// The pong event can arbitrarily contain data
+ ///
+ 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