diff options
author | Hugo Landau <hlandau@openssl.org> | 2022-09-26 17:06:59 +0100 |
---|---|---|
committer | Hugo Landau <hlandau@openssl.org> | 2022-11-24 08:15:20 +0000 |
commit | a73078b79fc6f229b95312dcb20e4f61120a108c (patch) | |
tree | 9a66c1046605081c484444eb7eeb5527c934bbe6 | |
parent | f5060f9b31654d7ca3b015d2b803e17dda760190 (diff) | |
download | openssl-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)
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"]))); |