]> git.ipfire.org Git - thirdparty/openssl.git/commitdiff
QUIC TX Packetiser and Streams Mapper
authorHugo Landau <hlandau@openssl.org>
Mon, 26 Sep 2022 16:06:59 +0000 (17:06 +0100)
committerHugo Landau <hlandau@openssl.org>
Thu, 24 Nov 2022 08:15:20 +0000 (08:15 +0000)
Reviewed-by: Tomas Mraz <tomas@openssl.org>
Reviewed-by: Paul Dale <pauli@openssl.org>
Reviewed-by: Matt Caswell <matt@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/19346)

30 files changed:
doc/designs/quic-design/tx-packetiser.md
include/internal/quic_ackm.h
include/internal/quic_fc.h
include/internal/quic_fifd.h
include/internal/quic_record_tx.h
include/internal/quic_stream.h
include/internal/quic_stream_map.h [new file with mode: 0644]
include/internal/quic_txp.h [new file with mode: 0644]
include/internal/quic_txpim.h
include/internal/quic_types.h
include/internal/quic_wire.h
include/internal/quic_wire_pkt.h
ssl/quic/build.info
ssl/quic/quic_ackm.c
ssl/quic/quic_fifd.c
ssl/quic/quic_record_rx.c
ssl/quic/quic_record_tx.c
ssl/quic/quic_sstream.c
ssl/quic/quic_stream_map.c [new file with mode: 0644]
ssl/quic/quic_txp.c [new file with mode: 0644]
ssl/quic/quic_wire.c
ssl/quic/quic_wire_pkt.c
test/build.info
test/quic_fifd_test.c
test/quic_record_test.c
test/quic_record_test_util.h [new file with mode: 0644]
test/quic_stream_test.c
test/quic_txp_test.c [new file with mode: 0644]
test/quic_wire_test.c
test/recipes/70-test_quic_txp.t [new file with mode: 0644]

index 8e0b4a3094257e22c085c2f316563ca082a5a209..f2d7e69a160a6d53047849b8accda9b73ada8e20 100644 (file)
@@ -12,13 +12,39 @@ Creation & Destruction
 ----------------------
 
 ```c
-struct ossl_quic_tx_packetiser_st {
-    QUIC_CONNECTION *conn;
-};
+typedef struct quic_tx_packetiser_args_st {
+    /* Configuration Settings */
+    QUIC_CONN_ID    cur_scid;   /* Current Source Connection ID we use. */
+    QUIC_CONN_ID    cur_dcid;   /* Current Destination Connection ID we use. */
+    BIO_ADDR        peer;       /* Current destination L4 address we use. */
+    /* ACK delay exponent used when encoding. */
+    uint32_t        ack_delay_exponent;
+
+    /* Injected Dependencies */
+    OSSL_QTX        *qtx;       /* QUIC Record Layer TX we are using */
+    QUIC_TXPIM      *txpim;     /* QUIC TX'd Packet Information Manager */
+    QUIC_CFQ        *cfq;       /* QUIC Control Frame Queue */
+    OSSL_ACKM       *ackm;      /* QUIC Acknowledgement Manager */
+    QUIC_STREAM_MAP *qsm;       /* QUIC Streams Map */
+    QUIC_TXFC       *conn_txfc; /* QUIC Connection-Level TX Flow Controller */
+    QUIC_RXFC       *conn_rxfc; /* QUIC Connection-Level RX Flow Controller */
+    const OSSL_CC_METHOD *cc_method; /* QUIC Congestion Controller */
+    OSSL_CC_DATA    *cc_data;   /* QUIC Congestion Controller Instance */
+    OSSL_TIME       (*now)(void *arg);  /* Callback to get current time. */
+    void            *now_arg;
+
+    /*
+     * Injected dependencies - crypto streams.
+     *
+     * Note: There is no crypto stream for the 0-RTT EL.
+     *       crypto[QUIC_PN_SPACE_APP] is the 1-RTT crypto stream.
+     */
+    QUIC_SSTREAM    *crypto[QUIC_PN_SPACE_NUM];
+} QUIC_TX_PACKETISER_ARGS;
 
 _owur typedef struct ossl_quic_tx_packetiser_st OSSL_QUIC_TX_PACKETISER;
 
-OSSL_QUIC_TX_PACKETISER ossl_quic_tx_packetiser_new(QUIC_CONNECTION *conn);
+OSSL_QUIC_TX_PACKETISER *ossl_quic_tx_packetiser_new(QUIC_TX_PACKETISER_ARGS *args);
 void ossl_quic_tx_packetiser_free(OSSL_QUIC_TX_PACKETISER *tx);
 ```
 
@@ -47,33 +73,188 @@ uint32_t SSL_get_priority(SSL *stream);
 For protocols where priority is not meaningful, the set function is a noop and
 the get function returns a constant value.
 
-### Frame
+Interactions
+------------
+
+The packetiser interacts with the following components, the APIs for which
+can be found in their respective design documents and header files:
+
+- SSTREAM: manages application stream data for transmission.
+- QUIC_STREAM_MAP: Maps stream IDs to QUIC_STREAM objects and tracks which
+  streams are active (i.e., need servicing by the TX packetiser).
+- Crypto streams for each EL other than 0-RTT (each is one SSTREAM).
+- CFQ: queried for generic control frames
+- QTX: record layer which completed packets are written to.
+- TXPIM: logs information about transmitted packets, provides information to
+  FIFD.
+- FIFD: notified of transmitted packets.
+- ACKM: loss detector.
+- Connection and stream-level TXFC and RXFC instances.
+- Congestion controller (not needed for MVP).
+
+### SSTREAM
+
+Each application or crypto stream has a SSTREAM object for the sending part.
+This manages the buffering of data written to the stream, frees that data when
+the packet it was sent in was acknowledged, and can return the data for
+retransmission on loss. It receives loss and acknowledgement notifications from
+the FIFD without direct TX packetiser involvement.
+
+### QUIC Stream Map
+
+The TX packetiser queries the QUIC stream map for a list of active streams
+(QUIC_STREAM), which are iterated on a rotating round robin basis. Each
+QUIC_STREAM provides access to the various components, such as a QUIC_SSTREAM
+instance (for streams with a send part). Streams are marked inactive when
+they no longer have any need to generate frames at the present time.
 
-QUIC frames are represented by a leading variable length integer
-indicating the type of the frame.  This is followed by the frame data.
-Only the first byte of the type is important because there are no defined
-packet types that need more than one byte to represent.  Thus:
+### Crypto Streams
+
+The crypto streams for each EL (other than 0-RTT, which does not have a crypto
+stream) are represented by SSTREAM instances. The TX packetiser queries SSTREAM
+instances provided to it as needed when generating packets.
+
+### CFQ
+
+Many control frames do not require special handling and are handled by the
+generic CFQ mechanism. The TX packetiser queries the CFQ for any frames to be
+sent and schedules them into a packet.
+
+### QUIC Write Record Layer
+
+Coalesced frames are passed to the QUIC record layer for encryption and sending.
+To send accumulated frames as packets to the QUIC Write Record Layer:
 
 ```c
-struct ossl_quic_frame_st {
-    unsigned char type;
-};
+int ossl_qtx_write_pkt(OSSL_QTX *qtx, const OSSL_QTX_PKT *pkt);
+```
 
-typedef struct ossl_quic_frame_st OSSL_QUIC_FRAME;
+The packetiser will attempt to maximise the number of bytes in a packet.
+It will also attempt to create multiple packets to send simultaneously.
 
-struct ossl_quic_txp_frame_st {
-    OSSL_QUIC_FRAME *frame; /* Frame in wire format */
-    size_t frame_len;       /* Size of frame */
-    uint32_t priority;      /* Priority of frame */
-};
+The packetiser should also implement a wait time to allow more data to
+accumulate before exhausting it's supply of data.  The length of the wait
+will depend on how much data is queued already and how much space remains in
+the packet being filled.  Once the wait is finished, the packets will be sent
+by calling:
 
-typedef struct ossl_quic_txp_frame_st OSSL_QUIC_TXP_FRAME;
+```c
+void ossl_qtx_flush_net(OSSL_QTX *qtx);
 ```
 
-The packetiser/ACK manager can alter the priority of a frame a small amount.
-For example, a retransmitted frame may have it's priority increased slightly.
+The write record layer is responsible for coalescing multiple QUIC packets
+into datagrams.
+
+### TXPIM, FIFD, ACK Handling and Loss Detector
+
+ACK handling and loss detection is provided by the ACKM and FIFD. The FIFD uses
+the per-packet information recorded by the TXPIM to track which frames are
+contained within a packet which was lost or acknowledged, and generates
+callbacks to the TX packetiser, SSTREAM instances and CFQ to allow it to
+regenerate those frames as needed.
+
+1. When a packet is sent, the packetiser informs the FIFD, which also informs
+   the ACK Manager.
+2. When a packet is ACKed, the FIFD notifies applicable SSTREAMs and the CFQ
+   as appropriate.
+3. When a packet is lost, the FIFD notifies the TX packetiser of any frames
+   which were in the lost packet for which the Regenerate strategy is
+   applicable.
+4. Currently, no notifications to the TX packetiser are needed when packets
+   are discarded (e.g. due to an EL being discarded).
+
+### Flow Control
+
+The packetiser interacts with connection and stream-level TXFC and RXFC
+instances. It interacts with RXFC instances to know when to generate flow
+control frames, and with TXFC instances to know how much stream data it is
+allowed to send in a packet.
+
+### Congestion Control
+
+The packetiser is likely to interact with the congestion controller in the
+future. Currently, congestion control is a no-op.
+
+Packets
+-------
+
+Packet formats are defined in [RFC 9000 17.1 Packet Formats].
+
+### Packet types
 
-#### Frames
+QUIC supports a number of different packets. The combination of packets of
+different encryption levels as per [RFC 9000 12.2 Coalescing Packets], is done
+by the record layer. Non-encrypted packets are not handled by the TX Packetiser
+and callers may send them by direct calls to the record layer.
+
+#### Initial Packet
+
+Refer to [RFC 9000 17.2.2 Initial Packet].
+
+#### Handshake Packet
+
+Refer to [RFC 9000 17.2.4 Handshake Packet].
+
+#### App Data 0-RTT Packet
+
+Refer to [RFC 9000 17.2.3 0-RTT].
+
+#### App Data 1-RTT Packet
+
+Refer to [RFC 9000 17.3.1 1-RTT].
+
+Packetisation and Processing
+----------------------------
+
+### Definitions
+
+ - Maximum Datagram Payload Length (MDPL): The maximum number of UDP payload
+   bytes we can put in a UDP packet. This is derived from the applicable PMTU.
+   This is also the maximum size of a single QUIC packet if we place only one
+   packet in a datagram. The MDPL may vary based on both local source IP and
+   destination IP due to different path MTUs.
+
+ - Maximum Packet Length (MPL): The maximum size of a fully encrypted
+   and serialized QUIC packet in bytes in some given context. Typically
+   equal to the MDPL and never greater than it.
+
+ - Maximum Plaintext Payload Length (MPPL): The maximum number of plaintext
+   bytes we can put in the payload of a QUIC packet. This is related to
+   the MDPL by the size of the encoded header and the size of any AEAD
+   authentication tag which will be attached to the ciphertext.
+
+ - Coalescing MPL (CMPL): The maximum number of bytes left to serialize
+   another QUIC packet into the same datagram as one or more previous
+   packets. This is just the MDPL minus the total size of all previous
+   packets already serialized into to the same datagram.
+
+ - Coalescing MPPL (CMPPL): The maximum number of payload bytes we can put in
+   the payload of another QUIC packet which is to be coalesced with one or
+   more previous QUIC packets and placed into the same datagram. Essentially,
+   this is the room we have left for another packet payload.
+
+ - Remaining CMPPL (RCMPPL): The number of bytes left in a packet whose payload
+   we are currently forming. This is the CMPPL minus any bytes we have already
+   put into the payload.
+
+ - Minimum Datagram Length (MinDPL): In some cases we must ensure a datagram
+   has a minimum size of a certain number of bytes. This does not need to be
+   accomplished with a single packet, but we may need to add PADDING frames
+   to the final packet added to a datagram in this case.
+
+ - Minimum Packet Length (MinPL): The minimum serialized packet length we
+   are using while serializing a given packet. May often be 0. Used to meet
+   MinDPL requirements, and thus equal to MinDPL minus the length of any packets
+   we have already encoded into the datagram.
+
+ - Minimum Plaintext Payload Length (MinPPL): The minimum number of bytes
+   which must be placed into a packet payload in order to meet the MinPL
+   minimum size when the packet is encoded.
+
+ - Active Stream: A stream which has data or flow control frames ready for
+   transmission.
+
+### Frames
 
 Frames are taken from [RFC 9000 12.4 Frames and Frame Types].
 
@@ -113,7 +294,7 @@ Frames are taken from [RFC 9000 12.4 Frames and Frame Types].
 
 The various fields are as defined in RFC 9000.
 
-##### Pkts
+#### Pkts
 
 _Pkts_ are defined as:
 
@@ -124,7 +305,7 @@ _Pkts_ are defined as:
 | 0 | Valid in 0-RTT packets|
 | 1 | Valid in 1-RTT packets|
 
-##### Spec
+#### Spec
 
 _Spec_ is defined as:
 
@@ -139,53 +320,6 @@ For `C`, `N` and `P`, the entire packet must consist of only frames with the
 marking for the packet to qualify for it.  For example, a packet with an ACK
 frame and a _stream_ frame would qualify for neither the `C` or `N` markings.
 
-### Packets
-
-Frames are coalesced into packets which are then sent by the record layer.
-The `packet_header` is a pointer to the leading bytes of the packet.
-The `frames` are pointers to the individual frames that make up the
-packet's body.
-It is expected that the record layer will encrypt from the `packet_header` and
-`frames` directly without a copy.
-
-```c
-enum packet_validity_e {
-    QUIC_PACKET_INITIAL,
-    QUIC_PACKET_HANDSHAKE,
-    QUIC_PACKET_0_RTT,
-    QUIC_PACKET_1_RTT
-};
-
-typedef enum packet_validity_e PACKET_VALIDITY;
-
-struct ossl_quic_packet_st {
-    QUIC_CONNECTION *conn;
-    unsigned char *packet_header;
-    size_t packet_header_length;
-    STACK_OF(OSSL_QUIC_TXP_FRAME) *frames;
-
-    QUIC_PN packet_number; /* RFC 9000 12.3 */
-    size_t packet_length;
-
-    /*
-     * One of the QUIC_PN_SPACE_* values. This qualifies the pkt_num field
-     * into a packet number space.
-     */
-    unsigned int pkt_space : 2;
-
-    /* Pkts options */
-    PACKET_VALIDITY validity;
-
-    /* Spec */
-    unsigned int no_ack : 1;
-    unsigned int no_congestion_control : 1;
-    unsigned int probing : 1;
-    unsigned int flow_controlled : 1;
-};
-
-typedef struct ossl_quic_packet_st OSSL_QUIC_PACKET;
-```
-
 #### Notes
 
 - Do we need the distinction between 0-rtt and 1-rtt when both are in
@@ -193,227 +327,364 @@ typedef struct ossl_quic_packet_st OSSL_QUIC_PACKET;
 - 0-RTT packets can morph into 1-RTT packets and this needs to be handled by
   the packetiser.
 
-Interactions
-------------
-
-The packetiser needs to interact with other modules.  This defines the APIs
-by which it does so.
-
-Frames are passed to the packetiser on a per stream basis.
-The frames must be fully formed.  By passing a frame to this function,
-ownership is passed to the packetiser which queues the frames for later
-sending by the record layer.
-
-```c
-int ossl_quic_packetiser_buffer_frame(OSSL_QUIC_TX_PACKETISER *tx,
-                                      QUIC_CONNECTION *stream,
-                                      const OSSL_QUIC_FRAME *frame,
-                                      size_t frame_length);
-```
-
-### Stream Send Buffers
-
-Data from the stream send buffers is treated specially.  The packetiser knows
-how much space is left in each packet and it will request that amount of data
-from the stream send buffers.  The stream send buffers will return a
-constructed frame header and a pointer to the steam data and length.  A second
-call exists to allow the packetiser to know how much data is queued for a stream
-so that planning for the creation of multiple packets is possible.
+### Frame Type Prioritisation
 
-```c
-int ossl_quic_get_app_data(QUIC_STREAM *stream, size_t request,
-                           const OSSL_QUIC_FRAME **frame,
-                           const unsigned char **data,
-                           size_t *data_len);
-
-size_t ossl_quic_get_app_data_size(QUIC_STREAM *stream);
-```
-
-#### Notes
-
-* Unclear how to best free the data after sent data was acked.
-  The data will be fragments from the buffers so the stream send buffers will
-  need to remember which fragment have been sent and which are pending and
-  only free once everything is sent:
+The frame types listed above are reordered below in the order of priority with
+which we want to serialize them. We discuss the motivations for this priority
+ordering below. Items without a line between them have the same priority.
 
