]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: mux-quic: implement QMux record parsing
authorAmaury Denoyelle <adenoyelle@haproxy.com>
Wed, 8 Apr 2026 08:31:57 +0000 (10:31 +0200)
committerAmaury Denoyelle <adenoyelle@haproxy.com>
Fri, 10 Apr 2026 08:20:52 +0000 (10:20 +0200)
This is the first patch of a serie which aims to support the new Record
layer defined by the draft 01 of QMux protocol.

  https://www.ietf.org/archive/id/draft-ietf-quic-qmux-01.html#name-qmux-records

This patch deals with QMux reception at the MUX layer. The function
qcc_qstrm_recv() is adapted to read record headers before frame parsing.
This requires to keep the last record length read in a new QCC field
named <rx.rlen>.

Frames are only parsed once a full record is received. One of the
advantage of the record layer is that it can only contains whole frame
without truncation.

include/haproxy/mux_quic-t.h
src/mux_quic_qstrm.c

index 51686c0dadd1e5feb10493499e43694b326cce5a..a8c2f5c83d3fa276731c376d49c757bcf8be869f 100644 (file)
@@ -93,6 +93,7 @@ struct qcc {
        } tx;
        struct {
                struct buffer qstrm_buf;
+               size_t rlen; /* last record length read */
        } rx;
 
        uint64_t largest_bidi_r; /* largest remote bidi stream ID opened. */
index 4d6107c0d3328c26888976d7eb3cf4baceb6741a..3b4b44f46897f72a2f7e61e1f8564431174ba67c 100644 (file)
@@ -93,8 +93,9 @@ int qcc_qstrm_recv(struct qcc *qcc)
 {
        struct connection *conn = qcc->conn;
        struct buffer *buf = &qcc->rx.qstrm_buf;
+       struct buffer buf_rec;
        int total = 0, frm_ret;
-       size_t ret;
+       size_t ret = 1;
 
        TRACE_ENTER(QMUX_EV_QCC_RECV, qcc->conn);
 
@@ -103,14 +104,9 @@ int qcc_qstrm_recv(struct qcc *qcc)
                /* Wrapping is not supported for QMux reception. */
                BUG_ON(b_data(buf) != b_contig_data(buf, 0));
 
-               /* Checks if there is no more room before wrapping position. */
-               if (b_head(buf) + b_contig_data(buf, 0) == b_wrap(buf)) {
-                       if (!b_room(buf)) {
-                               /* TODO frame bigger than buffer, connection must be closed */
-                               ABORT_NOW();
-                       }
-
-                       /* Realign data in the buffer to have more room. */
+               /* If current record is too big, realign buffer for more room. */
+               if (b_head(buf) + qcc->rx.rlen > b_wrap(buf)) {
+                       BUG_ON(qcc->rx.rlen > b_size(buf)); /* TODO max_record_size */
                        memmove(b_orig(buf), b_head(buf), b_data(buf));
                        buf->head = 0;
                }
@@ -119,23 +115,42 @@ int qcc_qstrm_recv(struct qcc *qcc)
                        b_realign_if_empty(buf);
                }
 
-               ret = conn->xprt->rcv_buf(conn, conn->xprt_ctx, buf, b_contig_space(buf), NULL, 0, 0);
-               BUG_ON(conn->flags & CO_FL_ERROR);
+               if ((!b_data(buf) && !qcc->rx.rlen) || qcc->rx.rlen > b_data(buf)) {
+                       /* Previous realign operation should ensure send cannot result in data wrapping. */
+                       BUG_ON(b_data(buf) && b_tail(buf) == b_orig(buf));
+                       ret = conn->xprt->rcv_buf(conn, conn->xprt_ctx, buf, b_contig_space(buf), NULL, 0, 0);
+                       BUG_ON(conn->flags & CO_FL_ERROR); /* TODO handle errors */
+                       /* Previous realign operation should ensure send cannot result in data wrapping. */
+                       BUG_ON(b_data(buf) != b_contig_data(buf, 0));
+               }
 
-               total += ret;
-               while (b_data(buf)) {
-                       frm_ret = qstrm_parse_frm(qcc, buf);
+               if (b_data(buf) && !qcc->rx.rlen) {
+                       int ret2 = b_quic_dec_int(&qcc->rx.rlen, buf, NULL);
+                       BUG_ON(!ret2); /* TODO incomplete record length */
+                       if (b_head(buf) + qcc->rx.rlen > b_wrap(buf))
+                               goto recv;
+                       BUG_ON(b_data(buf) < qcc->rx.rlen); /* TODO incomplete record */
+               }
+
+               /* TODO realign necessary if record boundary at the extreme end of the buffer */
+               BUG_ON(!qcc->rx.rlen && b_data(buf) && b_tail(buf) == b_orig(buf));
+
+               while (qcc->rx.rlen && b_data(buf) >= qcc->rx.rlen) {
+                       buf_rec = b_make(b_orig(buf), b_size(buf),
+                                        b_head_ofs(buf), qcc->rx.rlen);
+                       frm_ret = qstrm_parse_frm(qcc, &buf_rec);
 
                        BUG_ON(frm_ret < 0); /* TODO handle fatal errors */
                        if (!frm_ret) {
-                               /* Checks if wrapping position is reached, requires realign. */
-                               if (b_head(buf) + b_contig_data(buf, 0) == b_wrap(buf))
-                                       goto recv;
-                               /* Truncated frame read but room still left, subscribe to retry later. */
-                               break;
+                               /* emit FRAME_ENCODING_ERROR */
+                               ABORT_NOW();
                        }
 
+                       /* A frame cannot be bigger than a record thanks to <buf_rec> delimitation. */
+                       BUG_ON(qcc->rx.rlen < frm_ret);
                        b_del(buf, frm_ret);
+                       qcc->rx.rlen -= frm_ret;
+                       total += frm_ret;
                }
        } while (ret > 0);