123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382 |
- module vibe.http.internal.http2.settings;
- import vibe.http.internal.http2.multiplexing;
- import vibe.http.internal.http2.frame;
- import vibe.http.internal.http2.hpack.tables;
- import vibe.http.internal.http2.error;
- import vibe.http.server;
- import vibe.core.log;
- import vibe.core.net;
- import vibe.core.task;
- import vibe.internal.freelistref;
- import std.range;
- import std.base64;
- import std.traits;
- import std.bitmanip; // read from ubyte (decoding)
- import std.typecons;
- import std.conv : to;
- import std.exception : enforce;
- import std.algorithm : canFind; // alpn callback
- import std.variant : Algebraic;
- /*
- * 6.5.1. SETTINGS Format
- *
- * The payload of a SETTINGS frame consists of zero or more parameters,
- * each consisting of an unsigned 16-bit setting identifier and an
- * unsigned 32-bit value.
- *
- * +-------------------------------+
- * | IDentifier (16) |
- * +-------------------------------+-------------------------------+
- * | Value (32) |
- * +---------------------------------------------------------------+
- * Figure 10: Setting Format
- *
- * 6.5.2. Defined SETTINGS Parameters
- *
- * The following parameters are defined:
- *
- * SETTINGS_HEADER_TABLE_SIZE (0x1): 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.
- *
- * SETTINGS_ENABLE_PUSH (0x2): 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.
- *
- * SETTINGS_MAX_CONCURRENT_STREAMS (0x3): 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.
- *
- * SETTINGS_INITIAL_WINDOW_SIZE (0x4): 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.
- *
- * SETTINGS_MAX_FRAME_SIZE (0x5): 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.
- *
- * SETTINGS_MAX_HEADER_LIST_SIZE (0x6): 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.
- *
- * An endpoint that receives a SETTINGS frame with any unknown or
- * unsupported identifier MUST ignore that setting.
- */
- //version = VibeForceALPN;
- alias HTTP2SettingID = ushort;
- alias HTTP2SettingValue = uint;
- // useful for bound checking
- const HTTP2SettingID minID = 0x1;
- const HTTP2SettingID maxID = 0x6;
- enum HTTP2SettingsParameter {
- headerTableSize = 0x1,
- enablePush = 0x2,
- maxConcurrentStreams = 0x3,
- initialWindowSize = 0x4,
- maxFrameSize = 0x5,
- maxHeaderListSize = 0x6
- }
- // UDAs
- struct HTTP2Setting {
- HTTP2SettingID id;
- string name;
- }
- // UDAs
- HTTP2Setting http2Setting(HTTP2SettingID id, string name) {
- if (!__ctfe) assert(false, "May only be used as a UDA");
- return HTTP2Setting(id, name);
- }
- struct HTTP2Settings {
- // no limit specified in the RFC
- @http2Setting(0x1, "SETTINGS_HEADER_TABLE_SIZE")
- HTTP2SettingValue headerTableSize = DEFAULT_DYNAMIC_TABLE_SIZE;
- // TODO {0,1} otherwise CONNECTION_ERROR
- @http2Setting(0x2, "SETTINGS_ENABLE_PUSH")
- HTTP2SettingValue enablePush = 1;
- /* set to the max value (UNLIMITED)
- * TODO manage connection with value == 0
- * might be closed as soon as possible
- */
- @http2Setting(0x3, "SETTINGS_MAX_CONCURRENT_STREAMS")
- //HTTP2SettingValue maxConcurrentStreams = HTTP2SettingValue.max; // lowered
- //to 2^16
- HTTP2SettingValue maxConcurrentStreams = 65536;
- // TODO FLOW_CONTROL_ERRROR on values > 2^31-1
- @http2Setting(0x4, "SETTINGS_INITIAL_WINDOW_SIZE")
- HTTP2SettingValue initialWindowSize = 65535;
- // TODO PROTOCOL_ERROR on values > 2^24-1
- @http2Setting(0x5, "SETTINGS_MAX_FRAME_SIZE")
- HTTP2SettingValue maxFrameSize = 16384;
- // set to the max value (UNLIMITED)
- @http2Setting(0x6, "SETTINGS_MAX_HEADER_LIST_SIZE")
- HTTP2SettingValue maxHeaderListSize = HTTP2SettingValue.max;
- /**
- * Use Decoder to decode a string and set the corresponding settings
- * The decoder must follow the base64url encoding
- * `bool` since the handler must ignore the Upgrade request
- * if the settings cannot be decoded
- */
- bool decode(alias Decoder)(string encodedSettings) @safe
- if (isInstanceOf!(Base64Impl, Decoder))
- {
- ubyte[] uset;
- try {
- // the Base64URL decoder throws a Base64exception if it fails
- uset = Decoder.decode(encodedSettings);
- enforce!Base64Exception(uset.length % 6 == 0, "Invalid SETTINGS payload length");
- } catch (Base64Exception e) {
- logDiagnostic("Failed to decode SETTINGS payload: " ~ e.msg);
- return false;
- }
- // set values
- while(!uset.empty) m_set(uset.read!HTTP2SettingID, uset.read!HTTP2SettingValue);
- return true;
- }
- /*
- * Set parameter 'id' to 'value'
- * private overload for decoded parameters assignment
- */
- void set(HTTP2SettingID id)(HTTP2SettingValue value) @safe
- if(id <= maxID && id >= minID)
- {
- m_set(id,value);
- }
- private void m_set(HTTP2SettingID id, HTTP2SettingValue value) @safe
- {
- // must use labeled break w. static foreach
- assign: switch(id) {
- default: logWarn("Unsupported SETTINGS code:" ~ to!string(id)); return;
- static foreach(c; __traits(allMembers, HTTP2SettingsParameter)) {
- case __traits(getMember, HTTP2SettingsParameter, c):
- __traits(getMember, this, c) = value;
- break assign;
- }
- }
- }
- }
- void serializeSettings(R)(ref R dst, HTTP2Settings settings) @safe @nogc
- {
- static foreach(s; __traits(allMembers, HTTP2Settings)) {
- static if(is(typeof(__traits(getMember, HTTP2Settings, s)) == HTTP2SettingValue)) {
- mixin("dst.putBytes!2((getUDAs!(settings."~s~",HTTP2Setting)[0]).id);");
- mixin("dst.putBytes!4(settings."~s~");");
- }
- }
- }
- void unpackSettings(R)(ref HTTP2Settings settings, R src) @safe
- {
- while(!src.empty) {
- auto id = src.takeExactly(2).fromBytes(2);
- src.popFrontN(2);
- // invalid IDs: ignore setting
- if(!(id >= minID && id <= maxID)) {
- src.popFrontN(4);
- continue;
- }
- static foreach(s; __traits(allMembers, HTTP2Settings)) {
- static if(is(typeof(__traits(getMember, HTTP2Settings, s)) == HTTP2SettingValue)) {
- mixin("if(id == ((getUDAs!(settings."~s~",HTTP2Setting)[0]).id)) {
- settings."~s~" = src.takeExactly(4).fromBytes(4);
- src.popFrontN(4);
- }");
- }
- }
- }
- enforceHTTP2(settings.enablePush == 0 || settings.enablePush == 1,
- "Invalid value for ENABLE_PUSH setting.", HTTP2Error.PROTOCOL_ERROR);
- enforceHTTP2(settings.initialWindowSize < (1 << 31),
- "Invalid value for INITIAL_WINDOW_SIZE setting.", HTTP2Error.FLOW_CONTROL_ERROR);
- enforceHTTP2(settings.maxFrameSize >= (1 << 14) && settings.maxFrameSize < (1 << 24),
- "Invalid value for MAX_FRAME_SIZE setting.", HTTP2Error.FLOW_CONTROL_ERROR);
- }
- unittest {
- HTTP2Settings settings;
- // retrieve a value
- assert(settings.headerTableSize == 4096);
- //set a SETTINGS value using the enum table
- settings.set!(HTTP2SettingsParameter.headerTableSize)(2048);
- assert(settings.headerTableSize == 2048);
- //set a SETTINGS value using the code directly
- settings.set!0x4(1024);
- assert(settings.initialWindowSize == 1024);
- // SHOULD NOT COMPILE
- //settings.set!0x7(1);
- // get a HTTP2Setting struct containing the code and the parameter name
- import std.traits : getUDAs;
- assert(getUDAs!(settings.headerTableSize, HTTP2Setting)[0] == HTTP2Setting(0x1,
- "SETTINGS_HEADER_TABLE_SIZE"));
- // test decoding from base64url
- // h2settings contains:
- // 0x2 -> 0
- // 0x3 -> 100
- // 0x4 -> 1073741824
- string h2settings = "AAMAAABkAARAAAAAAAIAAAAA";
- assert(settings.decode!Base64URL(h2settings));
- assert(settings.enablePush == 0);
- assert(settings.maxConcurrentStreams == 100);
- assert(settings.initialWindowSize == 1073741824);
- // should throw a Base64Exception error (caught) and a logWarn
- assert(!settings.decode!Base64URL("a|b+*-c"));
- }
- /** Context is initialized on each new connection
- * it MUST remain consistent between streams of the same connection
- * Contains HTTP2Settings negotiated during handshake
- * TODO set of USED streams for proper HTTP2ConnectionStream initialization
- */
- final class HTTP2ServerContext
- {
- private {
- HTTPServerContext m_context;
- Nullable!HTTP2Settings m_settings;
- uint m_sid = 0;
- FreeListRef!IndexingTable m_table;
- FreeListRef!HTTP2Multiplexer m_multiplexer;
- bool m_initializedT = false;
- bool m_initializedM = false;
- }
- alias m_context this;
- // used to mantain the first request in case of `h2c` protocol switching
- ubyte[] resFrame = void;
- this(HTTPServerContext ctx, HTTP2Settings settings) @safe
- {
- m_context = ctx;
- m_settings = settings;
- }
- this(HTTPServerContext ctx) @safe
- {
- m_context = ctx;
- }
- @property auto ref table() @safe { return m_table.get; }
- @property bool hasTable() @safe { return m_initializedT; }
- @property void table(T)(T table) @safe
- if(is(T == typeof(m_table)))
- {
- assert(!m_initializedT);
- m_table = table;
- m_initializedT = true;
- }
- @property auto ref multiplexer() @safe { return m_multiplexer.get; }
- @property bool hasMultiplexer() @safe { return m_initializedM; }
- @property void multiplexer(T)(T multiplexer) @safe
- if(is(T == typeof(m_multiplexer)))
- {
- assert(!m_initializedM);
- m_multiplexer = multiplexer;
- m_initializedM = true;
- }
- @property HTTPServerContext h1context() @safe @nogc { return m_context; }
- @property uint next_sid() @safe @nogc { return m_sid; }
- @property void next_sid(uint sid) @safe @nogc { m_sid = sid; }
- @property ref HTTP2Settings settings() @safe @nogc
- {
- assert(!m_settings.isNull);
- return m_settings.get;
- }
- @property void settings(ref HTTP2Settings settings) @safe
- {
- assert(m_settings.isNull);
- m_settings = settings;
- () @trusted {
- if (settings.headerTableSize != 4096) {
- table.updateSize(settings.headerTableSize);
- }
- } ();
- }
- }
|