-```c
-int ossl_quic_free_app_data(QUIC_STREAM *stream, void *data, size_t data_len);
-```
-
-* Need a call to tell the stream send buffers to forget about previously
-  requested app data because it needs to be retransmitted and the
-  boundaries could change.  Any record of the indicated data having being
-  transmitted should be removed and the data is made eligible to be sent
-  again.
-
-```c
-int ossl_quic_retransmitting_app_data(QUIC_STREAM *stream,
-                                      void *data, size_t data_len);
-```
-
-### TLS Handshake Record Layer
-
-Uses the Record Layer API to implement the inner TLS-1.3 protocol handshake.
-It produces the QUIC crypto frames which are queued using the same mechanism
-as the [Stream Send Buffers](#stream-send-buffers) above.
-
-### Flow Controller and Statistics Collector
-
-To make decisions about what frames to coalesce, the packetiser relies
-on the flow controller to enforce stream and connection bandwidth limits
-[RFC 9000 4.1 Data Flow Control].
-
-```c
-/*
- * Return the maximum amount of data that is permitted for the given stream.
- * This includes both the stream limit and it's associated connection limit.
- */
-size_t ossl_quic_stream_flow_maximum_size(QUIC_STREAM *stream);
-
-/*
- * Inform the flow controller that an amount of data has been queued for
- * sending to a stream.
- */
-int ossl_quic_flow_controller_sent_data(QUIC_FLOW_CONTROLLER *flow,
-                                        QUIC_STREAM *stream, size_t bytes);
-```
-
-### Congestion Controller
-
-Also part of the frame coalescing decision is the congestion controller
-[RFC 9002].  For MVP, this will be a _just send it_.
+```plain
+HANDSHAKE_DONE          GCR / REGEN
+----------------------------
+MAX_DATA                      REGEN
+DATA_BLOCKED                  REGEN
+MAX_STREAMS                   REGEN
+STREAMS_BLOCKED               REGEN
+----------------------------
 
-```c
-/*
- * Pluggable congestion controller APIs go here
- * Extract that is required from #18018
- */
-```
 
-### QUIC Write Record Layer
+NEW_CONNECTION_ID             GCR
+RETIRE_CONNECTION_ID          GCR
+----------------------------
+PATH_CHALLENGE                  -
+PATH_RESPONSE                   -
+----------------------------
+ACK                             -     (non-ACK-eliciting)
+----------------------------
+CONNECTION_CLOSE              ***     (non-ACK-eliciting)
+----------------------------
+NEW_TOKEN                     GCR
 
-Coalesced frames are passed to the QUIC record layer for encryption and sending.
-To send accumulated frames as packets to the QUIC Write Record Layer:
+----------------------------
+CRYPTO                        GCR/*q
+
+============================          ]  priority group, repeats per stream
+RESET_STREAM                  GCR*    ]
+STOP_SENDING                  GCR*    ]
+----------------------------          ]
+MAX_STREAM_DATA               REGEN   ]
+STREAM_DATA_BLOCKED           REGEN   ]
+----------------------------          ]
+STREAM                        *q      ]
+============================          ]
 
-```c
-int ossl_qtx_write_pkt(OSSL_QTX *qtx, const OSSL_QTX_PKT *pkt);
+----------------------------
+PING                           -
+----------------------------
+PADDING                        -      (non-ACK-eliciting)
 ```
 
-The packetiser will attempt to maximise the number of bytes in a packet.
-It will also attempt to create multiple packets to send simultaneously.
-
-The packetiser should also implement a wait time to allow more data to
-accumulate before exhausting it's supply of data.  The length of the wait
-will depend on how much data is queue already and how much space remains in
-the packet being filled.  Once the wait is finished, the packets will be sent
-by calling:
+(See [Frame in Flight Manager](quic-fifm.md) for information on the meaning of
+the second column, which specifies the retransmission strategy for each frame
+type.)
+
+- `PADDING`: For obvious reasons, this frame type is the lowest priority. We only
+  add `PADDING` frames at the very end after serializing all other frames if we
+  have been asked to ensure a non-zero MinPL but have not yet met that minimum.
+
+- `PING`: The `PING` frame is encoded as a single byte. It is used to make a packet
+  ACK-eliciting if it would not otherwise be ACK-eliciting. Therefore we only
+  need to send it if
+
+  a. we have been asked to ensure the packet is ACK-eliciting, and
+  b. we do not have any other ACK-eliciting frames in the packet.
+
+  Thus we wait until the end before adding the PING frame as we may end up
+  adding other ACK-eliciting frames and not need to add it. There is never
+  a need to add more than one PING frame. If we have been asked to ensure
+  the packet is ACK-eliciting and we do not know for sure up front if we will
+  add any other ACK-eliciting packet, we must reserve one byte of our CMPPL
+  to ensure we have room for this. We can cancel this reservation if we
+  add an ACK-eliciting frame earlier. For example:
+
+  - We have been asked to ensure a packet is ACK-eliciting and the CMPPL is
+    1000 (we are coalescing with another packet).
+  - We allocate 999 bytes for non-PING frames.
+  - While adding non-PING frames, we add a STREAM frame, which is
+    ACK-eliciting, therefore the PING frame reservation is cancelled
+    and we increase our allocation for non-PING frames to 1000 bytes.
+
+- `HANDSHAKE_DONE`: This is a single byte frame with no data which is used to
+  indicate handshake completion. It is only ever sent once. As such, it can be
+  implemented as a single flag, and there is no risk of it outcompeting other
+  frames. It is therefore trivially given the highest priority.
+
+- `MAX_DATA`, `DATA_BLOCKED`: These manage connection-level flow control. They
+  consist of a single integer argument, and, as such, take up little space, but
+  are also critical to ensuring the timely expansion of the connection-level
+  flow control window. Thus there is a performance reason to include them in
+  packets with high priority and due to their small size and the fact that there
+  will only ever be at most one per packet, there is no risk of them
+  outcompeting other frames.
+
+- `MAX_STREAMS`, `STREAMS_BLOCKED`: Similar to the frames above for
+  connection-level flow control, but controls rate at which new streams are
+  opened. The same arguments apply here, so they are prioritised equally.
+
+- `STREAM`: This is the bread and butter of a QUIC packet, and contains
+  application-level stream data. As such these frames can usually be expected to
+  consume most of our packet's payload budget. We must generally assume that
+
+  - there are many streams, and
+  - several of those streams have much more data waiting to be sent than
+    can be sent in a single packet.
+
+  Therefore we must ensure some level of balance between multiple competing
+  streams. We refer to this as stream scheduling. There are many strategies that
+  can be used for this, and in the future we might even support
+  application-signalled prioritisation of specific streams. We discuss
+  stream scheduling further below.
+
+  Because these frames are expected to make up the bulk of most packets, we
+  consider them low priority, higher only than `PING` and `PADDING` frames.
+  Moreover, we give priority to control frames as unlike `STREAM` frames, they
+  are vital to the maintenance of the health of the connection itself. Once we
+  have serialized all other frame types, we can reserve the rest of the packet
+  for any `STREAM` frames. Since all `STREAM` frames are ACK-eliciting, if we
+  have any `STREAM` frame to send at all, it cancels any need for any `PING`
+  frame, and may be able to partially or wholly obviate our need for any
+  `PADDING` frames which we might otherwise have needed. Thus once we start
+  serializing STREAM frames, we are limited only by the remaining CMPPL.
+
+- `MAX_STREAM_DATA`, `STREAM_DATA_BLOCKED`: Stream-level flow control. These
+  contain only a stream ID and integer value used for flow control, so they are
+  not large. Since they are critical to the management and health of a specific
+  stream, and because they are small and have no risk of stealing too many bytes
+  from the `STREAM` frames they follow, we always serialize these before any
+  corresponding `STREAM` frames for a given stream ID.
+
+- `RESET_STREAM`, `STOP_SENDING`: These terminate a given stream ID and thus are
+  also associated with a stream. They are also small. As such, we consider these
+  higher priority than both `STREAM` frames and the stream-level flow control
+  frames.
+
+- `NEW_CONNECTION_ID`, `RETIRE_CONNECTION_ID`: These are critical for connection
+  management and are not particularly large, therefore they are given a high
+  priority.
+
+- `PATH_CHALLENGE`, `PATH_RESPONSE`: Used during connection migration, these
+  are small and are given a high priority.
+
+- `CRYPTO`: These frames generate the logical crypto stream, which is a logical
+  bidirectional bytestream used to transport TLS records for connection
+  handshake and management purposes. As such, the crypto stream is viewed as
+  similar to application streams but of a higher priority. We are willing to let
+  `CRYPTO` frames outcompete all application stream-related frames if need be,
+  as `CRYPTO` frames are more important to the maintenance of the connection and
+  the handshake layer should not generate an excessive amount of data.
+
+- `CONNECTION_CLOSE`, `NEW_TOKEN`: The `CONNECTION_CLOSE` frame can contain a
+  user-specified reason string. The `NEW_TOKEN` frame contains an opaque token
+  blob. Both can be arbitrarily large but for the fact that they must fit in a
+  single packet and are thus ultimately limited by the MPPL. However, these
+  frames are important to connection maintenance and thus are given a priority
+  just above that of `CRYPTO` frames. The `CONNECTION_CLOSE` frame has higher
+  priority than `NEW_TOKEN`.
+
+- `ACK`: `ACK` frames are critical to avoid needless retransmissions by our peer.
+  They can also potentially become large if a large number of ACK ranges needs
+  to be transmitted. Thus `ACK` frames are given a fairly high priority;
+  specifically, their priority is higher than all frames which have the
+  potential to be large but below all frames which contain only limited data,
+  such as connection-level flow control. However, we reserve the right to adapt
+  the size of the ACK frames we transmit by chopping off some of the PN ranges
+  to limit the size of the ACK frame if its size would be otherwise excessive.
+  This ensures that the high priority of the ACK frame does not starve the
+  packet of room for stream data.
+
+### Stream Scheduling
+
+**Stream budgeting.** When it is time to add STREAM frames to a packet under
+construction, we take our Remaining CMPPL and call this value the Streams
+Budget. There are many ways we could make use of this Streams Budget.
+
+For the purposes of stream budgeting, we consider all bytes of STREAM frames,
+stream-level flow control frames, RESET_STREAM and STOP_SENDING frames to
+“belong” to their respective streams, and the encoded sizes of these frames are
+accounted to those streams for budgeting purposes. If the total number of bytes
+of frames necessary to serialize all pending data from all active streams is
+less than our Streams Budget, there is no need for any prioritisation.
+Otherwise, there are a number of strategies we could employ. We can categorise
+the possible strategies into two groups to begin with:
+
+  - **Intrapacket muxing (IRPM)**. When the data available to send across all
+    streams exceeds the Streams Budget for the packet, allocate an equal
+    portion of the packet to each stream.
+
+  - **Interpacket muxing (IXPM).** When the data available to send across all
+    streams exceeds the Streams Budget for the packet, try to fill the packet
+    using as few streams as possible, and multiplex by using different
+    streams in different packets.
+
+Though obvious, IRPM does not appear to be a widely used strategy [1] [2],
+probably due to a clear downside: if a packet is lost and it contains data for
+multiple streams, all of those streams will be held up. This undermines a key
+advantage of QUIC, namely the ability of streams to function independently of
+one another for the purposes of head-of-line blocking. By contrast, with IXPM,
+if a packet is lost, typically only a single stream is held up.
+
+Suppose we choose IXPM. We must now choose a strategy for deciding when to
+schedule streams on packets. [1] establishes that there are two basic
+strategies found in use:
+
+  - A round robin (RR) strategy in which the frame scheduler switches to
+    the next active stream every n packets (where n ≥ 1).
+
+  - A sequential (SEQ) strategy in which a stream keeps being transmitted
+    until it is no longer active.
+
+The SEQ strategy does not appear to be suitable for general-purpose
+applications as it presumably starves other streams of bandwidth. It appears
+that this strategy may be chosen in some implementations because it can offer
+greater efficiency with HTTP/3, where there are performance benefits to
+completing transmission of one stream before beginning the next. However, it
+does not seem like a suitable choice for an application-agnostic QUIC
+implementation. Thus the RR strategy is the better choice and the popular choice
+in a survey of implementations.
+
+The choice of `n` for the RR strategy is most trivially 1 but there are
+suggestions [1] that a higher value of `n` may lead to greater performance due
+to packet loss in typical networks occurring in small durations affecting small
+numbers of consecutive packets. Thus, if `n` is greater than 1, fewer streams
+will be affected by packet loss and held up on average. However, implementing
+different values of `n` poses no non-trivial implementation concerns, so it is
+not a major concern for discussion here. Such a parameter can easily be made
+configurable.
+
+Thus, we choose what active stream to select to fill in a packet on a
+revolving round robin basis, moving to the next stream in the round robin
+every `n` packets. If the available data in the active stream is not enough to
+fill a packet, we do also move to the next stream, so IRPM can still occur in
+this case.
+
+When we fill a packet with a stream, we start with any applicable `RESET_STREAM`
+or `STOP_SENDING` frames, followed by stream-level flow control frames if
+needed, followed by `STREAM` frames.
+
+(This means that `RESET_STREAM`, `STOP_SENDING`, `MAX_STREAM_DATA`,
+ `STREAM_DATA_BLOCKED` and `STREAM` frames are interleaved rather than occurring
+ in a fixed priority order; i.e., first there could be a `STOP_SENDING` frame
+ for one stream, then a `STREAM` frame for another, then another `STOP_SENDING`
+ frame for another stream, etc.)
+
+[1] [Same Standards; Different Decisions: A Study of QUIC and HTTP/3
+Implementation Diversity (Marx et al. 2020)](https://qlog.edm.uhasselt.be/epiq/files/QUICImplementationDiversity_Marx_final_11jun2020.pdf)
+[2] [Resource Multiplexing and Prioritization in HTTP/2 over TCP versus HTTP/3
+over QUIC (Marx et al. 2020)](https://h3.edm.uhasselt.be/files/ResourceMultiplexing_H2andH3_Marx2020.pdf)
+
+### Packets with Special Requirements
+
+Some packets have special requirements which the TX packetiser must meet:
+
+- **Padded Initial Datagrams.**
+  A datagram must always be padded to at least 1200 bytes if it contains an
+  Initial packet. (If there are multiple packets in the datagram, the padding
+  does not necessarily need to be part of the Initial packet itself.) This
+  serves to confirm that the QUIC minimum MTU is met.
+
+- **Token in Initial Packets.**
+  Initial packets may need to contain a token. If used, token is contained in
+  all further Initial packets sent by the client, not just the first Initial
+  packet.
+
+- **Anti-amplification Limit.** Sometimes a lower MDPL may be imposed due to
+  anti-amplification limits. (Only a concern for servers, so not relevant to
+  MVP.)
+
+  Note: It has been observed that a lot of implementations are not fastidious
+  about enforcing the amplification limit in terms of precise packet sizes.
+  Rather, they just use it to determine if they can send another packet, but not
+  to determine what size that packet must be. Implementations with 'precise'
+  anti-amplification implementations appear to be rare.
+
+- **MTU Probes.** These packets have a precisely crafted size for the purposes
+  of probing a path MTU. Unlike ordinary packets, they are routinely expected to
+  be lost and this loss should not be taken as a signal for congestion control
+  purposes. (Not relevant for MVP.)
+
+- **Path/Migration Probes.** These packets are sent to verify a new path
+  for the purposes of connection migration.
+
+- **ACK Manager Probes.** Packets produced because the ACK manager has
+  requested a probe be sent. These MUST be made ACK-eliciting (using a PING
+  frame if necessary). However, these packets need not be reserved exclusively
+  for ACK Manager purposes; they SHOULD contain new data if available, and MAY
+  contain old data.
+
+We handle the need for different kinds of packet via a notion of “archetypes”.
+The TX packetiser is requested to generate a datagram via the following call:
 
 ```c
-void ossl_qtx_flush_net(OSSL_QTX *qtx);
-```
-
-The write record layer is responsible for coalescing multiple QUIC packets
-into datagrams.
+/* Generate normal packets containing most frame types. */
+#define TX_PACKETISER_ARCHETYPE_NORMAL      0
+/* Generate ACKs only. */
+#define TX_PACKETISER_ARCHETYPE_ACK_ONLY    1
 
-### ACK Handling and Loss Detector
-
-1. When a packet is sent, the packetiser needs to inform the ACK Manager.
-2. When a packet is ACKed, inform packetiser so it can drop sent frames.
-3. When a packet is lost, inform packetiser to create retransmission packet(s).
-4. When a packet is discarded without ACK/loss, inform packetiser to clean up.
-
-```c
-int ossl_ackm_on_tx_packet(OSSL_ACKM *ackm, OSSL_ACKM_TX_PKT *pkt)
-int ossl_quic_packet_acked(OSSL_QUIC_TX_PACKETISER *tx,
-                           OSSL_QUIC_PACKET *packet);
-int ossl_quic_packet_lost(OSSL_QUIC_TX_PACKETISER *tx,
-                          OSSL_QUIC_PACKET *packet);
-int ossl_quic_packet_discarded(OSSL_QUIC_TX_PACKETISER *tx,
-                               OSSL_QUIC_PACKET *packet);
+int ossl_quic_tx_packetiser_generate(OSSL_QUIC_TX_PACKETISER *txp,
+                                     uint32_t archetype);
 ```
 
-#### Notes
+More archetypes can be added in the future as required. The archetype limits
+what frames can be placed into the packets of a datagram.
 
-| Name here | Name in ACK Manager |
-| --- | --- |
-| `ossl_quic_packet_sent` | `QUIC_ACKM_on_tx_ack_packet` |
-| `ossl_quic_packet_acked` | `on_acked` |
-| `ossl_quic_packet_lost` | `on_lost` |
-| `ossl_quic_packet_discarded` | `on_discarded` |
+### Encryption Levels
 
-Packets
--------
+A QUIC connection progresses through Initial, Handshake, 0-RTT and 1-RTT
+encryption levels (ELs). The TX packetiser decides what EL to use to send a
+packet; or rather, it would be more accurate to say that the TX packetiser
+decides what ELs need a packet generating. Many resources are instantiated per
+EL, and can only be managed using a packet of that EL, therefore a datagram will
+frequently need to contain multiple packets to manage the resources of different
+ELs. We can thus view datagram construction as a process of determining if an EL
+needs to produce a packet for each EL, and concatenating the resulting packets.
 
-Packets formats are defined in [RFC 9000 17.1 Packet Formats].
+The following EL-specific resources exist:
 
-### Packet types
+- The crypto stream, a bidirectional byte stream abstraction provided
+  to the handshake layer. There is one crypto stream for each of the Initial,
+  Handshake and 1-RTT ELs. (`CRYPTO` frames are prohibited in 0-RTT packets,
+  which is to say the 0-RTT EL has no crypto stream of its own.)
 
-QUIC supports a number of different packets.  The combination of packets of
-different types as per [RFC 9000 12.2 Coalescing Packets], is done by the
-record layer.
+- Packet number spaces and acknowledgements. The 0-RTT and 1-RTT ELs
+  share a PN space, but Initial and Handshake ELs both have their own
+  PN spaces. Thus, Initial packets can only be acknowledged using an `ACK`
+  frame sent in an Initial packet, etc.
 
-#### Version Negotiation Packet
+Thus, a fully generalised datagram construction methodology looks like this:
 
-Refer to [RFC 9000 17.2.1 Version Negotiation Packet].
+- Let E be the set of ELs which are not discarded and for which `pending(el)` is
+  true, where `pending()` is a predicate function determining if the EL has data
+  to send.
 
-#### Initial Packet
+- Determine if we are limited by anti-amplification restrictions.
+  (Not relevant for MVP since this is only needed on the server side.)
 
-Refer to [RFC 9000 17.2.2 Initial Packet].
+- For each EL in E, construct a packet bearing in mind the Remaining CMPPL
+  and append it to the datagram.
 
-#### Handshake Packet
+  For the Initial EL, we attach a token if we have been given one.
 
-Refer to [RFC 9000 17.2.4 Handshake Packet].
+  If Initial is in E, the total length of the resulting datagram must be at
+  least 1200, but it is up to us to which packets of which ELs in E we add
+  padding to.
 
-#### App Data 0-RTT Packet
+- Send the datagram.
 
-Refer to [RFC 9000 17.2.3 0-RTT].
+### TX Key Update
 
-#### App Data 1-RTT Packet
-
-Refer to [RFC 9000 17.3.1 1-RTT].
-
-#### Retry Packet
-
-Refer to [RFC 9000 17.2.5 Retry Packet.
-
-Packetisation and Processing
-----------------------------
-
-### Application data frames
-
-The packetiser builds application data frames after requesting a specific
-amount of application data.  If insufficient data is available, or buffer
-boundaries prevent fulfilling the entire request, the stream send buffer module
-is free to return a smaller amount of data.
-
-### Retransmission
-
-When a packet is determined to be lost by the ACK Manager, the
-`ossl_quic_packet_lost()` function will be called.  This function will
-extract the frame references from the packet and re-queue them for
-transmission as if `ossl_quic_packetiser_buffer_frame()` had been called
-for each
-frame followed by `ossl_quic_packetiser_send_packets()`.  Frames that need to be
-retransmitted will be be considered higher priority than other pending
-frames, although both types are available to construct packets from.
-Moreover, any such constructed packets will not be subject to a delay
-before transmission.
+The TX packetiser decides when to tell the QRL to initiate a TX-side key update.
+It decides this using information provided by the QRL.
 
 ### Restricting packet sizes
 
-Three factors impact the size of packets that can be sent:
+Two factors impact the size of packets that can be sent:
 
-* MTU restricting packet sizes
-* Flow control
+* The maximum datagram payload length (MDPL)
 * Congestion control
 
-The MTU limits the size of an individual packet, the other two limit the
-total amount of data that can be sent.  The packetiser needs to query the
-current limits using the `ossl_quic_stream_flow_maximum_size()`,
-`get_send_allowance()` and `get_data_mtu()` calls.
-
-The packetiser will prioritise sending [`C`](#spec) spec packets together
-in order to maximise the amount of data available for the application.
+The MDPL limits the size of an entire datagram, whereas congestion control
+limits how much data can be in flight at any given time, which may cause a lower
+limit to be imposed on a given packet.
 
 ### Stateless Reset
 
index 492b0d8ca98ed1969384a2d7284ec3df0a3833f1..6c8b0c5a466cdb68874d5c4c3c0acc21aefe4312 100644 (file)
@@ -137,7 +137,15 @@ int ossl_ackm_on_rx_packet(OSSL_ACKM *ackm, const OSSL_ACKM_RX_PKT *pkt);
 int ossl_ackm_on_rx_ack_frame(OSSL_ACKM *ackm, const OSSL_QUIC_FRAME_ACK *ack,
                               int pkt_space, OSSL_TIME rx_time);
 
+/*
+ * Discards a PN space. This must be called for a PN space before freeing the
+ * ACKM if you want in-flight packets to have their discarded callbacks called.
+ * This should never be called in ordinary QUIC usage for the Application Data
+ * PN space, but it may be called for the Application Data PN space prior to
+ * freeing the ACKM to simplify teardown implementations.
+ */
 int ossl_ackm_on_pkt_space_discarded(OSSL_ACKM *ackm, int pkt_space);
+
 int ossl_ackm_on_handshake_confirmed(OSSL_ACKM *ackm);
 int ossl_ackm_on_timeout(OSSL_ACKM *ackm);
 
index 20c18b5e1b90d4f15bd13c8f9102147d7db4cf08..50301cc61e863195b59e520b953d446d0b071528 100644 (file)
@@ -172,10 +172,8 @@ void ossl_quic_rxfc_set_max_window_size(QUIC_RXFC *rxfc,
  *
  * is_fin should be 1 if the STREAM frame had the FIN flag set and 0 otherwise.
  *
- * conn_rxfc should point to a connection-level RXFC, which will have its state
- * updated correctly by the stream-level RXFC.
- *
- * This function may be used on a stream-level RXFC only.
+ * This function may be used on a stream-level RXFC only. The connection-level
+ * RXFC will have its state updated by the stream-level RXFC.
  *
  * You should check ossl_quic_rxfc_has_error() on both connection-level and
  * stream-level RXFCs after calling this function, as an incoming STREAM frame
index 15952e43d8917422b5ea8d82c1be8d50e50c9990..f58fabf83861efdd6e2e087c127be5347ea095f0 100644 (file)
@@ -27,10 +27,12 @@ struct quic_fifd_st {
     OSSL_ACKM      *ackm;
     QUIC_TXPIM     *txpim;
     QUIC_SSTREAM *(*get_sstream_by_id)(uint64_t stream_id,
+                                       uint32_t pn_space,
                                        void *arg);
     void           *get_sstream_by_id_arg;
     void          (*regen_frame)(uint64_t frame_type,
                                  uint64_t stream_id,
+                                 QUIC_TXPIM_PKT *pkt,
                                  void *arg);
     void           *regen_frame_arg;
 };
@@ -41,11 +43,13 @@ int ossl_quic_fifd_init(QUIC_FIFD *fifd,
                         QUIC_TXPIM *txpim,
                         /* stream_id is UINT64_MAX for the crypto stream */
                         QUIC_SSTREAM *(*get_sstream_by_id)(uint64_t stream_id,
+                                                           uint32_t pn_space,
                                                            void *arg),
                         void *get_sstream_by_id_arg,
                         /* stream_id is UINT64_MAX if not applicable */
                         void (*regen_frame)(uint64_t frame_type,
                                             uint64_t stream_id,
+                                            QUIC_TXPIM_PKT *pkt,
                                             void *arg),
                         void *regen_frame_arg);
 
index 71949ae05c22761bbd13b5aacc4adc6e70caebc1..6641d83dddec7fcc40166f0bc96628889a3b99ec 100644 (file)
@@ -90,6 +90,21 @@ int ossl_qtx_provide_secret(OSSL_QTX              *qtx,
  */
 int ossl_qtx_discard_enc_level(OSSL_QTX *qtx, uint32_t enc_level);
 
+/* Returns 1 if the given encryption level is provisioned. */
+int ossl_qtx_is_enc_level_provisioned(OSSL_QTX *qtx, uint32_t enc_level);
+
+/*
+ * Given the value ciphertext_len representing an encrypted packet payload
+ * length in bytes, determines how many plaintext bytes it will decrypt to.
+ * Returns 0 if the specified EL is not provisioned or ciphertext_len is too
+ * small. The result is written to *plaintext_len.
+ */
+int ossl_qtx_calculate_plaintext_payload_len(OSSL_QTX *qtx, uint32_t enc_level,
+                                             size_t ciphertext_len,
+                                             size_t *plaintext_len);
+
+uint32_t ossl_qrl_get_suite_cipher_tag_len(uint32_t suite_id);
+
 
 /*
  * Packet Transmission
@@ -232,6 +247,9 @@ int ossl_qtx_set1_bio(OSSL_QTX *qtx, BIO *bio);
 /* Changes the MDPL. */
 int ossl_qtx_set_mdpl(OSSL_QTX *qtx, size_t mdpl);
 
+/* Retrieves the current MDPL. */
+size_t ossl_qtx_get_mdpl(OSSL_QTX *qtx);
+
 
 /*
  * Key Update
index cd3b810ae1ac1de0a237a50ecd775e6796eb78ec..cef869b19d2f3fa741eb47cd927f5be9578ed17b 100644 (file)
@@ -130,6 +130,12 @@ int ossl_quic_sstream_get_stream_frame(QUIC_SSTREAM *qss,
                                        OSSL_QTX_IOVEC *iov,
                                        size_t *num_iov);
 
+/*
+ * Returns the current size of the stream; i.e., the number of bytes which have
+ * been appended to the stream so far.
+ */
+uint64_t ossl_quic_sstream_get_cur_size(QUIC_SSTREAM *qss);
+
 /*
  * (For TX packetizer use.) Marks a logical range of the send stream as having
  * been transmitted.
diff --git a/include/internal/quic_stream_map.h b/include/internal/quic_stream_map.h
new file mode 100644 (file)
index 0000000..7158334
--- /dev/null
@@ -0,0 +1,232 @@
+/*
+* Copyright 2022 The OpenSSL Project Authors. All Rights Reserved.
+*
+* Licensed under the Apache License 2.0 (the "License").  You may not use
+* this file except in compliance with the License.  You can obtain a copy
+* in the file LICENSE in the source distribution or at
+* https://www.openssl.org/source/license.html
+*/
+
+#ifndef OSSL_INTERNAL_QUIC_STREAM_MAP_H
+# define OSSL_INTERNAL_QUIC_STREAM_MAP_H
+# pragma once
+
+#include "internal/e_os.h"
+#include "internal/time.h"
+#include "internal/quic_types.h"
+#include "internal/quic_stream.h"
+#include "internal/quic_fc.h"
+#include <openssl/lhash.h>
+
+/*
+ * QUIC Stream
+ * ===========
+ *
+ * Logical QUIC stream composing all relevant send and receive components.
+ */
+typedef struct quic_stream_st QUIC_STREAM;
+
+typedef struct quic_stream_list_node_st QUIC_STREAM_LIST_NODE;
+
+struct quic_stream_list_node_st {
+    QUIC_STREAM_LIST_NODE *prev, *next;
+};
+
+struct quic_stream_st {
+    QUIC_STREAM_LIST_NODE active_node; /* for use by QUIC_STREAM_MAP */
+
+    /* Temporary link used by TXP. */
+    QUIC_STREAM    *txp_next;
+
+    /*
+     * QUIC Stream ID. Do not assume that this encodes a type as this is a
+     * version-specific property and may change between QUIC versions; instead,
+     * use the type field.
+     */
+    uint64_t        id;
+
+    /*
+     * Application Error Code (AEC) used for STOP_SENDING frame.
+     * This is only valid if stop_sending is 1.
+     */
+    uint64_t        stop_sending_aec;
+
+    /*
+     * Application Error Code (AEC) used for RESET_STREAM frame.
+     * This is only valid if reset_stream is 1.
+     */
+    uint64_t        reset_stream_aec;
+
+    /* Temporary value used by TXP. */
+    uint64_t        txp_txfc_new_credit_consumed;
+
+    QUIC_SSTREAM    *sstream;   /* NULL if RX-only */
+    void            *rstream;   /* NULL if TX only (placeholder) */
+    QUIC_TXFC       txfc;       /* NULL if RX-only */
+    QUIC_RXFC       rxfc;       /* NULL if TX-only */
+    unsigned int    type   : 8; /* QUIC_STREAM_INITIATOR_*, QUIC_STREAM_DIR_* */
+    unsigned int    active : 1;
+
+    /*
+     * Has STOP_SENDING been requested? Note that this is not the same as
+     * want_stop_sending below, as a STOP_SENDING frame may already have been
+     * sent and fully acknowledged.
+     */
+    unsigned int    stop_sending            : 1;
+
+    /*
+     * Has RESET_STREAM been requested? Works identically to STOP_SENDING for
+     * transmission purposes.
+     */
+    unsigned int    reset_stream            : 1;
+
+    /* Temporary flags used by TXP. */
+    unsigned int    txp_sent_fc             : 1;
+    unsigned int    txp_sent_stop_sending   : 1;
+    unsigned int    txp_sent_reset_stream   : 1;
+    unsigned int    txp_drained             : 1;
+    unsigned int    txp_blocked             : 1;
+
+    /* Frame regeneration flags. */
+    unsigned int    want_max_stream_data    : 1; /* used for regen only */
+    unsigned int    want_stop_sending       : 1; /* used for gen or regen */
+    unsigned int    want_reset_stream       : 1; /* used for gen or regen */
+};
+
+/*
+ * Marks a stream for STOP_SENDING. aec is the application error code (AEC).
+ * This can only fail if it has already been called.
+ */
+int ossl_quic_stream_stop_sending(QUIC_STREAM *s, uint64_t aec);
+
+/*
+ * Marks a stream for reset. aec is the application error code (AEC).
+ * This can only fail if it has already been called.
+ */
+int ossl_quic_stream_reset(QUIC_STREAM *s, uint64_t aec);
+
+/* 
+ * QUIC Stream Map
+ * ===============
+ *
+ * The QUIC stream map:
+ *
+ *   - maps stream IDs to QUIC_STREAM objects;
+ *   - tracks which streams are 'active' (currently have data for transmission);
+ *   - allows iteration over the active streams only.
+ *
+ */
+typedef struct quic_stream_map_st {
+    LHASH_OF(QUIC_STREAM)   *map;
+    QUIC_STREAM_LIST_NODE   active_list;
+    size_t                  rr_stepping, rr_counter;
+    QUIC_STREAM             *rr_cur;
+} QUIC_STREAM_MAP;
+
+int ossl_quic_stream_map_init(QUIC_STREAM_MAP *qsm);
+
+/*
+ * Any streams still in the map will be released as though
+ * ossl_quic_stream_map_release was called on them.
+ */
+void ossl_quic_stream_map_cleanup(QUIC_STREAM_MAP *qsm);
+
+#define QUIC_STREAM_INITIATOR_CLIENT        0
+#define QUIC_STREAM_INITIATOR_SERVER        1
+#define QUIC_STREAM_INITIATOR_MASK          1
+
+#define QUIC_STREAM_DIR_BIDI                0
+#define QUIC_STREAM_DIR_UNI                 2
+#define QUIC_STREAM_DIR_MASK                2
+
+/*
+ * Allocate a new stream. type is a combination of one QUIC_STREAM_INITIATOR_*
+ * value and one QUIC_STREAM_DIR_* value. Note that clients can e.g. allocate
+ * server-initiated streams as they will need to allocate a QUIC_STREAM
+ * structure to track any stream created by the server, etc.
+ *
+ * stream_id must be a valid value. Returns NULL if a stream already exists
+ * with the given ID.
+ */
+QUIC_STREAM *ossl_quic_stream_map_alloc(QUIC_STREAM_MAP *qsm,
+                                        uint64_t stream_id,
+                                        int type);
+
+/*
+ * Releases a stream object. Note that this must only be done once the teardown
+ * process is entirely complete and the object will never be referenced again.
+ */
+void ossl_quic_stream_map_release(QUIC_STREAM_MAP *qsm, QUIC_STREAM *stream);
+
+/*
+ * Calls visit_cb() for each stream in the map. visit_cb_arg is an opaque
+ * argument which is passed through.
+ */
+void ossl_quic_stream_map_visit(QUIC_STREAM_MAP *qsm,
+                                void (*visit_cb)(QUIC_STREAM *stream, void *arg),
+                                void *visit_cb_arg);
+
+/*
+ * Retrieves a stream by stream ID. Returns NULL if it does not exist.
+ */
+QUIC_STREAM *ossl_quic_stream_map_get_by_id(QUIC_STREAM_MAP *qsm,
+                                            uint64_t stream_id);
+
+/*
+ * Marks the given stream as active or inactive based on its state. Idempotent.
+ *
+ * When a stream is marked active, it becomes available in the iteration list,
+ * and when a stream is marked inactive, it no longer appears in the iteration
+ * list.
+ *
+ * Calling this function invalidates any iterator currently pointing at the
+ * given stream object, but iterators not currently pointing at the given stream
+ * object are not invalidated.
+ */
+void ossl_quic_stream_map_update_state(QUIC_STREAM_MAP *qsm, QUIC_STREAM *s);
+
+/*
+ * Sets the RR stepping value, n. The RR rotation will be advanced every n
+ * packets. The default value is 1.
+ */
+void ossl_quic_stream_map_set_rr_stepping(QUIC_STREAM_MAP *qsm, size_t stepping);
+
+/*
+ * QUIC Stream Iterator
+ * ====================
+ *
+ * Allows the current set of active streams to be walked using a RR-based
+ * algorithm. Each time ossl_quic_stream_iter_init is called, the RR algorithm
+ * is stepped. The RR algorithm rotates the iteration order such that the next
+ * active stream is returned first after n calls to ossl_quic_stream_iter_init,
+ * where n is the stepping value configured via
+ * ossl_quic_stream_map_set_rr_stepping.
+ *
+ * Suppose there are three active streams and the configured stepping is n:
+ *
+ *   Iteration 0n:  [Stream 1] [Stream 2] [Stream 3]
+ *   Iteration 1n:  [Stream 2] [Stream 3] [Stream 1]
+ *   Iteration 2n:  [Stream 3] [Stream 1] [Stream 2]
+ *
+ */
+typedef struct quic_stream_iter_st {
+    QUIC_STREAM_MAP     *qsm;
+    QUIC_STREAM         *first_stream, *stream;
+} QUIC_STREAM_ITER;
+
+/*
+ * Initialise an iterator, advancing the RR algorithm as necessary (if
+ * advance_rr is 1). After calling this, it->stream will be the first stream in
+ * the iteration sequence, or NULL if there are no active streams.
+ */
+void ossl_quic_stream_iter_init(QUIC_STREAM_ITER *it, QUIC_STREAM_MAP *qsm,
+                                int advance_rr);
+
+/*
+ * Advances to next stream in iteration sequence. You do not need to call this
+ * immediately after calling ossl_quic_stream_iter_init(). If the end of the
+ * list is reached, it->stream will be NULL after calling this.
+ */
+void ossl_quic_stream_iter_next(QUIC_STREAM_ITER *it);
+
+#endif
diff --git a/include/internal/quic_txp.h b/include/internal/quic_txp.h
new file mode 100644 (file)
index 0000000..e1983a5
--- /dev/null
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2022 The OpenSSL Project Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License 2.0 (the "License").  You may not use
+ * this file except in compliance with the License.  You can obtain a copy
+ * in the file LICENSE in the source distribution or at
+ * https://www.openssl.org/source/license.html
+ */
+
+#ifndef OSSL_QUIC_TXP_H
+# define OSSL_QUIC_TXP_H
+
+# include <openssl/ssl.h>
+# include "internal/quic_types.h"
+# include "internal/quic_record_tx.h"
+# include "internal/quic_cfq.h"
+# include "internal/quic_txpim.h"
+# include "internal/quic_stream.h"
+# include "internal/quic_stream_map.h"
+# include "internal/quic_fc.h"
+# include "internal/bio_addr.h"
+# include "internal/time.h"
+
+/*
+ * QUIC TX Packetiser
+ * ==================
+ */
+typedef struct ossl_quic_tx_packetiser_args_st {
+    /* Configuration Settings */
+    QUIC_CONN_ID    cur_scid;   /* Current Source Connection ID we use. */
+    QUIC_CONN_ID    cur_dcid;   /* Current Destination Connection ID we use. */
+    BIO_ADDR        peer;       /* Current destination L4 address we use. */
+    uint32_t        ack_delay_exponent; /* ACK delay exponent used when encoding. */
+
+    /* Injected Dependencies */
+    OSSL_QTX        *qtx;       /* QUIC Record Layer TX we are using */
+    QUIC_TXPIM      *txpim;     /* QUIC TX'd Packet Information Manager */
+    QUIC_CFQ        *cfq;       /* QUIC Control Frame Queue */
+    OSSL_ACKM       *ackm;      /* QUIC Acknowledgement Manager */
+    QUIC_STREAM_MAP *qsm;       /* QUIC Streams Map */
+    QUIC_TXFC       *conn_txfc; /* QUIC Connection-Level TX Flow Controller */
+    QUIC_RXFC       *conn_rxfc; /* QUIC Connection-Level RX Flow Controller */
+    const OSSL_CC_METHOD *cc_method; /* QUIC Congestion Controller */
+    OSSL_CC_DATA    *cc_data;   /* QUIC Congestion Controller Instance */
+    OSSL_TIME       (*now)(void *arg);  /* Callback to get current time. */
+    void            *now_arg;
+
+    /*
+     * Injected dependencies - crypto streams.
+     *
+     * Note: There is no crypto stream for the 0-RTT EL.
+     *       crypto[QUIC_PN_SPACE_APP] is the 1-RTT crypto stream.
+     */
+    QUIC_SSTREAM    *crypto[QUIC_PN_SPACE_NUM];
+} OSSL_QUIC_TX_PACKETISER_ARGS;
+
+typedef struct ossl_quic_tx_packetiser_st OSSL_QUIC_TX_PACKETISER;
+
+OSSL_QUIC_TX_PACKETISER *ossl_quic_tx_packetiser_new(const OSSL_QUIC_TX_PACKETISER_ARGS *args);
+
+typedef void (ossl_quic_initial_token_free_fn)(const unsigned char *buf,
+                                               size_t buf_len, void *arg);
+
+void ossl_quic_tx_packetiser_free(OSSL_QUIC_TX_PACKETISER *txp);
+
+/* Generate normal packets containing most frame types. */
+#define TX_PACKETISER_ARCHETYPE_NORMAL      0
+/* Generate ACKs only. */
+#define TX_PACKETISER_ARCHETYPE_ACK_ONLY    1
+#define TX_PACKETISER_ARCHETYPE_NUM         2
+
+/*
+ * Generates a datagram by polling the various ELs to determine if they want to
+ * generate any frames, and generating a datagram which coalesces packets for
+ * any ELs which do.
+ *
+ * archetype is a TX_PACKETISER_ARCHETYPE_* value.
+ *
+ * Returns TX_PACKETISER_RES_FAILURE on failure (e.g. allocation error),
+ * TX_PACKETISER_RES_NO_PKT if no packets were sent (e.g. because nothing wants
+ * to send anything), and TX_PACKETISER_RES_SENT_PKT if packets were sent.
+ */
+#define TX_PACKETISER_RES_FAILURE   0
+#define TX_PACKETISER_RES_NO_PKT    1
+#define TX_PACKETISER_RES_SENT_PKT  2
+int ossl_quic_tx_packetiser_generate(OSSL_QUIC_TX_PACKETISER *txp,
+                                     uint32_t archetype);
+
+/*
+ * Set the token used in Initial packets. The callback is called when the buffer
+ * is no longer needed; for example, when the TXP is freed or when this function
+ * is called again with a new buffer.
+ */
+void ossl_quic_tx_packetiser_set_initial_token(OSSL_QUIC_TX_PACKETISER *txp,
+                                               const unsigned char *token,
+                                               size_t token_len,
+                                               ossl_quic_initial_token_free_fn *free_cb,
+                                               void *free_cb_arg);
+
+/* Change the DCID the TXP uses to send outgoing packets. */
+int ossl_quic_tx_packetiser_set_cur_dcid(OSSL_QUIC_TX_PACKETISER *txp,
+                                         const QUIC_CONN_ID *dcid);
+
+/* Change the SCID the TXP uses to send outgoing (long) packets. */
+int ossl_quic_tx_packetiser_set_cur_scid(OSSL_QUIC_TX_PACKETISER *txp,
+                                         const QUIC_CONN_ID *scid);
+
+/* Change the destination L4 address the TXP uses to send datagrams. */
+int ossl_quic_tx_packetiser_set_peer(OSSL_QUIC_TX_PACKETISER *txp,
+                                     const BIO_ADDR *peer);
+
+/*
+ * Inform the TX packetiser that an EL has been discarded. Idempotent.
+ *
+ * This does not inform the QTX as well; the caller must also inform the QTX.
+ *
+ * The TXP will no longer reference the crypto[enc_level] QUIC_SSTREAM which was
+ * provided in the TXP arguments. However, it is the callers responsibility to
+ * free that QUIC_SSTREAM if desired.
+ */
+int ossl_quic_tx_packetiser_discard_enc_level(OSSL_QUIC_TX_PACKETISER *txp,
+                                              uint32_t enc_level);
+
+/* Asks the TXP to generate a HANDSHAKE_DONE frame in the next 1-RTT packet. */
+void ossl_quic_tx_packetiser_schedule_handshake_done(OSSL_QUIC_TX_PACKETISER *txp);
+
+/* Asks the TXP to ensure the next packet in the given PN space is ACK-eliciting. */
+void ossl_quic_tx_packetiser_schedule_ack_eliciting(OSSL_QUIC_TX_PACKETISER *txp,
+                                                    uint32_t pn_space);
+
+/*
+ * Schedules a connection close. *f and f->reason are copied. This operation is
+ * irreversible and causes all further packets generated by the TXP to contain a
+ * CONNECTION_CLOSE frame. This function fails if it has already been called
+ * successfully; the information in *f cannot be changed after the first
+ * successful call to this function.
+ */
+int ossl_quic_tx_packetiser_schedule_conn_close(OSSL_QUIC_TX_PACKETISER *txp,
+                                                const OSSL_QUIC_FRAME_CONN_CLOSE *f);
+
+#endif
index bcc64943944e88bd227b9faa502be97fb70ee9f8..eb24ea2bf1b6932800595cc5eae508c8890520f2 100644 (file)
@@ -48,7 +48,9 @@ typedef struct quic_txpim_chunk_st {
     uint64_t        stream_id;
     /*
      * The inclusive range of bytes in the stream. Exceptionally, if end <
-     * start, designates a frame of zero length (used for FIN-only frames).
+     * start, designates a frame of zero length (used for FIN-only frames). In
+     * this case end is the number of the final byte (i.e., one less than the
+     * final size of the stream).
      */
     uint64_t        start, end;
     /*
@@ -56,6 +58,16 @@ typedef struct quic_txpim_chunk_st {
      * CRYPTO stream.
      */
     unsigned int    has_fin : 1;
+    /*
+     * If set, a STOP_SENDING frame was sent for this stream ID. (If no data was
+     * sent for the stream, set end < start.)
+     */
+    unsigned int    has_stop_sending : 1;
+    /*
+     * If set, a RESET_STREAM frame was sent for this stream ID. (If no data was
+     * sent for the stream, set end < start.)
+     */
+    unsigned int    has_reset_stream : 1;
 } QUIC_TXPIM_CHUNK;
 
 QUIC_TXPIM *ossl_quic_txpim_new(void);
index 22de5f2d42de07ebe0d785ba17c6ca5d35d954eb..f288853a944c3a6d288c877e61318acb3b4e964d 100644 (file)
@@ -79,4 +79,6 @@ static ossl_unused ossl_inline int ossl_quic_conn_id_eq(const QUIC_CONN_ID *a,
     return memcmp(a->id, b->id, a->id_len) == 0;
 }
 
+#define QUIC_MIN_INITIAL_DGRAM_LEN  1200
+
 #endif
index 704684b0b4c36a65abf3963687cc30100d08e379..dec7aeddc1e0235781e70b14a1d7e834482bdef9 100644 (file)
 #define OSSL_QUIC_FRAME_TYPE_IS_CONN_CLOSE(x) \
     (((x) & ~(uint64_t)1) == OSSL_QUIC_FRAME_TYPE_CONN_CLOSE_TRANSPORT)
 
+static ossl_unused ossl_inline int
+ossl_quic_frame_type_is_ack_eliciting(uint64_t frame_type)
+{
+    switch (frame_type) {
+    case OSSL_QUIC_FRAME_TYPE_PADDING:
+    case OSSL_QUIC_FRAME_TYPE_ACK_WITHOUT_ECN:
+    case OSSL_QUIC_FRAME_TYPE_ACK_WITH_ECN:
+    case OSSL_QUIC_FRAME_TYPE_CONN_CLOSE_TRANSPORT:
+    case OSSL_QUIC_FRAME_TYPE_CONN_CLOSE_APP:
+        return 0;
+    default:
+        return 1;
+    }
+}
+
 /*
  * QUIC Frame Logical Representations
  * ==================================
@@ -178,7 +193,7 @@ typedef struct ossl_quic_frame_conn_close_st {
     unsigned int    is_app : 1; /* 0: transport error, 1: app error */
     uint64_t        error_code; /* 62-bit transport or app error code */
     uint64_t        frame_type; /* transport errors only */
-    const char     *reason;     /* UTF-8 string, not necessarily zero-terminated */
+    char            *reason;    /* UTF-8 string, not necessarily zero-terminated */
     size_t          reason_len; /* Length of reason in bytes */
 } OSSL_QUIC_FRAME_CONN_CLOSE;
 
@@ -243,6 +258,13 @@ int ossl_quic_wire_encode_frame_stop_sending(WPACKET *pkt,
 int ossl_quic_wire_encode_frame_crypto_hdr(WPACKET *hdr,
                                            const OSSL_QUIC_FRAME_CRYPTO *f);
 
+/*
+ * Returns the number of bytes which will be required to encode the given
+ * CRYPTO frame header. Does not include the payload bytes in the count.
+ * Returns 0 if input is invalid.
+ */
+size_t ossl_quic_wire_get_encoded_frame_len_crypto_hdr(const OSSL_QUIC_FRAME_CRYPTO *f);
+
 /*
  * Encodes a QUIC CRYPTO frame to the packet writer.
  *
@@ -279,6 +301,13 @@ int ossl_quic_wire_encode_frame_new_token(WPACKET *pkt,
 int ossl_quic_wire_encode_frame_stream_hdr(WPACKET *pkt,
                                            const OSSL_QUIC_FRAME_STREAM *f);
 
+/*
+ * Returns the number of bytes which will be required to encode the given
+ * STREAM frame header. Does not include the payload bytes in the count.
+ * Returns 0 if input is invalid.
+ */
+size_t ossl_quic_wire_get_encoded_frame_len_stream_hdr(const OSSL_QUIC_FRAME_STREAM *f);
+
 /*
  * Functions similarly to ossl_quic_wire_encode_frame_stream_hdr, but it also
  * allocates space for f->len bytes of data after the header, creating a
index 60528811ad0aaa4b6554b5e2fb04b87914774566..34e95ba7b6869f065533d8d580b7d49181b66d79 100644 (file)
@@ -46,6 +46,23 @@ ossl_quic_pkt_type_to_enc_level(uint32_t pkt_type)
     }
 }
 
+static ossl_inline ossl_unused uint32_t
+ossl_quic_enc_level_to_pkt_type(uint32_t enc_level)
+{
+    switch (enc_level) {
+        case QUIC_ENC_LEVEL_INITIAL:
+            return QUIC_PKT_TYPE_INITIAL;
+        case QUIC_ENC_LEVEL_HANDSHAKE:
+            return QUIC_PKT_TYPE_HANDSHAKE;
+        case QUIC_ENC_LEVEL_0RTT:
+            return QUIC_PKT_TYPE_0RTT;
+        case QUIC_ENC_LEVEL_1RTT:
+            return QUIC_PKT_TYPE_1RTT;
+        default:
+            return UINT32_MAX;
+    }
+}
+
 /* Determine if a packet type contains an encrypted payload. */
 static ossl_inline ossl_unused int
 ossl_quic_pkt_type_is_encrypted(uint32_t pkt_type)
index cc8c1fb5f5a1d767cd0132eb6d3200772978cf9a..97654b1021012ddacaf89368aa2abd35ef72a55e 100644 (file)
@@ -5,4 +5,5 @@ SOURCE[$LIBSSL]=cc_dummy.c quic_demux.c quic_record_rx.c
 SOURCE[$LIBSSL]=quic_record_tx.c quic_record_util.c quic_record_shared.c quic_wire_pkt.c
 SOURCE[$LIBSSL]=quic_record_rx_wrap.c quic_rx_depack.c
 SOURCE[$LIBSSL]=quic_fc.c uint_set.c quic_sf_list.c quic_rstream.c quic_sstream.c
-SOURCE[$LIBSSL]=quic_cfq.c quic_txpim.c quic_fifd.c
+SOURCE[$LIBSSL]=quic_cfq.c quic_txpim.c quic_fifd.c quic_txp.c
+SOURCE[$LIBSSL]=quic_stream_map.c
index 4378175f002216fbac523ee2987d7f597e96fcfc..0a9e55881a356c3c8cb8bc9296a0637187442abe 100644 (file)
@@ -1201,8 +1201,6 @@ int ossl_ackm_on_pkt_space_discarded(OSSL_ACKM *ackm, int pkt_space)
     OSSL_ACKM_TX_PKT *pkt, *pnext;
     uint64_t num_bytes_invalidated = 0;
 
-    assert(pkt_space < QUIC_PN_SPACE_APP);
-
     if (ackm->discarded[pkt_space])
         return 0;
 
index f30e9d65531ea6c368d67b8d9d92c14a7c977642..e7241f60a82746799b722422451d0240d1ac5ddd 100644 (file)
@@ -18,11 +18,13 @@ int ossl_quic_fifd_init(QUIC_FIFD *fifd,
                         QUIC_TXPIM *txpim,
                         /* stream_id is UINT64_MAX for the crypto stream */
                         QUIC_SSTREAM *(*get_sstream_by_id)(uint64_t stream_id,
+                                                           uint32_t pn_space,
                                                            void *arg),
                         void *get_sstream_by_id_arg,
                         /* stream_id is UINT64_MAX if not applicable */
                         void (*regen_frame)(uint64_t frame_type,
                                             uint64_t stream_id,
+                                            QUIC_TXPIM_PKT *pkt,
                                             void *arg),
                         void *regen_frame_arg)
 {
@@ -57,6 +59,7 @@ static void on_acked(void *arg)
     /* STREAM and CRYPTO stream chunks, FINs and stream FC frames */
     for (i = 0; i < num_chunks; ++i) {
         sstream = fifd->get_sstream_by_id(chunks[i].stream_id,
+                                          pkt->ackm_pkt.pkt_space,
                                           fifd->get_sstream_by_id_arg);
         if (sstream == NULL)
             continue;
@@ -90,6 +93,7 @@ static void on_lost(void *arg)
     /* STREAM and CRYPTO stream chunks, FIN and stream FC frames */
     for (i = 0; i < num_chunks; ++i) {
         sstream = fifd->get_sstream_by_id(chunks[i].stream_id,
+                                          pkt->ackm_pkt.pkt_space,
                                           fifd->get_sstream_by_id_arg);
         if (sstream == NULL)
             continue;
@@ -101,6 +105,16 @@ static void on_lost(void *arg)
         if (chunks[i].has_fin && chunks[i].stream_id != UINT64_MAX)
             ossl_quic_sstream_mark_lost_fin(sstream);
 
+        if (chunks[i].has_stop_sending && chunks[i].stream_id != UINT64_MAX)
+            fifd->regen_frame(OSSL_QUIC_FRAME_TYPE_STOP_SENDING,
+                              chunks[i].stream_id, pkt,
+                              fifd->regen_frame_arg);
+
+        if (chunks[i].has_reset_stream && chunks[i].stream_id != UINT64_MAX)
+            fifd->regen_frame(OSSL_QUIC_FRAME_TYPE_RESET_STREAM,
+                              chunks[i].stream_id, pkt,
+                              fifd->regen_frame_arg);
+
         /*
          * Inform caller that stream needs an FC frame.
          *
@@ -113,6 +127,7 @@ static void on_lost(void *arg)
          */
         fifd->regen_frame(OSSL_QUIC_FRAME_TYPE_MAX_STREAM_DATA,
                           chunks[i].stream_id,
+                          pkt,
                           fifd->regen_frame_arg);
     }
 
@@ -125,22 +140,22 @@ static void on_lost(void *arg)
     /* Regenerate flag frames */
     if (pkt->had_handshake_done_frame)
         fifd->regen_frame(OSSL_QUIC_FRAME_TYPE_HANDSHAKE_DONE,
-                          UINT64_MAX,
+                          UINT64_MAX, pkt,
                           fifd->regen_frame_arg);
 
     if (pkt->had_max_data_frame)
         fifd->regen_frame(OSSL_QUIC_FRAME_TYPE_MAX_DATA,
-                          UINT64_MAX,
+                          UINT64_MAX, pkt,
                           fifd->regen_frame_arg);
 
     if (pkt->had_max_streams_bidi_frame)
         fifd->regen_frame(OSSL_QUIC_FRAME_TYPE_MAX_STREAMS_BIDI,
-                          UINT64_MAX,
+                          UINT64_MAX, pkt,
                           fifd->regen_frame_arg);
 
     if (pkt->had_max_streams_uni_frame)
         fifd->regen_frame(OSSL_QUIC_FRAME_TYPE_MAX_STREAMS_UNI,
-                          UINT64_MAX,
+                          UINT64_MAX, pkt,
                           fifd->regen_frame_arg);
 
     if (pkt->had_ack_frame)
@@ -150,7 +165,7 @@ static void on_lost(void *arg)
          * whether it wants to send ECN data or not.
          */
         fifd->regen_frame(OSSL_QUIC_FRAME_TYPE_ACK_WITH_ECN,
-                          UINT64_MAX,
+                          UINT64_MAX, pkt,
                           fifd->regen_frame_arg);
 
     ossl_quic_txpim_pkt_release(fifd->txpim, pkt);
@@ -179,6 +194,9 @@ static void on_discarded(void *arg)
 int ossl_quic_fifd_pkt_commit(QUIC_FIFD *fifd, QUIC_TXPIM_PKT *pkt)
 {
     QUIC_CFQ_ITEM *cfq_item;
+    const QUIC_TXPIM_CHUNK *chunks;
+    size_t i, num_chunks;
+    QUIC_SSTREAM *sstream;
 
     pkt->fifd                   = fifd;
 
@@ -199,6 +217,31 @@ int ossl_quic_fifd_pkt_commit(QUIC_FIFD *fifd, QUIC_TXPIM_PKT *pkt)
          cfq_item = cfq_item->pkt_next)
         ossl_quic_cfq_mark_tx(fifd->cfq, cfq_item);
 
+    /*
+     * Mark the send stream chunks which have been added to the packet as having
+     * been transmitted.
+     */
+    chunks = ossl_quic_txpim_pkt_get_chunks(pkt);
+    num_chunks = ossl_quic_txpim_pkt_get_num_chunks(pkt);
+    for (i = 0; i < num_chunks; ++i) {
+        sstream = fifd->get_sstream_by_id(chunks[i].stream_id,
+                                          pkt->ackm_pkt.pkt_space,
+                                          fifd->get_sstream_by_id_arg);
+        if (sstream == NULL)
+            continue;
+
+        if (chunks[i].end >= chunks[i].start
+            && !ossl_quic_sstream_mark_transmitted(sstream,
+                                                   chunks[i].start,
+                                                   chunks[i].end))
+            return 0;
+
+        if (chunks[i].has_fin
+            && !ossl_quic_sstream_mark_transmitted_fin(sstream,
+                                                       chunks[i].end + 1))
+                return 0;
+    }
+
     /* Inform the ACKM. */
     return ossl_ackm_on_tx_packet(fifd->ackm, &pkt->ackm_pkt);
 }
index e10dd58515bbd231869665678cfc732d94e78865..5203e818a6215ba2b0b4272d022f8b0d20687c93 100644 (file)
@@ -197,6 +197,9 @@ void ossl_qrx_free(OSSL_QRX *qrx)
 {
     uint32_t i;
 
+    if (qrx == NULL)
+        return;
+
     /* Unregister from the RX DEMUX. */
     ossl_quic_demux_unregister_by_cb(qrx->demux, qrx_on_rx, qrx);
 
@@ -1067,10 +1070,11 @@ int ossl_qrx_read_pkt(OSSL_QRX *qrx, OSSL_QRX_PKT *pkt)
     if (!ossl_assert(rxe != NULL))
         return 0;
 
-    pkt->handle     = rxe;
-    pkt->hdr        = &rxe->hdr;
-    pkt->pn         = rxe->pn;
-    pkt->time       = rxe->time;
+    pkt->handle         = rxe;
+    pkt->hdr            = &rxe->hdr;
+    pkt->pn             = rxe->pn;
+    pkt->time           = rxe->time;
+    pkt->datagram_len   = rxe->datagram_len;
     pkt->peer
         = BIO_ADDR_family(&rxe->peer) != AF_UNSPEC ? &rxe->peer : NULL;
     pkt->local
index 24cae9a44e033aa3549756a1a5ed97d36ea4507f..14c8b0bd68a6c42a983007895ccd79591c281b7e 100644 (file)
@@ -97,6 +97,9 @@ OSSL_QTX *ossl_qtx_new(const OSSL_QTX_ARGS *args)
 {
     OSSL_QTX *qtx;
 
+    if (args->mdpl < QUIC_MIN_INITIAL_DGRAM_LEN)
+        return 0;
+
     qtx = OPENSSL_zalloc(sizeof(OSSL_QTX));
     if (qtx == NULL)
         return 0;
@@ -128,6 +131,9 @@ void ossl_qtx_free(OSSL_QTX *qtx)
 {
     uint32_t i;
 
+    if (qtx == NULL)
+        return;
+
     /* Free TXE queue data. */
     qtx_cleanup_txl(&qtx->pending);
     qtx_cleanup_txl(&qtx->free);
@@ -137,6 +143,7 @@ void ossl_qtx_free(OSSL_QTX *qtx)
     for (i = 0; i < QUIC_ENC_LEVEL_NUM; ++i)
         ossl_qrl_enc_level_set_discard(&qtx->el_set, i);
 
+    BIO_free(qtx->bio);
     OPENSSL_free(qtx);
 }
 
@@ -171,6 +178,11 @@ int ossl_qtx_discard_enc_level(OSSL_QTX *qtx, uint32_t enc_level)
     return 1;
 }
 
+int ossl_qtx_is_enc_level_provisioned(OSSL_QTX *qtx, uint32_t enc_level)
+{
+    return ossl_qrl_enc_level_set_get(&qtx->el_set, enc_level, 1) != NULL;
+}
+
 /* Allocate a new TXE. */
 static TXE *qtx_alloc_txe(size_t alloc_len)
 {
@@ -374,6 +386,31 @@ static size_t qtx_inflate_payload_len(OSSL_QTX *qtx, uint32_t enc_level,
     return plaintext_len + ossl_qrl_get_suite_cipher_tag_len(el->suite_id);
 }
 
+/* Determines the size of the AEAD input given the output size. */
+int ossl_qtx_calculate_plaintext_payload_len(OSSL_QTX *qtx, uint32_t enc_level,
+                                             size_t ciphertext_len,
+                                             size_t *plaintext_len)
+{
+    OSSL_QRL_ENC_LEVEL *el
+        = ossl_qrl_enc_level_set_get(&qtx->el_set, enc_level, 1);
+    size_t tag_len;
+
+    if (el == NULL) {
+        *plaintext_len = 0;
+        return 0;
+    }
+
+    tag_len = ossl_qrl_get_suite_cipher_tag_len(el->suite_id);
+
+    if (ciphertext_len < tag_len) {
+        *plaintext_len = 0;
+        return 0;
+    }
+
+    *plaintext_len = ciphertext_len - tag_len;
+    return 1;
+}
+
 /* Any other error (including packet being too big for MDPL). */
 #define QTX_FAIL_GENERIC            (-1)
 
@@ -530,6 +567,12 @@ static int qtx_write(OSSL_QTX *qtx, const OSSL_QTX_PKT *pkt, TXE *txe,
     /* Walk the iovecs to determine actual input payload length. */
     iovec_cur_init(&cur, pkt->iovec, pkt->num_iovec);
 
+    if (cur.bytes_remaining == 0) {
+        /* No zero-length payloads allowed. */
+        ret = QTX_FAIL_GENERIC;
+        goto err;
+    }
+
     /* Determine encrypted payload length. */
     payload_len = needs_encrypt ? qtx_inflate_payload_len(qtx, enc_level,
                                                           cur.bytes_remaining)
@@ -833,10 +876,18 @@ int ossl_qtx_set1_bio(OSSL_QTX *qtx, BIO *bio)
 
 int ossl_qtx_set_mdpl(OSSL_QTX *qtx, size_t mdpl)
 {
+    if (mdpl < QUIC_MIN_INITIAL_DGRAM_LEN)
+        return 0;
+
     qtx->mdpl = mdpl;
     return 1;
 }
 
+size_t ossl_qtx_get_mdpl(OSSL_QTX *qtx)
+{
+    return qtx->mdpl;
+}
+
 size_t ossl_qtx_get_queue_len_datagrams(OSSL_QTX *qtx)
 {
     return qtx->pending_count;
index 07113884e12f9593a13779f06e91d296bf1209ac..47d7ab1d21043b20094cc5d3ea996eaeae570c5e 100644 (file)
@@ -333,6 +333,11 @@ int ossl_quic_sstream_get_stream_frame(QUIC_SSTREAM *qss,
     return 1;
 }
 
+uint64_t ossl_quic_sstream_get_cur_size(QUIC_SSTREAM *qss)
+{
+    return qss->ring_buf.head_offset;
+}
+
 int ossl_quic_sstream_mark_transmitted(QUIC_SSTREAM *qss,
                                        uint64_t start,
                                        uint64_t end)
diff --git a/ssl/quic/quic_stream_map.c b/ssl/quic/quic_stream_map.c
new file mode 100644 (file)
index 0000000..7a63855
--- /dev/null
@@ -0,0 +1,273 @@
+/*
+* Copyright 2022 The OpenSSL Project Authors. All Rights Reserved.
+*
+* Licensed under the Apache License 2.0 (the "License").  You may not use
+* this file except in compliance with the License.  You can obtain a copy
+* in the file LICENSE in the source distribution or at
+* https://www.openssl.org/source/license.html
+*/
+
+#include "internal/quic_stream_map.h"
+
+/* QUIC Stream
+ * ===========
+ */
+
+int ossl_quic_stream_stop_sending(QUIC_STREAM *s, uint64_t aec)
+{
+    if (s->stop_sending)
+        return 0;
+
+    s->stop_sending_aec     = aec;
+    s->stop_sending         = 1;
+    s->want_stop_sending    = 1;
+    return 1;
+}
+
+int ossl_quic_stream_reset(QUIC_STREAM *s, uint64_t aec)
+{
+    if (s->reset_stream)
+        return 0;
+
+    s->reset_stream_aec     = aec;
+    s->reset_stream         = 1;
+    s->want_reset_stream    = 1;
+    return 1;
+}
+
+/*
+ * QUIC Stream Map
+ * ===============
+ */
+DEFINE_LHASH_OF_EX(QUIC_STREAM);
+
+/* Circular list management. */
+static void list_insert_tail(QUIC_STREAM_LIST_NODE *l,
+                             QUIC_STREAM_LIST_NODE *n)
+{
+    /* Must not be in list. */
+    assert(n->prev == NULL && n->next == NULL);
+
+    n->prev = l->prev;
+    n->prev->next = n;
+    l->prev = n;
+    n->next = l;
+}
+
+static void list_remove(QUIC_STREAM_LIST_NODE *l,
+                        QUIC_STREAM_LIST_NODE *n)
+{
+    assert(n->prev != NULL && n->next != NULL
+           && n->prev != n && n->next != n);
+
+    n->prev->next = n->next;
+    n->next->prev = n->prev;
+    n->next = n->prev = NULL;
+}
+
+static QUIC_STREAM *active_next(QUIC_STREAM_LIST_NODE *l, QUIC_STREAM *s)
+{
+    QUIC_STREAM_LIST_NODE *n = s->active_node.next;
+
+    if (n == l)
+        n = n->next;
+    if (n == l)
+        return NULL;
+    return (QUIC_STREAM *)n;
+}
+
+static unsigned long hash_stream(const QUIC_STREAM *s)
+{
+    return (unsigned long)s->id;
+}
+
+static int cmp_stream(const QUIC_STREAM *a, const QUIC_STREAM *b)
+{
+    if (a->id < b->id)
+        return -1;
+    if (a->id > b->id)
+        return 1;
+    return 0;
+}
+
+int ossl_quic_stream_map_init(QUIC_STREAM_MAP *qsm)
+{
+    qsm->map = lh_QUIC_STREAM_new(hash_stream, cmp_stream);
+    qsm->active_list.prev = qsm->active_list.next = &qsm->active_list;
+    qsm->rr_stepping = 1;
+    qsm->rr_counter  = 0;
+    qsm->rr_cur      = NULL;
+    return 1;
+}
+
+static void release_each(QUIC_STREAM *stream, void *arg)
+{
+    QUIC_STREAM_MAP *qsm = arg;
+
+    ossl_quic_stream_map_release(qsm, stream);
+}
+
+void ossl_quic_stream_map_cleanup(QUIC_STREAM_MAP *qsm)
+{
+    ossl_quic_stream_map_visit(qsm, release_each, qsm);
+
+    lh_QUIC_STREAM_free(qsm->map);
+    qsm->map = NULL;
+}
+
+void ossl_quic_stream_map_visit(QUIC_STREAM_MAP *qsm,
+                                void (*visit_cb)(QUIC_STREAM *stream, void *arg),
+                                void *visit_cb_arg)
+{
+    lh_QUIC_STREAM_doall_arg(qsm->map, visit_cb, visit_cb_arg);
+}
+
+QUIC_STREAM *ossl_quic_stream_map_alloc(QUIC_STREAM_MAP *qsm,
+                                        uint64_t stream_id,
+                                        int type)
+{
+    QUIC_STREAM *s;
+    QUIC_STREAM key;
+
+    key.id = stream_id;
+
+    s = lh_QUIC_STREAM_retrieve(qsm->map, &key);
+    if (s != NULL)
+        return NULL;
+
+    s = OPENSSL_zalloc(sizeof(*s));
+    if (s == NULL)
+        return NULL;
+
+    s->id   = stream_id;
+    s->type = type;
+    lh_QUIC_STREAM_insert(qsm->map, s);
+    return s;
+}
+
+void ossl_quic_stream_map_release(QUIC_STREAM_MAP *qsm, QUIC_STREAM *stream)
+{
+    if (stream == NULL)
+        return;
+
+    ossl_quic_sstream_free(stream->sstream);
+    stream->sstream = NULL;
+
+    lh_QUIC_STREAM_delete(qsm->map, stream);
+    OPENSSL_free(stream);
+}
+
+QUIC_STREAM *ossl_quic_stream_map_get_by_id(QUIC_STREAM_MAP *qsm,
+                                            uint64_t stream_id)
+{
+    QUIC_STREAM key;
+
+    key.id = stream_id;
+
+    return lh_QUIC_STREAM_retrieve(qsm->map, &key);
+}
+
+static void stream_map_mark_active(QUIC_STREAM_MAP *qsm, QUIC_STREAM *s)
+{
+    if (s->active)
+        return;
+
+    list_insert_tail(&qsm->active_list, &s->active_node);
+
+    if (qsm->rr_cur == NULL)
+        qsm->rr_cur = s;
+
+    s->active = 1;
+}
+
+static void stream_map_mark_inactive(QUIC_STREAM_MAP *qsm, QUIC_STREAM *s)
+{
+    if (!s->active)
+        return;
+
+    list_remove(&qsm->active_list, &s->active_node);
+
+    if (qsm->rr_cur == s)
+        qsm->rr_cur = active_next(&qsm->active_list, s);
+    if (qsm->rr_cur == s)
+        qsm->rr_cur = NULL;
+
+    s->active = 0;
+}
+
+void ossl_quic_stream_map_set_rr_stepping(QUIC_STREAM_MAP *qsm, size_t stepping)
+{
+    qsm->rr_stepping = stepping;
+    qsm->rr_counter  = 0;
+}
+
+static int stream_has_data_to_send(QUIC_STREAM *s)
+{
+    OSSL_QUIC_FRAME_STREAM shdr;
+    OSSL_QTX_IOVEC iov[2];
+    size_t num_iov;
+    uint64_t fc_credit, fc_swm, fc_limit;
+
+    if (s->sstream == NULL)
+        return 0;
+
+    /*
+     * We cannot determine if we have data to send simply by checking if
+     * ossl_quic_txfc_get_credit() is zero, because we may also have older
+     * stream data we need to retransmit. The SSTREAM returns older data first,
+     * so we do a simple comparison of the next chunk the SSTREAM wants to send
+     * against the TXFC CWM.
+     */
+    num_iov = OSSL_NELEM(iov);
+    if (!ossl_quic_sstream_get_stream_frame(s->sstream, 0, &shdr, iov,
+                                            &num_iov))
+        return 0;
+
+    fc_credit = ossl_quic_txfc_get_credit(&s->txfc);
+    fc_swm    = ossl_quic_txfc_get_swm(&s->txfc);
+    fc_limit  = fc_swm + fc_credit;
+
+    return (shdr.is_fin && shdr.len == 0) || shdr.offset < fc_limit;
+}
+
+void ossl_quic_stream_map_update_state(QUIC_STREAM_MAP *qsm, QUIC_STREAM *s)
+{
+    int should_be_active
+        = (s->rstream != NULL
+           && (s->want_max_stream_data
+               || ossl_quic_rxfc_has_cwm_changed(&s->rxfc, 0)))
+        || s->want_stop_sending
+        || s->want_reset_stream
+        || stream_has_data_to_send(s);
+
+    if (should_be_active)
+        stream_map_mark_active(qsm, s);
+    else
+        stream_map_mark_inactive(qsm, s);
+}
+
+/*
+ * QUIC Stream Iterator
+ * ====================
+ */
+void ossl_quic_stream_iter_init(QUIC_STREAM_ITER *it, QUIC_STREAM_MAP *qsm,
+                                int advance_rr)
+{
+    it->qsm    = qsm;
+    it->stream = it->first_stream = qsm->rr_cur;
+    if (advance_rr && it->stream != NULL
+        && ++qsm->rr_counter >= qsm->rr_stepping) {
+        qsm->rr_counter = 0;
+        qsm->rr_cur     = active_next(&qsm->active_list, qsm->rr_cur);
+    }
+}
+
+void ossl_quic_stream_iter_next(QUIC_STREAM_ITER *it)
+{
+    if (it->stream == NULL)
+        return;
+
+    it->stream = active_next(&it->qsm->active_list, it->stream);
+    if (it->stream == it->first_stream)
+        it->stream = NULL;
+}
diff --git a/ssl/quic/quic_txp.c b/ssl/quic/quic_txp.c
new file mode 100644 (file)
index 0000000..8508e87
--- /dev/null
@@ -0,0 +1,2162 @@
+/*
+ * Copyright 2022 The OpenSSL Project Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License 2.0 (the "License").  You may not use
+ * this file except in compliance with the License.  You can obtain a copy
+ * in the file LICENSE in the source distribution or at
+ * https://www.openssl.org/source/license.html
+ */
+
+#include "internal/quic_txp.h"
+#include "internal/quic_fifd.h"
+#include "internal/quic_stream_map.h"
+#include "internal/common.h"
+#include <openssl/err.h>
+
+#define MIN_CRYPTO_HDR_SIZE             3
+
+#define MIN_FRAME_SIZE_HANDSHAKE_DONE   1
+#define MIN_FRAME_SIZE_MAX_DATA         2
+#define MIN_FRAME_SIZE_ACK              5
+#define MIN_FRAME_SIZE_CRYPTO           (MIN_CRYPTO_HDR_SIZE + 1)
+#define MIN_FRAME_SIZE_STREAM           3 /* minimum useful size (for non-FIN) */
+#define MIN_FRAME_SIZE_MAX_STREAMS_BIDI 2
+#define MIN_FRAME_SIZE_MAX_STREAMS_UNI  2
+
+struct ossl_quic_tx_packetiser_st {
+    OSSL_QUIC_TX_PACKETISER_ARGS args;
+
+    /*
+     * Opaque initial token blob provided by caller. TXP frees using the
+     * callback when it is no longer needed.
+     */
+    const unsigned char             *initial_token;
+    size_t                          initial_token_len;
+    ossl_quic_initial_token_free_fn *initial_token_free_cb;
+    void                            *initial_token_free_cb_arg;
+
+    /* Subcomponents of the TXP that we own. */
+    QUIC_FIFD       fifd;       /* QUIC Frame-in-Flight Dispatcher */
+
+    /* Internal state. */
+    uint64_t        next_pn[QUIC_PN_SPACE_NUM]; /* Next PN to use in given PN space. */
+    OSSL_TIME       last_tx_time;               /* Last time a packet was generated, or 0. */
+
+    /* Internal state - frame (re)generation flags. */
+    unsigned int    want_handshake_done     : 1;
+    unsigned int    want_max_data           : 1;
+    unsigned int    want_max_streams_bidi   : 1;
+    unsigned int    want_max_streams_uni    : 1;
+
+    /* Internal state - frame (re)generation flags - per PN space. */
+    unsigned int    want_ack                : QUIC_PN_SPACE_NUM;
+    unsigned int    force_ack_eliciting     : QUIC_PN_SPACE_NUM;
+
+    /*
+     * Internal state - connection close terminal state.
+     * Once this is set, it is not unset unlike other want_ flags - we keep
+     * sending it in every packet.
+     */
+    unsigned int    want_conn_close         : 1;
+
+    OSSL_QUIC_FRAME_CONN_CLOSE  conn_close_frame;
+
+    /* Internal state - packet assembly. */
+    unsigned char   *scratch;       /* scratch buffer for packet assembly */
+    size_t          scratch_len;    /* number of bytes allocated for scratch */
+    OSSL_QTX_IOVEC  *iovec;         /* scratch iovec array for use with QTX */
+    size_t          alloc_iovec;    /* size of iovec array */
+};
+
+/*
+ * The TX helper records state used while generating frames into packets. It
+ * enables serialization into the packet to be done "transactionally" where
+ * serialization of a frame can be rolled back if it fails midway (e.g. if it
+ * does not fit).
+ */
+struct tx_helper {
+    OSSL_QUIC_TX_PACKETISER *txp;
+    /*
+     * The Maximum Packet Payload Length in bytes. This is the amount of
+     * space we have to generate frames into.
+     */
+    size_t max_ppl;
+    /*
+     * Number of bytes we have generated so far.
+     */
+    size_t bytes_appended;
+    /*
+     * Number of scratch bytes in txp->scratch we have used so far. Some iovecs
+     * will reference this scratch buffer. When we need to use more of it (e.g.
+     * when we need to put frame headers somewhere), we append to the scratch
+     * buffer, resizing if necessary, and increase this accordingly.
+     */
+    size_t scratch_bytes;
+    /*
+     * Bytes reserved in the MaxPPL budget. We keep this number of bytes spare
+     * until reserve_allowed is set to 1. Currently this is always at most 1, as
+     * a PING frame takes up one byte and this mechanism is only used to ensure
+     * we can encode a PING frame if we have been asked to ensure a packet is
+     * ACK-eliciting and we are unusure if we are going to add any other
+     * ACK-eliciting frames before we reach our MaxPPL budget.
+     */
+    size_t reserve;
+    /*
+     * Number of iovecs we have currently appended. This is the number of
+     * entries valid in txp->iovec.
+     */
+    size_t num_iovec;
+    /*
+     * Whether we are allowed to make use of the reserve bytes in our MaxPPL
+     * budget. This is used to ensure we have room to append a PING frame later
+     * if we need to. Once we know we will not need to append a PING frame, this
+     * is set to 1.
+     */
+    unsigned int reserve_allowed : 1;
+    /*
+     * Set to 1 if we have appended a STREAM frame with an implicit length. If
+     * this happens we should never append another frame after that frame as it
+     * cannot be validly encoded. This is just a safety check.
+     */
+    unsigned int done_implicit : 1;
+    struct {
+        /*
+         * The fields in this structure are valid if active is set, which means
+         * that a serialization transaction is currently in progress.
+         */
+        unsigned char   *data;
+        WPACKET         wpkt;
+        unsigned int    active : 1;
+    } txn;
+};
+
+static void tx_helper_rollback(struct tx_helper *h);
+static int txp_ensure_iovec(OSSL_QUIC_TX_PACKETISER *txp, size_t num);
+
+/* Initialises the TX helper. */
+static int tx_helper_init(struct tx_helper *h, OSSL_QUIC_TX_PACKETISER *txp,
+                          size_t max_ppl, size_t reserve)
+{
+    if (reserve > max_ppl)
+        return 0;
+
+    h->txp                  = txp;
+    h->max_ppl              = max_ppl;
+    h->reserve              = reserve;
+    h->num_iovec            = 0;
+    h->bytes_appended       = 0;
+    h->scratch_bytes        = 0;
+    h->reserve_allowed      = 0;
+    h->done_implicit        = 0;
+    h->txn.data             = NULL;
+    h->txn.active           = 0;
+
+    if (max_ppl > h->txp->scratch_len) {
+        unsigned char *scratch;
+
+        scratch = OPENSSL_realloc(h->txp->scratch, max_ppl);
+        if (scratch == NULL)
+            return 0;
+
+        h->txp->scratch     = scratch;
+        h->txp->scratch_len = max_ppl;
+    }
+
+    return 1;
+}
+
+static void tx_helper_cleanup(struct tx_helper *h)
+{
+    if (h->txn.active)
+        tx_helper_rollback(h);
+
+    h->txp = NULL;
+}
+
+static void tx_helper_unrestrict(struct tx_helper *h)
+{
+    h->reserve_allowed = 1;
+}
+
+/*
+ * Append an extent of memory to the iovec list. The memory must remain
+ * allocated until we finish generating the packet and call the QTX.
+ *
+ * In general, the buffers passed to this function will be from one of two
+ * ranges:
+ *
+ *   - Application data contained in stream buffers managed elsewhere
+ *     in the QUIC stack; or
+ *
+ *   - Control frame data appended into txp->scratch using tx_helper_begin and
+ *     tx_helper_commit.
+ *
+ */
+static int tx_helper_append_iovec(struct tx_helper *h,
+                                  const unsigned char *buf,
+                                  size_t buf_len)
+{
+    if (buf_len == 0)
+        return 1;
+
+    if (!ossl_assert(!h->done_implicit))
+        return 0;
+
+    if (!txp_ensure_iovec(h->txp, h->num_iovec + 1))
+        return 0;
+
+    h->txp->iovec[h->num_iovec].buf     = buf;
+    h->txp->iovec[h->num_iovec].buf_len = buf_len;
+
+    ++h->num_iovec;
+    h->bytes_appended += buf_len;
+    return 1;
+}
+
+/*
+ * How many more bytes of space do we have left in our plaintext packet payload?
+ */
+static size_t tx_helper_get_space_left(struct tx_helper *h)
+{
+    return h->max_ppl
+        - (h->reserve_allowed ? 0 : h->reserve) - h->bytes_appended;
+}
+
+/*
+ * Begin a control frame serialization transaction. This allows the
+ * serialization of the control frame to be backed out if it turns out it won't
+ * fit. Write the control frame to the returned WPACKET. Ensure you always
+ * call tx_helper_rollback or tx_helper_commit (or tx_helper_cleanup). Returns
+ * NULL on failure.
+ */
+static WPACKET *tx_helper_begin(struct tx_helper *h)
+{
+    size_t space_left, len;
+    unsigned char *data;
+
+    if (!ossl_assert(!h->txn.active))
+        return NULL;
+
+    if (!ossl_assert(!h->done_implicit))
+        return NULL;
+
+    data = (unsigned char *)h->txp->scratch + h->scratch_bytes;
+    len  = h->txp->scratch_len - h->scratch_bytes;
+
+    space_left = tx_helper_get_space_left(h);
+    if (!ossl_assert(space_left <= len))
+        return NULL;
+
+    if (!WPACKET_init_static_len(&h->txn.wpkt, data, len, 0))
+        return NULL;
+
+    if (!WPACKET_set_max_size(&h->txn.wpkt, space_left)) {
+        WPACKET_cleanup(&h->txn.wpkt);
+        return NULL;
+    }
+
+    h->txn.data     = data;
+    h->txn.active   = 1;
+    return &h->txn.wpkt;
+}
+
+static void tx_helper_end(struct tx_helper *h, int success)
+{
+    if (success)
+        WPACKET_finish(&h->txn.wpkt);
+    else
+        WPACKET_cleanup(&h->txn.wpkt);
+
+    h->txn.active       = 0;
+    h->txn.data         = NULL;
+}
+
+/* Abort a control frame serialization transaction. */
+static void tx_helper_rollback(struct tx_helper *h)
+{
+    if (!h->txn.active)
+        return;
+
+    tx_helper_end(h, 0);
+}
+
+/* Commit a control frame. */
+static int tx_helper_commit(struct tx_helper *h)
+{
+    size_t l = 0;
+
+    if (!h->txn.active)
+        return 0;
+
+    if (!WPACKET_get_total_written(&h->txn.wpkt, &l)) {
+        tx_helper_end(h, 0);
+        return 0;
+    }
+
+    if (!tx_helper_append_iovec(h, h->txn.data, l)) {
+        tx_helper_end(h, 0);
+        return 0;
+    }
+
+    h->scratch_bytes += l;
+    tx_helper_end(h, 1);
+    return 1;
+}
+
+static QUIC_SSTREAM *get_sstream_by_id(uint64_t stream_id, uint32_t pn_space,
+                                       void *arg);
+static void on_regen_notify(uint64_t frame_type, uint64_t stream_id,
+                            QUIC_TXPIM_PKT *pkt, void *arg);
+static int sstream_is_pending(QUIC_SSTREAM *sstream);
+static int txp_el_pending(OSSL_QUIC_TX_PACKETISER *txp, uint32_t enc_level,
+                          uint32_t archetype);
+static int txp_generate_for_el(OSSL_QUIC_TX_PACKETISER *txp, uint32_t enc_level,
+                               uint32_t archetype,
+                               char is_last_in_dgram,
+                               char dgram_contains_initial);
+static size_t txp_determine_pn_len(OSSL_QUIC_TX_PACKETISER *txp);
+static int txp_determine_ppl_from_pl(OSSL_QUIC_TX_PACKETISER *txp,
+                                     size_t pl,
+                                     uint32_t enc_level,
+                                     size_t hdr_len,
+                                     size_t *r);
+static size_t txp_get_mdpl(OSSL_QUIC_TX_PACKETISER *txp);
+static int txp_generate_for_el_actual(OSSL_QUIC_TX_PACKETISER *txp,
+                                      uint32_t enc_level,
+                                      uint32_t archetype,
+                                      size_t min_ppl,
+                                      size_t max_ppl,
+                                      size_t pkt_overhead,
+                                      QUIC_PKT_HDR *phdr);
+
+OSSL_QUIC_TX_PACKETISER *ossl_quic_tx_packetiser_new(const OSSL_QUIC_TX_PACKETISER_ARGS *args)
+{
+    OSSL_QUIC_TX_PACKETISER *txp;
+
+    if (args == NULL
+        || args->qtx == NULL
+        || args->txpim == NULL
+        || args->cfq == NULL
+        || args->ackm == NULL
+        || args->qsm == NULL
+        || args->conn_txfc == NULL
+        || args->conn_rxfc == NULL) {
+        ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_NULL_PARAMETER);
+        return NULL;
+    }
+
+    txp = OPENSSL_zalloc(sizeof(*txp));
+    if (txp == NULL)
+        return NULL;
+
+    txp->args           = *args;
+    txp->last_tx_time   = ossl_time_zero();
+
+    if (!ossl_quic_fifd_init(&txp->fifd,
+                             txp->args.cfq, txp->args.ackm, txp->args.txpim,
+                             get_sstream_by_id, txp,
+                             on_regen_notify, txp)) {
+        OPENSSL_free(txp);
+        return NULL;
+    }
+
+    return txp;
+}
+
+void ossl_quic_tx_packetiser_free(OSSL_QUIC_TX_PACKETISER *txp)
+{
+    if (txp == NULL)
+        return;
+
+    ossl_quic_tx_packetiser_set_initial_token(txp, NULL, 0, NULL, NULL);
+    ossl_quic_fifd_cleanup(&txp->fifd);
+    OPENSSL_free(txp->iovec);
+    OPENSSL_free(txp->conn_close_frame.reason);
+    OPENSSL_free(txp->scratch);
+    OPENSSL_free(txp);
+}
+
+void ossl_quic_tx_packetiser_set_initial_token(OSSL_QUIC_TX_PACKETISER *txp,
+                                               const unsigned char *token,
+                                               size_t token_len,
+                                               ossl_quic_initial_token_free_fn *free_cb,
+                                               void *free_cb_arg)
+{
+    if (txp->initial_token != NULL && txp->initial_token_free_cb != NULL)
+        txp->initial_token_free_cb(txp->initial_token, txp->initial_token_len,
+                                   txp->initial_token_free_cb_arg);
+
+    txp->initial_token              = token;
+    txp->initial_token_len          = token_len;
+    txp->initial_token_free_cb      = free_cb;
+    txp->initial_token_free_cb_arg  = free_cb_arg;
+}
+
+int ossl_quic_tx_packetiser_set_cur_dcid(OSSL_QUIC_TX_PACKETISER *txp,
+                                         const QUIC_CONN_ID *dcid)
+{
+    if (dcid == NULL) {
+        ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_NULL_PARAMETER);
+        return 0;
+    }
+
+    txp->args.cur_dcid = *dcid;
+    return 1;
+}
+
+int ossl_quic_tx_packetiser_set_cur_scid(OSSL_QUIC_TX_PACKETISER *txp,
+                                         const QUIC_CONN_ID *scid)
+{
+    if (scid == NULL) {
+        ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_NULL_PARAMETER);
+        return 0;
+    }
+
+    txp->args.cur_scid = *scid;
+    return 1;
+}
+
+/* Change the destination L4 address the TXP uses to send datagrams. */
+int ossl_quic_tx_packetiser_set_peer(OSSL_QUIC_TX_PACKETISER *txp,
+                                     const BIO_ADDR *peer)
+{
+    if (peer == NULL) {
+        ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_NULL_PARAMETER);
+        return 0;
+    }
+
+    txp->args.peer = *peer;
+    return 1;
+}
+
+int ossl_quic_tx_packetiser_discard_enc_level(OSSL_QUIC_TX_PACKETISER *txp,
+                                              uint32_t enc_level)
+{
+    if (enc_level >= QUIC_ENC_LEVEL_NUM) {
+        ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_INVALID_ARGUMENT);
+        return 0;
+    }
+
+    if (enc_level != QUIC_ENC_LEVEL_0RTT)
+        txp->args.crypto[ossl_quic_enc_level_to_pn_space(enc_level)] = NULL;
+
+    ossl_qtx_discard_enc_level(txp->args.qtx, enc_level);
+    return 1;
+}
+
+void ossl_quic_tx_packetiser_schedule_handshake_done(OSSL_QUIC_TX_PACKETISER *txp)
+{
+    txp->want_handshake_done = 1;
+}
+
+void ossl_quic_tx_packetiser_schedule_ack_eliciting(OSSL_QUIC_TX_PACKETISER *txp,
+                                                    uint32_t pn_space)
+{
+    txp->force_ack_eliciting |= (1UL << pn_space);
+}
+
+#define TXP_ERR_INTERNAL     0  /* Internal (e.g. alloc) error */
+#define TXP_ERR_SUCCESS      1  /* Success */
+#define TXP_ERR_SPACE        2  /* Not enough room for another packet */
+#define TXP_ERR_INPUT        3  /* Invalid/malformed input */
+
+/*
+ * Generates a datagram by polling the various ELs to determine if they want to
+ * generate any frames, and generating a datagram which coalesces packets for
+ * any ELs which do.
+ */
+int ossl_quic_tx_packetiser_generate(OSSL_QUIC_TX_PACKETISER *txp,
+                                     uint32_t archetype)
+{
+    uint32_t enc_level;
+    char have_pkt_for_el[QUIC_ENC_LEVEL_NUM], is_last_in_dgram;
+    size_t num_el_in_dgram = 0, pkts_done = 0;
+    int rc;
+
+    if (!txp->args.cc_method->can_send(txp->args.cc_data))
+        return TX_PACKETISER_RES_NO_PKT;
+
+    for (enc_level = QUIC_ENC_LEVEL_INITIAL;
+         enc_level < QUIC_ENC_LEVEL_NUM;
+         ++enc_level) {
+        have_pkt_for_el[enc_level] = txp_el_pending(txp, enc_level, archetype);
+        if (have_pkt_for_el[enc_level])
+            ++num_el_in_dgram;
+    }
+
+    if (num_el_in_dgram == 0)
+        return TX_PACKETISER_RES_NO_PKT;
+
+    /*
+     * Should not be needed, but a sanity check in case anyone else has been
+     * using the QTX.
+     */
+    ossl_qtx_finish_dgram(txp->args.qtx);
+
+    for (enc_level = QUIC_ENC_LEVEL_INITIAL;
+         enc_level < QUIC_ENC_LEVEL_NUM;
+         ++enc_level) {
+        if (!have_pkt_for_el[enc_level])
+            continue;
+
+        is_last_in_dgram = (pkts_done + 1 == num_el_in_dgram);
+        rc = txp_generate_for_el(txp, enc_level, archetype, is_last_in_dgram,
+                                 have_pkt_for_el[QUIC_ENC_LEVEL_INITIAL]);
+
+        if (rc != TXP_ERR_SUCCESS) {
+            /*
+             * If we already successfully did at least one, make sure we report
+             * this via the return code.
+             */
+            if (pkts_done > 0)
+                break;
+            else
+                return TX_PACKETISER_RES_FAILURE;
+        }
+
+        ++pkts_done;
+    }
+
+    ossl_qtx_finish_dgram(txp->args.qtx);
+    return TX_PACKETISER_RES_SENT_PKT;
+}
+
+struct archetype_data {
+    unsigned int allow_ack                  : 1;
+    unsigned int allow_ping                 : 1;
+    unsigned int allow_crypto               : 1;
+    unsigned int allow_handshake_done       : 1;
+    unsigned int allow_path_challenge       : 1;
+    unsigned int allow_path_response        : 1;
+    unsigned int allow_new_conn_id          : 1;
+    unsigned int allow_retire_conn_id       : 1;
+    unsigned int allow_stream_rel           : 1;
+    unsigned int allow_conn_fc              : 1;
+    unsigned int allow_conn_close           : 1;
+    unsigned int allow_cfq_other            : 1;
+    unsigned int allow_new_token            : 1;
+    unsigned int allow_force_ack_eliciting  : 1;
+};
+
+static const struct archetype_data archetypes[QUIC_ENC_LEVEL_NUM][TX_PACKETISER_ARCHETYPE_NUM] = {
+    /* EL 0(INITIAL) */
+    {
+        /* EL 0(INITIAL) - Archetype 0(NORMAL) */
+        {
+            /*allow_ack                       =*/ 1,
+            /*allow_ping                      =*/ 1,
+            /*allow_crypto                    =*/ 1,
+            /*allow_handshake_done            =*/ 0,
+            /*allow_path_challenge            =*/ 0,
+            /*allow_path_response             =*/ 0,
+            /*allow_new_conn_id               =*/ 0,
+            /*allow_retire_conn_id            =*/ 0,
+            /*allow_stream_rel                =*/ 0,
+            /*allow_conn_fc                   =*/ 0,
+            /*allow_conn_close                =*/ 1,
+            /*allow_cfq_other                 =*/ 1,
+            /*allow_new_token                 =*/ 0,
+            /*allow_force_ack_eliciting       =*/ 1,
+        },
+        /* EL 0(INITIAL) - Archetype 1(ACK_ONLY) */
+        {
+            /*allow_ack                       =*/ 1,
+            /*allow_ping                      =*/ 0,
+            /*allow_crypto                    =*/ 0,
+            /*allow_handshake_done            =*/ 0,
+            /*allow_path_challenge            =*/ 0,
+            /*allow_path_response             =*/ 0,
+            /*allow_new_conn_id               =*/ 0,
+            /*allow_retire_conn_id            =*/ 0,
+            /*allow_stream_rel                =*/ 0,
+            /*allow_conn_fc                   =*/ 0,
+            /*allow_conn_close                =*/ 0,
+            /*allow_cfq_other                 =*/ 0,
+            /*allow_new_token                 =*/ 0,
+            /*allow_force_ack_eliciting       =*/ 1,
+        },
+    },
+    /* EL 1(HANDSHAKE) */
+    {
+        /* EL 1(HANDSHAKE) - Archetype 0(NORMAL) */
+        {
+            /*allow_ack                       =*/ 1,
+            /*allow_ping                      =*/ 1,
+            /*allow_crypto                    =*/ 1,
+            /*allow_handshake_done            =*/ 0,
+            /*allow_path_challenge            =*/ 0,
+            /*allow_path_response             =*/ 0,
+            /*allow_new_conn_id               =*/ 0,
+            /*allow_retire_conn_id            =*/ 0,
+            /*allow_stream_rel                =*/ 0,
+            /*allow_conn_fc                   =*/ 0,
+            /*allow_conn_close                =*/ 1,
+            /*allow_cfq_other                 =*/ 1,
+            /*allow_new_token                 =*/ 0,
+            /*allow_force_ack_eliciting       =*/ 1,
+        },
+        /* EL 1(HANDSHAKE) - Archetype 1(ACK_ONLY) */
+        {
+            /*allow_ack                       =*/ 1,
+            /*allow_ping                      =*/ 0,
+            /*allow_crypto                    =*/ 0,
+            /*allow_handshake_done            =*/ 0,
+            /*allow_path_challenge            =*/ 0,
+            /*allow_path_response             =*/ 0,
+            /*allow_new_conn_id               =*/ 0,
+            /*allow_retire_conn_id            =*/ 0,
+            /*allow_stream_rel                =*/ 0,
+            /*allow_conn_fc                   =*/ 0,
+            /*allow_conn_close                =*/ 0,
+            /*allow_cfq_other                 =*/ 0,
+            /*allow_new_token                 =*/ 0,
+            /*allow_force_ack_eliciting       =*/ 1,
+        },
+    },
+    /* EL 2(0RTT) */
+    {
+        /* EL 2(0RTT) - Archetype 0(NORMAL) */
+        {
+            /*allow_ack                       =*/ 0,
+            /*allow_ping                      =*/ 1,
+            /*allow_crypto                    =*/ 0,
+            /*allow_handshake_done            =*/ 0,
+            /*allow_path_challenge            =*/ 0,
+            /*allow_path_response             =*/ 0,
+            /*allow_new_conn_id               =*/ 1,
+            /*allow_retire_conn_id            =*/ 1,
+            /*allow_stream_rel                =*/ 1,
+            /*allow_conn_fc                   =*/ 1,
+            /*allow_conn_close                =*/ 1,
+            /*allow_cfq_other                 =*/ 0,
+            /*allow_new_token                 =*/ 0,
+            /*allow_force_ack_eliciting       =*/ 0,
+        },
+        /* EL 2(0RTT) - Archetype 1(ACK_ONLY) */
+        {
+            /*allow_ack                       =*/ 0,
+            /*allow_ping                      =*/ 0,
+            /*allow_crypto                    =*/ 0,
+            /*allow_handshake_done            =*/ 0,
+            /*allow_path_challenge            =*/ 0,
+            /*allow_path_response             =*/ 0,
+            /*allow_new_conn_id               =*/ 0,
+            /*allow_retire_conn_id            =*/ 0,
+            /*allow_stream_rel                =*/ 0,
+            /*allow_conn_fc                   =*/ 0,
+            /*allow_conn_close                =*/ 0,
+            /*allow_cfq_other                 =*/ 0,
+            /*allow_new_token                 =*/ 0,
+            /*allow_force_ack_eliciting       =*/ 0,
+        },
+    },
+    /* EL 3(1RTT) */
+    {
+        /* EL 3(1RTT) - Archetype 0(NORMAL) */
+        {
+            /*allow_ack                       =*/ 1,
+            /*allow_ping                      =*/ 1,
+            /*allow_crypto                    =*/ 1,
+            /*allow_handshake_done            =*/ 1,
+            /*allow_path_challenge            =*/ 0,
+            /*allow_path_response             =*/ 0,
+            /*allow_new_conn_id               =*/ 1,
+            /*allow_retire_conn_id            =*/ 1,
+            /*allow_stream_rel                =*/ 1,
+            /*allow_conn_fc                   =*/ 1,
+            /*allow_conn_close                =*/ 1,
+            /*allow_cfq_other                 =*/ 1,
+            /*allow_new_token                 =*/ 1,
+            /*allow_force_ack_eliciting       =*/ 1,
+        },
+        /* EL 3(1RTT) - Archetype 1(ACK_ONLY) */
+        {
+            /*allow_ack                       =*/ 1,
+            /*allow_ping                      =*/ 0,
+            /*allow_crypto                    =*/ 0,
+            /*allow_handshake_done            =*/ 0,
+            /*allow_path_challenge            =*/ 0,
+            /*allow_path_response             =*/ 0,
+            /*allow_new_conn_id               =*/ 0,
+            /*allow_retire_conn_id            =*/ 0,
+            /*allow_stream_rel                =*/ 0,
+            /*allow_conn_fc                   =*/ 0,
+            /*allow_conn_close                =*/ 0,
+            /*allow_cfq_other                 =*/ 0,
+            /*allow_new_token                 =*/ 0,
+            /*allow_force_ack_eliciting       =*/ 1,
+        }
+    }
+};
+
+static int txp_get_archetype_data(uint32_t enc_level,
+                                  uint32_t archetype,
+                                  struct archetype_data *a)
+{
+    if (enc_level >= QUIC_ENC_LEVEL_NUM
+        || archetype >= TX_PACKETISER_ARCHETYPE_NUM)
+        return 0;
+
+    /* No need to avoid copying this as it should not exceed one int in size. */
+    *a = archetypes[enc_level][archetype];
+    return 1;
+}
+
+/*
+ * Returns 1 if the given EL wants to produce one or more frames.
+ * Always returns 0 if the given EL is discarded.
+ */
+static int txp_el_pending(OSSL_QUIC_TX_PACKETISER *txp, uint32_t enc_level,
+                          uint32_t archetype)
+{
+    struct archetype_data a;
+    uint32_t pn_space = ossl_quic_enc_level_to_pn_space(enc_level);
+    QUIC_CFQ_ITEM *cfq_item;
+
+    if (!ossl_qtx_is_enc_level_provisioned(txp->args.qtx, enc_level))
+        return 0;
+
+    if (!txp_get_archetype_data(enc_level, archetype, &a))
+        return 0;
+
+    /* Does the crypto stream for this EL want to produce anything? */
+    if (a.allow_crypto && sstream_is_pending(txp->args.crypto[pn_space]))
+        return 1;
+
+    /* Does the ACKM for this PN space want to produce anything? */
+    if (a.allow_ack && (ossl_ackm_is_ack_desired(txp->args.ackm, pn_space)
+                        || (txp->want_ack & (1UL << pn_space)) != 0))
+        return 1;
+
+    /* Do we need to force emission of an ACK-eliciting packet? */
+    if (a.allow_force_ack_eliciting
+        && (txp->force_ack_eliciting & (1UL << pn_space)) != 0)
+        return 1;
+
+    /* Does the connection-level RXFC want to produce a frame? */
+    if (a.allow_conn_fc && (txp->want_max_data
+        || ossl_quic_rxfc_has_cwm_changed(txp->args.conn_rxfc, 0)))
+        return 1;
+
+    /* Do we want to produce a MAX_STREAMS frame? */
+    if (a.allow_conn_fc && (txp->want_max_streams_bidi
+                            || txp->want_max_streams_uni))
+        return 1;
+
+    /* Do we want to produce a HANDSHAKE_DONE frame? */
+    if (a.allow_handshake_done && txp->want_handshake_done)
+        return 1;
+
+    /* Do we want to produce a CONNECTION_CLOSE frame? */
+    if (a.allow_conn_close && txp->want_conn_close)
+        return 1;
+
+    /* Does the CFQ have any frames queued for this PN space? */
+    if (enc_level != QUIC_ENC_LEVEL_0RTT)
+        for (cfq_item = ossl_quic_cfq_get_priority_head(txp->args.cfq, pn_space);
+             cfq_item != NULL;
+             cfq_item = ossl_quic_cfq_item_get_priority_next(cfq_item, pn_space)) {
+            uint64_t frame_type = ossl_quic_cfq_item_get_frame_type(cfq_item);
+
+            switch (frame_type) {
+            case OSSL_QUIC_FRAME_TYPE_NEW_CONN_ID:
+                if (a.allow_new_conn_id)
+                    return 1;
+                break;
+            case OSSL_QUIC_FRAME_TYPE_RETIRE_CONN_ID:
+                if (a.allow_retire_conn_id)
+                    return 1;
+                break;
+            case OSSL_QUIC_FRAME_TYPE_NEW_TOKEN:
+                if (a.allow_new_token)
+                    return 1;
+                break;
+            default:
+                if (a.allow_cfq_other)
+                    return 1;
+                break;
+            }
+       }
+
+    if (a.allow_stream_rel) {
+        QUIC_STREAM_ITER it;
+
+        /* If there are any active streams, 0/1-RTT wants to produce a packet.
+         * Whether a stream is on the active list is required to be precise
+         * (i.e., a stream is never on the active list if we cannot produce a
+         * frame for it), and all stream-related frames are governed by
+         * a.allow_stream_rel (i.e., if we can send one type of stream-related
+         * frame, we can send any of them), so we don't need to inspect
+         * individual streams on the active list, just confirm that the active
+         * list is non-empty.
+         */
+        ossl_quic_stream_iter_init(&it, txp->args.qsm, 0);
+        if (it.stream != NULL)
+            return 1;
+    }
+
+    return 0;
+}
+
+static int sstream_is_pending(QUIC_SSTREAM *sstream)
+{
+    OSSL_QUIC_FRAME_STREAM hdr;
+    OSSL_QTX_IOVEC iov[2];
+    size_t num_iov = OSSL_NELEM(iov);
+
+    return ossl_quic_sstream_get_stream_frame(sstream, 0, &hdr, iov, &num_iov);
+}
+
+/*
+ * Generates a packet for a given EL, coalescing it into the current datagram.
+ *
+ * is_last_in_dgram and dgram_contains_initial are used to determine padding
+ * requirements.
+ *
+ * Returns TXP_ERR_* value.
+ */
+static int txp_generate_for_el(OSSL_QUIC_TX_PACKETISER *txp, uint32_t enc_level,
+                               uint32_t archetype,
+                               char is_last_in_dgram,
+                               char dgram_contains_initial)
+{
+    char must_pad = dgram_contains_initial && is_last_in_dgram;
+    size_t min_dpl, min_pl, min_ppl, cmpl, cmppl, running_total;
+    size_t mdpl, hdr_len, pkt_overhead, cc_limit;
+    uint64_t cc_limit_;
+    QUIC_PKT_HDR phdr;
+    OSSL_TIME time_since_last;
+
+    /* Determine the limit CC imposes on what we can send. */
+    if (ossl_time_is_zero(txp->last_tx_time))
+        time_since_last = ossl_time_zero();
+    else
+        time_since_last = ossl_time_subtract(txp->args.now(txp->args.now_arg),
+                                             txp->last_tx_time);
+
+    cc_limit_ = txp->args.cc_method->get_send_allowance(txp->args.cc_data,
+                                                        time_since_last,
+                                                        ossl_time_is_zero(time_since_last));
+
+    cc_limit = (cc_limit_ > SIZE_MAX ? SIZE_MAX : (size_t)cc_limit_);
+
+    /* Assemble packet header. */
+    phdr.type           = ossl_quic_enc_level_to_pkt_type(enc_level);
+    phdr.spin_bit       = 0;
+    phdr.pn_len         = txp_determine_pn_len(txp);
+    phdr.partial        = 0;
+    phdr.fixed          = 1;
+    phdr.version        = QUIC_VERSION_1;
+    phdr.dst_conn_id    = txp->args.cur_dcid;
+    phdr.src_conn_id    = txp->args.cur_scid;
+
+    /*
+     * We need to know the length of the payload to get an accurate header
+     * length for non-1RTT packets, because the Length field found in
+     * Initial/Handshake/0-RTT packets uses a variable-length encoding. However,
+     * we don't have a good idea of the length of our payload, because the
+     * length of the payload depends on the room in the datagram after fitting
+     * the header, which depends on the size of the header.
+     *
+     * In general, it does not matter if a packet is slightly shorter (because
+     * e.g. we predicted use of a 2-byte length field, but ended up only needing
+     * a 1-byte length field). However this does matter for Initial packets
+     * which must be at least 1200 bytes, which is also the assumed default MTU;
+     * therefore in many cases Initial packets will be padded to 1200 bytes,
+     * which means if we overestimated the header size, we will be short by a
+     * few bytes and the server will ignore the packet for being too short. In
+     * this case, however, such packets always *will* be padded to meet 1200
+     * bytes, which requires a 2-byte length field, so we don't actually need to
+     * worry about this. Thus we estimate the header length assuming a 2-byte
+     * length field here, which should in practice work well in all cases.
+     */
+    phdr.len            = OSSL_QUIC_VLINT_2B_MAX - phdr.pn_len;
+
+    if (enc_level == QUIC_ENC_LEVEL_INITIAL) {
+        phdr.token      = txp->initial_token;
+        phdr.token_len  = txp->initial_token_len;
+    } else {
+        phdr.token      = NULL;
+        phdr.token_len  = 0;
+    }
+
+    hdr_len = ossl_quic_wire_get_encoded_pkt_hdr_len(phdr.dst_conn_id.id_len,
+                                                     &phdr);
+    if (hdr_len == 0)
+        return TXP_ERR_INPUT;
+
+    /* MinDPL: Minimum total datagram payload length. */
+    min_dpl = must_pad ? QUIC_MIN_INITIAL_DGRAM_LEN : 0;
+
+    /* How much data is already in the current datagram? */
+    running_total = ossl_qtx_get_cur_dgram_len_bytes(txp->args.qtx);
+
+    /* MinPL: Minimum length of the fully encoded packet. */
+    min_pl = running_total < min_dpl ? min_dpl - running_total : 0;
+    if ((uint64_t)min_pl > cc_limit)
+        /*
+         * Congestion control does not allow us to send a packet of adequate
+         * size.
+         */
+        return TXP_ERR_SPACE;
+
+    /* MinPPL: Minimum plaintext payload length needed to meet MinPL. */
+    if (!txp_determine_ppl_from_pl(txp, min_pl, enc_level, hdr_len, &min_ppl))
+        /* MinPL is less than a valid packet size, so just use a MinPPL of 0. */
+        min_ppl = 0;
+
+    /* MDPL: Maximum datagram payload length. */
+    mdpl = txp_get_mdpl(txp);
+
+    /*
+     * CMPL: Maximum encoded packet size we can put into this datagram given any
+     * previous packets coalesced into it.
+     */
+    if (running_total > mdpl)
+        /* Should not be possible, but if it happens: */
+        cmpl = 0;
+    else
+        cmpl = mdpl - running_total;
+
+    /* Clamp CMPL based on congestion control limit. */
+    if (cmpl > cc_limit)
+        cmpl = cc_limit;
+
+    /* CMPPL: Maximum amount we can put into the current datagram payload. */
+    if (!txp_determine_ppl_from_pl(txp, cmpl, enc_level, hdr_len, &cmppl))
+        return TXP_ERR_SPACE;
+
+    /* Packet overhead (size of headers, AEAD tag, etc.) */
+    pkt_overhead = cmpl - cmppl;
+
+    return txp_generate_for_el_actual(txp, enc_level, archetype, min_ppl, cmppl,
+                                      pkt_overhead, &phdr);
+}
+
+/* Determine how many bytes we should use for the encoded PN. */
+static size_t txp_determine_pn_len(OSSL_QUIC_TX_PACKETISER *txp)
+{
+    return 4; /* TODO(QUIC) */
+}
+
+/* Determine plaintext packet payload length from payload length. */
+static int txp_determine_ppl_from_pl(OSSL_QUIC_TX_PACKETISER *txp,
+                                     size_t pl,
+                                     uint32_t enc_level,
+                                     size_t hdr_len,
+                                     size_t *r)
+{
+    if (pl < hdr_len)
+        return 0;
+
+    pl -= hdr_len;
+
+    if (!ossl_qtx_calculate_plaintext_payload_len(txp->args.qtx, enc_level,
+                                                  pl, &pl))
+        return 0;
+
+    *r = pl;
+    return 1;
+}
+
+static size_t txp_get_mdpl(OSSL_QUIC_TX_PACKETISER *txp)
+{
+    return ossl_qtx_get_mdpl(txp->args.qtx);
+}
+
+static QUIC_SSTREAM *get_sstream_by_id(uint64_t stream_id, uint32_t pn_space,
+                                       void *arg)
+{
+    OSSL_QUIC_TX_PACKETISER *txp = arg;
+    QUIC_STREAM *s;
+
+    if (stream_id == UINT64_MAX)
+        return txp->args.crypto[pn_space];
+
+    s = ossl_quic_stream_map_get_by_id(txp->args.qsm, stream_id);
+    if (s == NULL)
+        return NULL;
+
+    return s->sstream;
+}
+
+static void on_regen_notify(uint64_t frame_type, uint64_t stream_id,
+                            QUIC_TXPIM_PKT *pkt, void *arg)
+{
+    OSSL_QUIC_TX_PACKETISER *txp = arg;
+
+    switch (frame_type) {
+        case OSSL_QUIC_FRAME_TYPE_HANDSHAKE_DONE:
+            txp->want_handshake_done = 1;
+            break;
+        case OSSL_QUIC_FRAME_TYPE_MAX_DATA:
+            txp->want_max_data = 1;
+            break;
+        case OSSL_QUIC_FRAME_TYPE_MAX_STREAMS_BIDI:
+            txp->want_max_streams_bidi = 1;
+            break;
+        case OSSL_QUIC_FRAME_TYPE_MAX_STREAMS_UNI:
+            txp->want_max_streams_uni = 1;
+            break;
+        case OSSL_QUIC_FRAME_TYPE_ACK_WITH_ECN:
+            txp->want_ack |= (1UL << pkt->ackm_pkt.pkt_space);
+            break;
+        case OSSL_QUIC_FRAME_TYPE_MAX_STREAM_DATA:
+            {
+                QUIC_STREAM *s
+                    = ossl_quic_stream_map_get_by_id(txp->args.qsm, stream_id);
+
+                if (s == NULL)
+                    return;
+
+                s->want_max_stream_data = 1;
+                ossl_quic_stream_map_update_state(txp->args.qsm, s);
+            }
+            break;
+        case OSSL_QUIC_FRAME_TYPE_STOP_SENDING:
+            {
+                QUIC_STREAM *s
+                    = ossl_quic_stream_map_get_by_id(txp->args.qsm, stream_id);
+
+                if (s == NULL)
+                    return;
+
+                s->want_stop_sending = 1;
+                ossl_quic_stream_map_update_state(txp->args.qsm, s);
+            }
+            break;
+        case OSSL_QUIC_FRAME_TYPE_RESET_STREAM:
+            {
+                QUIC_STREAM *s
+                    = ossl_quic_stream_map_get_by_id(txp->args.qsm, stream_id);
+
+                if (s == NULL)
+                    return;
+
+                s->want_reset_stream = 1;
+                ossl_quic_stream_map_update_state(txp->args.qsm, s);
+            }
+            break;
+        default:
+            assert(0);
+            break;
+    }
+}
+
+static int txp_generate_pre_token(OSSL_QUIC_TX_PACKETISER *txp,
+                                  struct tx_helper *h,
+                                  QUIC_TXPIM_PKT *tpkt,
+                                  uint32_t pn_space,
+                                  struct archetype_data *a)
+{
+    const OSSL_QUIC_FRAME_ACK *ack;
+    OSSL_QUIC_FRAME_ACK ack2;
+
+    tpkt->ackm_pkt.largest_acked = QUIC_PN_INVALID;
+
+    /* ACK Frames (Regenerate) */
+    if (a->allow_ack
+        && tx_helper_get_space_left(h) >= MIN_FRAME_SIZE_ACK
+        && (txp->want_ack
+            || ossl_ackm_is_ack_desired(txp->args.ackm, pn_space))
+        && (ack = ossl_ackm_get_ack_frame(txp->args.ackm, pn_space)) != NULL) {
+        WPACKET *wpkt = tx_helper_begin(h);
+
+        if (wpkt == NULL)
+            return 0;
+
+        /* We do not currently support ECN */
+        ack2 = *ack;
+        ack2.ecn_present = 0;
+
+        if (ossl_quic_wire_encode_frame_ack(wpkt,
+                                            txp->args.ack_delay_exponent,
+                                            &ack2)) {
+            if (!tx_helper_commit(h))
+                return 0;
+
+            tpkt->had_ack_frame = 1;
+
+            if (ack->num_ack_ranges > 0)
+                tpkt->ackm_pkt.largest_acked = ack->ack_ranges[0].end;
+        } else {
+            tx_helper_rollback(h);
+        }
+    }
+
+    /* CONNECTION_CLOSE Frames (Regenerate) */
+    if (a->allow_conn_close && txp->want_conn_close) {
+        WPACKET *wpkt = tx_helper_begin(h);
+
+        if (wpkt == NULL)
+            return 0;
+
+        if (ossl_quic_wire_encode_frame_conn_close(wpkt,
+                                                   &txp->conn_close_frame)) {
+            if (!tx_helper_commit(h))
+                return 0;
+        } else {
+            tx_helper_rollback(h);
+        }
+    }
+
+    return 1;
+}
+
+static int try_len(size_t space_left, size_t orig_len,
+                   size_t base_hdr_len, size_t lenbytes,
+                   uint64_t maxn, size_t *hdr_len, size_t *payload_len)
+{
+    size_t n;
+    size_t maxn_ = maxn > SIZE_MAX ? SIZE_MAX : (size_t)maxn;
+
+    *hdr_len = base_hdr_len + lenbytes;
+
+    n = orig_len;
+    if (n > maxn_)
+        n = maxn_;
+    if (n + *hdr_len > space_left)
+        n = (space_left >= *hdr_len) ? space_left - *hdr_len : 0;
+
+    *payload_len = n;
+    return n > 0;
+}
+
+static void determine_len(size_t space_left, size_t orig_len,
+                          size_t base_hdr_len,
+                          uint64_t *hlen, uint64_t *len)
+{
+    size_t chosen_payload_len = 0;
+    size_t chosen_hdr_len     = 0;
+    size_t payload_len[4], hdr_len[4];
+    int i, valid[4] = {0};
+
+    valid[0] = try_len(space_left, orig_len, base_hdr_len,
+                       1, OSSL_QUIC_VLINT_1B_MAX,
+                       &hdr_len[0], &payload_len[0]);
+    valid[1] = try_len(space_left, orig_len, base_hdr_len,
+                       2, OSSL_QUIC_VLINT_2B_MAX,
+                       &hdr_len[1], &payload_len[1]);
+    valid[2] = try_len(space_left, orig_len, base_hdr_len,
+                       4, OSSL_QUIC_VLINT_4B_MAX,
+                       &hdr_len[2], &payload_len[2]);
+    valid[3] = try_len(space_left, orig_len, base_hdr_len,
+                       8, OSSL_QUIC_VLINT_8B_MAX,
+                       &hdr_len[3], &payload_len[3]);
+
+   for (i = OSSL_NELEM(valid) - 1; i >= 0; --i)
+        if (valid[i] && payload_len[i] >= chosen_payload_len) {
+            chosen_payload_len = payload_len[i];
+            chosen_hdr_len     = hdr_len[i];
+        }
+
+    *hlen = chosen_hdr_len;
+    *len  = chosen_payload_len;
+}
+
+/*
+ * Given a CRYPTO frame header with accurate chdr->len and a budget
+ * (space_left), try to find the optimal value of chdr->len to fill as much of
+ * the budget as possible. This is slightly hairy because larger values of
+ * chdr->len cause larger encoded sizes of the length field of the frame, which
+ * in turn mean less space available for payload data. We check all possible
+ * encodings and choose the optimal encoding.
+ */
+static int determine_crypto_len(struct tx_helper *h,
+                                OSSL_QUIC_FRAME_CRYPTO *chdr,
+                                size_t space_left,
+                                uint64_t *hlen,
+                                uint64_t *len)
+{
+    size_t orig_len;
+    size_t base_hdr_len; /* CRYPTO header length without length field */
+
+    if (chdr->len > SIZE_MAX)
+        return 0;
+
+    orig_len = (size_t)chdr->len;
+
+    chdr->len = 0;
+    base_hdr_len = ossl_quic_wire_get_encoded_frame_len_crypto_hdr(chdr);
+    chdr->len = orig_len;
+    if (base_hdr_len == 0)
+        return 0;
+
+    --base_hdr_len;
+
+    determine_len(space_left, orig_len, base_hdr_len, hlen, len);
+    return 1;
+}
+
+static int determine_stream_len(struct tx_helper *h,
+                                OSSL_QUIC_FRAME_STREAM *shdr,
+                                size_t space_left,
+                                uint64_t *hlen,
+                                uint64_t *len)
+{
+    size_t orig_len;
+    size_t base_hdr_len; /* STREAM header length without length field */
+
+    if (shdr->len > SIZE_MAX)
+        return 0;
+
+    orig_len = (size_t)shdr->len;
+
+    shdr->len = 0;
+    base_hdr_len = ossl_quic_wire_get_encoded_frame_len_stream_hdr(shdr);
+    shdr->len = orig_len;
+    if (base_hdr_len == 0)
+        return 0;
+
+    if (shdr->has_explicit_len)
+        --base_hdr_len;
+
+    determine_len(space_left, orig_len, base_hdr_len, hlen, len);
+    return 1;
+}
+
+static int txp_generate_crypto_frames(OSSL_QUIC_TX_PACKETISER *txp,
+                                      struct tx_helper *h,
+                                      uint32_t pn_space,
+                                      QUIC_TXPIM_PKT *tpkt,
+                                      char *have_ack_eliciting)
+{
+    size_t num_stream_iovec;
+    OSSL_QUIC_FRAME_STREAM shdr = {0};
+    OSSL_QUIC_FRAME_CRYPTO chdr = {0};
+    OSSL_QTX_IOVEC iov[2];
+    uint64_t hdr_bytes;
+    WPACKET *wpkt;
+    QUIC_TXPIM_CHUNK chunk;
+    size_t i, space_left;
+
+    for (i = 0;; ++i) {
+        space_left = tx_helper_get_space_left(h);
+
+        if (space_left < MIN_FRAME_SIZE_CRYPTO)
+            return 1; /* no point trying */
+
+        /* Do we have any CRYPTO data waiting? */
+        num_stream_iovec = OSSL_NELEM(iov);
+        if (!ossl_quic_sstream_get_stream_frame(txp->args.crypto[pn_space],
+                                                i, &shdr, iov,
+                                                &num_stream_iovec))
+            return 1; /* nothing to do */
+
+        /* Convert STREAM frame header to CRYPTO frame header */
+        chdr.offset = shdr.offset;
+        chdr.len    = shdr.len;
+
+        if (chdr.len == 0)
+            return 1; /* nothing to do */
+
+        /* Find best fit (header length, payload length) combination. */
+        if (!determine_crypto_len(h, &chdr, space_left, &hdr_bytes,
+                                  &chdr.len)
+            || hdr_bytes == 0 || chdr.len == 0) {
+            return 1; /* can't fit anything */
+        }
+
+        /*
+         * Truncate IOVs to match our chosen length.
+         *
+         * The length cannot be more than SIZE_MAX because this length comes
+         * from our send stream buffer.
+         */
+        ossl_quic_sstream_adjust_iov((size_t)chdr.len, iov, num_stream_iovec);
+
+        /*
+         * Ensure we have enough iovecs allocated (1 for the header, up to 2 for
+         * the the stream data.)
+         */
+        if (!txp_ensure_iovec(txp, h->num_iovec + 3))
+            return 0; /* alloc error */
+
+        /* Encode the header. */
+        wpkt = tx_helper_begin(h);
+        if (wpkt == NULL)
+            return 0; /* alloc error */
+
+        if (!ossl_quic_wire_encode_frame_crypto_hdr(wpkt, &chdr)) {
+            tx_helper_rollback(h);
+            return 1; /* can't fit */
+        }
+
+        if (!tx_helper_commit(h))
+            return 0; /* alloc error */
+
+        /* Add payload iovecs to the helper (infallible). */
+        for (i = 0; i < num_stream_iovec; ++i)
+            tx_helper_append_iovec(h, iov[i].buf, iov[i].buf_len);
+
+        *have_ack_eliciting = 1;
+        tx_helper_unrestrict(h); /* no longer need PING */
+
+        /* Log chunk to TXPIM. */
+        chunk.stream_id = UINT64_MAX; /* crypto stream */
+        chunk.start     = chdr.offset;
+        chunk.end       = chdr.offset + chdr.len - 1;
+        chunk.has_fin   = 0; /* Crypto stream never ends */
+        if (!ossl_quic_txpim_pkt_append_chunk(tpkt, &chunk))
+            return 0; /* alloc error */
+    }
+}
+
+struct chunk_info {
+    OSSL_QUIC_FRAME_STREAM shdr;
+    OSSL_QTX_IOVEC iov[2];
+    size_t num_stream_iovec;
+    char valid;
+};
+
+static int txp_plan_stream_chunk(OSSL_QUIC_TX_PACKETISER *txp,
+                                 struct tx_helper *h,
+                                 QUIC_SSTREAM *sstream,
+                                 QUIC_TXFC *stream_txfc,
+                                 size_t skip,
+                                 struct chunk_info *chunk)
+{
+    uint64_t fc_credit, fc_swm, fc_limit;
+
+    chunk->num_stream_iovec = OSSL_NELEM(chunk->iov);
+    chunk->valid = ossl_quic_sstream_get_stream_frame(sstream, skip,
+                                                      &chunk->shdr,
+                                                      chunk->iov,
+                                                      &chunk->num_stream_iovec);
+    if (!chunk->valid)
+        return 1;
+
+    if (!ossl_assert(chunk->shdr.len > 0 || chunk->shdr.is_fin))
+        /* Should only have 0-length chunk if FIN */
+        return 0;
+
+    /* Clamp according to connection and stream-level TXFC. */
+    fc_credit   = ossl_quic_txfc_get_credit(stream_txfc);
+    fc_swm      = ossl_quic_txfc_get_swm(stream_txfc);
+    fc_limit    = fc_swm + fc_credit;
+
+    if (chunk->shdr.len > 0 && chunk->shdr.offset + chunk->shdr.len > fc_limit) {
+        chunk->shdr.len = (fc_limit <= chunk->shdr.offset)
+            ? 0 : fc_limit - chunk->shdr.offset;
+        chunk->shdr.is_fin = 0;
+    }
+
+    if (chunk->shdr.len == 0 && !chunk->shdr.is_fin) {
+        /*
+         * Nothing to do due to TXFC. Since SSTREAM returns chunks in ascending
+         * order of offset we don't need to check any later chunks, so stop
+         * iterating here.
+         */
+        chunk->valid = 0;
+        return 1;
+    }
+
+    return 1;
+}
+
+/*
+ * Returns 0 on fatal error (e.g. allocation failure), 1 on success.
+ * *packet_full is set to 1 if there is no longer enough room for another STREAM
+ * frame, and *stream_drained is set to 1 if all stream buffers have now been
+ * sent.
+ */
+static int txp_generate_stream_frames(OSSL_QUIC_TX_PACKETISER *txp,
+                                      struct tx_helper *h,
+                                      uint32_t pn_space,
+                                      QUIC_TXPIM_PKT *tpkt,
+                                      uint64_t id,
+                                      QUIC_SSTREAM *sstream,
+                                      QUIC_TXFC *stream_txfc,
+                                      QUIC_STREAM *next_stream,
+                                      size_t min_ppl,
+                                      char *have_ack_eliciting,
+                                      char *packet_full,
+                                      char *stream_drained,
+                                      uint64_t *new_credit_consumed)
+{
+    int rc = 0;
+    struct chunk_info chunks[2] = {0};
+
+    OSSL_QUIC_FRAME_STREAM *shdr;
+    WPACKET *wpkt;
+    QUIC_TXPIM_CHUNK chunk;
+    size_t i, j, space_left;
+    int needs_padding_if_implicit, can_fill_payload, use_explicit_len;
+    int could_have_following_chunk;
+    uint64_t hdr_len_implicit, payload_len_implicit;
+    uint64_t hdr_len_explicit, payload_len_explicit;
+    uint64_t fc_swm, fc_new_hwm;
+
+    fc_swm      = ossl_quic_txfc_get_swm(stream_txfc);
+    fc_new_hwm  = fc_swm;
+
+    /*
+     * Load the first two chunks if any offered by the send stream. We retrieve
+     * the next chunk in advance so we can determine if we need to send any more
+     * chunks from the same stream after this one, which is needed when
+     * determining when we can use an implicit length in a STREAM frame.
+     */
+    for (i = 0; i < 2; ++i) {
+        if (!txp_plan_stream_chunk(txp, h, sstream, stream_txfc, i, &chunks[i]))
+            goto err;
+
+        if (i == 0 && !chunks[i].valid) {
+            /* No chunks, nothing to do. */
+            *stream_drained = 1;
+            rc = 1;
+            goto err;
+        }
+    }
+
+    for (i = 0;; ++i) {
+        space_left = tx_helper_get_space_left(h);
+
+        if (space_left < MIN_FRAME_SIZE_STREAM) {
+            *packet_full = 1;
+            rc = 1;
+            goto err;
+        }
+
+        if (!chunks[i % 2].valid) {
+            /* Out of chunks; we're done. */
+            *stream_drained = 1;
+            rc = 1;
+            goto err;
+        }
+
+        if (!ossl_assert(!h->done_implicit))
+            /*
+             * Logic below should have ensured we didn't append an
+             * implicit-length unless we filled the packet or didn't have
+             * another stream to handle, so this should not be possible.
+             */
+            goto err;
+
+        shdr = &chunks[i % 2].shdr;
+        if (i > 0)
+            /* Load next chunk for lookahead. */
+            if (!txp_plan_stream_chunk(txp, h, sstream, stream_txfc, i + 1,
+                                       &chunks[(i + 1) % 2]))
+                goto err;
+
+        /*
+         * Find best fit (header length, payload length) combination for if we
+         * use an implicit length.
+         */
+        shdr->has_explicit_len = 0;
+        hdr_len_implicit = payload_len_implicit = 0;
+        if (!determine_stream_len(h, shdr, space_left,
+                                  &hdr_len_implicit, &payload_len_implicit)
+            || hdr_len_implicit == 0 || payload_len_implicit == 0) {
+            *packet_full = 1;
+            rc = 1;
+            goto err; /* can't fit anything */
+        }
+
+        /*
+         * If using the implicit-length representation would need padding, we
+         * can't use it.
+         */
+        needs_padding_if_implicit = (h->bytes_appended + hdr_len_implicit
+                                     + payload_len_implicit < min_ppl);
+
+        /*
+         * If there is a next stream, we don't use the implicit length so we can
+         * add more STREAM frames after this one, unless there is enough data
+         * for this STREAM frame to fill the packet.
+         */
+        can_fill_payload = (hdr_len_implicit + payload_len_implicit
+                            >= space_left);
+
+        /*
+         * Is there is a stream after this one, or another chunk pending
+         * transmission in this stream?
+         */
+        could_have_following_chunk
+            = (next_stream != NULL || chunks[(i + 1) % 2].valid);
+
+        /* Choose between explicit or implicit length representations. */
+        use_explicit_len = !((can_fill_payload || !could_have_following_chunk)
+                             && !needs_padding_if_implicit);
+
+        if (use_explicit_len) {
+            /*
+             * Find best fit (header length, payload length) combination for if
+             * we use an explicit length.
+             */
+            shdr->has_explicit_len = 1;
+            hdr_len_explicit = payload_len_explicit = 0;
+            if (!determine_stream_len(h, shdr, space_left,
+                                      &hdr_len_explicit, &payload_len_explicit)
+                || hdr_len_explicit == 0 || payload_len_explicit == 0) {
+                *packet_full = 1;
+                rc = 1;
+                goto err; /* can't fit anything */
+            }
+
+            shdr->len = payload_len_explicit;
+        } else {
+            shdr->has_explicit_len = 0;
+            shdr->len = payload_len_implicit;
+        }
+
+        /* Truncate IOVs to match our chosen length. */
+        ossl_quic_sstream_adjust_iov((size_t)shdr->len, chunks[i % 2].iov,
+                                     chunks[i % 2].num_stream_iovec);
+
+        /*
+         * Ensure we have enough iovecs allocated (1 for the header, up to 2 for
+         * the the stream data.)
+         */
+        if (!txp_ensure_iovec(txp, h->num_iovec + 3))
+            goto err; /* alloc error */
+
+        /* Encode the header. */
+        wpkt = tx_helper_begin(h);
+        if (wpkt == NULL)
+            goto err; /* alloc error */
+
+        shdr->stream_id = id;
+        if (!ossl_assert(ossl_quic_wire_encode_frame_stream_hdr(wpkt, shdr))) {
+            /* (Should not be possible.) */
+            tx_helper_rollback(h);
+            *packet_full = 1;
+            rc = 1;
+            goto err; /* can't fit */
+        }
+
+        if (!tx_helper_commit(h))
+            goto err; /* alloc error */
+
+        /* Add payload iovecs to the helper (infallible). */
+        for (j = 0; j < chunks[i % 2].num_stream_iovec; ++j)
+            tx_helper_append_iovec(h, chunks[i % 2].iov[j].buf,
+                                   chunks[i % 2].iov[j].buf_len);
+
+        *have_ack_eliciting = 1;
+        tx_helper_unrestrict(h); /* no longer need PING */
+        if (!shdr->has_explicit_len)
+            h->done_implicit = 1;
+
+        /* Log new TXFC credit which was consumed. */
+        if (shdr->len > 0 && shdr->offset + shdr->len > fc_new_hwm)
+            fc_new_hwm = shdr->offset + shdr->len;
+
+        /* Log chunk to TXPIM. */
+        chunk.stream_id         = shdr->stream_id;
+        chunk.start             = shdr->offset;
+        chunk.end               = shdr->offset + shdr->len - 1;
+        chunk.has_fin           = shdr->is_fin;
+        chunk.has_stop_sending  = 0;
+        chunk.has_reset_stream  = 0;
+        if (!ossl_quic_txpim_pkt_append_chunk(tpkt, &chunk))
+            goto err; /* alloc error */
+    }
+
+err:
+    *new_credit_consumed = fc_new_hwm - fc_swm;
+    return rc;
+}
+
+static void txp_enlink_tmp(QUIC_STREAM **tmp_head, QUIC_STREAM *stream)
+{
+    stream->txp_next = *tmp_head;
+    *tmp_head = stream;
+}
+
+static int txp_generate_stream_related(OSSL_QUIC_TX_PACKETISER *txp,
+                                       struct tx_helper *h,
+                                       uint32_t pn_space,
+                                       QUIC_TXPIM_PKT *tpkt,
+                                       size_t min_ppl,
+                                       char *have_ack_eliciting,
+                                       QUIC_STREAM **tmp_head)
+{
+    QUIC_STREAM_ITER it;
+    void *rstream;
+    WPACKET *wpkt;
+    uint64_t cwm;
+    QUIC_STREAM *stream, *snext;
+
+    for (ossl_quic_stream_iter_init(&it, txp->args.qsm, 1);
+         it.stream != NULL;) {
+
+        stream = it.stream;
+        ossl_quic_stream_iter_next(&it);
+        snext = it.stream;
+
+        stream->txp_sent_fc                  = 0;
+        stream->txp_sent_stop_sending        = 0;
+        stream->txp_sent_reset_stream        = 0;
+        stream->txp_drained                  = 0;
+        stream->txp_blocked                  = 0;
+        stream->txp_txfc_new_credit_consumed = 0;
+
+        rstream = stream->rstream;
+
+        /* Stream Abort Frames (STOP_SENDING, RESET_STREAM) */
+        if (stream->want_stop_sending) {
+            OSSL_QUIC_FRAME_STOP_SENDING f;
+
+            wpkt = tx_helper_begin(h);
+            if (wpkt == NULL)
+                return 0; /* alloc error */
+
+            f.stream_id         = stream->id;
+            f.app_error_code    = stream->stop_sending_aec;
+            if (!ossl_quic_wire_encode_frame_stop_sending(wpkt, &f)) {
+                tx_helper_rollback(h); /* can't fit */
+                txp_enlink_tmp(tmp_head, stream);
+                break;
+            }
+
+            if (!tx_helper_commit(h))
+                return 0; /* alloc error */
+
+            *have_ack_eliciting = 1;
+            tx_helper_unrestrict(h); /* no longer need PING */
+            stream->txp_sent_stop_sending = 1;
+        }
+
+        if (stream->want_reset_stream) {
+            OSSL_QUIC_FRAME_RESET_STREAM f;
+
+            wpkt = tx_helper_begin(h);
+            if (wpkt == NULL)
+                return 0; /* alloc error */
+
+            f.stream_id         = stream->id;
+            f.app_error_code    = stream->reset_stream_aec;
+            f.final_size        = ossl_quic_sstream_get_cur_size(stream->sstream);
+            if (!ossl_quic_wire_encode_frame_reset_stream(wpkt, &f)) {
+                tx_helper_rollback(h); /* can't fit */
+                txp_enlink_tmp(tmp_head, stream);
+                break;
+            }
+
+            if (!tx_helper_commit(h))
+                return 0; /* alloc error */
+
+            *have_ack_eliciting = 1;
+            tx_helper_unrestrict(h); /* no longer need PING */
+            stream->txp_sent_reset_stream = 1;
+        }
+
+        /* Stream Flow Control Frames (MAX_STREAM_DATA) */
+        if (rstream != NULL
+            && (stream->want_max_stream_data
+                || ossl_quic_rxfc_has_cwm_changed(&stream->rxfc, 0))) {
+
+            wpkt = tx_helper_begin(h);
+            if (wpkt == NULL)
+                return 0; /* alloc error */
+
+            cwm = ossl_quic_rxfc_get_cwm(&stream->rxfc);
+
+            if (!ossl_quic_wire_encode_frame_max_stream_data(wpkt, stream->id,
+                                                             cwm)) {
+                tx_helper_rollback(h); /* can't fit */
+                txp_enlink_tmp(tmp_head, stream);
+                break;
+            }
+
+            if (!tx_helper_commit(h))
+                return 0; /* alloc error */
+
+            *have_ack_eliciting = 1;
+            tx_helper_unrestrict(h); /* no longer need PING */
+            stream->txp_sent_fc = 1;
+        }
+
+        /* Stream Data Frames (STREAM) */
+        if (stream->sstream != NULL) {
+            char packet_full = 0, stream_drained = 0;
+
+            if (!txp_generate_stream_frames(txp, h, pn_space, tpkt,
+                                            stream->id, stream->sstream,
+                                            &stream->txfc,
+                                            snext, min_ppl,
+                                            have_ack_eliciting,
+                                            &packet_full,
+                                            &stream_drained,
+                                            &stream->txp_txfc_new_credit_consumed)) {
+                /* Fatal error (allocation, etc.) */
+                txp_enlink_tmp(tmp_head, stream);
+                return 0;
+            }
+
+            if (stream_drained)
+                stream->txp_drained = 1;
+
+            if (packet_full) {
+                txp_enlink_tmp(tmp_head, stream);
+                break;
+            }
+        }
+
+        txp_enlink_tmp(tmp_head, stream);
+    }
+
+    return 1;
+}
+
+/*
+ * Generates a packet for a given EL with the given minimum and maximum
+ * plaintext packet payload lengths. Returns TXP_ERR_* value.
+ */
+static int txp_generate_for_el_actual(OSSL_QUIC_TX_PACKETISER *txp,
+                                      uint32_t enc_level,
+                                      uint32_t archetype,
+                                      size_t min_ppl,
+                                      size_t max_ppl,
+                                      size_t pkt_overhead,
+                                      QUIC_PKT_HDR *phdr)
+{
+    int rc = TXP_ERR_SUCCESS;
+    struct archetype_data a;
+    uint32_t pn_space = ossl_quic_enc_level_to_pn_space(enc_level);
+    struct tx_helper h;
+    char have_helper = 0, have_ack_eliciting = 0, done_pre_token = 0;
+    char require_ack_eliciting;
+    QUIC_CFQ_ITEM *cfq_item;
+    QUIC_TXPIM_PKT *tpkt = NULL;
+    OSSL_QTX_PKT pkt;
+    QUIC_STREAM *tmp_head = NULL, *stream;
+
+    if (!txp_get_archetype_data(enc_level, archetype, &a))
+        goto fatal_err;
+
+    require_ack_eliciting
+        = (a.allow_force_ack_eliciting
+           && (txp->force_ack_eliciting & (1UL << pn_space)));
+
+    /* Minimum cannot be bigger than maximum. */
+    if (min_ppl > max_ppl)
+        goto fatal_err;
+
+    /* Maximum PN reached? */
+    if (txp->next_pn[pn_space] >= (((QUIC_PN)1) << 62))
+        goto fatal_err;
+
+    if ((tpkt = ossl_quic_txpim_pkt_alloc(txp->args.txpim)) == NULL)
+        goto fatal_err;
+
+    /*
+     * Initialise TX helper. If we must be ACK eliciting, reserve 1 byte for
+     * PING.
+     */
+    if (!tx_helper_init(&h, txp, max_ppl, require_ack_eliciting ? 1 : 0))
+        goto fatal_err;
+
+    have_helper = 1;
+
+    /*
+     * Frame Serialization
+     * ===================
+     *
+     * We now serialize frames into the packet in descending order of priority.
+     */
+
+    /* HANDSHAKE_DONE (Regenerate) */
+    if (a.allow_handshake_done && txp->want_handshake_done
+        && tx_helper_get_space_left(&h) >= MIN_FRAME_SIZE_HANDSHAKE_DONE) {
+        WPACKET *wpkt = tx_helper_begin(&h);
+
+        if (wpkt == NULL)
+            goto fatal_err;
+
+        if (ossl_quic_wire_encode_frame_handshake_done(wpkt)) {
+            tpkt->had_handshake_done_frame = 1;
+            have_ack_eliciting             = 1;
+
+            if (!tx_helper_commit(&h))
+                goto fatal_err;
+
+            tx_helper_unrestrict(&h); /* no longer need PING */
+        } else {
+            tx_helper_rollback(&h);
+        }
+    }
+
+    /* MAX_DATA (Regenerate) */
+    if (a.allow_conn_fc
+        && (txp->want_max_data
+            || ossl_quic_rxfc_has_cwm_changed(txp->args.conn_rxfc, 0))
+        && tx_helper_get_space_left(&h) >= MIN_FRAME_SIZE_MAX_DATA) {
+        WPACKET *wpkt = tx_helper_begin(&h);
+        uint64_t cwm = ossl_quic_rxfc_get_cwm(txp->args.conn_rxfc);
+
+        if (wpkt == NULL)
+            goto fatal_err;
+
+        if (ossl_quic_wire_encode_frame_max_data(wpkt, cwm)) {
+            tpkt->had_max_data_frame = 1;
+            have_ack_eliciting       = 1;
+
+            if (!tx_helper_commit(&h))
+                goto fatal_err;
+
+            tx_helper_unrestrict(&h); /* no longer need PING */
+        } else {
+            tx_helper_rollback(&h);
+        }
+    }
+
+    /* MAX_STREAMS_BIDI (Regenerate) */
+    /*
+     * TODO(STREAMS): Once we support multiple streams, add stream count FC
+     * and plug this in.
+     */
+    if (a.allow_conn_fc
+        && txp->want_max_streams_bidi
+        && tx_helper_get_space_left(&h) >= MIN_FRAME_SIZE_MAX_STREAMS_BIDI) {
+        WPACKET *wpkt = tx_helper_begin(&h);
+        uint64_t max_streams = 1; /* TODO */
+
+        if (wpkt == NULL)
+            goto fatal_err;
+
+        if (ossl_quic_wire_encode_frame_max_streams(wpkt, /*is_uni=*/0,
+                                                    max_streams)) {
+            tpkt->had_max_streams_bidi_frame = 1;
+            have_ack_eliciting               = 1;
+
+            if (!tx_helper_commit(&h))
+                goto fatal_err;
+
+            tx_helper_unrestrict(&h); /* no longer need PING */
+        } else {
+            tx_helper_rollback(&h);
+        }
+    }
+
+    /* MAX_STREAMS_UNI (Regenerate) */
+    if (a.allow_conn_fc
+        && txp->want_max_streams_uni
+        && tx_helper_get_space_left(&h) >= MIN_FRAME_SIZE_MAX_STREAMS_UNI) {
+        WPACKET *wpkt = tx_helper_begin(&h);
+        uint64_t max_streams = 0; /* TODO */
+
+        if (wpkt == NULL)
+            goto fatal_err;
+
+        if (ossl_quic_wire_encode_frame_max_streams(wpkt, /*is_uni=*/1,
+                                                    max_streams)) {
+            tpkt->had_max_streams_uni_frame = 1;
+            have_ack_eliciting              = 1;
+
+            if (!tx_helper_commit(&h))
+                goto fatal_err;
+
+            tx_helper_unrestrict(&h); /* no longer need PING */
+        } else {
+            tx_helper_rollback(&h);
+        }
+    }
+
+    /* GCR Frames */
+    for (cfq_item = ossl_quic_cfq_get_priority_head(txp->args.cfq, pn_space);
+         cfq_item != NULL;
+         cfq_item = ossl_quic_cfq_item_get_priority_next(cfq_item, pn_space)) {
+        uint64_t frame_type = ossl_quic_cfq_item_get_frame_type(cfq_item);
+        const unsigned char *encoded = ossl_quic_cfq_item_get_encoded(cfq_item);
+        size_t encoded_len = ossl_quic_cfq_item_get_encoded_len(cfq_item);
+
+        switch (frame_type) {
+            case OSSL_QUIC_FRAME_TYPE_NEW_CONN_ID:
+                if (!a.allow_new_conn_id)
+                    continue;
+                break;
+            case OSSL_QUIC_FRAME_TYPE_RETIRE_CONN_ID:
+                if (!a.allow_retire_conn_id)
+                    continue;
+                break;
+            case OSSL_QUIC_FRAME_TYPE_NEW_TOKEN:
+                if (!a.allow_new_token)
+                    continue;
+
+                /*
+                 * NEW_TOKEN frames are handled via GCR, but some
+                 * Regenerate-strategy frames should come before them (namely
+                 * ACK, CONNECTION_CLOSE, PATH_CHALLENGE and PATH_RESPONSE). If
+                 * we find a NEW_TOKEN frame, do these now. If there are no
+                 * NEW_TOKEN frames in the GCR queue we will handle these below.
+                 */
+                if (!done_pre_token)
+                    if (txp_generate_pre_token(txp, &h, tpkt, pn_space, &a))
+                        done_pre_token = 1;
+
+                break;
+            default:
+                if (!a.allow_cfq_other)
+                    continue;
+                break;
+        }
+
+        /*
+         * If the frame is too big, don't try to schedule any more GCR frames in
+         * this packet rather than sending subsequent ones out of order.
+         */
+        if (encoded_len > tx_helper_get_space_left(&h))
+            break;
+
+        if (!tx_helper_append_iovec(&h, encoded, encoded_len))
+            goto fatal_err;
+
+        ossl_quic_txpim_pkt_add_cfq_item(tpkt, cfq_item);
+
+        if (ossl_quic_frame_type_is_ack_eliciting(frame_type)) {
+            have_ack_eliciting = 1;
+            tx_helper_unrestrict(&h); /* no longer need PING */
+        }
+    }
+
+    /*
+     * If we didn't generate ACK, CONNECTION_CLOSE, PATH_CHALLENGE or
+     * PATH_RESPONSE (as desired) before, do so now.
+     */
+    if (!done_pre_token)
+        if (txp_generate_pre_token(txp, &h, tpkt, pn_space, &a))
+            done_pre_token = 1;
+
+    /* CRYPTO Frames */
+    if (a.allow_crypto)
+        if (!txp_generate_crypto_frames(txp, &h, pn_space, tpkt,
+                                        &have_ack_eliciting))
+            goto fatal_err;
+
+    /* Stream-specific frames */
+    if (a.allow_stream_rel)
+        if (!txp_generate_stream_related(txp, &h, pn_space, tpkt, min_ppl,
+                                         &have_ack_eliciting,
+                                         &tmp_head))
+            goto fatal_err;
+
+    /* PING */
+    tx_helper_unrestrict(&h);
+
+    if (require_ack_eliciting && !have_ack_eliciting && a.allow_ping) {
+        WPACKET *wpkt;
+
+        wpkt = tx_helper_begin(&h);
+        if (wpkt == NULL)
+            goto fatal_err;
+
+        if (!ossl_quic_wire_encode_frame_ping(wpkt)
+            || !tx_helper_commit(&h))
+            /*
+             * We treat a request to be ACK-eliciting as a requirement, so this
+             * is an error.
+             */
+            goto fatal_err;
+
+        have_ack_eliciting = 1;
+    }
+
+    /* PADDING */
+    if (h.bytes_appended < min_ppl) {
+        WPACKET *wpkt = tx_helper_begin(&h);
+        if (wpkt == NULL)
+            goto fatal_err;
+
+        if (!ossl_quic_wire_encode_padding(wpkt, min_ppl - h.bytes_appended)
+            || !tx_helper_commit(&h))
+            goto fatal_err;
+    }
+
+    /*
+     * Dispatch
+     * ========
+     */
+    /* ACKM Data */
+    tpkt->ackm_pkt.num_bytes        = h.bytes_appended + pkt_overhead;
+    tpkt->ackm_pkt.pkt_num          = txp->next_pn[pn_space];
+    /* largest_acked is set in txp_generate_pre_token */
+    tpkt->ackm_pkt.pkt_space        = pn_space;
+    tpkt->ackm_pkt.is_inflight      = 1;
+    tpkt->ackm_pkt.is_ack_eliciting = have_ack_eliciting;
+    tpkt->ackm_pkt.is_pto_probe     = 0;
+    tpkt->ackm_pkt.is_mtu_probe     = 0;
+    tpkt->ackm_pkt.time             = ossl_time_now();
+
+    /* Packet Information for QTX */
+    pkt.hdr         = phdr;
+    pkt.iovec       = txp->iovec;
+    pkt.num_iovec   = h.num_iovec;
+    pkt.local       = NULL;
+    pkt.peer        = BIO_ADDR_family(&txp->args.peer) == AF_UNSPEC
+        ? NULL : &txp->args.peer;
+    pkt.pn          = txp->next_pn[pn_space];
+    pkt.flags       = OSSL_QTX_PKT_FLAG_COALESCE; /* always try to coalesce */
+
+    /* Do TX key update if needed. */
+    if (enc_level == QUIC_ENC_LEVEL_1RTT) {
+        uint64_t cur_pkt_count, max_pkt_count;
+
+        cur_pkt_count = ossl_qtx_get_cur_epoch_pkt_count(txp->args.qtx, enc_level);
+        max_pkt_count = ossl_qtx_get_max_epoch_pkt_count(txp->args.qtx, enc_level);
+
+        if (cur_pkt_count >= max_pkt_count / 2)
+            if (!ossl_qtx_trigger_key_update(txp->args.qtx))
+                goto fatal_err;
+    }
+
+    if (!ossl_assert(h.bytes_appended > 0))
+        goto fatal_err;
+
+    /* Generate TXPIM chunks representing STOP_SENDING and RESET_STREAM frames. */
+    for (stream = tmp_head; stream != NULL; stream = stream->txp_next)
+        if (stream->txp_sent_stop_sending || stream->txp_sent_reset_stream) {
+            /* Log STOP_SENDING chunk to TXPIM. */
+            QUIC_TXPIM_CHUNK chunk;
+
+            chunk.stream_id         = stream->id;
+            chunk.start             = UINT64_MAX;
+            chunk.end               = 0;
+            chunk.has_fin           = 0;
+            chunk.has_stop_sending  = stream->txp_sent_stop_sending;
+            chunk.has_reset_stream  = stream->txp_sent_reset_stream;
+            if (!ossl_quic_txpim_pkt_append_chunk(tpkt, &chunk))
+                return 0; /* alloc error */
+        }
+
+    /* Dispatch to FIFD. */
+    if (!ossl_quic_fifd_pkt_commit(&txp->fifd, tpkt))
+        goto fatal_err;
+
+    /* Send the packet. */
+    if (!ossl_qtx_write_pkt(txp->args.qtx, &pkt))
+        goto fatal_err;
+
+    ++txp->next_pn[pn_space];
+
+    /*
+     * Record FC and stream abort frames as sent; deactivate streams which no
+     * longer have anything to do.
+     */
+    for (stream = tmp_head; stream != NULL; stream = stream->txp_next) {
+        if (stream->txp_sent_fc) {
+            stream->want_max_stream_data = 0;
+            ossl_quic_rxfc_has_cwm_changed(&stream->rxfc, 1);
+        }
+
+        if (stream->txp_sent_stop_sending)
+            stream->want_stop_sending = 0;
+
+        if (stream->txp_sent_reset_stream)
+            stream->want_reset_stream = 0;
+
+        if (stream->txp_txfc_new_credit_consumed > 0) {
+            if (!ossl_assert(ossl_quic_txfc_consume_credit(&stream->txfc,
+                                                           stream->txp_txfc_new_credit_consumed)))
+                /*
+                 * Should not be possible, but we should continue with our
+                 * bookkeeping as we have already committed the packet to the
+                 * FIFD. Just change the value we return.
+                 */
+                rc = TXP_ERR_INTERNAL;
+
+            stream->txp_txfc_new_credit_consumed = 0;
+        }
+
+        /*
+         * If we no longer need to generate any flow control (MAX_STREAM_DATA),
+         * STOP_SENDING or RESET_STREAM frames, nor any STREAM frames (because
+         * the stream is drained of data or TXFC-blocked), we can mark the
+         * stream as inactive.
+         */
+        ossl_quic_stream_map_update_state(txp->args.qsm, stream);
+
+        if (!stream->want_max_stream_data
+            && !stream->want_stop_sending
+            && !stream->want_reset_stream
+            && (stream->txp_drained || stream->txp_blocked))
+            assert(!stream->active);
+    }
+
+    /* We have now sent the packet, so update state accordingly. */
+    if (have_ack_eliciting)
+        txp->force_ack_eliciting &= ~(1UL << pn_space);
+
+    if (tpkt->had_handshake_done_frame)
+        txp->want_handshake_done = 0;
+
+    if (tpkt->had_max_data_frame) {
+        txp->want_max_data = 0;
+        ossl_quic_rxfc_has_cwm_changed(txp->args.conn_rxfc, 1);
+    }
+
+    if (tpkt->had_max_streams_bidi_frame)
+        txp->want_max_streams_bidi = 0;
+
+    if (tpkt->had_max_streams_uni_frame)
+        txp->want_max_streams_uni = 0;
+
+    if (tpkt->had_ack_frame)
+        txp->want_ack &= ~(1UL << pn_space);
+
+    /* Done. */
+    tx_helper_cleanup(&h);
+    return rc;
+
+fatal_err:
+    /*
+     * Handler for fatal errors, i.e. errors causing us to abort the entire
+     * packet rather than just one frame. Examples of such errors include
+     * allocation errors.
+     */
+    if (have_helper)
+        tx_helper_cleanup(&h);
+    if (tpkt != NULL)
+        ossl_quic_txpim_pkt_release(txp->args.txpim, tpkt);
+    return TXP_ERR_INTERNAL;
+}
+
+/* Ensure the iovec array is at least num elements long. */
+static int txp_ensure_iovec(OSSL_QUIC_TX_PACKETISER *txp, size_t num)
+{
+    OSSL_QTX_IOVEC *iovec;
+
+    if (txp->alloc_iovec >= num)
+        return 1;
+
+    num = txp->alloc_iovec != 0 ? txp->alloc_iovec * 2 : 8;
+
+    iovec = OPENSSL_realloc(txp->iovec, sizeof(OSSL_QTX_IOVEC) * num);
+    if (iovec == NULL)
+        return 0;
+
+    txp->iovec          = iovec;
+    txp->alloc_iovec    = num;
+    return 1;
+}
+
+int ossl_quic_tx_packetiser_schedule_conn_close(OSSL_QUIC_TX_PACKETISER *txp,
+                                                const OSSL_QUIC_FRAME_CONN_CLOSE *f)
+{
+    char *reason = NULL;
+    size_t reason_len = f->reason_len;
+    size_t max_reason_len = txp_get_mdpl(txp) / 2;
+
+    if (txp->want_conn_close)
+        return 0;
+
+    /*
+     * Arbitrarily limit the length of the reason length string to half of the
+     * MDPL.
+     */
+    if (reason_len > max_reason_len)
+        reason_len = max_reason_len;
+
+    if (reason_len > 0) {
+        reason = OPENSSL_memdup(f->reason, reason_len);
+        if (reason == NULL)
+            return 0;
+    }
+
+    txp->conn_close_frame               = *f;
+    txp->conn_close_frame.reason        = reason;
+    txp->conn_close_frame.reason_len    = reason_len;
+    txp->want_conn_close                = 1;
+    return 1;
+}
index e086834b5e2b4389a77320c14ae8bea734a4ce6c..bc66f6c592f8c318a6002ea8ac9c3c98f0017de4 100644 (file)
@@ -121,6 +121,19 @@ int ossl_quic_wire_encode_frame_crypto_hdr(WPACKET *pkt,
     return 1;
 }
 
+size_t ossl_quic_wire_get_encoded_frame_len_crypto_hdr(const OSSL_QUIC_FRAME_CRYPTO *f)
+{
+    size_t a, b, c;
+
+    a = ossl_quic_vlint_encode_len(OSSL_QUIC_FRAME_TYPE_CRYPTO);
+    b = ossl_quic_vlint_encode_len(f->offset);
+    c = ossl_quic_vlint_encode_len(f->len);
+    if (a == 0 || b == 0 || c == 0)
+        return 0;
+
+    return a + b + c;
+}
+
 void *ossl_quic_wire_encode_frame_crypto(WPACKET *pkt,
                                          const OSSL_QUIC_FRAME_CRYPTO *f)
 {
@@ -174,6 +187,34 @@ int ossl_quic_wire_encode_frame_stream_hdr(WPACKET *pkt,
     return 1;
 }
 
+size_t ossl_quic_wire_get_encoded_frame_len_stream_hdr(const OSSL_QUIC_FRAME_STREAM *f)
+{
+    size_t a, b, c, d;
+
+    a = ossl_quic_vlint_encode_len(OSSL_QUIC_FRAME_TYPE_STREAM);
+    b = ossl_quic_vlint_encode_len(f->stream_id);
+    if (a == 0 || b == 0)
+        return 0;
+
+    if (f->offset > 0) {
+        c = ossl_quic_vlint_encode_len(f->offset);
+        if (c == 0)
+            return 0;
+    } else {
+        c = 0;
+    }
+
+    if (f->has_explicit_len) {
+        d = ossl_quic_vlint_encode_len(f->len);
+        if (d == 0)
+            return 0;
+    } else {
+        d = 0;
+    }
+
+    return a + b + c + d;
+}
+
 void *ossl_quic_wire_encode_frame_stream(WPACKET *pkt,
                                          const OSSL_QUIC_FRAME_STREAM *f)
 {
index b2bf90e7b6850d01194a2e699e5ba4a514287a87..a4fdc7bf53008d8b22f0e6c0db008cadafd4ba06 100644 (file)
@@ -583,11 +583,12 @@ int ossl_quic_wire_get_encoded_pkt_hdr_len(size_t short_conn_id_len,
             enclen = ossl_quic_vlint_encode_len(hdr->token_len);
             if (!enclen)
                 return 0;
-            len += enclen;
+
+            len += enclen + hdr->token_len;
         }
 
         if (!ossl_quic_pkt_type_must_be_last(hdr->type)) {
-            enclen = ossl_quic_vlint_encode_len(hdr->len);
+            enclen = ossl_quic_vlint_encode_len(hdr->len + hdr->pn_len);
             if (!enclen)
                 return 0;
 
index 0c91ff602ece90020caf56d883a3e2317af966d1..346f503853b4a91b447bf853b2ffc341eb70ea08 100644 (file)
@@ -312,6 +312,10 @@ IF[{- !$disabled{tests} -}]
   INCLUDE[quic_fifd_test]=../include ../apps/include
   DEPEND[quic_fifd_test]=../libcrypto.a ../libssl.a libtestutil.a
 
+  SOURCE[quic_txp_test]=quic_txp_test.c
+  INCLUDE[quic_txp_test]=../include ../apps/include
+  DEPEND[quic_txp_test]=../libcrypto.a ../libssl.a libtestutil.a
+
   SOURCE[asynctest]=asynctest.c
   INCLUDE[asynctest]=../include ../apps/include
   DEPEND[asynctest]=../libcrypto
@@ -1040,7 +1044,7 @@ ENDIF
   ENDIF
 
   IF[{- !$disabled{'quic'} -}]
-    PROGRAMS{noinst}=quicapitest quic_wire_test quic_ackm_test quic_record_test quic_fc_test quic_stream_test quic_cfq_test quic_txpim_test quic_fifd_test
+    PROGRAMS{noinst}=quicapitest quic_wire_test quic_ackm_test quic_record_test quic_fc_test quic_stream_test quic_cfq_test quic_txpim_test quic_fifd_test quic_txp_test
   ENDIF
 
   SOURCE[quicapitest]=quicapitest.c helpers/ssltestlib.c
index 47eb03093019b0235b693e4a657943693e4ee818..dfcabfa7cdbcc40ca494526002cc0408647e4164 100644 (file)
@@ -22,18 +22,22 @@ static void step_time(uint64_t ms) {
     cur_time = ossl_time_add(cur_time, ossl_ms2time(ms));
 }
 
-static QUIC_SSTREAM *(*get_sstream_by_id_p)(uint64_t stream_id, void *arg);
+static QUIC_SSTREAM *(*get_sstream_by_id_p)(uint64_t stream_id, uint32_t pn_space,
+                                            void *arg);
 
-static QUIC_SSTREAM *get_sstream_by_id(uint64_t stream_id, void *arg)
+static QUIC_SSTREAM *get_sstream_by_id(uint64_t stream_id, uint32_t pn_space,
+                                       void *arg)
 {
-    return get_sstream_by_id_p(stream_id, arg);
+    return get_sstream_by_id_p(stream_id, pn_space, arg);
 }
 
-static void (*regen_frame_p)(uint64_t frame_type, uint64_t stream_id, void *arg);
+static void (*regen_frame_p)(uint64_t frame_type, uint64_t stream_id,
+                             QUIC_TXPIM_PKT *pkt, void *arg);
 
-static void regen_frame(uint64_t frame_type, uint64_t stream_id, void *arg)
+static void regen_frame(uint64_t frame_type, uint64_t stream_id,
+                        QUIC_TXPIM_PKT *pkt, void *arg)
 {
-    regen_frame_p(frame_type, stream_id, arg);
+    regen_frame_p(frame_type, stream_id, pkt, arg);
 }
 
 typedef struct info_st {
@@ -57,7 +61,8 @@ static int cfq_freed;
  *    Test that a submitted packet, on ack, acks all fins inside it
  *    Test that a submitted packet, on ack, releases the TXPIM packet
  */
-static QUIC_SSTREAM *sstream_expect(uint64_t stream_id, void *arg)
+static QUIC_SSTREAM *sstream_expect(uint64_t stream_id, uint32_t pn_space,
+                                    void *arg)
 {
     if (stream_id == 42 || stream_id == 43)
         return cur_info->sstream[stream_id - 42];
@@ -70,7 +75,8 @@ static uint64_t regen_frame_type[16];
 static uint64_t regen_stream_id[16];
 static size_t regen_count;
 
-static void regen_expect(uint64_t frame_type, uint64_t stream_id, void *arg)
+static void regen_expect(uint64_t frame_type, uint64_t stream_id,
+                         QUIC_TXPIM_PKT *pkt, void *arg)
 {
     regen_frame_type[regen_count] = frame_type;
     regen_stream_id[regen_count] = stream_id;
index 5349b70b178919d00823ddaba3632f5878ffbbe2..c6ac10d854c77a93c400c0d2762d905452ee9c11 100644 (file)
@@ -14,6 +14,7 @@
 #include "internal/quic_cc.h"
 #include "internal/quic_ssl.h"
 #include "testutil.h"
+#include "quic_record_test_util.h"
 
 static const QUIC_CONN_ID empty_conn_id = {0, {0}};
 
@@ -1668,45 +1669,6 @@ static const struct rx_test_op *rx_scripts[] = {
     rx_script_8
 };
 
-static int cmp_pkt_hdr(const QUIC_PKT_HDR *a, const QUIC_PKT_HDR *b,
-                       const unsigned char *b_data, size_t b_len,
-                       int cmp_data)
-{
-    int ok = 1;
-
-    if (b_data == NULL) {
-        b_data = b->data;
-        b_len  = b->len;
-    }
-
-    if (!TEST_int_eq(a->type, b->type)
-        || !TEST_int_eq(a->spin_bit, b->spin_bit)
-        || !TEST_int_eq(a->key_phase, b->key_phase)
-        || !TEST_int_eq(a->pn_len, b->pn_len)
-        || !TEST_int_eq(a->partial, b->partial)
-        || !TEST_int_eq(a->fixed, b->fixed)
-        || !TEST_uint_eq(a->version, b->version)
-        || !TEST_true(ossl_quic_conn_id_eq(&a->dst_conn_id, &b->dst_conn_id))
-        || !TEST_true(ossl_quic_conn_id_eq(&a->src_conn_id, &b->src_conn_id))
-        || !TEST_mem_eq(a->pn, sizeof(a->pn), b->pn, sizeof(b->pn))
-        || !TEST_size_t_eq(a->token_len, b->token_len)
-        || !TEST_uint64_t_eq(a->len, b->len))
-        ok = 0;
-
-    if (a->token_len > 0 && b->token_len > 0
-        && !TEST_mem_eq(a->token, a->token_len, b->token, b->token_len))
-        ok = 0;
-
-    if ((a->token_len == 0 && !TEST_ptr_null(a->token))
-        || (b->token_len == 0 && !TEST_ptr_null(b->token)))
-        ok = 0;
-
-    if (cmp_data && !TEST_mem_eq(a->data, a->len, b_data, b_len))
-        ok = 0;
-
-    return ok;
-}
-
 struct rx_state {
     QUIC_DEMUX         *demux;
 
diff --git a/test/quic_record_test_util.h b/test/quic_record_test_util.h
new file mode 100644 (file)
index 0000000..51d5db8
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2022 The OpenSSL Project Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License 2.0 (the "License").  You may not use
+ * this file except in compliance with the License.  You can obtain a copy
+ * in the file LICENSE in the source distribution or at
+ * https://www.openssl.org/source/license.html
+ */
+
+#ifndef OSSL_RECORD_TEST_UTIL_H
+# define OSSL_RECORD_TEST_UTIL_H
+
+static int cmp_pkt_hdr(const QUIC_PKT_HDR *a, const QUIC_PKT_HDR *b,
+                       const unsigned char *b_data, size_t b_len,
+                       int cmp_data)
+{
+    int ok = 1;
+
+    if (b_data == NULL) {
+        b_data = b->data;
+        b_len  = b->len;
+    }
+
+    if (!TEST_int_eq(a->type, b->type)
+        || !TEST_int_eq(a->spin_bit, b->spin_bit)
+        || !TEST_int_eq(a->key_phase, b->key_phase)
+        || !TEST_int_eq(a->pn_len, b->pn_len)
+        || !TEST_int_eq(a->partial, b->partial)
+        || !TEST_int_eq(a->fixed, b->fixed)
+        || !TEST_uint_eq(a->version, b->version)
+        || !TEST_true(ossl_quic_conn_id_eq(&a->dst_conn_id, &b->dst_conn_id))
+        || !TEST_true(ossl_quic_conn_id_eq(&a->src_conn_id, &b->src_conn_id))
+        || !TEST_mem_eq(a->pn, sizeof(a->pn), b->pn, sizeof(b->pn))
+        || !TEST_size_t_eq(a->token_len, b->token_len)
+        || !TEST_uint64_t_eq(a->len, b->len))
+        ok = 0;
+
+    if (a->token_len > 0 && b->token_len > 0
+        && !TEST_mem_eq(a->token, a->token_len, b->token, b->token_len))
+        ok = 0;
+
+    if ((a->token_len == 0 && !TEST_ptr_null(a->token))
+        || (b->token_len == 0 && !TEST_ptr_null(b->token)))
+        ok = 0;
+
+    if (cmp_data && !TEST_mem_eq(a->data, a->len, b_data, b_len))
+        ok = 0;
+
+    return ok;
+}
+
+#endif
index 918d67aea8f6c50186d99242dfaa70559bd158e8..dc0a618cd68cff58ed1ce023273f628a8b33ab93 100644 (file)
@@ -19,10 +19,8 @@ static int compare_iov(const unsigned char *ref, size_t ref_len,
     for (i = 0; i < iov_len; ++i)
         total_len += iov[i].buf_len;
 
-    if (ref_len != total_len) {
-        fprintf(stderr, "# expected %lu == %lu\n", ref_len, total_len);
+    if (ref_len != total_len)
         return 0;
-    }
 
     for (i = 0; i < iov_len; ++i) {
         if (memcmp(cur, iov[i].buf, iov[i].buf_len))
diff --git a/test/quic_txp_test.c b/test/quic_txp_test.c
new file mode 100644 (file)
index 0000000..afdfba3
--- /dev/null
@@ -0,0 +1,1424 @@
+/*
+ * Copyright 2022 The OpenSSL Project Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License 2.0 (the "License").  You may not use
+ * this file except in compliance with the License.  You can obtain a copy
+ * in the file LICENSE in the source distribution or at
+ * https://www.openssl.org/source/license.html
+ */
+#include "internal/packet.h"
+#include "internal/quic_txp.h"
+#include "internal/quic_statm.h"
+#include "internal/quic_demux.h"
+#include "internal/quic_record_rx.h"
+#include "testutil.h"
+#include "quic_record_test_util.h"
+
+static const QUIC_CONN_ID scid_1 = {
+    1, { 0x5f }
+};
+
+static const QUIC_CONN_ID dcid_1 = {
+    8, { 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8 }
+};
+
+static const QUIC_CONN_ID cid_1 = {
+    8, { 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8 }
+};
+
+static const unsigned char reset_token_1[16] = {
+    0x99, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11,
+    0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x12,
+};
+
+static const unsigned char secret_1[32] = {
+    0x01
+};
+
+static OSSL_TIME fake_now(void *arg)
+{
+    return ossl_time_now(); /* TODO */
+}
+
+struct helper {
+    OSSL_QUIC_TX_PACKETISER         *txp;
+    OSSL_QUIC_TX_PACKETISER_ARGS    args;
+    OSSL_QTX_ARGS                   qtx_args;
+    BIO                             *bio1, *bio2;
+    QUIC_TXFC                       conn_txfc;
+    QUIC_RXFC                       conn_rxfc, stream_rxfc;
+    OSSL_STATM                      statm;
+    OSSL_CC_DATA                    *cc_data;
+    const OSSL_CC_METHOD            *cc_method;
+    QUIC_STREAM_MAP                 qsm;
+    char                            have_statm, have_qsm;
+    QUIC_DEMUX                      *demux;
+    OSSL_QRX                        *qrx;
+    OSSL_QRX_ARGS                   qrx_args;
+    OSSL_QRX_PKT                    qrx_pkt;
+    PACKET                          pkt;
+    uint64_t                        frame_type;
+    union {
+        uint64_t                        max_data;
+        OSSL_QUIC_FRAME_NEW_CONN_ID     new_conn_id;
+        OSSL_QUIC_FRAME_ACK             ack;
+        struct {
+            const unsigned char *token;
+            size_t              token_len;
+        } new_token;
+        OSSL_QUIC_FRAME_CRYPTO          crypto;
+        OSSL_QUIC_FRAME_STREAM          stream;
+        OSSL_QUIC_FRAME_STOP_SENDING    stop_sending;
+        OSSL_QUIC_FRAME_RESET_STREAM    reset_stream;
+        OSSL_QUIC_FRAME_CONN_CLOSE      conn_close;
+    } frame;
+    OSSL_QUIC_ACK_RANGE     ack_ranges[16];
+};
+
+static void helper_cleanup(struct helper *h)
+{
+    size_t i;
+    uint32_t pn_space;
+
+    if (h->qrx_pkt.handle != NULL)
+        ossl_qrx_release_pkt(h->qrx, h->qrx_pkt.handle);
+
+    for (pn_space = QUIC_PN_SPACE_INITIAL;
+         pn_space < QUIC_PN_SPACE_NUM;
+         ++pn_space)
+        ossl_ackm_on_pkt_space_discarded(h->args.ackm, pn_space);
+
+    ossl_quic_tx_packetiser_free(h->txp);
+    ossl_qtx_free(h->args.qtx);
+    ossl_quic_txpim_free(h->args.txpim);
+    ossl_quic_cfq_free(h->args.cfq);
+    if (h->cc_data != NULL)
+        h->cc_method->free(h->cc_data);
+    if (h->have_statm)
+        ossl_statm_destroy(&h->statm);
+    if (h->have_qsm)
+        ossl_quic_stream_map_cleanup(&h->qsm);
+    for (i = 0; i < QUIC_PN_SPACE_NUM; ++i)
+        ossl_quic_sstream_free(h->args.crypto[i]);
+    ossl_ackm_free(h->args.ackm);
+    ossl_qrx_free(h->qrx);
+    ossl_quic_demux_free(h->demux);
+    BIO_free(h->bio1);
+    BIO_free(h->bio2);
+}
+
+static int helper_init(struct helper *h)
+{
+    int rc = 0;
+    size_t i;
+
+    memset(h, 0, sizeof(*h));
+
+    /* Initialisation */
+    if (!TEST_true(BIO_new_bio_dgram_pair(&h->bio1, 0, &h->bio2, 0)))
+        goto err;
+
+    h->qtx_args.bio    = h->bio1;
+    h->qtx_args.mdpl   = 1200;
+
+    if (!TEST_ptr(h->args.qtx = ossl_qtx_new(&h->qtx_args)))
+        goto err;
+
+    if (!TEST_ptr(h->args.txpim = ossl_quic_txpim_new()))
+        goto err;
+
+    if (!TEST_ptr(h->args.cfq = ossl_quic_cfq_new()))
+        goto err;
+
+    if (!TEST_true(ossl_quic_txfc_init(&h->conn_txfc, NULL)))
+        goto err;
+
+    if (!TEST_true(ossl_quic_rxfc_init(&h->conn_rxfc, NULL,
+                                       2 * 1024 * 1024,
+                                       10 * 1024 * 1024,
+                                       fake_now,
+                                       NULL)))
+        goto err;
+
+    if (!TEST_true(ossl_quic_rxfc_init(&h->stream_rxfc, &h->conn_rxfc,
+                                       1 * 1024 * 1024,
+                                       5 * 1024 * 1024,
+                                       fake_now,
+                                       NULL)))
+        goto err;
+
+    if (!TEST_true(ossl_statm_init(&h->statm)))
+        goto err;
+
+    h->have_statm = 1;
+
+    h->cc_method = &ossl_cc_dummy_method;
+    if (!TEST_ptr(h->cc_data = h->cc_method->new(NULL, NULL, NULL)))
+        goto err;
+
+    if (!TEST_ptr(h->args.ackm = ossl_ackm_new(fake_now, NULL,
+                                               &h->statm,
+                                               h->cc_method,
+                                               h->cc_data)))
+        goto err;
+
+    if (!TEST_true(ossl_quic_stream_map_init(&h->qsm)))
+        goto err;
+
+    h->have_qsm = 1;
+
+    for (i = 0; i < QUIC_PN_SPACE_NUM; ++i)
+        if (!TEST_ptr(h->args.crypto[i] = ossl_quic_sstream_new(4096)))
+            goto err;
+
+    h->args.cur_scid   = scid_1;
+    h->args.cur_dcid   = dcid_1;
+    h->args.qsm        = &h->qsm;
+    h->args.conn_txfc  = &h->conn_txfc;
+    h->args.conn_rxfc  = &h->conn_rxfc;
+    h->args.cc_method  = h->cc_method;
+    h->args.cc_data    = h->cc_data;
+    h->args.now        = fake_now;
+
+    if (!TEST_ptr(h->txp = ossl_quic_tx_packetiser_new(&h->args)))
+        goto err;
+
+    if (!TEST_ptr(h->demux = ossl_quic_demux_new(h->bio2, 8, 1200,
+                                                 fake_now, NULL)))
+        goto err;
+
+    h->qrx_args.demux                  = h->demux;
+    h->qrx_args.short_conn_id_len      = 8;
+    h->qrx_args.max_deferred           = 32;
+
+    if (!TEST_ptr(h->qrx = ossl_qrx_new(&h->qrx_args)))
+        goto err;
+
+    if (!TEST_true(ossl_qrx_add_dst_conn_id(h->qrx, &dcid_1)))
+        goto err;
+
+    rc = 1;
+err:
+    if (!rc)
+        helper_cleanup(h);
+
+    return rc;
+}
+
+#define OPK_END                     0   /* End of Script */
+#define OPK_TXP_GENERATE            1   /* Call generate, expect packet output */
+#define OPK_TXP_GENERATE_NONE       2   /* Call generate, expect no packet output */
+#define OPK_RX_PKT                  3   /* Receive, expect packet */
+#define OPK_RX_PKT_NONE             4   /* Receive, expect no packet */
+#define OPK_EXPECT_DGRAM_LEN        5   /* Expect received datagram length in range */
+#define OPK_EXPECT_FRAME            6   /* Expect next frame is of type */
+#define OPK_EXPECT_INITIAL_TOKEN    7   /* Expect initial token buffer match */
+#define OPK_EXPECT_HDR              8   /* Expect header structure match */
+#define OPK_CHECK                   9   /* Call check function */
+#define OPK_NEXT_FRAME              10  /* Next frame */
+#define OPK_EXPECT_NO_FRAME         11  /* Expect no further frames */
+#define OPK_PROVIDE_SECRET          12  /* Provide secret to QTX and QRX */
+#define OPK_DISCARD_EL              13  /* Discard QTX EL */
+#define OPK_CRYPTO_SEND             14  /* Push data into crypto send stream */
+#define OPK_STREAM_NEW              15  /* Create new application stream */
+#define OPK_STREAM_SEND             16  /* Push data into application send stream */
+#define OPK_STREAM_FIN              17  /* Mark stream as finished */
+#define OPK_STOP_SENDING            18  /* Mark stream for STOP_SENDING */
+#define OPK_RESET_STREAM            19  /* Mark stream for RESET_STREAM */
+#define OPK_CONN_TXFC_BUMP          20  /* Bump connection TXFC CWM */
+#define OPK_STREAM_TXFC_BUMP        21  /* Bump stream TXFC CWM */
+
+struct script_op {
+    uint32_t opcode;
+    uint64_t arg0, arg1;
+    const void *buf;
+    size_t buf_len;
+    int (*check_func)(struct helper *h);
+};
+
+#define OP_END      \
+    { OPK_END }
+#define OP_TXP_GENERATE(archetype) \
+    { OPK_TXP_GENERATE, (archetype) },
+#define OP_TXP_GENERATE_NONE(archetype) \
+    { OPK_TXP_GENERATE_NONE, (archetype) },
+#define OP_RX_PKT() \
+    { OPK_RX_PKT },
+#define OP_RX_PKT_NONE() \
+    { OPK_RX_PKT_NONE },
+#define OP_EXPECT_DGRAM_LEN(lo, hi) \
+    { OPK_EXPECT_DGRAM_LEN, (lo), (hi) },
+#define OP_EXPECT_FRAME(frame_type) \
+    { OPK_EXPECT_FRAME, (frame_type) },
+#define OP_EXPECT_INITIAL_TOKEN(buf) \
+    { OPK_EXPECT_INITIAL_TOKEN, sizeof(buf), 0, buf },
+#define OP_EXPECT_HDR(hdr) \
+    { OPK_EXPECT_HDR, 0, 0, &(hdr) },
+#define OP_CHECK(func) \
+    { OPK_CHECK, 0, 0, NULL, 0, (func) },
+#define OP_NEXT_FRAME() \
+    { OPK_NEXT_FRAME },
+#define OP_EXPECT_NO_FRAME() \
+    { OPK_EXPECT_NO_FRAME },
+#define OP_PROVIDE_SECRET(el, suite, secret) \
+    { OPK_PROVIDE_SECRET, (el), (suite), (secret), sizeof(secret) },
+#define OP_DISCARD_EL(el) \
+    { OPK_DISCARD_EL, (el) },
+#define OP_CRYPTO_SEND(pn_space, buf) \
+    { OPK_CRYPTO_SEND, (pn_space), 0, (buf), sizeof(buf) },
+#define OP_STREAM_NEW(id) \
+    { OPK_STREAM_NEW, (id) },
+#define OP_STREAM_SEND(id, buf) \
+    { OPK_STREAM_SEND, (id), 0, (buf), sizeof(buf) },
+#define OP_STREAM_FIN(id) \
+    { OPK_STREAM_FIN, (id) },
+#define OP_STOP_SENDING(id, aec) \
+    { OPK_STOP_SENDING, (id), (aec) },
+#define OP_RESET_STREAM(id, aec) \
+    { OPK_RESET_STREAM, (id), (aec) },
+#define OP_CONN_TXFC_BUMP(cwm) \
+    { OPK_CONN_TXFC_BUMP, (cwm) },
+#define OP_STREAM_TXFC_BUMP(id, cwm) \
+    { OPK_STREAM_TXFC_BUMP, (cwm), (id) },
+
+static int schedule_handshake_done(struct helper *h)
+{
+    ossl_quic_tx_packetiser_schedule_handshake_done(h->txp);
+    return 1;
+}
+
+static int schedule_ack_eliciting_app(struct helper *h)
+{
+    ossl_quic_tx_packetiser_schedule_ack_eliciting(h->txp, QUIC_PN_SPACE_APP);
+    return 1;
+}
+
+/* 1. 1-RTT, Single Handshake Done Frame */
+static const struct script_op script_1[] = {
+    OP_PROVIDE_SECRET(QUIC_ENC_LEVEL_1RTT, QRL_SUITE_AES128GCM, secret_1)
+    OP_TXP_GENERATE_NONE(TX_PACKETISER_ARCHETYPE_NORMAL)
+    OP_CHECK(schedule_handshake_done)
+    OP_TXP_GENERATE(TX_PACKETISER_ARCHETYPE_NORMAL)
+    OP_RX_PKT()
+    /* Should not be long */
+    OP_EXPECT_DGRAM_LEN(21, 32)
+    OP_NEXT_FRAME()
+    OP_EXPECT_FRAME(OSSL_QUIC_FRAME_TYPE_HANDSHAKE_DONE)
+    OP_EXPECT_NO_FRAME()
+    OP_RX_PKT_NONE()
+    OP_TXP_GENERATE_NONE(TX_PACKETISER_ARCHETYPE_NORMAL)
+    OP_END
+};
+
+/* 2. 1-RTT, Forced ACK-Eliciting Frame */
+static const struct script_op script_2[] = {
+    OP_PROVIDE_SECRET(QUIC_ENC_LEVEL_1RTT, QRL_SUITE_AES128GCM, secret_1)
+    OP_TXP_GENERATE_NONE(TX_PACKETISER_ARCHETYPE_NORMAL)
+    OP_CHECK(schedule_ack_eliciting_app)
+    OP_TXP_GENERATE(TX_PACKETISER_ARCHETYPE_NORMAL)
+    OP_RX_PKT()
+    /* Should not be long */
+    OP_EXPECT_DGRAM_LEN(21, 32)
+    /* A PING frame should have been added */
+    OP_NEXT_FRAME()
+    OP_EXPECT_FRAME(OSSL_QUIC_FRAME_TYPE_PING)
+    OP_EXPECT_NO_FRAME()
+    OP_RX_PKT_NONE()
+    OP_TXP_GENERATE_NONE(TX_PACKETISER_ARCHETYPE_NORMAL)
+    OP_END
+};
+
+/* 3. 1-RTT, MAX_DATA */
+static int schedule_max_data(struct helper *h)
+{
+    uint64_t cwm;
+
+    cwm = ossl_quic_rxfc_get_cwm(&h->stream_rxfc);
+
+    if (!TEST_true(ossl_quic_rxfc_on_rx_stream_frame(&h->stream_rxfc, cwm, 0))
+        || !TEST_true(ossl_quic_rxfc_on_retire(&h->stream_rxfc, cwm,
+                                               ossl_ticks2time(OSSL_TIME_MS))))
+        return 0;
+
+    return 1;
+}
+
+static const struct script_op script_3[] = {
+    OP_PROVIDE_SECRET(QUIC_ENC_LEVEL_1RTT, QRL_SUITE_AES128GCM, secret_1)
+    OP_TXP_GENERATE_NONE(TX_PACKETISER_ARCHETYPE_NORMAL)
+    OP_CHECK(schedule_max_data)
+    OP_TXP_GENERATE(TX_PACKETISER_ARCHETYPE_NORMAL)
+    OP_RX_PKT()
+    /* Should not be long */
+    OP_EXPECT_DGRAM_LEN(21, 40)
+    /* A PING frame should have been added */
+    OP_NEXT_FRAME()
+    OP_EXPECT_FRAME(OSSL_QUIC_FRAME_TYPE_MAX_DATA)
+    OP_EXPECT_NO_FRAME()
+    OP_RX_PKT_NONE()
+    OP_TXP_GENERATE_NONE(TX_PACKETISER_ARCHETYPE_NORMAL)
+    OP_END
+};
+
+/* 4. 1-RTT, CFQ (NEW_CONN_ID) */
+static void free_buf_mem(unsigned char *buf, size_t buf_len, void *arg)
+{
+    BUF_MEM_free((BUF_MEM *)arg);
+}
+
+static int schedule_cfq_new_conn_id(struct helper *h)
+{
+    int rc = 0;
+    QUIC_CFQ_ITEM *cfq_item;
+    WPACKET wpkt;
+    BUF_MEM *buf_mem = NULL;
+    char have_wpkt = 0;
+    size_t l = 0;
+    OSSL_QUIC_FRAME_NEW_CONN_ID ncid = {0};
+
+    ncid.seq_num         = 1234;
+    ncid.retire_prior_to = 2345;
+    ncid.conn_id         = cid_1;
+    memcpy(ncid.stateless_reset_token, reset_token_1, sizeof(reset_token_1));
+
+    if (!TEST_ptr(buf_mem = BUF_MEM_new()))
+        goto err;
+
+    if (!TEST_true(WPACKET_init(&wpkt, buf_mem)))
+        goto err;
+
+    have_wpkt = 1;
+    if (!TEST_true(ossl_quic_wire_encode_frame_new_conn_id(&wpkt, &ncid)))
+        goto err;
+
+    if (!TEST_true(WPACKET_get_total_written(&wpkt, &l)))
+        goto err;
+
+    if (!TEST_ptr(cfq_item = ossl_quic_cfq_add_frame(h->args.cfq, 1,
+                                                     QUIC_PN_SPACE_APP,
+                                                     OSSL_QUIC_FRAME_TYPE_NEW_CONN_ID,
+                                                     (unsigned char *)buf_mem->data, l,
+                                                     free_buf_mem,
+                                                     buf_mem)))
+        goto err;
+
+    rc = 1;
+err:
+    if (have_wpkt)
+        WPACKET_cleanup(&wpkt);
+    return rc;
+}
+
+static int check_cfq_new_conn_id(struct helper *h)
+{
+    if (!TEST_uint64_t_eq(h->frame.new_conn_id.seq_num, 1234)
+        || !TEST_uint64_t_eq(h->frame.new_conn_id.retire_prior_to, 2345)
+        || !TEST_mem_eq(&h->frame.new_conn_id.conn_id, sizeof(cid_1),
+                        &cid_1, sizeof(cid_1))
+        || !TEST_mem_eq(&h->frame.new_conn_id.stateless_reset_token,
+                        sizeof(reset_token_1),
+                        reset_token_1,
+                        sizeof(reset_token_1)))
+        return 0;
+
+    return 1;
+}
+
+static const struct script_op script_4[] = {
+    OP_PROVIDE_SECRET(QUIC_ENC_LEVEL_1RTT, QRL_SUITE_AES128GCM, secret_1)
+    OP_TXP_GENERATE_NONE(TX_PACKETISER_ARCHETYPE_NORMAL)
+    OP_CHECK(schedule_cfq_new_conn_id)
+    OP_TXP_GENERATE(TX_PACKETISER_ARCHETYPE_NORMAL)
+    OP_RX_PKT()
+    OP_EXPECT_DGRAM_LEN(21, 128)
+    OP_NEXT_FRAME()
+    OP_EXPECT_FRAME(OSSL_QUIC_FRAME_TYPE_NEW_CONN_ID)
+    OP_CHECK(check_cfq_new_conn_id)
+    OP_EXPECT_NO_FRAME()
+    OP_RX_PKT_NONE()
+    OP_TXP_GENERATE_NONE(TX_PACKETISER_ARCHETYPE_NORMAL)
+    OP_END
+};
+
+/* 5. 1-RTT, CFQ (NEW_TOKEN) */
+static const unsigned char token_1[] = {
+    0x10, 0x11, 0x12, 0x13, 0x14, 0x15
+};
+
+static int schedule_cfq_new_token(struct helper *h)
+{
+    int rc = 0;
+    QUIC_CFQ_ITEM *cfq_item;
+    WPACKET wpkt;
+    BUF_MEM *buf_mem = NULL;
+    char have_wpkt = 0;
+    size_t l = 0;
+
+    if (!TEST_ptr(buf_mem = BUF_MEM_new()))
+        goto err;
+
+    if (!TEST_true(WPACKET_init(&wpkt, buf_mem)))
+        goto err;
+
+    have_wpkt = 1;
+    if (!TEST_true(ossl_quic_wire_encode_frame_new_token(&wpkt, token_1,
+                                                         sizeof(token_1))))
+        goto err;
+
+    if (!TEST_true(WPACKET_get_total_written(&wpkt, &l)))
+        goto err;
+
+    if (!TEST_ptr(cfq_item = ossl_quic_cfq_add_frame(h->args.cfq, 1,
+                                                     QUIC_PN_SPACE_APP,
+                                                     OSSL_QUIC_FRAME_TYPE_NEW_TOKEN,
+                                                     (unsigned char *)buf_mem->data, l,
+                                                     free_buf_mem,
+                                                     buf_mem)))
+        goto err;
+
+    rc = 1;
+err:
+    if (have_wpkt)
+        WPACKET_cleanup(&wpkt);
+    return rc;
+}
+
+static int check_cfq_new_token(struct helper *h)
+{
+    if (!TEST_mem_eq(h->frame.new_token.token,
+                     h->frame.new_token.token_len,
+                     token_1,
+                     sizeof(token_1)))
+        return 0;
+
+    return 1;
+}
+
+static const struct script_op script_5[] = {
+    OP_PROVIDE_SECRET(QUIC_ENC_LEVEL_1RTT, QRL_SUITE_AES128GCM, secret_1)
+    OP_TXP_GENERATE_NONE(TX_PACKETISER_ARCHETYPE_NORMAL)
+    OP_CHECK(schedule_cfq_new_token)
+    OP_TXP_GENERATE(TX_PACKETISER_ARCHETYPE_NORMAL)
+    OP_RX_PKT()
+    OP_EXPECT_DGRAM_LEN(21, 512)
+    OP_NEXT_FRAME()
+    OP_EXPECT_FRAME(OSSL_QUIC_FRAME_TYPE_NEW_TOKEN)
+    OP_CHECK(check_cfq_new_token)
+    OP_EXPECT_NO_FRAME()
+    OP_RX_PKT_NONE()
+    OP_TXP_GENERATE_NONE(TX_PACKETISER_ARCHETYPE_NORMAL)
+    OP_END
+};
+
+/* 6. 1-RTT, ACK */
+static int schedule_ack(struct helper *h)
+{
+    size_t i;
+    OSSL_ACKM_RX_PKT rx_pkt = {0};
+
+    /* Stimulate ACK emission by simulating a few received packets. */
+    for (i = 0; i < 5; ++i) {
+        rx_pkt.pkt_num          = i;
+        rx_pkt.time             = fake_now(NULL);
+        rx_pkt.pkt_space        = QUIC_PN_SPACE_APP;
+        rx_pkt.is_ack_eliciting = 1;
+
+        if (!TEST_true(ossl_ackm_on_rx_packet(h->args.ackm, &rx_pkt)))
+            return 0;
+    }
+
+    return 1;
+}
+
+static const struct script_op script_6[] = {
+    OP_PROVIDE_SECRET(QUIC_ENC_LEVEL_1RTT, QRL_SUITE_AES128GCM, secret_1)
+    OP_TXP_GENERATE_NONE(TX_PACKETISER_ARCHETYPE_NORMAL)
+    OP_CHECK(schedule_ack)
+    OP_TXP_GENERATE(TX_PACKETISER_ARCHETYPE_NORMAL)
+    OP_RX_PKT()
+    OP_EXPECT_DGRAM_LEN(21, 512)
+    OP_NEXT_FRAME()
+    OP_EXPECT_FRAME(OSSL_QUIC_FRAME_TYPE_ACK_WITHOUT_ECN)
+    OP_EXPECT_NO_FRAME()
+    OP_RX_PKT_NONE()
+    OP_TXP_GENERATE_NONE(TX_PACKETISER_ARCHETYPE_NORMAL)
+    OP_END
+};
+
+/* 7. 1-RTT, ACK, NEW_TOKEN */
+static const struct script_op script_7[] = {
+    OP_PROVIDE_SECRET(QUIC_ENC_LEVEL_1RTT, QRL_SUITE_AES128GCM, secret_1)
+    OP_TXP_GENERATE_NONE(TX_PACKETISER_ARCHETYPE_NORMAL)
+    OP_CHECK(schedule_cfq_new_token)
+    OP_CHECK(schedule_ack)
+    OP_TXP_GENERATE(TX_PACKETISER_ARCHETYPE_NORMAL)
+    OP_RX_PKT()
+    OP_EXPECT_DGRAM_LEN(21, 512)
+    /* ACK must come before NEW_TOKEN */
+    OP_NEXT_FRAME()
+    OP_EXPECT_FRAME(OSSL_QUIC_FRAME_TYPE_ACK_WITHOUT_ECN)
+    OP_NEXT_FRAME()
+    OP_EXPECT_FRAME(OSSL_QUIC_FRAME_TYPE_NEW_TOKEN)
+    OP_EXPECT_NO_FRAME()
+    OP_RX_PKT_NONE()
+    OP_TXP_GENERATE_NONE(TX_PACKETISER_ARCHETYPE_NORMAL)
+    OP_END
+};
+
+/* 8. 1-RTT, CRYPTO */
+static const unsigned char crypto_1[] = {
+    0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09
+};
+
+static const struct script_op script_8[] = {
+    OP_PROVIDE_SECRET(QUIC_ENC_LEVEL_1RTT, QRL_SUITE_AES128GCM, secret_1)
+    OP_TXP_GENERATE_NONE(TX_PACKETISER_ARCHETYPE_NORMAL)
+    OP_CRYPTO_SEND(QUIC_PN_SPACE_APP, crypto_1)
+    OP_TXP_GENERATE(TX_PACKETISER_ARCHETYPE_NORMAL)
+    OP_RX_PKT()
+    OP_EXPECT_DGRAM_LEN(21, 512)
+    OP_NEXT_FRAME()
+    OP_EXPECT_FRAME(OSSL_QUIC_FRAME_TYPE_CRYPTO)
+    OP_EXPECT_NO_FRAME()
+    OP_RX_PKT_NONE()
+    OP_TXP_GENERATE_NONE(TX_PACKETISER_ARCHETYPE_NORMAL)
+    OP_END
+};
+
+/* 9. 1-RTT, STREAM */
+static const unsigned char stream_9[] = {
+    0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x7a, 0x7b
+};
+
+static int check_stream_9(struct helper *h)
+{
+    if (!TEST_mem_eq(h->frame.stream.data, (size_t)h->frame.stream.len,
+                     stream_9, sizeof(stream_9)))
+        return 0;
+
+    return 1;
+}
+
+static const struct script_op script_9[] = {
+    OP_PROVIDE_SECRET(QUIC_ENC_LEVEL_1RTT, QRL_SUITE_AES128GCM, secret_1)
+    OP_TXP_GENERATE_NONE(TX_PACKETISER_ARCHETYPE_NORMAL)
+    OP_STREAM_NEW(42)
+    OP_STREAM_SEND(42, stream_9)
+    /* Still no output because of TXFC */
+    OP_TXP_GENERATE_NONE(TX_PACKETISER_ARCHETYPE_NORMAL)
+    /* Now grant a TXFC budget */
+    OP_CONN_TXFC_BUMP(1000)
+    OP_STREAM_TXFC_BUMP(42, 1000)
+    OP_TXP_GENERATE(TX_PACKETISER_ARCHETYPE_NORMAL)
+    OP_RX_PKT()
+    OP_EXPECT_DGRAM_LEN(21, 512)
+    OP_NEXT_FRAME()
+    OP_EXPECT_FRAME(OSSL_QUIC_FRAME_TYPE_STREAM)
+    OP_CHECK(check_stream_9)
+    OP_EXPECT_NO_FRAME()
+    OP_RX_PKT_NONE()
+    OP_TXP_GENERATE_NONE(TX_PACKETISER_ARCHETYPE_NORMAL)
+    OP_END
+};
+
+/* 10. 1-RTT, STREAM, round robin */
+/* The data below is randomly generated data. */
+static const unsigned char stream_10a[1300] = {
+    0x40, 0x0d, 0xb6, 0x0d, 0x25, 0x5f, 0xdd, 0xb9, 0x05, 0x79, 0xa8, 0xe3,
+    0x79, 0x32, 0xb2, 0xa7, 0x30, 0x6d, 0x29, 0xf6, 0xba, 0x50, 0xbe, 0x83,
+    0xcb, 0x56, 0xec, 0xd6, 0xc7, 0x80, 0x84, 0xa2, 0x2f, 0xeb, 0xc4, 0x37,
+    0x40, 0x44, 0xef, 0xd8, 0x78, 0xbb, 0x92, 0x80, 0x22, 0x33, 0xc0, 0xce,
+    0x33, 0x5b, 0x75, 0x8c, 0xa5, 0x1a, 0x7a, 0x2a, 0xa9, 0x88, 0xaf, 0xf6,
+    0x3a, 0xe2, 0x5e, 0x60, 0x52, 0x6d, 0xef, 0x7f, 0x2a, 0x9a, 0xaa, 0x17,
+    0x0e, 0x12, 0x51, 0x82, 0x08, 0x2f, 0x0f, 0x5b, 0xff, 0xf5, 0x7c, 0x7c,
+    0x89, 0x04, 0xfb, 0xa7, 0x80, 0x4e, 0xda, 0x12, 0x89, 0x01, 0x4a, 0x81,
+    0x84, 0x78, 0x15, 0xa9, 0x12, 0x28, 0x69, 0x4a, 0x25, 0xe5, 0x8b, 0x69,
+    0xc2, 0x9f, 0xb6, 0x59, 0x49, 0xe3, 0x53, 0x90, 0xef, 0xc9, 0xb8, 0x40,
+    0xdd, 0x62, 0x5f, 0x99, 0x68, 0xd2, 0x0a, 0x77, 0xde, 0xf3, 0x11, 0x39,
+    0x7f, 0x93, 0x8b, 0x81, 0x69, 0x36, 0xa7, 0x76, 0xa4, 0x10, 0x56, 0x51,
+    0xe5, 0x45, 0x3a, 0x42, 0x49, 0x6c, 0xc6, 0xa0, 0xb4, 0x13, 0x46, 0x59,
+    0x0e, 0x48, 0x60, 0xc9, 0xff, 0x70, 0x10, 0x8d, 0x6a, 0xf9, 0x5b, 0x94,
+    0xc2, 0x9e, 0x49, 0x19, 0x56, 0xf2, 0xc1, 0xff, 0x08, 0x3f, 0x9e, 0x26,
+    0x8e, 0x99, 0x71, 0xc4, 0x25, 0xb1, 0x4e, 0xcc, 0x7e, 0x5f, 0xf0, 0x4e,
+    0x25, 0xa2, 0x2f, 0x3f, 0x68, 0xaa, 0xcf, 0xbd, 0x19, 0x19, 0x1c, 0x92,
+    0xa0, 0xb6, 0xb8, 0x32, 0xb1, 0x0b, 0x91, 0x05, 0xa9, 0xf8, 0x1a, 0x4b,
+    0x74, 0x09, 0xf9, 0x57, 0xd0, 0x1c, 0x38, 0x10, 0x05, 0x54, 0xd8, 0x4e,
+    0x12, 0x67, 0xcc, 0x43, 0xa3, 0x81, 0xa9, 0x3a, 0x12, 0x57, 0xe7, 0x4b,
+    0x0e, 0xe5, 0x51, 0xf9, 0x5f, 0xd4, 0x46, 0x73, 0xa2, 0x78, 0xb7, 0x00,
+    0x24, 0x69, 0x35, 0x10, 0x1e, 0xb8, 0xa7, 0x4a, 0x9b, 0xbc, 0xfc, 0x04,
+    0x6f, 0x1a, 0xb0, 0x4f, 0x12, 0xc9, 0x2b, 0x3b, 0x94, 0x85, 0x1b, 0x8e,
+    0xba, 0xac, 0xfd, 0x10, 0x22, 0x68, 0x90, 0x17, 0x13, 0x44, 0x18, 0x2f,
+    0x33, 0x37, 0x1a, 0x89, 0xc0, 0x2c, 0x14, 0x59, 0xb2, 0xaf, 0xc0, 0x6b,
+    0xdc, 0x28, 0xe1, 0xe9, 0xc1, 0x0c, 0xb4, 0x80, 0x90, 0xb9, 0x1f, 0x45,
+    0xb4, 0x63, 0x9a, 0x0e, 0xfa, 0x33, 0xf5, 0x75, 0x3a, 0x4f, 0xc3, 0x8c,
+    0x70, 0xdb, 0xd7, 0xbf, 0xf6, 0xb8, 0x7f, 0xcc, 0xe5, 0x85, 0xb6, 0xae,
+    0x25, 0x60, 0x18, 0x5b, 0xf1, 0x51, 0x1a, 0x85, 0xc1, 0x7f, 0xf3, 0xbe,
+    0xb6, 0x82, 0x38, 0xe3, 0xd2, 0xff, 0x8a, 0xc4, 0xdb, 0x08, 0xe6, 0x96,
+    0xd5, 0x3d, 0x1f, 0xc5, 0x12, 0x35, 0x45, 0x75, 0x5d, 0x17, 0x4e, 0xe1,
+    0xb8, 0xc9, 0xf0, 0x45, 0x95, 0x0b, 0x03, 0xcb, 0x85, 0x47, 0xaf, 0xc7,
+    0x88, 0xb6, 0xc1, 0x2c, 0xb8, 0x9b, 0xe6, 0x8b, 0x51, 0xd5, 0x2e, 0x71,
+    0xba, 0xc9, 0xa9, 0x37, 0x5e, 0x1c, 0x2c, 0x03, 0xf0, 0xc7, 0xc1, 0xd3,
+    0x72, 0xaa, 0x4d, 0x19, 0xd6, 0x51, 0x64, 0x12, 0xeb, 0x39, 0xeb, 0x45,
+    0xe9, 0xb4, 0x84, 0x08, 0xb6, 0x6c, 0xc7, 0x3e, 0xf0, 0x88, 0x64, 0xc2,
+    0x91, 0xb7, 0xa5, 0x86, 0x66, 0x83, 0xd5, 0xd3, 0x41, 0x24, 0xb2, 0x1c,
+    0x9a, 0x18, 0x10, 0x0e, 0xa5, 0xc9, 0xef, 0xcd, 0x06, 0xce, 0xa8, 0xaf,
+    0x22, 0x52, 0x25, 0x0b, 0x99, 0x3d, 0xe9, 0x26, 0xda, 0xa9, 0x47, 0xd1,
+    0x4b, 0xa6, 0x4c, 0xfc, 0x80, 0xaf, 0x6a, 0x59, 0x4b, 0x35, 0xa4, 0x93,
+    0x39, 0x5b, 0xfa, 0x91, 0x9d, 0xdf, 0x9d, 0x3c, 0xfb, 0x53, 0xca, 0x18,
+    0x19, 0xe4, 0xda, 0x95, 0x47, 0x5a, 0x37, 0x59, 0xd7, 0xd2, 0xe4, 0x75,
+    0x45, 0x0d, 0x03, 0x7f, 0xa0, 0xa9, 0xa0, 0x71, 0x06, 0xb1, 0x9d, 0x46,
+    0xbd, 0xcf, 0x4a, 0x8b, 0x73, 0xc1, 0x45, 0x5c, 0x00, 0x61, 0xfd, 0xd1,
+    0xa4, 0xa2, 0x3e, 0xaa, 0xbe, 0x72, 0xf1, 0x7a, 0x1a, 0x76, 0x88, 0x5c,
+    0x9e, 0x74, 0x6d, 0x2a, 0x34, 0xfc, 0xf7, 0x41, 0x28, 0xe8, 0xa3, 0x43,
+    0x4d, 0x43, 0x1d, 0x6c, 0x36, 0xb1, 0x45, 0x71, 0x5a, 0x3c, 0xd3, 0x28,
+    0x44, 0xe4, 0x9b, 0xbf, 0x54, 0x16, 0xc3, 0x99, 0x6c, 0x42, 0xd8, 0x20,
+    0xb6, 0x20, 0x5f, 0x6e, 0xbc, 0xba, 0x88, 0x5e, 0x2f, 0xa5, 0xd1, 0x82,
+    0x5c, 0x92, 0xd0, 0x79, 0xfd, 0xcc, 0x61, 0x49, 0xd0, 0x73, 0x92, 0xe6,
+    0x98, 0xe3, 0x80, 0x7a, 0xf9, 0x56, 0x63, 0x33, 0x19, 0xda, 0x54, 0x13,
+    0xf0, 0x21, 0xa8, 0x15, 0xf6, 0xb7, 0x43, 0x7c, 0x1c, 0x1e, 0xb1, 0x89,
+    0x8d, 0xce, 0x20, 0x54, 0x81, 0x80, 0xb5, 0x8f, 0x9b, 0xb1, 0x09, 0x92,
+    0xdb, 0x25, 0x6f, 0x30, 0x29, 0x08, 0x1a, 0x05, 0x08, 0xf4, 0x83, 0x8b,
+    0x1e, 0x2d, 0xfd, 0xe4, 0xb2, 0x76, 0xc8, 0x4d, 0xf3, 0xa6, 0x49, 0x5f,
+    0x2c, 0x99, 0x78, 0xbd, 0x07, 0xef, 0xc8, 0xd9, 0xb5, 0x70, 0x3b, 0x0a,
+    0xcb, 0xbd, 0xa0, 0xea, 0x15, 0xfb, 0xd1, 0x6e, 0x61, 0x83, 0xcb, 0x90,
+    0xd0, 0xa3, 0x81, 0x28, 0xdc, 0xd5, 0x84, 0xae, 0x55, 0x28, 0x13, 0x9e,
+    0xc6, 0xd8, 0xf4, 0x67, 0xd6, 0x0d, 0xd4, 0x69, 0xac, 0xf6, 0x35, 0x95,
+    0x99, 0x44, 0x26, 0x72, 0x36, 0x55, 0xf9, 0x42, 0xa6, 0x1b, 0x00, 0x93,
+    0x00, 0x19, 0x2f, 0x70, 0xd3, 0x16, 0x66, 0x4e, 0x80, 0xbb, 0xb6, 0x84,
+    0xa1, 0x2c, 0x09, 0xfb, 0x41, 0xdf, 0x63, 0xde, 0x62, 0x3e, 0xd0, 0xa8,
+    0xd8, 0x0c, 0x03, 0x06, 0xa9, 0x82, 0x17, 0x9c, 0xd2, 0xa9, 0xd5, 0x6f,
+    0xcc, 0xc0, 0xf2, 0x5d, 0xb1, 0xba, 0xf8, 0x2e, 0x37, 0x8b, 0xe6, 0x5d,
+    0x9f, 0x1b, 0xfb, 0x53, 0x0a, 0x96, 0xbe, 0x69, 0x31, 0x19, 0x8f, 0x44,
+    0x1b, 0xc2, 0x42, 0x7e, 0x65, 0x12, 0x1d, 0x52, 0x1e, 0xe2, 0xc0, 0x86,
+    0x70, 0x88, 0xe5, 0xf6, 0x87, 0x5d, 0x03, 0x4b, 0x12, 0x3c, 0x2d, 0xaf,
+    0x09, 0xf5, 0x4f, 0x82, 0x2e, 0x2e, 0xbe, 0x07, 0xe8, 0x8d, 0x57, 0x6e,
+    0xc0, 0xeb, 0xf9, 0x37, 0xac, 0x89, 0x01, 0xb7, 0xc6, 0x52, 0x1c, 0x86,
+    0xe5, 0xbc, 0x1f, 0xbd, 0xde, 0xa2, 0x42, 0xb6, 0x73, 0x85, 0x6f, 0x06,
+    0x36, 0x56, 0x40, 0x2b, 0xea, 0x16, 0x8c, 0xf4, 0x7b, 0x65, 0x6a, 0xca,
+    0x3c, 0x56, 0x68, 0x01, 0xe3, 0x9c, 0xbb, 0xb9, 0x45, 0x54, 0xcd, 0x13,
+    0x74, 0xad, 0x80, 0x40, 0xbc, 0xd0, 0x74, 0xb4, 0x31, 0xe4, 0xca, 0xd5,
+    0xf8, 0x4f, 0x08, 0x5b, 0xc4, 0x15, 0x1a, 0x51, 0x3b, 0xc6, 0x40, 0xc8,
+    0xea, 0x76, 0x30, 0x95, 0xb7, 0x76, 0xa4, 0xda, 0x20, 0xdb, 0x75, 0x1c,
+    0xf4, 0x87, 0x24, 0x29, 0x54, 0xc6, 0x59, 0x0c, 0xf0, 0xed, 0xf5, 0x3d,
+    0xce, 0x95, 0x23, 0x30, 0x49, 0x91, 0xa7, 0x7b, 0x22, 0xb5, 0xd7, 0x71,
+    0xb0, 0x60, 0xe1, 0xf0, 0x84, 0x74, 0x0e, 0x2f, 0xa8, 0x79, 0x35, 0xb9,
+    0x03, 0xb5, 0x2c, 0xdc, 0x60, 0x48, 0x12, 0xd9, 0x14, 0x5a, 0x58, 0x5d,
+    0x95, 0xc6, 0x47, 0xfd, 0xaf, 0x09, 0xc2, 0x67, 0xa5, 0x09, 0xae, 0xff,
+    0x4b, 0xd5, 0x6c, 0x2f, 0x1d, 0x33, 0x31, 0xcb, 0xdb, 0xcf, 0xf5, 0xf6,
+    0xbc, 0x90, 0xb2, 0x15, 0xd4, 0x34, 0xeb, 0xde, 0x0e, 0x8f, 0x3d, 0xea,
+    0xa4, 0x9b, 0x29, 0x8a, 0xf9, 0x4a, 0xac, 0x38, 0x1e, 0x46, 0xb2, 0x2d,
+    0xa2, 0x61, 0xc5, 0x99, 0x5e, 0x85, 0x36, 0x85, 0xb0, 0xb1, 0x6b, 0xc4,
+    0x06, 0x68, 0xc7, 0x9b, 0x54, 0xb9, 0xc8, 0x9d, 0xf3, 0x1a, 0xe0, 0x67,
+    0x0e, 0x4d, 0x5c, 0x13, 0x54, 0xa4, 0x62, 0x62, 0x6f, 0xae, 0x0e, 0x86,
+    0xa2, 0xe0, 0x31, 0xc7, 0x72, 0xa1, 0xbb, 0x87, 0x3e, 0x61, 0x96, 0xb7,
+    0x53, 0xf9, 0x34, 0xcb, 0xfd, 0x6c, 0x67, 0x25, 0x73, 0x61, 0x75, 0x4f,
+    0xab, 0x37, 0x08, 0xef, 0x35, 0x5a, 0x03, 0xe5, 0x08, 0x43, 0xec, 0xdc,
+    0xb5, 0x2c, 0x1f, 0xe6, 0xeb, 0xc6, 0x06, 0x0b, 0xed, 0xad, 0x74, 0xf4,
+    0x55, 0xef, 0xe0, 0x2e, 0x83, 0x00, 0xdb, 0x32, 0xde, 0xe9, 0xe4, 0x2f,
+    0xf5, 0x20, 0x6d, 0x72, 0x47, 0xf4, 0x68, 0xa6, 0x7f, 0x3e, 0x6a, 0x5a,
+    0x21, 0x76, 0x31, 0x97, 0xa0, 0xc6, 0x7d, 0x03, 0xf7, 0x27, 0x45, 0x5a,
+    0x75, 0x03, 0xc1, 0x5c, 0x94, 0x2b, 0x37, 0x9f, 0x46, 0x8f, 0xc3, 0xa7,
+    0x50, 0xe4, 0xe7, 0x23, 0xf7, 0x20, 0xa2, 0x8e, 0x4b, 0xfd, 0x7a, 0xa7,
+    0x8a, 0x54, 0x7b, 0x32, 0xef, 0x0e, 0x82, 0xb9, 0xf9, 0x14, 0x62, 0x68,
+    0x32, 0x9e, 0x55, 0xc0, 0xd8, 0xc7, 0x41, 0x9c, 0x67, 0x95, 0xbf, 0xc3,
+    0x86, 0x74, 0x70, 0x64, 0x44, 0x23, 0x77, 0x79, 0x82, 0x23, 0x1c, 0xf4,
+    0xa1, 0x05, 0xd3, 0x98, 0x89, 0xde, 0x7d, 0xb3, 0x5b, 0xef, 0x38, 0xd2,
+    0x07, 0xbc, 0x5a, 0x69, 0xa3, 0xe4, 0x37, 0x9b, 0x53, 0xff, 0x04, 0x6b,
+    0xd9, 0xd8, 0x32, 0x89, 0xf7, 0x82, 0x77, 0xcf, 0xe6, 0xff, 0xf4, 0x15,
+    0x54, 0x91, 0x65, 0x96, 0x49, 0xd7, 0x0a, 0xa4, 0xf3, 0x55, 0x2b, 0xc1,
+    0x48, 0xc1, 0x7e, 0x56, 0x69, 0x27, 0xf4, 0xd1, 0x47, 0x1f, 0xde, 0x86,
+    0x15, 0x67, 0x04, 0x9d, 0x41, 0x1f, 0xe8, 0xe1, 0x23, 0xe4, 0x56, 0xb9,
+    0xdb, 0x4e, 0xe4, 0x84, 0x6c, 0x63, 0x39, 0xad, 0x44, 0x6d, 0x4e, 0x28,
+    0xcd, 0xf6, 0xac, 0xec, 0xc2, 0xad, 0xcd, 0xc3, 0xed, 0x03, 0x63, 0x5d,
+    0xef, 0x1d, 0x40, 0x8d, 0x9a, 0x02, 0x67, 0x4b, 0x55, 0xb5, 0xfe, 0x75,
+    0xb6, 0x53, 0x34, 0x1d, 0x7b, 0x26, 0x23, 0xfe, 0xb9, 0x21, 0xd3, 0xe0,
+    0xa0, 0x1a, 0x85, 0xe5
+};
+
+static const unsigned char stream_10b[1300] = {
+    0x18, 0x00, 0xd7, 0xfb, 0x12, 0xda, 0xdb, 0x68, 0xeb, 0x38, 0x4d, 0xf6,
+    0xb2, 0x45, 0x74, 0x4c, 0xcc, 0xe7, 0xa7, 0xc1, 0x26, 0x84, 0x3d, 0xdf,
+    0x7d, 0xc5, 0xe9, 0xd4, 0x31, 0xa2, 0x51, 0x38, 0x95, 0xe2, 0x68, 0x11,
+    0x9d, 0xd1, 0x52, 0xb5, 0xef, 0x76, 0xe0, 0x3d, 0x11, 0x50, 0xd7, 0xb2,
+    0xc1, 0x7d, 0x12, 0xaf, 0x02, 0x52, 0x97, 0x03, 0xf3, 0x2e, 0x54, 0xdf,
+    0xa0, 0x40, 0x76, 0x52, 0x82, 0x23, 0x3c, 0xbd, 0x20, 0x6d, 0x0a, 0x6f,
+    0x81, 0xfc, 0x41, 0x9d, 0x2e, 0xa7, 0x2c, 0x78, 0x9c, 0xd8, 0x56, 0xb0,
+    0x31, 0x35, 0xc8, 0x53, 0xef, 0xf9, 0x43, 0x17, 0xc0, 0x8c, 0x2c, 0x8f,
+    0x4a, 0x68, 0xe8, 0x9f, 0xbd, 0x3f, 0xf2, 0x18, 0xb8, 0xe6, 0x55, 0xea,
+    0x2a, 0x37, 0x3e, 0xac, 0xb0, 0x75, 0xd4, 0x75, 0x12, 0x82, 0xec, 0x21,
+    0xb9, 0xce, 0xe5, 0xc1, 0x62, 0x49, 0xd5, 0xf1, 0xca, 0xd4, 0x32, 0x76,
+    0x34, 0x5f, 0x3e, 0xc9, 0xb3, 0x54, 0xe4, 0xd0, 0xa9, 0x7d, 0x0c, 0x64,
+    0x48, 0x0a, 0x74, 0x38, 0x03, 0xd0, 0x20, 0xac, 0xe3, 0x58, 0x3d, 0x4b,
+    0xa7, 0x46, 0xac, 0x57, 0x63, 0x12, 0x17, 0xcb, 0x96, 0xed, 0xc9, 0x39,
+    0x64, 0xde, 0xff, 0xc6, 0xb2, 0x40, 0x2c, 0xf9, 0x1d, 0xa6, 0x94, 0x2a,
+    0x16, 0x4d, 0x7f, 0x22, 0x91, 0x8b, 0xfe, 0x83, 0x77, 0x02, 0x68, 0x62,
+    0x27, 0x77, 0x2e, 0xe9, 0xce, 0xbc, 0x20, 0xe8, 0xfb, 0xf8, 0x4e, 0x17,
+    0x07, 0xe1, 0xaa, 0x29, 0xb7, 0x50, 0xcf, 0xb0, 0x6a, 0xcf, 0x01, 0xec,
+    0xbf, 0xff, 0xb5, 0x9f, 0x00, 0x64, 0x80, 0xbb, 0xa6, 0xe4, 0xa2, 0x1e,
+    0xe4, 0xf8, 0xa3, 0x0d, 0xc7, 0x65, 0x45, 0xb7, 0x01, 0x33, 0x80, 0x37,
+    0x11, 0x16, 0x34, 0xc1, 0x06, 0xc5, 0xd3, 0xc4, 0x70, 0x62, 0x75, 0xd8,
+    0xa3, 0xba, 0x84, 0x9f, 0x81, 0x9f, 0xda, 0x01, 0x83, 0x42, 0x84, 0x05,
+    0x69, 0x68, 0xb0, 0x74, 0x73, 0x0f, 0x68, 0x39, 0xd3, 0x11, 0xc5, 0x55,
+    0x3e, 0xf2, 0xb7, 0xf4, 0xa6, 0xed, 0x0b, 0x50, 0xbe, 0x44, 0xf8, 0x67,
+    0x48, 0x46, 0x5e, 0x71, 0x07, 0xcf, 0xca, 0x8a, 0xbc, 0xa4, 0x3c, 0xd2,
+    0x4a, 0x80, 0x2e, 0x4f, 0xc5, 0x3b, 0x61, 0xc1, 0x7e, 0x93, 0x9e, 0xe0,
+    0x05, 0xfb, 0x10, 0xe8, 0x53, 0xff, 0x16, 0x5e, 0x18, 0xe0, 0x9f, 0x39,
+    0xbf, 0xaa, 0x80, 0x6d, 0xb7, 0x9f, 0x51, 0x91, 0xa0, 0xf6, 0xce, 0xad,
+    0xed, 0x56, 0x15, 0xb9, 0x12, 0x57, 0x60, 0xa6, 0xae, 0x54, 0x6e, 0x36,
+    0xf3, 0xe0, 0x05, 0xd8, 0x3e, 0x6d, 0x08, 0x36, 0xc9, 0x79, 0x64, 0x51,
+    0x63, 0x92, 0xa8, 0xa1, 0xbf, 0x55, 0x26, 0x80, 0x75, 0x44, 0x33, 0x33,
+    0xfb, 0xb7, 0xec, 0xf9, 0xc6, 0x01, 0xf9, 0xd5, 0x93, 0xfc, 0xb7, 0x43,
+    0xa2, 0x38, 0x0d, 0x17, 0x75, 0x67, 0xec, 0xc9, 0x98, 0xd6, 0x25, 0xe6,
+    0xb9, 0xed, 0x61, 0xa4, 0xee, 0x2c, 0xda, 0x27, 0xbd, 0xff, 0x86, 0x1e,
+    0x45, 0x64, 0xfe, 0xcf, 0x0c, 0x9b, 0x7b, 0x75, 0x5f, 0xf1, 0xe0, 0xba,
+    0x77, 0x8c, 0x03, 0x8f, 0xb4, 0x3a, 0xb6, 0x9c, 0xda, 0x9a, 0x83, 0xcb,
+    0xe9, 0xcb, 0x3f, 0xf4, 0x10, 0x99, 0x5b, 0xe1, 0x19, 0x8f, 0x6b, 0x95,
+    0x50, 0xe6, 0x78, 0xc9, 0x35, 0xb6, 0x87, 0xd8, 0x9e, 0x17, 0x30, 0x96,
+    0x70, 0xa3, 0x04, 0x69, 0x1c, 0xa2, 0x6c, 0xd4, 0x88, 0x48, 0x44, 0x14,
+    0x94, 0xd4, 0xc9, 0x4d, 0xe3, 0x82, 0x7e, 0x62, 0xf0, 0x0a, 0x18, 0x4d,
+    0xd0, 0xd6, 0x63, 0xa3, 0xdf, 0xea, 0x28, 0xf4, 0x00, 0x75, 0x70, 0x78,
+    0x08, 0x70, 0x3f, 0xff, 0x84, 0x86, 0x72, 0xea, 0x4f, 0x15, 0x8c, 0x17,
+    0x60, 0x5f, 0xa1, 0x50, 0xa0, 0xfc, 0x6f, 0x8a, 0x46, 0xfc, 0x01, 0x8d,
+    0x7c, 0xdc, 0x69, 0x6a, 0xd3, 0x74, 0x69, 0x76, 0x77, 0xdd, 0xe4, 0x9c,
+    0x49, 0x1e, 0x6f, 0x7d, 0x31, 0x14, 0xd9, 0xe9, 0xe7, 0x17, 0x66, 0x82,
+    0x1b, 0xf1, 0x0f, 0xe2, 0xba, 0xd2, 0x28, 0xd1, 0x6f, 0x48, 0xc7, 0xac,
+    0x08, 0x4e, 0xee, 0x94, 0x66, 0x99, 0x34, 0x16, 0x5d, 0x95, 0xae, 0xe3,
+    0x59, 0x79, 0x7f, 0x8e, 0x9f, 0xe3, 0xdb, 0xff, 0xdc, 0x4d, 0xb0, 0xbf,
+    0xf9, 0xf3, 0x3e, 0xec, 0xcf, 0x50, 0x3d, 0x2d, 0xba, 0x94, 0x1f, 0x1a,
+    0xab, 0xa4, 0xf4, 0x67, 0x43, 0x7e, 0xb9, 0x65, 0x20, 0x13, 0xb1, 0xd9,
+    0x88, 0x4a, 0x24, 0x13, 0x84, 0x86, 0xae, 0x2b, 0x0c, 0x6c, 0x7e, 0xd4,
+    0x25, 0x6e, 0xaa, 0x8d, 0x0c, 0x54, 0x99, 0xde, 0x1d, 0xac, 0x8c, 0x5c,
+    0x73, 0x94, 0xd9, 0x75, 0xcb, 0x5a, 0x54, 0x3d, 0xeb, 0xff, 0xc1, 0x95,
+    0x53, 0xb5, 0x39, 0xf7, 0xe5, 0xf1, 0x77, 0xd1, 0x42, 0x82, 0x4b, 0xb0,
+    0xab, 0x19, 0x28, 0xff, 0x53, 0x28, 0x87, 0x46, 0xc6, 0x6f, 0x05, 0x06,
+    0xa6, 0x0c, 0x97, 0x93, 0x68, 0x38, 0xe1, 0x61, 0xed, 0xf8, 0x90, 0x13,
+    0xa3, 0x6f, 0xf2, 0x08, 0x37, 0xd7, 0x05, 0x25, 0x34, 0x43, 0x57, 0x72,
+    0xfd, 0x6c, 0xc2, 0x19, 0x26, 0xe7, 0x50, 0x30, 0xb8, 0x6d, 0x09, 0x71,
+    0x83, 0x75, 0xd4, 0x11, 0x25, 0x29, 0xc6, 0xee, 0xb2, 0x51, 0x1c, 0x1c,
+    0x9e, 0x2d, 0x09, 0xb9, 0x73, 0x2b, 0xbf, 0xda, 0xc8, 0x1e, 0x2b, 0xe5,
+    0x3f, 0x1e, 0x63, 0xe9, 0xc0, 0x6d, 0x04, 0x3a, 0x48, 0x61, 0xa8, 0xc6,
+    0x16, 0x8d, 0x69, 0xc0, 0x67, 0x0c, 0x3b, 0xc4, 0x05, 0x36, 0xa1, 0x30,
+    0x62, 0x92, 0x4d, 0x44, 0x31, 0x66, 0x46, 0xda, 0xef, 0x0f, 0x4e, 0xfb,
+    0x78, 0x6a, 0xa9, 0x5b, 0xf8, 0x56, 0x26, 0x74, 0x16, 0xab, 0x17, 0x93,
+    0x3c, 0x36, 0xbb, 0xa2, 0xbf, 0xad, 0xba, 0xb1, 0xfe, 0xc4, 0x9f, 0x75,
+    0x47, 0x1e, 0x99, 0x7e, 0x32, 0xe8, 0xd4, 0x6c, 0xa4, 0xf8, 0xd2, 0xe4,
+    0xb2, 0x51, 0xbb, 0xb2, 0xd7, 0xce, 0x94, 0xaf, 0x7f, 0xe6, 0x2c, 0x13,
+    0xae, 0xd2, 0x29, 0x30, 0x7b, 0xfd, 0x25, 0x61, 0xf9, 0xe8, 0x35, 0x2d,
+    0x1a, 0xc9, 0x81, 0xa5, 0xfe, 0xce, 0xf6, 0x17, 0xc5, 0xfb, 0x8c, 0x79,
+    0x67, 0xa8, 0x5f, 0x5c, 0x31, 0xbc, 0xfc, 0xf3, 0x6b, 0xd3, 0x0d, 0xe0,
+    0x62, 0xab, 0x86, 0xc3, 0x17, 0x5a, 0xba, 0x97, 0x86, 0x8f, 0x65, 0xd6,
+    0xbd, 0x0c, 0xa1, 0xfb, 0x7f, 0x7c, 0xdc, 0xcb, 0x94, 0x30, 0x0b, 0x04,
+    0x54, 0xc4, 0x31, 0xa1, 0xca, 0x1e, 0xc5, 0xf0, 0xb6, 0x08, 0xd7, 0x2e,
+    0xa1, 0x90, 0x41, 0xce, 0xd9, 0xef, 0x3a, 0x58, 0x01, 0x1a, 0x73, 0x18,
+    0xad, 0xdc, 0x20, 0x25, 0x95, 0x1a, 0xfe, 0x61, 0xf1, 0x58, 0x32, 0x8b,
+    0x43, 0x59, 0xd6, 0x21, 0xdb, 0xa9, 0x8e, 0x54, 0xe6, 0x21, 0xcf, 0xd3,
+    0x6b, 0x59, 0x29, 0x9b, 0x3e, 0x6c, 0x7f, 0xe2, 0x29, 0x72, 0x8c, 0xd1,
+    0x3e, 0x9a, 0x84, 0x98, 0xb0, 0xf3, 0x20, 0x30, 0x34, 0x71, 0xa7, 0x5b,
+    0xf0, 0x26, 0xe1, 0xf4, 0x76, 0x65, 0xc9, 0xd7, 0xe4, 0xb9, 0x25, 0x48,
+    0xc2, 0x7e, 0xa6, 0x0b, 0x0d, 0x05, 0x68, 0xa1, 0x96, 0x61, 0x0b, 0x4c,
+    0x2f, 0x1a, 0xe3, 0x56, 0x71, 0x89, 0x48, 0x66, 0xd8, 0xd0, 0x69, 0x37,
+    0x7a, 0xdf, 0xdb, 0xed, 0xad, 0x82, 0xaa, 0x40, 0x25, 0x47, 0x3e, 0x75,
+    0xa6, 0x0e, 0xf5, 0x2f, 0xa7, 0x4e, 0x97, 0xa2, 0x5f, 0x01, 0x99, 0x48,
+    0x3a, 0x63, 0x18, 0x20, 0x61, 0x72, 0xe4, 0xcf, 0x4b, 0x3b, 0x99, 0x36,
+    0xe1, 0xf3, 0xbf, 0xae, 0x2b, 0x6b, 0xa1, 0x94, 0xa0, 0x15, 0x94, 0xd6,
+    0xe0, 0xba, 0x71, 0xa2, 0x85, 0xa0, 0x8c, 0x5e, 0x58, 0xe2, 0xde, 0x6b,
+    0x08, 0x68, 0x90, 0x82, 0x71, 0x8d, 0xfd, 0x12, 0xa2, 0x49, 0x87, 0x70,
+    0xee, 0x2a, 0x08, 0xe2, 0x26, 0xaf, 0xeb, 0x85, 0x35, 0xd2, 0x0e, 0xfd,
+    0x2b, 0x6f, 0xc0, 0xfe, 0x41, 0xbb, 0xd7, 0x0a, 0xa3, 0x8d, 0x8b, 0xec,
+    0x44, 0x9f, 0x46, 0x59, 0x4d, 0xac, 0x04, 0x1e, 0xde, 0x10, 0x7b, 0x17,
+    0x0a, 0xb0, 0xcc, 0x26, 0x0c, 0xa9, 0x3c, 0x5f, 0xd8, 0xe6, 0x52, 0xd3,
+    0xfd, 0x0b, 0x66, 0x75, 0x06, 0x84, 0x23, 0x64, 0x2b, 0x80, 0x68, 0xf9,
+    0xcb, 0xcd, 0x04, 0x07, 0xf7, 0xe0, 0x07, 0xb4, 0xc6, 0xa0, 0x08, 0xd0,
+    0x76, 0x16, 0x77, 0xd8, 0x48, 0xf0, 0x45, 0x4e, 0xe2, 0xf2, 0x88, 0xcd,
+    0x0f, 0xbd, 0x7d, 0xb6, 0xbe, 0x4e, 0x9e, 0x5d, 0x6c, 0x47, 0x26, 0x34,
+    0x94, 0xfb, 0xc5, 0x4f, 0x5c, 0xb5, 0xb5, 0xfc, 0x99, 0x34, 0x71, 0xe5,
+    0xe1, 0x36, 0x0c, 0xd2, 0x95, 0xb8, 0x93, 0x3c, 0x5d, 0x2d, 0x71, 0x55,
+    0x0b, 0x96, 0x4e, 0x9f, 0x07, 0x9a, 0x38, 0x9a, 0xcc, 0x24, 0xb5, 0xac,
+    0x05, 0x8b, 0x1c, 0x61, 0xd4, 0xf2, 0xdf, 0x9e, 0x11, 0xe3, 0x7d, 0x64,
+    0x2f, 0xe5, 0x13, 0xd4, 0x0a, 0xe9, 0x32, 0x26, 0xa8, 0x93, 0x21, 0x59,
+    0xf3, 0x41, 0x48, 0x0a, 0xbd, 0x59, 0x8f, 0xf8, 0x72, 0xab, 0xd3, 0x65,
+    0x8e, 0xdc, 0xaa, 0x0c, 0xc0, 0x01, 0x36, 0xb7, 0xf5, 0x84, 0x27, 0x9a,
+    0x98, 0x89, 0x73, 0x3a, 0xeb, 0x55, 0x15, 0xc9, 0x3d, 0xe1, 0xf8, 0xea,
+    0xf6, 0x11, 0x28, 0xe0, 0x80, 0x93, 0xcc, 0xba, 0xe1, 0xf1, 0x81, 0xbc,
+    0xa4, 0x30, 0xbc, 0x98, 0xe8, 0x9e, 0x8d, 0x17, 0x7e, 0xb7, 0xb1, 0x27,
+    0x6f, 0xcf, 0x9c, 0x0d, 0x1d, 0x01, 0xea, 0x45, 0xc0, 0x90, 0xda, 0x53,
+    0xf6, 0xde, 0xdf, 0x12, 0xa1, 0x23, 0x3d, 0x92, 0x89, 0x77, 0xa7, 0x2a,
+    0xe7, 0x45, 0x24, 0xdd, 0xf2, 0x17, 0x10, 0xca, 0x6e, 0x14, 0xb2, 0x77,
+    0x08, 0xc4, 0x18, 0xcd
+};
+
+static uint64_t stream_10a_off, stream_10b_off;
+
+static int check_stream_10a(struct helper *h)
+{
+    /*
+     * Must have filled or almost filled the packet (using default MDPL of
+     * 1200).
+     */
+    if (!TEST_uint64_t_ge(h->frame.stream.len, 1150)
+        || !TEST_uint64_t_le(h->frame.stream.len, 1200))
+        return 0;
+
+    if (!TEST_mem_eq(h->frame.stream.data, (size_t)h->frame.stream.len,
+                     stream_10a, (size_t)h->frame.stream.len))
+        return 0;
+
+    stream_10a_off = h->frame.stream.offset + h->frame.stream.len;
+    return 1;
+}
+
+static int check_stream_10b(struct helper *h)
+{
+    if (!TEST_uint64_t_ge(h->frame.stream.len, 1150)
+        || !TEST_uint64_t_le(h->frame.stream.len, 1200))
+        return 0;
+
+    if (!TEST_mem_eq(h->frame.stream.data, (size_t)h->frame.stream.len,
+                     stream_10b, (size_t)h->frame.stream.len))
+        return 0;
+
+    stream_10b_off = h->frame.stream.offset + h->frame.stream.len;
+    return 1;
+}
+
+static int check_stream_10c(struct helper *h)
+{
+    if (!TEST_uint64_t_ge(h->frame.stream.len, 5)
+        || !TEST_uint64_t_le(h->frame.stream.len, 200))
+        return 0;
+
+    if (!TEST_mem_eq(h->frame.stream.data, (size_t)h->frame.stream.len,
+                     stream_10a + stream_10a_off, (size_t)h->frame.stream.len))
+        return 0;
+
+    return 1;
+}
+
+static int check_stream_10d(struct helper *h)
+{
+    if (!TEST_uint64_t_ge(h->frame.stream.len, 5)
+        || !TEST_uint64_t_le(h->frame.stream.len, 200))
+        return 0;
+
+    if (!TEST_mem_eq(h->frame.stream.data, (size_t)h->frame.stream.len,
+                     stream_10b + stream_10b_off, (size_t)h->frame.stream.len))
+        return 0;
+
+    return 1;
+}
+
+static const struct script_op script_10[] = {
+    OP_PROVIDE_SECRET(QUIC_ENC_LEVEL_1RTT, QRL_SUITE_AES128GCM, secret_1)
+    OP_TXP_GENERATE_NONE(TX_PACKETISER_ARCHETYPE_NORMAL)
+    OP_STREAM_NEW(42)
+    OP_STREAM_NEW(43)
+    OP_CONN_TXFC_BUMP(10000)
+    OP_STREAM_TXFC_BUMP(42, 5000)
+    OP_STREAM_TXFC_BUMP(43, 5000)
+    OP_STREAM_SEND(42, stream_10a)
+    OP_STREAM_SEND(43, stream_10b)
+
+    /* First packet containing data from stream 42 */
+    OP_TXP_GENERATE(TX_PACKETISER_ARCHETYPE_NORMAL)
+    OP_RX_PKT()
+    OP_EXPECT_DGRAM_LEN(1100, 1200)
+    OP_NEXT_FRAME()
+    OP_EXPECT_FRAME(OSSL_QUIC_FRAME_TYPE_STREAM)
+    OP_CHECK(check_stream_10a)
+    OP_EXPECT_NO_FRAME()
+
+    /* Second packet containing data from stream 43 */
+    OP_TXP_GENERATE(TX_PACKETISER_ARCHETYPE_NORMAL)
+    OP_RX_PKT()
+    OP_EXPECT_DGRAM_LEN(1100, 1200)
+    OP_NEXT_FRAME()
+    OP_EXPECT_FRAME(OSSL_QUIC_FRAME_TYPE_STREAM)
+    OP_CHECK(check_stream_10b)
+    OP_EXPECT_NO_FRAME()
+
+    /* Third packet containing data from stream 42 */
+    OP_TXP_GENERATE(TX_PACKETISER_ARCHETYPE_NORMAL)
+    OP_RX_PKT()
+    OP_EXPECT_DGRAM_LEN(200, 500)
+    OP_NEXT_FRAME()
+    OP_EXPECT_FRAME(OSSL_QUIC_FRAME_TYPE_STREAM_OFF_LEN)
+    OP_CHECK(check_stream_10c)
+    OP_NEXT_FRAME()
+    OP_EXPECT_FRAME(OSSL_QUIC_FRAME_TYPE_STREAM_OFF)
+    OP_CHECK(check_stream_10d)
+    OP_EXPECT_NO_FRAME()
+
+    OP_RX_PKT_NONE()
+    OP_TXP_GENERATE_NONE(TX_PACKETISER_ARCHETYPE_NORMAL)
+
+    OP_END
+};
+
+/* 11. Initial, CRYPTO */
+static const struct script_op script_11[] = {
+    OP_PROVIDE_SECRET(QUIC_ENC_LEVEL_INITIAL, QRL_SUITE_AES128GCM, secret_1)
+    OP_TXP_GENERATE_NONE(TX_PACKETISER_ARCHETYPE_NORMAL)
+    OP_CRYPTO_SEND(QUIC_PN_SPACE_INITIAL, crypto_1)
+    OP_TXP_GENERATE(TX_PACKETISER_ARCHETYPE_NORMAL)
+    OP_RX_PKT()
+    OP_EXPECT_DGRAM_LEN(1200, 1200)
+    OP_NEXT_FRAME()
+    OP_EXPECT_FRAME(OSSL_QUIC_FRAME_TYPE_CRYPTO)
+    OP_EXPECT_NO_FRAME()
+    OP_RX_PKT_NONE()
+    OP_TXP_GENERATE_NONE(TX_PACKETISER_ARCHETYPE_NORMAL)
+    OP_END
+};
+
+/* 12. 1-RTT, STOP_SENDING */
+static int check_stream_12(struct helper *h)
+{
+    if (!TEST_uint64_t_eq(h->frame.stop_sending.stream_id, 42)
+        || !TEST_uint64_t_eq(h->frame.stop_sending.app_error_code, 4568))
+        return 0;
+
+    return 1;
+}
+
+static const struct script_op script_12[] = {
+    OP_PROVIDE_SECRET(QUIC_ENC_LEVEL_1RTT, QRL_SUITE_AES128GCM, secret_1)
+    OP_TXP_GENERATE_NONE(TX_PACKETISER_ARCHETYPE_NORMAL)
+    OP_STREAM_NEW(42)
+    OP_STOP_SENDING(42, 4568)
+    OP_TXP_GENERATE(TX_PACKETISER_ARCHETYPE_NORMAL)
+    OP_RX_PKT()
+    OP_EXPECT_DGRAM_LEN(21, 128)
+    OP_NEXT_FRAME()
+    OP_EXPECT_FRAME(OSSL_QUIC_FRAME_TYPE_STOP_SENDING)
+    OP_CHECK(check_stream_12)
+    OP_EXPECT_NO_FRAME()
+    OP_RX_PKT_NONE()
+    OP_TXP_GENERATE_NONE(TX_PACKETISER_ARCHETYPE_NORMAL)
+    OP_END
+};
+
+/* 13. 1-RTT, RESET_STREAM */
+static const unsigned char stream_13[] = {
+    0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x7a, 0x7b
+};
+
+static ossl_unused int check_stream_13(struct helper *h)
+{
+    if (!TEST_uint64_t_eq(h->frame.reset_stream.stream_id, 42)
+        || !TEST_uint64_t_eq(h->frame.reset_stream.app_error_code, 4568)
+        || !TEST_uint64_t_eq(h->frame.reset_stream.final_size, 8))
+        return 0;
+
+    return 1;
+}
+
+static const struct script_op script_13[] = {
+    OP_PROVIDE_SECRET(QUIC_ENC_LEVEL_1RTT, QRL_SUITE_AES128GCM, secret_1)
+    OP_TXP_GENERATE_NONE(TX_PACKETISER_ARCHETYPE_NORMAL)
+    OP_STREAM_NEW(42)
+    OP_CONN_TXFC_BUMP(8)
+    OP_STREAM_TXFC_BUMP(42, 8)
+    OP_STREAM_SEND(42, stream_13)
+    OP_RESET_STREAM(42, 4568)
+    OP_TXP_GENERATE(TX_PACKETISER_ARCHETYPE_NORMAL)
+    OP_RX_PKT()
+    OP_EXPECT_DGRAM_LEN(21, 128)
+    OP_NEXT_FRAME()
+    OP_EXPECT_FRAME(OSSL_QUIC_FRAME_TYPE_RESET_STREAM)
+    OP_CHECK(check_stream_13)
+    OP_NEXT_FRAME()
+    OP_EXPECT_FRAME(OSSL_QUIC_FRAME_TYPE_STREAM)
+    OP_EXPECT_NO_FRAME()
+    OP_RX_PKT_NONE()
+    OP_TXP_GENERATE_NONE(TX_PACKETISER_ARCHETYPE_NORMAL)
+    OP_END
+};
+
+/* 14. 1-RTT, CONNECTION_CLOSE */
+static int gen_conn_close(struct helper *h)
+{
+    OSSL_QUIC_FRAME_CONN_CLOSE f = {0};
+
+    f.error_code     = 2345;
+    f.frame_type     = OSSL_QUIC_FRAME_TYPE_HANDSHAKE_DONE;
+    f.reason         = "Reason string";
+    f.reason_len     = strlen(f.reason);
+
+    if (!TEST_true(ossl_quic_tx_packetiser_schedule_conn_close(h->txp, &f)))
+        return 0;
+
+    return 1;
+}
+
+static int check_14(struct helper *h)
+{
+    if (!TEST_int_eq(h->frame.conn_close.is_app, 0)
+        || !TEST_uint64_t_eq(h->frame.conn_close.frame_type,
+                             OSSL_QUIC_FRAME_TYPE_HANDSHAKE_DONE)
+        || !TEST_uint64_t_eq(h->frame.conn_close.error_code, 2345)
+        || !TEST_mem_eq(h->frame.conn_close.reason, h->frame.conn_close.reason_len,
+                        "Reason string", 13))
+        return 0;
+
+    return 1;
+}
+
+static const struct script_op script_14[] = {
+    OP_PROVIDE_SECRET(QUIC_ENC_LEVEL_1RTT, QRL_SUITE_AES128GCM, secret_1)
+    OP_TXP_GENERATE_NONE(TX_PACKETISER_ARCHETYPE_NORMAL)
+    OP_CHECK(gen_conn_close)
+    OP_TXP_GENERATE(TX_PACKETISER_ARCHETYPE_NORMAL)
+    OP_RX_PKT()
+    OP_EXPECT_DGRAM_LEN(21, 512)
+    OP_NEXT_FRAME()
+    OP_EXPECT_FRAME(OSSL_QUIC_FRAME_TYPE_CONN_CLOSE_TRANSPORT)
+    OP_CHECK(check_14)
+    OP_EXPECT_NO_FRAME()
+    OP_RX_PKT_NONE()
+    OP_END
+};
+
+static const struct script_op *const scripts[] = {
+    script_1,
+    script_2,
+    script_3,
+    script_4,
+    script_5,
+    script_6,
+    script_7,
+    script_8,
+    script_9,
+    script_10,
+    script_11,
+    script_12,
+    script_13,
+    script_14
+};
+
+static void skip_padding(struct helper *h)
+{
+    uint64_t frame_type;
+
+    if (!ossl_quic_wire_peek_frame_header(&h->pkt, &frame_type))
+        return; /* EOF */
+
+    if (frame_type == OSSL_QUIC_FRAME_TYPE_PADDING)
+        ossl_quic_wire_decode_padding(&h->pkt);
+}
+
+static int run_script(const struct script_op *script)
+{
+    int testresult = 0, have_helper = 0;
+    struct helper h;
+    const struct script_op *op;
+
+    if (!helper_init(&h))
+        goto err;
+
+    have_helper = 1;
+    for (op = script; op->opcode != OPK_END; ++op) {
+        switch (op->opcode) {
+        case OPK_TXP_GENERATE:
+            if (!TEST_int_eq(ossl_quic_tx_packetiser_generate(h.txp, (int)op->arg0),
+                             TX_PACKETISER_RES_SENT_PKT))
+                goto err;
+
+            ossl_qtx_finish_dgram(h.args.qtx);
+            ossl_qtx_flush_net(h.args.qtx);
+            break;
+        case OPK_TXP_GENERATE_NONE:
+            if (!TEST_int_eq(ossl_quic_tx_packetiser_generate(h.txp, (int)op->arg0),
+                             TX_PACKETISER_RES_NO_PKT))
+                goto err;
+
+            break;
+        case OPK_RX_PKT:
+            ossl_quic_demux_pump(h.demux);
+            if (h.qrx_pkt.handle != NULL)
+                ossl_qrx_release_pkt(h.qrx, h.qrx_pkt.handle);
+            if (!TEST_true(ossl_qrx_read_pkt(h.qrx, &h.qrx_pkt)))
+                goto err;
+            if (!TEST_true(PACKET_buf_init(&h.pkt,
+                                           h.qrx_pkt.hdr->data,
+                                           h.qrx_pkt.hdr->len)))
+                goto err;
+            h.frame_type = UINT64_MAX;
+            break;
+        case OPK_RX_PKT_NONE:
+            ossl_quic_demux_pump(h.demux);
+            if (!TEST_false(ossl_qrx_read_pkt(h.qrx, &h.qrx_pkt)))
+                goto err;
+            h.frame_type = UINT64_MAX;
+            break;
+        case OPK_EXPECT_DGRAM_LEN:
+            if (!TEST_size_t_ge(h.qrx_pkt.datagram_len, (size_t)op->arg0)
+                || !TEST_size_t_le(h.qrx_pkt.datagram_len, (size_t)op->arg1))
+                goto err;
+            break;
+        case OPK_EXPECT_FRAME:
+            if (!TEST_uint64_t_eq(h.frame_type, op->arg0))
+                goto err;
+            break;
+        case OPK_EXPECT_INITIAL_TOKEN:
+            if (!TEST_mem_eq(h.qrx_pkt.hdr->token, h.qrx_pkt.hdr->token_len,
+                             op->buf, (size_t)op->arg0))
+                goto err;
+            break;
+        case OPK_EXPECT_HDR:
+            if (!TEST_true(cmp_pkt_hdr(h.qrx_pkt.hdr, op->buf,
+                                       NULL, 0, 0)))
+                goto err;
+            break;
+        case OPK_CHECK:
+            if (!TEST_true(op->check_func(&h)))
+                goto err;
+            break;
+        case OPK_NEXT_FRAME:
+            skip_padding(&h);
+            if (!ossl_quic_wire_peek_frame_header(&h.pkt, &h.frame_type)) {
+                h.frame_type = UINT64_MAX;
+                break;
+            }
+
+            switch (h.frame_type) {
+            case OSSL_QUIC_FRAME_TYPE_HANDSHAKE_DONE:
+                if (!TEST_true(ossl_quic_wire_decode_frame_handshake_done(&h.pkt)))
+                    goto err;
+                break;
+            case OSSL_QUIC_FRAME_TYPE_PING:
+                if (!TEST_true(ossl_quic_wire_decode_frame_ping(&h.pkt)))
+                    goto err;
+                break;
+            case OSSL_QUIC_FRAME_TYPE_MAX_DATA:
+                if (!TEST_true(ossl_quic_wire_decode_frame_max_data(&h.pkt,
+                                                                    &h.frame.max_data)))
+                    goto err;
+                break;
+            case OSSL_QUIC_FRAME_TYPE_NEW_CONN_ID:
+                if (!TEST_true(ossl_quic_wire_decode_frame_new_conn_id(&h.pkt,
+                                                                       &h.frame.new_conn_id)))
+                    goto err;
+                break;
+            case OSSL_QUIC_FRAME_TYPE_NEW_TOKEN:
+                if (!TEST_true(ossl_quic_wire_decode_frame_new_token(&h.pkt,
+                                                                     &h.frame.new_token.token,
+                                                                     &h.frame.new_token.token_len)))
+                    goto err;
+                break;
+            case OSSL_QUIC_FRAME_TYPE_ACK_WITH_ECN:
+            case OSSL_QUIC_FRAME_TYPE_ACK_WITHOUT_ECN:
+                h.frame.ack.ack_ranges      = h.ack_ranges;
+                h.frame.ack.num_ack_ranges  = OSSL_NELEM(h.ack_ranges);
+                if (!TEST_true(ossl_quic_wire_decode_frame_ack(&h.pkt,
+                                                               h.args.ack_delay_exponent,
+                                                               &h.frame.ack,
+                                                               NULL)))
+                    goto err;
+                break;
+            case OSSL_QUIC_FRAME_TYPE_CRYPTO:
+                if (!TEST_true(ossl_quic_wire_decode_frame_crypto(&h.pkt, &h.frame.crypto)))
+                    goto err;
+                break;
+
+            case OSSL_QUIC_FRAME_TYPE_STREAM:
+            case OSSL_QUIC_FRAME_TYPE_STREAM_FIN:
+            case OSSL_QUIC_FRAME_TYPE_STREAM_LEN:
+            case OSSL_QUIC_FRAME_TYPE_STREAM_LEN_FIN:
+            case OSSL_QUIC_FRAME_TYPE_STREAM_OFF:
+            case OSSL_QUIC_FRAME_TYPE_STREAM_OFF_FIN:
+            case OSSL_QUIC_FRAME_TYPE_STREAM_OFF_LEN:
+            case OSSL_QUIC_FRAME_TYPE_STREAM_OFF_LEN_FIN:
+                if (!TEST_true(ossl_quic_wire_decode_frame_stream(&h.pkt, &h.frame.stream)))
+                    goto err;
+                break;
+
+            case OSSL_QUIC_FRAME_TYPE_STOP_SENDING:
+                if (!TEST_true(ossl_quic_wire_decode_frame_stop_sending(&h.pkt,
+                                                                        &h.frame.stop_sending)))
+                    goto err;
+                break;
+
+            case OSSL_QUIC_FRAME_TYPE_RESET_STREAM:
+                if (!TEST_true(ossl_quic_wire_decode_frame_reset_stream(&h.pkt,
+                                                                        &h.frame.reset_stream)))
+                    goto err;
+                break;
+
+            case OSSL_QUIC_FRAME_TYPE_CONN_CLOSE_TRANSPORT:
+            case OSSL_QUIC_FRAME_TYPE_CONN_CLOSE_APP:
+                if (!TEST_true(ossl_quic_wire_decode_frame_conn_close(&h.pkt,
+                                                                      &h.frame.conn_close)))
+                    goto err;
+                break;
+
+            default:
+                TEST_error("unknown frame type");
+                goto err;
+            }
+            break;
+        case OPK_EXPECT_NO_FRAME:
+            skip_padding(&h);
+            if (!TEST_size_t_eq(PACKET_remaining(&h.pkt), 0))
+                goto err;
+            break;
+        case OPK_PROVIDE_SECRET:
+            if (!TEST_true(ossl_qtx_provide_secret(h.args.qtx,
+                                                   (uint32_t)op->arg0,
+                                                   (uint32_t)op->arg1,
+                                                   NULL, op->buf, op->buf_len)))
+                goto err;
+            if (!TEST_true(ossl_qrx_provide_secret(h.qrx,
+                                                   (uint32_t)op->arg0,
+                                                   (uint32_t)op->arg1,
+                                                   NULL, op->buf, op->buf_len)))
+                goto err;
+            break;
+        case OPK_DISCARD_EL:
+            if (!TEST_true(ossl_quic_tx_packetiser_discard_enc_level(h.txp,
+                                                                     (uint32_t)op->arg0)))
+                goto err;
+            /*
+             * We do not discard on the QRX here, the object is to test the
+             * TXP so if the TXP does erroneously send at a discarded EL we
+             * want to know about it.
+             */
+            break;
+        case OPK_CRYPTO_SEND:
+            {
+                size_t consumed = 0;
+
+                if (!TEST_true(ossl_quic_sstream_append(h.args.crypto[op->arg0],
+                                                        op->buf, op->buf_len,
+                                                        &consumed)))
+                    goto err;
+
+                if (!TEST_size_t_eq(consumed, op->buf_len))
+                    goto err;
+            }
+            break;
+        case OPK_STREAM_NEW:
+            {
+                QUIC_STREAM *s;
+
+                if (!TEST_ptr(s = ossl_quic_stream_map_alloc(h.args.qsm, op->arg0,
+                                                             QUIC_STREAM_DIR_BIDI)))
+                    goto err;
+
+                if (!TEST_ptr(s->sstream = ossl_quic_sstream_new(512 * 1024))
+                    || !TEST_true(ossl_quic_txfc_init(&s->txfc, &h.conn_txfc))
+                    || !TEST_true(ossl_quic_rxfc_init(&s->rxfc, &h.conn_rxfc,
+                                                      1 * 1024 * 1024,
+                                                      16 * 1024 * 1024,
+                                                      fake_now, NULL))) {
+                    ossl_quic_sstream_free(s->sstream);
+                    ossl_quic_stream_map_release(h.args.qsm, s);
+                    goto err;
+                }
+            }
+            break;
+        case OPK_STREAM_SEND:
+            {
+                QUIC_STREAM *s;
+                size_t consumed = 0;
+
+                if (!TEST_ptr(s = ossl_quic_stream_map_get_by_id(h.args.qsm,
+                                                                 op->arg0)))
+                    goto err;
+
+                if (!TEST_true(ossl_quic_sstream_append(s->sstream, op->buf,
+                                                        op->buf_len, &consumed)))
+                    goto err;
+
+                if (!TEST_size_t_eq(consumed, op->buf_len))
+                    goto err;
+
+                ossl_quic_stream_map_update_state(h.args.qsm, s);
+            }
+            break;
+        case OPK_STREAM_FIN:
+            {
+                QUIC_STREAM *s;
+
+                if (!TEST_ptr(s = ossl_quic_stream_map_get_by_id(h.args.qsm,
+                                                                 op->arg0)))
+                    goto err;
+
+                ossl_quic_sstream_fin(s->sstream);
+            }
+            break;
+        case OPK_STOP_SENDING:
+            {
+                QUIC_STREAM *s;
+
+                if (!TEST_ptr(s = ossl_quic_stream_map_get_by_id(h.args.qsm,
+                                                                 op->arg0)))
+                    goto err;
+
+                if (!TEST_true(ossl_quic_stream_stop_sending(s, op->arg1)))
+                    goto err;
+
+                ossl_quic_stream_map_update_state(h.args.qsm, s);
+
+                if (!TEST_true(s->active))
+                    goto err;
+            }
+            break;
+        case OPK_RESET_STREAM:
+            {
+                QUIC_STREAM *s;
+
+                if (!TEST_ptr(s = ossl_quic_stream_map_get_by_id(h.args.qsm,
+                                                                 op->arg0)))
+                    goto err;
+
+                if (!TEST_true(ossl_quic_stream_reset(s, op->arg1)))
+                    goto err;
+
+                ossl_quic_stream_map_update_state(h.args.qsm, s);
+
+                if (!TEST_true(s->active))
+                    goto err;
+            }
+            break;
+        case OPK_CONN_TXFC_BUMP:
+            if (!TEST_true(ossl_quic_txfc_bump_cwm(h.args.conn_txfc, op->arg0)))
+                goto err;
+
+            break;
+        case OPK_STREAM_TXFC_BUMP:
+            {
+                QUIC_STREAM *s;
+
+                if (!TEST_ptr(s = ossl_quic_stream_map_get_by_id(h.args.qsm,
+                                                                 op->arg1)))
+                    goto err;
+
+                if (!TEST_true(ossl_quic_txfc_bump_cwm(&s->txfc, op->arg0)))
+                    goto err;
+
+                ossl_quic_stream_map_update_state(h.args.qsm, s);
+            }
+            break;
+        default:
+            TEST_error("bad opcode");
+            goto err;
+        }
+    }
+
+    testresult = 1;
+err:
+    if (have_helper)
+        helper_cleanup(&h);
+    return testresult;
+}
+
+static int test_script(int idx)
+{
+    return run_script(scripts[idx]);
+}
+
+int setup_tests(void)
+{
+    ADD_ALL_TESTS(test_script, OSSL_NELEM(scripts));
+    return 1;
+}
index 6948e69ef045b392042ab04d5c3ca379900336ec..325e322694f04109dff2b9c4ce295160635c5338 100644 (file)
@@ -886,7 +886,7 @@ static const OSSL_QUIC_FRAME_CONN_CLOSE encode_case_20_f = {
     0,
     0x1234,
     0x9781,
-    encode_case_20_reason,
+    (char *)encode_case_20_reason,
     sizeof(encode_case_20_reason)
 };
 
diff --git a/test/recipes/70-test_quic_txp.t b/test/recipes/70-test_quic_txp.t
new file mode 100644 (file)
index 0000000..8548fa4
--- /dev/null
@@ -0,0 +1,19 @@
+#! /usr/bin/env perl
+# Copyright 2022 The OpenSSL Project Authors. All Rights Reserved.
+#
+# Licensed under the Apache License 2.0 (the "License").  You may not use
+# this file except in compliance with the License.  You can obtain a copy
+# in the file LICENSE in the source distribution or at
+# https://www.openssl.org/source/license.html
+
+use OpenSSL::Test;
+use OpenSSL::Test::Utils;
+
+setup("test_quic_txp");
+
+plan skip_all => "QUIC protocol is not supported by this OpenSSL build"
+    if disabled('quic');
+
+plan tests => 1;
+
+ok(run(test(["quic_txp_test"])));