123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241 |
- #if !BESTHTTP_DISABLE_WEBSOCKET && (!UNITY_WEBGL || UNITY_EDITOR)
- using BestHTTP.Extensions;
- using BestHTTP.PlatformSupport.Memory;
- using System;
- using System.IO;
- namespace BestHTTP.WebSocket.Frames
- {
- public struct RawFrameData : IDisposable
- {
- public byte[] Data;
- public int Length;
- public RawFrameData(byte[] data, int length)
- {
- Data = data;
- Length = length;
- }
- public void Dispose()
- {
- BufferPool.Release(Data);
- Data = null;
- }
- }
- /// <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>
- [BestHTTP.PlatformSupport.IL2CPP.Il2CppEagerStaticClassConstructionAttribute]
- public sealed class WebSocketFrame
- {
- 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 int DataLength { get; private set; }
- public bool UseExtensions { get; private set; }
- public override string ToString()
- {
- return string.Format("[WebSocketFrame Type: {0}, IsFinal: {1}, Header: {2:X2}, DataLength: {3}, UseExtensions: {4}]",
- this.Type, this.IsFinal, this.Header, this.DataLength, this.UseExtensions);
- }
- #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;
- this.DataLength = (int)length;
- if (data != null)
- {
- this.Data = BufferPool.Get(this.DataLength, true);
- Array.Copy(data, (int)pos, this.Data, 0, this.DataLength);
- }
- else
- data = BufferPool.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);
- byte[] newData = ext.Encode(this);
- if (newData != this.Data)
- {
- BufferPool.Release(this.Data);
- this.Data = newData;
- this.DataLength = newData.Length;
- }
- }
- }
- }
- }
- #endregion
- #region Public Functions
- public unsafe RawFrameData Get()
- {
- if (Data == null)
- Data = BufferPool.NoData;
- using (var ms = new BufferPoolMemoryStream(this.DataLength + 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.DataLength < 126)
- ms.WriteByte((byte)(0x80 | (byte)this.DataLength));
- else if (this.DataLength < UInt16.MaxValue)
- {
- ms.WriteByte((byte)(0x80 | 126));
- byte[] len = BitConverter.GetBytes((UInt16)this.DataLength);
- 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.DataLength);
- 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 = BufferPool.Get(4, true);
- int hash = this.GetHashCode();
- mask[0] = (byte)((hash >> 24) & 0xFF);
- mask[1] = (byte)((hash >> 16) & 0xFF);
- mask[2] = (byte)((hash >> 8) & 0xFF);
- mask[3] = (byte)(hash & 0xFF);
- ms.Write(mask, 0, 4);
- // Do the masking.
- fixed (byte* pData = Data, pmask = mask)
- {
- // Here, instead of byte by byte, we reinterpret cast the data as uints and apply the masking so.
- // This way, we can mask 4 bytes in one cycle, instead of just 1
- int localLength = this.DataLength / 4;
- if (localLength > 0)
- {
- uint* upData = (uint*)pData;
- uint umask = *(uint*)pmask;
- unchecked
- {
- for (int i = 0; i < localLength; ++i)
- upData[i] = upData[i] ^ umask;
- }
- }
- // Because data might not be exactly dividable by 4, we have to mask the remaining 0..3 too.
- int from = localLength * 4;
- localLength = from + this.DataLength % 4;
- for (int i = from; i < localLength; ++i)
- pData[i] = (byte)(pData[i] ^ pmask[i % 4]);
- }
- BufferPool.Release(mask);
- ms.Write(Data, 0, DataLength);
- return new RawFrameData(ms.ToArray(true), (int)ms.Length);
- }
- }
- public WebSocketFrame[] Fragment(uint 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.DataLength <= 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 = (int)((this.DataLength / maxFragmentSize) + (this.DataLength % maxFragmentSize == 0 ? -1 : 0));
- WebSocketFrame[] fragments = new WebSocketFrame[count];
- // Skip one chunk, for the current one
- UInt64 pos = maxFragmentSize;
- while (pos < (UInt64)this.DataLength)
- {
- UInt64 chunkLength = Math.Min(maxFragmentSize, (UInt64)this.DataLength - pos);
- fragments[fragments.Length - count--] = new WebSocketFrame(null, WebSocketFrameTypes.Continuation, this.Data, pos, chunkLength, pos + chunkLength >= (UInt64)this.DataLength, false);
- pos += chunkLength;
- }
- //byte[] newData = VariableSizedBufferPool.Get(maxFragmentSize, true);
- //Array.Copy(this.Data, 0, newData, 0, maxFragmentSize);
- //VariableSizedBufferPool.Release(this.Data);
- //this.Data = newData;
- this.DataLength = (int)maxFragmentSize;
- return fragments;
- }
- #endregion
- }
- }
- #endif
|