WebSocketFrame.cs 7.5 KB

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