123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277 |
- #if (!UNITY_WEBGL || UNITY_EDITOR) && !BESTHTTP_DISABLE_ALTERNATE_SSL && !BESTHTTP_DISABLE_HTTP2
- using System;
- using System.Collections.Generic;
- namespace BestHTTP.Connections.HTTP2
- {
- // https://httpwg.org/specs/rfc7540.html#iana-settings
- public enum HTTP2Settings : ushort
- {
- /// <summary>
- /// Allows the sender to inform the remote endpoint of the maximum size of the
- /// header compression table used to decode header blocks, in octets.
- /// The encoder can select any size equal to or less than this value
- /// by using signaling specific to the header compression format inside a header block (see [COMPRESSION]).
- /// The initial value is 4,096 octets.
- /// </summary>
- HEADER_TABLE_SIZE = 0x01,
- /// <summary>
- /// This setting can be used to disable server push (Section 8.2).
- /// An endpoint MUST NOT send a PUSH_PROMISE frame if it receives this parameter set to a value of 0.
- /// An endpoint that has both set this parameter to 0 and had it acknowledged MUST treat the receipt of a
- /// PUSH_PROMISE frame as a connection error (Section 5.4.1) of type PROTOCOL_ERROR.
- ///
- /// The initial value is 1, which indicates that server push is permitted.
- /// Any value other than 0 or 1 MUST be treated as a connection error (Section 5.4.1) of type PROTOCOL_ERROR.
- /// </summary>
- ENABLE_PUSH = 0x02,
- /// <summary>
- /// Indicates the maximum number of concurrent streams that the sender will allow. This limit is directional:
- /// it applies to the number of streams that the sender permits the receiver to create.
- /// Initially, there is no limit to this value. It is recommended that this value be no smaller than 100,
- /// so as to not unnecessarily limit parallelism.
- ///
- /// A value of 0 for SETTINGS_MAX_CONCURRENT_STREAMS SHOULD NOT be treated as special by endpoints.
- /// A zero value does prevent the creation of new streams;
- /// however, this can also happen for any limit that is exhausted with active streams.
- /// Servers SHOULD only set a zero value for short durations; if a server does not wish to accept requests,
- /// closing the connection is more appropriate.
- /// </summary>
- MAX_CONCURRENT_STREAMS = 0x03,
- /// <summary>
- /// Indicates the sender's initial window size (in octets) for stream-level flow control.
- /// The initial value is 2^16-1 (65,535) octets.
- ///
- /// This setting affects the window size of all streams (see Section 6.9.2).
- ///
- /// Values above the maximum flow-control window size of 2^31-1 MUST be treated as a connection error
- /// (Section 5.4.1) of type FLOW_CONTROL_ERROR.
- /// </summary>
- INITIAL_WINDOW_SIZE = 0x04,
- /// <summary>
- /// Indicates the size of the largest frame payload that the sender is willing to receive, in octets.
- ///
- /// The initial value is 2^14 (16,384) octets.
- /// The value advertised by an endpoint MUST be between this initial value and the maximum allowed frame size
- /// (2^24-1 or 16,777,215 octets), inclusive.
- /// Values outside this range MUST be treated as a connection error (Section 5.4.1) of type PROTOCOL_ERROR.
- /// </summary>
- MAX_FRAME_SIZE = 0x05,
- /// <summary>
- /// This advisory setting informs a peer of the maximum size of header list that the sender is prepared to accept, in octets.
- /// The value is based on the uncompressed size of header fields,
- /// including the length of the name and value in octets plus an overhead of 32 octets for each header field.
- ///
- /// For any given request, a lower limit than what is advertised MAY be enforced. The initial value of this setting is unlimited.
- /// </summary>
- MAX_HEADER_LIST_SIZE = 0x06,
- RESERVED = 0x07,
- /// <summary>
- /// https://tools.ietf.org/html/rfc8441
- /// 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.
- /// Receipt of this parameter by a server does not have any impact.
- ///
- /// A sender MUST NOT send a SETTINGS_ENABLE_CONNECT_PROTOCOL parameter with the value of 0 after previously sending a value of 1.
- /// </summary>
- ENABLE_CONNECT_PROTOCOL = 0x08
- }
- public sealed class HTTP2SettingsRegistry
- {
- public bool IsReadOnly { get; private set; }
- public Action<HTTP2SettingsRegistry, HTTP2Settings, UInt32, UInt32> OnSettingChangedEvent;
- private UInt32[] values;
- private bool[] changeFlags;
- public UInt32 this[HTTP2Settings setting]
- {
- get { return this.values[(ushort)setting]; }
- set
- {
- if (this.IsReadOnly)
- throw new NotSupportedException("It's a read-only one!");
- ushort idx = (ushort)setting;
- // https://httpwg.org/specs/rfc7540.html#SettingValues
- // An endpoint that receives a SETTINGS frame with any unknown or unsupported identifier MUST ignore that setting.
- if (idx == 0 || idx >= this.values.Length)
- return;
- UInt32 oldValue = this.values[idx];
- if (oldValue != value)
- {
- this.values[idx] = value;
- this.changeFlags[idx] = true;
- IsChanged = true;
- if (this.OnSettingChangedEvent != null)
- this.OnSettingChangedEvent(this, setting, oldValue, value);
- }
- }
- }
- public bool IsChanged { get; private set; }
- private HTTP2SettingsManager _parent;
- public HTTP2SettingsRegistry(HTTP2SettingsManager parent, bool readOnly, bool treatItAsAlreadyChanged)
- {
- this._parent = parent;
- this.values = new UInt32[HTTP2SettingsManager.SettingsCount];
- this.IsReadOnly = readOnly;
- if (!this.IsReadOnly)
- this.changeFlags = new bool[HTTP2SettingsManager.SettingsCount];
- // Set default values (https://httpwg.org/specs/rfc7540.html#iana-settings)
- this.values[(UInt16)HTTP2Settings.HEADER_TABLE_SIZE] = 4096;
- this.values[(UInt16)HTTP2Settings.ENABLE_PUSH] = 1;
- this.values[(UInt16)HTTP2Settings.MAX_CONCURRENT_STREAMS] = 128;
- this.values[(UInt16)HTTP2Settings.INITIAL_WINDOW_SIZE] = 65535;
- this.values[(UInt16)HTTP2Settings.MAX_FRAME_SIZE] = 16384;
- this.values[(UInt16)HTTP2Settings.MAX_HEADER_LIST_SIZE] = UInt32.MaxValue; // infinite
- if (this.IsChanged = treatItAsAlreadyChanged)
- {
- this.changeFlags[(UInt16)HTTP2Settings.MAX_CONCURRENT_STREAMS] = true;
- }
- }
- public void Merge(List<KeyValuePair<HTTP2Settings, UInt32>> settings)
- {
- if (settings == null)
- return;
- for (int i = 0; i < settings.Count; ++i)
- {
- HTTP2Settings setting = settings[i].Key;
- UInt16 key = (UInt16)setting;
- UInt32 value = settings[i].Value;
- if (key > 0 && key <= HTTP2SettingsManager.SettingsCount)
- {
- UInt32 oldValue = this.values[key];
- this.values[key] = value;
- if (oldValue != value && this.OnSettingChangedEvent != null)
- this.OnSettingChangedEvent(this, setting, oldValue, value);
- if (HTTPManager.Logger.Level <= Logger.Loglevels.All)
- HTTPManager.Logger.Information("HTTP2SettingsRegistry", string.Format("Merge {0}({1}) = {2}", setting, key, value), this._parent.Parent.Context);
- }
- }
- }
- public void Merge(HTTP2SettingsRegistry from)
- {
- if (this.values != null)
- this.values = new uint[from.values.Length];
- for (int i = 0; i < this.values.Length; ++i)
- this.values[i] = from.values[i];
- }
- internal HTTP2FrameHeaderAndPayload CreateFrame()
- {
- List<KeyValuePair<HTTP2Settings, UInt32>> keyValuePairs = new List<KeyValuePair<HTTP2Settings, uint>>(HTTP2SettingsManager.SettingsCount);
- for (int i = 1; i < HTTP2SettingsManager.SettingsCount; ++i)
- if (this.changeFlags[i])
- {
- keyValuePairs.Add(new KeyValuePair<HTTP2Settings, uint>((HTTP2Settings)i, this[(HTTP2Settings)i]));
- this.changeFlags[i] = false;
- }
- this.IsChanged = false;
- return HTTP2FrameHelper.CreateSettingsFrame(keyValuePairs);
- }
- }
- public sealed class HTTP2SettingsManager
- {
- public static readonly int SettingsCount = Enum.GetNames(typeof(HTTP2Settings)).Length + 1;
- /// <summary>
- /// This is the ACKd or default settings that we sent to the server.
- /// </summary>
- public HTTP2SettingsRegistry MySettings { get; private set; }
- /// <summary>
- /// This is the setting that can be changed. It will be sent to the server ASAP, and when ACKd, it will be copied
- /// to MySettings.
- /// </summary>
- public HTTP2SettingsRegistry InitiatedMySettings { get; private set; }
- /// <summary>
- /// Settings of the remote peer
- /// </summary>
- public HTTP2SettingsRegistry RemoteSettings { get; private set; }
- public DateTime SettingsChangesSentAt { get; private set; }
- public HTTP2Handler Parent { get; private set; }
- public HTTP2SettingsManager(HTTP2Handler parentHandler)
- {
- this.Parent = parentHandler;
- this.MySettings = new HTTP2SettingsRegistry(this, readOnly: true, treatItAsAlreadyChanged: false);
- this.InitiatedMySettings = new HTTP2SettingsRegistry(this, readOnly: false, treatItAsAlreadyChanged: true);
- this.RemoteSettings = new HTTP2SettingsRegistry(this, readOnly: true, treatItAsAlreadyChanged: false);
- this.SettingsChangesSentAt = DateTime.MinValue;
- }
- internal void Process(HTTP2FrameHeaderAndPayload frame, List<HTTP2FrameHeaderAndPayload> outgoingFrames)
- {
- if (frame.Type != HTTP2FrameTypes.SETTINGS)
- return;
- HTTP2SettingsFrame settingsFrame = HTTP2FrameHelper.ReadSettings(frame);
- if (HTTPManager.Logger.Level <= Logger.Loglevels.Information)
- HTTPManager.Logger.Information("HTTP2SettingsManager", "Processing Settings frame: " + settingsFrame.ToString(), this.Parent.Context);
- if ((settingsFrame.Flags & HTTP2SettingsFlags.ACK) == HTTP2SettingsFlags.ACK)
- {
- this.MySettings.Merge(this.InitiatedMySettings);
- this.SettingsChangesSentAt = DateTime.MinValue;
- }
- else
- {
- this.RemoteSettings.Merge(settingsFrame.Settings);
- outgoingFrames.Add(HTTP2FrameHelper.CreateACKSettingsFrame());
- }
- }
- internal void SendChanges(List<HTTP2FrameHeaderAndPayload> outgoingFrames)
- {
- if (this.SettingsChangesSentAt != DateTime.MinValue && DateTime.UtcNow - this.SettingsChangesSentAt >= TimeSpan.FromSeconds(10))
- {
- HTTPManager.Logger.Error("HTTP2SettingsManager", "No ACK received for settings frame!", this.Parent.Context);
- this.SettingsChangesSentAt = DateTime.MinValue;
- }
- // Upon receiving a SETTINGS frame with the ACK flag set, the sender of the altered parameters can rely on the setting having been applied.
- if (!this.InitiatedMySettings.IsChanged)
- return;
- outgoingFrames.Add(this.InitiatedMySettings.CreateFrame());
- this.SettingsChangesSentAt = DateTime.UtcNow;
- }
- }
- }
- #endif
|