aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Framework/Servers/HttpServer/WebsocketServerHandler.cs
diff options
context:
space:
mode:
Diffstat (limited to 'OpenSim/Framework/Servers/HttpServer/WebsocketServerHandler.cs')
-rw-r--r--OpenSim/Framework/Servers/HttpServer/WebsocketServerHandler.cs1102
1 files changed, 1102 insertions, 0 deletions
diff --git a/OpenSim/Framework/Servers/HttpServer/WebsocketServerHandler.cs b/OpenSim/Framework/Servers/HttpServer/WebsocketServerHandler.cs
new file mode 100644
index 0000000..ee96b47
--- /dev/null
+++ b/OpenSim/Framework/Servers/HttpServer/WebsocketServerHandler.cs
@@ -0,0 +1,1102 @@
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
28using System;
29using System.Collections.Generic;
30using System.IO;
31using System.Security.Cryptography;
32using System.Text;
33using HttpServer;
34
35namespace OpenSim.Framework.Servers.HttpServer
36{
37 // Sealed class. If you're going to unseal it, implement IDisposable.
38 /// <summary>
39 /// This class implements websockets. It grabs the network context from C#Webserver and utilizes it directly as a tcp streaming service
40 /// </summary>
41 public sealed class WebSocketHttpServerHandler : BaseRequestHandler
42 {
43
44 private class WebSocketState
45 {
46 public List<byte> ReceivedBytes;
47 public int ExpectedBytes;
48 public WebsocketFrameHeader Header;
49 public bool FrameComplete;
50 public WebSocketFrame ContinuationFrame;
51 }
52
53 /// <summary>
54 /// Binary Data will trigger this event
55 /// </summary>
56 public event DataDelegate OnData;
57
58 /// <summary>
59 /// Textual Data will trigger this event
60 /// </summary>
61 public event TextDelegate OnText;
62
63 /// <summary>
64 /// A ping request form the other side will trigger this event.
65 /// This class responds to the ping automatically. You shouldn't send a pong.
66 /// it's informational.
67 /// </summary>
68 public event PingDelegate OnPing;
69
70 /// <summary>
71 /// This is a response to a ping you sent.
72 /// </summary>
73 public event PongDelegate OnPong;
74
75 /// <summary>
76 /// This is a regular HTTP Request... This may be removed in the future.
77 /// </summary>
78 public event RegularHttpRequestDelegate OnRegularHttpRequest;
79
80 /// <summary>
81 /// When the upgrade from a HTTP request to a Websocket is completed, this will be fired
82 /// </summary>
83 public event UpgradeCompletedDelegate OnUpgradeCompleted;
84
85 /// <summary>
86 /// If the upgrade failed, this will be fired
87 /// </summary>
88 public event UpgradeFailedDelegate OnUpgradeFailed;
89
90 /// <summary>
91 /// When the websocket is closed, this will be fired.
92 /// </summary>
93 public event CloseDelegate OnClose;
94
95 /// <summary>
96 /// Set this delegate to allow your module to validate the origin of the
97 /// Websocket request. Primary line of defense against cross site scripting
98 /// </summary>
99 public ValidateHandshake HandshakeValidateMethodOverride = null;
100
101 private OSHttpRequest _request;
102 private HTTPNetworkContext _networkContext;
103 private IHttpClientContext _clientContext;
104
105 private int _pingtime = 0;
106 private byte[] _buffer;
107 private int _bufferPosition;
108 private int _bufferLength;
109 private bool _closing;
110 private bool _upgraded;
111 private int _maxPayloadBytes = 41943040;
112
113 private const string HandshakeAcceptText =
114 "HTTP/1.1 101 Switching Protocols\r\n" +
115 "upgrade: websocket\r\n" +
116 "Connection: Upgrade\r\n" +
117 "sec-websocket-accept: {0}\r\n\r\n";// +
118 //"{1}";
119
120 private const string HandshakeDeclineText =
121 "HTTP/1.1 {0} {1}\r\n" +
122 "Connection: close\r\n\r\n";
123
124 /// <summary>
125 /// Mysterious constant defined in RFC6455 to append to the client provided security key
126 /// </summary>
127 private const string WebsocketHandshakeAcceptHashConstant = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
128
129 public WebSocketHttpServerHandler(OSHttpRequest preq, IHttpClientContext pContext, int bufferlen)
130 : base(preq.HttpMethod, preq.Url.OriginalString)
131 {
132 _request = preq;
133 _networkContext = pContext.GiveMeTheNetworkStreamIKnowWhatImDoing();
134 _clientContext = pContext;
135 _bufferLength = bufferlen;
136 _buffer = new byte[_bufferLength];
137 }
138
139 // Sealed class implments destructor and an internal dispose method. complies with C# unmanaged resource best practices.
140 ~WebSocketHttpServerHandler()
141 {
142 Dispose();
143
144 }
145
146 /// <summary>
147 /// Sets the length of the stream buffer
148 /// </summary>
149 /// <param name="pChunk">Byte length.</param>
150 public void SetChunksize(int pChunk)
151 {
152 if (!_upgraded)
153 {
154 _buffer = new byte[pChunk];
155 }
156 else
157 {
158 throw new InvalidOperationException("You must set the chunksize before the connection is upgraded");
159 }
160 }
161
162 /// <summary>
163 /// This is the famous nagle.
164 /// </summary>
165 public bool NoDelay_TCP_Nagle
166 {
167 get
168 {
169 if (_networkContext != null && _networkContext.Socket != null)
170 {
171 return _networkContext.Socket.NoDelay;
172 }
173 else
174 {
175 throw new InvalidOperationException("The socket has been shutdown");
176 }
177 }
178 set
179 {
180 if (_networkContext != null && _networkContext.Socket != null)
181 _networkContext.Socket.NoDelay = value;
182 else
183 {
184 throw new InvalidOperationException("The socket has been shutdown");
185 }
186 }
187 }
188
189 /// <summary>
190 /// This triggers the websocket to start the upgrade process...
191 /// This is a Generalized Networking 'common sense' helper method. Some people expect to call Start() instead
192 /// of the more context appropriate HandshakeAndUpgrade()
193 /// </summary>
194 public void Start()
195 {
196 HandshakeAndUpgrade();
197 }
198
199 /// <summary>
200 /// Max Payload Size in bytes. Defaults to 40MB, but could be set upon connection before calling handshake and upgrade.
201 /// </summary>
202 public int MaxPayloadSize
203 {
204 get { return _maxPayloadBytes; }
205 set { _maxPayloadBytes = value; }
206 }
207
208 /// <summary>
209 /// This triggers the websocket start the upgrade process
210 /// </summary>
211 public void HandshakeAndUpgrade()
212 {
213 string webOrigin = string.Empty;
214 string websocketKey = string.Empty;
215 string acceptKey = string.Empty;
216 string accepthost = string.Empty;
217 if (!string.IsNullOrEmpty(_request.Headers["origin"]))
218 webOrigin = _request.Headers["origin"];
219
220 if (!string.IsNullOrEmpty(_request.Headers["sec-websocket-key"]))
221 websocketKey = _request.Headers["sec-websocket-key"];
222
223 if (!string.IsNullOrEmpty(_request.Headers["host"]))
224 accepthost = _request.Headers["host"];
225
226 if (string.IsNullOrEmpty(_request.Headers["upgrade"]))
227 {
228 FailUpgrade(OSHttpStatusCode.ClientErrorBadRequest, "no upgrade request submitted");
229 }
230
231 string connectionheader = _request.Headers["upgrade"];
232 if (connectionheader.ToLower() != "websocket")
233 {
234 FailUpgrade(OSHttpStatusCode.ClientErrorBadRequest, "no connection upgrade request submitted");
235 }
236
237 // If the object consumer provided a method to validate the origin, we should call it and give the client a success or fail.
238 // If not.. we should accept any. The assumption here is that there would be no Websocket handlers registered in baseHTTPServer unless
239 // Something asked for it...
240 if (HandshakeValidateMethodOverride != null)
241 {
242 if (HandshakeValidateMethodOverride(webOrigin, websocketKey, accepthost))
243 {
244 acceptKey = GenerateAcceptKey(websocketKey);
245 string rawaccept = string.Format(HandshakeAcceptText, acceptKey);
246 SendUpgradeSuccess(rawaccept);
247
248 }
249 else
250 {
251 FailUpgrade(OSHttpStatusCode.ClientErrorForbidden, "Origin Validation Failed");
252 }
253 }
254 else
255 {
256 acceptKey = GenerateAcceptKey(websocketKey);
257 string rawaccept = string.Format(HandshakeAcceptText, acceptKey);
258 SendUpgradeSuccess(rawaccept);
259 }
260 }
261
262 /// <summary>
263 /// Generates a handshake response key string based on the client's
264 /// provided key to prove to the client that we're allowing the Websocket
265 /// upgrade of our own free will and we were not coerced into doing it.
266 /// </summary>
267 /// <param name="key">Client provided security key</param>
268 /// <returns></returns>
269 private static string GenerateAcceptKey(string key)
270 {
271 if (string.IsNullOrEmpty(key))
272 return string.Empty;
273
274 string acceptkey = key + WebsocketHandshakeAcceptHashConstant;
275
276 SHA1 hashobj = SHA1.Create();
277 string ret = Convert.ToBase64String(hashobj.ComputeHash(Encoding.UTF8.GetBytes(acceptkey)));
278 hashobj.Clear();
279
280 return ret;
281 }
282
283 /// <summary>
284 /// Informs the otherside that we accepted their upgrade request
285 /// </summary>
286 /// <param name="pHandshakeResponse">The HTTP 1.1 101 response that says Yay \o/ </param>
287 private void SendUpgradeSuccess(string pHandshakeResponse)
288 {
289 // Create a new websocket state so we can keep track of data in between network reads.
290 WebSocketState socketState = new WebSocketState() { ReceivedBytes = new List<byte>(), Header = WebsocketFrameHeader.HeaderDefault(), FrameComplete = true};
291
292 byte[] bhandshakeResponse = Encoding.UTF8.GetBytes(pHandshakeResponse);
293 try
294 {
295
296 // Begin reading the TCP stream before writing the Upgrade success message to the other side of the stream.
297 _networkContext.Stream.BeginRead(_buffer, 0, _bufferLength, OnReceive, socketState);
298
299 // Write the upgrade handshake success message
300 _networkContext.Stream.Write(bhandshakeResponse, 0, bhandshakeResponse.Length);
301 _networkContext.Stream.Flush();
302 _upgraded = true;
303 UpgradeCompletedDelegate d = OnUpgradeCompleted;
304 if (d != null)
305 d(this, new UpgradeCompletedEventArgs());
306 }
307 catch (IOException fail)
308 {
309 Close(string.Empty);
310 }
311 catch (ObjectDisposedException fail)
312 {
313 Close(string.Empty);
314 }
315
316 }
317
318 /// <summary>
319 /// The server has decided not to allow the upgrade to a websocket for some reason. The Http 1.1 response that says Nay >:(
320 /// </summary>
321 /// <param name="pCode">HTTP Status reflecting the reason why</param>
322 /// <param name="pMessage">Textual reason for the upgrade fail</param>
323 private void FailUpgrade(OSHttpStatusCode pCode, string pMessage )
324 {
325 string handshakeResponse = string.Format(HandshakeDeclineText, (int)pCode, pMessage.Replace("\n", string.Empty).Replace("\r", string.Empty));
326 byte[] bhandshakeResponse = Encoding.UTF8.GetBytes(handshakeResponse);
327 _networkContext.Stream.Write(bhandshakeResponse, 0, bhandshakeResponse.Length);
328 _networkContext.Stream.Flush();
329 _networkContext.Stream.Dispose();
330
331 UpgradeFailedDelegate d = OnUpgradeFailed;
332 if (d != null)
333 d(this,new UpgradeFailedEventArgs());
334 }
335
336
337 /// <summary>
338 /// This is our ugly Async OnReceive event handler.
339 /// This chunks the input stream based on the length of the provided buffer and processes out
340 /// as many frames as it can. It then moves the unprocessed data to the beginning of the buffer.
341 /// </summary>
342 /// <param name="ar">Our Async State from beginread</param>
343 private void OnReceive(IAsyncResult ar)
344 {
345 WebSocketState _socketState = ar.AsyncState as WebSocketState;
346 try
347 {
348 int bytesRead = _networkContext.Stream.EndRead(ar);
349 if (bytesRead == 0)
350 {
351 // Do Disconnect
352 _networkContext.Stream.Dispose();
353 _networkContext = null;
354 return;
355 }
356 _bufferPosition += bytesRead;
357
358 if (_bufferPosition > _bufferLength)
359 {
360 // Message too big for chunksize.. not sure how this happened...
361 //Close(string.Empty);
362 }
363
364 int offset = 0;
365 bool headerread = true;
366 int headerforwardposition = 0;
367 while (headerread && offset < bytesRead)
368 {
369 if (_socketState.FrameComplete)
370 {
371 WebsocketFrameHeader pheader = WebsocketFrameHeader.ZeroHeader;
372
373 headerread = WebSocketReader.TryReadHeader(_buffer, offset, _bufferPosition - offset, out pheader,
374 out headerforwardposition);
375 offset += headerforwardposition;
376
377 if (headerread)
378 {
379 _socketState.FrameComplete = false;
380 if (pheader.PayloadLen > (ulong) _maxPayloadBytes)
381 {
382 Close("Invalid Payload size");
383
384 return;
385 }
386 if (pheader.PayloadLen > 0)
387 {
388 if ((int) pheader.PayloadLen > _bufferPosition - offset)
389 {
390 byte[] writebytes = new byte[_bufferPosition - offset];
391
392 Buffer.BlockCopy(_buffer, offset, writebytes, 0, (int) _bufferPosition - offset);
393 _socketState.ExpectedBytes = (int) pheader.PayloadLen;
394 _socketState.ReceivedBytes.AddRange(writebytes);
395 _socketState.Header = pheader; // We need to add the header so that we can unmask it
396 offset += (int) _bufferPosition - offset;
397 }
398 else
399 {
400 byte[] writebytes = new byte[pheader.PayloadLen];
401 Buffer.BlockCopy(_buffer, offset, writebytes, 0, (int) pheader.PayloadLen);
402 WebSocketReader.Mask(pheader.Mask, writebytes);
403 pheader.IsMasked = false;
404 _socketState.FrameComplete = true;
405 _socketState.ReceivedBytes.AddRange(writebytes);
406 _socketState.Header = pheader;
407 offset += (int) pheader.PayloadLen;
408 }
409 }
410 else
411 {
412 pheader.Mask = 0;
413 _socketState.FrameComplete = true;
414 _socketState.Header = pheader;
415 }
416
417
418
419 if (_socketState.FrameComplete)
420 {
421 ProcessFrame(_socketState);
422 _socketState.Header.SetDefault();
423 _socketState.ReceivedBytes.Clear();
424 _socketState.ExpectedBytes = 0;
425
426 }
427
428 }
429 }
430 else
431 {
432 WebsocketFrameHeader frameHeader = _socketState.Header;
433 int bytesleft = _socketState.ExpectedBytes - _socketState.ReceivedBytes.Count;
434
435 if (bytesleft > _bufferPosition)
436 {
437 byte[] writebytes = new byte[_bufferPosition];
438
439 Buffer.BlockCopy(_buffer, offset, writebytes, 0, (int) _bufferPosition);
440 _socketState.ReceivedBytes.AddRange(writebytes);
441 _socketState.Header = frameHeader; // We need to add the header so that we can unmask it
442 offset += (int) _bufferPosition;
443 }
444 else
445 {
446 byte[] writebytes = new byte[_bufferPosition];
447 Buffer.BlockCopy(_buffer, offset, writebytes, 0, (int) _bufferPosition);
448 _socketState.FrameComplete = true;
449 _socketState.ReceivedBytes.AddRange(writebytes);
450 _socketState.Header = frameHeader;
451 offset += (int) _bufferPosition;
452 }
453 if (_socketState.FrameComplete)
454 {
455 ProcessFrame(_socketState);
456 _socketState.Header.SetDefault();
457 _socketState.ReceivedBytes.Clear();
458 _socketState.ExpectedBytes = 0;
459 // do some processing
460 }
461
462 }
463 }
464 if (offset > 0)
465 {
466 // If the buffer is maxed out.. we can just move the cursor. Nothing to move to the beginning.
467 if (offset <_buffer.Length)
468 Buffer.BlockCopy(_buffer, offset, _buffer, 0, _bufferPosition - offset);
469 _bufferPosition -= offset;
470 }
471 if (_networkContext.Stream != null && _networkContext.Stream.CanRead && !_closing)
472 {
473 _networkContext.Stream.BeginRead(_buffer, _bufferPosition, _bufferLength - _bufferPosition, OnReceive,
474 _socketState);
475 }
476 else
477 {
478 // We can't read the stream anymore...
479 }
480
481 }
482 catch (IOException fail)
483 {
484 Close(string.Empty);
485 }
486 catch (ObjectDisposedException fail)
487 {
488 Close(string.Empty);
489 }
490 }
491
492 /// <summary>
493 /// Sends a string to the other side
494 /// </summary>
495 /// <param name="message">the string message that is to be sent</param>
496 public void SendMessage(string message)
497 {
498 byte[] messagedata = Encoding.UTF8.GetBytes(message);
499 WebSocketFrame textMessageFrame = new WebSocketFrame() { Header = WebsocketFrameHeader.HeaderDefault(), WebSocketPayload = messagedata };
500 textMessageFrame.Header.Opcode = WebSocketReader.OpCode.Text;
501 textMessageFrame.Header.IsEnd = true;
502 SendSocket(textMessageFrame.ToBytes());
503
504 }
505
506 public void SendData(byte[] data)
507 {
508 WebSocketFrame dataMessageFrame = new WebSocketFrame() { Header = WebsocketFrameHeader.HeaderDefault(), WebSocketPayload = data};
509 dataMessageFrame.Header.IsEnd = true;
510 dataMessageFrame.Header.Opcode = WebSocketReader.OpCode.Binary;
511 SendSocket(dataMessageFrame.ToBytes());
512
513 }
514
515 /// <summary>
516 /// Writes raw bytes to the websocket. Unframed data will cause disconnection
517 /// </summary>
518 /// <param name="data"></param>
519 private void SendSocket(byte[] data)
520 {
521 if (!_closing)
522 {
523 try
524 {
525
526 _networkContext.Stream.Write(data, 0, data.Length);
527 }
528 catch (IOException)
529 {
530
531 }
532 }
533 }
534
535 /// <summary>
536 /// 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.
537 /// </summary>
538 public void SendPingCheck()
539 {
540 WebSocketFrame pingFrame = new WebSocketFrame() { Header = WebsocketFrameHeader.HeaderDefault(), WebSocketPayload = new byte[0] };
541 pingFrame.Header.Opcode = WebSocketReader.OpCode.Ping;
542 pingFrame.Header.IsEnd = true;
543 _pingtime = Util.EnvironmentTickCount();
544 SendSocket(pingFrame.ToBytes());
545 }
546
547 /// <summary>
548 /// Closes the websocket connection. Sends a close message to the other side if it hasn't already done so.
549 /// </summary>
550 /// <param name="message"></param>
551 public void Close(string message)
552 {
553 if (_networkContext == null)
554 return;
555 if (_networkContext.Stream != null)
556 {
557 if (_networkContext.Stream.CanWrite)
558 {
559 byte[] messagedata = Encoding.UTF8.GetBytes(message);
560 WebSocketFrame closeResponseFrame = new WebSocketFrame()
561 {
562 Header = WebsocketFrameHeader.HeaderDefault(),
563 WebSocketPayload = messagedata
564 };
565 closeResponseFrame.Header.Opcode = WebSocketReader.OpCode.Close;
566 closeResponseFrame.Header.PayloadLen = (ulong) messagedata.Length;
567 closeResponseFrame.Header.IsEnd = true;
568 SendSocket(closeResponseFrame.ToBytes());
569 }
570 }
571 CloseDelegate closeD = OnClose;
572 if (closeD != null)
573 {
574 closeD(this, new CloseEventArgs());
575 }
576
577 _closing = true;
578 }
579
580 /// <summary>
581 /// Processes a websocket frame and triggers consumer events
582 /// </summary>
583 /// <param name="psocketState">We need to modify the websocket state here depending on the frame</param>
584 private void ProcessFrame(WebSocketState psocketState)
585 {
586 if (psocketState.Header.IsMasked)
587 {
588 byte[] unmask = psocketState.ReceivedBytes.ToArray();
589 WebSocketReader.Mask(psocketState.Header.Mask, unmask);
590 psocketState.ReceivedBytes = new List<byte>(unmask);
591 }
592
593 switch (psocketState.Header.Opcode)
594 {
595 case WebSocketReader.OpCode.Ping:
596 PingDelegate pingD = OnPing;
597 if (pingD != null)
598 {
599 pingD(this, new PingEventArgs());
600 }
601
602 WebSocketFrame pongFrame = new WebSocketFrame(){Header = WebsocketFrameHeader.HeaderDefault(),WebSocketPayload = new byte[0]};
603 pongFrame.Header.Opcode = WebSocketReader.OpCode.Pong;
604 pongFrame.Header.IsEnd = true;
605 SendSocket(pongFrame.ToBytes());
606 break;
607 case WebSocketReader.OpCode.Pong:
608
609 PongDelegate pongD = OnPong;
610 if (pongD != null)
611 {
612 pongD(this, new PongEventArgs(){PingResponseMS = Util.EnvironmentTickCountSubtract(Util.EnvironmentTickCount(),_pingtime)});
613 }
614 break;
615 case WebSocketReader.OpCode.Binary:
616 if (!psocketState.Header.IsEnd) // Not done, so we need to store this and wait for the end frame.
617 {
618 psocketState.ContinuationFrame = new WebSocketFrame
619 {
620 Header = psocketState.Header,
621 WebSocketPayload =
622 psocketState.ReceivedBytes.ToArray()
623 };
624 }
625 else
626 {
627 // Send Done Event!
628 DataDelegate dataD = OnData;
629 if (dataD != null)
630 {
631 dataD(this,new WebsocketDataEventArgs(){Data = psocketState.ReceivedBytes.ToArray()});
632 }
633 }
634 break;
635 case WebSocketReader.OpCode.Text:
636 if (!psocketState.Header.IsEnd) // Not done, so we need to store this and wait for the end frame.
637 {
638 psocketState.ContinuationFrame = new WebSocketFrame
639 {
640 Header = psocketState.Header,
641 WebSocketPayload =
642 psocketState.ReceivedBytes.ToArray()
643 };
644 }
645 else
646 {
647 TextDelegate textD = OnText;
648 if (textD != null)
649 {
650 textD(this, new WebsocketTextEventArgs() { Data = Encoding.UTF8.GetString(psocketState.ReceivedBytes.ToArray()) });
651 }
652
653 // Send Done Event!
654 }
655 break;
656 case WebSocketReader.OpCode.Continue: // Continuation. Multiple frames worth of data for one message. Only valid when not using Control Opcodes
657 //Console.WriteLine("currhead " + psocketState.Header.IsEnd);
658 //Console.WriteLine("Continuation! " + psocketState.ContinuationFrame.Header.IsEnd);
659 byte[] combineddata = new byte[psocketState.ReceivedBytes.Count+psocketState.ContinuationFrame.WebSocketPayload.Length];
660 byte[] newdata = psocketState.ReceivedBytes.ToArray();
661 Buffer.BlockCopy(psocketState.ContinuationFrame.WebSocketPayload, 0, combineddata, 0, psocketState.ContinuationFrame.WebSocketPayload.Length);
662 Buffer.BlockCopy(newdata, 0, combineddata,
663 psocketState.ContinuationFrame.WebSocketPayload.Length, newdata.Length);
664 psocketState.ContinuationFrame.WebSocketPayload = combineddata;
665 psocketState.Header.PayloadLen = (ulong)combineddata.Length;
666 if (psocketState.Header.IsEnd)
667 {
668 if (psocketState.ContinuationFrame.Header.Opcode == WebSocketReader.OpCode.Text)
669 {
670 // Send Done event
671 TextDelegate textD = OnText;
672 if (textD != null)
673 {
674 textD(this, new WebsocketTextEventArgs() { Data = Encoding.UTF8.GetString(combineddata) });
675 }
676 }
677 else if (psocketState.ContinuationFrame.Header.Opcode == WebSocketReader.OpCode.Binary)
678 {
679 // Send Done event
680 DataDelegate dataD = OnData;
681 if (dataD != null)
682 {
683 dataD(this, new WebsocketDataEventArgs() { Data = combineddata });
684 }
685 }
686 else
687 {
688 // protocol violation
689 }
690 psocketState.ContinuationFrame = null;
691 }
692 break;
693 case WebSocketReader.OpCode.Close:
694 Close(string.Empty);
695
696 break;
697
698 }
699 psocketState.Header.SetDefault();
700 psocketState.ReceivedBytes.Clear();
701 psocketState.ExpectedBytes = 0;
702 }
703 public void Dispose()
704 {
705 if (_networkContext != null && _networkContext.Stream != null)
706 {
707 if (_networkContext.Stream.CanWrite)
708 _networkContext.Stream.Flush();
709 _networkContext.Stream.Close();
710 _networkContext.Stream.Dispose();
711 _networkContext.Stream = null;
712 }
713
714 if (_request != null && _request.InputStream != null)
715 {
716 _request.InputStream.Close();
717 _request.InputStream.Dispose();
718 _request = null;
719 }
720
721 if (_clientContext != null)
722 {
723 _clientContext.Close();
724 _clientContext = null;
725 }
726 }
727 }
728
729 /// <summary>
730 /// Reads a byte stream and returns Websocket frames.
731 /// </summary>
732 public class WebSocketReader
733 {
734 /// <summary>
735 /// Bit to determine if the frame read on the stream is the last frame in a sequence of fragmented frames
736 /// </summary>
737 private const byte EndBit = 0x80;
738
739 /// <summary>
740 /// These are the Frame Opcodes
741 /// </summary>
742 public enum OpCode
743 {
744 // Data Opcodes
745 Continue = 0x0,
746 Text = 0x1,
747 Binary = 0x2,
748
749 // Control flow Opcodes
750 Close = 0x8,
751 Ping = 0x9,
752 Pong = 0xA
753 }
754
755 /// <summary>
756 /// Masks and Unmasks data using the frame mask. Mask is applied per octal
757 /// Note: Frames from clients MUST be masked
758 /// Note: Frames from servers MUST NOT be masked
759 /// </summary>
760 /// <param name="pMask">Int representing 32 bytes of mask data. Mask is applied per octal</param>
761 /// <param name="pBuffer"></param>
762 public static void Mask(int pMask, byte[] pBuffer)
763 {
764 byte[] maskKey = BitConverter.GetBytes(pMask);
765 int currentMaskIndex = 0;
766 for (int i = 0; i < pBuffer.Length; i++)
767 {
768 pBuffer[i] = (byte)(pBuffer[i] ^ maskKey[currentMaskIndex]);
769 if (currentMaskIndex == 3)
770 {
771 currentMaskIndex = 0;
772 }
773 else
774 {
775 currentMaskIndex++;
776
777 }
778
779 }
780 }
781
782 /// <summary>
783 /// Attempts to read a header off the provided buffer. Returns true, exports a WebSocketFrameheader,
784 /// and an int to move the buffer forward when it reads a header. False when it can't read a header
785 /// </summary>
786 /// <param name="pBuffer">Bytes read from the stream</param>
787 /// <param name="pOffset">Starting place in the stream to begin trying to read from</param>
788 /// <param name="length">Lenth in the stream to try and read from. Provided for cases where the
789 /// buffer's length is larger then the data in it</param>
790 /// <param name="oHeader">Outputs the read WebSocket frame header</param>
791 /// <param name="moveBuffer">Informs the calling stream to move the buffer forward</param>
792 /// <returns>True if it got a header, False if it didn't get a header</returns>
793 public static bool TryReadHeader(byte[] pBuffer, int pOffset, int length, out WebsocketFrameHeader oHeader,
794 out int moveBuffer)
795 {
796 oHeader = WebsocketFrameHeader.ZeroHeader;
797 int minumheadersize = 2;
798 if (length > pBuffer.Length - pOffset)
799 throw new ArgumentOutOfRangeException("The Length specified was larger the byte array supplied");
800 if (length < minumheadersize)
801 {
802 moveBuffer = 0;
803 return false;
804 }
805
806 byte nibble1 = (byte)(pBuffer[pOffset] & 0xF0); //FIN/RSV1/RSV2/RSV3
807 byte nibble2 = (byte)(pBuffer[pOffset] & 0x0F); // Opcode block
808
809 oHeader = new WebsocketFrameHeader();
810 oHeader.SetDefault();
811
812 if ((nibble1 & WebSocketReader.EndBit) == WebSocketReader.EndBit)
813 {
814 oHeader.IsEnd = true;
815 }
816 else
817 {
818 oHeader.IsEnd = false;
819 }
820 //Opcode
821 oHeader.Opcode = (WebSocketReader.OpCode)nibble2;
822 //Mask
823 oHeader.IsMasked = Convert.ToBoolean((pBuffer[pOffset + 1] & 0x80) >> 7);
824
825 // Payload length
826 oHeader.PayloadLen = (byte)(pBuffer[pOffset + 1] & 0x7F);
827
828 int index = 2; // LargerPayload length starts at byte 3
829
830 switch (oHeader.PayloadLen)
831 {
832 case 126:
833 minumheadersize += 2;
834 if (length < minumheadersize)
835 {
836 moveBuffer = 0;
837 return false;
838 }
839 Array.Reverse(pBuffer, pOffset + index, 2); // two bytes
840 oHeader.PayloadLen = BitConverter.ToUInt16(pBuffer, pOffset + index);
841 index += 2;
842 break;
843 case 127: // we got more this is a bigger frame
844 // 8 bytes - uint64 - most significant bit 0 network byte order
845 minumheadersize += 8;
846 if (length < minumheadersize)
847 {
848 moveBuffer = 0;
849 return false;
850 }
851 Array.Reverse(pBuffer, pOffset + index, 8);
852 oHeader.PayloadLen = BitConverter.ToUInt64(pBuffer, pOffset + index);
853 index += 8;
854 break;
855
856 }
857 //oHeader.PayloadLeft = oHeader.PayloadLen; // Start the count in case it's chunked over the network. This is different then frame fragmentation
858 if (oHeader.IsMasked)
859 {
860 minumheadersize += 4;
861 if (length < minumheadersize)
862 {
863 moveBuffer = 0;
864 return false;
865 }
866 oHeader.Mask = BitConverter.ToInt32(pBuffer, pOffset + index);
867 index += 4;
868 }
869 moveBuffer = index;
870 return true;
871
872 }
873 }
874
875 /// <summary>
876 /// RFC6455 Websocket Frame
877 /// </summary>
878 public class WebSocketFrame
879 {
880 /*
881 * RFC6455
882nib 0 1 2 3 4 5 6 7
883byt 0 1 2 3
884dec 0 1 2 3
885 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
886 +-+-+-+-+-------+-+-------------+-------------------------------+
887 |F|R|R|R| opcode|M| Payload len | Extended payload length |
888 |I|S|S|S| (4) |A| (7) | (16/64) +
889 |N|V|V|V| |S| | (if payload len==126/127) |
890 | |1|2|3| |K| | +
891 +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
892 | Extended payload length continued, if payload len == 127 |
893 + - - - - - - - - - - - - - - - +-------------------------------+
894 | |Masking-key, if MASK set to 1 |
895 +-------------------------------+-------------------------------+
896 | Masking-key (continued) | Payload Data |
897 +-------------------------------- - - - - - - - - - - - - - - - +
898 : Payload Data continued ... :
899 + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
900 | Payload Data continued ... |
901 +---------------------------------------------------------------+
902
903 * When reading these, the frames are possibly fragmented and interleaved with control frames
904 * the fragmented frames are not interleaved with data frames. Just control frames
905 */
906 public static readonly WebSocketFrame DefaultFrame = new WebSocketFrame(){Header = new WebsocketFrameHeader(),WebSocketPayload = new byte[0]};
907 public WebsocketFrameHeader Header;
908 public byte[] WebSocketPayload;
909
910 public byte[] ToBytes()
911 {
912 Header.PayloadLen = (ulong)WebSocketPayload.Length;
913 return Header.ToBytes(WebSocketPayload);
914 }
915
916 }
917
918 public struct WebsocketFrameHeader
919 {
920 //public byte CurrentMaskIndex;
921 /// <summary>
922 /// The last frame in a sequence of fragmented frames or the one and only frame for this message.
923 /// </summary>
924 public bool IsEnd;
925
926 /// <summary>
927 /// Returns whether the payload data is masked or not. Data from Clients MUST be masked, Data from Servers MUST NOT be masked
928 /// </summary>
929 public bool IsMasked;
930
931 /// <summary>
932 /// A set of cryptologically sound random bytes XoR-ed against the payload octally. Looped
933 /// </summary>
934 public int Mask;
935 /*
936byt 0 1 2 3
937 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
938 +---------------+---------------+---------------+---------------+
939 | Octal 1 | Octal 2 | Octal 3 | Octal 4 |
940 +---------------+---------------+---------------+---------------+
941*/
942
943
944 public WebSocketReader.OpCode Opcode;
945
946 public UInt64 PayloadLen;
947 //public UInt64 PayloadLeft;
948 // Payload is X + Y
949 //public UInt64 ExtensionDataLength;
950 //public UInt64 ApplicationDataLength;
951 public static readonly WebsocketFrameHeader ZeroHeader = WebsocketFrameHeader.HeaderDefault();
952
953 public void SetDefault()
954 {
955
956 //CurrentMaskIndex = 0;
957 IsEnd = true;
958 IsMasked = true;
959 Mask = 0;
960 Opcode = WebSocketReader.OpCode.Close;
961 // PayloadLeft = 0;
962 PayloadLen = 0;
963 // ExtensionDataLength = 0;
964 // ApplicationDataLength = 0;
965
966 }
967
968 /// <summary>
969 /// Returns a byte array representing the Frame header
970 /// </summary>
971 /// <param name="payload">This is the frame data payload. The header describes the size of the payload.
972 /// If payload is null, a Zero sized payload is assumed</param>
973 /// <returns>Returns a byte array representing the frame header</returns>
974 public byte[] ToBytes(byte[] payload)
975 {
976 List<byte> result = new List<byte>();
977
978 // Squeeze in our opcode and our ending bit.
979 result.Add((byte)((byte)Opcode | (IsEnd?0x80:0x00) ));
980
981 // Again with the three different byte interpretations of size..
982
983 //bytesize
984 if (PayloadLen <= 125)
985 {
986 result.Add((byte) PayloadLen);
987 } //Uint16
988 else if (PayloadLen <= ushort.MaxValue)
989 {
990 result.Add(126);
991 byte[] payloadLengthByte = BitConverter.GetBytes(Convert.ToUInt16(PayloadLen));
992 Array.Reverse(payloadLengthByte);
993 result.AddRange(payloadLengthByte);
994 } //UInt64
995 else
996 {
997 result.Add(127);
998 byte[] payloadLengthByte = BitConverter.GetBytes(PayloadLen);
999 Array.Reverse(payloadLengthByte);
1000 result.AddRange(payloadLengthByte);
1001 }
1002
1003 // Only add a payload if it's not null
1004 if (payload != null)
1005 {
1006 result.AddRange(payload);
1007 }
1008 return result.ToArray();
1009 }
1010
1011 /// <summary>
1012 /// A Helper method to define the defaults
1013 /// </summary>
1014 /// <returns></returns>
1015
1016 public static WebsocketFrameHeader HeaderDefault()
1017 {
1018 return new WebsocketFrameHeader
1019 {
1020 //CurrentMaskIndex = 0,
1021 IsEnd = false,
1022 IsMasked = true,
1023 Mask = 0,
1024 Opcode = WebSocketReader.OpCode.Close,
1025 //PayloadLeft = 0,
1026 PayloadLen = 0,
1027 // ExtensionDataLength = 0,
1028 // ApplicationDataLength = 0
1029 };
1030 }
1031 }
1032
1033 public delegate void DataDelegate(object sender, WebsocketDataEventArgs data);
1034
1035 public delegate void TextDelegate(object sender, WebsocketTextEventArgs text);
1036
1037 public delegate void PingDelegate(object sender, PingEventArgs pingdata);
1038
1039 public delegate void PongDelegate(object sender, PongEventArgs pongdata);
1040
1041 public delegate void RegularHttpRequestDelegate(object sender, RegularHttpRequestEvnetArgs request);
1042
1043 public delegate void UpgradeCompletedDelegate(object sender, UpgradeCompletedEventArgs completeddata);
1044
1045 public delegate void UpgradeFailedDelegate(object sender, UpgradeFailedEventArgs faileddata);
1046
1047 public delegate void CloseDelegate(object sender, CloseEventArgs closedata);
1048
1049 public delegate bool ValidateHandshake(string pWebOrigin, string pWebSocketKey, string pHost);
1050
1051
1052 public class WebsocketDataEventArgs : EventArgs
1053 {
1054 public byte[] Data;
1055 }
1056
1057 public class WebsocketTextEventArgs : EventArgs
1058 {
1059 public string Data;
1060 }
1061
1062 public class PingEventArgs : EventArgs
1063 {
1064 /// <summary>
1065 /// The ping event can arbitrarily contain data
1066 /// </summary>
1067 public byte[] Data;
1068 }
1069
1070 public class PongEventArgs : EventArgs
1071 {
1072 /// <summary>
1073 /// The pong event can arbitrarily contain data
1074 /// </summary>
1075 public byte[] Data;
1076
1077 public int PingResponseMS;
1078
1079 }
1080
1081 public class RegularHttpRequestEvnetArgs : EventArgs
1082 {
1083
1084 }
1085
1086 public class UpgradeCompletedEventArgs : EventArgs
1087 {
1088
1089 }
1090
1091 public class UpgradeFailedEventArgs : EventArgs
1092 {
1093
1094 }
1095
1096 public class CloseEventArgs : EventArgs
1097 {
1098
1099 }
1100
1101
1102}