HTTP2SettingsRegistry.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. #if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL && !BESTHTTP_DISABLE_HTTP2
  2. using System;
  3. using System.Collections.Generic;
  4. namespace BestHTTP.Connections.HTTP2
  5. {
  6. // https://httpwg.org/specs/rfc7540.html#iana-settings
  7. public enum HTTP2Settings : ushort
  8. {
  9. /// <summary>
  10. /// Allows the sender to inform the remote endpoint of the maximum size of the
  11. /// header compression table used to decode header blocks, in octets.
  12. /// The encoder can select any size equal to or less than this value
  13. /// by using signaling specific to the header compression format inside a header block (see [COMPRESSION]).
  14. /// The initial value is 4,096 octets.
  15. /// </summary>
  16. HEADER_TABLE_SIZE = 0x01,
  17. /// <summary>
  18. /// This setting can be used to disable server push (Section 8.2).
  19. /// An endpoint MUST NOT send a PUSH_PROMISE frame if it receives this parameter set to a value of 0.
  20. /// An endpoint that has both set this parameter to 0 and had it acknowledged MUST treat the receipt of a
  21. /// PUSH_PROMISE frame as a connection error (Section 5.4.1) of type PROTOCOL_ERROR.
  22. ///
  23. /// The initial value is 1, which indicates that server push is permitted.
  24. /// Any value other than 0 or 1 MUST be treated as a connection error (Section 5.4.1) of type PROTOCOL_ERROR.
  25. /// </summary>
  26. ENABLE_PUSH = 0x02,
  27. /// <summary>
  28. /// Indicates the maximum number of concurrent streams that the sender will allow. This limit is directional:
  29. /// it applies to the number of streams that the sender permits the receiver to create.
  30. /// Initially, there is no limit to this value. It is recommended that this value be no smaller than 100,
  31. /// so as to not unnecessarily limit parallelism.
  32. ///
  33. /// A value of 0 for SETTINGS_MAX_CONCURRENT_STREAMS SHOULD NOT be treated as special by endpoints.
  34. /// A zero value does prevent the creation of new streams;
  35. /// however, this can also happen for any limit that is exhausted with active streams.
  36. /// Servers SHOULD only set a zero value for short durations; if a server does not wish to accept requests,
  37. /// closing the connection is more appropriate.
  38. /// </summary>
  39. MAX_CONCURRENT_STREAMS = 0x03,
  40. /// <summary>
  41. /// Indicates the sender's initial window size (in octets) for stream-level flow control.
  42. /// The initial value is 2^16-1 (65,535) octets.
  43. ///
  44. /// This setting affects the window size of all streams (see Section 6.9.2).
  45. ///
  46. /// Values above the maximum flow-control window size of 2^31-1 MUST be treated as a connection error
  47. /// (Section 5.4.1) of type FLOW_CONTROL_ERROR.
  48. /// </summary>
  49. INITIAL_WINDOW_SIZE = 0x04,
  50. /// <summary>
  51. /// Indicates the size of the largest frame payload that the sender is willing to receive, in octets.
  52. ///
  53. /// The initial value is 2^14 (16,384) octets.
  54. /// The value advertised by an endpoint MUST be between this initial value and the maximum allowed frame size
  55. /// (2^24-1 or 16,777,215 octets), inclusive.
  56. /// Values outside this range MUST be treated as a connection error (Section 5.4.1) of type PROTOCOL_ERROR.
  57. /// </summary>
  58. MAX_FRAME_SIZE = 0x05,
  59. /// <summary>
  60. /// This advisory setting informs a peer of the maximum size of header list that the sender is prepared to accept, in octets.
  61. /// The value is based on the uncompressed size of header fields,
  62. /// including the length of the name and value in octets plus an overhead of 32 octets for each header field.
  63. ///
  64. /// For any given request, a lower limit than what is advertised MAY be enforced. The initial value of this setting is unlimited.
  65. /// </summary>
  66. MAX_HEADER_LIST_SIZE = 0x06,
  67. RESERVED = 0x07,
  68. /// <summary>
  69. /// https://tools.ietf.org/html/rfc8441
  70. /// Upon receipt of SETTINGS_ENABLE_CONNECT_PROTOCOL with a value of 1, a client MAY use the Extended CONNECT as defined in this document when creating new streams.
  71. /// Receipt of this parameter by a server does not have any impact.
  72. ///
  73. /// A sender MUST NOT send a SETTINGS_ENABLE_CONNECT_PROTOCOL parameter with the value of 0 after previously sending a value of 1.
  74. /// </summary>
  75. ENABLE_CONNECT_PROTOCOL = 0x08
  76. }
  77. public sealed class HTTP2SettingsRegistry
  78. {
  79. public bool IsReadOnly { get; private set; }
  80. public Action<HTTP2SettingsRegistry, HTTP2Settings, UInt32, UInt32> OnSettingChangedEvent;
  81. private UInt32[] values;
  82. private bool[] changeFlags;
  83. public UInt32 this[HTTP2Settings setting]
  84. {
  85. get { return this.values[(ushort)setting]; }
  86. set
  87. {
  88. if (this.IsReadOnly)
  89. throw new NotSupportedException("It's a read-only one!");
  90. ushort idx = (ushort)setting;
  91. // https://httpwg.org/specs/rfc7540.html#SettingValues
  92. // An endpoint that receives a SETTINGS frame with any unknown or unsupported identifier MUST ignore that setting.
  93. if (idx == 0 || idx >= this.values.Length)
  94. return;
  95. UInt32 oldValue = this.values[idx];
  96. if (oldValue != value)
  97. {
  98. this.values[idx] = value;
  99. this.changeFlags[idx] = true;
  100. IsChanged = true;
  101. if (this.OnSettingChangedEvent != null)
  102. this.OnSettingChangedEvent(this, setting, oldValue, value);
  103. }
  104. }
  105. }
  106. public bool IsChanged { get; private set; }
  107. private HTTP2SettingsManager _parent;
  108. public HTTP2SettingsRegistry(HTTP2SettingsManager parent, bool readOnly, bool treatItAsAlreadyChanged)
  109. {
  110. this._parent = parent;
  111. this.values = new UInt32[HTTP2SettingsManager.SettingsCount];
  112. this.IsReadOnly = readOnly;
  113. if (!this.IsReadOnly)
  114. this.changeFlags = new bool[HTTP2SettingsManager.SettingsCount];
  115. // Set default values (https://httpwg.org/specs/rfc7540.html#iana-settings)
  116. this.values[(UInt16)HTTP2Settings.HEADER_TABLE_SIZE] = 4096;
  117. this.values[(UInt16)HTTP2Settings.ENABLE_PUSH] = 1;
  118. this.values[(UInt16)HTTP2Settings.MAX_CONCURRENT_STREAMS] = 128;
  119. this.values[(UInt16)HTTP2Settings.INITIAL_WINDOW_SIZE] = 65535;
  120. this.values[(UInt16)HTTP2Settings.MAX_FRAME_SIZE] = 16384;
  121. this.values[(UInt16)HTTP2Settings.MAX_HEADER_LIST_SIZE] = UInt32.MaxValue; // infinite
  122. if (this.IsChanged = treatItAsAlreadyChanged)
  123. {
  124. this.changeFlags[(UInt16)HTTP2Settings.MAX_CONCURRENT_STREAMS] = true;
  125. }
  126. }
  127. public void Merge(List<KeyValuePair<HTTP2Settings, UInt32>> settings)
  128. {
  129. if (settings == null)
  130. return;
  131. for (int i = 0; i < settings.Count; ++i)
  132. {
  133. HTTP2Settings setting = settings[i].Key;
  134. UInt16 key = (UInt16)setting;
  135. UInt32 value = settings[i].Value;
  136. if (key > 0 && key <= HTTP2SettingsManager.SettingsCount)
  137. {
  138. UInt32 oldValue = this.values[key];
  139. this.values[key] = value;
  140. if (oldValue != value && this.OnSettingChangedEvent != null)
  141. this.OnSettingChangedEvent(this, setting, oldValue, value);
  142. if (HTTPManager.Logger.Level <= Logger.Loglevels.All)
  143. HTTPManager.Logger.Information("HTTP2SettingsRegistry", string.Format("Merge {0}({1}) = {2}", setting, key, value), this._parent.Parent.Context);
  144. }
  145. }
  146. }
  147. public void Merge(HTTP2SettingsRegistry from)
  148. {
  149. if (this.values != null)
  150. this.values = new uint[from.values.Length];
  151. for (int i = 0; i < this.values.Length; ++i)
  152. this.values[i] = from.values[i];
  153. }
  154. internal HTTP2FrameHeaderAndPayload CreateFrame()
  155. {
  156. List<KeyValuePair<HTTP2Settings, UInt32>> keyValuePairs = new List<KeyValuePair<HTTP2Settings, uint>>(HTTP2SettingsManager.SettingsCount);
  157. for (int i = 1; i < HTTP2SettingsManager.SettingsCount; ++i)
  158. if (this.changeFlags[i])
  159. {
  160. keyValuePairs.Add(new KeyValuePair<HTTP2Settings, uint>((HTTP2Settings)i, this[(HTTP2Settings)i]));
  161. this.changeFlags[i] = false;
  162. }
  163. this.IsChanged = false;
  164. return HTTP2FrameHelper.CreateSettingsFrame(keyValuePairs);
  165. }
  166. }
  167. public sealed class HTTP2SettingsManager
  168. {
  169. public static readonly int SettingsCount = Enum.GetNames(typeof(HTTP2Settings)).Length + 1;
  170. /// <summary>
  171. /// This is the ACKd or default settings that we sent to the server.
  172. /// </summary>
  173. public HTTP2SettingsRegistry MySettings { get; private set; }
  174. /// <summary>
  175. /// This is the setting that can be changed. It will be sent to the server ASAP, and when ACKd, it will be copied
  176. /// to MySettings.
  177. /// </summary>
  178. public HTTP2SettingsRegistry InitiatedMySettings { get; private set; }
  179. /// <summary>
  180. /// Settings of the remote peer
  181. /// </summary>
  182. public HTTP2SettingsRegistry RemoteSettings { get; private set; }
  183. public DateTime SettingsChangesSentAt { get; private set; }
  184. public HTTP2Handler Parent { get; private set; }
  185. public HTTP2SettingsManager(HTTP2Handler parentHandler)
  186. {
  187. this.Parent = parentHandler;
  188. this.MySettings = new HTTP2SettingsRegistry(this, readOnly: true, treatItAsAlreadyChanged: false);
  189. this.InitiatedMySettings = new HTTP2SettingsRegistry(this, readOnly: false, treatItAsAlreadyChanged: true);
  190. this.RemoteSettings = new HTTP2SettingsRegistry(this, readOnly: true, treatItAsAlreadyChanged: false);
  191. this.SettingsChangesSentAt = DateTime.MinValue;
  192. }
  193. internal void Process(HTTP2FrameHeaderAndPayload frame, List<HTTP2FrameHeaderAndPayload> outgoingFrames)
  194. {
  195. if (frame.Type != HTTP2FrameTypes.SETTINGS)
  196. return;
  197. HTTP2SettingsFrame settingsFrame = HTTP2FrameHelper.ReadSettings(frame);
  198. if (HTTPManager.Logger.Level <= Logger.Loglevels.Information)
  199. HTTPManager.Logger.Information("HTTP2SettingsManager", "Processing Settings frame: " + settingsFrame.ToString(), this.Parent.Context);
  200. if ((settingsFrame.Flags & HTTP2SettingsFlags.ACK) == HTTP2SettingsFlags.ACK)
  201. {
  202. this.MySettings.Merge(this.InitiatedMySettings);
  203. this.SettingsChangesSentAt = DateTime.MinValue;
  204. }
  205. else
  206. {
  207. this.RemoteSettings.Merge(settingsFrame.Settings);
  208. outgoingFrames.Add(HTTP2FrameHelper.CreateACKSettingsFrame());
  209. }
  210. }
  211. internal void SendChanges(List<HTTP2FrameHeaderAndPayload> outgoingFrames)
  212. {
  213. if (this.SettingsChangesSentAt != DateTime.MinValue && DateTime.UtcNow - this.SettingsChangesSentAt >= TimeSpan.FromSeconds(10))
  214. {
  215. HTTPManager.Logger.Error("HTTP2SettingsManager", "No ACK received for settings frame!", this.Parent.Context);
  216. this.SettingsChangesSentAt = DateTime.MinValue;
  217. }
  218. // Upon receiving a SETTINGS frame with the ACK flag set, the sender of the altered parameters can rely on the setting having been applied.
  219. if (!this.InitiatedMySettings.IsChanged)
  220. return;
  221. outgoingFrames.Add(this.InitiatedMySettings.CreateFrame());
  222. this.SettingsChangesSentAt = DateTime.UtcNow;
  223. }
  224. }
  225. }
  226. #endif