]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: quic: refactor STREAM encoding and splitting
authorAmaury Denoyelle <adenoyelle@haproxy.com>
Fri, 7 Feb 2025 17:48:18 +0000 (18:48 +0100)
committerAmaury Denoyelle <adenoyelle@haproxy.com>
Wed, 12 Feb 2025 14:10:03 +0000 (15:10 +0100)
CRYPTO and STREAM frames encoding is similar. If payload is too large,
frame will be splitted and only the first payload part will be written
in the output QUIC packet. This process is complexified by the presence
of a variable-length integer Length field prior to the payload.

This commit aims at refactor these operations. Define two functions to
simplify the code :
* quic_strm_frm_fillbuf() which is used to calculate the optimal frame
  length of a STREAM/CRYPTO frame with its payload in a buffer
* quic_strm_frm_split() which is used to split the frame payload if
  buffer is too small

With this patch, both functions are now implemented for STREAM encoding.

include/haproxy/quic_frame.h
src/quic_frame.c
src/quic_tx.c

index 2da1ee14efcca8cd073fd27c722d916d7b93218a..4bed44d872419ab006e7e7580e2fecb630528854 100644 (file)
@@ -277,5 +277,8 @@ static inline void qc_stream_frm_mv_fwd(struct quic_frame *frm, uint64_t data)
        strm_frm->data = (unsigned char *)b_peek(&cf_buf, data);
 }
 
+size_t quic_strm_frm_fillbuf(size_t room, struct quic_frame *frm, size_t *split_size);
+struct quic_frame *quic_strm_frm_split(struct quic_frame *frm, uint64_t left);
+
 #endif /* USE_QUIC */
 #endif /* _HAPROXY_QUIC_FRAME_H */
index cdf14c8faa32bc913ddcccae99b45c741a0bf5ad..b5a592d1775c370cc5e5fc9dc9073f88c6abac8f 100644 (file)
@@ -1286,3 +1286,109 @@ void qc_release_frm(struct quic_conn *qc, struct quic_frame *frm)
        TRACE_LEAVE(QUIC_EV_CONN_PRSAFRM, qc);
 }
 
