29 using System.Collections.Generic;
32 using System.Security.Cryptography;
34 using System.Threading;
37 namespace OpenSim.Framework.Servers.HttpServer
46 private class WebSocketState
48 public List<byte> ReceivedBytes;
49 public int ExpectedBytes;
51 public bool FrameComplete;
103 private ManualResetEvent _receiveDone =
new ManualResetEvent(
false);
106 private HTTPNetworkContext _networkContext;
107 private IHttpClientContext _clientContext;
109 private int _pingtime = 0;
110 private byte[] _buffer;
111 private int _bufferPosition;
112 private int _bufferLength;
113 private bool _closing;
114 private bool _upgraded;
115 private int _maxPayloadBytes = 41943040;
116 private int _initialMsgTimeout = 0;
117 private int _defaultReadTimeout = 10000;
119 private const string HandshakeAcceptText =
120 "HTTP/1.1 101 Switching Protocols\r\n" +
121 "upgrade: websocket\r\n" +
122 "Connection: Upgrade\r\n" +
123 "sec-websocket-accept: {0}\r\n\r\n";
126 private const string HandshakeDeclineText =
127 "HTTP/1.1 {0} {1}\r\n" +
128 "Connection: close\r\n\r\n";
133 private const string WebsocketHandshakeAcceptHashConstant =
"258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
136 : base(preq.HttpMethod, preq.
Url.OriginalString)
139 _networkContext = pContext.GiveMeTheNetworkStreamIKnowWhatImDoing();
140 _networkContext.Stream.ReadTimeout = _defaultReadTimeout;
141 _clientContext = pContext;
142 _bufferLength = bufferlen;
143 _buffer =
new byte[_bufferLength];
161 _buffer =
new byte[pChunk];
165 throw new InvalidOperationException(
"You must set the chunksize before the connection is upgraded");
172 public bool NoDelay_TCP_Nagle
176 if (_networkContext != null && _networkContext.Socket != null)
178 return _networkContext.Socket.NoDelay;
182 throw new InvalidOperationException(
"The socket has been shutdown");
187 if (_networkContext != null && _networkContext.Socket != null)
188 _networkContext.Socket.NoDelay = value;
191 throw new InvalidOperationException(
"The socket has been shutdown");
203 HandshakeAndUpgrade();
209 public int MaxPayloadSize
211 get {
return _maxPayloadBytes; }
212 set { _maxPayloadBytes = value; }
219 public int InitialMsgTimeout
221 get {
return _initialMsgTimeout; }
222 set { _initialMsgTimeout = value; }
230 string webOrigin = string.Empty;
231 string websocketKey = string.Empty;
232 string acceptKey = string.Empty;
233 string accepthost = string.Empty;
234 if (!
string.IsNullOrEmpty(_request.Headers[
"origin"]))
235 webOrigin = _request.Headers[
"origin"];
237 if (!
string.IsNullOrEmpty(_request.Headers[
"sec-websocket-key"]))
238 websocketKey = _request.Headers[
"sec-websocket-key"];
240 if (!
string.IsNullOrEmpty(_request.Headers[
"host"]))
241 accepthost = _request.Headers[
"host"];
243 if (
string.IsNullOrEmpty(_request.Headers[
"upgrade"]))
245 FailUpgrade(
OSHttpStatusCode.ClientErrorBadRequest,
"no upgrade request submitted");
248 string connectionheader = _request.Headers[
"upgrade"];
249 if (connectionheader.ToLower() !=
"websocket")
251 FailUpgrade(
OSHttpStatusCode.ClientErrorBadRequest,
"no connection upgrade request submitted");
257 if (HandshakeValidateMethodOverride != null)
259 if (HandshakeValidateMethodOverride(webOrigin, websocketKey, accepthost))
261 acceptKey = GenerateAcceptKey(websocketKey);
262 string rawaccept = string.Format(HandshakeAcceptText, acceptKey);
263 SendUpgradeSuccess(rawaccept);
269 FailUpgrade(
OSHttpStatusCode.ClientErrorForbidden,
"Origin Validation Failed");
274 acceptKey = GenerateAcceptKey(websocketKey);
275 string rawaccept = string.Format(HandshakeAcceptText, acceptKey);
276 SendUpgradeSuccess(rawaccept);
281 return _request.RemoteIPEndPoint;
291 private static string GenerateAcceptKey(
string key)
293 if (
string.IsNullOrEmpty(key))
296 string acceptkey = key + WebsocketHandshakeAcceptHashConstant;
298 SHA1 hashobj = SHA1.Create();
299 string ret = Convert.ToBase64String(hashobj.ComputeHash(Encoding.UTF8.GetBytes(acceptkey)));
309 private void SendUpgradeSuccess(
string pHandshakeResponse)
312 WebSocketState socketState =
new WebSocketState() { ReceivedBytes =
new List<byte>(), Header = WebsocketFrameHeader.HeaderDefault(), FrameComplete =
true};
314 byte[] bhandshakeResponse = Encoding.UTF8.GetBytes(pHandshakeResponse);
321 if (_initialMsgTimeout > 0)
323 _receiveDone.Reset();
326 _networkContext.Stream.BeginRead(_buffer, 0, _bufferLength, OnReceive, socketState);
329 _networkContext.Stream.Write(bhandshakeResponse, 0, bhandshakeResponse.Length);
330 _networkContext.Stream.Flush();
334 d(
this,
new UpgradeCompletedEventArgs());
335 if (_initialMsgTimeout > 0)
337 if (!_receiveDone.WaitOne(TimeSpan.FromMilliseconds(_initialMsgTimeout)))
345 catch (ObjectDisposedException)
358 string handshakeResponse = string.Format(HandshakeDeclineText, (int)pCode, pMessage.Replace(
"\n",
string.Empty).Replace(
"\r", string.Empty));
359 byte[] bhandshakeResponse = Encoding.UTF8.GetBytes(handshakeResponse);
360 _networkContext.Stream.Write(bhandshakeResponse, 0, bhandshakeResponse.Length);
361 _networkContext.Stream.Flush();
362 _networkContext.Stream.Dispose();
366 d(
this,
new UpgradeFailedEventArgs());
376 private void OnReceive(IAsyncResult ar)
378 WebSocketState _socketState = ar.AsyncState as WebSocketState;
381 int bytesRead = _networkContext.Stream.EndRead(ar);
385 _networkContext.Stream.Dispose();
386 _networkContext = null;
389 _bufferPosition += bytesRead;
391 if (_bufferPosition > _bufferLength)
398 bool headerread =
true;
399 int headerforwardposition = 0;
400 while (headerread && offset < bytesRead)
402 if (_socketState.FrameComplete)
404 WebsocketFrameHeader pheader = WebsocketFrameHeader.ZeroHeader;
406 headerread = WebSocketReader.TryReadHeader(_buffer, offset, _bufferPosition - offset, out pheader,
407 out headerforwardposition);
408 offset += headerforwardposition;
412 _socketState.FrameComplete =
false;
413 if (pheader.PayloadLen > (ulong) _maxPayloadBytes)
415 Close(
"Invalid Payload size");
419 if (pheader.PayloadLen > 0)
421 if ((
int) pheader.PayloadLen > _bufferPosition - offset)
423 byte[] writebytes =
new byte[_bufferPosition - offset];
425 Buffer.BlockCopy(_buffer, offset, writebytes, 0, (int) _bufferPosition - offset);
426 _socketState.ExpectedBytes = (int) pheader.PayloadLen;
427 _socketState.ReceivedBytes.AddRange(writebytes);
428 _socketState.Header = pheader;
429 offset += (int) _bufferPosition - offset;
433 byte[] writebytes =
new byte[pheader.PayloadLen];
434 Buffer.BlockCopy(_buffer, offset, writebytes, 0, (int) pheader.PayloadLen);
435 WebSocketReader.Mask(pheader.Mask, writebytes);
436 pheader.IsMasked =
false;
437 _socketState.FrameComplete =
true;
438 _socketState.ReceivedBytes.AddRange(writebytes);
439 _socketState.Header = pheader;
440 offset += (int) pheader.PayloadLen;
446 _socketState.FrameComplete =
true;
447 _socketState.Header = pheader;
450 if (_socketState.FrameComplete)
452 ProcessFrame(_socketState);
453 _socketState.Header.SetDefault();
454 _socketState.ReceivedBytes.Clear();
455 _socketState.ExpectedBytes = 0;
462 WebsocketFrameHeader frameHeader = _socketState.Header;
463 int bytesleft = _socketState.ExpectedBytes - _socketState.ReceivedBytes.Count;
465 if (bytesleft > _bufferPosition)
467 byte[] writebytes =
new byte[_bufferPosition];
469 Buffer.BlockCopy(_buffer, offset, writebytes, 0, (int) _bufferPosition);
470 _socketState.ReceivedBytes.AddRange(writebytes);
471 _socketState.Header = frameHeader;
472 offset += (int) _bufferPosition;
476 byte[] writebytes =
new byte[_bufferPosition];
477 Buffer.BlockCopy(_buffer, offset, writebytes, 0, (int) _bufferPosition);
478 _socketState.FrameComplete =
true;
479 _socketState.ReceivedBytes.AddRange(writebytes);
480 _socketState.Header = frameHeader;
481 offset += (int) _bufferPosition;
483 if (_socketState.FrameComplete)
485 ProcessFrame(_socketState);
486 _socketState.Header.SetDefault();
487 _socketState.ReceivedBytes.Clear();
488 _socketState.ExpectedBytes = 0;
496 if (offset <_buffer.Length)
497 Buffer.BlockCopy(_buffer, offset, _buffer, 0, _bufferPosition - offset);
498 _bufferPosition -= offset;
500 if (_networkContext.Stream != null && _networkContext.Stream.CanRead && !_closing)
502 _networkContext.Stream.BeginRead(_buffer, _bufferPosition, _bufferLength - _bufferPosition, OnReceive,
514 catch (ObjectDisposedException)
526 if (_initialMsgTimeout > 0)
529 _initialMsgTimeout = 0;
531 byte[] messagedata = Encoding.UTF8.GetBytes(message);
532 WebSocketFrame textMessageFrame =
new WebSocketFrame() { Header = WebsocketFrameHeader.HeaderDefault(), WebSocketPayload = messagedata };
533 textMessageFrame.Header.Opcode = WebSocketReader.OpCode.Text;
534 textMessageFrame.Header.IsEnd =
true;
535 SendSocket(textMessageFrame.ToBytes());
541 if (_initialMsgTimeout > 0)
544 _initialMsgTimeout = 0;
547 dataMessageFrame.Header.IsEnd =
true;
548 dataMessageFrame.Header.Opcode = WebSocketReader.OpCode.Binary;
549 SendSocket(dataMessageFrame.
ToBytes());
557 private void SendSocket(byte[] data)
564 _networkContext.Stream.Write(data, 0, data.Length);
578 if (_initialMsgTimeout > 0)
581 _initialMsgTimeout = 0;
584 pingFrame.Header.Opcode = WebSocketReader.OpCode.Ping;
585 pingFrame.Header.IsEnd =
true;
586 _pingtime = Util.EnvironmentTickCount();
587 SendSocket(pingFrame.
ToBytes());
596 if (_initialMsgTimeout > 0)
599 _initialMsgTimeout = 0;
601 if (_networkContext == null)
603 if (_networkContext.Stream != null)
605 if (_networkContext.Stream.CanWrite)
607 byte[] messagedata = Encoding.UTF8.GetBytes(message);
610 Header = WebsocketFrameHeader.HeaderDefault(),
611 WebSocketPayload = messagedata
613 closeResponseFrame.Header.Opcode = WebSocketReader.OpCode.Close;
614 closeResponseFrame.Header.PayloadLen = (ulong) messagedata.Length;
616 SendSocket(closeResponseFrame.ToBytes());
632 private void ProcessFrame(WebSocketState psocketState)
634 if (psocketState.Header.IsMasked)
636 byte[] unmask = psocketState.ReceivedBytes.ToArray();
637 WebSocketReader.Mask(psocketState.Header.Mask, unmask);
638 psocketState.ReceivedBytes =
new List<byte>(unmask);
643 _initialMsgTimeout = 0;
645 switch (psocketState.Header.Opcode)
647 case WebSocketReader.OpCode.Ping:
655 pongFrame.Header.Opcode = WebSocketReader.OpCode.Pong;
656 pongFrame.Header.IsEnd =
true;
657 SendSocket(pongFrame.
ToBytes());
659 case WebSocketReader.OpCode.Pong:
664 pongD(
this,
new PongEventArgs(){PingResponseMS = Util.EnvironmentTickCountSubtract(Util.EnvironmentTickCount(),_pingtime)});
667 case WebSocketReader.OpCode.Binary:
668 if (!psocketState.Header.IsEnd)
672 Header = psocketState.Header,
674 psocketState.ReceivedBytes.ToArray()
687 case WebSocketReader.OpCode.Text:
688 if (!psocketState.Header.IsEnd)
692 Header = psocketState.Header,
694 psocketState.ReceivedBytes.ToArray()
702 textD(
this,
new WebsocketTextEventArgs() { Data = Encoding.UTF8.GetString(psocketState.ReceivedBytes.ToArray()) });
708 case WebSocketReader.OpCode.Continue:
711 byte[] combineddata =
new byte[psocketState.ReceivedBytes.Count+psocketState.ContinuationFrame.WebSocketPayload.Length];
712 byte[] newdata = psocketState.ReceivedBytes.ToArray();
713 Buffer.BlockCopy(psocketState.ContinuationFrame.WebSocketPayload, 0, combineddata, 0, psocketState.ContinuationFrame.WebSocketPayload.Length);
714 Buffer.BlockCopy(newdata, 0, combineddata,
715 psocketState.ContinuationFrame.WebSocketPayload.Length, newdata.Length);
716 psocketState.ContinuationFrame.WebSocketPayload = combineddata;
717 psocketState.Header.PayloadLen = (ulong)combineddata.Length;
718 if (psocketState.Header.IsEnd)
742 psocketState.ContinuationFrame = null;
745 case WebSocketReader.OpCode.Close:
751 psocketState.Header.SetDefault();
752 psocketState.ReceivedBytes.Clear();
753 psocketState.ExpectedBytes = 0;
757 if (_initialMsgTimeout > 0)
760 _initialMsgTimeout = 0;
762 if (_networkContext != null && _networkContext.Stream != null)
764 if (_networkContext.Stream.CanWrite)
765 _networkContext.Stream.Flush();
766 _networkContext.Stream.Close();
767 _networkContext.Stream.Dispose();
768 _networkContext.Stream = null;
771 if (_request != null && _request.InputStream != null)
773 _request.InputStream.Close();
774 _request.InputStream.Dispose();
778 if (_clientContext != null)
780 _clientContext.Close();
781 _clientContext = null;
794 private const byte EndBit = 0x80;
819 public static void Mask(
int pMask, byte[] pBuffer)
821 byte[] maskKey = BitConverter.GetBytes(pMask);
822 int currentMaskIndex = 0;
823 for (
int i = 0; i < pBuffer.Length; i++)
825 pBuffer[i] = (byte)(pBuffer[i] ^ maskKey[currentMaskIndex]);
826 if (currentMaskIndex == 3)
828 currentMaskIndex = 0;
853 oHeader = WebsocketFrameHeader.ZeroHeader;
854 int minumheadersize = 2;
855 if (length > pBuffer.Length - pOffset)
856 throw new ArgumentOutOfRangeException(
"The Length specified was larger the byte array supplied");
857 if (length < minumheadersize)
863 byte nibble1 = (byte)(pBuffer[pOffset] & 0xF0);
864 byte nibble2 = (byte)(pBuffer[pOffset] & 0x0F);
867 oHeader.SetDefault();
871 oHeader.IsEnd =
true;
875 oHeader.IsEnd =
false;
880 oHeader.IsMasked = Convert.ToBoolean((pBuffer[pOffset + 1] & 0x80) >> 7);
883 oHeader.PayloadLen = (byte)(pBuffer[pOffset + 1] & 0x7F);
887 switch (oHeader.PayloadLen)
890 minumheadersize += 2;
891 if (length < minumheadersize)
896 Array.Reverse(pBuffer, pOffset + index, 2);
897 oHeader.PayloadLen = BitConverter.ToUInt16(pBuffer, pOffset + index);
902 minumheadersize += 8;
903 if (length < minumheadersize)
908 Array.Reverse(pBuffer, pOffset + index, 8);
909 oHeader.PayloadLen = BitConverter.ToUInt64(pBuffer, pOffset + index);
915 if (oHeader.IsMasked)
917 minumheadersize += 4;
918 if (length < minumheadersize)
923 oHeader.Mask = BitConverter.ToInt32(pBuffer, pOffset + index);
969 Header.PayloadLen = (ulong)WebSocketPayload.Length;
970 return Header.ToBytes(WebSocketPayload);
1017 Opcode = WebSocketReader.OpCode.Close;
1033 List<byte> result =
new List<byte>();
1036 result.Add((byte)((byte)Opcode | (IsEnd?0x80:0x00) ));
1041 if (PayloadLen <= 125)
1043 result.Add((byte) PayloadLen);
1045 else if (PayloadLen <= ushort.MaxValue)
1048 byte[] payloadLengthByte = BitConverter.GetBytes(Convert.ToUInt16(PayloadLen));
1049 Array.Reverse(payloadLengthByte);
1050 result.AddRange(payloadLengthByte);
1055 byte[] payloadLengthByte = BitConverter.GetBytes(PayloadLen);
1056 Array.Reverse(payloadLengthByte);
1057 result.AddRange(payloadLengthByte);
1061 if (payload != null)
1063 result.AddRange(payload);
1065 return result.ToArray();
1081 Opcode = WebSocketReader.OpCode.Close,
1090 public delegate
void DataDelegate(
object sender, WebsocketDataEventArgs data);
1092 public delegate
void TextDelegate(
object sender, WebsocketTextEventArgs text);
1094 public delegate
void PingDelegate(
object sender, PingEventArgs pingdata);
1096 public delegate
void PongDelegate(
object sender, PongEventArgs pongdata);
1104 public delegate
void CloseDelegate(
object sender, CloseEventArgs closedata);
1106 public delegate
bool ValidateHandshake(
string pWebOrigin,
string pWebSocketKey,
string pHost);
byte[] Data
The pong event can arbitrarily contain data
CloseDelegate OnClose
When the websocket is closed, this will be fired.
PongDelegate OnPong
This is a response to a ping you sent.
void HandshakeAndUpgrade()
This triggers the websocket start the upgrade process
void SendMessage(string message)
Sends a string to the other side
static bool TryReadHeader(byte[] pBuffer, int pOffset, int length, out WebsocketFrameHeader oHeader, out int moveBuffer)
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
UpgradeFailedDelegate OnUpgradeFailed
If the upgrade failed, this will be fired
This class implements websockets. It grabs the network context from C::Webserver and utilizes it dire...
OpCode
These are the Frame Opcodes
void SetChunksize(int pChunk)
Sets the length of the stream buffer
TextDelegate OnText
Textual Data will trigger this event
DataDelegate OnData
Binary Data will trigger this event
void Start()
This triggers the websocket to start the upgrade process... This is a Generalized Networking 'common ...
delegate void RegularHttpRequestDelegate(object sender, RegularHttpRequestEvnetArgs request)
OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString key
WebsocketFrameHeader Header
delegate void PingDelegate(object sender, PingEventArgs pingdata)
IPEndPoint GetRemoteIPEndpoint()
delegate void DataDelegate(object sender, WebsocketDataEventArgs data)
delegate bool ValidateHandshake(string pWebOrigin, string pWebSocketKey, string pHost)
delegate void PongDelegate(object sender, PongEventArgs pongdata)
delegate void CloseDelegate(object sender, CloseEventArgs closedata)
delegate void UpgradeCompletedDelegate(object sender, UpgradeCompletedEventArgs completeddata)
UpgradeCompletedDelegate OnUpgradeCompleted
This is a regular HTTP Request... This may be removed in the future.
PingDelegate OnPing
A ping request form the other side will trigger this event. This class responds to the ping automatic...
WebSocketHttpServerHandler(OSHttpRequest preq, IHttpClientContext pContext, int bufferlen)
void SendData(byte[] data)
Reads a byte stream and returns Websocket frames.
delegate void TextDelegate(object sender, WebsocketTextEventArgs text)
delegate void UpgradeFailedDelegate(object sender, UpgradeFailedEventArgs faileddata)
static void Mask(int pMask, byte[] pBuffer)
Masks and Unmasks data using the frame mask. Mask is applied per octal Note: Frames from clients MUST...
void SendPingCheck()
Sends a Ping check to the other side. The other side SHOULD respond as soon as possible with a pong f...
OSHttpStatusCode
HTTP status codes (almost) as defined by W3C in http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html and IETF in http://tools.ietf.org/html/rfc6585
void Close(string message)
Closes the websocket connection. Sends a close message to the other side if it hasn't already done so...
byte[] Data
The ping event can arbitrarily contain data