jellyfin/SocketHttpListener/WebSocketFrame.cs

433 lines
13 KiB
C#
Raw Normal View History

2016-11-11 19:55:12 +00:00
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
2019-02-09 12:41:09 +00:00
using System.Threading.Tasks;
2016-11-11 19:55:12 +00:00
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 => _extPayloadLength;
2016-11-11 19:55:12 +00:00
public Fin Fin => _fin;
2016-11-11 19:55:12 +00:00
public bool IsBinary => _opcode == Opcode.Binary;
2016-11-11 19:55:12 +00:00
public bool IsClose => _opcode == Opcode.Close;
2016-11-11 19:55:12 +00:00
public bool IsCompressed => _rsv1 == Rsv.On;
2016-11-11 19:55:12 +00:00
public bool IsContinuation => _opcode == Opcode.Cont;
2016-11-11 19:55:12 +00:00
public bool IsControl => _opcode == Opcode.Close || _opcode == Opcode.Ping || _opcode == Opcode.Pong;
2016-11-11 19:55:12 +00:00
public bool IsData => _opcode == Opcode.Binary || _opcode == Opcode.Text;
2016-11-11 19:55:12 +00:00
public bool IsFinal => _fin == Fin.Final;
2016-11-11 19:55:12 +00:00
public bool IsFragmented => _fin == Fin.More || _opcode == Opcode.Cont;
2016-11-11 19:55:12 +00:00
public bool IsMasked => _mask == Mask.Mask;
2016-11-11 19:55:12 +00:00
public bool IsPerMessageCompressed => (_opcode == Opcode.Binary || _opcode == Opcode.Text) && _rsv1 == Rsv.On;
2016-11-11 19:55:12 +00:00
public bool IsPing => _opcode == Opcode.Ping;
2016-11-11 19:55:12 +00:00
public bool IsPong => _opcode == Opcode.Pong;
2016-11-11 19:55:12 +00:00
public bool IsText => _opcode == Opcode.Text;
2016-11-11 19:55:12 +00:00
public ulong Length => 2 + (ulong)(_extPayloadLength.Length + _maskingKey.Length) + _payloadData.Length;
2016-11-11 19:55:12 +00:00
public Mask Mask => _mask;
2016-11-11 19:55:12 +00:00
public byte[] MaskingKey => _maskingKey;
2016-11-11 19:55:12 +00:00
public Opcode Opcode => _opcode;
2016-11-11 19:55:12 +00:00
public PayloadData PayloadData => _payloadData;
2016-11-11 19:55:12 +00:00
public byte PayloadLength => _payloadLength;
2016-11-11 19:55:12 +00:00
public Rsv Rsv1 => _rsv1;
2016-11-11 19:55:12 +00:00
public Rsv Rsv2 => _rsv2;
2016-11-11 19:55:12 +00:00
public Rsv Rsv3 => _rsv3;
2016-11-11 19:55:12 +00:00
#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;
}
2019-02-09 12:41:09 +00:00
private static async Task<WebSocketFrame> ReadAsync(byte[] header, Stream stream, bool unmask)
2016-11-11 19:55:12 +00:00
{
/* 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;
2019-02-09 12:41:09 +00:00
var extPayloadLen = size > 0 ? await stream.ReadBytesAsync(size).ConfigureAwait(false) : Array.Empty<byte>();
2016-11-11 19:55:12 +00:00
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;
2019-02-09 12:41:09 +00:00
var maskingKey = masked ? await stream.ReadBytesAsync(4).ConfigureAwait(false) : Array.Empty<byte>();
2016-11-11 19:55:12 +00:00
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
2019-02-09 12:41:09 +00:00
? await stream.ReadBytesAsync((long)len, 1024).ConfigureAwait(false)
: await stream.ReadBytesAsync((int)len).ConfigureAwait(false);
2016-11-11 19:55:12 +00:00
//if (data.LongLength != (long)len)
// throw new WebSocketException(
// "The 'Payload Data' of a frame cannot be read from the data source.");
}
else
{
2019-02-09 12:41:09 +00:00
data = Array.Empty<byte>();
2016-11-11 19:55:12 +00:00
}
var payload = new PayloadData(data, masked);
if (masked && unmask)
{
payload.Mask(maskingKey);
frame._mask = Mask.Unmask;
2019-02-09 12:41:09 +00:00
frame._maskingKey = Array.Empty<byte>();
2016-11-11 19:55:12 +00:00
}
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);
}
2019-02-09 14:39:17 +00:00
internal static async Task<WebSocketFrame> CreateCloseFrameAsync(Mask mask, CloseStatusCode code, string reason)
2016-11-11 19:55:12 +00:00
{
return new WebSocketFrame(
2019-02-09 14:39:17 +00:00
Opcode.Close, mask, new PayloadData(await ((ushort)code).AppendAsync(reason).ConfigureAwait(false)));
2016-11-11 19:55:12 +00:00
}
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);
}
2019-02-09 12:41:09 +00:00
internal static Task<WebSocketFrame> ReadAsync(Stream stream)
=> ReadAsync(stream, true);
2016-11-11 19:55:12 +00:00
2019-02-09 12:41:09 +00:00
internal static async Task<WebSocketFrame> ReadAsync(Stream stream, bool unmask)
2016-11-11 19:55:12 +00:00
{
2019-02-09 12:41:09 +00:00
var header = await stream.ReadBytesAsync(2).ConfigureAwait(false);
2016-11-11 19:55:12 +00:00
if (header.Length != 2)
2019-02-09 12:41:09 +00:00
{
2016-11-11 19:55:12 +00:00
throw new WebSocketException(
"The header part of a frame cannot be read from the data source.");
2019-02-09 12:41:09 +00:00
}
2016-11-11 19:55:12 +00:00
2019-02-09 12:41:09 +00:00
return await ReadAsync(header, stream, unmask).ConfigureAwait(false);
2016-11-11 19:55:12 +00:00
}
2019-02-09 12:41:09 +00:00
internal static async Task ReadAsync(
2016-11-11 19:55:12 +00:00
Stream stream, bool unmask, Action<WebSocketFrame> completed, Action<Exception> error)
{
try
{
var header = await stream.ReadBytesAsync(2).ConfigureAwait(false);
if (header.Length != 2)
2019-02-09 12:41:09 +00:00
{
2016-11-11 19:55:12 +00:00
throw new WebSocketException(
"The header part of a frame cannot be read from the data source.");
2019-02-09 12:41:09 +00:00
}
2016-11-11 19:55:12 +00:00
2019-02-09 12:41:09 +00:00
var frame = await ReadAsync(header, stream, unmask).ConfigureAwait(false);
completed?.Invoke(frame);
2016-11-11 19:55:12 +00:00
}
catch (Exception ex)
{
2019-02-09 12:41:09 +00:00
error.Invoke(ex);
2016-11-11 19:55:12 +00:00
}
}
#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
}
}