MEDIUM: h2: decode SETTINGS frames and extract relevant settings

The INITIAL_WINDOW_SIZE and MAX_FRAME_SIZE settings are now extracted
from the settings frame, assigned to the connection, and attempted to
be propagated to all existing streams as per the specification. In
practice clients rarely update the settings after sending the first
stream, so the propagation will rarely be used. The ACK is properly
sent after the frame is completely parsed.
diff --git a/src/mux_h2.c b/src/mux_h2.c
index 0986baa..8fee6bd 100644
--- a/src/mux_h2.c
+++ b/src/mux_h2.c
@@ -753,6 +753,140 @@
 	return ret;
 }
 
+/* Increase all streams' outgoing window size by the difference passed in
+ * argument. This is needed upon receipt of the settings frame if the initial
+ * window size is different. The difference may be negative and the resulting
+ * window size as well, for the time it takes to receive some window updates.
+ */
+static void h2c_update_all_ws(struct h2c *h2c, int diff)
+{
+	struct h2s *h2s;
+	struct eb32_node *node;
+
+	if (!diff)
+		return;
+
+	node = eb32_first(&h2c->streams_by_id);
+	while (node) {
+		h2s = container_of(node, struct h2s, by_id);
+		h2s->mws += diff;
+		node = eb32_next(node);
+	}
+}
+
+/* processes a SETTINGS frame whose payload is <payload> for <plen> bytes, and
+ * ACKs it if needed. Returns > 0 on success or zero on missing data. It may
+ * return an error in h2c. Described in RFC7540#6.5.
+ */
+static int h2c_handle_settings(struct h2c *h2c)
+{
+	unsigned int offset;
+	int error;
+
+	if (h2c->dff & H2_F_SETTINGS_ACK) {
+		if (h2c->dfl) {
+			error = H2_ERR_FRAME_SIZE_ERROR;
+			goto fail;
+		}
+		return 1;
+	}
+
+	if (h2c->dsi != 0) {
+		error = H2_ERR_PROTOCOL_ERROR;
+		goto fail;
+	}
+
+	if (h2c->dfl % 6) {
+		error = H2_ERR_FRAME_SIZE_ERROR;
+		goto fail;
+	}
+
+	/* that's the limit we can process */
+	if (h2c->dfl > global.tune.bufsize) {
+		error = H2_ERR_FRAME_SIZE_ERROR;
+		goto fail;
+	}
+
+	/* process full frame only */
+	if (h2c->dbuf->i < h2c->dfl)
+		return 0;
+
+	/* parse the frame */
+	for (offset = 0; offset < h2c->dfl; offset += 6) {
+		uint16_t type = h2_get_n16(h2c->dbuf, offset);
+		int32_t  arg  = h2_get_n32(h2c->dbuf, offset + 2);
+
+		switch (type) {
+		case H2_SETTINGS_INITIAL_WINDOW_SIZE:
+			/* we need to update all existing streams with the
+			 * difference from the previous iws.
+			 */
+			if (arg < 0) { // RFC7540#6.5.2
+				error = H2_ERR_FLOW_CONTROL_ERROR;
+				goto fail;
+			}
+			h2c_update_all_ws(h2c, arg - h2c->miw);
+			h2c->miw = arg;
+			break;
+		case H2_SETTINGS_MAX_FRAME_SIZE:
+			if (arg < 16384 || arg > 16777215) { // RFC7540#6.5.2
+				error = H2_ERR_PROTOCOL_ERROR;
+				goto fail;
+			}
+			h2c->mfs = arg;
+			break;
+		}
+	}
+
+	/* need to ACK this frame now */
+	h2c->st0 = H2_CS_FRAME_A;
+	return 1;
+ fail:
+	h2c_error(h2c, error);
+	return 0;
+}
+
+/* try to send an ACK for a settings frame on the connection. Returns > 0 on
+ * success or one of the h2_status values.
+ */
+static int h2c_ack_settings(struct h2c *h2c)
+{
+	struct buffer *res;
+	char str[9];
+	int ret = -1;
+
+	if (h2c_mux_busy(h2c, NULL)) {
+		h2c->flags |= H2_CF_DEM_MBUSY;
+		return 0;
+	}
+
+	res = h2_get_mbuf(h2c);
+	if (!res) {
+		h2c->flags |= H2_CF_MUX_MALLOC;
+		h2c->flags |= H2_CF_DEM_MROOM;
+		return 0;
+	}
+
+	memcpy(str,
+	       "\x00\x00\x00"     /* length : 0 (no data)  */
+	       "\x04" "\x01"      /* type   : 4, flags : ACK */
+	       "\x00\x00\x00\x00" /* stream ID */, 9);
+
+	ret = bo_istput(res, ist2(str, 9));
+	if (unlikely(ret <= 0)) {
+		if (!ret) {
+			h2c->flags |= H2_CF_MUX_MFULL;
+			h2c->flags |= H2_CF_DEM_MROOM;
+			return 0;
+		}
+		else {
+			h2c_error(h2c, H2_ERR_INTERNAL_ERROR);
+			return 0;
+		}
+	}
+	return ret;
+}
+
 /* processes a PING frame and schedules an ACK if needed. The caller must pass
  * the pointer to the payload in <payload>. Returns > 0 on success or zero on
  * missing data. It may return an error in h2c.
@@ -903,6 +1037,14 @@
 		/* Only H2_CS_FRAME_P and H2_CS_FRAME_A here */
 
 		switch (h2c->dft) {
+		case H2_FT_SETTINGS:
+			if (h2c->st0 == H2_CS_FRAME_P)
+				ret = h2c_handle_settings(h2c);
+
+			if (h2c->st0 == H2_CS_FRAME_A)
+				ret = h2c_ack_settings(h2c);
+			break;
+
 		case H2_FT_PING:
 			if (h2c->st0 == H2_CS_FRAME_P)
 				ret = h2c_handle_ping(h2c);