MEDIUM: quic: Ack delay implementation

Reuse the idle timeout task to delay the acknowledgments. The time of the
idle timer expiration is for now on stored in ->idle_expire. The one to
trigger the acknowledgements is stored in ->ack_expire.
Add QUIC_FL_CONN_ACK_TIMER_FIRED new connection flag to mark a connection
as having its acknowledgement timer been triggered.
Modify qc_may_build_pkt() to prevent the sending of "ack only" packets and
allows the connection to send packet when the ack timer has fired.
It is possible that acks are sent before the ack timer has triggered. In
this case it is cancelled only if ACK frames are really sent.
The idle timer expiration must be set again when the ack timer has been
triggered or when it is cancelled.

Must be backported to 2.7.
diff --git a/include/haproxy/quic_conn-t.h b/include/haproxy/quic_conn-t.h
index e2e6d0b..1ec368c 100644
--- a/include/haproxy/quic_conn-t.h
+++ b/include/haproxy/quic_conn-t.h
@@ -394,6 +394,7 @@
  * ACK frame was sent
  */
 #define QUIC_MAX_RX_AEPKTS_SINCE_LAST_ACK       2
+#define QUIC_ACK_DELAY   (QUIC_TP_DFLT_MAX_ACK_DELAY - 5)
 /* Flag a received packet as being an ack-eliciting packet. */
 #define QUIC_FL_RX_PACKET_ACK_ELICITING (1UL << 0)
 /* Packet is the first one in the containing datagram. */
@@ -622,6 +623,7 @@
 /* gap here */
 #define QUIC_FL_CONN_HALF_OPEN_CNT_DECREMENTED   (1U << 11) /* The half-open connection counter was decremented */
 #define QUIC_FL_CONN_HANDSHAKE_SPEED_UP          (1U << 12) /* Handshake speeding up was done */
+#define QUIC_FL_CONN_ACK_TIMER_FIRED             (1U << 13) /* idle timer triggered for acknowledgements */
 #define QUIC_FL_CONN_TO_KILL                     (1U << 24) /* Unusable connection, to be killed */
 #define QUIC_FL_CONN_TX_TP_RECEIVED              (1U << 25) /* Peer transport parameters have been received (used for the transmitting part) */
 #define QUIC_FL_CONN_FINALIZED                   (1U << 26) /* QUIC connection finalized (functional, ready to send/receive) */
@@ -729,6 +731,8 @@
 	unsigned int timer;
 	/* Idle timer task */
 	struct task *idle_timer_task;
+	unsigned int idle_expire;
+	unsigned int ack_expire;
 	unsigned int flags;
 
 	/* When in closing state, number of packet before sending CC */
diff --git a/src/quic_conn.c b/src/quic_conn.c
index f65e217..f6d9a68 100644
--- a/src/quic_conn.c
+++ b/src/quic_conn.c
@@ -228,8 +228,8 @@
                                            const struct quic_version *ver, size_t dglen, int pkt_type,
                                            int force_ack, int padding, int probe, int cc, int *err);
 struct task *quic_conn_app_io_cb(struct task *t, void *context, unsigned int state);
-static void qc_idle_timer_do_rearm(struct quic_conn *qc);
-static void qc_idle_timer_rearm(struct quic_conn *qc, int read);
+static void qc_idle_timer_do_rearm(struct quic_conn *qc, int arm_ack);
+static void qc_idle_timer_rearm(struct quic_conn *qc, int read, int arm_ack);
 static int qc_conn_alloc_ssl_ctx(struct quic_conn *qc);
 static int quic_conn_init_timer(struct quic_conn *qc);
 static int quic_conn_init_idle_timer_task(struct quic_conn *qc);
@@ -3255,7 +3255,7 @@
 				 */
 				qc->flags |= QUIC_FL_CONN_DRAINING|QUIC_FL_CONN_IMMEDIATE_CLOSE;
 				qc_detach_th_ctx_list(qc, 1);
-				qc_idle_timer_do_rearm(qc);
+				qc_idle_timer_do_rearm(qc, 0);
 				qc_notify_close(qc);
 			}
 			break;
