up
This commit is contained in:
170
Assets/BestHTTP/WebSocket/Frames/WebSocketFrame.cs
Normal file
170
Assets/BestHTTP/WebSocket/Frames/WebSocketFrame.cs
Normal file
@@ -0,0 +1,170 @@
|
||||
#if !BESTHTTP_DISABLE_WEBSOCKET && (!UNITY_WEBGL || UNITY_EDITOR)
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace BestHTTP.WebSocket.Frames
|
||||
{
|
||||
/// <summary>
|
||||
/// Denotes a binary frame. The "Payload data" is arbitrary binary data whose interpretation is solely up to the application layer.
|
||||
/// This is the base class of all other frame writers, as all frame can be represented as a byte array.
|
||||
/// </summary>
|
||||
public sealed class WebSocketFrame
|
||||
{
|
||||
public static readonly byte[] NoData = new byte[0];
|
||||
|
||||
public WebSocketFrameTypes Type { get; private set; }
|
||||
public bool IsFinal { get; private set; }
|
||||
public byte Header { get; private set; }
|
||||
|
||||
public byte[] Data { get; private set; }
|
||||
public bool UseExtensions { get; private set; }
|
||||
|
||||
#region Constructors
|
||||
|
||||
public WebSocketFrame(WebSocket webSocket, WebSocketFrameTypes type, byte[] data)
|
||||
:this(webSocket, type, data, true)
|
||||
{ }
|
||||
|
||||
public WebSocketFrame(WebSocket webSocket, WebSocketFrameTypes type, byte[] data, bool useExtensions)
|
||||
: this(webSocket, type, data, 0, data != null ? (UInt64)data.Length : 0, true, useExtensions)
|
||||
{
|
||||
}
|
||||
|
||||
public WebSocketFrame(WebSocket webSocket, WebSocketFrameTypes type, byte[] data, bool isFinal, bool useExtensions)
|
||||
: this(webSocket, type, data, 0, data != null ? (UInt64)data.Length : 0, isFinal, useExtensions)
|
||||
{
|
||||
}
|
||||
|
||||
public WebSocketFrame(WebSocket webSocket, WebSocketFrameTypes type, byte[] data, UInt64 pos, UInt64 length, bool isFinal, bool useExtensions)
|
||||
{
|
||||
this.Type = type;
|
||||
this.IsFinal = isFinal;
|
||||
this.UseExtensions = useExtensions;
|
||||
|
||||
if (data != null)
|
||||
{
|
||||
this.Data = new byte[length];
|
||||
Array.Copy(data, (int)pos, this.Data, 0, (int)length);
|
||||
}
|
||||
else
|
||||
data = NoData;
|
||||
|
||||
// First byte: Final Bit + Rsv flags + OpCode
|
||||
byte finalBit = (byte)(IsFinal ? 0x80 : 0x0);
|
||||
this.Header = (byte)(finalBit | (byte)Type);
|
||||
|
||||
if (this.UseExtensions && webSocket != null && webSocket.Extensions != null)
|
||||
{
|
||||
for (int i = 0; i < webSocket.Extensions.Length; ++i)
|
||||
{
|
||||
var ext = webSocket.Extensions[i];
|
||||
if (ext != null)
|
||||
{
|
||||
this.Header |= ext.GetFrameHeader(this, this.Header);
|
||||
this.Data = ext.Encode(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Functions
|
||||
|
||||
public byte[] Get()
|
||||
{
|
||||
if (Data == null)
|
||||
Data = NoData;
|
||||
|
||||
using (var ms = new MemoryStream(this.Data.Length + 9))
|
||||
{
|
||||
// For the complete documentation for this section see:
|
||||
// http://tools.ietf.org/html/rfc6455#section-5.2
|
||||
|
||||
// Write the header
|
||||
ms.WriteByte(this.Header);
|
||||
|
||||
// The length of the "Payload data", in bytes: if 0-125, that is the payload length. If 126, the following 2 bytes interpreted as a
|
||||
// 16-bit unsigned integer are the payload length. If 127, the following 8 bytes interpreted as a 64-bit unsigned integer (the
|
||||
// most significant bit MUST be 0) are the payload length. Multibyte length quantities are expressed in network byte order.
|
||||
if (this.Data.Length < 126)
|
||||
ms.WriteByte((byte)(0x80 | (byte)this.Data.Length));
|
||||
else if (this.Data.Length < UInt16.MaxValue)
|
||||
{
|
||||
ms.WriteByte((byte)(0x80 | 126));
|
||||
byte[] len = BitConverter.GetBytes((UInt16)this.Data.Length);
|
||||
if (BitConverter.IsLittleEndian)
|
||||
Array.Reverse(len, 0, len.Length);
|
||||
|
||||
ms.Write(len, 0, len.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
ms.WriteByte((byte)(0x80 | 127));
|
||||
byte[] len = BitConverter.GetBytes((UInt64)this.Data.Length);
|
||||
if (BitConverter.IsLittleEndian)
|
||||
Array.Reverse(len, 0, len.Length);
|
||||
|
||||
ms.Write(len, 0, len.Length);
|
||||
}
|
||||
|
||||
// All frames sent from the client to the server are masked by a 32-bit value that is contained within the frame. This field is
|
||||
// present if the mask bit is set to 1 and is absent if the mask bit is set to 0.
|
||||
// If the data is being sent by the client, the frame(s) MUST be masked.
|
||||
byte[] mask = BitConverter.GetBytes((Int32)this.GetHashCode());
|
||||
ms.Write(mask, 0, mask.Length);
|
||||
|
||||
// Do the masking.
|
||||
for (int i = 0; i < this.Data.Length; ++i)
|
||||
ms.WriteByte((byte)(Data[i] ^ mask[i % 4]));
|
||||
|
||||
return ms.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public WebSocketFrame[] Fragment(ushort maxFragmentSize)
|
||||
{
|
||||
if (this.Data == null)
|
||||
return null;
|
||||
|
||||
// All control frames MUST have a payload length of 125 bytes or less and MUST NOT be fragmented.
|
||||
if (this.Type != WebSocketFrameTypes.Binary && this.Type != WebSocketFrameTypes.Text)
|
||||
return null;
|
||||
|
||||
if (this.Data.Length <= maxFragmentSize)
|
||||
return null;
|
||||
|
||||
this.IsFinal = false;
|
||||
|
||||
// Clear final bit from the header flags
|
||||
this.Header &= 0x7F;
|
||||
|
||||
// One chunk will remain in this fragment, so we have to allocate one less
|
||||
int count = (this.Data.Length / maxFragmentSize) + (this.Data.Length % maxFragmentSize) - 1;
|
||||
|
||||
WebSocketFrame[] fragments = new WebSocketFrame[count];
|
||||
|
||||
// Skip one chunk, for the current one
|
||||
UInt64 pos = maxFragmentSize;
|
||||
while (pos < (UInt64)this.Data.Length)
|
||||
{
|
||||
UInt64 chunkLength = Math.Min(maxFragmentSize, (UInt64)this.Data.Length - pos);
|
||||
|
||||
fragments[fragments.Length - count--] = new WebSocketFrame(null, WebSocketFrameTypes.Continuation, this.Data, pos, chunkLength, pos + chunkLength >= (UInt64)this.Data.Length, false);
|
||||
|
||||
pos += chunkLength;
|
||||
}
|
||||
|
||||
byte[] newData = new byte[maxFragmentSize];
|
||||
Array.Copy(this.Data, 0, newData, 0, maxFragmentSize);
|
||||
this.Data = newData;
|
||||
|
||||
return fragments;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
11
Assets/BestHTTP/WebSocket/Frames/WebSocketFrame.cs.meta
Normal file
11
Assets/BestHTTP/WebSocket/Frames/WebSocketFrame.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d2f7c577d761a412a99e996eda082191
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
201
Assets/BestHTTP/WebSocket/Frames/WebSocketFrameReader.cs
Normal file
201
Assets/BestHTTP/WebSocket/Frames/WebSocketFrameReader.cs
Normal file
@@ -0,0 +1,201 @@
|
||||
#if !BESTHTTP_DISABLE_WEBSOCKET && (!UNITY_WEBGL || UNITY_EDITOR)
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
using BestHTTP.Extensions;
|
||||
using BestHTTP.WebSocket.Extensions;
|
||||
|
||||
namespace BestHTTP.WebSocket.Frames
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an incoming WebSocket Frame.
|
||||
/// </summary>
|
||||
public sealed class WebSocketFrameReader
|
||||
{
|
||||
#region Properties
|
||||
|
||||
public byte Header { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// True if it's a final Frame in a sequence, or the only one.
|
||||
/// </summary>
|
||||
public bool IsFinal { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The type of the Frame.
|
||||
/// </summary>
|
||||
public WebSocketFrameTypes Type { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if there are any mask sent to decode the data.
|
||||
/// </summary>
|
||||
public bool HasMask { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The length of the Data.
|
||||
/// </summary>
|
||||
public UInt64 Length { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The sent byte array as a mask to decode the data.
|
||||
/// </summary>
|
||||
public byte[] Mask { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The decoded array of bytes.
|
||||
/// </summary>
|
||||
public byte[] Data { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Textual representation of the received Data.
|
||||
/// </summary>
|
||||
public string DataAsText { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Internal & Private Functions
|
||||
|
||||
internal void Read(Stream stream)
|
||||
{
|
||||
// For the complete documentation for this section see:
|
||||
// http://tools.ietf.org/html/rfc6455#section-5.2
|
||||
|
||||
this.Header = ReadByte(stream);
|
||||
|
||||
// The first byte is the Final Bit and the type of the frame
|
||||
IsFinal = (this.Header & 0x80) != 0;
|
||||
Type = (WebSocketFrameTypes)(this.Header & 0xF);
|
||||
|
||||
byte maskAndLength = ReadByte(stream);
|
||||
|
||||
// The second byte is the Mask Bit and the length of the payload data
|
||||
HasMask = (maskAndLength & 0x80) != 0;
|
||||
|
||||
// if 0-125, that is the payload length.
|
||||
Length = (UInt64)(maskAndLength & 127);
|
||||
|
||||
// If 126, the following 2 bytes interpreted as a 16-bit unsigned integer are the payload length.
|
||||
if (Length == 126)
|
||||
{
|
||||
byte[] rawLen = new byte[2];
|
||||
stream.ReadBuffer(rawLen);
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
Array.Reverse(rawLen, 0, rawLen.Length);
|
||||
|
||||
Length = (UInt64)BitConverter.ToUInt16(rawLen, 0);
|
||||
}
|
||||
else if (Length == 127)
|
||||
{
|
||||
// If 127, the following 8 bytes interpreted as a 64-bit unsigned integer (the
|
||||
// most significant bit MUST be 0) are the payload length.
|
||||
|
||||
byte[] rawLen = new byte[8];
|
||||
stream.ReadBuffer(rawLen);
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
Array.Reverse(rawLen, 0, rawLen.Length);
|
||||
|
||||
Length = (UInt64)BitConverter.ToUInt64(rawLen, 0);
|
||||
}
|
||||
|
||||
// Read the Mask, if has any
|
||||
if (HasMask)
|
||||
{
|
||||
Mask = new byte[4];
|
||||
if (stream.Read(Mask, 0, 4) < Mask.Length)
|
||||
throw ExceptionHelper.ServerClosedTCPStream();
|
||||
}
|
||||
|
||||
Data = new byte[Length];
|
||||
|
||||
if (Length == 0L)
|
||||
return;
|
||||
|
||||
int readLength = 0;
|
||||
|
||||
do
|
||||
{
|
||||
int read = stream.Read(Data, readLength, Data.Length - readLength);
|
||||
|
||||
if (read <= 0)
|
||||
throw ExceptionHelper.ServerClosedTCPStream();
|
||||
|
||||
readLength += read;
|
||||
} while (readLength < Data.Length);
|
||||
|
||||
// It would be great to speed this up with SSE
|
||||
if (HasMask)
|
||||
for (int i = 0; i < Data.Length; ++i)
|
||||
Data[i] = (byte)(Data[i] ^ Mask[i % 4]);
|
||||
}
|
||||
|
||||
private byte ReadByte(Stream stream)
|
||||
{
|
||||
int read = stream.ReadByte();
|
||||
|
||||
if (read < 0)
|
||||
throw ExceptionHelper.ServerClosedTCPStream();
|
||||
|
||||
return (byte)read;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Functions
|
||||
|
||||
/// <summary>
|
||||
/// Assembles all fragments into a final frame. Call this on the last fragment of a frame.
|
||||
/// </summary>
|
||||
/// <param name="fragments">The list of previously downloaded and parsed fragments of the frame</param>
|
||||
public void Assemble(List<WebSocketFrameReader> fragments)
|
||||
{
|
||||
// this way the following algorithms will handle this fragment's data too
|
||||
fragments.Add(this);
|
||||
|
||||
UInt64 finalLength = 0;
|
||||
for (int i = 0; i < fragments.Count; ++i)
|
||||
finalLength += fragments[i].Length;
|
||||
|
||||
byte[] buffer = new byte[finalLength];
|
||||
UInt64 pos = 0;
|
||||
for (int i = 0; i < fragments.Count; ++i)
|
||||
{
|
||||
Array.Copy(fragments[i].Data, 0, buffer, (int)pos, (int)fragments[i].Length);
|
||||
pos += fragments[i].Length;
|
||||
}
|
||||
|
||||
// All fragments of a message are of the same type, as set by the first fragment's opcode.
|
||||
this.Type = fragments[0].Type;
|
||||
|
||||
// Reserver flags may be contained only in the first fragment
|
||||
|
||||
this.Header = fragments[0].Header;
|
||||
this.Length = finalLength;
|
||||
this.Data = buffer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This function will decode the received data incrementally with the associated websocket's extensions.
|
||||
/// </summary>
|
||||
public void DecodeWithExtensions(WebSocket webSocket)
|
||||
{
|
||||
if (webSocket.Extensions != null)
|
||||
for (int i = 0; i < webSocket.Extensions.Length; ++i)
|
||||
{
|
||||
var ext = webSocket.Extensions[i];
|
||||
if (ext != null)
|
||||
this.Data = ext.Decode(this.Header, this.Data);
|
||||
}
|
||||
|
||||
if (this.Type == WebSocketFrameTypes.Text && this.Data != null)
|
||||
this.DataAsText = System.Text.Encoding.UTF8.GetString(this.Data, 0, this.Data.Length);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 02b9d3788c68045428a0ec2b4b0f5ad0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
48
Assets/BestHTTP/WebSocket/Frames/WebSocketFrameTypes.cs
Normal file
48
Assets/BestHTTP/WebSocket/Frames/WebSocketFrameTypes.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
#if !BESTHTTP_DISABLE_WEBSOCKET && (!UNITY_WEBGL || UNITY_EDITOR)
|
||||
|
||||
namespace BestHTTP.WebSocket.Frames
|
||||
{
|
||||
public enum WebSocketFrameTypes : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// A fragmented message's first frame's contain the type of the message(binary or text), all consecutive frame of that message must be a Continuation frame.
|
||||
/// Last of these frame's Fin bit must be 1.
|
||||
/// </summary>
|
||||
/// <example>For a text message sent as three fragments, the first fragment would have an opcode of 0x1 (text) and a FIN bit clear,
|
||||
/// the second fragment would have an opcode of 0x0 (Continuation) and a FIN bit clear,
|
||||
/// and the third fragment would have an opcode of 0x0 (Continuation) and a FIN bit that is set.</example>
|
||||
Continuation = 0x0,
|
||||
Text = 0x1,
|
||||
Binary = 0x2,
|
||||
//Reserved1 = 0x3,
|
||||
//Reserved2 = 0x4,
|
||||
//Reserved3 = 0x5,
|
||||
//Reserved4 = 0x6,
|
||||
//Reserved5 = 0x7,
|
||||
|
||||
/// <summary>
|
||||
/// The Close frame MAY contain a body (the "Application data" portion of the frame) that indicates a reason for closing,
|
||||
/// such as an endpoint shutting down, an endpoint having received a frame too large, or an endpoint having received a frame that
|
||||
/// does not conform to the format expected by the endpoint.
|
||||
/// As the data is not guaranteed to be human readable, clients MUST NOT show it to end users.
|
||||
/// </summary>
|
||||
ConnectionClose = 0x8,
|
||||
|
||||
/// <summary>
|
||||
/// The Ping frame contains an opcode of 0x9. A Ping frame MAY include "Application data".
|
||||
/// </summary>
|
||||
Ping = 0x9,
|
||||
|
||||
/// <summary>
|
||||
/// A Pong frame sent in response to a Ping frame must have identical "Application data" as found in the message body of the Ping frame being replied to.
|
||||
/// </summary>
|
||||
Pong = 0xA,
|
||||
//Reserved6 = 0xB,
|
||||
//Reserved7 = 0xC,
|
||||
//Reserved8 = 0xD,
|
||||
//Reserved9 = 0xE,
|
||||
//Reserved10 = 0xF,
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
11
Assets/BestHTTP/WebSocket/Frames/WebSocketFrameTypes.cs.meta
Normal file
11
Assets/BestHTTP/WebSocket/Frames/WebSocketFrameTypes.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 68366ab5bdc1f47a4ba5f6d7106329f2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user