MINOR: mux-quic: handle RESET_STREAM reception

Implement RESET_STREAM reception by mux-quic. On reception, qcs instance
will be mark as remotely closed and its Rx buffer released. The stream
layer will be flagged on error if still attached.

This commit is part of implementing H3 errors at the stream level.
Indeed, on H3 stream errors, STOP_SENDING + RESET_STREAM should be
emitted. The STOP_SENDING will in turn generate a RESET_STREAM by the
remote peer which will be handled thanks to this patch.

This should be backported up to 2.7.
diff --git a/include/haproxy/mux_quic.h b/include/haproxy/mux_quic.h
index 908a02f..0a88f6b 100644
--- a/include/haproxy/mux_quic.h
+++ b/include/haproxy/mux_quic.h
@@ -25,6 +25,7 @@
              char fin, char *data);
 int qcc_recv_max_data(struct qcc *qcc, uint64_t max);
 int qcc_recv_max_stream_data(struct qcc *qcc, uint64_t id, uint64_t max);
+int qcc_recv_reset_stream(struct qcc *qcc, uint64_t id, uint64_t err, uint64_t final_size);
 int qcc_recv_stop_sending(struct qcc *qcc, uint64_t id, uint64_t err);
 void qcc_streams_sent_done(struct qcs *qcs, uint64_t data, uint64_t offset);
 
diff --git a/src/mux_quic.c b/src/mux_quic.c
index e4721fd..c780ebc 100644
--- a/src/mux_quic.c
+++ b/src/mux_quic.c
@@ -888,6 +888,11 @@
 		goto err;
 	}
 
+	if (qcs_is_close_remote(qcs)) {
+		TRACE_DATA("skipping STREAM for remotely closed", QMUX_EV_QCC_RECV, qcc->conn);
+		goto out;
+	}
+
 	if (offset + len <= qcs->rx.offset) {
 		/* TODO offset may have been received without FIN first and now
 		 * with it. In this case, it must be notified to be able to
@@ -1051,8 +1056,62 @@
 	if (qcc_may_expire(qcc) && !qcc->nb_hreq)
 		qcc_refresh_timeout(qcc);
 
+	TRACE_LEAVE(QMUX_EV_QCC_RECV, qcc->conn);
+	return 0;
+}
+
+/* Handle a new RESET_STREAM frame from stream ID <id> with error code <err>
+ * and final stream size <final_size>.
+ *
+ * Returns 0 on success else non-zero. On error, the received frame should not
+ * be acknowledged.
+ */
+int qcc_recv_reset_stream(struct qcc *qcc, uint64_t id, uint64_t err, uint64_t final_size)
+{
+	struct qcs *qcs;
+
+	TRACE_ENTER(QMUX_EV_QCC_RECV, qcc->conn);
+
+	/* RFC 9000 19.4. RESET_STREAM Frames
+	 *
+	 * An endpoint that receives a RESET_STREAM frame for a send-only stream
+	 * MUST terminate the connection with error STREAM_STATE_ERROR.
+	 */
+	if (qcc_get_qcs(qcc, id, 1, 0, &qcs)) {
+		TRACE_ERROR("RESET_STREAM for send-only stream received", QMUX_EV_QCC_RECV|QMUX_EV_QCS_RECV, qcc->conn, qcs);
+		qcc_emit_cc(qcc, QC_ERR_STREAM_STATE_ERROR);
+		goto err;
+	}
+
+	if (!qcs || qcs_is_close_remote(qcs))
+		goto out;
+
+	TRACE_PROTO("receiving RESET_STREAM", QMUX_EV_QCC_RECV|QMUX_EV_QCS_RECV, qcc->conn, qcs);
+	qcs_idle_open(qcs);
+
+	if (qcs->rx.offset_max > final_size ||
+	    ((qcs->flags & QC_SF_SIZE_KNOWN) && qcs->rx.offset_max != final_size)) {
+		TRACE_ERROR("final size error on RESET_STREAM", QMUX_EV_QCC_RECV|QMUX_EV_QCS_RECV, qcc->conn, qcs);
+		qcc_emit_cc(qcc, QC_ERR_FINAL_SIZE_ERROR);
+		goto err;
+	}
+
+	qcs->flags |= QC_SF_SIZE_KNOWN;
+	qcs_close_remote(qcs);
+	qc_free_ncbuf(qcs, &qcs->rx.ncbuf);
+
+	if (qcs_sc(qcs)) {
+		se_fl_set_error(qcs->sd);
+		qcs_alert(qcs);
+	}
+
+ out:
 	TRACE_LEAVE(QMUX_EV_QCC_RECV, qcc->conn);
 	return 0;
+
+ err:
+	TRACE_LEAVE(QMUX_EV_QCC_RECV, qcc->conn);
+	return 1;
 }
 
 /* Handle a new STOP_SENDING frame for stream ID <id>. The error code should be
diff --git a/src/quic_conn.c b/src/quic_conn.c
index ddb4bbb..e3a8b46 100644
--- a/src/quic_conn.c
+++ b/src/quic_conn.c
@@ -2802,8 +2802,11 @@
 			break;
 		}
 		case QUIC_FT_RESET_STREAM:
-		    /* TODO: handle this frame at STREAM level */
-		    break;
+			if (qc->mux_state == QC_MUX_READY) {
+				struct quic_reset_stream *rs = &frm.reset_stream;
+				qcc_recv_reset_stream(qc->qcc, rs->id, rs->app_error_code, rs->final_size);
+			}
+			break;
 		case QUIC_FT_STOP_SENDING:
 		{
 			struct quic_stop_sending *stop_sending = &frm.stop_sending;