@@ -3370,8 +3370,7 @@
 static int qc_may_build_pkt(struct quic_conn *qc, struct list *frms,
                             struct quic_enc_level *qel, int cc, int probe, int force_ack)
 {
-	unsigned int must_ack = force_ack ||
-		(LIST_ISEMPTY(frms) && (qel->pktns->flags & QUIC_FL_PKTNS_ACK_REQUIRED));
+	unsigned int must_ack = force_ack || (qc->flags & QUIC_FL_CONN_ACK_TIMER_FIRED);
 
 	/* Do not build any more packet if the TX secrets are not available or
 	 * if there is nothing to send, i.e. if no CONNECTION_CLOSE or ACK are required
@@ -3775,7 +3774,7 @@
 				pkt->pktns->tx.time_of_last_eliciting = time_sent;
 				qc->path->ifae_pkts++;
 				if (qc->flags & QUIC_FL_CONN_IDLE_TIMER_RESTARTED_AFTER_READ)
-					qc_idle_timer_rearm(qc, 0);
+					qc_idle_timer_rearm(qc, 0, 0);
 			}
 			if (!(qc->flags & QUIC_FL_CONN_CLOSING) &&
 			    (pkt->flags & QUIC_FL_TX_PACKET_CC)) {
@@ -3792,7 +3791,7 @@
 				 * Rearm the idle timeout only one time when entering closing
 				 * state.
 				 */
-				qc_idle_timer_do_rearm(qc);
+				qc_idle_timer_do_rearm(qc, 0);
 				if (qc->timer_task) {
 					task_destroy(qc->timer_task);
 					qc->timer_task = NULL;
@@ -4398,7 +4397,7 @@
 				if (pkt->flags & QUIC_FL_RX_PACKET_ACK_ELICITING) {
 					qel->pktns->flags |= QUIC_FL_PKTNS_ACK_REQUIRED;
 					qel->pktns->rx.nb_aepkts_since_last_ack++;
-					qc_idle_timer_rearm(qc, 1);
+					qc_idle_timer_rearm(qc, 1, 1);
 				}
 				if (pkt->pn > largest_pn) {
 					largest_pn = pkt->pn;
@@ -5609,26 +5608,42 @@
 	return ret;
 }
 
-/* Rearm the idle timer for <qc> QUIC connection. */
-static void qc_idle_timer_do_rearm(struct quic_conn *qc)
+/* Rearm the idle timer or the ack timer (if not already armde) for <qc> QUIC
+ * connection. */
+static void qc_idle_timer_do_rearm(struct quic_conn *qc, int arm_ack)
 {
 	unsigned int expire;
 
 	if (stopping && qc->flags & (QUIC_FL_CONN_CLOSING|QUIC_FL_CONN_DRAINING)) {
 		TRACE_STATE("executing idle timer immediately on stopping", QUIC_EV_CONN_IDLE_TIMER, qc);
+		qc->ack_expire = TICK_ETERNITY;
 		task_wakeup(qc->idle_timer_task, TASK_WOKEN_MSG);
 	}
 	else {
 		expire = QUIC_MAX(3 * quic_pto(qc), qc->max_idle_timeout);
-		qc->idle_timer_task->expire = tick_add(now_ms, MS_TO_TICKS(expire));
-		task_queue(qc->idle_timer_task);
+		qc->idle_expire = tick_add(now_ms, MS_TO_TICKS(expire));
+		if (arm_ack) {
+			/* Arm the ack timer only if not already armed. */
+			if (!tick_isset(qc->ack_expire)) {
+				qc->ack_expire = tick_add(now_ms, MS_TO_TICKS(QUIC_ACK_DELAY));
+				qc->idle_timer_task->expire = qc->ack_expire;
+				task_queue(qc->idle_timer_task);
+				TRACE_PROTO("ack timer armed", QUIC_EV_CONN_SSLALERT, qc);
+			}
+		}
+		else {
+			qc->idle_timer_task->expire = tick_first(qc->ack_expire, qc->idle_expire);
+			task_queue(qc->idle_timer_task);
+		}
 	}
 }
 
-/* Rearm the idle timer for <qc> QUIC connection depending on <read> boolean
- * which is set to 1 when receiving a packet , and 0 when sending packet
+/* Rearm the idle timer or ack timer for <qc> QUIC connection depending on <read>
+ * and <arm_ack> booleans. The former is set to 1 when receiving a packet ,
+ * and 0 when sending packet. <arm_ack> is set to 1 if this is the ack timer
+ * which must be rearmed.
  */
-static void qc_idle_timer_rearm(struct quic_conn *qc, int read)
+static void qc_idle_timer_rearm(struct quic_conn *qc, int read, int arm_ack)
 {
 	TRACE_ENTER(QUIC_EV_CONN_IDLE_TIMER, qc);
 
@@ -5638,7 +5653,7 @@
 	else {
 		qc->flags &= ~QUIC_FL_CONN_IDLE_TIMER_RESTARTED_AFTER_READ;
 	}
-	qc_idle_timer_do_rearm(qc);
+	qc_idle_timer_do_rearm(qc, arm_ack);
 
 	TRACE_LEAVE(QUIC_EV_CONN_IDLE_TIMER, qc);
 }
@@ -5652,6 +5667,16 @@
 
 	TRACE_ENTER(QUIC_EV_CONN_IDLE_TIMER, qc);
 
+	if (tick_is_expired(qc->ack_expire, now_ms)) {
+		TRACE_PROTO("ack timer expired", QUIC_EV_CONN_SSLALERT, qc);
+		qc->ack_expire = TICK_ETERNITY;
+		/* Note that ->idle_expire is always set. */
+		t->expire = qc->idle_expire;
+		qc->flags |= QUIC_FL_CONN_ACK_TIMER_FIRED;
+		tasklet_wakeup(qc->wait_event.tasklet);
+		goto requeue;
+	}
+
 	/* Notify the MUX before settings QUIC_FL_CONN_EXP_TIMER or the MUX
 	 * might free the quic-conn too early via quic_close().
 	 */
@@ -5674,8 +5699,13 @@
 		HA_ATOMIC_DEC(&prx_counters->half_open_conn);
 	}
 
+ leave:
 	TRACE_LEAVE(QUIC_EV_CONN_IDLE_TIMER, qc);
 	return NULL;
+
+ requeue:
+	TRACE_LEAVE(QUIC_EV_CONN_IDLE_TIMER, qc);
+	return t;
 }
 
 /* Initialize the idle timeout task for <qc>.
@@ -5695,7 +5725,8 @@
 
 	qc->idle_timer_task->process = qc_idle_timer_task;
 	qc->idle_timer_task->context = qc;
-	qc_idle_timer_rearm(qc, 1);
+	qc->ack_expire = TICK_ETERNITY;
+	qc_idle_timer_rearm(qc, 1, 0);
 	task_queue(qc->idle_timer_task);
 
 	ret = 1;
@@ -7884,11 +7915,18 @@
 		pkt->in_flight_len = pkt->len;
 		qc->path->prep_in_flight += pkt->len;
 	}
-	/* Always reset this flags */
+	/* Always reset this flag */
 	qc->flags &= ~QUIC_FL_CONN_IMMEDIATE_CLOSE;
 	if (pkt->flags & QUIC_FL_TX_PACKET_ACK) {
 		qel->pktns->flags &= ~QUIC_FL_PKTNS_ACK_REQUIRED;
 		qel->pktns->rx.nb_aepkts_since_last_ack = 0;
+		qc->flags &= ~QUIC_FL_CONN_ACK_TIMER_FIRED;
+		if (tick_isset(qc->ack_expire)) {
+		    qc->ack_expire = TICK_ETERNITY;
+		    qc->idle_timer_task->expire = qc->idle_expire;
+		    task_queue(qc->idle_timer_task);
+		    TRACE_PROTO("ack timer cancelled", QUIC_EV_CONN_TXPKT, qc);
+		}
 	}
 
 	pkt->pktns = qel->pktns;