OpenSim
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros
WebsocketServerHandler.cs
Go to the documentation of this file.
1 /*
2  * Copyright (c) Contributors, http://opensimulator.org/
3  * See CONTRIBUTORS.TXT for a full list of copyright holders.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  * * Redistributions of source code must retain the above copyright
8  * notice, this list of conditions and the following disclaimer.
9  * * Redistributions in binary form must reproduce the above copyright
10  * notice, this list of conditions and the following disclaimer in the
11  * documentation and/or other materials provided with the distribution.
12  * * Neither the name of the OpenSimulator Project nor the
13  * names of its contributors may be used to endorse or promote products
14  * derived from this software without specific prior written permission.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
17  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19  * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
20  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27 
28 using System;
29 using System.Collections.Generic;
30 using System.IO;
31 using System.Net;
32 using System.Security.Cryptography;
33 using System.Text;
34 using System.Threading;
35 using HttpServer;
36 
37 namespace OpenSim.Framework.Servers.HttpServer
38 {
39  // Sealed class. If you're going to unseal it, implement IDisposable.
44  {
45 
46  private class WebSocketState
47  {
48  public List<byte> ReceivedBytes;
49  public int ExpectedBytes;
50  public WebsocketFrameHeader Header;
51  public bool FrameComplete;
52  public WebSocketFrame ContinuationFrame;
53  }
54 
58  public event DataDelegate OnData;
59 
63  public event TextDelegate OnText;
64 
70  public event PingDelegate OnPing;
71 
75  public event PongDelegate OnPong;
76 
80 // public event RegularHttpRequestDelegate OnRegularHttpRequest;
81 
86 
91 
95  public event CloseDelegate OnClose;
96 
101  public ValidateHandshake HandshakeValidateMethodOverride = null;
102 
103  private ManualResetEvent _receiveDone = new ManualResetEvent(false);
104 
105  private OSHttpRequest _request;
106  private HTTPNetworkContext _networkContext;
107  private IHttpClientContext _clientContext;
108 
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;
118 
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";// +
124  //"{1}";
125 
126  private const string HandshakeDeclineText =
127  "HTTP/1.1 {0} {1}\r\n" +
128  "Connection: close\r\n\r\n";
129 
133  private const string WebsocketHandshakeAcceptHashConstant = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
134 
135  public WebSocketHttpServerHandler(OSHttpRequest preq, IHttpClientContext pContext, int bufferlen)
136  : base(preq.HttpMethod, preq.Url.OriginalString)
137  {
138  _request = preq;
139  _networkContext = pContext.GiveMeTheNetworkStreamIKnowWhatImDoing();
140  _networkContext.Stream.ReadTimeout = _defaultReadTimeout;
141  _clientContext = pContext;
142  _bufferLength = bufferlen;
143  _buffer = new byte[_bufferLength];
144  }
145 
146  // Sealed class implments destructor and an internal dispose method. complies with C# unmanaged resource best practices.
148  {
149  Dispose();
150 
151  }
152 
157  public void SetChunksize(int pChunk)
158  {
159  if (!_upgraded)
160  {
161  _buffer = new byte[pChunk];
162  }
163  else
164  {
165  throw new InvalidOperationException("You must set the chunksize before the connection is upgraded");
166  }
167  }
168 
172  public bool NoDelay_TCP_Nagle
173  {
174  get
175  {
176  if (_networkContext != null && _networkContext.Socket != null)
177  {
178  return _networkContext.Socket.NoDelay;
179  }
180  else
181  {
182  throw new InvalidOperationException("The socket has been shutdown");
183  }
184  }
185  set
186  {
187  if (_networkContext != null && _networkContext.Socket != null)
188  _networkContext.Socket.NoDelay = value;
189  else
190  {
191  throw new InvalidOperationException("The socket has been shutdown");
192  }
193  }
194  }
195 
201  public void Start()
202  {
203  HandshakeAndUpgrade();
204  }
205 
209  public int MaxPayloadSize
210  {
211  get { return _maxPayloadBytes; }
212  set { _maxPayloadBytes = value; }
213  }
214 
219  public int InitialMsgTimeout
220  {
221  get { return _initialMsgTimeout; }
222  set { _initialMsgTimeout = value; }
223  }
224 
228  public void HandshakeAndUpgrade()
229  {
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"];
236 
237  if (!string.IsNullOrEmpty(_request.Headers["sec-websocket-key"]))
238  websocketKey = _request.Headers["sec-websocket-key"];
239 
240  if (!string.IsNullOrEmpty(_request.Headers["host"]))
241  accepthost = _request.Headers["host"];
242 
243  if (string.IsNullOrEmpty(_request.Headers["upgrade"]))
244  {
245  FailUpgrade(OSHttpStatusCode.ClientErrorBadRequest, "no upgrade request submitted");
246  }
247 
248  string connectionheader = _request.Headers["upgrade"];
249  if (connectionheader.ToLower() != "websocket")
250  {
251  FailUpgrade(OSHttpStatusCode.ClientErrorBadRequest, "no connection upgrade request submitted");
252  }
253 
254  // If the object consumer provided a method to validate the origin, we should call it and give the client a success or fail.
255  // If not.. we should accept any. The assumption here is that there would be no Websocket handlers registered in baseHTTPServer unless
256  // Something asked for it...
257  if (HandshakeValidateMethodOverride != null)
258  {
259  if (HandshakeValidateMethodOverride(webOrigin, websocketKey, accepthost))
260  {
261  acceptKey = GenerateAcceptKey(websocketKey);
262  string rawaccept = string.Format(HandshakeAcceptText, acceptKey);
263  SendUpgradeSuccess(rawaccept);
264 
265 
266  }
267  else
268  {
269  FailUpgrade(OSHttpStatusCode.ClientErrorForbidden, "Origin Validation Failed");
270  }
271  }
272  else
273  {
274  acceptKey = GenerateAcceptKey(websocketKey);
275  string rawaccept = string.Format(HandshakeAcceptText, acceptKey);
276  SendUpgradeSuccess(rawaccept);
277  }
278  }
279  public IPEndPoint GetRemoteIPEndpoint()
280  {
281  return _request.RemoteIPEndPoint;
282  }
283 
291  private static string GenerateAcceptKey(string key)
292  {
293  if (string.IsNullOrEmpty(key))
294  return string.Empty;
295 
296  string acceptkey = key + WebsocketHandshakeAcceptHashConstant;
297 
298  SHA1 hashobj = SHA1.Create();
299  string ret = Convert.ToBase64String(hashobj.ComputeHash(Encoding.UTF8.GetBytes(acceptkey)));
300  hashobj.Clear();
301 
302  return ret;
303  }
304 
309  private void SendUpgradeSuccess(string pHandshakeResponse)
310  {
311  // Create a new websocket state so we can keep track of data in between network reads.
312  WebSocketState socketState = new WebSocketState() { ReceivedBytes = new List<byte>(), Header = WebsocketFrameHeader.HeaderDefault(), FrameComplete = true};
313 
314  byte[] bhandshakeResponse = Encoding.UTF8.GetBytes(pHandshakeResponse);
315 
316 
317 
318 
319  try
320  {
321  if (_initialMsgTimeout > 0)
322  {
323  _receiveDone.Reset();
324  }
325  // Begin reading the TCP stream before writing the Upgrade success message to the other side of the stream.
326  _networkContext.Stream.BeginRead(_buffer, 0, _bufferLength, OnReceive, socketState);
327 
328  // Write the upgrade handshake success message
329  _networkContext.Stream.Write(bhandshakeResponse, 0, bhandshakeResponse.Length);
330  _networkContext.Stream.Flush();
331  _upgraded = true;
332  UpgradeCompletedDelegate d = OnUpgradeCompleted;
333  if (d != null)
334  d(this, new UpgradeCompletedEventArgs());
335  if (_initialMsgTimeout > 0)
336  {
337  if (!_receiveDone.WaitOne(TimeSpan.FromMilliseconds(_initialMsgTimeout)))
338  Close(string.Empty);
339  }
340  }
341  catch (IOException)
342  {
343  Close(string.Empty);
344  }
345  catch (ObjectDisposedException)
346  {
347  Close(string.Empty);
348  }
349  }
350 
356  private void FailUpgrade(OSHttpStatusCode pCode, string pMessage )
357  {
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();
363 
364  UpgradeFailedDelegate d = OnUpgradeFailed;
365  if (d != null)
366  d(this,new UpgradeFailedEventArgs());
367  }
368 
369 
376  private void OnReceive(IAsyncResult ar)
377  {
378  WebSocketState _socketState = ar.AsyncState as WebSocketState;
379  try
380  {
381  int bytesRead = _networkContext.Stream.EndRead(ar);
382  if (bytesRead == 0)
383  {
384  // Do Disconnect
385  _networkContext.Stream.Dispose();
386  _networkContext = null;
387  return;
388  }
389  _bufferPosition += bytesRead;
390 
391  if (_bufferPosition > _bufferLength)
392  {
393  // Message too big for chunksize.. not sure how this happened...
394  //Close(string.Empty);
395  }
396 
397  int offset = 0;
398  bool headerread = true;
399  int headerforwardposition = 0;
400  while (headerread && offset < bytesRead)
401  {
402  if (_socketState.FrameComplete)
403  {
404  WebsocketFrameHeader pheader = WebsocketFrameHeader.ZeroHeader;
405 
406  headerread = WebSocketReader.TryReadHeader(_buffer, offset, _bufferPosition - offset, out pheader,
407  out headerforwardposition);
408  offset += headerforwardposition;
409 
410  if (headerread)
411  {
412  _socketState.FrameComplete = false;
413  if (pheader.PayloadLen > (ulong) _maxPayloadBytes)
414  {
415  Close("Invalid Payload size");
416 
417  return;
418  }
419  if (pheader.PayloadLen > 0)
420  {
421  if ((int) pheader.PayloadLen > _bufferPosition - offset)
422  {
423  byte[] writebytes = new byte[_bufferPosition - offset];
424 
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; // We need to add the header so that we can unmask it
429  offset += (int) _bufferPosition - offset;
430  }
431  else
432  {
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;
441  }
442  }
443  else
444  {
445  pheader.Mask = 0;
446  _socketState.FrameComplete = true;
447  _socketState.Header = pheader;
448  }
449 
450  if (_socketState.FrameComplete)
451  {
452  ProcessFrame(_socketState);
453  _socketState.Header.SetDefault();
454  _socketState.ReceivedBytes.Clear();
455  _socketState.ExpectedBytes = 0;
456 
457  }
458  }
459  }
460  else
461  {
462  WebsocketFrameHeader frameHeader = _socketState.Header;
463  int bytesleft = _socketState.ExpectedBytes - _socketState.ReceivedBytes.Count;
464 
465  if (bytesleft > _bufferPosition)
466  {
467  byte[] writebytes = new byte[_bufferPosition];
468 
469  Buffer.BlockCopy(_buffer, offset, writebytes, 0, (int) _bufferPosition);
470  _socketState.ReceivedBytes.AddRange(writebytes);
471  _socketState.Header = frameHeader; // We need to add the header so that we can unmask it
472  offset += (int) _bufferPosition;
473  }
474  else
475  {
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;
482  }
483  if (_socketState.FrameComplete)
484  {
485  ProcessFrame(_socketState);
486  _socketState.Header.SetDefault();
487  _socketState.ReceivedBytes.Clear();
488  _socketState.ExpectedBytes = 0;
489  // do some processing
490  }
491  }
492  }
493  if (offset > 0)
494  {
495  // If the buffer is maxed out.. we can just move the cursor. Nothing to move to the beginning.
496  if (offset <_buffer.Length)
497  Buffer.BlockCopy(_buffer, offset, _buffer, 0, _bufferPosition - offset);
498  _bufferPosition -= offset;
499  }
500  if (_networkContext.Stream != null && _networkContext.Stream.CanRead && !_closing)
501  {
502  _networkContext.Stream.BeginRead(_buffer, _bufferPosition, _bufferLength - _bufferPosition, OnReceive,
503  _socketState);
504  }
505  else
506  {
507  // We can't read the stream anymore...
508  }
509  }
510  catch (IOException)
511  {
512  Close(string.Empty);
513  }
514  catch (ObjectDisposedException)
515  {
516  Close(string.Empty);
517  }
518  }
519 
524  public void SendMessage(string message)
525  {
526  if (_initialMsgTimeout > 0)
527  {
528  _receiveDone.Set();
529  _initialMsgTimeout = 0;
530  }
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());
536 
537  }
538 
539  public void SendData(byte[] data)
540  {
541  if (_initialMsgTimeout > 0)
542  {
543  _receiveDone.Set();
544  _initialMsgTimeout = 0;
545  }
546  WebSocketFrame dataMessageFrame = new WebSocketFrame() { Header = WebsocketFrameHeader.HeaderDefault(), WebSocketPayload = data};
547  dataMessageFrame.Header.IsEnd = true;
548  dataMessageFrame.Header.Opcode = WebSocketReader.OpCode.Binary;
549  SendSocket(dataMessageFrame.ToBytes());
550 
551  }
552 
557  private void SendSocket(byte[] data)
558  {
559  if (!_closing)
560  {
561  try
562  {
563 
564  _networkContext.Stream.Write(data, 0, data.Length);
565  }
566  catch (IOException)
567  {
568 
569  }
570  }
571  }
572 
576  public void SendPingCheck()
577  {
578  if (_initialMsgTimeout > 0)
579  {
580  _receiveDone.Set();
581  _initialMsgTimeout = 0;
582  }
583  WebSocketFrame pingFrame = new WebSocketFrame() { Header = WebsocketFrameHeader.HeaderDefault(), WebSocketPayload = new byte[0] };
584  pingFrame.Header.Opcode = WebSocketReader.OpCode.Ping;
585  pingFrame.Header.IsEnd = true;
586  _pingtime = Util.EnvironmentTickCount();
587  SendSocket(pingFrame.ToBytes());
588  }
589 
594  public void Close(string message)
595  {
596  if (_initialMsgTimeout > 0)
597  {
598  _receiveDone.Set();
599  _initialMsgTimeout = 0;
600  }
601  if (_networkContext == null)
602  return;
603  if (_networkContext.Stream != null)
604  {
605  if (_networkContext.Stream.CanWrite)
606  {
607  byte[] messagedata = Encoding.UTF8.GetBytes(message);
608  WebSocketFrame closeResponseFrame = new WebSocketFrame()
609  {
610  Header = WebsocketFrameHeader.HeaderDefault(),
611  WebSocketPayload = messagedata
612  };
613  closeResponseFrame.Header.Opcode = WebSocketReader.OpCode.Close;
614  closeResponseFrame.Header.PayloadLen = (ulong) messagedata.Length;
615  closeResponseFrame.Header.IsEnd = true;
616  SendSocket(closeResponseFrame.ToBytes());
617  }
618  }
619  CloseDelegate closeD = OnClose;
620  if (closeD != null)
621  {
622  closeD(this, new CloseEventArgs());
623  }
624 
625  _closing = true;
626  }
627 
632  private void ProcessFrame(WebSocketState psocketState)
633  {
634  if (psocketState.Header.IsMasked)
635  {
636  byte[] unmask = psocketState.ReceivedBytes.ToArray();
637  WebSocketReader.Mask(psocketState.Header.Mask, unmask);
638  psocketState.ReceivedBytes = new List<byte>(unmask);
639  }
640  if (psocketState.Header.Opcode != WebSocketReader.OpCode.Continue && _initialMsgTimeout > 0)
641  {
642  _receiveDone.Set();
643  _initialMsgTimeout = 0;
644  }
645  switch (psocketState.Header.Opcode)
646  {
647  case WebSocketReader.OpCode.Ping:
648  PingDelegate pingD = OnPing;
649  if (pingD != null)
650  {
651  pingD(this, new PingEventArgs());
652  }
653 
654  WebSocketFrame pongFrame = new WebSocketFrame(){Header = WebsocketFrameHeader.HeaderDefault(),WebSocketPayload = new byte[0]};
655  pongFrame.Header.Opcode = WebSocketReader.OpCode.Pong;
656  pongFrame.Header.IsEnd = true;
657  SendSocket(pongFrame.ToBytes());
658  break;
659  case WebSocketReader.OpCode.Pong:
660 
661  PongDelegate pongD = OnPong;
662  if (pongD != null)
663  {
664  pongD(this, new PongEventArgs(){PingResponseMS = Util.EnvironmentTickCountSubtract(Util.EnvironmentTickCount(),_pingtime)});
665  }
666  break;
667  case WebSocketReader.OpCode.Binary:
668  if (!psocketState.Header.IsEnd) // Not done, so we need to store this and wait for the end frame.
669  {
670  psocketState.ContinuationFrame = new WebSocketFrame
671  {
672  Header = psocketState.Header,
673  WebSocketPayload =
674  psocketState.ReceivedBytes.ToArray()
675  };
676  }
677  else
678  {
679  // Send Done Event!
680  DataDelegate dataD = OnData;
681  if (dataD != null)
682  {
683  dataD(this,new WebsocketDataEventArgs(){Data = psocketState.ReceivedBytes.ToArray()});
684  }
685  }
686  break;
687  case WebSocketReader.OpCode.Text:
688  if (!psocketState.Header.IsEnd) // Not done, so we need to store this and wait for the end frame.
689  {
690  psocketState.ContinuationFrame = new WebSocketFrame
691  {
692  Header = psocketState.Header,
693  WebSocketPayload =
694  psocketState.ReceivedBytes.ToArray()
695  };
696  }
697  else
698  {
699  TextDelegate textD = OnText;
700  if (textD != null)
701  {
702  textD(this, new WebsocketTextEventArgs() { Data = Encoding.UTF8.GetString(psocketState.ReceivedBytes.ToArray()) });
703  }
704 
705  // Send Done Event!
706  }
707  break;
708  case WebSocketReader.OpCode.Continue: // Continuation. Multiple frames worth of data for one message. Only valid when not using Control Opcodes
709  //Console.WriteLine("currhead " + psocketState.Header.IsEnd);
710  //Console.WriteLine("Continuation! " + psocketState.ContinuationFrame.Header.IsEnd);
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)
719  {
720  if (psocketState.ContinuationFrame.Header.Opcode == WebSocketReader.OpCode.Text)
721  {
722  // Send Done event
723  TextDelegate textD = OnText;
724  if (textD != null)
725  {
726  textD(this, new WebsocketTextEventArgs() { Data = Encoding.UTF8.GetString(combineddata) });
727  }
728  }
729  else if (psocketState.ContinuationFrame.Header.Opcode == WebSocketReader.OpCode.Binary)
730  {
731  // Send Done event
732  DataDelegate dataD = OnData;
733  if (dataD != null)
734  {
735  dataD(this, new WebsocketDataEventArgs() { Data = combineddata });
736  }
737  }
738  else
739  {
740  // protocol violation
741  }
742  psocketState.ContinuationFrame = null;
743  }
744  break;
745  case WebSocketReader.OpCode.Close:
746  Close(string.Empty);
747 
748  break;
749 
750  }
751  psocketState.Header.SetDefault();
752  psocketState.ReceivedBytes.Clear();
753  psocketState.ExpectedBytes = 0;
754  }
755  public void Dispose()
756  {
757  if (_initialMsgTimeout > 0)
758  {
759  _receiveDone.Set();
760  _initialMsgTimeout = 0;
761  }
762  if (_networkContext != null && _networkContext.Stream != null)
763  {
764  if (_networkContext.Stream.CanWrite)
765  _networkContext.Stream.Flush();
766  _networkContext.Stream.Close();
767  _networkContext.Stream.Dispose();
768  _networkContext.Stream = null;
769  }
770 
771  if (_request != null && _request.InputStream != null)
772  {
773  _request.InputStream.Close();
774  _request.InputStream.Dispose();
775  _request = null;
776  }
777 
778  if (_clientContext != null)
779  {
780  _clientContext.Close();
781  _clientContext = null;
782  }
783  }
784  }
785 
789  public class WebSocketReader
790  {
794  private const byte EndBit = 0x80;
795 
799  public enum OpCode
800  {
801  // Data Opcodes
802  Continue = 0x0,
803  Text = 0x1,
804  Binary = 0x2,
805 
806  // Control flow Opcodes
807  Close = 0x8,
808  Ping = 0x9,
809  Pong = 0xA
810  }
811 
819  public static void Mask(int pMask, byte[] pBuffer)
820  {
821  byte[] maskKey = BitConverter.GetBytes(pMask);
822  int currentMaskIndex = 0;
823  for (int i = 0; i < pBuffer.Length; i++)
824  {
825  pBuffer[i] = (byte)(pBuffer[i] ^ maskKey[currentMaskIndex]);
826  if (currentMaskIndex == 3)
827  {
828  currentMaskIndex = 0;
829  }
830  else
831  {
832  currentMaskIndex++;
833 
834  }
835 
836  }
837  }
838 
850  public static bool TryReadHeader(byte[] pBuffer, int pOffset, int length, out WebsocketFrameHeader oHeader,
851  out int moveBuffer)
852  {
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)
858  {
859  moveBuffer = 0;
860  return false;
861  }
862 
863  byte nibble1 = (byte)(pBuffer[pOffset] & 0xF0); //FIN/RSV1/RSV2/RSV3
864  byte nibble2 = (byte)(pBuffer[pOffset] & 0x0F); // Opcode block
865 
866  oHeader = new WebsocketFrameHeader();
867  oHeader.SetDefault();
868 
869  if ((nibble1 & WebSocketReader.EndBit) == WebSocketReader.EndBit)
870  {
871  oHeader.IsEnd = true;
872  }
873  else
874  {
875  oHeader.IsEnd = false;
876  }
877  //Opcode
878  oHeader.Opcode = (WebSocketReader.OpCode)nibble2;
879  //Mask
880  oHeader.IsMasked = Convert.ToBoolean((pBuffer[pOffset + 1] & 0x80) >> 7);
881 
882  // Payload length
883  oHeader.PayloadLen = (byte)(pBuffer[pOffset + 1] & 0x7F);
884 
885  int index = 2; // LargerPayload length starts at byte 3
886 
887  switch (oHeader.PayloadLen)
888  {
889  case 126:
890  minumheadersize += 2;
891  if (length < minumheadersize)
892  {
893  moveBuffer = 0;
894  return false;
895  }
896  Array.Reverse(pBuffer, pOffset + index, 2); // two bytes
897  oHeader.PayloadLen = BitConverter.ToUInt16(pBuffer, pOffset + index);
898  index += 2;
899  break;
900  case 127: // we got more this is a bigger frame
901  // 8 bytes - uint64 - most significant bit 0 network byte order
902  minumheadersize += 8;
903  if (length < minumheadersize)
904  {
905  moveBuffer = 0;
906  return false;
907  }
908  Array.Reverse(pBuffer, pOffset + index, 8);
909  oHeader.PayloadLen = BitConverter.ToUInt64(pBuffer, pOffset + index);
910  index += 8;
911  break;
912 
913  }
914  //oHeader.PayloadLeft = oHeader.PayloadLen; // Start the count in case it's chunked over the network. This is different then frame fragmentation
915  if (oHeader.IsMasked)
916  {
917  minumheadersize += 4;
918  if (length < minumheadersize)
919  {
920  moveBuffer = 0;
921  return false;
922  }
923  oHeader.Mask = BitConverter.ToInt32(pBuffer, pOffset + index);
924  index += 4;
925  }
926  moveBuffer = index;
927  return true;
928 
929  }
930  }
931 
935  public class WebSocketFrame
936  {
937  /*
938  * RFC6455
939 nib 0 1 2 3 4 5 6 7
940 byt 0 1 2 3
941 dec 0 1 2 3
942  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
943  +-+-+-+-+-------+-+-------------+-------------------------------+
944  |F|R|R|R| opcode|M| Payload len | Extended payload length |
945  |I|S|S|S| (4) |A| (7) | (16/64) +
946  |N|V|V|V| |S| | (if payload len==126/127) |
947  | |1|2|3| |K| | +
948  +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
949  | Extended payload length continued, if payload len == 127 |
950  + - - - - - - - - - - - - - - - +-------------------------------+
951  | |Masking-key, if MASK set to 1 |
952  +-------------------------------+-------------------------------+
953  | Masking-key (continued) | Payload Data |
954  +-------------------------------- - - - - - - - - - - - - - - - +
955  : Payload Data continued ... :
956  + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
957  | Payload Data continued ... |
958  +---------------------------------------------------------------+
959 
960  * When reading these, the frames are possibly fragmented and interleaved with control frames
961  * the fragmented frames are not interleaved with data frames. Just control frames
962  */
963  public static readonly WebSocketFrame DefaultFrame = new WebSocketFrame(){Header = new WebsocketFrameHeader(),WebSocketPayload = new byte[0]};
965  public byte[] WebSocketPayload;
966 
967  public byte[] ToBytes()
968  {
969  Header.PayloadLen = (ulong)WebSocketPayload.Length;
970  return Header.ToBytes(WebSocketPayload);
971  }
972 
973  }
974 
975  public struct WebsocketFrameHeader
976  {
977  //public byte CurrentMaskIndex;
981  public bool IsEnd;
982 
986  public bool IsMasked;
987 
991  public int Mask;
992  /*
993 byt 0 1 2 3
994  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
995  +---------------+---------------+---------------+---------------+
996  | Octal 1 | Octal 2 | Octal 3 | Octal 4 |
997  +---------------+---------------+---------------+---------------+
998 */
999 
1000 
1002 
1003  public UInt64 PayloadLen;
1004  //public UInt64 PayloadLeft;
1005  // Payload is X + Y
1006  //public UInt64 ExtensionDataLength;
1007  //public UInt64 ApplicationDataLength;
1008  public static readonly WebsocketFrameHeader ZeroHeader = WebsocketFrameHeader.HeaderDefault();
1009 
1010  public void SetDefault()
1011  {
1012 
1013  //CurrentMaskIndex = 0;
1014  IsEnd = true;
1015  IsMasked = true;
1016  Mask = 0;
1017  Opcode = WebSocketReader.OpCode.Close;
1018  // PayloadLeft = 0;
1019  PayloadLen = 0;
1020  // ExtensionDataLength = 0;
1021  // ApplicationDataLength = 0;
1022 
1023  }
1024 
1031  public byte[] ToBytes(byte[] payload)
1032  {
1033  List<byte> result = new List<byte>();
1034 
1035  // Squeeze in our opcode and our ending bit.
1036  result.Add((byte)((byte)Opcode | (IsEnd?0x80:0x00) ));
1037 
1038  // Again with the three different byte interpretations of size..
1039 
1040  //bytesize
1041  if (PayloadLen <= 125)
1042  {
1043  result.Add((byte) PayloadLen);
1044  } //Uint16
1045  else if (PayloadLen <= ushort.MaxValue)
1046  {
1047  result.Add(126);
1048  byte[] payloadLengthByte = BitConverter.GetBytes(Convert.ToUInt16(PayloadLen));
1049  Array.Reverse(payloadLengthByte);
1050  result.AddRange(payloadLengthByte);
1051  } //UInt64
1052  else
1053  {
1054  result.Add(127);
1055  byte[] payloadLengthByte = BitConverter.GetBytes(PayloadLen);
1056  Array.Reverse(payloadLengthByte);
1057  result.AddRange(payloadLengthByte);
1058  }
1059 
1060  // Only add a payload if it's not null
1061  if (payload != null)
1062  {
1063  result.AddRange(payload);
1064  }
1065  return result.ToArray();
1066  }
1067 
1072 
1074  {
1075  return new WebsocketFrameHeader
1076  {
1077  //CurrentMaskIndex = 0,
1078  IsEnd = false,
1079  IsMasked = true,
1080  Mask = 0,
1081  Opcode = WebSocketReader.OpCode.Close,
1082  //PayloadLeft = 0,
1083  PayloadLen = 0,
1084  // ExtensionDataLength = 0,
1085  // ApplicationDataLength = 0
1086  };
1087  }
1088  }
1089 
1090  public delegate void DataDelegate(object sender, WebsocketDataEventArgs data);
1091 
1092  public delegate void TextDelegate(object sender, WebsocketTextEventArgs text);
1093 
1094  public delegate void PingDelegate(object sender, PingEventArgs pingdata);
1095 
1096  public delegate void PongDelegate(object sender, PongEventArgs pongdata);
1097 
1098  public delegate void RegularHttpRequestDelegate(object sender, RegularHttpRequestEvnetArgs request);
1099 
1100  public delegate void UpgradeCompletedDelegate(object sender, UpgradeCompletedEventArgs completeddata);
1101 
1102  public delegate void UpgradeFailedDelegate(object sender, UpgradeFailedEventArgs faileddata);
1103 
1104  public delegate void CloseDelegate(object sender, CloseEventArgs closedata);
1105 
1106  public delegate bool ValidateHandshake(string pWebOrigin, string pWebSocketKey, string pHost);
1107 
1108 
1109  public class WebsocketDataEventArgs : EventArgs
1110  {
1111  public byte[] Data;
1112  }
1113 
1114  public class WebsocketTextEventArgs : EventArgs
1115  {
1116  public string Data;
1117  }
1118 
1119  public class PingEventArgs : EventArgs
1120  {
1124  public byte[] Data;
1125  }
1126 
1127  public class PongEventArgs : EventArgs
1128  {
1132  public byte[] Data;
1133 
1134  public int PingResponseMS;
1135 
1136  }
1137 
1138  public class RegularHttpRequestEvnetArgs : EventArgs
1139  {
1140 
1141  }
1142 
1143  public class UpgradeCompletedEventArgs : EventArgs
1144  {
1145 
1146  }
1147 
1148  public class UpgradeFailedEventArgs : EventArgs
1149  {
1150 
1151  }
1152 
1153  public class CloseEventArgs : EventArgs
1154  {
1155 
1156  }
1157 
1158 
1159 }
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
bool IsEnd
The last frame in a sequence of fragmented frames or the one and only frame for this message...
This class implements websockets. It grabs the network context from C::Webserver and utilizes it dire...
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
int Mask
A set of cryptologically sound random bytes XoR-ed against the payload octally. Looped ...
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
Definition: ICM_Api.cs:31
delegate void PingDelegate(object sender, PingEventArgs pingdata)
static WebsocketFrameHeader HeaderDefault()
A Helper method to define the defaults
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.
bool IsMasked
Returns whether the payload data is masked or not. Data from Clients MUST be masked, Data from Servers MUST NOT be masked
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)
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
byte[] ToBytes(byte[] payload)
Returns a byte array representing the Frame header
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