You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
277 lines
12 KiB
277 lines
12 KiB
#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
|
|
|