MEDIUM: mux-quic: implement shutw

Implement mux_ops shutw operation for QUIC mux. A RESET_STREAM is
emitted unless the stream is already closed due to all data or
RESET_STREAM already transmitted.

This operation is notably useful when upper stream layer wants to close
the connection early due to an error.

This was tested by using a HTTP server which listens with PROXY protocol
support. The corresponding server line on haproxy configuration
deliberately not specify send-proxy. This causes the server to close
abruptly the connection. Without this patch, nothing was done on the QUIC
stream which was kept open until the whole connection is closed. Now, a
proper RESET_STREAM is emitted to report the error.

This should be backported up to 2.7.
diff --git a/include/haproxy/qmux_trace.h b/include/haproxy/qmux_trace.h
index c612c96..0bc89a3 100644
--- a/include/haproxy/qmux_trace.h
+++ b/include/haproxy/qmux_trace.h
@@ -34,17 +34,19 @@
 	{ .mask = QMUX_EV_STRM_RECV,    .name = "strm_recv",    .desc = "receiving data for stream" },
 #define           QMUX_EV_STRM_SEND     (1ULL << 11)
 	{ .mask = QMUX_EV_STRM_SEND,    .name = "strm_send",    .desc = "sending data for stream" },
-#define           QMUX_EV_STRM_END      (1ULL << 12)
+#define           QMUX_EV_STRM_SHUT     (1ULL << 12)
+	{ .mask = QMUX_EV_STRM_SHUT,    .name = "strm_shut",    .desc = "stream shutdown" },
+#define           QMUX_EV_STRM_END      (1ULL << 13)
 	{ .mask = QMUX_EV_STRM_END,     .name = "strm_end",     .desc = "detaching app-layer stream" },
-#define           QMUX_EV_SEND_FRM      (1ULL << 13)
+#define           QMUX_EV_SEND_FRM      (1ULL << 14)
 	{ .mask = QMUX_EV_SEND_FRM,     .name = "send_frm",     .desc = "sending QUIC frame" },
 /* special event dedicated to qcs_xfer_data */
-#define           QMUX_EV_QCS_XFER_DATA  (1ULL << 14)
+#define           QMUX_EV_QCS_XFER_DATA  (1ULL << 15)
 	{ .mask = QMUX_EV_QCS_XFER_DATA,  .name = "qcs_xfer_data", .desc = "qcs_xfer_data" },
 /* special event dedicated to qcs_build_stream_frm */
-#define           QMUX_EV_QCS_BUILD_STRM (1ULL << 15)
+#define           QMUX_EV_QCS_BUILD_STRM (1ULL << 16)
 	{ .mask = QMUX_EV_QCS_BUILD_STRM, .name = "qcs_build_stream_frm", .desc = "qcs_build_stream_frm" },
-#define           QMUX_EV_PROTO_ERR     (1ULL << 16)
+#define           QMUX_EV_PROTO_ERR     (1ULL << 17)
 	{ .mask = QMUX_EV_PROTO_ERR,    .name = "proto_err",    .desc = "protocol error" },
 	{ }
 };
diff --git a/src/mux_quic.c b/src/mux_quic.c
index 8e48493..a5e443f 100644
--- a/src/mux_quic.c
+++ b/src/mux_quic.c
@@ -2269,6 +2269,25 @@
 	return 1;
 }
 
+static void qc_shutw(struct stconn *sc, enum co_shw_mode mode)
+{
+	struct qcs *qcs = __sc_mux_strm(sc);
+
+	TRACE_ENTER(QMUX_EV_STRM_SHUT, qcs->qcc->conn, qcs);
+
+	/* If QC_SF_FIN_STREAM is not set and stream is not closed locally, it
+	 * means that upper layer reported an early closure. A RESET_STREAM is
+	 * necessary if not already scheduled.
+	 */
+
+	if (!qcs_is_close_local(qcs) &&
+	    !(qcs->flags & (QC_SF_FIN_STREAM|QC_SF_TO_RESET))) {
+		qcc_reset_stream(qcs, 0);
+		se_fl_set_error(qcs->sd);
+	}
+
+	TRACE_LEAVE(QMUX_EV_STRM_SHUT, qcs->qcc->conn, qcs);
+}
 
 /* for debugging with CLI's "show sess" command. May emit multiple lines, each
  * new one being prefixed with <pfx>, if <pfx> is not NULL, otherwise a single
@@ -2306,6 +2325,7 @@
 	.subscribe = qc_subscribe,
 	.unsubscribe = qc_unsubscribe,
 	.wake = qc_wake,
+	.shutw = qc_shutw,
 	.show_sd = qc_show_sd,
 	.flags = MX_FL_HTX|MX_FL_NO_UPG|MX_FL_FRAMED,
 	.name = "QUIC",