settings.d 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382
  1. module vibe.http.internal.http2.settings;
  2. import vibe.http.internal.http2.multiplexing;
  3. import vibe.http.internal.http2.frame;
  4. import vibe.http.internal.http2.hpack.tables;
  5. import vibe.http.internal.http2.error;
  6. import vibe.http.server;
  7. import vibe.core.log;
  8. import vibe.core.net;
  9. import vibe.core.task;
  10. import vibe.internal.freelistref;
  11. import std.range;
  12. import std.base64;
  13. import std.traits;
  14. import std.bitmanip; // read from ubyte (decoding)
  15. import std.typecons;
  16. import std.conv : to;
  17. import std.exception : enforce;
  18. import std.algorithm : canFind; // alpn callback
  19. import std.variant : Algebraic;
  20. /*
  21. * 6.5.1. SETTINGS Format
  22. *
  23. * The payload of a SETTINGS frame consists of zero or more parameters,
  24. * each consisting of an unsigned 16-bit setting identifier and an
  25. * unsigned 32-bit value.
  26. *
  27. * +-------------------------------+
  28. * | IDentifier (16) |
  29. * +-------------------------------+-------------------------------+
  30. * | Value (32) |
  31. * +---------------------------------------------------------------+
  32. * Figure 10: Setting Format
  33. *
  34. * 6.5.2. Defined SETTINGS Parameters
  35. *
  36. * The following parameters are defined:
  37. *
  38. * SETTINGS_HEADER_TABLE_SIZE (0x1): Allows the sender to inform the
  39. * remote endpoint of the maximum size of the header compression
  40. * table used to decode header blocks, in octets. The encoder can
  41. * select any size equal to or less than this value by using
  42. * signaling specific to the header compression format inside a
  43. * header block (see [COMPRESSION]). The initial value is 4,096
  44. * octets.
  45. *
  46. * SETTINGS_ENABLE_PUSH (0x2): This setting can be used to disable
  47. * server push (Section 8.2). An endpoint MUST NOT send a
  48. * PUSH_PROMISE frame if it receives this parameter set to a value of
  49. * 0. An endpoint that has both set this parameter to 0 and had it
  50. * acknowledged MUST treat the receipt of a PUSH_PROMISE frame as a
  51. * connection error (Section 5.4.1) of type PROTOCOL_ERROR.
  52. *
  53. * The initial value is 1, which indicates that server push is
  54. * permitted. Any value other than 0 or 1 MUST be treated as a
  55. * connection error (Section 5.4.1) of type PROTOCOL_ERROR.
  56. *
  57. * SETTINGS_MAX_CONCURRENT_STREAMS (0x3): Indicates the maximum number
  58. * of concurrent streams that the sender will allow. This limit is
  59. * directional: it applies to the number of streams that the sender
  60. * permits the receiver to create. Initially, there is no limit to
  61. * this value. It is recommended that this value be no smaller than
  62. * 100, so as to not unnecessarily limit parallelism.
  63. *
  64. * A value of 0 for SETTINGS_MAX_CONCURRENT_STREAMS SHOULD NOT be
  65. * treated as special by endpoints. A zero value does prevent the
  66. * creation of new streams; however, this can also happen for any
  67. * limit that is exhausted with active streams. Servers SHOULD only
  68. * set a zero value for short durations; if a server does not wish to
  69. * accept requests, closing the connection is more appropriate.
  70. *
  71. * SETTINGS_INITIAL_WINDOW_SIZE (0x4): Indicates the sender's initial
  72. * window size (in octets) for stream-level flow control. The
  73. * initial value is 2^16-1 (65,535) octets.
  74. *
  75. * This setting affects the window size of all streams (see
  76. * Section 6.9.2).
  77. *
  78. * Values above the maximum flow-control window size of 2^31-1 MUST
  79. * be treated as a connection error (Section 5.4.1) of type
  80. * FLOW_CONTROL_ERROR.
  81. *
  82. * SETTINGS_MAX_FRAME_SIZE (0x5): Indicates the size of the largest
  83. * frame payload that the sender is willing to receive, in octets.
  84. *
  85. * The initial value is 2^14 (16,384) octets. The value advertised
  86. * by an endpoint MUST be between this initial value and the maximum
  87. * allowed frame size (2^24-1 or 16,777,215 octets), inclusive.
  88. * Values outside this range MUST be treated as a connection error
  89. * (Section 5.4.1) of type PROTOCOL_ERROR.
  90. *
  91. * SETTINGS_MAX_HEADER_LIST_SIZE (0x6): This advisory setting informs a
  92. * peer of the maximum size of header list that the sender is
  93. * prepared to accept, in octets. The value is based on the
  94. * uncompressed size of header fields, including the length of the
  95. * name and value in octets plus an overhead of 32 octets for each
  96. * header field.
  97. *
  98. * For any given request, a lower limit than what is advertised MAY
  99. * be enforced. The initial value of this setting is unlimited.
  100. *
  101. * An endpoint that receives a SETTINGS frame with any unknown or
  102. * unsupported identifier MUST ignore that setting.
  103. */
  104. //version = VibeForceALPN;
  105. alias HTTP2SettingID = ushort;
  106. alias HTTP2SettingValue = uint;
  107. // useful for bound checking
  108. const HTTP2SettingID minID = 0x1;
  109. const HTTP2SettingID maxID = 0x6;
  110. enum HTTP2SettingsParameter {
  111. headerTableSize = 0x1,
  112. enablePush = 0x2,
  113. maxConcurrentStreams = 0x3,
  114. initialWindowSize = 0x4,
  115. maxFrameSize = 0x5,
  116. maxHeaderListSize = 0x6
  117. }
  118. // UDAs
  119. struct HTTP2Setting {
  120. HTTP2SettingID id;
  121. string name;
  122. }
  123. // UDAs
  124. HTTP2Setting http2Setting(HTTP2SettingID id, string name) {
  125. if (!__ctfe) assert(false, "May only be used as a UDA");
  126. return HTTP2Setting(id, name);
  127. }
  128. struct HTTP2Settings {
  129. // no limit specified in the RFC
  130. @http2Setting(0x1, "SETTINGS_HEADER_TABLE_SIZE")
  131. HTTP2SettingValue headerTableSize = DEFAULT_DYNAMIC_TABLE_SIZE;
  132. // TODO {0,1} otherwise CONNECTION_ERROR
  133. @http2Setting(0x2, "SETTINGS_ENABLE_PUSH")
  134. HTTP2SettingValue enablePush = 1;
  135. /* set to the max value (UNLIMITED)
  136. * TODO manage connection with value == 0
  137. * might be closed as soon as possible
  138. */
  139. @http2Setting(0x3, "SETTINGS_MAX_CONCURRENT_STREAMS")
  140. //HTTP2SettingValue maxConcurrentStreams = HTTP2SettingValue.max; // lowered
  141. //to 2^16
  142. HTTP2SettingValue maxConcurrentStreams = 65536;
  143. // TODO FLOW_CONTROL_ERRROR on values > 2^31-1
  144. @http2Setting(0x4, "SETTINGS_INITIAL_WINDOW_SIZE")
  145. HTTP2SettingValue initialWindowSize = 65535;
  146. // TODO PROTOCOL_ERROR on values > 2^24-1
  147. @http2Setting(0x5, "SETTINGS_MAX_FRAME_SIZE")
  148. HTTP2SettingValue maxFrameSize = 16384;
  149. // set to the max value (UNLIMITED)
  150. @http2Setting(0x6, "SETTINGS_MAX_HEADER_LIST_SIZE")
  151. HTTP2SettingValue maxHeaderListSize = HTTP2SettingValue.max;
  152. /**
  153. * Use Decoder to decode a string and set the corresponding settings
  154. * The decoder must follow the base64url encoding
  155. * `bool` since the handler must ignore the Upgrade request
  156. * if the settings cannot be decoded
  157. */
  158. bool decode(alias Decoder)(string encodedSettings) @safe
  159. if (isInstanceOf!(Base64Impl, Decoder))
  160. {
  161. ubyte[] uset;
  162. try {
  163. // the Base64URL decoder throws a Base64exception if it fails
  164. uset = Decoder.decode(encodedSettings);
  165. enforce!Base64Exception(uset.length % 6 == 0, "Invalid SETTINGS payload length");
  166. } catch (Base64Exception e) {
  167. logDiagnostic("Failed to decode SETTINGS payload: " ~ e.msg);
  168. return false;
  169. }
  170. // set values
  171. while(!uset.empty) m_set(uset.read!HTTP2SettingID, uset.read!HTTP2SettingValue);
  172. return true;
  173. }
  174. /*
  175. * Set parameter 'id' to 'value'
  176. * private overload for decoded parameters assignment
  177. */
  178. void set(HTTP2SettingID id)(HTTP2SettingValue value) @safe
  179. if(id <= maxID && id >= minID)
  180. {
  181. m_set(id,value);
  182. }
  183. private void m_set(HTTP2SettingID id, HTTP2SettingValue value) @safe
  184. {
  185. // must use labeled break w. static foreach
  186. assign: switch(id) {
  187. default: logWarn("Unsupported SETTINGS code:" ~ to!string(id)); return;
  188. static foreach(c; __traits(allMembers, HTTP2SettingsParameter)) {
  189. case __traits(getMember, HTTP2SettingsParameter, c):
  190. __traits(getMember, this, c) = value;
  191. break assign;
  192. }
  193. }
  194. }
  195. }
  196. void serializeSettings(R)(ref R dst, HTTP2Settings settings) @safe @nogc
  197. {
  198. static foreach(s; __traits(allMembers, HTTP2Settings)) {
  199. static if(is(typeof(__traits(getMember, HTTP2Settings, s)) == HTTP2SettingValue)) {
  200. mixin("dst.putBytes!2((getUDAs!(settings."~s~",HTTP2Setting)[0]).id);");
  201. mixin("dst.putBytes!4(settings."~s~");");
  202. }
  203. }
  204. }
  205. void unpackSettings(R)(ref HTTP2Settings settings, R src) @safe
  206. {
  207. while(!src.empty) {
  208. auto id = src.takeExactly(2).fromBytes(2);
  209. src.popFrontN(2);
  210. // invalid IDs: ignore setting
  211. if(!(id >= minID && id <= maxID)) {
  212. src.popFrontN(4);
  213. continue;
  214. }
  215. static foreach(s; __traits(allMembers, HTTP2Settings)) {
  216. static if(is(typeof(__traits(getMember, HTTP2Settings, s)) == HTTP2SettingValue)) {
  217. mixin("if(id == ((getUDAs!(settings."~s~",HTTP2Setting)[0]).id)) {
  218. settings."~s~" = src.takeExactly(4).fromBytes(4);
  219. src.popFrontN(4);
  220. }");
  221. }
  222. }
  223. }
  224. enforceHTTP2(settings.enablePush == 0 || settings.enablePush == 1,
  225. "Invalid value for ENABLE_PUSH setting.", HTTP2Error.PROTOCOL_ERROR);
  226. enforceHTTP2(settings.initialWindowSize < (1 << 31),
  227. "Invalid value for INITIAL_WINDOW_SIZE setting.", HTTP2Error.FLOW_CONTROL_ERROR);
  228. enforceHTTP2(settings.maxFrameSize >= (1 << 14) && settings.maxFrameSize < (1 << 24),
  229. "Invalid value for MAX_FRAME_SIZE setting.", HTTP2Error.FLOW_CONTROL_ERROR);
  230. }
  231. unittest {
  232. HTTP2Settings settings;
  233. // retrieve a value
  234. assert(settings.headerTableSize == 4096);
  235. //set a SETTINGS value using the enum table
  236. settings.set!(HTTP2SettingsParameter.headerTableSize)(2048);
  237. assert(settings.headerTableSize == 2048);
  238. //set a SETTINGS value using the code directly
  239. settings.set!0x4(1024);
  240. assert(settings.initialWindowSize == 1024);
  241. // SHOULD NOT COMPILE
  242. //settings.set!0x7(1);
  243. // get a HTTP2Setting struct containing the code and the parameter name
  244. import std.traits : getUDAs;
  245. assert(getUDAs!(settings.headerTableSize, HTTP2Setting)[0] == HTTP2Setting(0x1,
  246. "SETTINGS_HEADER_TABLE_SIZE"));
  247. // test decoding from base64url
  248. // h2settings contains:
  249. // 0x2 -> 0
  250. // 0x3 -> 100
  251. // 0x4 -> 1073741824
  252. string h2settings = "AAMAAABkAARAAAAAAAIAAAAA";
  253. assert(settings.decode!Base64URL(h2settings));
  254. assert(settings.enablePush == 0);
  255. assert(settings.maxConcurrentStreams == 100);
  256. assert(settings.initialWindowSize == 1073741824);
  257. // should throw a Base64Exception error (caught) and a logWarn
  258. assert(!settings.decode!Base64URL("a|b+*-c"));
  259. }
  260. /** Context is initialized on each new connection
  261. * it MUST remain consistent between streams of the same connection
  262. * Contains HTTP2Settings negotiated during handshake
  263. * TODO set of USED streams for proper HTTP2ConnectionStream initialization
  264. */
  265. final class HTTP2ServerContext
  266. {
  267. private {
  268. HTTPServerContext m_context;
  269. Nullable!HTTP2Settings m_settings;
  270. uint m_sid = 0;
  271. FreeListRef!IndexingTable m_table;
  272. FreeListRef!HTTP2Multiplexer m_multiplexer;
  273. bool m_initializedT = false;
  274. bool m_initializedM = false;
  275. }
  276. alias m_context this;
  277. // used to mantain the first request in case of `h2c` protocol switching
  278. ubyte[] resFrame = void;
  279. this(HTTPServerContext ctx, HTTP2Settings settings) @safe
  280. {
  281. m_context = ctx;
  282. m_settings = settings;
  283. }
  284. this(HTTPServerContext ctx) @safe
  285. {
  286. m_context = ctx;
  287. }
  288. @property auto ref table() @safe { return m_table.get; }
  289. @property bool hasTable() @safe { return m_initializedT; }
  290. @property void table(T)(T table) @safe
  291. if(is(T == typeof(m_table)))
  292. {
  293. assert(!m_initializedT);
  294. m_table = table;
  295. m_initializedT = true;
  296. }
  297. @property auto ref multiplexer() @safe { return m_multiplexer.get; }
  298. @property bool hasMultiplexer() @safe { return m_initializedM; }
  299. @property void multiplexer(T)(T multiplexer) @safe
  300. if(is(T == typeof(m_multiplexer)))
  301. {
  302. assert(!m_initializedM);
  303. m_multiplexer = multiplexer;
  304. m_initializedM = true;
  305. }
  306. @property HTTPServerContext h1context() @safe @nogc { return m_context; }
  307. @property uint next_sid() @safe @nogc { return m_sid; }
  308. @property void next_sid(uint sid) @safe @nogc { m_sid = sid; }
  309. @property ref HTTP2Settings settings() @safe @nogc
  310. {
  311. assert(!m_settings.isNull);
  312. return m_settings.get;
  313. }
  314. @property void settings(ref HTTP2Settings settings) @safe
  315. {
  316. assert(m_settings.isNull);
  317. m_settings = settings;
  318. () @trusted {
  319. if (settings.headerTableSize != 4096) {
  320. table.updateSize(settings.headerTableSize);
  321. }
  322. } ();
  323. }
  324. }