frame.d 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471
  1. module vibe.http.internal.http2.frame;
  2. import vibe.http.internal.http2.settings;
  3. import vibe.http.internal.http2.error;
  4. import vibe.internal.array;
  5. import std.typecons;
  6. import std.traits;
  7. import std.range;
  8. import std.array;
  9. import std.exception;
  10. import std.algorithm.iteration;
  11. import std.algorithm.mutation;
  12. /** This module implements HTTP/2 Frames, as defined in RFC 7540 under:
  13. *
  14. * Section 4: Frame overview, Frame header composition (octets) and their meaning
  15. * https://tools.ietf.org/html/rfc7540#section-4
  16. *
  17. * Section 6: Frame definition according to Frame Type
  18. * https://tools.ietf.org/html/rfc7540#section-6
  19. */
  20. enum uint HTTP2HeaderLength = 9;
  21. enum HTTP2FrameType {
  22. DATA = 0x0,
  23. HEADERS = 0x1,
  24. PRIORITY = 0x2,
  25. RST_STREAM = 0x3,
  26. SETTINGS = 0x4,
  27. PUSH_PROMISE = 0x5,
  28. PING = 0x6,
  29. GOAWAY = 0x7,
  30. WINDOW_UPDATE = 0x8,
  31. CONTINUATION = 0x9
  32. }
  33. /*** FRAME PARSING ***/
  34. /// updated by `unpackHTTP2Frame`
  35. struct HTTP2FrameStreamDependency {
  36. bool exclusive = false;
  37. bool isPushPromise = false;
  38. uint streamId = 0;
  39. ubyte weight = 0;
  40. @property bool isSet() @safe @nogc { return streamId != 0; }
  41. void fill(R)(ref R src) @safe @nogc
  42. if(is(ElementType!R : ubyte))
  43. {
  44. uint first = src.takeExactly(4).fromBytes(4);
  45. exclusive = first & (cast(ulong)1 << 32);
  46. streamId = first & ((cast(ulong)1 << 32) - 1);
  47. src.popFrontN(4);
  48. if(!isPushPromise) {
  49. weight = src.front;
  50. src.popFront();
  51. }
  52. }
  53. }
  54. /** unpacks a frame putting the payload in `payloadDst` and returning the header
  55. * implements the checks required for each frame type (Section 6 of HTTP/2 RFC)
  56. *
  57. * Invoked by a possible HTTP/2 request handler, the payload is meant to be handled by
  58. * the caller.
  59. *
  60. * Note: @nogc-compatible as long as payloadDst.put is @nogc (AllocAppender.put isn't)
  61. */
  62. HTTP2FrameHeader unpackHTTP2Frame(R,T)(ref R payloadDst, T src, ref bool endStream, ref bool endHeaders, ref bool ack, ref HTTP2FrameStreamDependency sdep) @safe
  63. {
  64. auto header = unpackHTTP2FrameHeader(src);
  65. unpackHTTP2Frame(payloadDst, src, header, endStream, endHeaders, ack, sdep);
  66. return header;
  67. }
  68. /// DITTO
  69. void unpackHTTP2Frame(R,T)(ref R payloadDst, T src, HTTP2FrameHeader header, ref bool endStream, ref bool endHeaders, ref bool ack, ref HTTP2FrameStreamDependency sdep) @safe
  70. {
  71. size_t len = header.payloadLength;
  72. switch(header.type) {
  73. case HTTP2FrameType.DATA:
  74. if(header.flags & 0x8) { // padding is set, first bit is pad length
  75. len -= cast(size_t)src.front + 1;
  76. src.popFront();
  77. enforceHTTP2(src.length >= len, "Invalid pad length", HTTP2Error.PROTOCOL_ERROR);
  78. }
  79. foreach(b; src.takeExactly(len)) {
  80. payloadDst.put(b);
  81. src.popFront();
  82. }
  83. src.popFrontN(header.payloadLength - len - 1); // remove padding
  84. if(header.flags & 0x1) endStream = true;
  85. break;
  86. case HTTP2FrameType.HEADERS:
  87. if(header.flags & 0x8) { // padding is set, first bit is pad length
  88. len -= cast(size_t)src.front + 1;
  89. src.popFront();
  90. enforceHTTP2(src.length >= len, "Invalid pad length", HTTP2Error.PROTOCOL_ERROR);
  91. }
  92. if(header.flags & 0x20) { // priority is set, fill `sdep`
  93. sdep.fill(src);
  94. len -= 5;
  95. }
  96. foreach(b; src.takeExactly(len)) {
  97. payloadDst.put(b);
  98. src.popFront();
  99. }
  100. src.popFrontN(header.payloadLength - len - 1); // remove padding
  101. if(header.flags & 0x1) endStream = true;
  102. if(header.flags & 0x4) endHeaders = true;
  103. break;
  104. case HTTP2FrameType.PRIORITY:
  105. enforceHTTP2(len == 5, "Invalid PRIORITY Frame", HTTP2Error.PROTOCOL_ERROR);
  106. sdep.fill(src);
  107. break;
  108. case HTTP2FrameType.RST_STREAM:
  109. enforceHTTP2(len == 4, "Invalid RST_STREAM Frame", HTTP2Error.PROTOCOL_ERROR);
  110. foreach(b; src.takeExactly(len)) {
  111. payloadDst.put(b);
  112. src.popFront();
  113. }
  114. break;
  115. case HTTP2FrameType.SETTINGS:
  116. enforceHTTP2(len % 6 == 0, "Invalid SETTINGS Frame (FRAME_SIZE error)", HTTP2Error.PROTOCOL_ERROR);
  117. enforceHTTP2(header.streamId == 0, "Invalid streamId for SETTINGS Frame", HTTP2Error.PROTOCOL_ERROR);
  118. if(header.flags & 0x1) { // this is an ACK frame
  119. enforceHTTP2(len == 0, "Invalid SETTINGS ACK Frame (FRAME_SIZE error)", HTTP2Error.PROTOCOL_ERROR);
  120. ack = true;
  121. break;
  122. }
  123. foreach(b; src.takeExactly(len)) {
  124. payloadDst.put(b);
  125. src.popFront();
  126. }
  127. break;
  128. case HTTP2FrameType.PUSH_PROMISE:
  129. if(header.flags & 0x8) { // padding is set, first bit is pad length
  130. len -= cast(size_t)src.front + 1;
  131. src.popFront();
  132. enforceHTTP2(src.length >= len, "Invalid pad length", HTTP2Error.PROTOCOL_ERROR);
  133. }
  134. sdep.isPushPromise = true;
  135. sdep.fill(src);
  136. len -= 4;
  137. foreach(b; src.takeExactly(len)) {
  138. payloadDst.put(b);
  139. src.popFront();
  140. }
  141. src.popFrontN(header.payloadLength - len - 1); // remove padding
  142. if(header.flags & 0x4) endHeaders = true;
  143. break;
  144. case HTTP2FrameType.PING:
  145. enforceHTTP2(len == 8, "Invalid PING Frame (FRAME_SIZE error)",
  146. HTTP2Error.PROTOCOL_ERROR);
  147. enforceHTTP2(header.streamId == 0, "Invalid streamId for PING Frame",
  148. HTTP2Error.PROTOCOL_ERROR);
  149. if(header.flags & 0x1) {
  150. ack = true;
  151. }
  152. foreach(b; src.takeExactly(len)) {
  153. payloadDst.put(b);
  154. src.popFront();
  155. }
  156. break;
  157. case HTTP2FrameType.GOAWAY: // GOAWAY is used to close connection (in handler)
  158. enforceHTTP2(len >= 8, "Invalid GOAWAY Frame (FRAME_SIZE error)",
  159. HTTP2Error.PROTOCOL_ERROR);
  160. enforceHTTP2(header.streamId == 0, "Invalid streamId for GOAWAY Frame",
  161. HTTP2Error.PROTOCOL_ERROR);
  162. foreach(b; src.takeExactly(len)) {
  163. payloadDst.put(b);
  164. src.popFront();
  165. }
  166. break;
  167. case HTTP2FrameType.WINDOW_UPDATE:
  168. enforceHTTP2(len == 4, "Invalid WINDOW_UPDATE Frame (FRAME_SIZE error)",
  169. HTTP2Error.PROTOCOL_ERROR);
  170. foreach(i,b; src.takeExactly(len).enumerate) {
  171. if(i == 0) b &= 0x7F; // reserved bit
  172. payloadDst.put(b);
  173. src.popFront();
  174. }
  175. break;
  176. case HTTP2FrameType.CONTINUATION:
  177. enforceHTTP2(header.streamId != 0, "Invalid streamId for CONTINUATION frame",
  178. HTTP2Error.PROTOCOL_ERROR);
  179. foreach(b; src.takeExactly(len)) {
  180. payloadDst.put(b);
  181. src.popFront();
  182. }
  183. if(header.flags & 0x4) endHeaders = true;
  184. break;
  185. default:
  186. enforceHTTP2(false, "Invalid frame header unpacked.", HTTP2Error.PROTOCOL_ERROR);
  187. break;
  188. }
  189. }
  190. unittest {
  191. import vibe.internal.array : FixedAppender;
  192. FixedAppender!(ubyte[], 4) payloadDst;
  193. bool endStream = false;
  194. bool endHeaders = false;
  195. bool ack = false;
  196. HTTP2FrameStreamDependency sdep;
  197. // DATA Frame
  198. ubyte[] data = [0, 0, 4, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1];
  199. payloadDst.unpackHTTP2Frame(data, endStream, endHeaders, ack, sdep);
  200. assert(payloadDst.data == [1, 1, 1, 1]);
  201. // HEADERS Frame
  202. payloadDst.clear;
  203. data = [0, 0, 4, 1, 0, 0, 0, 0, 2, 2, 2, 2, 2];
  204. payloadDst.unpackHTTP2Frame(data, endStream, endHeaders, ack, sdep);
  205. assert(payloadDst.data == [2, 2, 2, 2]);
  206. // PRIORITY Frame
  207. payloadDst.clear;
  208. data = [0, 0, 5, 2, 0, 0, 0, 0, 3, 0, 0, 0, 2, 5];
  209. payloadDst.unpackHTTP2Frame(data, endStream, endHeaders, ack, sdep);
  210. assert(payloadDst.data == []);
  211. assert(sdep.weight == 5 && sdep.streamId == 2);
  212. // RST_STREAM Frame
  213. payloadDst.clear;
  214. data = [0, 0, 4, 3, 0, 0, 0, 0, 4, 4, 4, 4, 4];
  215. payloadDst.unpackHTTP2Frame(data, endStream, endHeaders, ack, sdep);
  216. assert(payloadDst.data == [4, 4, 4, 4]);
  217. // SETTINGS Frame
  218. FixedAppender!(ubyte[], 6) settingsDst;
  219. data = [0, 0, 6, 4, 0, 0, 0, 0, 0, 0, 1, 2, 2, 2, 2];
  220. settingsDst.unpackHTTP2Frame(data, endStream, endHeaders, ack, sdep);
  221. assert(settingsDst.data == [0, 1, 2, 2, 2, 2]);
  222. // PUSH_PROMISE Frame
  223. payloadDst.clear;
  224. data = [0, 0, 8, 5, 0, 0, 0, 0, 5, 0, 0, 0, 2, 4, 4, 4, 4];
  225. payloadDst.unpackHTTP2Frame(data, endStream, endHeaders, ack, sdep);
  226. assert(payloadDst.data == [4, 4, 4, 4]);
  227. assert(sdep.weight == 5 && sdep.streamId == 2);
  228. // PING Frame
  229. FixedAppender!(ubyte[], 8) pingDst;
  230. data = [0, 0, 8, 6, 0, 0, 0, 0, 0, 0, 0, 0, 2, 4, 4, 4, 4];
  231. pingDst.unpackHTTP2Frame(data, endStream, endHeaders, ack, sdep);
  232. assert(pingDst.data == [0, 0, 0, 2, 4, 4, 4, 4]);
  233. // GOAWAY Frame
  234. pingDst.clear;
  235. data = [0, 0, 8, 7, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 4];
  236. pingDst.unpackHTTP2Frame(data, endStream, endHeaders, ack, sdep);
  237. assert(pingDst.data == [0, 0, 0, 2, 0, 0, 0, 4]);
  238. // WINDOW_UPDATE
  239. payloadDst.clear;
  240. data = [0, 0, 4, 8, 0, 0, 0, 0, 6, 1, 1, 1, 1];
  241. payloadDst.unpackHTTP2Frame(data, endStream, endHeaders, ack, sdep);
  242. assert(payloadDst.data == [1, 1, 1, 1]);
  243. // CONTINUATION
  244. payloadDst.clear;
  245. data = [0, 0, 4, 9, 0, 0, 0, 0, 6, 2, 2, 2, 2];
  246. payloadDst.unpackHTTP2Frame(data, endStream, endHeaders, ack, sdep);
  247. assert(payloadDst.data == [2, 2, 2, 2]);
  248. }
  249. /*** FRAME BUILDING ***/
  250. /// concatenates a Frame header with a Frame payload
  251. void buildHTTP2Frame(R,H,T)(ref R dst, ref H header, ref T payload) @safe @nogc
  252. if(is(ElementType!R : ubyte) && is(ElementType!T : ubyte))
  253. {
  254. // put header
  255. static if(is(H == HTTP2FrameHeader)) {
  256. assert(header.payloadLength == payload.length, "Invalid payload length");
  257. dst.serializeHTTP2FrameHeader(header);
  258. } else static if(is(ElementType!H : ubyte)) {
  259. auto len = header.takeExactly(3).fromBytes(3);
  260. assert(len == payload.length, "Invalid payload length");
  261. foreach(b; header) dst.put(b);
  262. }
  263. // put payload
  264. foreach(b; payload) dst.put(b);
  265. }
  266. /// DITTO
  267. /// @nogc-compatible if dst.put is @nogc
  268. void buildHTTP2Frame(R,T)(ref R dst, T payload) @safe
  269. {
  270. payload.copy(dst);
  271. }
  272. unittest {
  273. auto header = HTTP2FrameHeader(4, cast(HTTP2FrameType)1, 0, 5);
  274. ubyte[4] payload = [0, 1, 2, 3];
  275. ubyte[] bheader = [0, 0, 4, 1, 0, 0, 0, 0, 5];
  276. ubyte[13] expected = [0, 0, 4, 1, 0, 0, 0, 0, 5, 0, 1, 2, 3];
  277. BatchBuffer!(ubyte, 13) dst, ddst;
  278. dst.putN(13);
  279. ddst.putN(13);
  280. dst.buildHTTP2Frame(header, payload);
  281. ddst.buildHTTP2Frame(bheader, payload);
  282. assert(dst.peekDst == expected);
  283. assert(ddst.peekDst == expected);
  284. }
  285. /*** FRAME HEADER ***/
  286. /// header packing
  287. /// @nogc-compatible if dst.put is @nogc
  288. void createHTTP2FrameHeader(R)(ref R dst, const uint len, const HTTP2FrameType type, const ubyte flags, const uint sid) @safe
  289. {
  290. dst.serialize(HTTP2FrameHeader(len, type, flags, sid));
  291. }
  292. /// serializing
  293. void serializeHTTP2FrameHeader(R)(ref R dst, HTTP2FrameHeader header) @safe @nogc
  294. {
  295. dst.serialize(header);
  296. }
  297. /// unpacking
  298. HTTP2FrameHeader unpackHTTP2FrameHeader(R)(scope ref R src) @safe @nogc
  299. {
  300. scope header = HTTP2FrameHeader(src);
  301. return header;
  302. }
  303. /** Implement an HTTP/2 Frame header
  304. * The header is a 9-bit ubyte[9] string
  305. */
  306. struct HTTP2FrameHeader
  307. {
  308. private {
  309. //ubyte[3] m_length; // 24-bit frame payload length
  310. FixedAppender!(ubyte[], 3) m_length;
  311. HTTP2FrameType m_type; // frame type (stored as ubyte for serialization)
  312. ubyte m_flags; // frame flags
  313. //ubyte[4] m_streamId; // stream id, uint (stored as ubyte for serialization)
  314. FixedAppender!(ubyte[], 4) m_streamId;
  315. }
  316. this(const uint len, const HTTP2FrameType tp, const ubyte flg, const uint sid) @safe @nogc
  317. {
  318. assert(sid < (cast(ulong)1 << 32), "Invalid stream id");
  319. m_length.putBytes!3(len);
  320. m_type = tp;
  321. m_flags = flg;
  322. m_streamId.putBytes!4(sid & ((cast(ulong)1 << 32) - 1)); // reserved bit is 0
  323. }
  324. this(T)(ref T src) @safe @nogc
  325. if(is(ElementType!T : ubyte))
  326. {
  327. m_length.put(src.take(3));
  328. src.popFrontN(3);
  329. m_type = cast(HTTP2FrameType)src.front; src.popFront;
  330. m_flags = src.front; src.popFront;
  331. m_streamId.put(src.take(1).front & 127); src.popFront; // ignore reserved bit
  332. m_streamId.put(src.take(3));
  333. src.popFrontN(3);
  334. }
  335. @property HTTP2FrameType type() @safe @nogc { return m_type; }
  336. @property uint payloadLength() @safe @nogc { return m_length.data.fromBytes(3); }
  337. @property ubyte flags() @safe @nogc { return m_flags; }
  338. @property uint streamId() @safe @nogc { return m_streamId.data.fromBytes(4); }
  339. }
  340. /// convert 32-bit unsigned integer to N bytes (MSB first)
  341. void putBytes(uint N, R)(ref R dst, const(ulong) src) @safe @nogc
  342. {
  343. assert(src >= 0 && src < (cast(ulong)1 << N*8), "Invalid frame payload length");
  344. static if(hasLength!R) assert(dst.length >= N);
  345. ubyte[N] buf;
  346. foreach(i,ref b; buf) b = cast(ubyte)(src >> 8*(N-1-i)) & 0xff;
  347. static if(isArray!R) {
  348. dst.put(buf);
  349. } else {
  350. foreach(b; buf) dst.put(b);
  351. }
  352. }
  353. /// convert a N-bytes representation MSB->LSB to uint
  354. uint fromBytes(R)(R src, uint n) @safe @nogc
  355. {
  356. uint res = 0;
  357. static if(isArray!R) {
  358. foreach(i,b; src) res = res + (b << 8*(n-1-i));
  359. } else {
  360. foreach(i,b; src.enumerate.retro) res = res + (b << 8*i);
  361. }
  362. return res;
  363. }
  364. /// fill a buffer with fields from `header`
  365. /// @nogc-compatible if dst.put is @nogc
  366. private void serialize(R)(ref R dst, HTTP2FrameHeader header) @safe
  367. if(isOutputRange!(R, ubyte))
  368. {
  369. static foreach(f; __traits(allMembers, HTTP2FrameHeader)) {
  370. static if(f != "__ctor" && f != "type"
  371. && f != "payloadLength" && f != "flags" && f != "streamId") {
  372. static if(f == "m_length" || f == "m_streamId") {
  373. mixin("dst.put(header."~f~".data);");
  374. } else static if(f == "m_type") {
  375. mixin("dst.put(cast(ubyte)header."~f~");");
  376. } else {
  377. mixin("dst.put(header."~f~");");
  378. }
  379. }
  380. }
  381. }
  382. unittest {
  383. import vibe.internal.array : FixedAppender;
  384. auto header = HTTP2FrameHeader(2, cast(HTTP2FrameType)1, 0, 5);
  385. ubyte[] expected = [0, 0, 2, 1, 0, 0, 0, 0, 5];
  386. FixedAppender!(ubyte[], 9) dst;
  387. // serialize to a ubyte[9] array
  388. serialize(dst,header);
  389. assert(dst.data == expected);
  390. // test utility functions
  391. FixedAppender!(ubyte[], 9) ddst;
  392. ddst.createHTTP2FrameHeader(2, cast(HTTP2FrameType)1, 0, 5);
  393. assert(dst.data == ddst.data);
  394. FixedAppender!(ubyte[], 9) dddst;
  395. dddst.serializeHTTP2FrameHeader(header);
  396. assert(dst.data == dddst.data);
  397. // test unpacking
  398. assert(header == unpackHTTP2FrameHeader(expected));
  399. assert(header.payloadLength == 2);
  400. }