MINOR: quic: Add a fake congestion control algorithm named "nocc"

This algorithm does nothing except initializing the congestion control window
to a fixed value. Very smart!

Modify the QUIC congestion control configuration parser to support this new
algorithm. The congestion control algorithm must be set as follows:

     quic-cc-algo nocc-<cc window size(KB))

For instance if "nocc-15" is provided as quic-cc-algo keyword value, this
will set a fixed window of 15KB.
diff --git a/Makefile b/Makefile
index b5d1602..1f1de6d 100644
--- a/Makefile
+++ b/Makefile
@@ -607,7 +607,7 @@
                 src/quic_cc_newreno.o src/quic_cc_cubic.o src/qpack-tbl.o  \
                 src/qpack-dec.o src/hq_interop.o src/quic_stream.o         \
                 src/h3_stats.o src/qmux_http.o src/cfgparse-quic.o         \
-                src/cbuf.o src/quic_cc.o
+                src/cbuf.o src/quic_cc.o src/quic_cc_nocc.o
 endif
 
 ifneq ($(USE_LUA),)
diff --git a/include/haproxy/quic_cc-t.h b/include/haproxy/quic_cc-t.h
index f646c73..35996d7 100644
--- a/include/haproxy/quic_cc-t.h
+++ b/include/haproxy/quic_cc-t.h
@@ -37,6 +37,10 @@
 extern struct quic_cc_algo quic_cc_algo_cubic;
 extern struct quic_cc_algo *default_quic_cc_algo;
 
+/* Fake algorithm with its fixed window */
+extern struct quic_cc_algo quic_cc_algo_nocc;
+extern unsigned int quic_cc_nocc_fixed_cwnd;
+
 extern unsigned long long last_ts;
 
 enum quic_cc_algo_state_type {
@@ -73,6 +77,7 @@
 enum quic_cc_algo_type {
 	QUIC_CC_ALGO_TP_NEWRENO,
 	QUIC_CC_ALGO_TP_CUBIC,
+	QUIC_CC_ALGO_TP_NOCC,
 };
 
 struct quic_cc {
diff --git a/src/cfgparse-quic.c b/src/cfgparse-quic.c
index 35548d3..c8cb7d9 100644
--- a/src/cfgparse-quic.c
+++ b/src/cfgparse-quic.c
@@ -20,16 +20,27 @@
                                    struct bind_conf *conf, char **err)
 {
 	struct quic_cc_algo *cc_algo;
+	char *arg;
 
 	if (!*args[cur_arg + 1]) {
 		memprintf(err, "'%s' : missing control congestion algorithm", args[cur_arg]);
 		return ERR_ALERT | ERR_FATAL;
 	}
 
-	if (strcmp(args[cur_arg + 1], "newreno") == 0)
+	arg = args[cur_arg + 1];
+	if (strcmp(arg, "newreno") == 0)
 	    cc_algo = &quic_cc_algo_nr;
-	else if (strcmp(args[cur_arg + 1], "cubic") == 0)
+	else if (strcmp(arg, "cubic") == 0)
 	    cc_algo = &quic_cc_algo_cubic;
+	else if (strlen(arg) >= 6 && strncmp(arg, "nocc-", 5) == 0) {
+		if (!experimental_directives_allowed) {
+			ha_alert("'%s' algo is experimental, must be allowed via a global 'expose-experimental-directives'\n", arg);
+			return ERR_ALERT | ERR_FATAL;
+		}
+
+	    cc_algo = &quic_cc_algo_nocc;
+	    quic_cc_nocc_fixed_cwnd = atoi(arg + 5);
+	}
 	else {
 		memprintf(err, "'%s' : unknown control congestion algorithm", args[cur_arg]);
 		return ERR_ALERT | ERR_FATAL;
diff --git a/src/quic_cc_nocc.c b/src/quic_cc_nocc.c
new file mode 100644
index 0000000..27db350
--- /dev/null
+++ b/src/quic_cc_nocc.c
@@ -0,0 +1,79 @@
+/*
+ * Fake congestion control algorithm which does nothing except initializing
+ * the congestion control window to a fixed value.
+ *
+ */
+
+#include <haproxy/api-t.h>
+#include <haproxy/quic_conn-t.h>
+#include <haproxy/trace.h>
+
+#define TRACE_SOURCE    &trace_quic
+
+unsigned int quic_cc_nocc_fixed_cwnd;
+
+static int quic_cc_nocc_init(struct quic_cc *cc)
+{
+	struct quic_path *path;
+
+	path = container_of(cc, struct quic_path, cc);
+	path->cwnd = quic_cc_nocc_fixed_cwnd << 10;
+	return 1;
+}
+
+static void quic_cc_nocc_slow_start(struct quic_cc *cc)
+{
+}
+
+/* Slow start callback. */
+static void quic_cc_nocc_ss_cb(struct quic_cc *cc, struct quic_cc_event *ev)
+{
+	TRACE_ENTER(QUIC_EV_CONN_CC, cc->qc);
+	TRACE_PROTO("CC nocc", QUIC_EV_CONN_CC, cc->qc, ev, cc);
+	TRACE_LEAVE(QUIC_EV_CONN_CC, cc->qc);
+}
+
+/* Congestion avoidance callback. */
+static void quic_cc_nocc_ca_cb(struct quic_cc *cc, struct quic_cc_event *ev)
+{
+	TRACE_ENTER(QUIC_EV_CONN_CC, cc->qc);
+	TRACE_PROTO("CC nocc", QUIC_EV_CONN_CC, cc->qc, ev, cc);
+	TRACE_LEAVE(QUIC_EV_CONN_CC, cc->qc);
+}
+
+/*  Recovery period callback. */
+static void quic_cc_nocc_rp_cb(struct quic_cc *cc, struct quic_cc_event *ev)
+{
+	TRACE_ENTER(QUIC_EV_CONN_CC, cc->qc);
+	TRACE_PROTO("CC nocc", QUIC_EV_CONN_CC, cc->qc, ev, cc);
+	TRACE_LEAVE(QUIC_EV_CONN_CC, cc->qc);
+}
+
+static void quic_cc_nocc_state_trace(struct buffer *buf, const struct quic_cc *cc)
+{
+	struct quic_path *path;
+
+	path = container_of(cc, struct quic_path, cc);
+	chunk_appendf(buf, " cwnd=%llu", (unsigned long long)path->cwnd);
+}
+
+static void (*quic_cc_nocc_state_cbs[])(struct quic_cc *cc,
+                                      struct quic_cc_event *ev) = {
+	[QUIC_CC_ST_SS] = quic_cc_nocc_ss_cb,
+	[QUIC_CC_ST_CA] = quic_cc_nocc_ca_cb,
+	[QUIC_CC_ST_RP] = quic_cc_nocc_rp_cb,
+};
+
+static void quic_cc_nocc_event(struct quic_cc *cc, struct quic_cc_event *ev)
+{
+	return quic_cc_nocc_state_cbs[cc->algo->state](cc, ev);
+}
+
+struct quic_cc_algo quic_cc_algo_nocc = {
+	.type        = QUIC_CC_ALGO_TP_NOCC,
+	.init        = quic_cc_nocc_init,
+	.event       = quic_cc_nocc_event,
+	.slow_start  = quic_cc_nocc_slow_start,
+	.state_trace = quic_cc_nocc_state_trace,
+};
+