aboutsummaryrefslogtreecommitdiff
path: root/SocketHttpListener/WebSocketFrame.cs
diff options
context:
space:
mode:
Diffstat (limited to 'SocketHttpListener/WebSocketFrame.cs')
-rw-r--r--SocketHttpListener/WebSocketFrame.cs578
1 files changed, 578 insertions, 0 deletions
diff --git a/SocketHttpListener/WebSocketFrame.cs b/SocketHttpListener/WebSocketFrame.cs
new file mode 100644
index 000000000..44fa4a5dc
--- /dev/null
+++ b/SocketHttpListener/WebSocketFrame.cs
@@ -0,0 +1,578 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+namespace SocketHttpListener
+{
+ internal class WebSocketFrame : IEnumerable<byte>
+ {
+ #region Private Fields
+
+ private byte[] _extPayloadLength;
+ private Fin _fin;
+ private Mask _mask;
+ private byte[] _maskingKey;
+ private Opcode _opcode;
+ private PayloadData _payloadData;
+ private byte _payloadLength;
+ private Rsv _rsv1;
+ private Rsv _rsv2;
+ private Rsv _rsv3;
+
+ #endregion
+
+ #region Internal Fields
+
+ internal static readonly byte[] EmptyUnmaskPingData;
+
+ #endregion
+
+ #region Static Constructor
+
+ static WebSocketFrame()
+ {
+ EmptyUnmaskPingData = CreatePingFrame(Mask.Unmask).ToByteArray();
+ }
+
+ #endregion
+
+ #region Private Constructors
+
+ private WebSocketFrame()
+ {
+ }
+
+ #endregion
+
+ #region Internal Constructors
+
+ internal WebSocketFrame(Opcode opcode, PayloadData payload)
+ : this(Fin.Final, opcode, Mask.Mask, payload, false)
+ {
+ }
+
+ internal WebSocketFrame(Opcode opcode, Mask mask, PayloadData payload)
+ : this(Fin.Final, opcode, mask, payload, false)
+ {
+ }
+
+ internal WebSocketFrame(Fin fin, Opcode opcode, Mask mask, PayloadData payload)
+ : this(fin, opcode, mask, payload, false)
+ {
+ }
+
+ internal WebSocketFrame(
+ Fin fin, Opcode opcode, Mask mask, PayloadData payload, bool compressed)
+ {
+ _fin = fin;
+ _rsv1 = isData(opcode) && compressed ? Rsv.On : Rsv.Off;
+ _rsv2 = Rsv.Off;
+ _rsv3 = Rsv.Off;
+ _opcode = opcode;
+ _mask = mask;
+
+ var len = payload.Length;
+ if (len < 126)
+ {
+ _payloadLength = (byte)len;
+ _extPayloadLength = new byte[0];
+ }
+ else if (len < 0x010000)
+ {
+ _payloadLength = (byte)126;
+ _extPayloadLength = ((ushort)len).ToByteArrayInternally(ByteOrder.Big);
+ }
+ else
+ {
+ _payloadLength = (byte)127;
+ _extPayloadLength = len.ToByteArrayInternally(ByteOrder.Big);
+ }
+
+ if (mask == Mask.Mask)
+ {
+ _maskingKey = createMaskingKey();
+ payload.Mask(_maskingKey);
+ }
+ else
+ {
+ _maskingKey = new byte[0];
+ }
+
+ _payloadData = payload;
+ }
+
+ #endregion
+
+ #region Public Properties
+
+ public byte[] ExtendedPayloadLength
+ {
+ get
+ {
+ return _extPayloadLength;
+ }
+ }
+
+ public Fin Fin
+ {
+ get
+ {
+ return _fin;
+ }
+ }
+
+ public bool IsBinary
+ {
+ get
+ {
+ return _opcode == Opcode.Binary;
+ }
+ }
+
+ public bool IsClose
+ {
+ get
+ {
+ return _opcode == Opcode.Close;
+ }
+ }
+
+ public bool IsCompressed
+ {
+ get
+ {
+ return _rsv1 == Rsv.On;
+ }
+ }
+
+ public bool IsContinuation
+ {
+ get
+ {
+ return _opcode == Opcode.Cont;
+ }
+ }
+
+ public bool IsControl
+ {
+ get
+ {
+ return _opcode == Opcode.Close || _opcode == Opcode.Ping || _opcode == Opcode.Pong;
+ }
+ }
+
+ public bool IsData
+ {
+ get
+ {
+ return _opcode == Opcode.Binary || _opcode == Opcode.Text;
+ }
+ }
+
+ public bool IsFinal
+ {
+ get
+ {
+ return _fin == Fin.Final;
+ }
+ }
+
+ public bool IsFragmented
+ {
+ get
+ {
+ return _fin == Fin.More || _opcode == Opcode.Cont;
+ }
+ }
+
+ public bool IsMasked
+ {
+ get
+ {
+ return _mask == Mask.Mask;
+ }
+ }
+
+ public bool IsPerMessageCompressed
+ {
+ get
+ {
+ return (_opcode == Opcode.Binary || _opcode == Opcode.Text) && _rsv1 == Rsv.On;
+ }
+ }
+
+ public bool IsPing
+ {
+ get
+ {
+ return _opcode == Opcode.Ping;
+ }
+ }
+
+ public bool IsPong
+ {
+ get
+ {
+ return _opcode == Opcode.Pong;
+ }
+ }
+
+ public bool IsText
+ {
+ get
+ {
+ return _opcode == Opcode.Text;
+ }
+ }
+
+ public ulong Length
+ {
+ get
+ {
+ return 2 + (ulong)(_extPayloadLength.Length + _maskingKey.Length) + _payloadData.Length;
+ }
+ }
+
+ public Mask Mask
+ {
+ get
+ {
+ return _mask;
+ }
+ }
+
+ public byte[] MaskingKey
+ {
+ get
+ {
+ return _maskingKey;
+ }
+ }
+
+ public Opcode Opcode
+ {
+ get
+ {
+ return _opcode;
+ }
+ }
+
+ public PayloadData PayloadData
+ {
+ get
+ {
+ return _payloadData;
+ }
+ }
+
+ public byte PayloadLength
+ {
+ get
+ {
+ return _payloadLength;
+ }
+ }
+
+ public Rsv Rsv1
+ {
+ get
+ {
+ return _rsv1;
+ }
+ }
+
+ public Rsv Rsv2
+ {
+ get
+ {
+ return _rsv2;
+ }
+ }
+
+ public Rsv Rsv3
+ {
+ get
+ {
+ return _rsv3;
+ }
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ private byte[] createMaskingKey()
+ {
+ var key = new byte[4];
+ var rand = new Random();
+ rand.NextBytes(key);
+
+ return key;
+ }
+
+ private static bool isControl(Opcode opcode)
+ {
+ return opcode == Opcode.Close || opcode == Opcode.Ping || opcode == Opcode.Pong;
+ }
+
+ private static bool isData(Opcode opcode)
+ {
+ return opcode == Opcode.Text || opcode == Opcode.Binary;
+ }
+
+ private static WebSocketFrame read(byte[] header, Stream stream, bool unmask)
+ {
+ /* Header */
+
+ // FIN
+ var fin = (header[0] & 0x80) == 0x80 ? Fin.Final : Fin.More;
+ // RSV1
+ var rsv1 = (header[0] & 0x40) == 0x40 ? Rsv.On : Rsv.Off;
+ // RSV2
+ var rsv2 = (header[0] & 0x20) == 0x20 ? Rsv.On : Rsv.Off;
+ // RSV3
+ var rsv3 = (header[0] & 0x10) == 0x10 ? Rsv.On : Rsv.Off;
+ // Opcode
+ var opcode = (Opcode)(header[0] & 0x0f);
+ // MASK
+ var mask = (header[1] & 0x80) == 0x80 ? Mask.Mask : Mask.Unmask;
+ // Payload Length
+ var payloadLen = (byte)(header[1] & 0x7f);
+
+ // Check if correct frame.
+ var incorrect = isControl(opcode) && fin == Fin.More
+ ? "A control frame is fragmented."
+ : !isData(opcode) && rsv1 == Rsv.On
+ ? "A non data frame is compressed."
+ : null;
+
+ if (incorrect != null)
+ throw new WebSocketException(CloseStatusCode.IncorrectData, incorrect);
+
+ // Check if consistent frame.
+ if (isControl(opcode) && payloadLen > 125)
+ throw new WebSocketException(
+ CloseStatusCode.InconsistentData,
+ "The length of payload data of a control frame is greater than 125 bytes.");
+
+ var frame = new WebSocketFrame();
+ frame._fin = fin;
+ frame._rsv1 = rsv1;
+ frame._rsv2 = rsv2;
+ frame._rsv3 = rsv3;
+ frame._opcode = opcode;
+ frame._mask = mask;
+ frame._payloadLength = payloadLen;
+
+ /* Extended Payload Length */
+
+ var size = payloadLen < 126
+ ? 0
+ : payloadLen == 126
+ ? 2
+ : 8;
+
+ var extPayloadLen = size > 0 ? stream.ReadBytes(size) : new byte[0];
+ if (size > 0 && extPayloadLen.Length != size)
+ throw new WebSocketException(
+ "The 'Extended Payload Length' of a frame cannot be read from the data source.");
+
+ frame._extPayloadLength = extPayloadLen;
+
+ /* Masking Key */
+
+ var masked = mask == Mask.Mask;
+ var maskingKey = masked ? stream.ReadBytes(4) : new byte[0];
+ if (masked && maskingKey.Length != 4)
+ throw new WebSocketException(
+ "The 'Masking Key' of a frame cannot be read from the data source.");
+
+ frame._maskingKey = maskingKey;
+
+ /* Payload Data */
+
+ ulong len = payloadLen < 126
+ ? payloadLen
+ : payloadLen == 126
+ ? extPayloadLen.ToUInt16(ByteOrder.Big)
+ : extPayloadLen.ToUInt64(ByteOrder.Big);
+
+ byte[] data = null;
+ if (len > 0)
+ {
+ // Check if allowable payload data length.
+ if (payloadLen > 126 && len > PayloadData.MaxLength)
+ throw new WebSocketException(
+ CloseStatusCode.TooBig,
+ "The length of 'Payload Data' of a frame is greater than the allowable length.");
+
+ data = payloadLen > 126
+ ? stream.ReadBytes((long)len, 1024)
+ : stream.ReadBytes((int)len);
+
+ //if (data.LongLength != (long)len)
+ // throw new WebSocketException(
+ // "The 'Payload Data' of a frame cannot be read from the data source.");
+ }
+ else
+ {
+ data = new byte[0];
+ }
+
+ var payload = new PayloadData(data, masked);
+ if (masked && unmask)
+ {
+ payload.Mask(maskingKey);
+ frame._mask = Mask.Unmask;
+ frame._maskingKey = new byte[0];
+ }
+
+ frame._payloadData = payload;
+ return frame;
+ }
+
+ #endregion
+
+ #region Internal Methods
+
+ internal static WebSocketFrame CreateCloseFrame(Mask mask, byte[] data)
+ {
+ return new WebSocketFrame(Opcode.Close, mask, new PayloadData(data));
+ }
+
+ internal static WebSocketFrame CreateCloseFrame(Mask mask, PayloadData payload)
+ {
+ return new WebSocketFrame(Opcode.Close, mask, payload);
+ }
+
+ internal static WebSocketFrame CreateCloseFrame(Mask mask, CloseStatusCode code, string reason)
+ {
+ return new WebSocketFrame(
+ Opcode.Close, mask, new PayloadData(((ushort)code).Append(reason)));
+ }
+
+ internal static WebSocketFrame CreatePingFrame(Mask mask)
+ {
+ return new WebSocketFrame(Opcode.Ping, mask, new PayloadData());
+ }
+
+ internal static WebSocketFrame CreatePingFrame(Mask mask, byte[] data)
+ {
+ return new WebSocketFrame(Opcode.Ping, mask, new PayloadData(data));
+ }
+
+ internal static WebSocketFrame CreatePongFrame(Mask mask, PayloadData payload)
+ {
+ return new WebSocketFrame(Opcode.Pong, mask, payload);
+ }
+
+ internal static WebSocketFrame CreateWebSocketFrame(
+ Fin fin, Opcode opcode, Mask mask, byte[] data, bool compressed)
+ {
+ return new WebSocketFrame(fin, opcode, mask, new PayloadData(data), compressed);
+ }
+
+ internal static WebSocketFrame Read(Stream stream)
+ {
+ return Read(stream, true);
+ }
+
+ internal static WebSocketFrame Read(Stream stream, bool unmask)
+ {
+ var header = stream.ReadBytes(2);
+ if (header.Length != 2)
+ throw new WebSocketException(
+ "The header part of a frame cannot be read from the data source.");
+
+ return read(header, stream, unmask);
+ }
+
+ internal static async void ReadAsync(
+ Stream stream, bool unmask, Action<WebSocketFrame> completed, Action<Exception> error)
+ {
+ try
+ {
+ var header = await stream.ReadBytesAsync(2).ConfigureAwait(false);
+ if (header.Length != 2)
+ throw new WebSocketException(
+ "The header part of a frame cannot be read from the data source.");
+
+ var frame = read(header, stream, unmask);
+ if (completed != null)
+ completed(frame);
+ }
+ catch (Exception ex)
+ {
+ if (error != null)
+ {
+ error(ex);
+ }
+ }
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ public IEnumerator<byte> GetEnumerator()
+ {
+ foreach (var b in ToByteArray())
+ yield return b;
+ }
+
+ public void Print(bool dumped)
+ {
+ //Console.WriteLine(dumped ? dump(this) : print(this));
+ }
+
+ public byte[] ToByteArray()
+ {
+ using (var buff = new MemoryStream())
+ {
+ var header = (int)_fin;
+ header = (header << 1) + (int)_rsv1;
+ header = (header << 1) + (int)_rsv2;
+ header = (header << 1) + (int)_rsv3;
+ header = (header << 4) + (int)_opcode;
+ header = (header << 1) + (int)_mask;
+ header = (header << 7) + (int)_payloadLength;
+ buff.Write(((ushort)header).ToByteArrayInternally(ByteOrder.Big), 0, 2);
+
+ if (_payloadLength > 125)
+ buff.Write(_extPayloadLength, 0, _extPayloadLength.Length);
+
+ if (_mask == Mask.Mask)
+ buff.Write(_maskingKey, 0, _maskingKey.Length);
+
+ if (_payloadLength > 0)
+ {
+ var payload = _payloadData.ToByteArray();
+ if (_payloadLength < 127)
+ buff.Write(payload, 0, payload.Length);
+ else
+ buff.WriteBytes(payload);
+ }
+
+ return buff.ToArray();
+ }
+ }
+
+ public override string ToString()
+ {
+ return BitConverter.ToString(ToByteArray());
+ }
+
+ #endregion
+
+ #region Explicitly Implemented Interface Members
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ #endregion
+ }
+} \ No newline at end of file