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.cs1159
1 files changed, 1159 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..c2925e3
--- /dev/null
+++ b/OpenSim/Framework/Servers/HttpServer/WebsocketServerHandler.cs
@@ -0,0 +1,1159 @@
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.Net;
32using System.Security.Cryptography;
33using System.Text;
34using System.Threading;
35using HttpServer;
36
37namespace OpenSim.Framework.Servers.HttpServer
38{
39 // Sealed class. If you're going to unseal it, implement IDisposable.
40 /// <summary>
41 /// This class implements websockets. It grabs the network context from C#Webserver and utilizes it directly as a tcp streaming service
42 /// </summary>
43 public sealed class WebSocketHttpServerHandler : BaseRequestHandler
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
55 /// <summary>
56 /// Binary Data will trigger this event
57 /// </summary>
58 public event DataDelegate OnData;
59
60 /// <summary>
61 /// Textual Data will trigger this event
62 /// </summary>
63 public event TextDelegate OnText;
64
65 /// <summary>
66 /// A ping request form the other side will trigger this event.
67 /// This class responds to the ping automatically. You shouldn't send a pong.
68 /// it's informational.
69 /// </summary>
70 public event PingDelegate OnPing;
71
72 /// <summary>
73 /// This is a response to a ping you sent.
74 /// </summary>
75 public event PongDelegate OnPong;
76
77 /// <summary>
78 /// This is a regular HTTP Request... This may be removed in the future.
79 /// </summary>
80// public event RegularHttpRequestDelegate OnRegularHttpRequest;
81
82 /// <summary>
83 /// When the upgrade from a HTTP request to a Websocket is completed, this will be fired
84 /// </summary>
85 public event UpgradeCompletedDelegate OnUpgradeCompleted;
86
87 /// <summary>
88 /// If the upgrade failed, this will be fired
89 /// </summary>
90 public event UpgradeFailedDelegate OnUpgradeFailed;
91
92 /// <summary>
93 /// When the websocket is closed, this will be fired.
94 /// </summary>
95 public event CloseDelegate OnClose;
96
97 /// <summary>
98 /// Set this delegate to allow your module to validate the origin of the
99 /// Websocket request. Primary line of defense against cross site scripting
100 /// </summary>
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
130 /// <summary>
131 /// Mysterious constant defined in RFC6455 to append to the client provided security key
132 /// </summary>
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.
147 ~WebSocketHttpServerHandler()
148 {
149 Dispose();
150
151 }
152
153 /// <summary>
154 /// Sets the length of the stream buffer
155 /// </summary>
156 /// <param name="pChunk">Byte length.</param>
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
169 /// <summary>
170 /// This is the famous nagle.
171 /// </summary>
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
196 /// <summary>
197 /// This triggers the websocket to start the upgrade process...
198 /// This is a Generalized Networking 'common sense' helper method. Some people expect to call Start() instead
199 /// of the more context appropriate HandshakeAndUpgrade()
200 /// </summary>
201 public void Start()
202 {
203 HandshakeAndUpgrade();
204 }
205
206 /// <summary>
207 /// Max Payload Size in bytes. Defaults to 40MB, but could be set upon connection before calling handshake and upgrade.
208 /// </summary>
209 public int MaxPayloadSize
210 {
211 get { return _maxPayloadBytes; }
212 set { _maxPayloadBytes = value; }
213 }
214
215 /// <summary>
216 /// 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
217 /// 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.
218 /// </summary>
219 public int InitialMsgTimeout
220 {
221 get { return _initialMsgTimeout; }
222 set { _initialMsgTimeout = value; }
223 }
224
225 /// <summary>
226 /// This triggers the websocket start the upgrade process
227 /// </summary>
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
284 /// <summary>
285 /// Generates a handshake response key string based on the client's
286 /// provided key to prove to the client that we're allowing the Websocket
287 /// upgrade of our own free will and we were not coerced into doing it.
288 /// </summary>
289 /// <param name="key">Client provided security key</param>
290 /// <returns></returns>
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
305 /// <summary>
306 /// Informs the otherside that we accepted their upgrade request
307 /// </summary>
308 /// <param name="pHandshakeResponse">The HTTP 1.1 101 response that says Yay \o/ </param>
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
351 /// <summary>
352 /// The server has decided not to allow the upgrade to a websocket for some reason. The Http 1.1 response that says Nay >:(
353 /// </summary>
354 /// <param name="pCode">HTTP Status reflecting the reason why</param>
355 /// <param name="pMessage">Textual reason for the upgrade fail</param>
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
370 /// <summary>
371 /// This is our ugly Async OnReceive event handler.
372 /// This chunks the input stream based on the length of the provided buffer and processes out
373 /// as many frames as it can. It then moves the unprocessed data to the beginning of the buffer.
374 /// </summary>
375 /// <param name="ar">Our Async State from beginread</param>
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
520 /// <summary>
521 /// Sends a string to the other side
522 /// </summary>
523 /// <param name="message">the string message that is to be sent</param>
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
553 /// <summary>
554 /// Writes raw bytes to the websocket. Unframed data will cause disconnection
555 /// </summary>
556 /// <param name="data"></param>
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
573 /// <summary>
574 /// 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.
575 /// </summary>
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
590 /// <summary>
591 /// Closes the websocket connection. Sends a close message to the other side if it hasn't already done so.
592 /// </summary>
593 /// <param name="message"></param>
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
628 /// <summary>
629 /// Processes a websocket frame and triggers consumer events
630 /// </summary>
631 /// <param name="psocketState">We need to modify the websocket state here depending on the frame</param>
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
786 /// <summary>
787 /// Reads a byte stream and returns Websocket frames.
788 /// </summary>
789 public class WebSocketReader
790 {
791 /// <summary>
792 /// Bit to determine if the frame read on the stream is the last frame in a sequence of fragmented frames
793 /// </summary>
794 private const byte EndBit = 0x80;
795
796 /// <summary>
797 /// These are the Frame Opcodes
798 /// </summary>
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
812 /// <summary>
813 /// Masks and Unmasks data using the frame mask. Mask is applied per octal
814 /// Note: Frames from clients MUST be masked
815 /// Note: Frames from servers MUST NOT be masked
816 /// </summary>
817 /// <param name="pMask">Int representing 32 bytes of mask data. Mask is applied per octal</param>
818 /// <param name="pBuffer"></param>
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
839 /// <summary>
840 /// Attempts to read a header off the provided buffer. Returns true, exports a WebSocketFrameheader,
841 /// and an int to move the buffer forward when it reads a header. False when it can't read a header
842 /// </summary>
843 /// <param name="pBuffer">Bytes read from the stream</param>
844 /// <param name="pOffset">Starting place in the stream to begin trying to read from</param>
845 /// <param name="length">Lenth in the stream to try and read from. Provided for cases where the
846 /// buffer's length is larger then the data in it</param>
847 /// <param name="oHeader">Outputs the read WebSocket frame header</param>
848 /// <param name="moveBuffer">Informs the calling stream to move the buffer forward</param>
849 /// <returns>True if it got a header, False if it didn't get a header</returns>
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
932 /// <summary>
933 /// RFC6455 Websocket Frame
934 /// </summary>
935 public class WebSocketFrame
936 {
937 /*
938 * RFC6455
939nib 0 1 2 3 4 5 6 7
940byt 0 1 2 3
941dec 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]};
964 public WebsocketFrameHeader Header;
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;
978 /// <summary>
979 /// The last frame in a sequence of fragmented frames or the one and only frame for this message.
980 /// </summary>
981 public bool IsEnd;
982
983 /// <summary>
984 /// Returns whether the payload data is masked or not. Data from Clients MUST be masked, Data from Servers MUST NOT be masked
985 /// </summary>
986 public bool IsMasked;
987
988 /// <summary>
989 /// A set of cryptologically sound random bytes XoR-ed against the payload octally. Looped
990 /// </summary>
991 public int Mask;
992 /*
993byt 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
1001 public WebSocketReader.OpCode Opcode;
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
1025 /// <summary>
1026 /// Returns a byte array representing the Frame header
1027 /// </summary>
1028 /// <param name="payload">This is the frame data payload. The header describes the size of the payload.
1029 /// If payload is null, a Zero sized payload is assumed</param>
1030 /// <returns>Returns a byte array representing the frame header</returns>
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
1068 /// <summary>
1069 /// A Helper method to define the defaults
1070 /// </summary>
1071 /// <returns></returns>
1072
1073 public static WebsocketFrameHeader HeaderDefault()
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 {
1121 /// <summary>
1122 /// The ping event can arbitrarily contain data
1123 /// </summary>
1124 public byte[] Data;
1125 }
1126
1127 public class PongEventArgs : EventArgs
1128 {
1129 /// <summary>
1130 /// The pong event can arbitrarily contain data
1131 /// </summary>
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}