+/* Calculate the length of <frm> frame header and payload using a buffer of
+ * <room> size as destination. This helper function can deal both with STREAM
+ * and CRYPTO frames. If input frame payload is too big for <room>, it must be
+ * truncated to <split> offset output parameter.
+ *
+ * Returns the total frame length, or 0 if not enough space.
+ */
+size_t quic_strm_frm_fillbuf(size_t room, struct quic_frame *frm, size_t *split)
+{
+       size_t total = 0; /* Length of frame header and payload. */
+       size_t payload; /* Input payload length. */
+
+       *split = 0;
+
+       if (frm->type >= QUIC_FT_STREAM_8 && frm->type <= QUIC_FT_STREAM_F) {
+               total = 1;
+               total += quic_int_getsize(frm->stream.id);
+               if (frm->type & QUIC_STREAM_FRAME_TYPE_OFF_BIT)
+                       total += quic_int_getsize(frm->stream.offset);
+               payload = frm->stream.len;
+       }
+       else {
+               /* Function must only be used with STREAM or CRYPTO frames. */
+               ABORT_NOW();
+       }
+
+       if (total > room) {
+               /* Header (without Length) already too large. */
+               return 0;
+       }
+       room -= total;
+
+       if (payload) {
+               /* Payload requested, determine Length field varint size. */
+
+               /* Optimal length value if whole room is used. */
+               const size_t room_data = quic_int_cap_length(room);
+               if (!room_data) {
+                       /* Not enough space to encode a varint length first. */
+                       return 0;
+               }
+
+               if (payload > room_data) {
+                       total += quic_int_getsize(room_data);
+                       total += room_data;
+                       *split = room_data;
+               }
+               else {
+                       total += quic_int_getsize(payload);
+                       total += payload;
+               }
+       }
+
+       return total;
+}
+
+/* Split a STREAM or CRYPTO <frm> frame payload at <split> bytes. A newly
+ * allocated frame will point to the original payload head up to the split.
+ * Input frame payload is reduced to contains the remaining data only.
+ *
+ * Returns the newly allocated frame or NULL on error.
+ */
+struct quic_frame *quic_strm_frm_split(struct quic_frame *frm, uint64_t split)
+{
+       struct quic_frame *new;
+       struct buffer stream_buf;
+
+       new = qc_frm_alloc(frm->type);
+       if (!new)
+               return NULL;
+
+       if (frm->type >= QUIC_FT_STREAM_8 && frm->type <= QUIC_FT_STREAM_F) {
+               new->stream.stream = frm->stream.stream;
+               new->stream.buf = frm->stream.buf;
+               new->stream.id = frm->stream.id;
+               new->stream.offset = frm->stream.offset;
+               new->stream.len = split;
+               new->type |= QUIC_STREAM_FRAME_TYPE_LEN_BIT;
+               new->type &= ~QUIC_STREAM_FRAME_TYPE_FIN_BIT;
+               new->stream.data = frm->stream.data;
+               new->stream.dup = frm->stream.dup;
+
+               /* Advance original frame to point to the remaining data. */
+               frm->type |= QUIC_STREAM_FRAME_TYPE_OFF_BIT;
+               stream_buf = b_make(b_orig(frm->stream.buf),
+                                   b_size(frm->stream.buf),
+                                   (char *)frm->stream.data - b_orig(frm->stream.buf), 0);
+               frm->stream.len -= split;
+               frm->stream.offset += split;
+               frm->stream.data = (unsigned char *)b_peek(&stream_buf, split);
+       }
+       else {
+               /* Function must only be used with STREAM or CRYPTO frames. */
+               ABORT_NOW();
+       }
+
+       /* Detach <frm> from its origin if it was duplicated. */
+       if (frm->origin) {
+               LIST_APPEND(&frm->origin->reflist, &new->ref);
+               new->origin = frm->origin;
+               LIST_DEL_INIT(&frm->ref);
+               frm->origin = NULL;
+       }
+
+       return new;
+}
index cde2a7a86fa44df394f291143c7e1cb974841727..e2314652c3fc6891737c5f7b24f09b8598b47576 100644 (file)
@@ -1532,8 +1532,10 @@ static int qc_build_frms(struct list *outlist, struct list *inlist,
         * in the switch/case block.
         */
        list_for_each_entry_safe(cf, cfbak, inlist, list) {
+               struct quic_frame *split_frm;
                /* header length, data length, frame length. */
                size_t hlen, dlen, flen;
+               size_t split_size;
 
                if (!room)
                        break;
@@ -1601,111 +1603,47 @@ static int qc_build_frms(struct list *outlist, struct list *inlist,
                                        continue;
                                }
                        }
-                       /* Note that these frames are accepted in short packets only without
-                        * "Length" packet field. Here, <*len> is used only to compute the
-                        * sum of the lengths of the already built frames for this packet.
-                        *
-                        * Compute the length of this STREAM frame "header" made a all the field
-                        * excepting the variable ones. Note that +1 is for the type of this frame.
-                        */
-                       hlen = 1 + quic_int_getsize(cf->stream.id) +
-                               ((cf->type & QUIC_STREAM_FRAME_TYPE_OFF_BIT) ? quic_int_getsize(cf->stream.offset) : 0);
-                       if (room <= hlen)
-                               continue;
-
-                       TRACE_DEVEL("          New STREAM frame build (room, len)",
-                                   QUIC_EV_CONN_BCFRMS, qc, &room, len);
 
-                       /* hlen contains STREAM id and offset. Ensure there is
-                        * enough room for length field.
-                        */
-                       if (cf->type & QUIC_STREAM_FRAME_TYPE_LEN_BIT) {
-                               dlen = QUIC_MIN(quic_int_cap_length(room - hlen),
-                                               cf->stream.len);
-                               flen = hlen + quic_int_getsize(dlen) + dlen;
-                       }
-                       else {
-                               dlen = QUIC_MIN(room - hlen, cf->stream.len);
-                               flen = hlen + dlen;
-                       }
-
-                       if (cf->stream.len && !dlen) {
-                               /* Only a small gap is left on buffer, not
-                                * enough to encode the STREAM data length.
-                                */
+                       flen = quic_strm_frm_fillbuf(room, cf, &split_size);
+                       if (!flen)
                                continue;
-                       }
 
-                       TRACE_DEVEL(" STREAM data length (hlen, stream.len, dlen)",
-                                   QUIC_EV_CONN_BCFRMS, qc, &hlen, &cf->stream.len, &dlen);
                        TRACE_DEVEL("                 STREAM frame length (flen)",
                                    QUIC_EV_CONN_BCFRMS, qc, &flen);
-                       /* Add the STREAM data length and its encoded length to the packet
-                        * length and the length of this length.
-                        */
-                       *len += flen;
-                       room -= flen;
-                       if (dlen == cf->stream.len) {
-                               /* <cf> STREAM data have been consumed. */
-                               LIST_DEL_INIT(&cf->list);
-                               LIST_APPEND(outlist, &cf->list);
 
-                               qc_stream_desc_send(cf->stream.stream,
-                                                   cf->stream.offset,
-                                                   cf->stream.len);
-                       }
-                       else {
-                               struct quic_frame *new_cf;
-                               struct buffer cf_buf;
+                       /* TODO the MUX is notified about the frame sending via
+                        * previous qc_stream_desc_send call. However, the
+                        * sending can fail later, for example if the sendto
+                        * system call returns an error. As the MUX has been
+                        * notified, the transport layer is responsible to
+                        * bufferize and resent the announced data later.
+                        */
 
-                               new_cf = qc_frm_alloc(cf->type);
-                               if (!new_cf) {
+                       if (split_size) {
+                               split_frm = quic_strm_frm_split(cf, split_size);
+                               if (!split_frm) {
                                        TRACE_ERROR("No memory for new STREAM frame", QUIC_EV_CONN_BCFRMS, qc);
                                        continue;
                                }
 
-                               new_cf->stream.stream = cf->stream.stream;
-                               new_cf->stream.buf = cf->stream.buf;
-                               new_cf->stream.id = cf->stream.id;
-                               new_cf->stream.offset = cf->stream.offset;
-                               new_cf->stream.len = dlen;
-                               new_cf->type |= QUIC_STREAM_FRAME_TYPE_LEN_BIT;
-                               /* FIN bit reset */
-                               new_cf->type &= ~QUIC_STREAM_FRAME_TYPE_FIN_BIT;
-                               new_cf->stream.data = cf->stream.data;
-                               new_cf->stream.dup = cf->stream.dup;
-                               TRACE_DEVEL("split frame", QUIC_EV_CONN_PRSAFRM, qc, new_cf);
-                               if (cf->origin) {
+                               TRACE_DEVEL("split frame", QUIC_EV_CONN_PRSAFRM, qc, split_frm);
+                               if (split_frm->origin)
                                        TRACE_DEVEL("duplicated frame", QUIC_EV_CONN_PRSAFRM, qc);
-                                       /* This <cf> frame was duplicated */
-                                       LIST_APPEND(&cf->origin->reflist, &new_cf->ref);
-                                       new_cf->origin = cf->origin;
-                                       /* Detach this STREAM frame from its origin */
-                                       LIST_DEL_INIT(&cf->ref);
-                                       cf->origin = NULL;
-                               }
-                               LIST_APPEND(outlist, &new_cf->list);
-                               cf->type |= QUIC_STREAM_FRAME_TYPE_OFF_BIT;
-                               /* Consume <dlen> bytes of the current frame. */
-                               cf_buf = b_make(b_orig(cf->stream.buf),
-                                               b_size(cf->stream.buf),
-                                               (char *)cf->stream.data - b_orig(cf->stream.buf), 0);
-                               cf->stream.len -= dlen;
-                               cf->stream.offset += dlen;
-                               cf->stream.data = (unsigned char *)b_peek(&cf_buf, dlen);
-
-                               qc_stream_desc_send(new_cf->stream.stream,
-                                                   new_cf->stream.offset,
-                                                   new_cf->stream.len);
+                               LIST_APPEND(outlist, &split_frm->list);
+                               qc_stream_desc_send(split_frm->stream.stream,
+                                                   split_frm->stream.offset,
+                                                   split_frm->stream.len);
+                       }
+                       else {
+                               LIST_DEL_INIT(&cf->list);
+                               LIST_APPEND(outlist, &cf->list);
+                               qc_stream_desc_send(cf->stream.stream,
+                                                   cf->stream.offset,
+                                                   cf->stream.len);
                        }
 
-                       /* TODO the MUX is notified about the frame sending via
-                        * previous qc_stream_desc_send call. However, the
-                        * sending can fail later, for example if the sendto
-                        * system call returns an error. As the MUX has been
-                        * notified, the transport layer is responsible to
-                        * bufferize and resent the announced data later.
-                        */
+                       *len += flen;
+                       room -= flen;
 
                        break;