WebSocketFrame.cs 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. #if !BESTHTTP_DISABLE_WEBSOCKET && (!UNITY_WEBGL || UNITY_EDITOR)
  2. using BestHTTP.Extensions;
  3. using BestHTTP.PlatformSupport.Memory;
  4. using System;
  5. using System.IO;
  6. namespace BestHTTP.WebSocket.Frames
  7. {
  8. public struct RawFrameData : IDisposable
  9. {
  10. public byte[] Data;
  11. public int Length;
  12. public RawFrameData(byte[] data, int length)
  13. {
  14. Data = data;
  15. Length = length;
  16. }
  17. public void Dispose()
  18. {
  19. BufferPool.Release(Data);
  20. Data = null;
  21. }
  22. }
  23. /// <summary>
  24. /// Denotes a binary frame. The "Payload data" is arbitrary binary data whose interpretation is solely up to the application layer.
  25. /// This is the base class of all other frame writers, as all frame can be represented as a byte array.
  26. /// </summary>
  27. [BestHTTP.PlatformSupport.IL2CPP.Il2CppEagerStaticClassConstructionAttribute]
  28. public sealed class WebSocketFrame
  29. {
  30. public WebSocketFrameTypes Type { get; private set; }
  31. public bool IsFinal { get; private set; }
  32. public byte Header { get; private set; }
  33. public byte[] Data { get; private set; }
  34. public int DataLength { get; private set; }
  35. public bool UseExtensions { get; private set; }
  36. public override string ToString()
  37. {
  38. return string.Format("[WebSocketFrame Type: {0}, IsFinal: {1}, Header: {2:X2}, DataLength: {3}, UseExtensions: {4}]",
  39. this.Type, this.IsFinal, this.Header, this.DataLength, this.UseExtensions);
  40. }
  41. #region Constructors
  42. public WebSocketFrame(WebSocket webSocket, WebSocketFrameTypes type, byte[] data)
  43. :this(webSocket, type, data, true)
  44. { }
  45. public WebSocketFrame(WebSocket webSocket, WebSocketFrameTypes type, byte[] data, bool useExtensions)
  46. : this(webSocket, type, data, 0, data != null ? (UInt64)data.Length : 0, true, useExtensions)
  47. {
  48. }
  49. public WebSocketFrame(WebSocket webSocket, WebSocketFrameTypes type, byte[] data, bool isFinal, bool useExtensions)
  50. : this(webSocket, type, data, 0, data != null ? (UInt64)data.Length : 0, isFinal, useExtensions)
  51. {
  52. }
  53. public WebSocketFrame(WebSocket webSocket, WebSocketFrameTypes type, byte[] data, UInt64 pos, UInt64 length, bool isFinal, bool useExtensions)
  54. {
  55. this.Type = type;
  56. this.IsFinal = isFinal;
  57. this.UseExtensions = useExtensions;
  58. this.DataLength = (int)length;
  59. if (data != null)
  60. {
  61. this.Data = BufferPool.Get(this.DataLength, true);
  62. Array.Copy(data, (int)pos, this.Data, 0, this.DataLength);
  63. }
  64. else
  65. data = BufferPool.NoData;
  66. // First byte: Final Bit + Rsv flags + OpCode
  67. byte finalBit = (byte)(IsFinal ? 0x80 : 0x0);
  68. this.Header = (byte)(finalBit | (byte)Type);
  69. if (this.UseExtensions && webSocket != null && webSocket.Extensions != null)
  70. {
  71. for (int i = 0; i < webSocket.Extensions.Length; ++i)
  72. {
  73. var ext = webSocket.Extensions[i];
  74. if (ext != null)
  75. {
  76. this.Header |= ext.GetFrameHeader(this, this.Header);
  77. byte[] newData = ext.Encode(this);
  78. if (newData != this.Data)
  79. {
  80. BufferPool.Release(this.Data);
  81. this.Data = newData;
  82. this.DataLength = newData.Length;
  83. }
  84. }
  85. }
  86. }
  87. }
  88. #endregion
  89. #region Public Functions
  90. public unsafe RawFrameData Get()
  91. {
  92. if (Data == null)
  93. Data = BufferPool.NoData;
  94. using (var ms = new BufferPoolMemoryStream(this.DataLength + 9))
  95. {
  96. // For the complete documentation for this section see:
  97. // http://tools.ietf.org/html/rfc6455#section-5.2
  98. // Write the header
  99. ms.WriteByte(this.Header);
  100. // 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
  101. // 16-bit unsigned integer are the payload length. If 127, the following 8 bytes interpreted as a 64-bit unsigned integer (the
  102. // most significant bit MUST be 0) are the payload length. Multibyte length quantities are expressed in network byte order.
  103. if (this.DataLength < 126)
  104. ms.WriteByte((byte)(0x80 | (byte)this.DataLength));
  105. else if (this.DataLength < UInt16.MaxValue)
  106. {
  107. ms.WriteByte((byte)(0x80 | 126));
  108. byte[] len = BitConverter.GetBytes((UInt16)this.DataLength);
  109. if (BitConverter.IsLittleEndian)
  110. Array.Reverse(len, 0, len.Length);
  111. ms.Write(len, 0, len.Length);
  112. }
  113. else
  114. {
  115. ms.WriteByte((byte)(0x80 | 127));
  116. byte[] len = BitConverter.GetBytes((UInt64)this.DataLength);
  117. if (BitConverter.IsLittleEndian)
  118. Array.Reverse(len, 0, len.Length);
  119. ms.Write(len, 0, len.Length);
  120. }
  121. // 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
  122. // present if the mask bit is set to 1 and is absent if the mask bit is set to 0.
  123. // If the data is being sent by the client, the frame(s) MUST be masked.
  124. byte[] mask = BufferPool.Get(4, true);
  125. int hash = this.GetHashCode();
  126. mask[0] = (byte)((hash >> 24) & 0xFF);
  127. mask[1] = (byte)((hash >> 16) & 0xFF);
  128. mask[2] = (byte)((hash >> 8) & 0xFF);
  129. mask[3] = (byte)(hash & 0xFF);
  130. ms.Write(mask, 0, 4);
  131. // Do the masking.
  132. fixed (byte* pData = Data, pmask = mask)
  133. {
  134. // Here, instead of byte by byte, we reinterpret cast the data as uints and apply the masking so.
  135. // This way, we can mask 4 bytes in one cycle, instead of just 1
  136. int localLength = this.DataLength / 4;
  137. if (localLength > 0)
  138. {
  139. uint* upData = (uint*)pData;
  140. uint umask = *(uint*)pmask;
  141. unchecked
  142. {
  143. for (int i = 0; i < localLength; ++i)
  144. upData[i] = upData[i] ^ umask;
  145. }
  146. }
  147. // Because data might not be exactly dividable by 4, we have to mask the remaining 0..3 too.
  148. int from = localLength * 4;
  149. localLength = from + this.DataLength % 4;
  150. for (int i = from; i < localLength; ++i)
  151. pData[i] = (byte)(pData[i] ^ pmask[i % 4]);
  152. }
  153. BufferPool.Release(mask);
  154. ms.Write(Data, 0, DataLength);
  155. return new RawFrameData(ms.ToArray(true), (int)ms.Length);
  156. }
  157. }
  158. public WebSocketFrame[] Fragment(uint maxFragmentSize)
  159. {
  160. if (this.Data == null)
  161. return null;
  162. // All control frames MUST have a payload length of 125 bytes or less and MUST NOT be fragmented.
  163. if (this.Type != WebSocketFrameTypes.Binary && this.Type != WebSocketFrameTypes.Text)
  164. return null;
  165. if (this.DataLength <= maxFragmentSize)
  166. return null;
  167. this.IsFinal = false;
  168. // Clear final bit from the header flags
  169. this.Header &= 0x7F;
  170. // One chunk will remain in this fragment, so we have to allocate one less
  171. int count = (int)((this.DataLength / maxFragmentSize) + (this.DataLength % maxFragmentSize == 0 ? -1 : 0));
  172. WebSocketFrame[] fragments = new WebSocketFrame[count];
  173. // Skip one chunk, for the current one
  174. UInt64 pos = maxFragmentSize;
  175. while (pos < (UInt64)this.DataLength)
  176. {
  177. UInt64 chunkLength = Math.Min(maxFragmentSize, (UInt64)this.DataLength - pos);
  178. fragments[fragments.Length - count--] = new WebSocketFrame(null, WebSocketFrameTypes.Continuation, this.Data, pos, chunkLength, pos + chunkLength >= (UInt64)this.DataLength, false);
  179. pos += chunkLength;
  180. }
  181. //byte[] newData = VariableSizedBufferPool.Get(maxFragmentSize, true);
  182. //Array.Copy(this.Data, 0, newData, 0, maxFragmentSize);
  183. //VariableSizedBufferPool.Release(this.Data);
  184. //this.Data = newData;
  185. this.DataLength = (int)maxFragmentSize;
  186. return fragments;
  187. }
  188. #endregion
  189. }
  190. }
  191. #endif