HTTP2SettingsRegistry.cs 14 KB

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