MINOR: mux-quic: create a timeout task

This task will be used to schedule a timer when there is no activity on
the mux. The timeout is set via the "timeout client" from the
configuration file.

The timeout task process schedule the timeout only on specific
conditions. Currently, it's done if there is no opened bidirectional
stream.

For now this task is not used. This will be implemented in the following
commit.
diff --git a/include/haproxy/mux_quic-t.h b/include/haproxy/mux_quic-t.h
index 0a60868..f2b217f 100644
--- a/include/haproxy/mux_quic-t.h
+++ b/include/haproxy/mux_quic-t.h
@@ -54,6 +54,10 @@
 	struct wait_event wait_event;  /* To be used if we're waiting for I/Os */
 	struct wait_event *subs;
 
+	/* haproxy timeout management */
+	struct task *task;
+	int timeout;
+
 	const struct qcc_app_ops *app_ops;
 	void *ctx; /* Application layer context */
 };
diff --git a/src/mux_quic.c b/src/mux_quic.c
index 9efb2f2..532304f 100644
--- a/src/mux_quic.c
+++ b/src/mux_quic.c
@@ -209,6 +209,19 @@
 {
 	fprintf(stderr, "%s: %lu\n", __func__, qcc->strms[QCS_CLT_BIDI].nb_streams);
 
+	if (!qcc->strms[QCS_CLT_BIDI].nb_streams && !qcc->task)
+		return 1;
+
+	return 0;
+}
+
+/* Return true if the mux timeout should be armed. */
+static inline int qcc_may_expire(struct qcc *qcc)
+{
+
+	/* Consider that the timeout must be set if no bidirectional streams
+	 * are opened.
+	 */
 	if (!qcc->strms[QCS_CLT_BIDI].nb_streams)
 		return 1;
 
@@ -387,6 +400,36 @@
 	return NULL;
 }
 
+static struct task *qc_timeout_task(struct task *t, void *ctx, unsigned int state)
+{
+	struct qcc *qcc = ctx;
+	int expired = tick_is_expired(t->expire, now_ms);
+
+	fprintf(stderr, "%s\n", __func__);
+
+	if (qcc) {
+		if (!expired) {
+			fprintf(stderr, "%s: not expired\n", __func__);
+			return t;
+		}
+
+		if (!qcc_may_expire(qcc)) {
+			fprintf(stderr, "%s: cannot expire\n", __func__);
+			t->expire = TICK_ETERNITY;
+			return t;
+		}
+	}
+
+	fprintf(stderr, "%s: timeout\n", __func__);
+	task_destroy(t);
+	qcc->task = NULL;
+
+	if (qcc_is_dead(qcc))
+		qc_release(qcc);
+
+	return NULL;
+}
+
 static int qc_init(struct connection *conn, struct proxy *prx,
                    struct session *sess, struct buffer *input)
 {
@@ -445,12 +488,23 @@
 	qcc->wait_event.tasklet->process = qc_io_cb;
 	qcc->wait_event.tasklet->context = qcc;
 
+	/* haproxy timeouts */
+	qcc->timeout = prx->timeout.client;
+	qcc->task = task_new_here();
+	if (!qcc->task)
+		goto fail_no_timeout_task;
+	qcc->task->process = qc_timeout_task;
+	qcc->task->context = qcc;
+	qcc->task->expire = tick_add(now_ms, qcc->timeout);
+
 	HA_ATOMIC_STORE(&conn->qc->qcc, qcc);
 	/* init read cycle */
 	tasklet_wakeup(qcc->wait_event.tasklet);
 
 	return 0;
 
+ fail_no_timeout_task:
+	tasklet_free(qcc->wait_event.tasklet);
  fail_no_tasklet:
 	pool_free(pool_head_qcc, qcc);
  fail_no_qcc: