WebSocketFrame.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. #if !BESTHTTP_DISABLE_WEBSOCKET && (!UNITY_WEBGL || UNITY_EDITOR)
  2. using System;
  3. using BestHTTP.Extensions;
  4. using BestHTTP.PlatformSupport.Memory;
  5. using System.Runtime.CompilerServices;
  6. using BestHTTP.PlatformSupport.IL2CPP;
  7. using BestHTTP.Logger;
  8. #if BESTHTTP_WITH_BURST
  9. using Unity.Burst;
  10. using Unity.Burst.Intrinsics;
  11. using static Unity.Burst.Intrinsics.X86.Avx2;
  12. using static Unity.Burst.Intrinsics.X86.Sse2;
  13. using static Unity.Burst.Intrinsics.Arm.Neon;
  14. #endif
  15. namespace BestHTTP.WebSocket.Frames
  16. {
  17. /// <summary>
  18. /// Denotes a binary frame. The "Payload data" is arbitrary binary data whose interpretation is solely up to the application layer.
  19. /// This is the base class of all other frame writers, as all frame can be represented as a byte array.
  20. /// </summary>
  21. #if BESTHTTP_WITH_BURST
  22. [BurstCompile]
  23. #endif
  24. [Il2CppEagerStaticClassConstruction]
  25. public struct WebSocketFrame
  26. {
  27. public WebSocketFrameTypes Type { get; private set; }
  28. public BufferSegment Data { get; private set; }
  29. public WebSocket Websocket { get; private set; }
  30. public byte Header;
  31. public WebSocketFrame(WebSocket webSocket, WebSocketFrameTypes type, BufferSegment data)
  32. :this(webSocket, type, data, true)
  33. { }
  34. public WebSocketFrame(WebSocket webSocket, WebSocketFrameTypes type, BufferSegment data, bool useExtensions)
  35. : this(webSocket, type, data, true, useExtensions)
  36. {
  37. }
  38. public WebSocketFrame(WebSocket webSocket, WebSocketFrameTypes type, BufferSegment data, bool isFinal, bool useExtensions)
  39. :this(webSocket, type, data, isFinal, useExtensions, copyData: true)
  40. {
  41. }
  42. public WebSocketFrame(WebSocket webSocket, WebSocketFrameTypes type, BufferSegment data, bool isFinal, bool useExtensions, bool copyData)
  43. {
  44. if (!isFinal)
  45. throw new NotSupportedException("isFinal parameter is not true");
  46. this.Type = type;
  47. this.Websocket = webSocket;
  48. this.Data = data;
  49. if (this.Data.Data != null)
  50. {
  51. if (copyData)
  52. {
  53. var from = this.Data;
  54. var buffer = BufferPool.Get(this.Data.Count, true);
  55. this.Data = new BufferSegment(buffer, 0, this.Data.Count);
  56. Array.Copy(from.Data, (int)from.Offset, this.Data.Data, this.Data.Offset, this.Data.Count);
  57. }
  58. }
  59. else
  60. this.Data = BufferSegment.Empty;
  61. // We use the header only for storing extension flags only
  62. this.Header = 0x00;
  63. }
  64. public override string ToString()
  65. {
  66. return string.Format("[WebSocketFrame Type: {0}, Header: {1:X2}, Data: {2}]", this.Type, this.Header, this.Data);
  67. }
  68. public void WriteTo(Action<BufferSegment, BufferSegment> callback, uint maxFragmentSize, bool mask, LoggingContext context)
  69. {
  70. if (HTTPManager.Logger.Level <= Logger.Loglevels.All)
  71. HTTPManager.Logger.Verbose("WebSocketFrame", "WriteTo - Frame: " + ToString(), context);
  72. if ((this.Type == WebSocketFrameTypes.Binary || this.Type == WebSocketFrameTypes.Text))
  73. {
  74. DoExtensions();
  75. if (this.Data.Count > maxFragmentSize)
  76. FragmentAndSend(callback, maxFragmentSize, mask, context);
  77. else
  78. WriteFragment(callback, (byte)(0x80 | this.Header | (byte)this.Type), this.Data, mask, context);
  79. }
  80. else
  81. {
  82. WriteFragment(callback, (byte)(0x80 | this.Header | (byte)this.Type), this.Data, mask, context);
  83. }
  84. }
  85. private void DoExtensions()
  86. {
  87. if (this.Websocket != null && this.Websocket.Extensions != null)
  88. {
  89. for (int i = 0; i < this.Websocket.Extensions.Length; ++i)
  90. {
  91. var ext = this.Websocket.Extensions[i];
  92. if (ext != null)
  93. {
  94. this.Header |= ext.GetFrameHeader(this, this.Header);
  95. BufferSegment newData = ext.Encode(this);
  96. if (newData != this.Data)
  97. {
  98. BufferPool.Release(this.Data);
  99. this.Data = newData;
  100. }
  101. }
  102. }
  103. }
  104. }
  105. private void FragmentAndSend(Action<BufferSegment, BufferSegment> callback, uint maxFragmentSize, bool mask, LoggingContext context)
  106. {
  107. int pos = this.Data.Offset;
  108. int endPos = this.Data.Offset + this.Data.Count;
  109. byte header = (byte)(0x00 | this.Header | (byte)this.Type);
  110. while (pos < endPos)
  111. {
  112. int chunkLength = Math.Min((int)maxFragmentSize, endPos - pos);
  113. WriteFragment(callback: callback,
  114. Header: header,
  115. Data: this.Data.Slice((int)pos, (int)chunkLength),
  116. mask: mask,
  117. context: context);
  118. pos += chunkLength;
  119. // set only the IsFinal flag, every other flags are zero
  120. header = (byte)(pos + chunkLength >= this.Data.Count ? 0x80 : 0x00);
  121. }
  122. }
  123. private static unsafe void WriteFragment(Action<BufferSegment, BufferSegment> callback, byte Header, BufferSegment Data, bool mask, LoggingContext context)
  124. {
  125. // For the complete documentation for this section see:
  126. // http://tools.ietf.org/html/rfc6455#section-5.2
  127. // Header(1) + Len(8) + Mask (4)
  128. byte[] wsHeader = BufferPool.Get(13, true);
  129. int pos = 0;
  130. // Write the header
  131. wsHeader[pos++] = Header;
  132. // 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
  133. // 16-bit unsigned integer are the payload length. If 127, the following 8 bytes interpreted as a 64-bit unsigned integer (the
  134. // most significant bit MUST be 0) are the payload length. Multibyte length quantities are expressed in network byte order.
  135. if (Data.Count < 126)
  136. {
  137. wsHeader[pos++] = (byte)(0x80 | (byte)Data.Count);
  138. }
  139. else if (Data.Count < UInt16.MaxValue)
  140. {
  141. wsHeader[pos++] = (byte)(0x80 | 126);
  142. var count = (UInt16)Data.Count;
  143. wsHeader[pos++] = (byte)(count >> 8);
  144. wsHeader[pos++] = (byte)(count);
  145. }
  146. else
  147. {
  148. wsHeader[pos++] = (byte)(0x80 | 127);
  149. var count = (UInt64)Data.Count;
  150. wsHeader[pos++] = (byte)(count >> 56);
  151. wsHeader[pos++] = (byte)(count >> 48);
  152. wsHeader[pos++] = (byte)(count >> 40);
  153. wsHeader[pos++] = (byte)(count >> 32);
  154. wsHeader[pos++] = (byte)(count >> 24);
  155. wsHeader[pos++] = (byte)(count >> 16);
  156. wsHeader[pos++] = (byte)(count >> 8);
  157. wsHeader[pos++] = (byte)(count);
  158. }
  159. if (Data != BufferSegment.Empty)
  160. {
  161. // 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
  162. // present if the mask bit is set to 1 and is absent if the mask bit is set to 0.
  163. // If the data is being sent by the client, the frame(s) MUST be masked.
  164. uint hash = mask ? (uint)wsHeader.GetHashCode() : 0;
  165. wsHeader[pos++] = (byte)(hash >> 24);
  166. wsHeader[pos++] = (byte)(hash >> 16);
  167. wsHeader[pos++] = (byte)(hash >> 8);
  168. wsHeader[pos++] = (byte)(hash);
  169. // Do the masking.
  170. if (mask)
  171. {
  172. fixed (byte* pData = Data.Data/*, pmask = &wsHeader[pos - 4]*/)
  173. {
  174. byte* alignedMask = stackalloc byte[4];
  175. alignedMask[0] = wsHeader[pos - 4];
  176. alignedMask[1] = wsHeader[pos - 3];
  177. alignedMask[2] = wsHeader[pos - 2];
  178. alignedMask[3] = wsHeader[pos - 1];
  179. ApplyMask(pData, Data.Offset, Data.Count, alignedMask);
  180. }
  181. }
  182. }
  183. else
  184. {
  185. wsHeader[pos++] = 0;
  186. wsHeader[pos++] = 0;
  187. wsHeader[pos++] = 0;
  188. wsHeader[pos++] = 0;
  189. }
  190. var header = wsHeader.AsBuffer(pos);
  191. if (HTTPManager.Logger.Level <= Logger.Loglevels.All)
  192. HTTPManager.Logger.Verbose("WebSocketFrame", string.Format("WriteFragment - Header: {0}, data chunk: {1}", header.ToString(), Data.ToString()), context);
  193. callback(header, Data);
  194. }
  195. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  196. #if BESTHTTP_WITH_BURST
  197. [BurstCompile(CompileSynchronously = true)]
  198. #endif
  199. public unsafe static void ApplyMask(
  200. #if BESTHTTP_WITH_BURST
  201. [NoAlias]
  202. #endif
  203. byte* pData,
  204. int DataOffset,
  205. int DataCount,
  206. #if BESTHTTP_WITH_BURST
  207. [NoAlias]
  208. #endif
  209. byte* pmask
  210. )
  211. {
  212. int targetOffset = DataOffset + DataCount;
  213. uint umask = *(uint*)pmask;
  214. #if BESTHTTP_WITH_BURST
  215. if (targetOffset - DataOffset >= 32)
  216. {
  217. if (IsAvx2Supported)
  218. {
  219. v256 mask = new v256(umask);
  220. v256 ldstrMask = new v256((byte)0xFF);
  221. while (targetOffset - DataOffset >= 32)
  222. {
  223. // load data
  224. v256 data = mm256_maskload_epi32(pData + DataOffset, ldstrMask);
  225. // xor
  226. v256 result = mm256_xor_si256(data, mask);
  227. // store
  228. mm256_maskstore_epi32(pData + DataOffset, ldstrMask, result);
  229. // advance
  230. DataOffset += 32;
  231. }
  232. }
  233. }
  234. if (targetOffset - DataOffset >= 16)
  235. {
  236. v128 mask = new v128(umask);
  237. if (IsSse2Supported)
  238. {
  239. while (targetOffset - DataOffset >= 16)
  240. {
  241. // load data
  242. v128 data = loadu_si128(pData + DataOffset);
  243. // xor
  244. var result = xor_si128(data, mask);
  245. // store
  246. storeu_si128(pData + DataOffset, result);
  247. // advance
  248. DataOffset += 16;
  249. }
  250. }
  251. else if (IsNeonSupported)
  252. {
  253. while (targetOffset - DataOffset >= 16)
  254. {
  255. // load data
  256. v128 data = vld1q_u8(pData + DataOffset);
  257. // xor
  258. v128 result = veorq_u8(data, mask);
  259. // store
  260. vst1q_u8(pData + DataOffset, result);
  261. // advance
  262. DataOffset += 16;
  263. }
  264. }
  265. }
  266. #endif
  267. // fallback to calculate by reinterpret-casting to ulong
  268. if (targetOffset - DataOffset >= 8)
  269. {
  270. ulong* ulpData = (ulong*)(pData + DataOffset);
  271. #if UNITY_ANDROID && !UNITY_EDITOR
  272. if ((long)ulpData % sizeof(ulong) == 0)
  273. {
  274. #endif
  275. // duplicate the mask to fill up a whole ulong.
  276. ulong ulmask = (((ulong)umask << 32) | umask);
  277. while (targetOffset - DataOffset >= 8)
  278. {
  279. *ulpData = *ulpData ^ ulmask;
  280. ulpData++;
  281. DataOffset += 8;
  282. }
  283. #if UNITY_ANDROID && !UNITY_EDITOR
  284. }
  285. #endif
  286. }
  287. // process remaining bytes (0..7)
  288. for (int i = DataOffset; i < targetOffset; ++i)
  289. pData[i] = (byte)(pData[i] ^ pmask[(i - DataOffset) % 4]);
  290. }
  291. }
  292. }
  293. #endif