summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHugo Landau <hlandau@openssl.org>2022-09-26 17:06:59 +0100
committerHugo Landau <hlandau@openssl.org>2022-11-24 08:15:20 +0000
commita73078b79fc6f229b95312dcb20e4f61120a108c (patch)
tree9a66c1046605081c484444eb7eeb5527c934bbe6
parentf5060f9b31654d7ca3b015d2b803e17dda760190 (diff)
downloadopenssl-new-a73078b79fc6f229b95312dcb20e4f61120a108c.tar.gz
QUIC TX Packetiser and Streams Mapper
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)
-rw-r--r--doc/designs/quic-design/tx-packetiser.md789
-rw-r--r--include/internal/quic_ackm.h8
-rw-r--r--include/internal/quic_fc.h6
-rw-r--r--include/internal/quic_fifd.h4
-rw-r--r--include/internal/quic_record_tx.h18
-rw-r--r--include/internal/quic_stream.h6
-rw-r--r--include/internal/quic_stream_map.h232
-rw-r--r--include/internal/quic_txp.h141
-rw-r--r--include/internal/quic_txpim.h14
-rw-r--r--include/internal/quic_types.h2
-rw-r--r--include/internal/quic_wire.h31
-rw-r--r--include/internal/quic_wire_pkt.h17
-rw-r--r--ssl/quic/build.info3
-rw-r--r--ssl/quic/quic_ackm.c2
-rw-r--r--ssl/quic/quic_fifd.c53
-rw-r--r--ssl/quic/quic_record_rx.c12
-rw-r--r--ssl/quic/quic_record_tx.c51
-rw-r--r--ssl/quic/quic_sstream.c5
-rw-r--r--ssl/quic/quic_stream_map.c273
-rw-r--r--ssl/quic/quic_txp.c2162
-rw-r--r--ssl/quic/quic_wire.c41
-rw-r--r--ssl/quic/quic_wire_pkt.c5
-rw-r--r--test/build.info6
-rw-r--r--test/quic_fifd_test.c22
-rw-r--r--test/quic_record_test.c40
-rw-r--r--test/quic_record_test_util.h52
-rw-r--r--test/quic_stream_test.c4
-rw-r--r--test/quic_txp_test.c1424
-rw-r--r--test/quic_wire_test.c2
-rw-r--r--test/recipes/70-test_quic_txp.t19
30 files changed, 5113 insertions, 331 deletions
diff --git a/doc/designs/quic-design/tx-packetiser.md b/doc/designs/quic-design/tx-packetiser.md
index 8e0b4a3094..f2d7e69a16 100644
--- a/doc/designs/quic-design/tx-packetiser.md
+++ b/doc/designs/quic-design/tx-packetiser.md
@@ -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
diff --git a/include/internal/quic_ackm.h b/include/internal/quic_ackm.h
index 492b0d8ca9..6c8b0c5a46 100644
--- a/include/internal/quic_ackm.h
+++ b/include/internal/quic_ackm.h
@@ -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);
diff --git a/include/internal/quic_fc.h b/include/internal/quic_fc.h
index 20c18b5e1b..50301cc61e 100644
--- a/include/internal/quic_fc.h
+++ b/include/internal/quic_fc.h
@@ -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
diff --git a/include/internal/quic_fifd.h b/include/internal/quic_fifd.h
index 15952e43d8..f58fabf838 100644
--- a/include/internal/quic_fifd.h
+++ b/include/internal/quic_fifd.h
@@ -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);
diff --git a/include/internal/quic_record_tx.h b/include/internal/quic_record_tx.h
index 71949ae05c..6641d83ddd 100644
--- a/include/internal/quic_record_tx.h
+++ b/include/internal/quic_record_tx.h
@@ -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
diff --git a/include/internal/quic_stream.h b/include/internal/quic_stream.h
index cd3b810ae1..cef869b19d 100644
--- a/include/internal/quic_stream.h
+++ b/include/internal/quic_stream.h
@@ -131,6 +131,12 @@ int ossl_quic_sstream_get_stream_frame(QUIC_SSTREAM *qss,
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
index 0000000000..7158334219
--- /dev/null
+++ b/include/internal/quic_stream_map.h
@@ -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
index 0000000000..e1983a57c7
--- /dev/null
+++ b/include/internal/quic_txp.h
@@ -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
diff --git a/include/internal/quic_txpim.h b/include/internal/quic_txpim.h
index bcc6494394..eb24ea2bf1 100644
--- a/include/internal/quic_txpim.h
+++ b/include/internal/quic_txpim.h
@@ -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);
diff --git a/include/internal/quic_types.h b/include/internal/quic_types.h
index 22de5f2d42..f288853a94 100644
--- a/include/internal/quic_types.h
+++ b/include/internal/quic_types.h
@@ -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
diff --git a/include/internal/quic_wire.h b/include/internal/quic_wire.h
index 704684b0b4..dec7aeddc1 100644
--- a/include/internal/quic_wire.h
+++ b/include/internal/quic_wire.h
@@ -85,6 +85,21 @@
#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;
@@ -244,6 +259,13 @@ 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.
*
* This function returns a pointer to a buffer of f->len bytes which the caller
@@ -280,6 +302,13 @@ 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
* well-formed QUIC STREAM frame in one call.
diff --git a/include/internal/quic_wire_pkt.h b/include/internal/quic_wire_pkt.h
index 60528811ad..34e95ba7b6 100644
--- a/include/internal/quic_wire_pkt.h
+++ b/include/internal/quic_wire_pkt.h
@@ -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)
diff --git a/ssl/quic/build.info b/ssl/quic/build.info
index cc8c1fb5f5..97654b1021 100644
--- a/ssl/quic/build.info
+++ b/ssl/quic/build.info
@@ -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
diff --git a/ssl/quic/quic_ackm.c b/ssl/quic/quic_ackm.c
index 4378175f00..0a9e55881a 100644
--- a/ssl/quic/quic_ackm.c
+++ b/ssl/quic/quic_ackm.c
@@ -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;
diff --git a/ssl/quic/quic_fifd.c b/ssl/quic/quic_fifd.c
index f30e9d6553..e7241f60a8 100644
--- a/ssl/quic/quic_fifd.c
+++ b/ssl/quic/quic_fifd.c
@@ -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);
}
diff --git a/ssl/quic/quic_record_rx.c b/ssl/quic/quic_record_rx.c
index e10dd58515..5203e818a6 100644
--- a/ssl/quic/quic_record_rx.c
+++ b/ssl/quic/quic_record_rx.c
@@ -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
diff --git a/ssl/quic/quic_record_tx.c b/ssl/quic/quic_record_tx.c
index 24cae9a44e..14c8b0bd68 100644
--- a/ssl/quic/quic_record_tx.c
+++ b/ssl/quic/quic_record_tx.c
@@ -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;
diff --git a/ssl/quic/quic_sstream.c b/ssl/quic/quic_sstream.c
index 07113884e1..47d7ab1d21 100644
--- a/ssl/quic/quic_sstream.c
+++ b/ssl/quic/quic_sstream.c
@@ -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
index 0000000000..7a638550b9
--- /dev/null
+++ b/ssl/quic/quic_stream_map.c
@@ -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
index 0000000000..8508e87503
--- /dev/null
+++ b/ssl/quic/quic_txp.c
@@ -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;
+}
diff --git a/ssl/quic/quic_wire.c b/ssl/quic/quic_wire.c
index e086834b5e..bc66f6c592 100644
--- a/ssl/quic/quic_wire.c
+++ b/ssl/quic/quic_wire.c
@@ -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)
{
diff --git a/ssl/quic/quic_wire_pkt.c b/ssl/quic/quic_wire_pkt.c
index b2bf90e7b6..a4fdc7bf53 100644
--- a/ssl/quic/quic_wire_pkt.c
+++ b/ssl/quic/quic_wire_pkt.c
@@ -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;
diff --git a/test/build.info b/test/build.info
index 0c91ff602e..346f503853 100644
--- a/test/build.info
+++ b/test/build.info
@@ -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
diff --git a/test/quic_fifd_test.c b/test/quic_fifd_test.c
index 47eb030930..dfcabfa7cd 100644
--- a/test/quic_fifd_test.c
+++ b/test/quic_fifd_test.c
@@ -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;
diff --git a/test/quic_record_test.c b/test/quic_record_test.c
index 5349b70b17..c6ac10d854 100644
--- a/test/quic_record_test.c
+++ b/test/quic_record_test.c
@@ -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
index 0000000000..51d5db8a6e
--- /dev/null
+++ b/test/quic_record_test_util.h
@@ -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
diff --git a/test/quic_stream_test.c b/test/quic_stream_test.c
index 918d67aea8..dc0a618cd6 100644
--- a/test/quic_stream_test.c
+++ b/test/quic_stream_test.c
@@ -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
index 0000000000..afdfba33e6
--- /dev/null
+++ b/test/quic_txp_test.c
@@ -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;
+}
diff --git a/test/quic_wire_test.c b/test/quic_wire_test.c
index 6948e69ef0..325e322694 100644
--- a/test/quic_wire_test.c
+++ b/test/quic_wire_test.c
@@ -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
index 0000000000..8548fa4000
--- /dev/null
+++ b/test/recipes/70-test_quic_txp.t
@@ -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"])));