2013-02-07 15:26:48 +00:00
/ *
* 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 ;
2013-10-05 01:37:59 +00:00
using System.Net ;
2013-02-07 15:26:48 +00:00
using System.Security.Cryptography ;
using System.Text ;
2013-10-05 01:37:59 +00:00
using System.Threading ;
2020-04-02 20:44:34 +00:00
using OSHttpServer ;
2013-02-07 15:26:48 +00:00
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>
2017-01-05 19:07:37 +00:00
/// This is a regular HTTP Request... This may be removed in the future.
2013-02-07 15:26:48 +00:00
/// </summary>
2013-06-19 23:54:19 +00:00
// public event RegularHttpRequestDelegate OnRegularHttpRequest;
2013-02-07 15:26:48 +00:00
/// <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 ;
2017-01-05 19:07:37 +00:00
2013-02-07 15:26:48 +00:00
/// <summary>
2017-01-05 19:07:37 +00:00
/// Set this delegate to allow your module to validate the origin of the
2013-02-07 15:26:48 +00:00
/// Websocket request. Primary line of defense against cross site scripting
/// </summary>
public ValidateHandshake HandshakeValidateMethodOverride = null ;
2013-10-05 01:37:59 +00:00
private ManualResetEvent _receiveDone = new ManualResetEvent ( false ) ;
2013-02-07 15:26:48 +00:00
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 ;
2013-03-05 05:04:09 +00:00
private int _maxPayloadBytes = 41943040 ;
2013-10-05 01:37:59 +00:00
private int _initialMsgTimeout = 0 ;
private int _defaultReadTimeout = 10000 ;
2013-02-07 15:26:48 +00:00
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 ( ) ;
2013-10-05 01:37:59 +00:00
_networkContext . Stream . ReadTimeout = _defaultReadTimeout ;
2013-02-07 15:26:48 +00:00
_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" ) ;
}
2017-01-05 19:07:37 +00:00
}
2013-02-07 15:26:48 +00:00
set
{
if ( _networkContext ! = null & & _networkContext . Socket ! = null )
_networkContext . Socket . NoDelay = value ;
else
{
throw new InvalidOperationException ( "The socket has been shutdown" ) ;
}
}
}
/// <summary>
2017-01-05 19:07:37 +00:00
/// 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
2013-02-07 15:26:48 +00:00
/// of the more context appropriate HandshakeAndUpgrade()
/// </summary>
public void Start ( )
{
HandshakeAndUpgrade ( ) ;
}
2013-03-05 05:04:09 +00:00
/// <summary>
/// Max Payload Size in bytes. Defaults to 40MB, but could be set upon connection before calling handshake and upgrade.
/// </summary>
public int MaxPayloadSize
{
get { return _maxPayloadBytes ; }
set { _maxPayloadBytes = value ; }
}
2013-10-05 01:37:59 +00:00
/// <summary>
/// Set this to the maximum amount of milliseconds to wait for the first complete message to be sent or received on the websocket after upgrading
/// Default, it waits forever. Use this when your Websocket consuming code opens a connection and waits for a message from the other side to avoid a Denial of Service vector.
/// </summary>
public int InitialMsgTimeout
{
get { return _initialMsgTimeout ; }
set { _initialMsgTimeout = value ; }
}
2013-02-07 15:26:48 +00:00
/// <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 ) ;
2017-01-05 19:07:37 +00:00
2013-10-05 01:37:59 +00:00
2013-02-07 15:26:48 +00:00
}
else
{
FailUpgrade ( OSHttpStatusCode . ClientErrorForbidden , "Origin Validation Failed" ) ;
}
}
else
{
acceptKey = GenerateAcceptKey ( websocketKey ) ;
string rawaccept = string . Format ( HandshakeAcceptText , acceptKey ) ;
SendUpgradeSuccess ( rawaccept ) ;
}
}
2013-10-05 01:37:59 +00:00
public IPEndPoint GetRemoteIPEndpoint ( )
{
return _request . RemoteIPEndPoint ;
}
2013-02-07 15:26:48 +00:00
/// <summary>
2017-01-05 19:07:37 +00:00
/// Generates a handshake response key string based on the client's
2013-02-07 15:26:48 +00:00
/// 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 ( ) ;
2017-01-05 19:07:37 +00:00
2013-02-07 15:26:48 +00:00
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 } ;
2017-01-05 19:07:37 +00:00
2013-02-07 15:26:48 +00:00
byte [ ] bhandshakeResponse = Encoding . UTF8 . GetBytes ( pHandshakeResponse ) ;
2013-10-05 01:37:59 +00:00
2017-01-05 19:07:37 +00:00
2013-10-05 01:37:59 +00:00
2013-02-07 15:26:48 +00:00
try
{
2013-10-05 01:37:59 +00:00
if ( _initialMsgTimeout > 0 )
{
_receiveDone . Reset ( ) ;
}
2013-02-07 15:26:48 +00:00
// 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 ) ;
2017-01-05 19:07:37 +00:00
2013-02-07 15:26:48 +00:00
// 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 ( ) ) ;
2013-10-05 01:37:59 +00:00
if ( _initialMsgTimeout > 0 )
{
if ( ! _receiveDone . WaitOne ( TimeSpan . FromMilliseconds ( _initialMsgTimeout ) ) )
Close ( string . Empty ) ;
}
2013-02-07 15:26:48 +00:00
}
2013-06-19 23:54:19 +00:00
catch ( IOException )
2013-02-07 15:26:48 +00:00
{
Close ( string . Empty ) ;
}
2013-06-19 23:54:19 +00:00
catch ( ObjectDisposedException )
2013-02-07 15:26:48 +00:00
{
Close ( string . Empty ) ;
2017-01-05 19:07:37 +00:00
}
2013-02-07 15:26:48 +00:00
}
/// <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.
2017-01-05 19:07:37 +00:00
/// This chunks the input stream based on the length of the provided buffer and processes out
2013-02-07 15:26:48 +00:00
/// 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 )
{
2017-01-05 19:07:37 +00:00
// Message too big for chunksize.. not sure how this happened...
2013-02-07 15:26:48 +00:00
//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 ;
2013-03-05 05:04:09 +00:00
if ( pheader . PayloadLen > ( ulong ) _maxPayloadBytes )
{
Close ( "Invalid Payload size" ) ;
2017-01-05 19:07:37 +00:00
2013-03-05 05:04:09 +00:00
return ;
}
2013-02-07 15:26:48 +00:00
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
2017-01-05 19:07:37 +00:00
}
2013-02-07 15:26:48 +00:00
}
}
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
{
2017-01-05 19:07:37 +00:00
// We can't read the stream anymore...
2013-02-07 15:26:48 +00:00
}
}
2013-06-19 23:54:19 +00:00
catch ( IOException )
2013-02-07 15:26:48 +00:00
{
Close ( string . Empty ) ;
}
2013-06-19 23:54:19 +00:00
catch ( ObjectDisposedException )
2013-02-07 15:26:48 +00:00
{
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 )
{
2013-10-05 01:37:59 +00:00
if ( _initialMsgTimeout > 0 )
{
_receiveDone . Set ( ) ;
_initialMsgTimeout = 0 ;
}
2013-02-07 15:26:48 +00:00
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 ( ) ) ;
2017-01-05 19:07:37 +00:00
2013-02-07 15:26:48 +00:00
}
public void SendData ( byte [ ] data )
{
2013-10-05 01:37:59 +00:00
if ( _initialMsgTimeout > 0 )
{
_receiveDone . Set ( ) ;
_initialMsgTimeout = 0 ;
}
2013-02-07 15:26:48 +00:00
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 ( )
{
2013-10-05 01:37:59 +00:00
if ( _initialMsgTimeout > 0 )
{
_receiveDone . Set ( ) ;
_initialMsgTimeout = 0 ;
}
2013-02-07 15:26:48 +00:00
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 )
{
2013-10-05 01:37:59 +00:00
if ( _initialMsgTimeout > 0 )
{
_receiveDone . Set ( ) ;
_initialMsgTimeout = 0 ;
}
2013-02-14 23:43:53 +00:00
if ( _networkContext = = null )
return ;
2013-02-07 15:26:48 +00:00
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 ) ;
}
2013-10-05 01:37:59 +00:00
if ( psocketState . Header . Opcode ! = WebSocketReader . OpCode . Continue & & _initialMsgTimeout > 0 )
{
_receiveDone . Set ( ) ;
_initialMsgTimeout = 0 ;
}
2013-02-07 15:26:48 +00:00
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 :
2017-01-05 19:07:37 +00:00
2013-02-07 15:26:48 +00:00
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 ( ) ) } ) ;
}
2017-01-05 19:07:37 +00:00
2013-02-07 15:26:48 +00:00
// 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 )
{
2017-01-05 19:07:37 +00:00
// Send Done event
2013-02-07 15:26:48 +00:00
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 ) ;
2017-01-05 19:07:37 +00:00
2013-02-07 15:26:48 +00:00
break ;
2017-01-05 19:07:37 +00:00
2013-02-07 15:26:48 +00:00
}
psocketState . Header . SetDefault ( ) ;
psocketState . ReceivedBytes . Clear ( ) ;
psocketState . ExpectedBytes = 0 ;
}
public void Dispose ( )
{
2013-10-05 01:37:59 +00:00
if ( _initialMsgTimeout > 0 )
{
_receiveDone . Set ( ) ;
_initialMsgTimeout = 0 ;
}
2013-02-07 15:26:48 +00:00
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>
2017-01-05 19:07:37 +00:00
/// Attempts to read a header off the provided buffer. Returns true, exports a WebSocketFrameheader,
2013-02-07 15:26:48 +00:00
/// 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>
2017-01-05 19:07:37 +00:00
/// <param name="length">Lenth in the stream to try and read from. Provided for cases where the
2013-02-07 15:26:48 +00:00
/// 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 ;
2017-01-05 19:07:37 +00:00
case 127 : // we got more this is a bigger frame
2013-02-07 15:26:48 +00:00
// 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 ;
2017-01-05 19:07:37 +00:00
2013-02-07 15:26:48 +00:00
}
//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
2017-01-05 19:07:37 +00:00
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
2013-02-07 15:26:48 +00:00
+ - + - + - + - + - - - - - - - + - + - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| 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 ;
2017-01-05 19:07:37 +00:00
2013-02-07 15:26:48 +00:00
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
2017-01-05 19:07:37 +00:00
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
2013-02-07 15:26:48 +00:00
+ - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - +
| Octal 1 | Octal 2 | Octal 3 | Octal 4 |
+ - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - +
* /
public WebSocketReader . OpCode Opcode ;
public UInt64 PayloadLen ;
//public UInt64 PayloadLeft;
2017-01-05 19:07:37 +00:00
// Payload is X + Y
2013-02-07 15:26:48 +00:00
//public UInt64 ExtensionDataLength;
//public UInt64 ApplicationDataLength;
public static readonly WebsocketFrameHeader ZeroHeader = WebsocketFrameHeader . HeaderDefault ( ) ;
2017-01-05 19:07:37 +00:00
2013-02-07 15:26:48 +00:00
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>
2017-01-05 19:07:37 +00:00
/// <param name="payload">This is the frame data payload. The header describes the size of the payload.
2013-02-07 15:26:48 +00:00
/// 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 > ( ) ;
2017-01-05 19:07:37 +00:00
2013-02-07 15:26:48 +00:00
// Squeeze in our opcode and our ending bit.
result . Add ( ( byte ) ( ( byte ) Opcode | ( IsEnd ? 0x80 : 0x00 ) ) ) ;
2017-01-05 19:07:37 +00:00
2013-02-07 15:26:48 +00:00
// 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 ) ;
}
2017-01-05 19:07:37 +00:00
2013-02-07 15:26:48 +00:00
// 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
{
}
2017-01-05 19:07:37 +00:00
2013-02-07 15:26:48 +00:00
}