MEDIUM: mux-h2: implement encoding of H2 request on the backend side

This creates an H2 HEADERS frame from an HTX request. The code is
very similar to the response encoding, so probably that in the future
we'll have to factor these functions differently. The HTX's start line
type is used to decide on the direction. We also purposely error out
when trying to encode an H2 request from an H1 message since it's not
implemented.
diff --git a/src/mux_h2.c b/src/mux_h2.c
index 14efcb8..b69852a 100644
--- a/src/mux_h2.c
+++ b/src/mux_h2.c
@@ -3898,6 +3898,243 @@
 	goto end;
 }
 
+/* Try to send a HEADERS frame matching HTX request present in HTX message
+ * <htx> for the H2 stream <h2s>. Returns the number of bytes sent. The caller
+ * must check the stream's status to detect any error which might have happened
+ * subsequently to a successful send. The htx blocks are automatically removed
+ * from the message. The htx message is assumed to be valid since produced from
+ * the internal code, hence it contains a start line, an optional series of
+ * header blocks and an end of header, otherwise an invalid frame could be
+ * emitted and the resulting htx message could be left in an inconsistent state.
+ */
+static size_t h2s_htx_bck_make_req_headers(struct h2s *h2s, struct htx *htx)
+{
+	struct http_hdr list[MAX_HTTP_HDR];
+	struct h2c *h2c = h2s->h2c;
+	struct htx_blk *blk;
+	struct htx_blk *blk_end;
+	struct buffer outbuf;
+	struct htx_sl *sl;
+	struct ist meth, path;
+	enum htx_blk_type type;
+	int es_now = 0;
+	int ret = 0;
+	int hdr;
+	int idx;
+
+	if (h2c_mux_busy(h2c, h2s)) {
+		h2s->flags |= H2_SF_BLK_MBUSY;
+		return 0;
+	}
+
+	if (!h2_get_buf(h2c, &h2c->mbuf)) {
+		h2c->flags |= H2_CF_MUX_MALLOC;
+		h2s->flags |= H2_SF_BLK_MROOM;
+		return 0;
+	}
+
+	/* determine the first block which must not be deleted, blk_end may
+	 * be NULL if all blocks have to be deleted.
+	 */
+	idx = htx_get_head(htx);
+	blk_end = NULL;
+	while (idx != -1) {
+		type = htx_get_blk_type(htx_get_blk(htx, idx));
+		idx = htx_get_next(htx, idx);
+		if (type == HTX_BLK_EOH) {
+			if (idx != -1)
+				blk_end = htx_get_blk(htx, idx);
+			break;
+		}
+	}
+
+	/* get the start line, we do have one */
+	blk = htx_get_blk(htx, htx->sl_off);
+	sl = htx_get_blk_ptr(htx, blk);
+	meth = htx_sl_req_meth(sl);
+	path = htx_sl_req_uri(sl);
+
+	/* and the rest of the headers, that we dump starting at header 0 */
+	hdr = 0;
+
+	idx = htx->sl_off;
+	while ((idx = htx_get_next(htx, idx)) != -1) {
+		blk = htx_get_blk(htx, idx);
+		type = htx_get_blk_type(blk);
+
+		if (type == HTX_BLK_UNUSED)
+			continue;
+
+		if (type != HTX_BLK_HDR)
+			break;
+
+		if (unlikely(hdr >= sizeof(list)/sizeof(list[0]) - 1))
+			goto fail;
+
+		list[hdr].n = htx_get_blk_name(htx, blk);
+		list[hdr].v = htx_get_blk_value(htx, blk);
+
+#if 1
+		{
+			/* FIXME: header names MUST be lower case in H2. For now it's
+			 * not granted by HTX so let's force them now.
+			 */
+			char *p;
+			for (p = list[hdr].n.ptr; p != list[hdr].n.ptr + list[hdr].n.len; p++)
+				if (unlikely(isupper(*p)))
+					*p = tolower(*p);
+		}
+#endif
+		hdr++;
+	}
+
+	/* marker for end of headers */
+	list[hdr].n = ist("");
+
+	chunk_reset(&outbuf);
+
+	while (1) {
+		outbuf.area  = b_tail(&h2c->mbuf);
+		outbuf.size = b_contig_space(&h2c->mbuf);
+		outbuf.data = 0;
+
+		if (outbuf.size >= 9 || !b_space_wraps(&h2c->mbuf))
+			break;
+	realign_again:
+		b_slow_realign(&h2c->mbuf, trash.area, b_data(&h2c->mbuf));
+	}
+
+	if (outbuf.size < 9)
+		goto full;
+
+	/* len: 0x000000 (fill later), type: 1(HEADERS), flags: ENDH=4 */
+	memcpy(outbuf.area, "\x00\x00\x00\x01\x04", 5);
+	write_n32(outbuf.area + 5, h2s->id); // 4 bytes
+	outbuf.data = 9;
+
+	/* encode the method, which necessarily is the first one */
+	if (outbuf.data < outbuf.size && sl->info.req.meth == HTTP_METH_GET)
+		outbuf.area[outbuf.data++] = 0x82; // indexed field : idx[02]=(":method", "GET")
+	else if (outbuf.data < outbuf.size && sl->info.req.meth == HTTP_METH_POST)
+		outbuf.area[outbuf.data++] = 0x83; // indexed field : idx[03]=(":method", "POST")
+	else if (unlikely(outbuf.data + 2 + meth.len <= outbuf.size)) {
+		/* basic encoding of the method code */
+		outbuf.area[outbuf.data++] = 0x42; // indexed name -- name=":method" (idx 2)
+		outbuf.area[outbuf.data++] = meth.len; // method length
+		memcpy(&outbuf.area[outbuf.data], meth.ptr, meth.len);
+	}
+	else {
+		if (b_space_wraps(&h2c->mbuf))
+			goto realign_again;
+		goto full;
+	}
+
+	/* encode the scheme which is always "https" (or 0x86 for "http") */
+	if (outbuf.data < outbuf.size)
+		outbuf.area[outbuf.data++] = 0x87; // indexed field : idx[02]=(":scheme", "https")
+
+	/* encode the path, which necessarily is the second one */
+	if (outbuf.data < outbuf.size && isteq(path, ist("/"))) {
+		outbuf.area[outbuf.data++] = 0x84; // indexed field : idx[04]=(":path", "/")
+	}
+	else if (outbuf.data < outbuf.size && isteq(path, ist("/index.html"))) {
+		outbuf.area[outbuf.data++] = 0x85; // indexed field : idx[04]=(":path", "/index.html")
+	}
+	else if (!hpack_encode_header(&outbuf, ist(":path"), path)) {
+		/* output full */
+		if (b_space_wraps(&h2c->mbuf))
+			goto realign_again;
+		goto full;
+	}
+
+	/* encode all headers, stop at empty name */
+	for (hdr = 0; hdr < sizeof(list)/sizeof(list[0]); hdr++) {
+		/* these ones do not exist in H2 and must be dropped. */
+		if (isteq(list[hdr].n, ist("connection")) ||
+		    isteq(list[hdr].n, ist("proxy-connection")) ||
+		    isteq(list[hdr].n, ist("keep-alive")) ||
+		    isteq(list[hdr].n, ist("upgrade")) ||
+		    isteq(list[hdr].n, ist("transfer-encoding")))
+			continue;
+
+		if (isteq(list[hdr].n, ist("")))
+			break; // end
+
+		if (!hpack_encode_header(&outbuf, list[hdr].n, list[hdr].v)) {
+			/* output full */
+			if (b_space_wraps(&h2c->mbuf))
+				goto realign_again;
+			goto full;
+		}
+	}
+
+	/* we may need to add END_STREAM if we have no body :
+	 *  - request already closed, or :
+	 *  - no transfer-encoding, and :
+	 *  - no content-length or content-length:0
+	 * Fixme: this doesn't take into account CONNECT requests.
+	 */
+	if (blk_end && htx_get_blk_type(blk_end) == HTX_BLK_EOM)
+		es_now = 1;
+
+	if (sl->flags & HTX_SL_F_BODYLESS)
+		es_now = 1;
+
+	if (h2s->cs->flags & CS_FL_SHW)
+		es_now = 1;
+
+	/* update the frame's size */
+	h2_set_frame_size(outbuf.area, outbuf.data - 9);
+
+	if (es_now)
+		outbuf.area[4] |= H2_F_HEADERS_END_STREAM;
+
+	/* commit the H2 response */
+	b_add(&h2c->mbuf, outbuf.data);
+	h2s->flags |= H2_SF_HEADERS_SENT;
+	h2s->st = H2_SS_OPEN;
+
+	/* for now we don't implemented CONTINUATION, so we wait for a
+	 * body or directly end in TRL2.
+	 */
+	if (es_now) {
+		// trim any possibly pending data (eg: inconsistent content-length)
+		h2s->flags |= H2_SF_ES_SENT;
+		h2s->st = H2_SS_HLOC;
+	}
+
+	/* remove all header blocks including the EOH and compute the
+	 * corresponding size.
+	 *
+	 * FIXME: We should remove everything when es_now is set.
+	 */
+	ret = 0;
+	idx = htx_get_head(htx);
+	blk = htx_get_blk(htx, idx);
+	while (blk != blk_end) {
+		ret += htx_get_blksz(blk);
+		blk = htx_remove_blk(htx, blk);
+	}
+
+	if (blk_end && htx_get_blk_type(blk_end) == HTX_BLK_EOM)
+		htx_remove_blk(htx, blk_end);
+
+ end:
+	return ret;
+ full:
+	h2c->flags |= H2_CF_MUX_MFULL;
+	h2s->flags |= H2_SF_BLK_MROOM;
+	ret = 0;
+	goto end;
+ fail:
+	/* unparsable HTX messages, too large ones to be produced in the local
+	 * list etc go here (unrecoverable errors).
+	 */
+	h2s_error(h2s, H2_ERR_INTERNAL_ERROR);
+	ret = 0;
+	goto end;
+}
+
 /* Try to send a DATA frame matching HTTP response present in HTX structure
  * <htx>, for stream <h2s>. Returns the number of bytes sent. The caller must
  * check the stream's status to detect any error which might have happened
@@ -4276,6 +4513,17 @@
 			bsize = htx_get_blksz(blk);
 
 			switch (btype) {
+			case HTX_BLK_REQ_SL:
+				/* start-line before headers */
+				ret = h2s_htx_bck_make_req_headers(h2s, htx);
+				if (ret > 0) {
+					total += ret;
+					count -= ret;
+					if (ret < bsize)
+						goto done;
+				}
+				break;
+
 			case HTX_BLK_RES_SL:
 				/* start-line before headers */
 				ret = h2s_htx_frt_make_resp_headers(h2s, htx);
@@ -4313,7 +4561,10 @@
 	/* legacy transfer mode */
 	while (h2s->h1m.state < H1_MSG_DONE && count) {
 		if (h2s->h1m.state <= H1_MSG_LAST_LF) {
-			ret = h2s_frt_make_resp_headers(h2s, buf, total, count);
+			if (h2s->h2c->flags & H2_CF_IS_BACK)
+				ret = -1;
+			else
+				ret = h2s_frt_make_resp_headers(h2s, buf, total, count);
 		}
 		else if (h2s->h1m.state < H1_MSG_TRAILERS) {
 			ret = h2s_frt_make_resp_data(h2s, buf, total, count);