diff --git a/doc/designs/quic-design/images/ackm.png b/doc/designs/quic-design/images/ackm.png new file mode 100644 index 0000000000..75915345db Binary files /dev/null and b/doc/designs/quic-design/images/ackm.png differ diff --git a/doc/designs/quic-design/quic-ackm.md b/doc/designs/quic-design/quic-ackm.md new file mode 100644 index 0000000000..f81634a2e1 --- /dev/null +++ b/doc/designs/quic-design/quic-ackm.md @@ -0,0 +1,521 @@ +QUIC ACK Manager +================ + +![(Overview block diagram.)](images/ackm.png "QUIC ACK Manager Block Diagram") + +The QUIC ACK manager is responsible for, on the TX side: + + - Handling received ACK frames + - Generating notifications that a packet we sent was delivered successfully + - Generating notifications that a packet we sent was lost + - Generating requests for probe transmission + - Providing information on the largest unacked packet number so that packet + numbers in packet headers can be encoded and decoded correctly + +On the RX side, it is responsible for: + + - Generating ACK frames for later transmission in response to packets we + received + - Providing information on whether a given RX packet number is potentially + duplicate and should not be processed + +In order to allow it to perform these tasks, the ACK manager must: + + - be notified of all transmitted packets + - be notified of all received datagrams + - be notified of all received packets + - be notified of all received ACK frames + - be notified when a packet number space is discarded + - be notified when its loss detection deadline arrives + +The ACK manager consumes: + + - an arbitrary function which returns the current time; + - a RTT statistics tracker; + - a congestion controller. + +The ACK manager provides the following outputs: + + - It indicates the current deadline by which the loss detection + event should be invoked. + + - It indicates when probes should be generated. + + - It indicates what ACK frames should be generated. + + - It indicates the current deadline by which new ACK frames + will be generated, if any. + + - It indicates the largest unacknowledged packet number + for a given packet number space. + + - It calls a callback for each transmitted packet it is notified + of, specifying whether the packet was successfully acknowledged by the peer, + lost or discarded. + + - It may communicate with a congestion controller, causing the + congestion controller to update its state. + + - It may communicate with a RTT statistics tracker, causing it + to update its state. + +In this document, “the caller” refers to the system which makes use of the ACK +manager. + +Utility Definitions +------------------- + +There are three QUIC packet number spaces: Initial, Handshake and Application +Data. + +```c +/* QUIC packet number spaces. */ +#define QUIC_PN_SPACE_INITIAL 0 +#define QUIC_PN_SPACE_HANDSHAKE 1 +#define QUIC_PN_SPACE_APP 2 +#define QUIC_PN_SPACE_NUM 3 +``` + +Packet numbers are 62-bit values represented herein by `QUIC_PN`. +`QUIC_PN_INFINITE` evaluates to an invalid QUIC packet number value. + +```c +/* QUIC packet number representation. */ +typedef uint64_t QUIC_PN; +#define QUIC_PN_INFINITE UINT64_MAX +``` + +Instantiation +------------- + +The QUIC ACK manager is instantiated as follows: + +```c +typedef struct ossl_ackm_st OSSL_ACKM; + +OSSL_ACKM *ossl_ackm_new(OSSL_TIME (*now)(void *arg), + void *now_arg, + QUIC_STATM *statm, + OSSL_CC_METHOD *cc_method, + OSSL_CC_DATA *cc_data); + +void ossl_ackm_free(OSSL_ACKM *ackm); +``` + +The function pointer `now` is invoked by the ACK manager to obtain the current +time. `now_arg` is passed as the argument. The congestion controller method and +instance passed are used by the ACK manager instance. `statm` points to a +[Statistics Manager tracker instance](quic-statm.md). + +Events +------ + +The ACK manager state is evolved in response to events provided to the ACK +manager by the caller. + +### On TX Packet + +This must be called when a packet is transmitted. It does not provide the +payload of the packet, but provides metadata about the packet which is relevant +to the loss detection and acknowledgement process. + +The caller is responsible for the allocation of the structure and the structure +must remain allocated until one of the callbacks is called or the ACK manager is +freed. It is expected this structure will usually be freed (or returned to a +pool) in the implementation of either callback passed by the caller. + +Only exactly one of the callbacks in the structure will be called over the +lifetime of a `OSSL_ACKM_TX_PKT`, and only once. + +Returns 1 on success. + +```c +typedef struct ossl_ackm_tx_pkt_st { + /* The packet number of the transmitted packet. */ + QUIC_PN pkt_num; + + /* The number of bytes in the packet which was sent. */ + size_t num_bytes; + + /* The time at which the packet was sent. */ + OSSL_TIME time; + + /* + * If the packet being described by this structure contains an ACK frame, + * this must be set to the largest PN ACK'd by that frame. + * + * Otherwise, it should be set to QUIC_PN_INVALID. + * + * This is necessary to bound the number of PNs we have to keep track of on + * the RX side (RFC 9000 s. 13.2.4). It allows older PN tracking information + * on the RX side to be discarded. + */ + QUIC_PN largest_acked; + + /* + * One of the QUIC_PN_SPACE_* values. This qualifies the pkt_num field + * into a packet number space. + */ + unsigned int pkt_space :2; + + /* 1 if the packet is in flight. */ + unsigned int is_inflight :1; + + /* 1 if the packet has one or more ACK-eliciting frames. */ + unsigned int is_ack_eliciting :1; + + /* 1 if the packet is a PTO probe. */ + unsigned int is_pto_probe :1; + + /* 1 if the packet is an MTU probe. */ + unsigned int is_mtu_probe :1; + + /* Callback called if frames in this packet are lost. arg is cb_arg. */ + void (*on_lost)(void *arg); + + /* Callback called if frames in this packet are acked. arg is cb_arg. */ + void (*on_acked)(void *arg); + + /* + * Callback called if frames in this packet are neither acked nor lost. arg + * is cb_arg. + */ + void (*on_discarded)(void *arg); + void *cb_arg; + + /* (Internal use fields are appended here and must be zero-initialized.) */ +} OSSL_ACKM_TX_PKT; + +int ossl_ackm_on_tx_packet(OSSL_ACKM *ackm, const OSSL_ACKM_TX_PKT *pkt); +``` + +### On RX Datagram + +This must be called whenever a datagram is received. A datagram may contain +multiple packets, and this function should be called before the calls to +`ossl_ackm_on_rx_packet`. + +The primary use of this function is to inform the ACK manager of new credit to +the anti-amplification budget. Packet and ACK-frame related logic are handled +separately in the subsequent calls to `ossl_ackm_on_rx_packet` and +`ossl_ackm_on_rx_ack_frame`, respectively. + +Returns 1 on success. + +```c +int ossl_ackm_on_rx_datagram(OSSL_ACKM *ackm, size_t num_bytes); +``` + +### On RX Packet + +This must be called whenever a packet is received. It should be called after +`ossl_ackm_on_rx_datagram` was called for the datagram containing the packet. + +Returns 1 on success. + +```c +#define OSSL_ACKM_ECN_NONE 0 +#define OSSL_ACKM_ECN_ECT1 1 +#define OSSL_ACKM_ECN_ECT0 2 +#define OSSL_ACKM_ECN_ECNCE 3 + +typedef struct ossl_ackm_rx_pkt_st { + /* The packet number of the received packet. */ + QUIC_PN pkt_num; + + /* The time at which the packet was received. */ + OSSL_TIME time; + + /* + * One of the QUIC_PN_SPACE_* values. This qualifies the pkt_num field + * into a packet number space. + */ + unsigned int pkt_space :2; + + /* 1 if the packet has one or more ACK-eliciting frames. */ + unsigned int is_ack_eliciting :1; + + /* + * One of the OSSL_ACKM_ECN_* values. This is the ECN labelling applied + * to the received packet. If unknown, use OSSL_ACKM_ECN_NONE. + */ + unsigned int ecn :2; +} OSSL_ACKM_RX_PKT; + +int ossl_ackm_on_rx_packet(OSSL_ACKM *ackm, const OSSL_ACKM_RX_PKT *pkt); +``` + +### On RX ACK Frame + +This must be called whenever an ACK frame is received. It should be called +after any call to `ossl_ackm_on_rx_packet`. + +The ranges of packet numbers being acknowledged are passed as an argument. +`pkt_space` is one of the `QUIC_PN_SPACE_*` values, specifying the packet number +space of the containing packet. `rx_time` is the time the frame was +received. + +This function causes `on_acked` callbacks to be invoked on applicable packets. + +Returns 1 on success. + +```c +typedef struct ossl_ackm_ack_range_st { + /* + * Represents an inclusive range of packet numbers [start, end]. + * start must be <= end. + */ + QUIC_PN start, end; +} OSSL_ACKM_ACK_RANGE; + +typedef struct ossl_ackm_ack { + /* + * A sequence of packet number ranges [[start, end]...]. + * + * The ranges must be sorted in descending order, for example: + * [ 95, 100] + * [ 90, 92] + * etc. + * + * As such, ack_ranges[0].end is always the highest packet number + * being acknowledged and ack_ranges[num_ack_ranges-1].start is + * always the lowest packet number being acknowledged. + * + * num_ack_ranges must be greater than zero, as an ACK frame must + * acknowledge at least one packet number. + */ + const OSSL_ACKM_ACK_RANGE *ack_ranges; + size_t num_ack_ranges; + + OSSL_TIME delay_time; + uint64_t ect0, ect1, ecnce; + /* 1 if the ect0, ect1 and ecnce fields are valid */ + char ecn_present; +} OSSL_ACKM_ACK; + +int ossl_ackm_on_rx_ack_frame(OSSL_ACKM *ackm, const OSSL_ACKM_ACK *ack, + int pkt_space, OSSL_TIME rx_time); +``` + +### On Packet Space Discarded + +This must be called whenever a packet number space is discarded. ACK-tracking +information for the number space is thrown away. Any previously provided +`OSSL_ACKM_TX_PKT` structures have their `on_discarded` callback invoked, +providing an opportunity for them to be freed. + +Returns 1 on success. + +```c +int ossl_ackm_on_pkt_space_discarded(OSSL_ACKM *ackm, int pkt_space); +``` + +### On Handshake Confirmed + +This should be called by the caller when the QUIC handshake is confirmed. The +Probe Timeout (PTO) algorithm behaves differently depending on whether the QUIC +handshake is confirmed yet. + +Returns 1 on success. + +```c +int ossl_ackm_on_handshake_confirmed(OSSL_ACKM *ackm); +``` + +### On Timeout + +This must be called whenever the loss detection deadline expires. + +```c +int ossl_ackm_on_timeout(OSSL_ACKM *ackm); +``` + +Queries +------- + +These functions allow information about the status of the ACK manager to be +obtained. + +### Get Loss Detection Deadline + +This returns a deadline after which `ossl_ackm_on_timeout` should be called. + +If it is `OSSL_TIME_INFINITY`, no timeout is currently active. + +The value returned by this function may change after any call to any of the +event functions above is made. + +```c +OSSL_TIME ossl_ackm_get_loss_detection_deadline(OSSL_ACKM *ackm); +``` + +### Get ACK Frame + +This returns a pointer to a `OSSL_ACKM_ACK` structure representing the +information which should be packed into an ACK frame and transmitted. + +This generates an ACK frame regardless of whether the ACK manager thinks one +should currently be sent. To determine if the ACK manager thinks an ACK frame +should be sent, use `ossl_ackm_is_ack_desired`, discussed below. + +If no new ACK frame is currently needed, returns NULL. After calling this +function, calling the function immediately again will return NULL. + +The structure pointed to by the returned pointer, and the referenced ACK range +structures, are guaranteed to remain valid until the next call to any +`OSSL_ACKM` function. After such a call is made, all fields become undefined. + +This function is used to provide ACK frames for acknowledging packets which have +been received and notified to the ACK manager via `ossl_ackm_on_rx_packet`. + +Calling this function clears the flag returned by `ossl_ackm_is_ack_desired` and +the deadline returned by `ossl_ackm_get_ack_deadline`. + +```c +const OSSL_ACKM_ACK *ossl_ackm_get_ack_frame(OSSL_ACKM *ackm, int pkt_space); +``` + +### Is ACK Desired + +This returns 1 if the ACK manager thinks an ACK frame ought to be generated and +sent at this time. `ossl_ackm_get_ack_frame` will always provide an ACK frame +whether or not this returns 1, so it is suggested that you call this function +first to determine whether you need to generate an ACK frame. + +The return value of this function can change based on calls to +`ossl_ackm_on_rx_packet` and based on the passage of time (see +`ossl_ackm_get_ack_deadline`). + +```c +int ossl_ackm_is_ack_desired(OSSL_ACKM *ackm, int pkt_space); +``` + +### Get ACK Deadline + +The ACK manager may defer generation of ACK frames to optimize performance. For +example, after a packet requiring acknowledgement is received, it may decide to +wait until a few more packets are received before generating an ACK frame, so +that a single ACK frame can acknowledge all of them. However, if further +packets do not arrive, an ACK frame must be generated anyway within a certain +amount of time. + +This function returns the deadline at which the return value of +`ossl_ackm_is_ack_desired` will change to 1, or `OSSL_TIME_INFINITY`, which +means that no deadline is currently applicable. If the deadline has already +passed, it may either return that deadline or `OSSL_TIME_ZERO`. + +```c +OSSL_TIME ossl_ackm_get_ack_deadline(OSSL_ACKM *ackm, int pkt_space); +``` + +### Is RX PN Processable + +Returns 1 if the given RX packet number is “processable”. A processable PN is +one that is not either + + - duplicate, meaning that we have already been passed such a PN in a call + to `ossl_ackm_on_rx_packet`; or + + - written off, meaning that the PN is so old that we have stopped tracking + state for it (meaning we cannot tell whether it is a duplicate and cannot + process it safely). + +This should be called for a packet before attempting to process its contents. +Failure to do so may may result in processing a duplicated packet in violation +of the RFC. + +The returrn value of this function transitions from 1 to 0 for a given PN once +that PN is passed to ossl_ackm_on_rx_packet, thus this functiion must be used +before calling `ossl_ackm_on_rx_packet`. + +```c +int ossl_ackm_is_rx_pn_processable(OSSL_ACKM *ackm, QUIC_PN pn, int pkt_space); +``` + +### Get Probe Packet + +This determines if the ACK manager is requesting any probe packets to be +transmitted. + +The user calls `ossl_ackm_get_probe_request`. The structure pointed +to by `info` is filled and the function returns 1 on success. + +The fields of `OSSL_ACKM_PROBE_INFO` record the number of probe requests +of each type which are outstanding. In short: + + - `handshake` designates the number of ACK-eliciting Handshake + packets being requested. This is equivalent to + `SendOneAckElicitingHandshakePacket()` in RFC 9002. + + - `padded_initial` designates the number of ACK-eliciting + padded Initial packets being requested. This is equivalent to + `SendOneAckElicitingPaddedInitialPacket()` in RFC 9002. + + - `pto` designates the number of ACK-eliciting outstanding probe events + corresponding to each packet number space. This is equivalent to + `SendOneOrTwoAckElicitingPackets(pn_space)` in RFC 9002. + +Once the caller has processed these requests, the caller must clear these +outstanding requests by calling `ossl_ackm_get_probe_request` with `clear` set +to 1. If `clear` is non-zero, the current values are returned and then zeroed, +so that the next call to `ossl_ackm_get_probe_request` (if made immediately) +will return zero values for all fields. + +```c +typedef struct ossl_ackm_probe_info_st { + uint32_t handshake; + uint32_t padded_initial; + uint32_t pto[QUIC_PN_SPACE_NUM]; +} OSSL_ACKM_PROBE_INFO; + +int ossl_ackm_get_probe_request(OSSL_ACKM *ackm, int clear, + OSSL_ACKM_PROBE_INFO *info); +``` + +### Get Largest Unacked Packet Number + +This gets the largest unacknowledged packet number in the given packet number +space. The packet number is written to `*pn`. Returns 1 on success. + +This is needed so that packet encoders can determine with what length to encode +the abridged packet number in the packet header. + +```c +int ossl_ackm_get_largest_unacked(OSSL_ACKM *ackm, int pkt_space, QUIC_PN *pn); +``` + +Callback Functionality +---------------------- + +The ACK manager supports optional callback functionality when its deadlines +are updated. By default, the callback functionality is not enabled. To use +the callback functionality, call either or both of the following functions +with a non-NULL function pointer: + +```c +void ossl_ackm_set_loss_detection_deadline_callback(OSSL_ACKM *ackm, + void (*fn)(OSSL_TIME deadline, + void *arg), + void *arg); + +void ossl_ackm_set_ack_deadline_callback(OSSL_ACKM *ackm, + void (*fn)(OSSL_TIME deadline, + int pkt_space, + void *arg), + void *arg); +``` + +Callbacks can be subsequently disabled by calling these functions with a NULL +function pointer. The callbacks are not called at the time that they are set, +therefore it is recommended to call them immediately after the call to +`ossl_ackm_new`. + +The loss detection deadline callback is called whenever the value returned +by `ossl_ackm_get_loss_detection_deadline` changes. + +The ACK deadline callback is called whenever the value returned by +`ossl_ackm_get_ack_deadline` changes for a given packet space. + +The `deadline` argument reflects the value which will be newly returned by the +corresponding function. If the configured callback calls either of these +functions, the returned value will reflect the new deadline. diff --git a/doc/designs/quic-design/quic-statm.md b/doc/designs/quic-design/quic-statm.md new file mode 100644 index 0000000000..1fa9c8aa5e --- /dev/null +++ b/doc/designs/quic-design/quic-statm.md @@ -0,0 +1,73 @@ +QUIC Statistics Manager +======================= + +The statistics manager keeps track of RTT statistics for use by the QUIC +implementation. + +It provides the following interface: + +Instantiation +------------- + +The QUIC statistics manager is instantiated as follows: + +```c +typedef struct ossl_statm_st { + ... +} OSSL_STATM; + +int ossl_statm_init(OSSL_STATM *statm); + +void ossl_statm_destroy(OSSL_STATM *statm); +``` + +The structure is defined in headers, so it may be initialised without needing +its own memory allocation. However, other code should not examine the fields of +`OSSL_STATM` directly. + +Get RTT Info +------------ + +The current RTT info is retrieved using the function `ossl_statm_get_rtt_info`, +which fills an `OSSL_RTT_INFO` structure: + +```c +typedef struct ossl_rtt_info_st { + /* As defined in RFC 9002. */ + OSSL_TIME smoothed_rtt, latest_rtt, rtt_variance, min_rtt, + max_ack_delay; +} OSSL_RTT_INFO; + +void ossl_statm_get_rtt_info(OSSL_STATM *statm, OSSL_RTT_INFO *rtt_info); +``` + +Update RTT +---------- + +New RTT samples are provided using the `ossl_statm_update_rtt` function: + + - `ack_delay`. This is the ACK Delay value; see RFC 9000. + + - `override_latest_rtt` provides a new latest RTT sample. If it is + `OSSL_TIME_ZERO`, the existing Latest RTT value is used when updating the + RTT. + +The maximum ACK delay configured using `ossl_statm_set_max_ack_delay` is not +enforced automatically on the `ack_delay` argument as the circumstances where +this should be enforced are context sensitive. It is the caller's responsibility +to retrieve the value and enforce the maximum ACK delay if appropriate. + +```c +void ossl_statm_update_rtt(OSSL_STATM *statm, + OSSL_TIME ack_delay, + OSSL_TIME override_latest_rtt); +``` + +Set Max. Ack Delay +------------------ + +Sets the maximum ACK delay field reported by `OSSL_RTT_INFO`. + +```c +void ossl_statm_set_max_ack_delay(OSSL_STATM *statm, OSSL_TIME max_ack_delay); +``` diff --git a/doc/internal/man3/OSSL_TIME.pod b/doc/internal/man3/OSSL_TIME.pod index 81dad2e0d9..eda5b98744 100644 --- a/doc/internal/man3/OSSL_TIME.pod +++ b/doc/internal/man3/OSSL_TIME.pod @@ -5,8 +5,9 @@ OSSL_TIME, OSSL_TIME_SECOND, ossl_time_infinite, ossl_time_zero, ossl_ticks2time, ossl_time2ticks, ossl_time_now, ossl_time_time_to_timeval, ossl_time_compare, -ossl_time_add, ossl_time_subtract -- times and durations +ossl_time_add, ossl_time_subtract, ossl_time_multiply, +ossl_time_divide, ossl_time_abs_difference, ossl_time_max, +ossl_time_min - times and durations =head1 SYNOPSIS @@ -28,6 +29,11 @@ ossl_time_add, ossl_time_subtract int ossl_time_compare(OSSL_TIME a, OSSL_TIME b); OSSL_TIME ossl_time_add(OSSL_TIME a, OSSL_TIME b); OSSL_TIME ossl_time_subtract(OSSL_TIME a, OSSL_TIME b); + OSSL_TIME ossl_time_multiply(OSSL_TIME a, uint64_t b); + OSSL_TIME ossl_time_divide(OSSL_TIME a, uint64_t b); + OSSL_TIME ossl_time_abs_difference(OSSL_TIME a, OSSL_TIME b); + OSSL_TIME ossl_time_max(OSSL_TIME a, OSSL_TIME b); + OSSL_TIME ossl_time_min(OSSL_TIME a, OSSL_TIME b); =head1 DESCRIPTION @@ -78,6 +84,19 @@ B performs a saturating subtraction of the two items, returning I - I. If the difference would be negative, B<0> is returned. +B performs a saturating multiplication of a time value by a +given integer multiplier. + +B performs floor division of a time value by a given integer +divisor. + +B returns the magnitude of the difference between two +time values. + +B returns the lesser of two time values. + +B returns the greater of two time values. + =head1 NOTES The largest representable duration is guaranteed to be at least 500 years. @@ -102,6 +121,17 @@ the last representable time on overflow. B returns the difference of the two times or the time of the Epoch on underflow. +B returns the result of multiplying the given time by the +given integral multiplier, or B on overflow. + +B returns the result of dividing the given time by the given +integral divisor. + +B returns the magnitude of the difference between the +input time values. + +B and B choose and return one of the input values. + =head1 HISTORY This functionality was added in OpenSSL 3.1. diff --git a/include/internal/quic_ackm.h b/include/internal/quic_ackm.h new file mode 100644 index 0000000000..1188e30177 --- /dev/null +++ b/include/internal/quic_ackm.h @@ -0,0 +1,206 @@ +/* + * 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_ACKM_H +# define OSSL_QUIC_ACKM_H + +# include "internal/quic_statm.h" +# include "internal/quic_cc.h" +# include "internal/quic_types.h" +# include "internal/quic_wire.h" +# include "internal/time.h" + +typedef struct ossl_ackm_st OSSL_ACKM; + +OSSL_ACKM *ossl_ackm_new(OSSL_TIME (*now)(void *arg), + void *now_arg, + OSSL_STATM *statm, + const OSSL_CC_METHOD *cc_method, + OSSL_CC_DATA *cc_data); +void ossl_ackm_free(OSSL_ACKM *ackm); + +void ossl_ackm_set_loss_detection_deadline_callback(OSSL_ACKM *ackm, + void (*fn)(OSSL_TIME deadline, + void *arg), + void *arg); + +void ossl_ackm_set_ack_deadline_callback(OSSL_ACKM *ackm, + void (*fn)(OSSL_TIME deadline, + int pkt_space, + void *arg), + void *arg); + +typedef struct ossl_ackm_tx_pkt_st { + /* The packet number of the transmitted packet. */ + QUIC_PN pkt_num; + + /* The number of bytes in the packet which was sent. */ + size_t num_bytes; + + /* The time at which the packet was sent. */ + OSSL_TIME time; + + /* + * If the packet being described by this structure contains an ACK frame, + * this must be set to the largest PN ACK'd by that frame. + * + * Otherwise, it should be set to QUIC_PN_INVALID. + * + * This is necessary to bound the number of PNs we have to keep track of on + * the RX side (RFC 9000 s. 13.2.4). It allows older PN tracking information + * on the RX side to be discarded. + */ + QUIC_PN largest_acked; + + /* + * One of the QUIC_PN_SPACE_* values. This qualifies the pkt_num field + * into a packet number space. + */ + unsigned int pkt_space :2; + + /* 1 if the packet is in flight. */ + unsigned int is_inflight :1; + + /* 1 if the packet has one or more ACK-eliciting frames. */ + unsigned int is_ack_eliciting :1; + + /* 1 if the packet is a PTO probe. */ + unsigned int is_pto_probe :1; + + /* 1 if the packet is an MTU probe. */ + unsigned int is_mtu_probe :1; + + /* Callback called if frames in this packet are lost. arg is cb_arg. */ + void (*on_lost)(void *arg); + /* Callback called if frames in this packet are acked. arg is cb_arg. */ + void (*on_acked)(void *arg); + /* + * Callback called if frames in this packet are neither acked nor lost. arg + * is cb_arg. + */ + void (*on_discarded)(void *arg); + void *cb_arg; + + /* + * (Internal use fields; must be zero-initialized.) + * + * prev and next link us into the TX history list, anext is used to manifest + * a singly-linked list of newly-acknowledged packets, and lnext is used to + * manifest a singly-linked list of newly lost packets. + */ + struct ossl_ackm_tx_pkt_st *prev; + struct ossl_ackm_tx_pkt_st *next; + struct ossl_ackm_tx_pkt_st *anext; + struct ossl_ackm_tx_pkt_st *lnext; +} OSSL_ACKM_TX_PKT; + +int ossl_ackm_on_tx_packet(OSSL_ACKM *ackm, OSSL_ACKM_TX_PKT *pkt); +int ossl_ackm_on_rx_datagram(OSSL_ACKM *ackm, size_t num_bytes); + +#define OSSL_ACKM_ECN_NONE 0 +#define OSSL_ACKM_ECN_ECT1 1 +#define OSSL_ACKM_ECN_ECT0 2 +#define OSSL_ACKM_ECN_ECNCE 3 + +typedef struct ossl_ackm_rx_pkt_st { + /* The packet number of the received packet. */ + QUIC_PN pkt_num; + + /* The time at which the packet was received. */ + OSSL_TIME time; + + /* + * One of the QUIC_PN_SPACE_* values. This qualifies the pkt_num field + * into a packet number space. + */ + unsigned int pkt_space :2; + + /* 1 if the packet has one or more ACK-eliciting frames. */ + unsigned int is_ack_eliciting :1; + + /* + * One of the OSSL_ACKM_ECN_* values. This is the ECN labelling applied to + * the received packet. If unknown, use OSSL_ACKM_ECN_NONE. + */ + unsigned int ecn :2; +} OSSL_ACKM_RX_PKT; + +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); + +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); + +OSSL_TIME ossl_ackm_get_loss_detection_deadline(OSSL_ACKM *ackm); + +/* + * Generates an ACK frame, regardless of whether the ACK manager thinks + * one should currently be sent. + * + * This clears the flag returned by ossl_ackm_is_ack_desired and the deadline + * returned by ossl_ackm_get_ack_deadline. + */ +const OSSL_QUIC_FRAME_ACK *ossl_ackm_get_ack_frame(OSSL_ACKM *ackm, + int pkt_space); + +/* + * Returns the deadline after which an ACK frame should be generated by calling + * ossl_ackm_get_ack_frame, or OSSL_TIME_INFINITY if no deadline is currently + * applicable. If the deadline has already passed, this function may return that + * deadline, or may return OSSL_TIME_ZERO. + */ +OSSL_TIME ossl_ackm_get_ack_deadline(OSSL_ACKM *ackm, int pkt_space); + +/* + * Returns 1 if the ACK manager thinks an ACK frame ought to be generated and + * sent at this time. ossl_ackm_get_ack_frame will always provide an ACK frame + * whether or not this returns 1, so it is suggested that you call this function + * first to determine whether you need to generate an ACK frame. + * + * The return value of this function can change based on calls to + * ossl_ackm_on_rx_packet and based on the passage of time (see + * ossl_ackm_get_ack_deadline). + */ +int ossl_ackm_is_ack_desired(OSSL_ACKM *ackm, int pkt_space); + +/* + * Returns 1 if the given RX PN is 'processable'. A processable PN is one that + * is not either + * + * - duplicate, meaning that we have already been passed such a PN in a call + * to ossl_ackm_on_rx_packet; or + * + * - written off, meaning that the PN is so old we have stopped tracking state + * for it (meaning that we cannot tell whether it is a duplicate and cannot + * process it safely). + * + * This should be called for a packet before attempting to process its contents. + * Failure to do so may result in processing a duplicated packet in violation of + * the RFC. + * + * The return value of this function transitions from 1 to 0 for a given PN once + * that PN is passed to ossl_ackm_on_rx_packet, thus thus function must be used + * before calling ossl_ackm_on_rx_packet. + */ +int ossl_ackm_is_rx_pn_processable(OSSL_ACKM *ackm, QUIC_PN pn, int pkt_space); + +typedef struct ossl_ackm_probe_info_st { + uint32_t handshake; + uint32_t padded_initial; + uint32_t pto[QUIC_PN_SPACE_NUM]; +} OSSL_ACKM_PROBE_INFO; + +int ossl_ackm_get_probe_request(OSSL_ACKM *ackm, int clear, + OSSL_ACKM_PROBE_INFO *info); + +int ossl_ackm_get_largest_unacked(OSSL_ACKM *ackm, int pkt_space, QUIC_PN *pn); + +#endif diff --git a/include/internal/quic_cc.h b/include/internal/quic_cc.h new file mode 100644 index 0000000000..298d9c5890 --- /dev/null +++ b/include/internal/quic_cc.h @@ -0,0 +1,150 @@ +/* + * 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_CC_H +# define OSSL_QUIC_CC_H + +#include "openssl/params.h" +#include "internal/time.h" + +typedef struct ossl_cc_data_st *OSSL_CC_DATA; + +typedef struct ossl_cc_method_st { + void *dummy; + + /* + * Create a new OSSL_CC_DATA object to handle the congestion control + * calculations. + * + * |settings| are mandatory settings that will cause the + * new() call to fail if they are not understood). + * |options| are optional settings that will not + * cause the new() call to fail if they are not understood. + * |changeables| contain additional parameters that the congestion + * control algorithms need that can be updated during the + * connection lifetime - for example size of the datagram payload. + * To avoid calling a function with OSSL_PARAM array every time + * these parameters are changed the addresses of these param + * values are considered permanent and the values can be updated + * any time. + */ + OSSL_CC_DATA *(*new)(OSSL_PARAM *settings, OSSL_PARAM *options, + OSSL_PARAM *changeables); + + /* + * Release the OSSL_CC_DATA. + */ + void (*free)(OSSL_CC_DATA *ccdata); + + /* + * Reset the congestion control state. + * |flags| to support different level of reset (partial/full). + */ + void (*reset)(OSSL_CC_DATA *ccdata, int flags); + + /* + * Set number of packets exempted from CC - used for probing + * |numpackets| is a small value (2). + * Returns 0 on error, 1 otherwise. + */ + int (*set_exemption)(OSSL_CC_DATA *ccdata, int numpackets); + + /* + * Get current number of packets exempted from CC. + * Returns negative value on error, the number otherwise. + */ + int (*get_exemption)(OSSL_CC_DATA *ccdata); + + /* + * Returns 1 if sending is allowed, 0 otherwise. + */ + int (*can_send)(OSSL_CC_DATA *ccdata); + + /* + * Returns number of bytes allowed to be sent. + * |time_since_last_send| is time since last send operation + * in microseconds. + * |time_valid| is 1 if the |time_since_last_send| holds + * a meaningful value, 0 otherwise. + */ + size_t (*get_send_allowance)(OSSL_CC_DATA *ccdata, + OSSL_TIME time_since_last_send, + int time_valid); + + /* + * Returns the maximum number of bytes allowed to be in flight. + */ + size_t (*get_bytes_in_flight_max)(OSSL_CC_DATA *ccdata); + + /* + * To be called when a packet with retransmittable data was sent. + * |num_retransmittable_bytes| is the number of bytes sent + * in the packet that are retransmittable. + * Returns 1 on success, 0 otherwise. + */ + int (*on_data_sent)(OSSL_CC_DATA *ccdata, + size_t num_retransmittable_bytes); + + /* + * To be called when retransmittable data was invalidated. + * I.E. they are not considered in-flight anymore but + * are neither acknowledged nor lost. In particular used when + * 0RTT data was rejected. + * |num_retransmittable_bytes| is the number of bytes + * of the invalidated data. + * Returns 1 if sending is unblocked (can_send returns 1), 0 + * otherwise. + */ + int (*on_data_invalidated)(OSSL_CC_DATA *ccdata, + size_t num_retransmittable_bytes); + + /* + * To be called when sent data was acked. + * |time_now| is current time in microseconds. + * |largest_pn_acked| is the largest packet number of the acked + * packets. + * |num_retransmittable_bytes| is the number of retransmittable + * packet bytes that were newly acked. + * Returns 1 if sending is unblocked (can_send returns 1), 0 + * otherwise. + */ + int (*on_data_acked)(OSSL_CC_DATA *ccdata, + OSSL_TIME time_now, + uint64_t last_pn_acked, + size_t num_retransmittable_bytes); + + /* + * To be called when sent data is considered lost. + * |largest_pn_lost| is the largest packet number of the lost + * packets. + * |largest_pn_sent| is the largest packet number sent on this + * connection. + * |num_retransmittable_bytes| is the number of retransmittable + * packet bytes that are newly considered lost. + * |persistent_congestion| is 1 if the congestion is considered + * persistent (see RFC 9002 Section 7.6), 0 otherwise. + */ + void (*on_data_lost)(OSSL_CC_DATA *ccdata, + uint64_t largest_pn_lost, + uint64_t largest_pn_sent, + size_t num_retransmittable_bytes, + int persistent_congestion); + + /* + * To be called when all lost data from the previous call to + * on_data_lost() was actually acknowledged. + * This reverts the size of the congestion window to the state + * before the on_data_lost() call. + * Returns 1 if sending is unblocked, 0 otherwise. + */ + int (*on_spurious_congestion_event)(OSSL_CC_DATA *ccdata); +} OSSL_CC_METHOD; + +extern const OSSL_CC_METHOD ossl_cc_dummy_method; + +#endif diff --git a/include/internal/quic_statm.h b/include/internal/quic_statm.h new file mode 100644 index 0000000000..b551130007 --- /dev/null +++ b/include/internal/quic_statm.h @@ -0,0 +1,38 @@ +/* + * 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_STATS_H +# define OSSL_QUIC_STATS_H + +# include +# include "internal/time.h" + +typedef struct ossl_statm_st { + OSSL_TIME smoothed_rtt, latest_rtt, min_rtt, rtt_variance, max_ack_delay; + char have_first_sample; +} OSSL_STATM; + +typedef struct ossl_rtt_info_st { + /* As defined in RFC 9002. */ + OSSL_TIME smoothed_rtt, latest_rtt, rtt_variance, min_rtt, max_ack_delay; +} OSSL_RTT_INFO; + +int ossl_statm_init(OSSL_STATM *statm); + +void ossl_statm_destroy(OSSL_STATM *statm); + +void ossl_statm_get_rtt_info(OSSL_STATM *statm, OSSL_RTT_INFO *rtt_info); + +void ossl_statm_update_rtt(OSSL_STATM *statm, + OSSL_TIME ack_delay, + OSSL_TIME override_latest_rtt); + +void ossl_statm_set_max_ack_delay(OSSL_STATM *statm, OSSL_TIME max_ack_delay); + +#endif diff --git a/include/internal/quic_types.h b/include/internal/quic_types.h index 6787de9c11..bd37019d21 100644 --- a/include/internal/quic_types.h +++ b/include/internal/quic_types.h @@ -12,6 +12,12 @@ # include +/* QUIC packet number spaces. */ +#define QUIC_PN_SPACE_INITIAL 0 +#define QUIC_PN_SPACE_HANDSHAKE 1 +#define QUIC_PN_SPACE_APP 2 +#define QUIC_PN_SPACE_NUM 3 + /* QUIC packet number representation. */ typedef uint64_t QUIC_PN; # define QUIC_PN_INVALID UINT64_MAX diff --git a/include/internal/time.h b/include/internal/time.h index 6a7a05aae5..5cc55b3f51 100644 --- a/include/internal/time.h +++ b/include/internal/time.h @@ -98,6 +98,20 @@ int ossl_time_compare(OSSL_TIME a, OSSL_TIME b) return 0; } +/* Returns true if an OSSL_TIME is OSSL_TIME_ZERO. */ +static ossl_unused ossl_inline +int ossl_time_is_zero(OSSL_TIME t) +{ + return t == OSSL_TIME_ZERO; +} + +/* Returns true if an OSSL_TIME is OSSL_TIME_INFINITY. */ +static ossl_unused ossl_inline +int ossl_time_is_infinity(OSSL_TIME t) +{ + return t == OSSL_TIME_INFINITY; +} + /* * Arithmetic operations on times. * These operations are saturating, in that an overflow or underflow returns diff --git a/ssl/quic/build.info b/ssl/quic/build.info index 3e76055c62..9e411011e7 100644 --- a/ssl/quic/build.info +++ b/ssl/quic/build.info @@ -1,3 +1,3 @@ $LIBSSL=../../libssl -SOURCE[$LIBSSL]=quic_method.c quic_impl.c quic_wire.c +SOURCE[$LIBSSL]=quic_method.c quic_impl.c quic_wire.c quic_ackm.c quic_statm.c cc_dummy.c diff --git a/ssl/quic/cc_dummy.c b/ssl/quic/cc_dummy.c new file mode 100644 index 0000000000..a5f94d9498 --- /dev/null +++ b/ssl/quic/cc_dummy.c @@ -0,0 +1,106 @@ +/* + * 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_cc.h" + +typedef struct ossl_cc_dummy_st { + char dummy; +} OSSL_CC_DUMMY; + +static OSSL_CC_DATA *dummy_new(OSSL_PARAM *settings, OSSL_PARAM *options, + OSSL_PARAM *changeables) +{ + return OPENSSL_zalloc(sizeof(OSSL_CC_DUMMY)); +} + +static void dummy_free(OSSL_CC_DATA *cc) +{ + OPENSSL_free(cc); +} + +static void dummy_reset(OSSL_CC_DATA *cc, int flags) +{ + +} + +static int dummy_set_exemption(OSSL_CC_DATA *cc, int numpackets) +{ + return 1; +} + +static int dummy_get_exemption(OSSL_CC_DATA *cc) +{ + return 0; +} + +static int dummy_can_send(OSSL_CC_DATA *cc) +{ + return 1; +} + +static size_t dummy_get_send_allowance(OSSL_CC_DATA *cc, + OSSL_TIME time_since_last_send, + int time_valid) +{ + return SIZE_MAX; +} + +static size_t dummy_get_bytes_in_flight_max(OSSL_CC_DATA *cc) +{ + return SIZE_MAX; +} + +static int dummy_on_data_sent(OSSL_CC_DATA *cc, size_t num_retransmittable_bytes) +{ + return 1; +} + +static int dummy_on_data_invalidated(OSSL_CC_DATA *cc, + size_t num_retransmittable_bytes) +{ + return 1; +} + +static int dummy_on_data_acked(OSSL_CC_DATA *cc, OSSL_TIME time_now, + uint64_t last_pn_acked, + size_t num_retransmittable_bytes) +{ + return 1; +} + +static void dummy_on_data_lost(OSSL_CC_DATA *cc, + uint64_t largest_pn_lost, + uint64_t largest_pn_sent, + size_t num_retransmittable_bytes, + int persistent_congestion) +{ + +} + +static int dummy_on_spurious_congestion_event(OSSL_CC_DATA *cc) +{ + return 1; +} + +const OSSL_CC_METHOD ossl_cc_dummy_method = { + NULL, + dummy_new, + dummy_free, + dummy_reset, + dummy_set_exemption, + dummy_get_exemption, + dummy_can_send, + dummy_get_send_allowance, + dummy_get_bytes_in_flight_max, + dummy_on_data_sent, + dummy_on_data_invalidated, + dummy_on_data_acked, + dummy_on_data_lost, + dummy_on_spurious_congestion_event, +}; diff --git a/ssl/quic/quic_ackm.c b/ssl/quic/quic_ackm.c new file mode 100644 index 0000000000..670b2d316e --- /dev/null +++ b/ssl/quic/quic_ackm.c @@ -0,0 +1,2018 @@ +/* + * 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_ackm.h" +#include "internal/common.h" +#include + +/* + * TX Packet History + * ***************** + * + * The TX Packet History object tracks information about packets which have been + * sent for which we later expect to receive an ACK. It is essentially a simple + * database keeping a list of packet information structures in packet number + * order which can also be looked up directly by packet number. + * + * We currently only allow packets to be appended to the list (i.e. the packet + * numbers of the packets appended to the list must monotonically increase), as + * we should not currently need more general functionality such as a sorted list + * insert. + */ +struct tx_pkt_history_st { + /* A linked list of all our packets. */ + OSSL_ACKM_TX_PKT *head, *tail; + + /* Number of packets in the list. */ + size_t num_packets; + + /* + * Mapping from packet numbers (uint64_t) to (OSSL_ACKM_TX_PKT *) + * + * Invariant: A packet is in this map if and only if it is in the linked + * list. + */ + LHASH_OF(OSSL_ACKM_TX_PKT) *map; + + /* + * The lowest packet number which may currently be added to the history list + * (inclusive). We do not allow packet numbers to be added to the history + * list non-monotonically, so packet numbers must be greater than or equal + * to this value. + */ + uint64_t watermark; + + /* + * Packet number of the highest packet info structure we have yet appended + * to the list. This is usually one less than watermark, except when we have + * not added any packet yet. + */ + uint64_t highest_sent; +}; + +DEFINE_LHASH_OF_EX(OSSL_ACKM_TX_PKT); + +static unsigned long tx_pkt_info_hash(const OSSL_ACKM_TX_PKT *pkt) +{ + return pkt->pkt_num; +} + +static int tx_pkt_info_compare(const OSSL_ACKM_TX_PKT *a, + const OSSL_ACKM_TX_PKT *b) +{ + if (a->pkt_num < b->pkt_num) + return -1; + if (a->pkt_num > b->pkt_num) + return 1; + return 0; +} + +static int +tx_pkt_history_init(struct tx_pkt_history_st *h) +{ + h->head = h->tail = NULL; + h->num_packets = 0; + h->watermark = 0; + h->highest_sent = 0; + + h->map = lh_OSSL_ACKM_TX_PKT_new(tx_pkt_info_hash, tx_pkt_info_compare); + if (h->map == NULL) + return 0; + + return 1; +} + +static void +tx_pkt_history_destroy(struct tx_pkt_history_st *h) +{ + lh_OSSL_ACKM_TX_PKT_free(h->map); + h->map = NULL; + h->head = h->tail = NULL; +} + +static int +tx_pkt_history_add_actual(struct tx_pkt_history_st *h, + OSSL_ACKM_TX_PKT *pkt) +{ + OSSL_ACKM_TX_PKT *existing; + + /* + * There should not be any existing packet with this number + * in our mapping. + */ + existing = lh_OSSL_ACKM_TX_PKT_retrieve(h->map, pkt); + if (!ossl_assert(existing == NULL)) + return 0; + + /* Should not already be in a list. */ + if (!ossl_assert(pkt->next == NULL && pkt->prev == NULL)) + return 0; + + lh_OSSL_ACKM_TX_PKT_insert(h->map, pkt); + + pkt->next = NULL; + pkt->prev = h->tail; + if (h->tail != NULL) + h->tail->next = pkt; + h->tail = pkt; + if (h->head == NULL) + h->head = h->tail; + + ++h->num_packets; + return 1; +} + +/* Adds a packet information structure to the history list. */ +static int +tx_pkt_history_add(struct tx_pkt_history_st *h, + OSSL_ACKM_TX_PKT *pkt) +{ + if (!ossl_assert(pkt->pkt_num >= h->watermark)) + return 0; + + if (tx_pkt_history_add_actual(h, pkt) < 1) + return 0; + + h->watermark = pkt->pkt_num + 1; + h->highest_sent = pkt->pkt_num; + return 1; +} + +/* Retrieve a packet information structure by packet number. */ +static OSSL_ACKM_TX_PKT * +tx_pkt_history_by_pkt_num(struct tx_pkt_history_st *h, uint64_t pkt_num) +{ + OSSL_ACKM_TX_PKT key; + + key.pkt_num = pkt_num; + + return lh_OSSL_ACKM_TX_PKT_retrieve(h->map, &key); +} + +/* Remove a packet information structure from the history log. */ +static int +tx_pkt_history_remove(struct tx_pkt_history_st *h, uint64_t pkt_num) +{ + OSSL_ACKM_TX_PKT key, *pkt; + key.pkt_num = pkt_num; + + pkt = tx_pkt_history_by_pkt_num(h, pkt_num); + if (pkt == NULL) + return 0; + + if (pkt->prev != NULL) + pkt->prev->next = pkt->next; + if (pkt->next != NULL) + pkt->next->prev = pkt->prev; + if (h->head == pkt) + h->head = pkt->next; + if (h->tail == pkt) + h->tail = pkt->prev; + + pkt->prev = pkt->next = NULL; + + lh_OSSL_ACKM_TX_PKT_delete(h->map, &key); + --h->num_packets; + return 1; +} + +/* + * RX Packet Number Tracking + * ************************* + * + * **Background.** The RX side of the ACK manager must track packets we have + * received for which we have to generate ACK frames. Broadly, this means we + * store a set of packet numbers which we have received but which we do not know + * for a fact that the transmitter knows we have received. + * + * This must handle various situations: + * + * 1. We receive a packet but have not sent an ACK yet, so the transmitter + * does not know whether we have received it or not yet. + * + * 2. We receive a packet and send an ACK which is lost. We do not + * immediately know that the ACK was lost and the transmitter does not know + * that we have received the packet. + * + * 3. We receive a packet and send an ACK which is received by the + * transmitter. The transmitter does not immediately respond with an ACK, + * or responds with an ACK which is lost. The transmitter knows that we + * have received the packet, but we do not know for sure that it knows, + * because the ACK we sent could have been lost. + * + * 4. We receive a packet and send an ACK which is received by the + * transmitter. The transmitter subsequently sends us an ACK which confirms + * its receipt of the ACK we sent, and we successfully receive that ACK, so + * we know that the transmitter knows, that we received the original + * packet. + * + * Only when we reach case (4) are we relieved of any need to track a given + * packet number we have received, because only in this case do we know for sure + * that the peer knows we have received the packet. Having reached case (4) we + * will never again need to generate an ACK containing the PN in question, but + * until we reach that point, we must keep track of the PN as not having been + * provably ACKed, as we may have to keep generating ACKs for the given PN not + * just until the transmitter receives one, but until we know that it has + * received one. This will be referred to herein as "provably ACKed". + * + * **Duplicate handling.** The above discusses the case where we have received a + * packet with a given PN but are at best unsure whether the sender knows we + * have received it or not. However, we must also handle the case where we have + * yet to receive a packet with a given PN in the first place. The reason for + * this is because of the requirement expressed by RFC 9000 s. 12.3: + * + * "A receiver MUST discard a newly unprotected packet unless it is certain + * that it has not processed another packet with the same packet number from + * the same packet number space." + * + * We must ensure we never process a duplicate PN. As such, each possible PN we + * can receive must exist in one of the following logical states: + * + * - We have never processed this PN before + * (so if we receive such a PN, it can be processed) + * + * - We have processed this PN but it has not yet been provably ACKed + * (and should therefore be in any future ACK frame generated; + * if we receive such a PN again, it must be ignored) + * + * - We have processed this PN and it has been provably ACKed + * (if we receive such a PN again, it must be ignored) + * + * However, if we were to track this state for every PN ever used in the history + * of a connection, the amount of state required would increase unboundedly as + * the connection goes on (for example, we would have to store a set of every PN + * ever received.) + * + * RFC 9000 s. 12.3 continues: + * + * "Endpoints that track all individual packets for the purposes of detecting + * duplicates are at risk of accumulating excessive state. The data required + * for detecting duplicates can be limited by maintaining a minimum packet + * number below which all packets are immediately dropped." + * + * Moreover, RFC 9000 s. 13.2.3 states that: + * + * "A receiver MUST retain an ACK Range unless it can ensure that it will not + * subsequently accept packets with numbers in that range. Maintaining a + * minimum packet number that increases as ranges are discarded is one way to + * achieve this with minimal state." + * + * This touches on a subtlety of the original requirement quoted above: the + * receiver MUST discard a packet unless it is certain that it has not processed + * another packet with the same PN. However, this does not forbid the receiver + * from also discarding some PNs even though it has not yet processed them. In + * other words, implementations must be conservative and err in the direction of + * assuming a packet is a duplicate, but it is acceptable for this to come at + * the cost of falsely identifying some packets as duplicates. + * + * This allows us to bound the amount of state we must keep, and we adopt the + * suggested strategy quoted above to do so. We define a watermark PN below + * which all PNs are in the same state. This watermark is only ever increased. + * Thus the PNs the state for which needs to be explicitly tracked is limited to + * only a small number of recent PNs, and all older PNs have an assumed state. + * + * Any given PN thus falls into one of the following states: + * + * - (A) The PN is above the watermark but we have not yet received it. + * + * If we receive such a PN, we should process it and record the PN as + * received. + * + * - (B) The PN is above the watermark and we have received it. + * + * The PN should be included in any future ACK frame we generate. + * If we receive such a PN again, we should ignore it. + * + * - (C) The PN is below the watermark. + * + * We do not know whether a packet with the given PN was received or + * not. To be safe, if we receive such a packet, it is not processed. + * + * Note that state (C) corresponds to both "we have processed this PN and it has + * been provably ACKed" logical state and a subset of the PNs in the "we have + * never processed this PN before" logical state (namely all PNs which were lost + * and never received, but which are not recent enough to be above the + * watermark). The reason we can merge these states and avoid tracking states + * for the PNs in this state is because the provably ACKed and never-received + * states are functionally identical in terms of how we need to handle them: we + * don't need to do anything for PNs in either of these states, so we don't have + * to care about PNs in this state nor do we have to care about distinguishing + * the two states for a given PN. + * + * Note that under this scheme provably ACKed PNs are by definition always below + * the watermark; therefore, it follows that when a PN becomes provably ACKed, + * the watermark must be immediately increased to exceed it (otherwise we would + * keep reporting it in future ACK frames). + * + * This is in line with RFC 9000 s. 13.2.4's suggested strategy on when + * to advance the watermark: + * + * "When a packet containing an ACK frame is sent, the Largest Acknowledged + * field in that frame can be saved. When a packet containing an ACK frame is + * acknowledged, the receiver can stop acknowledging packets less than or + * equal to the Largest Acknowledged field in the sent ACK frame." + * + * This is where our scheme's false positives arise. When a packet containing an + * ACK frame is itself ACK'd, PNs referenced in that ACK frame become provably + * acked, and the watermark is bumped accordingly. However, the Largest + * Acknowledged field does not imply that all lower PNs have been received, + * because there may be gaps expressed in the ranges of PNs expressed by that + * and previous ACK frames. Thus, some unreceived PNs may be moved below the + * watermark, and we may subsequently reject those PNs as possibly being + * duplicates even though we have not actually received those PNs. Since we bump + * the watermark when a PN becomes provably ACKed, it follows that an unreceived + * PN falls below the watermark (and thus becomes a false positive for the + * purposes of duplicate detection) when a higher-numbered PN becomes provably + * ACKed. + * + * Thus, when PN n becomes provably acked, any unreceived PNs in the range [0, + * n) will no longer be processed. Although datagrams may be reordered in the + * network, a PN we receive can only become provably ACKed after our own + * subsequently generated ACK frame is sent in a future TX packet, and then we + * receive another RX PN acknowleding that TX packet. This means that a given RX + * PN can only become provably ACKed at least 1 RTT after it is received; it is + * unlikely that any reordered datagrams will still be "in the network" (and not + * lost) by this time. If this does occur for whatever reason and a late PN is + * received, the packet will be discarded unprocessed and the PN is simply + * handled as though lost (a "written off" PN). + * + * **Data structure.** Our state for the RX handling side of the ACK manager, as + * discussed above, mainly comprises: + * + * a) a logical set of PNs, and + * b) a monotonically increasing PN counter (the watermark). + * + * For (a), we define a data structure which stores a logical set of PNs, which + * we use to keep track of which PNs we have received but which have not yet + * been provably ACKed, and thus will later need to generate an ACK frame for. + * + * The correspondance with the logical states discussed above is as follows. A + * PN is in state (C) if it is below the watermark; otherwise it is in state (B) + * if it is in the logical set of PNs, and in state (A) otherwise. + * + * Note that PNs are only removed from the PN set (when they become provably + * ACKed or written off) by virtue of advancement of the watermark. Removing PNs + * from the PN set any other way would be ambiguous as it would be + * indistinguishable from a PN we have not yet received and risk us processing a + * duplicate packet. In other words, for a given PN: + * + * - State (A) can transition to state (B) or (C) + * - State (B) can transition to state (C) only + * - State (C) is the terminal state + * + * We can query the logical set data structure for PNs which have been received + * but which have not been provably ACKed when we want to generate ACK frames. + * Since ACK frames can be lost and/or we might not know that the peer has + * successfully received them, we might generate multiple ACK frames covering a + * given PN until that PN becomes provably ACKed and we finally remove it from + * our set (by bumping the watermark) as no longer being our concern. + * + * The data structure supports the following operations: + * + * Insert Range: Adds an inclusive range of packet numbers [start, end] + * to the set. Equivalent to Insert for each number + * in the range. (Used when we receive a new PN.) + * + * Remove Range: Removes an inclusive range of packet numbers [start, end] + * from the set. Not all of the range need already be in + * the set, but any part of the range in the set is removed. + * (Used when bumping the watermark.) + * + * Query: Is a PN in the data structure? + * + * The data structure can be iterated. + * + * For greater efficiency in tracking large numbers of contiguous PNs, we track + * PN ranges rather than individual PNs. The data structure manages a list of PN + * ranges [[start, end]...]. Internally this is implemented as a doubly linked + * sorted list of range structures, which are automatically split and merged as + * necessary. + * + * This data structure requires O(n) traversal of the list for insertion, + * removal and query when we are not adding/removing ranges which are near the + * beginning or end of the set of ranges. It is expected that the number of PN + * ranges needed at any given time will generally be small and that most + * operations will be close to the beginning or end of the range. + * + * Invariant: The data structure is always sorted in ascending order by PN. + * + * Invariant: No two adjacent ranges ever 'border' one another (have no + * numerical gap between them) as the data structure always ensures + * such ranges are merged. + * + * Invariant: No two ranges ever overlap. + * + * Invariant: No range [a, b] ever has a > b. + * + * Invariant: Since ranges are represented using inclusive bounds, no range + * item inside the data structure can represent a span of zero PNs. + * + * **Possible duplicates.** A PN is considered a possible duplicate when either: + * + * a) its PN is already in the PN set (i.e. has already been received), or + * b) its PN is below the watermark (i.e. was provably ACKed or written off). + * + * A packet with a given PN is considered 'processable' when that PN is not + * considered a possible duplicate (see ossl_ackm_is_rx_pn_processable). + * + * **TX/RX interaction.** The watermark is bumped whenever an RX packet becomes + * provably ACKed. This occurs when an ACK frame is received by the TX side of + * the ACK manager; thus, there is necessary interaction between the TX and RX + * sides of the ACK manager. + * + * This is implemented as follows. When a packet is queued as sent in the TX + * side of the ACK manager, it may optionally have a Largest Acked value set on + * it. The user of the ACK manager should do this if the packet being + * transmitted contains an ACK frame, by setting the field to the Largest Acked + * field of that frame. Otherwise, this field should be set to QUIC_PN_INVALID. + * When a TX packet is eventually acknowledged which has this field set, it is + * used to update the state of the RX side of the ACK manager by bumping the + * watermark accordingly. + */ +struct pn_set_item_st { + struct pn_set_item_st *prev, *next; + OSSL_QUIC_ACK_RANGE range; +}; + +struct pn_set_st { + struct pn_set_item_st *head, *tail; + + /* Number of ranges (not PNs) in the set. */ + size_t num_ranges; +}; + +static void pn_set_init(struct pn_set_st *s) +{ + s->head = s->tail = NULL; + s->num_ranges = 0; +} + +static void pn_set_destroy(struct pn_set_st *s) +{ + struct pn_set_item_st *x, *xnext; + + for (x = s->head; x != NULL; x = xnext) { + xnext = x->next; + OPENSSL_free(x); + } +} + +/* Possible merge of x, x->prev */ +static void pn_set_merge_adjacent(struct pn_set_st *s, struct pn_set_item_st *x) +{ + struct pn_set_item_st *xprev = x->prev; + + if (xprev == NULL) + return; + + if (x->range.start - 1 != xprev->range.end) + return; + + x->range.start = xprev->range.start; + x->prev = xprev->prev; + if (x->prev != NULL) + x->prev->next = x; + + if (s->head == xprev) + s->head = x; + + OPENSSL_free(xprev); + --s->num_ranges; +} + +/* Returns 1 if there exists a PN x which falls within both ranges a and b. */ +static int pn_range_overlaps(const OSSL_QUIC_ACK_RANGE *a, + const OSSL_QUIC_ACK_RANGE *b) +{ + return ossl_quic_pn_min(a->end, b->end) + >= ossl_quic_pn_max(a->start, b->start); +} + +/* + * Insert a range into a PN set. Returns 0 on allocation failure, in which case + * the PN set is in a valid but undefined state. Otherwise, returns 1. Ranges + * can overlap existing ranges without limitation. If a range is a subset of + * an existing range in the set, this is a no-op and returns 1. + */ +static int pn_set_insert(struct pn_set_st *s, const OSSL_QUIC_ACK_RANGE *range) +{ + struct pn_set_item_st *x, *z, *xnext, *f, *fnext; + QUIC_PN start = range->start, end = range->end; + + if (!ossl_assert(start <= end)) + return 0; + + if (s->head == NULL) { + /* Nothing in the set yet, so just add this range. */ + x = OPENSSL_zalloc(sizeof(struct pn_set_item_st)); + if (x == NULL) + return 0; + + x->range.start = start; + x->range.end = end; + s->head = s->tail = x; + ++s->num_ranges; + return 1; + } + + if (start > s->tail->range.end) { + /* + * Range is after the latest range in the set, so append. + * + * Note: The case where the range is before the earliest range in the + * set is handled as a degenerate case of the final case below. See + * optimization note (*) below. + */ + if (s->tail->range.end + 1 == start) { + s->tail->range.end = end; + return 1; + } + + x = OPENSSL_zalloc(sizeof(struct pn_set_item_st)); + if (x == NULL) + return 0; + + x->range.start = start; + x->range.end = end; + x->prev = s->tail; + if (s->tail != NULL) + s->tail->next = x; + s->tail = x; + ++s->num_ranges; + return 1; + } + + if (start <= s->head->range.start && end >= s->tail->range.end) { + /* + * New range dwarfs all ranges in our set. + * + * Free everything except the first range in the set, which we scavenge + * and reuse. + */ + for (x = s->head->next; x != NULL; x = xnext) { + xnext = x->next; + OPENSSL_free(x); + } + + s->head->range.start = start; + s->head->range.end = end; + s->head->next = s->head->prev = NULL; + s->tail = s->head; + s->num_ranges = 1; + return 1; + } + + /* + * Walk backwards since we will most often be inserting at the end. As an + * optimization, test the head node first and skip iterating over the + * entire list if we are inserting at the start. The assumption is that + * insertion at the start and end of the space will be the most common + * operations. (*) + */ + z = end < s->head->range.start ? s->head : s->tail; + + for (; z != NULL; z = z->prev) { + /* An existing range dwarfs our new range (optimisation). */ + if (z->range.start <= start && z->range.end >= end) + return 1; + + if (pn_range_overlaps(&z->range, range)) { + /* + * Our new range overlaps an existing range, or possibly several + * existing ranges. + */ + struct pn_set_item_st *ovend = z; + OSSL_QUIC_ACK_RANGE t; + size_t n = 0; + + t.end = ossl_quic_pn_max(end, z->range.end); + + /* Get earliest overlapping range. */ + for (; z->prev != NULL && pn_range_overlaps(&z->prev->range, range); + z = z->prev); + + t.start = ossl_quic_pn_min(start, z->range.start); + + /* Replace sequence of nodes z..ovend with ovend only. */ + ovend->range = t; + ovend->prev = z->prev; + if (z->prev != NULL) + z->prev->next = ovend; + if (s->head == z) + s->head = ovend; + + /* Free now unused nodes. */ + for (f = z; f != ovend; f = fnext, ++n) { + fnext = f->next; + OPENSSL_free(f); + } + + s->num_ranges -= n; + break; + } else if (end < z->range.start + && (z->prev == NULL || start > z->prev->range.end)) { + if (z->range.start == end + 1) { + /* We can extend the following range backwards. */ + z->range.start = start; + + /* + * If this closes a gap we now need to merge + * consecutive nodes. + */ + pn_set_merge_adjacent(s, z); + } else if (z->prev != NULL && z->prev->range.end + 1 == start) { + /* We can extend the preceding range forwards. */ + z->prev->range.end = end; + + /* + * If this closes a gap we now need to merge + * consecutive nodes. + */ + pn_set_merge_adjacent(s, z); + } else { + /* + * The new interval is between intervals without overlapping or + * touching them, so insert between, preserving sort. + */ + x = OPENSSL_zalloc(sizeof(struct pn_set_item_st)); + if (x == NULL) + return 0; + + x->range.start = start; + x->range.end = end; + + x->next = z; + x->prev = z->prev; + if (x->prev != NULL) + x->prev->next = x; + z->prev = x; + if (s->head == z) + s->head = x; + + ++s->num_ranges; + } + break; + } + } + + return 1; +} + +/* + * Remove a range from the set. Returns 0 on allocation failure, in which case + * the PN set is unchanged. Otherwise, returns 1. Ranges which are not already + * in the set can be removed without issue. If a passed range is not in the PN + * set at all, this is a no-op and returns 1. + */ +static int pn_set_remove(struct pn_set_st *s, const OSSL_QUIC_ACK_RANGE *range) +{ + struct pn_set_item_st *z, *zprev, *y; + QUIC_PN start = range->start, end = range->end; + + if (!ossl_assert(start <= end)) + return 0; + + /* Walk backwards since we will most often be removing at the end. */ + for (z = s->tail; z != NULL; z = zprev) { + zprev = z->prev; + + if (start > z->range.end) + /* No overlapping ranges can exist beyond this point, so stop. */ + break; + + if (start <= z->range.start && end >= z->range.end) { + /* + * The range being removed dwarfs this range, so it should be + * removed. + */ + if (z->next != NULL) + z->next->prev = z->prev; + if (z->prev != NULL) + z->prev->next = z->next; + if (s->head == z) + s->head = z->next; + if (s->tail == z) + s->tail = z->prev; + + OPENSSL_free(z); + --s->num_ranges; + } else if (start <= z->range.start) { + /* + * The range being removed includes start of this range, but does + * not cover the entire range (as this would be caught by the case + * above). Shorten the range. + */ + assert(end < z->range.end); + z->range.start = end + 1; + } else if (end >= z->range.end) { + /* + * The range being removed includes the end of this range, but does + * not cover the entire range (as this would be caught by the case + * above). Shorten the range. We can also stop iterating. + */ + assert(start > z->range.start); + assert(start > 0); + z->range.end = start - 1; + break; + } else if (start > z->range.start && end < z->range.end) { + /* + * The range being removed falls entirely in this range, so cut it + * into two. Cases where a zero-length range would be created are + * handled by the above cases. + */ + y = OPENSSL_zalloc(sizeof(struct pn_set_item_st)); + if (y == NULL) + return 0; + + y->range.end = z->range.end; + y->range.start = end + 1; + y->next = z->next; + y->prev = z; + if (y->next != NULL) + y->next->prev = y; + + z->range.end = start - 1; + z->next = y; + + if (s->tail == z) + s->tail = y; + + ++s->num_ranges; + break; + } else { + /* Assert no partial overlap; all cases should be covered above. */ + assert(!pn_range_overlaps(&z->range, range)); + } + } + + return 1; +} + +/* Returns 1 iff the given PN is in the PN set. */ +static int pn_set_query(const struct pn_set_st *s, QUIC_PN pn) +{ + struct pn_set_item_st *x; + + if (s->head == NULL) + return 0; + + for (x = s->tail; x != NULL; x = x->prev) + if (x->range.start <= pn && x->range.end >= pn) + return 1; + else if (x->range.end < pn) + return 0; + + return 0; +} + +struct rx_pkt_history_st { + struct pn_set_st set; + + /* + * Invariant: PNs below this are not in the set. + * Invariant: This is monotonic and only ever increases. + */ + QUIC_PN watermark; +}; + +static int rx_pkt_history_bump_watermark(struct rx_pkt_history_st *h, + QUIC_PN watermark); + +static void rx_pkt_history_init(struct rx_pkt_history_st *h) +{ + pn_set_init(&h->set); + h->watermark = 0; +} + +static void rx_pkt_history_destroy(struct rx_pkt_history_st *h) +{ + pn_set_destroy(&h->set); +} + +/* + * Limit the number of ACK ranges we store to prevent resource consumption DoS + * attacks. + */ +#define MAX_RX_ACK_RANGES 32 + +static void rx_pkt_history_trim_range_count(struct rx_pkt_history_st *h) +{ + QUIC_PN highest = QUIC_PN_INVALID; + + while (h->set.num_ranges > MAX_RX_ACK_RANGES) { + OSSL_QUIC_ACK_RANGE r = h->set.head->range; + + highest = (highest == QUIC_PN_INVALID) + ? r.end : ossl_quic_pn_max(highest, r.end); + + pn_set_remove(&h->set, &r); + } + + /* + * Bump watermark to cover all PNs we removed to avoid accidential + * reprocessing of packets. + */ + if (highest != QUIC_PN_INVALID) + rx_pkt_history_bump_watermark(h, highest + 1); +} + +static int rx_pkt_history_add_pn(struct rx_pkt_history_st *h, + QUIC_PN pn) +{ + OSSL_QUIC_ACK_RANGE r; + + r.start = pn; + r.end = pn; + + if (pn < h->watermark) + return 1; /* consider this a success case */ + + if (pn_set_insert(&h->set, &r) != 1) + return 0; + + rx_pkt_history_trim_range_count(h); + return 1; +} + +static int rx_pkt_history_bump_watermark(struct rx_pkt_history_st *h, + QUIC_PN watermark) +{ + OSSL_QUIC_ACK_RANGE r; + + if (watermark <= h->watermark) + return 1; + + /* Remove existing PNs below the watermark. */ + r.start = 0; + r.end = watermark - 1; + if (pn_set_remove(&h->set, &r) != 1) + return 0; + + h->watermark = watermark; + return 1; +} + +/* + * ACK Manager Implementation + * ************************** + * Implementation of the ACK manager proper. + */ + +/* Constants used by the ACK manager; see RFC 9002. */ +#define K_GRANULARITY (1 * OSSL_TIME_MS) +#define K_PKT_THRESHOLD 3 +#define K_TIME_THRESHOLD_NUM 9 +#define K_TIME_THRESHOLD_DEN 8 + +/* The maximum number of times we allow PTO to be doubled. */ +#define MAX_PTO_COUNT 16 + +struct ossl_ackm_st { + /* Our list of transmitted packets. Corresponds to RFC 9002 sent_packets. */ + struct tx_pkt_history_st tx_history[QUIC_PN_SPACE_NUM]; + + /* Our list of received PNs which are not yet provably acked. */ + struct rx_pkt_history_st rx_history[QUIC_PN_SPACE_NUM]; + + /* Polymorphic dependencies that we consume. */ + OSSL_TIME (*now)(void *arg); + void *now_arg; + OSSL_STATM *statm; + const OSSL_CC_METHOD *cc_method; + OSSL_CC_DATA *cc_data; + + /* RFC 9002 variables. */ + uint32_t pto_count; + QUIC_PN largest_acked_pkt[QUIC_PN_SPACE_NUM]; + OSSL_TIME time_of_last_ack_eliciting_pkt[QUIC_PN_SPACE_NUM]; + OSSL_TIME loss_time[QUIC_PN_SPACE_NUM]; + OSSL_TIME loss_detection_deadline; + + /* Lowest PN which is still not known to be ACKed. */ + QUIC_PN lowest_unacked_pkt[QUIC_PN_SPACE_NUM]; + + /* Time at which we got our first RTT sample, or 0. */ + OSSL_TIME first_rtt_sample; + + /* + * A packet's num_bytes are added to this if it is inflight, + * and removed again once ack'd/lost/discarded. + */ + uint64_t bytes_in_flight; + + /* + * A packet's num_bytes are added to this if it is both inflight and + * ack-eliciting, and removed again once ack'd/lost/discarded. + */ + uint64_t ack_eliciting_bytes_in_flight[QUIC_PN_SPACE_NUM]; + + /* Count of ECN-CE events. */ + uint64_t peer_ecnce[QUIC_PN_SPACE_NUM]; + + /* Set to 1 when the handshake is confirmed. */ + char handshake_confirmed; + + /* Set to 1 when the peer has completed address validation. */ + char peer_completed_addr_validation; + + /* Set to 1 when a PN space has been discarded. */ + char discarded[QUIC_PN_SPACE_NUM]; + + /* Set to 1 when we think an ACK frame should be generated. */ + char rx_ack_desired[QUIC_PN_SPACE_NUM]; + + /* Set to 1 if an ACK frame has ever been generated. */ + char rx_ack_generated[QUIC_PN_SPACE_NUM]; + + /* Probe request counts for reporting to the user. */ + OSSL_ACKM_PROBE_INFO pending_probe; + + /* Generated ACK frames for each PN space. */ + OSSL_QUIC_FRAME_ACK ack[QUIC_PN_SPACE_NUM]; + OSSL_QUIC_ACK_RANGE ack_ranges[QUIC_PN_SPACE_NUM][MAX_RX_ACK_RANGES]; + + /* Other RX state. */ + /* Largest PN we have RX'd. */ + QUIC_PN rx_largest_pn[QUIC_PN_SPACE_NUM]; + + /* Time at which the PN in rx_largest_pn was RX'd. */ + OSSL_TIME rx_largest_time[QUIC_PN_SPACE_NUM]; + + /* + * ECN event counters. Each time we receive a packet with a given ECN label, + * the corresponding ECN counter here is incremented. + */ + uint64_t rx_ect0[QUIC_PN_SPACE_NUM]; + uint64_t rx_ect1[QUIC_PN_SPACE_NUM]; + uint64_t rx_ecnce[QUIC_PN_SPACE_NUM]; + + /* + * Number of ACK-eliciting packets since last ACK. We use this to defer + * emitting ACK frames until a threshold number of ACK-eliciting packets + * have been received. + */ + uint32_t rx_ack_eliciting_pkts_since_last_ack[QUIC_PN_SPACE_NUM]; + + /* + * The ACK frame coalescing deadline at which we should flush any unsent ACK + * frames. + */ + OSSL_TIME rx_ack_flush_deadline[QUIC_PN_SPACE_NUM]; + + /* Callbacks for deadline updates. */ + void (*loss_detection_deadline_cb)(OSSL_TIME deadline, void *arg); + void *loss_detection_deadline_cb_arg; + + void (*ack_deadline_cb)(OSSL_TIME deadline, int pkt_space, void *arg); + void *ack_deadline_cb_arg; +}; + +static ossl_inline uint32_t min_u32(uint32_t x, uint32_t y) +{ + return x < y ? x : y; +} + +/* + * Get TX history for a given packet number space. Must not have been + * discarded. + */ +static struct tx_pkt_history_st *get_tx_history(OSSL_ACKM *ackm, int pkt_space) +{ + assert(!ackm->discarded[pkt_space]); + + return &ackm->tx_history[pkt_space]; +} + +/* + * Get RX history for a given packet number space. Must not have been + * discarded. + */ +static struct rx_pkt_history_st *get_rx_history(OSSL_ACKM *ackm, int pkt_space) +{ + assert(!ackm->discarded[pkt_space]); + + return &ackm->rx_history[pkt_space]; +} + +/* Does the newly-acknowledged list contain any ack-eliciting packet? */ +static int ack_includes_ack_eliciting(OSSL_ACKM_TX_PKT *pkt) +{ + for (; pkt != NULL; pkt = pkt->anext) + if (pkt->is_ack_eliciting) + return 1; + + return 0; +} + +/* Return number of ACK-eliciting bytes in flight across all PN spaces. */ +static uint64_t ackm_ack_eliciting_bytes_in_flight(OSSL_ACKM *ackm) +{ + int i; + uint64_t total = 0; + + for (i = 0; i < QUIC_PN_SPACE_NUM; ++i) + total += ackm->ack_eliciting_bytes_in_flight[i]; + + return total; +} + +/* Return 1 if the range contains the given PN. */ +static int range_contains(const OSSL_QUIC_ACK_RANGE *range, QUIC_PN pn) +{ + return pn >= range->start && pn <= range->end; +} + +/* + * Given a logical representation of an ACK frame 'ack', create a singly-linked + * list of the newly ACK'd frames; that is, of frames which are matched by the + * list of PN ranges contained in the ACK frame. The packet structures in the + * list returned are removed from the TX history list. Returns a pointer to the + * list head (or NULL) if empty. + */ +static OSSL_ACKM_TX_PKT *ackm_detect_and_remove_newly_acked_pkts(OSSL_ACKM *ackm, + const OSSL_QUIC_FRAME_ACK *ack, + int pkt_space) +{ + OSSL_ACKM_TX_PKT *acked_pkts = NULL, **fixup = &acked_pkts, *pkt, *pprev; + struct tx_pkt_history_st *h; + size_t ridx = 0; + + assert(ack->num_ack_ranges > 0); + + /* + * Our history list is a list of packets sorted in ascending order + * by packet number. + * + * ack->ack_ranges is a list of packet number ranges in descending order. + * + * Walk through our history list from the end in order to efficiently detect + * membership in the specified ack ranges. As an optimization, we use our + * hashtable to try and skip to the first matching packet. This may fail if + * the ACK ranges given include nonexistent packets. + */ + h = get_tx_history(ackm, pkt_space); + + pkt = tx_pkt_history_by_pkt_num(h, ack->ack_ranges[0].end); + if (pkt == NULL) + pkt = h->tail; + + for (; pkt != NULL; pkt = pprev) { + /* + * Save prev value as it will be zeroed if we remove the packet from the + * history list below. + */ + pprev = pkt->prev; + + for (;; ++ridx) { + if (ridx >= ack->num_ack_ranges) { + /* + * We have exhausted all ranges so stop here, even if there are + * more packets to look at. + */ + goto stop; + } + + if (range_contains(&ack->ack_ranges[ridx], pkt->pkt_num)) { + /* We have matched this range. */ + tx_pkt_history_remove(h, pkt->pkt_num); + + *fixup = pkt; + fixup = &pkt->anext; + *fixup = NULL; + break; + } else if (pkt->pkt_num > ack->ack_ranges[ridx].end) { + /* + * We have not reached this range yet in our list, so do not + * advance ridx. + */ + break; + } else { + /* + * We have moved beyond this range, so advance to the next range + * and try matching again. + */ + assert(pkt->pkt_num < ack->ack_ranges[ridx].start); + continue; + } + } + } +stop: + + return acked_pkts; +} + +/* + * Create a singly-linked list of newly detected-lost packets in the given + * packet number space. Returns the head of the list or NULL if no packets were + * detected lost. The packets in the list are removed from the TX history list. + */ +static OSSL_ACKM_TX_PKT *ackm_detect_and_remove_lost_pkts(OSSL_ACKM *ackm, + int pkt_space) +{ + OSSL_ACKM_TX_PKT *lost_pkts = NULL, **fixup = &lost_pkts, *pkt, *pnext; + OSSL_TIME loss_delay, lost_send_time, now; + OSSL_RTT_INFO rtt; + struct tx_pkt_history_st *h; + + assert(ackm->largest_acked_pkt[pkt_space] != QUIC_PN_INVALID); + + ossl_statm_get_rtt_info(ackm->statm, &rtt); + + ackm->loss_time[pkt_space] = 0; + + loss_delay = ossl_time_multiply(K_TIME_THRESHOLD_NUM, + ossl_time_max(rtt.latest_rtt, + rtt.smoothed_rtt)); + loss_delay = ossl_time_divide(loss_delay, K_TIME_THRESHOLD_DEN); + + /* Minimum time of K_GRANULARITY before packets are deemed lost. */ + loss_delay = ossl_time_max(loss_delay, K_GRANULARITY); + + /* Packets sent before this time are deemed lost. */ + now = ackm->now(ackm->now_arg); + lost_send_time = ossl_time_subtract(now, loss_delay); + + h = get_tx_history(ackm, pkt_space); + pkt = h->head; + + for (; pkt != NULL; pkt = pnext) { + assert(pkt_space == pkt->pkt_space); + + /* + * Save prev value as it will be zeroed if we remove the packet from the + * history list below. + */ + pnext = pkt->next; + + if (pkt->pkt_num > ackm->largest_acked_pkt[pkt_space]) + continue; + + /* + * Mark packet as lost, or set time when it should be marked. + */ + if (ossl_time_compare(pkt->time, lost_send_time) <= 0 + || ackm->largest_acked_pkt[pkt_space] + >= pkt->pkt_num + K_PKT_THRESHOLD) { + tx_pkt_history_remove(h, pkt->pkt_num); + + *fixup = pkt; + fixup = &pkt->lnext; + *fixup = NULL; + } else { + if (ossl_time_is_zero(ackm->loss_time[pkt_space])) + ackm->loss_time[pkt_space] = + ossl_time_add(pkt->time, loss_delay); + else + ackm->loss_time[pkt_space] = + ossl_time_min(ackm->loss_time[pkt_space], + ossl_time_add(pkt->time, loss_delay)); + } + } + + return lost_pkts; +} + +static OSSL_TIME ackm_get_loss_time_and_space(OSSL_ACKM *ackm, int *pspace) +{ + OSSL_TIME time = ackm->loss_time[QUIC_PN_SPACE_INITIAL]; + int i, space = QUIC_PN_SPACE_INITIAL; + + for (i = space + 1; i < QUIC_PN_SPACE_NUM; ++i) + if (ossl_time_is_zero(time) + || ossl_time_compare(ackm->loss_time[i], time) == -1) { + time = ackm->loss_time[i]; + space = i; + } + + *pspace = space; + return time; +} + +static OSSL_TIME ackm_get_pto_time_and_space(OSSL_ACKM *ackm, int *space) +{ + OSSL_RTT_INFO rtt; + OSSL_TIME duration; + OSSL_TIME pto_timeout = OSSL_TIME_INFINITY, t; + int pto_space = QUIC_PN_SPACE_INITIAL, i; + + ossl_statm_get_rtt_info(ackm->statm, &rtt); + + duration + = ossl_time_add(rtt.smoothed_rtt, + ossl_time_max(ossl_time_multiply(4, rtt.rtt_variance), + K_GRANULARITY)); + + duration + = ossl_time_multiply(duration, 1U << min_u32(ackm->pto_count, + MAX_PTO_COUNT)); + + /* Anti-deadlock PTO starts from the current time. */ + if (ackm_ack_eliciting_bytes_in_flight(ackm) == 0) { + assert(!ackm->peer_completed_addr_validation); + + *space = ackm->discarded[QUIC_PN_SPACE_INITIAL] + ? QUIC_PN_SPACE_HANDSHAKE + : QUIC_PN_SPACE_INITIAL; + return ossl_time_add(ackm->now(ackm->now_arg), duration); + } + + for (i = QUIC_PN_SPACE_INITIAL; i < QUIC_PN_SPACE_NUM; ++i) { + if (ackm->ack_eliciting_bytes_in_flight[i] == 0) + continue; + + if (i == QUIC_PN_SPACE_APP) { + /* Skip application data until handshake confirmed. */ + if (!ackm->handshake_confirmed) + break; + + /* Include max_ack_delay and backoff for app data. */ + if (!ossl_time_is_infinity(rtt.max_ack_delay)) + duration + = ossl_time_add(duration, + ossl_time_multiply(rtt.max_ack_delay, + 1U << min_u32(ackm->pto_count, + MAX_PTO_COUNT))); + } + + t = ossl_time_add(ackm->time_of_last_ack_eliciting_pkt[i], duration); + if (t < pto_timeout) { + pto_timeout = t; + pto_space = i; + } + } + + *space = pto_space; + return pto_timeout; +} + +static void ackm_set_loss_detection_timer_actual(OSSL_ACKM *ackm, + OSSL_TIME deadline) +{ + ackm->loss_detection_deadline = deadline; + + if (ackm->loss_detection_deadline_cb != NULL) + ackm->loss_detection_deadline_cb(deadline, + ackm->loss_detection_deadline_cb_arg); +} + +static int ackm_set_loss_detection_timer(OSSL_ACKM *ackm) +{ + int space; + OSSL_TIME earliest_loss_time, timeout; + + earliest_loss_time = ackm_get_loss_time_and_space(ackm, &space); + if (!ossl_time_is_zero(earliest_loss_time)) { + /* Time threshold loss detection. */ + ackm_set_loss_detection_timer_actual(ackm, earliest_loss_time); + return 1; + } + + if (ackm_ack_eliciting_bytes_in_flight(ackm) == 0 + && ackm->peer_completed_addr_validation) { + /* + * Nothing to detect lost, so no timer is set. However, the client + * needs to arm the timer if the server might be blocked by the + * anti-amplification limit. + */ + ackm_set_loss_detection_timer_actual(ackm, OSSL_TIME_ZERO); + return 1; + } + + timeout = ackm_get_pto_time_and_space(ackm, &space); + ackm_set_loss_detection_timer_actual(ackm, timeout); + return 1; +} + +static int ackm_in_persistent_congestion(OSSL_ACKM *ackm, + const OSSL_ACKM_TX_PKT *lpkt) +{ + /* Persistent congestion not currently implemented. */ + return 0; +} + +static void ackm_on_pkts_lost(OSSL_ACKM *ackm, int pkt_space, + const OSSL_ACKM_TX_PKT *lpkt) +{ + const OSSL_ACKM_TX_PKT *p, *pnext; + OSSL_RTT_INFO rtt; + QUIC_PN largest_pn_lost = 0; + uint64_t num_bytes = 0; + + for (p = lpkt; p != NULL; p = pnext) { + pnext = p->lnext; + + if (p->is_inflight) { + ackm->bytes_in_flight -= p->num_bytes; + if (p->is_ack_eliciting) + ackm->ack_eliciting_bytes_in_flight[p->pkt_space] + -= p->num_bytes; + + if (p->pkt_num > largest_pn_lost) + largest_pn_lost = p->pkt_num; + + num_bytes += p->num_bytes; + } + + p->on_lost(p->cb_arg); + } + + /* + * Only consider lost packets with regards to congestion after getting an + * RTT sample. + */ + ossl_statm_get_rtt_info(ackm->statm, &rtt); + + if (ackm->first_rtt_sample == 0) + return; + + ackm->cc_method->on_data_lost(ackm->cc_data, + largest_pn_lost, + ackm->tx_history[pkt_space].highest_sent, + num_bytes, + ackm_in_persistent_congestion(ackm, lpkt)); +} + +static void ackm_on_pkts_acked(OSSL_ACKM *ackm, const OSSL_ACKM_TX_PKT *apkt) +{ + const OSSL_ACKM_TX_PKT *anext; + OSSL_TIME now; + uint64_t num_retransmittable_bytes = 0; + QUIC_PN last_pn_acked = 0; + + now = ackm->now(ackm->now_arg); + + for (; apkt != NULL; apkt = anext) { + if (apkt->is_inflight) { + ackm->bytes_in_flight -= apkt->num_bytes; + if (apkt->is_ack_eliciting) + ackm->ack_eliciting_bytes_in_flight[apkt->pkt_space] + -= apkt->num_bytes; + + num_retransmittable_bytes += apkt->num_bytes; + if (apkt->pkt_num > last_pn_acked) + last_pn_acked = apkt->pkt_num; + + if (apkt->largest_acked != QUIC_PN_INVALID) + /* + * This can fail, but it is monotonic; worst case we try again + * next time. + */ + rx_pkt_history_bump_watermark(get_rx_history(ackm, + apkt->pkt_space), + apkt->largest_acked + 1); + } + + anext = apkt->anext; + apkt->on_acked(apkt->cb_arg); /* may free apkt */ + } + + ackm->cc_method->on_data_acked(ackm->cc_data, now, + last_pn_acked, num_retransmittable_bytes); +} + +OSSL_ACKM *ossl_ackm_new(OSSL_TIME (*now)(void *arg), + void *now_arg, + OSSL_STATM *statm, + const OSSL_CC_METHOD *cc_method, + OSSL_CC_DATA *cc_data) +{ + OSSL_ACKM *ackm; + int i; + + ackm = OPENSSL_zalloc(sizeof(OSSL_ACKM)); + if (ackm == NULL) + return NULL; + + for (i = 0; i < (int)OSSL_NELEM(ackm->tx_history); ++i) { + ackm->largest_acked_pkt[i] = QUIC_PN_INVALID; + ackm->rx_ack_flush_deadline[i] = OSSL_TIME_INFINITY; + if (tx_pkt_history_init(&ackm->tx_history[i]) < 1) + goto err; + } + + for (i = 0; i < (int)OSSL_NELEM(ackm->rx_history); ++i) + rx_pkt_history_init(&ackm->rx_history[i]); + + ackm->now = now; + ackm->now_arg = now_arg; + ackm->statm = statm; + ackm->cc_method = cc_method; + ackm->cc_data = cc_data; + return ackm; + +err: + while (--i >= 0) + tx_pkt_history_destroy(&ackm->tx_history[i]); + + OPENSSL_free(ackm); + return NULL; +} + +void ossl_ackm_free(OSSL_ACKM *ackm) +{ + size_t i; + + if (ackm == NULL) + return; + + for (i = 0; i < OSSL_NELEM(ackm->tx_history); ++i) + if (!ackm->discarded[i]) { + tx_pkt_history_destroy(&ackm->tx_history[i]); + rx_pkt_history_destroy(&ackm->rx_history[i]); + } + + OPENSSL_free(ackm); +} + +int ossl_ackm_on_tx_packet(OSSL_ACKM *ackm, OSSL_ACKM_TX_PKT *pkt) +{ + struct tx_pkt_history_st *h = get_tx_history(ackm, pkt->pkt_space); + + /* Time must be set and not move backwards. */ + if (ossl_time_is_zero(pkt->time) + || ossl_time_compare(ackm->time_of_last_ack_eliciting_pkt[pkt->pkt_space], + pkt->time) > 0) + return 0; + + /* Must have non-zero number of bytes. */ + if (pkt->num_bytes == 0) + return 0; + + if (tx_pkt_history_add(h, pkt) == 0) + return 0; + + if (pkt->is_inflight) { + if (pkt->is_ack_eliciting) { + ackm->time_of_last_ack_eliciting_pkt[pkt->pkt_space] = pkt->time; + ackm->ack_eliciting_bytes_in_flight[pkt->pkt_space] + += pkt->num_bytes; + } + + ackm->bytes_in_flight += pkt->num_bytes; + ackm_set_loss_detection_timer(ackm); + + ackm->cc_method->on_data_sent(ackm->cc_data, pkt->num_bytes); + } + + return 1; +} + +int ossl_ackm_on_rx_datagram(OSSL_ACKM *ackm, size_t num_bytes) +{ + /* No-op on the client. */ + return 1; +} + +static void ackm_on_congestion(OSSL_ACKM *ackm, OSSL_TIME send_time) +{ + /* Not currently implemented. */ +} + +static void ackm_process_ecn(OSSL_ACKM *ackm, const OSSL_QUIC_FRAME_ACK *ack, + int pkt_space) +{ + struct tx_pkt_history_st *h; + OSSL_ACKM_TX_PKT *pkt; + + /* + * If the ECN-CE counter reported by the peer has increased, this could + * be a new congestion event. + */ + if (ack->ecnce > ackm->peer_ecnce[pkt_space]) { + ackm->peer_ecnce[pkt_space] = ack->ecnce; + + h = get_tx_history(ackm, pkt_space); + pkt = tx_pkt_history_by_pkt_num(h, ack->ack_ranges[0].end); + if (pkt == NULL) + return; + + ackm_on_congestion(ackm, pkt->time); + } +} + +int ossl_ackm_on_rx_ack_frame(OSSL_ACKM *ackm, const OSSL_QUIC_FRAME_ACK *ack, + int pkt_space, OSSL_TIME rx_time) +{ + OSSL_ACKM_TX_PKT *na_pkts, *lost_pkts; + int must_set_timer = 0; + + if (ackm->largest_acked_pkt[pkt_space] == QUIC_PN_INVALID) + ackm->largest_acked_pkt[pkt_space] = ack->ack_ranges[0].end; + else + ackm->largest_acked_pkt[pkt_space] + = ossl_quic_pn_max(ackm->largest_acked_pkt[pkt_space], + ack->ack_ranges[0].end); + + /* + * If we get an ACK in the handshake space, address validation is completed. + * Make sure we update the timer, even if no packets were ACK'd. + */ + if (!ackm->peer_completed_addr_validation + && pkt_space == QUIC_PN_SPACE_HANDSHAKE) { + ackm->peer_completed_addr_validation = 1; + must_set_timer = 1; + } + + /* + * Find packets that are newly acknowledged and remove them from the list. + */ + na_pkts = ackm_detect_and_remove_newly_acked_pkts(ackm, ack, pkt_space); + if (na_pkts == NULL) { + if (must_set_timer) + ackm_set_loss_detection_timer(ackm); + + return 1; + } + + /* + * Update the RTT if the largest acknowledged is newly acked and at least + * one ACK-eliciting packet was newly acked. + * + * First packet in the list is always the one with the largest PN. + */ + if (na_pkts->pkt_num == ack->ack_ranges[0].end && + ack_includes_ack_eliciting(na_pkts)) { + OSSL_TIME now = ackm->now(ackm->now_arg), ack_delay; + if (ossl_time_is_zero(ackm->first_rtt_sample)) + ackm->first_rtt_sample = now; + + /* Enforce maximum ACK delay. */ + ack_delay = ack->delay_time; + if (ackm->handshake_confirmed) { + OSSL_RTT_INFO rtt; + + ossl_statm_get_rtt_info(ackm->statm, &rtt); + ack_delay = ossl_time_min(ack_delay, rtt.max_ack_delay); + } + + ossl_statm_update_rtt(ackm->statm, ack_delay, + ossl_time_subtract(now, na_pkts->time)); + } + + /* Process ECN information if present. */ + if (ack->ecn_present) + ackm_process_ecn(ackm, ack, pkt_space); + + /* Handle inferred loss. */ + lost_pkts = ackm_detect_and_remove_lost_pkts(ackm, pkt_space); + if (lost_pkts != NULL) + ackm_on_pkts_lost(ackm, pkt_space, lost_pkts); + + ackm_on_pkts_acked(ackm, na_pkts); + + /* + * Reset pto_count unless the client is unsure if the server validated the + * client's address. + */ + if (ackm->peer_completed_addr_validation) + ackm->pto_count = 0; + + ackm_set_loss_detection_timer(ackm); + return 1; +} + +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; + + if (pkt_space == QUIC_PN_SPACE_HANDSHAKE) + ackm->peer_completed_addr_validation = 1; + + for (pkt = get_tx_history(ackm, pkt_space)->head; pkt != NULL; pkt = pnext) { + pnext = pkt->next; + if (pkt->is_inflight) { + ackm->bytes_in_flight -= pkt->num_bytes; + num_bytes_invalidated += pkt->num_bytes; + } + + pkt->on_discarded(pkt->cb_arg); /* may free pkt */ + } + + tx_pkt_history_destroy(&ackm->tx_history[pkt_space]); + rx_pkt_history_destroy(&ackm->rx_history[pkt_space]); + + if (num_bytes_invalidated > 0) + ackm->cc_method->on_data_invalidated(ackm->cc_data, + num_bytes_invalidated); + + ackm->time_of_last_ack_eliciting_pkt[pkt_space] = OSSL_TIME_ZERO; + ackm->loss_time[pkt_space] = OSSL_TIME_ZERO; + ackm->pto_count = 0; + ackm->discarded[pkt_space] = 1; + ackm->ack_eliciting_bytes_in_flight[pkt_space] = 0; + ackm_set_loss_detection_timer(ackm); + return 1; +} + +int ossl_ackm_on_handshake_confirmed(OSSL_ACKM *ackm) +{ + ackm->handshake_confirmed = 1; + ackm->peer_completed_addr_validation = 1; + ackm_set_loss_detection_timer(ackm); + return 1; +} + +static void ackm_queue_probe_handshake(OSSL_ACKM *ackm) +{ + ++ackm->pending_probe.handshake; +} + +static void ackm_queue_probe_padded_initial(OSSL_ACKM *ackm) +{ + ++ackm->pending_probe.padded_initial; +} + +static void ackm_queue_probe(OSSL_ACKM *ackm, int pkt_space) +{ + ++ackm->pending_probe.pto[pkt_space]; +} + +int ossl_ackm_on_timeout(OSSL_ACKM *ackm) +{ + int pkt_space; + OSSL_TIME earliest_loss_time; + OSSL_ACKM_TX_PKT *lost_pkts; + + earliest_loss_time = ackm_get_loss_time_and_space(ackm, &pkt_space); + if (!ossl_time_is_zero(earliest_loss_time)) { + /* Time threshold loss detection. */ + lost_pkts = ackm_detect_and_remove_lost_pkts(ackm, pkt_space); + assert(lost_pkts != NULL); + ackm_on_pkts_lost(ackm, pkt_space, lost_pkts); + ackm_set_loss_detection_timer(ackm); + return 1; + } + + if (ackm_ack_eliciting_bytes_in_flight(ackm) == 0) { + assert(!ackm->peer_completed_addr_validation); + /* + * Client sends an anti-deadlock packet: Initial is padded to earn more + * anti-amplification credit. A handshake packet proves address + * ownership. + */ + if (ackm->discarded[QUIC_PN_SPACE_INITIAL]) + ackm_queue_probe_handshake(ackm); + else + ackm_queue_probe_padded_initial(ackm); + } else { + /* + * PTO. The user of the ACKM should send new data if available, else + * retransmit old data, or if neither is available, send a single PING + * frame. + */ + ackm_get_pto_time_and_space(ackm, &pkt_space); + ackm_queue_probe(ackm, pkt_space); + } + + ++ackm->pto_count; + ackm_set_loss_detection_timer(ackm); + return 1; +} + +OSSL_TIME ossl_ackm_get_loss_detection_deadline(OSSL_ACKM *ackm) +{ + return ackm->loss_detection_deadline; +} + +int ossl_ackm_get_probe_request(OSSL_ACKM *ackm, int clear, + OSSL_ACKM_PROBE_INFO *info) +{ + *info = ackm->pending_probe; + + if (clear != 0) + memset(&ackm->pending_probe, 0, sizeof(ackm->pending_probe)); + + return 1; +} + +int ossl_ackm_get_largest_unacked(OSSL_ACKM *ackm, int pkt_space, QUIC_PN *pn) +{ + struct tx_pkt_history_st *h; + + h = get_tx_history(ackm, pkt_space); + if (h->tail != NULL) { + *pn = h->tail->pkt_num; + return 1; + } + + return 0; +} + +/* Number of ACK-eliciting packets RX'd before we always emit an ACK. */ +#define PKTS_BEFORE_ACK 2 +/* Maximum amount of time to leave an ACK-eliciting packet un-ACK'd. */ +#define MAX_ACK_DELAY (ossl_time_multiply(OSSL_TIME_MS, 25)) + +/* + * Return 1 if emission of an ACK frame is currently desired. + * + * This occurs when one or more of the following conditions occurs: + * + * - We have flagged that we want to send an ACK frame + * (for example, due to the packet threshold count being exceeded), or + * + * - We have exceeded the ACK flush deadline, meaning that + * we have received at least one ACK-eliciting packet, but held off on + * sending an ACK frame immediately in the hope that more ACK-eliciting + * packets might come in, but not enough did and we are now requesting + * transmission of an ACK frame anyway. + * + */ +int ossl_ackm_is_ack_desired(OSSL_ACKM *ackm, int pkt_space) +{ + return ackm->rx_ack_desired[pkt_space] + || (!ossl_time_is_infinity(ackm->rx_ack_flush_deadline[pkt_space]) + && ossl_time_compare(ackm->now(ackm->now_arg), + ackm->rx_ack_flush_deadline[pkt_space]) >= 0); +} + +/* + * Returns 1 if an ACK frame matches a given packet number. + */ +static int ack_contains(const OSSL_QUIC_FRAME_ACK *ack, QUIC_PN pkt_num) +{ + size_t i; + + for (i = 0; i < ack->num_ack_ranges; ++i) + if (range_contains(&ack->ack_ranges[i], pkt_num)) + return 1; + + return 0; +} + +/* + * Returns 1 iff a PN (which we have just received) was previously reported as + * implied missing (by us, in an ACK frame we previously generated). + */ +static int ackm_is_missing(OSSL_ACKM *ackm, int pkt_space, QUIC_PN pkt_num) +{ + /* + * A PN is implied missing if it is not greater than the highest PN in our + * generated ACK frame, but is not matched by the frame. + */ + return ackm->ack[pkt_space].num_ack_ranges > 0 + && pkt_num <= ackm->ack[pkt_space].ack_ranges[0].end + && !ack_contains(&ackm->ack[pkt_space], pkt_num); +} + +/* + * Returns 1 iff our RX of a PN newly establishes the implication of missing + * packets. + */ +static int ackm_has_newly_missing(OSSL_ACKM *ackm, int pkt_space) +{ + struct rx_pkt_history_st *h; + + h = get_rx_history(ackm, pkt_space); + + if (h->set.tail == NULL) + return 0; + + /* + * The second condition here establishes that the highest PN range in our RX + * history comprises only a single PN. If there is more than one, then this + * function will have returned 1 during a previous call to + * ossl_ackm_on_rx_packet assuming the third condition below was met. Thus + * we only return 1 when the missing PN condition is newly established. + * + * The third condition here establishes that the highest PN range in our RX + * history is beyond (and does not border) the highest PN we have yet + * reported in any ACK frame. Thus there is a gap of at least one PN between + * the PNs we have ACK'd previously and the PN we have just received. + */ + return ackm->ack[pkt_space].num_ack_ranges > 0 + && h->set.tail->range.start == h->set.tail->range.end + && h->set.tail->range.start + > ackm->ack[pkt_space].ack_ranges[0].end + 1; +} + +static void ackm_set_flush_deadline(OSSL_ACKM *ackm, int pkt_space, + OSSL_TIME deadline) +{ + ackm->rx_ack_flush_deadline[pkt_space] = deadline; + + if (ackm->ack_deadline_cb != NULL) + ackm->ack_deadline_cb(ossl_ackm_get_ack_deadline(ackm, pkt_space), + pkt_space, ackm->ack_deadline_cb_arg); +} + +/* Explicitly flags that we want to generate an ACK frame. */ +static void ackm_queue_ack(OSSL_ACKM *ackm, int pkt_space) +{ + ackm->rx_ack_desired[pkt_space] = 1; + + /* Cancel deadline. */ + ackm_set_flush_deadline(ackm, pkt_space, OSSL_TIME_INFINITY); +} + +static void ackm_on_rx_ack_eliciting(OSSL_ACKM *ackm, + OSSL_TIME rx_time, int pkt_space, + int was_missing) +{ + if (ackm->rx_ack_desired[pkt_space]) + /* ACK generation already requested so nothing to do. */ + return; + + ++ackm->rx_ack_eliciting_pkts_since_last_ack[pkt_space]; + + if (!ackm->rx_ack_generated[pkt_space] + || was_missing + || ackm->rx_ack_eliciting_pkts_since_last_ack[pkt_space] + >= PKTS_BEFORE_ACK + || ackm_has_newly_missing(ackm, pkt_space)) { + /* + * Either: + * + * - We have never yet generated an ACK frame, meaning that this + * is the first ever packet received, which we should always + * acknowledge immediately, or + * + * - We previously reported the PN that we have just received as + * missing in a previous ACK frame (meaning that we should report + * the fact that we now have it to the peer immediately), or + * + * - We have exceeded the ACK-eliciting packet threshold count + * for the purposes of ACK coalescing, so request transmission + * of an ACK frame, or + * + * - The PN we just received and added to our PN RX history + * newly implies one or more missing PNs, in which case we should + * inform the peer by sending an ACK frame immediately. + * + * We do not test the ACK flush deadline here because it is tested + * separately in ossl_ackm_is_ack_desired. + */ + ackm_queue_ack(ackm, pkt_space); + return; + } + + /* + * Not emitting an ACK yet. + * + * Update the ACK flush deadline. + */ + if (ossl_time_is_infinity(ackm->rx_ack_flush_deadline[pkt_space])) + ackm_set_flush_deadline(ackm, pkt_space, + ossl_time_add(rx_time, MAX_ACK_DELAY)); + else + ackm_set_flush_deadline(ackm, pkt_space, + ossl_time_min(ackm->rx_ack_flush_deadline[pkt_space], + ossl_time_add(rx_time, + MAX_ACK_DELAY))); +} + +int ossl_ackm_on_rx_packet(OSSL_ACKM *ackm, const OSSL_ACKM_RX_PKT *pkt) +{ + struct rx_pkt_history_st *h = get_rx_history(ackm, pkt->pkt_space); + int was_missing; + + if (ossl_ackm_is_rx_pn_processable(ackm, pkt->pkt_num, pkt->pkt_space) != 1) + /* PN has already been processed or written off, no-op. */ + return 1; + + /* + * Record the largest PN we have RX'd and the time we received it. + * We use this to calculate the ACK delay field of ACK frames. + */ + if (pkt->pkt_num > ackm->rx_largest_pn[pkt->pkt_space]) { + ackm->rx_largest_pn[pkt->pkt_space] = pkt->pkt_num; + ackm->rx_largest_time[pkt->pkt_space] = pkt->time; + } + + /* + * If the PN we just received was previously implied missing by virtue of + * being omitted from a previous ACK frame generated, we skip any packet + * count thresholds or coalescing delays and emit a new ACK frame + * immediately. + */ + was_missing = ackm_is_missing(ackm, pkt->pkt_space, pkt->pkt_num); + + /* + * Add the packet number to our history list of PNs we have not yet provably + * acked. + */ + if (rx_pkt_history_add_pn(h, pkt->pkt_num) != 1) + return 0; + + /* + * Receiving this packet may or may not cause us to emit an ACK frame. + * We may not emit an ACK frame yet if we have not yet received a threshold + * number of packets. + */ + if (pkt->is_ack_eliciting) + ackm_on_rx_ack_eliciting(ackm, pkt->time, pkt->pkt_space, was_missing); + + /* Update the ECN counters according to which ECN signal we got, if any. */ + switch (pkt->ecn) { + case OSSL_ACKM_ECN_ECT0: + ++ackm->rx_ect0[pkt->pkt_space]; + break; + case OSSL_ACKM_ECN_ECT1: + ++ackm->rx_ect1[pkt->pkt_space]; + break; + case OSSL_ACKM_ECN_ECNCE: + ++ackm->rx_ecnce[pkt->pkt_space]; + break; + default: + break; + } + + return 1; +} + +static void ackm_fill_rx_ack_ranges(OSSL_ACKM *ackm, int pkt_space, + OSSL_QUIC_FRAME_ACK *ack) +{ + struct rx_pkt_history_st *h = get_rx_history(ackm, pkt_space); + struct pn_set_item_st *x; + size_t i = 0; + + /* + * Copy out ranges from the PN set, starting at the end, until we reach our + * maximum number of ranges. + */ + for (x = h->set.tail; + x != NULL && i < OSSL_NELEM(ackm->ack_ranges); + x = x->prev, ++i) + ackm->ack_ranges[pkt_space][i] = x->range; + + ack->ack_ranges = ackm->ack_ranges[pkt_space]; + ack->num_ack_ranges = i; +} + +const OSSL_QUIC_FRAME_ACK *ossl_ackm_get_ack_frame(OSSL_ACKM *ackm, + int pkt_space) +{ + OSSL_QUIC_FRAME_ACK *ack = &ackm->ack[pkt_space]; + OSSL_TIME now = ackm->now(ackm->now_arg); + + ackm_fill_rx_ack_ranges(ackm, pkt_space, ack); + + if (!ossl_time_is_zero(ackm->rx_largest_time[pkt_space]) + && ossl_time_compare(now, ackm->rx_largest_time[pkt_space]) > 0 + && pkt_space == QUIC_PN_SPACE_APP) + ack->delay_time = + ossl_time_subtract(now, ackm->rx_largest_time[pkt_space]); + else + ack->delay_time = OSSL_TIME_ZERO; + + ack->ect0 = ackm->rx_ect0[pkt_space]; + ack->ect1 = ackm->rx_ect1[pkt_space]; + ack->ecnce = ackm->rx_ecnce[pkt_space]; + ack->ecn_present = 1; + + ackm->rx_ack_eliciting_pkts_since_last_ack[pkt_space] = 0; + + ackm->rx_ack_generated[pkt_space] = 1; + ackm->rx_ack_desired[pkt_space] = 0; + ackm_set_flush_deadline(ackm, pkt_space, OSSL_TIME_INFINITY); + return ack; +} + + +OSSL_TIME ossl_ackm_get_ack_deadline(OSSL_ACKM *ackm, int pkt_space) +{ + if (ackm->rx_ack_desired[pkt_space]) + /* Already desired, deadline is now. */ + return OSSL_TIME_ZERO; + + return ackm->rx_ack_flush_deadline[pkt_space]; +} + +int ossl_ackm_is_rx_pn_processable(OSSL_ACKM *ackm, QUIC_PN pn, int pkt_space) +{ + struct rx_pkt_history_st *h = get_rx_history(ackm, pkt_space); + + return pn >= h->watermark && pn_set_query(&h->set, pn) == 0; +} + +void ossl_ackm_set_loss_detection_deadline_callback(OSSL_ACKM *ackm, + void (*fn)(OSSL_TIME deadline, + void *arg), + void *arg) +{ + ackm->loss_detection_deadline_cb = fn; + ackm->loss_detection_deadline_cb_arg = arg; +} + +void ossl_ackm_set_ack_deadline_callback(OSSL_ACKM *ackm, + void (*fn)(OSSL_TIME deadline, + int pkt_space, + void *arg), + void *arg) +{ + ackm->ack_deadline_cb = fn; + ackm->ack_deadline_cb_arg = arg; +} diff --git a/ssl/quic/quic_statm.c b/ssl/quic/quic_statm.c new file mode 100644 index 0000000000..35f5722735 --- /dev/null +++ b/ssl/quic/quic_statm.c @@ -0,0 +1,83 @@ +/* + * 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_statm.h" + +void ossl_statm_update_rtt(OSSL_STATM *statm, + OSSL_TIME ack_delay, + OSSL_TIME override_latest_rtt) +{ + OSSL_TIME adjusted_rtt, latest_rtt = override_latest_rtt; + + /* Use provided RTT value, or else last RTT value. */ + if (ossl_time_is_zero(latest_rtt)) + latest_rtt = statm->latest_rtt; + else + statm->latest_rtt = latest_rtt; + + if (!statm->have_first_sample) { + statm->min_rtt = latest_rtt; + statm->smoothed_rtt = latest_rtt; + statm->rtt_variance = ossl_time_divide(latest_rtt, 2); + statm->have_first_sample = 1; + return; + } + + /* Update minimum RTT. */ + if (ossl_time_compare(latest_rtt, statm->min_rtt) < 0) + statm->min_rtt = latest_rtt; + + /* + * Enforcement of max_ack_delay is the responsibility of + * the caller as it is context-dependent. + */ + + adjusted_rtt = latest_rtt; + if (ossl_time_compare(latest_rtt, ossl_time_add(statm->min_rtt, ack_delay)) >= 0) + adjusted_rtt = ossl_time_subtract(latest_rtt, ack_delay); + + statm->rtt_variance = ossl_time_divide(ossl_time_add(ossl_time_multiply(3, statm->rtt_variance), + ossl_time_abs_difference(statm->smoothed_rtt, + adjusted_rtt)), 4); + statm->smoothed_rtt = ossl_time_divide(ossl_time_add(ossl_time_multiply(7, statm->smoothed_rtt), + adjusted_rtt), 8); +} + +/* RFC 9002 kInitialRtt value. RFC recommended value. */ +#define K_INITIAL_RTT (ossl_time_multiply(OSSL_TIME_MS, 333)) + +int ossl_statm_init(OSSL_STATM *statm) +{ + statm->smoothed_rtt = K_INITIAL_RTT; + statm->latest_rtt = OSSL_TIME_ZERO; + statm->min_rtt = OSSL_TIME_INFINITY; + statm->rtt_variance = ossl_time_divide(K_INITIAL_RTT, 2); + statm->have_first_sample = 0; + statm->max_ack_delay = OSSL_TIME_INFINITY; + return 1; +} + +void ossl_statm_destroy(OSSL_STATM *statm) +{ + /* No-op. */ +} + +void ossl_statm_set_max_ack_delay(OSSL_STATM *statm, OSSL_TIME max_ack_delay) +{ + statm->max_ack_delay = max_ack_delay; +} + +void ossl_statm_get_rtt_info(OSSL_STATM *statm, OSSL_RTT_INFO *rtt_info) +{ + rtt_info->min_rtt = statm->min_rtt; + rtt_info->latest_rtt = statm->latest_rtt; + rtt_info->smoothed_rtt = statm->smoothed_rtt; + rtt_info->rtt_variance = statm->rtt_variance; + rtt_info->max_ack_delay = statm->max_ack_delay; +} diff --git a/test/build.info b/test/build.info index a74dd36de2..29d6af0709 100644 --- a/test/build.info +++ b/test/build.info @@ -964,13 +964,17 @@ ENDIF ENDIF IF[{- !$disabled{'quic'} -}] - PROGRAMS{noinst}=quicapitest quic_wire_test + PROGRAMS{noinst}=quicapitest quic_wire_test quic_ackm_test ENDIF SOURCE[quicapitest]=quicapitest.c helpers/ssltestlib.c INCLUDE[quicapitest]=../include ../apps/include DEPEND[quicapitest]=../libcrypto ../libssl libtestutil.a + SOURCE[quic_ackm_test]=quic_ackm_test.c + INCLUDE[quic_ackm_test]=../include ../apps/include + DEPEND[quic_ackm_test]=../libcrypto.a ../libssl.a libtestutil.a + {- use File::Spec::Functions; use File::Basename; diff --git a/test/quic_ackm_test.c b/test/quic_ackm_test.c new file mode 100644 index 0000000000..cea7c6fed9 --- /dev/null +++ b/test/quic_ackm_test.c @@ -0,0 +1,1040 @@ +/* + * 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 "testutil.h" +#include +#include "internal/quic_ackm.h" +#include "internal/quic_cc.h" + +static OSSL_TIME fake_time = 0; + +#define TIME_BASE (123 * OSSL_TIME_SECOND) + +static OSSL_TIME fake_now(void *arg) +{ + return fake_time; +} + +struct pkt_info { + OSSL_ACKM_TX_PKT *pkt; + int lost, acked, discarded; +}; + +static void on_lost(void *arg) +{ + struct pkt_info *info = arg; + ++info->lost; +} + +static void on_acked(void *arg) +{ + struct pkt_info *info = arg; + ++info->acked; +} + +static void on_discarded(void *arg) +{ + struct pkt_info *info = arg; + ++info->discarded; +} + +struct helper { + OSSL_ACKM *ackm; + struct pkt_info *pkts; + size_t num_pkts; + OSSL_CC_DATA *ccdata; + OSSL_STATM statm; + int have_statm; +}; + +static void helper_destroy(struct helper *h) +{ + size_t i; + + if (h->ackm != NULL) { + ossl_ackm_free(h->ackm); + h->ackm = NULL; + } + + if (h->ccdata != NULL) { + ossl_cc_dummy_method.free(h->ccdata); + h->ccdata = NULL; + } + + if (h->have_statm) { + ossl_statm_destroy(&h->statm); + h->have_statm = 0; + } + + if (h->pkts != NULL) { + for (i = 0; i < h->num_pkts; ++i) { + OPENSSL_free(h->pkts[i].pkt); + h->pkts[i].pkt = NULL; + } + + OPENSSL_free(h->pkts); + h->pkts = NULL; + } +} + +static int helper_init(struct helper *h, size_t num_pkts) +{ + int rc = 0; + + memset(h, 0, sizeof(*h)); + + fake_time = TIME_BASE; + + /* Initialise statistics tracker. */ + if (!TEST_int_eq(ossl_statm_init(&h->statm), 1)) + goto err; + + h->have_statm = 1; + + /* Initialise congestion controller. */ + h->ccdata = ossl_cc_dummy_method.new(NULL, NULL, NULL); + if (!TEST_ptr(h->ccdata)) + goto err; + + /* Initialise ACK manager. */ + h->ackm = ossl_ackm_new(fake_now, NULL, &h->statm, + &ossl_cc_dummy_method, h->ccdata); + if (!TEST_ptr(h->ackm)) + goto err; + + /* Allocate our array of packet information. */ + h->num_pkts = num_pkts; + if (num_pkts > 0) { + h->pkts = OPENSSL_zalloc(sizeof(struct pkt_info) * num_pkts); + if (!TEST_ptr(h->pkts)) + goto err; + } else { + h->pkts = NULL; + } + + rc = 1; +err: + if (rc == 0) + helper_destroy(h); + + return rc; +} + +static const QUIC_PN linear_20[] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 +}; + +static const QUIC_PN high_linear_20[] = { + 1000, 1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, + 1009, 1010, 1011, 1012, 1013, 1014, 1015, 1016, 1017, + 1018, 1019 +}; + +/* + * TX ACK (Packet Threshold) Test Cases + * ****************************************************************** + */ +struct tx_ack_test_case { + const QUIC_PN *pn_table; + size_t pn_table_len; + const OSSL_QUIC_ACK_RANGE *ack_ranges; + size_t num_ack_ranges; + const char *expect_ack; /* 1=ack, 2=lost, 4=discarded */ +}; + +#define DEFINE_TX_ACK_CASE(n, pntable) \ + static const struct tx_ack_test_case tx_ack_case_##n = { \ + (pntable), OSSL_NELEM(pntable), \ + tx_ack_range_##n, OSSL_NELEM(tx_ack_range_##n), \ + tx_ack_expect_##n \ + } + +/* One range, partial coverage of space */ +static const OSSL_QUIC_ACK_RANGE tx_ack_range_1[] = { + { 0, 10 }, +}; +static const char tx_ack_expect_1[] = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; +DEFINE_TX_ACK_CASE(1, linear_20); + +/* Two ranges, partial coverage of space, overlapping by 1 */ +static const OSSL_QUIC_ACK_RANGE tx_ack_range_2[] = { + { 5, 10 }, { 0, 5 } +}; +static const char tx_ack_expect_2[] = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; +DEFINE_TX_ACK_CASE(2, linear_20); + +/* Two ranges, partial coverage of space, together contiguous */ +static const OSSL_QUIC_ACK_RANGE tx_ack_range_3[] = { + { 6, 10 }, { 0, 5 } +}; +static const char tx_ack_expect_3[] = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; +DEFINE_TX_ACK_CASE(3, linear_20); + +/* + * Two ranges, partial coverage of space, non-contiguous by 1 + * Causes inferred loss due to packet threshold being exceeded. + */ +static const OSSL_QUIC_ACK_RANGE tx_ack_range_4[] = { + { 7, 10 }, { 0, 5 } +}; +static const char tx_ack_expect_4[] = { + 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; +DEFINE_TX_ACK_CASE(4, linear_20); + +/* + * Two ranges, partial coverage of space, non-contiguous by 2 + * Causes inferred loss due to packet threshold being exceeded. + */ +static const OSSL_QUIC_ACK_RANGE tx_ack_range_5[] = { + { 7, 10 }, { 0, 4 } +}; +static const char tx_ack_expect_5[] = { + 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; +DEFINE_TX_ACK_CASE(5, linear_20); + +/* One range, covering entire space */ +static const OSSL_QUIC_ACK_RANGE tx_ack_range_6[] = { + { 0, 20 }, +}; +static const char tx_ack_expect_6[] = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 +}; +DEFINE_TX_ACK_CASE(6, linear_20); + +/* One range, covering more space than exists */ +static const OSSL_QUIC_ACK_RANGE tx_ack_range_7[] = { + { 0, 30 }, +}; +static const char tx_ack_expect_7[] = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 +}; +DEFINE_TX_ACK_CASE(7, linear_20); + +/* One range, covering nothing (too high) */ +static const OSSL_QUIC_ACK_RANGE tx_ack_range_8[] = { + { 21, 30 }, +}; +static const char tx_ack_expect_8[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; +DEFINE_TX_ACK_CASE(8, linear_20); + +/* One range, covering nothing (too low) */ +static const OSSL_QUIC_ACK_RANGE tx_ack_range_9[] = { + { 0, 999 }, +}; +static const char tx_ack_expect_9[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; +DEFINE_TX_ACK_CASE(9, high_linear_20); + +/* One single packet at start of PN set */ +static const OSSL_QUIC_ACK_RANGE tx_ack_range_10[] = { + { 0, 0 }, +}; +static const char tx_ack_expect_10[] = { + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; +DEFINE_TX_ACK_CASE(10, linear_20); + +/* + * One single packet in middle of PN set + * Causes inferred loss of one packet due to packet threshold being exceeded, + * but several other previous packets survive as they are under the threshold. + */ +static const OSSL_QUIC_ACK_RANGE tx_ack_range_11[] = { + { 3, 3 }, +}; +static const char tx_ack_expect_11[] = { + 2, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; +DEFINE_TX_ACK_CASE(11, linear_20); + +/* + * One single packet at end of PN set + * Causes inferred loss due to packet threshold being exceeded. + */ +static const OSSL_QUIC_ACK_RANGE tx_ack_range_12[] = { + { 19, 19 }, +}; +static const char tx_ack_expect_12[] = { + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 1 +}; +DEFINE_TX_ACK_CASE(12, linear_20); + +/* + * Mixed straddling + * Causes inferred loss due to packet threshold being exceeded. + */ +static const OSSL_QUIC_ACK_RANGE tx_ack_range_13[] = { + { 1008, 1008 }, { 1004, 1005 }, { 1001, 1002 } +}; +static const char tx_ack_expect_13[] = { + 2, 1, 1, 2, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; +DEFINE_TX_ACK_CASE(13, high_linear_20); + +static const struct tx_ack_test_case *const tx_ack_cases[] = { + &tx_ack_case_1, + &tx_ack_case_2, + &tx_ack_case_3, + &tx_ack_case_4, + &tx_ack_case_5, + &tx_ack_case_6, + &tx_ack_case_7, + &tx_ack_case_8, + &tx_ack_case_9, + &tx_ack_case_10, + &tx_ack_case_11, + &tx_ack_case_12, + &tx_ack_case_13, +}; + +enum { + MODE_ACK, MODE_DISCARD, MODE_PTO, MODE_NUM +}; + +static int test_probe_counts(const OSSL_ACKM_PROBE_INFO *p, + uint32_t handshake, + uint32_t padded_initial, + uint32_t pto_initial, + uint32_t pto_handshake, + uint32_t pto_app) +{ + if (!TEST_uint_eq(p->handshake, handshake)) + return 0; + if (!TEST_uint_eq(p->padded_initial, padded_initial)) + return 0; + if (!TEST_uint_eq(p->pto[QUIC_PN_SPACE_INITIAL], pto_initial)) + return 0; + if (!TEST_uint_eq(p->pto[QUIC_PN_SPACE_HANDSHAKE], pto_handshake)) + return 0; + if (!TEST_uint_eq(p->pto[QUIC_PN_SPACE_APP], pto_app)) + return 0; + return 1; +} + +static void on_loss_detection_deadline_callback(OSSL_TIME deadline, void *arg) +{ + *(OSSL_TIME *)arg = deadline; +} + +static int test_tx_ack_case_actual(int tidx, int space, int mode) +{ + int testresult = 0; + struct helper h; + size_t i; + OSSL_ACKM_TX_PKT *tx; + const struct tx_ack_test_case *c = tx_ack_cases[tidx]; + OSSL_QUIC_FRAME_ACK ack = {0}; + OSSL_TIME loss_detection_deadline = OSSL_TIME_ZERO; + + /* Cannot discard app space, so skip this */ + if (mode == MODE_DISCARD && space == QUIC_PN_SPACE_APP) { + TEST_skip("skipping test for app space"); + return 1; + } + + if (!TEST_int_eq(helper_init(&h, c->pn_table_len), 1)) + goto err; + + /* Arm callback. */ + ossl_ackm_set_loss_detection_deadline_callback(h.ackm, + on_loss_detection_deadline_callback, + &loss_detection_deadline); + + /* Allocate TX packet structures. */ + for (i = 0; i < c->pn_table_len; ++i) { + h.pkts[i].pkt = tx = OPENSSL_zalloc(sizeof(*tx)); + if (!TEST_ptr(tx)) + goto err; + + tx->pkt_num = c->pn_table[i]; + tx->pkt_space = space; + tx->is_inflight = 1; + tx->is_ack_eliciting = 1; + tx->num_bytes = 123; + tx->largest_acked = QUIC_PN_INVALID; + tx->on_lost = on_lost; + tx->on_acked = on_acked; + tx->on_discarded = on_discarded; + tx->cb_arg = &h.pkts[i]; + + tx->time = fake_time; + + if (!TEST_int_eq(ossl_ackm_on_tx_packet(h.ackm, tx), 1)) + goto err; + } + + if (mode == MODE_DISCARD) { + /* Try discarding. */ + if (!TEST_int_eq(ossl_ackm_on_pkt_space_discarded(h.ackm, space), 1)) + goto err; + + /* Check all discard callbacks were called. */ + for (i = 0; i < c->pn_table_len; ++i) { + if (!TEST_int_eq(h.pkts[i].acked, 0)) + goto err; + if (!TEST_int_eq(h.pkts[i].lost, 0)) + goto err; + if (!TEST_int_eq(h.pkts[i].discarded, 1)) + goto err; + } + } else if (mode == MODE_ACK) { + /* Try acknowledging. */ + ack.ack_ranges = (OSSL_QUIC_ACK_RANGE *)c->ack_ranges; + ack.num_ack_ranges = c->num_ack_ranges; + if (!TEST_int_eq(ossl_ackm_on_rx_ack_frame(h.ackm, &ack, space, fake_time), 1)) + goto err; + + /* Check correct ranges were acknowledged. */ + for (i = 0; i < c->pn_table_len; ++i) { + if (!TEST_int_eq(h.pkts[i].acked, + (c->expect_ack[i] & 1) != 0 ? 1 : 0)) + goto err; + if (!TEST_int_eq(h.pkts[i].lost, + (c->expect_ack[i] & 2) != 0 ? 1 : 0)) + goto err; + if (!TEST_int_eq(h.pkts[i].discarded, + (c->expect_ack[i] & 4) != 0 ? 1 : 0)) + goto err; + } + } else if (mode == MODE_PTO) { + OSSL_TIME deadline = ossl_ackm_get_loss_detection_deadline(h.ackm); + OSSL_ACKM_PROBE_INFO probe = {0}; + + if (!TEST_true(deadline == loss_detection_deadline)) + goto err; + + /* We should have a PTO deadline. */ + if (!TEST_true(deadline > fake_time)) + goto err; + + /* Should not have any probe requests yet. */ + if (!TEST_int_eq(ossl_ackm_get_probe_request(h.ackm, 0, &probe), 1)) + goto err; + + if (!TEST_int_eq(test_probe_counts(&probe, 0, 0, 0, 0, 0), 1)) + goto err; + + /* + * If in app space, confirm handshake, as this is necessary to enable + * app space PTO probe requests. + */ + if (space == QUIC_PN_SPACE_APP) + if (!TEST_int_eq(ossl_ackm_on_handshake_confirmed(h.ackm), 1)) + goto err; + + /* Advance to the PTO deadline. */ + fake_time = deadline + 1; + + if (!TEST_int_eq(ossl_ackm_on_timeout(h.ackm), 1)) + goto err; + + /* Should have a probe request. Not cleared by first call. */ + for (i = 0; i < 3; ++i) { + if (!TEST_int_eq(ossl_ackm_get_probe_request(h.ackm, i > 0, &probe), 1)) + goto err; + + if (i == 2) { + if (!TEST_int_eq(test_probe_counts(&probe, 0, 0, 0, 0, 0), 1)) + goto err; + } else { + if (!TEST_int_eq(test_probe_counts(&probe, 0, 0, + space == QUIC_PN_SPACE_INITIAL, + space == QUIC_PN_SPACE_HANDSHAKE, + space == QUIC_PN_SPACE_APP), 1)) + goto err; + } + } + + } else + goto err; + + testresult = 1; +err: + helper_destroy(&h); + return testresult; +} + +/* + * TX ACK (Time Threshold) Test + * ****************************************************************** + */ +enum { + TX_ACK_TIME_OP_END, + TX_ACK_TIME_OP_PKT, /* TX packets */ + TX_ACK_TIME_OP_ACK, /* Synthesise incoming ACK of single PN range */ + TX_ACK_TIME_OP_EXPECT /* Ack/loss assertion */ +}; + +struct tx_ack_time_op { + int kind; + OSSL_TIME time_advance; /* all ops */ + QUIC_PN pn; /* PKT, ACK */ + size_t num_pn; /* PKT, ACK */ + const char *expect; /* 1=ack, 2=lost, 4=discarded */ +}; + +#define TX_OP_PKT(advance, pn, num_pn) \ + { TX_ACK_TIME_OP_PKT, (advance) * OSSL_TIME_MS, (pn), (num_pn), NULL }, +#define TX_OP_ACK(advance, pn, num_pn) \ + { TX_ACK_TIME_OP_ACK, (advance) * OSSL_TIME_MS, (pn), (num_pn), NULL }, +#define TX_OP_EXPECT(expect) \ + { TX_ACK_TIME_OP_EXPECT, 0, 0, 0, (expect) }, +#define TX_OP_END { TX_ACK_TIME_OP_END } + +static const char tx_ack_time_script_1_expect[] = { + 2, 1 +}; + +static const struct tx_ack_time_op tx_ack_time_script_1[] = { + TX_OP_PKT ( 0, 0, 1) + TX_OP_PKT (3600000, 1, 1) + TX_OP_ACK ( 1000, 1, 1) + TX_OP_EXPECT(tx_ack_time_script_1_expect) + TX_OP_END +}; + +static const struct tx_ack_time_op *const tx_ack_time_scripts[] = { + tx_ack_time_script_1, +}; + +static int test_tx_ack_time_script(int tidx) +{ + int testresult = 0; + struct helper h; + OSSL_ACKM_TX_PKT *tx = NULL; + OSSL_QUIC_FRAME_ACK ack = {0}; + OSSL_QUIC_ACK_RANGE ack_range = {0}; + size_t i, num_pkts = 0, pkt_idx = 0; + const struct tx_ack_time_op *script = tx_ack_time_scripts[tidx], *s; + + /* Calculate number of packets. */ + for (s = script; s->kind != TX_ACK_TIME_OP_END; ++s) + if (s->kind == TX_ACK_TIME_OP_PKT) + num_pkts += s->num_pn; + + /* Initialise ACK manager and packet structures. */ + if (!TEST_int_eq(helper_init(&h, num_pkts), 1)) + goto err; + + for (i = 0; i < num_pkts; ++i) { + h.pkts[i].pkt = tx = OPENSSL_zalloc(sizeof(*tx)); + if (!TEST_ptr(tx)) + goto err; + } + + /* Run script. */ + for (s = script; s->kind != TX_ACK_TIME_OP_END; ++s) + switch (s->kind) { + case TX_ACK_TIME_OP_PKT: + for (i = 0; i < s->num_pn; ++i) { + tx = h.pkts[pkt_idx + i].pkt; + + tx->pkt_num = s->pn + i; + tx->pkt_space = QUIC_PN_SPACE_INITIAL; + tx->num_bytes = 123; + tx->largest_acked = QUIC_PN_INVALID; + tx->is_inflight = 1; + tx->is_ack_eliciting = 1; + tx->on_lost = on_lost; + tx->on_acked = on_acked; + tx->on_discarded = on_discarded; + tx->cb_arg = &h.pkts[pkt_idx + i]; + + fake_time += s->time_advance; + tx->time = fake_time; + + if (!TEST_int_eq(ossl_ackm_on_tx_packet(h.ackm, tx), 1)) + goto err; + } + + pkt_idx += s->num_pn; + break; + + case TX_ACK_TIME_OP_ACK: + ack.ack_ranges = &ack_range; + ack.num_ack_ranges = 1; + + ack_range.start = s->pn; + ack_range.end = s->pn + s->num_pn; + + fake_time += s->time_advance; + + if (!TEST_int_eq(ossl_ackm_on_rx_ack_frame(h.ackm, &ack, + QUIC_PN_SPACE_INITIAL, + fake_time), 1)) + goto err; + + break; + + case TX_ACK_TIME_OP_EXPECT: + for (i = 0; i < num_pkts; ++i) { + if (!TEST_int_eq(h.pkts[i].acked, + (s->expect[i] & 1) != 0 ? 1 : 0)) + goto err; + if (!TEST_int_eq(h.pkts[i].lost, + (s->expect[i] & 2) != 0 ? 1 : 0)) + goto err; + if (!TEST_int_eq(h.pkts[i].discarded, + (s->expect[i] & 4) != 0 ? 1 : 0)) + goto err; + } + + break; + } + + testresult = 1; +err: + helper_destroy(&h); + return testresult; +} + +/* + * RX ACK Test + * ****************************************************************** + */ +enum { + RX_OPK_END, + RX_OPK_PKT, /* RX packet */ + RX_OPK_CHECK_UNPROC, /* check PNs unprocessable */ + RX_OPK_CHECK_PROC, /* check PNs processable */ + RX_OPK_CHECK_STATE, /* check is_desired/deadline */ + RX_OPK_CHECK_ACKS, /* check ACK ranges */ + RX_OPK_TX, /* TX packet */ + RX_OPK_RX_ACK /* RX ACK frame */ +}; + +struct rx_test_op { + int kind; + OSSL_TIME time_advance; + + QUIC_PN pn; /* PKT, CHECK_(UN)PROC, TX, RX_ACK */ + size_t num_pn; /* PKT, CHECK_(UN)PROC, TX, RX_ACK */ + + char expect_desired; /* CHECK_STATE */ + char expect_deadline; /* CHECK_STATE */ + + const OSSL_QUIC_ACK_RANGE *ack_ranges; /* CHECK_ACKS */ + size_t num_ack_ranges; /* CHECK_ACKS */ + + QUIC_PN largest_acked; /* TX */ +}; + +#define RX_OP_PKT(advance, pn, num_pn) \ + { \ + RX_OPK_PKT, (advance) * OSSL_TIME_MS, (pn), (num_pn), \ + 0, 0, NULL, 0, 0 \ + }, + +#define RX_OP_CHECK_UNPROC(advance, pn, num_pn) \ + { \ + RX_OPK_CHECK_UNPROC, (advance) * OSSL_TIME_MS, (pn), (num_pn),\ + 0, 0, NULL, 0, 0 \ + }, + +#define RX_OP_CHECK_PROC(advance, pn, num_pn) \ + { \ + RX_OPK_CHECK_PROC, (advance) * OSSL_TIME_MS, (pn), (num_pn), \ + 0, 0, NULL, 0, 0 \ + }, + +#define RX_OP_CHECK_STATE(advance, expect_desired, expect_deadline) \ + { \ + RX_OPK_CHECK_STATE, (advance) * OSSL_TIME_MS, 0, 0, \ + (expect_desired), (expect_deadline), NULL, 0, 0 \ + }, + +#define RX_OP_CHECK_ACKS(advance, ack_ranges) \ + { \ + RX_OPK_CHECK_ACKS, (advance) * OSSL_TIME_MS, 0, 0, \ + 0, 0, (ack_ranges), OSSL_NELEM(ack_ranges), 0 \ + }, + +#define RX_OP_CHECK_NO_ACKS(advance) \ + { \ + RX_OPK_CHECK_ACKS, (advance) * OSSL_TIME_MS, 0, 0, \ + 0, 0, NULL, 0, 0 \ + }, + +#define RX_OP_TX(advance, pn, largest_acked) \ + { \ + RX_OPK_TX, (advance) * OSSL_TIME_MS, (pn), 1, \ + 0, 0, NULL, 0, (largest_acked) \ + }, + +#define RX_OP_RX_ACK(advance, pn, num_pn) \ + { \ + RX_OPK_RX_ACK, (advance) * OSSL_TIME_MS, (pn), (num_pn), \ + 0, 0, NULL, 0, 0 \ + }, + +#define RX_OP_END \ + { RX_OPK_END } + +/* RX 1. Simple Test with ACK Desired (Packet Threshold, Exactly) */ +static const OSSL_QUIC_ACK_RANGE rx_ack_ranges_1a[] = { + { 0, 1 } +}; + +static const struct rx_test_op rx_script_1[] = { + RX_OP_CHECK_STATE (0, 0, 0) /* no threshold yet */ + RX_OP_CHECK_PROC (0, 0, 3) + + RX_OP_PKT (0, 0, 2) /* two packets, threshold */ + RX_OP_CHECK_UNPROC (0, 0, 2) + RX_OP_CHECK_PROC (0, 2, 1) + RX_OP_CHECK_STATE (0, 1, 0) /* threshold met, immediate */ + RX_OP_CHECK_ACKS (0, rx_ack_ranges_1a) + + /* At this point we would generate e.g. a packet with an ACK. */ + RX_OP_TX (0, 0, 1) /* ACKs both */ + RX_OP_CHECK_ACKS (0, rx_ack_ranges_1a) /* not provably ACKed yet */ + RX_OP_RX_ACK (0, 0, 1) /* TX'd packet is ACK'd */ + + RX_OP_CHECK_NO_ACKS (0) /* nothing more to ACK */ + RX_OP_CHECK_UNPROC (0, 0, 2) /* still unprocessable */ + RX_OP_CHECK_PROC (0, 2, 1) /* still processable */ + + RX_OP_END +}; + +/* RX 2. Simple Test with ACK Not Yet Desired (Packet Threshold) */ +static const OSSL_QUIC_ACK_RANGE rx_ack_ranges_2a[] = { + { 0, 0 } +}; + +static const OSSL_QUIC_ACK_RANGE rx_ack_ranges_2b[] = { + { 0, 2 } +}; + +static const struct rx_test_op rx_script_2[] = { + RX_OP_CHECK_STATE (0, 0, 0) /* no threshold yet */ + RX_OP_CHECK_PROC (0, 0, 3) + + /* First packet always generates an ACK so get it out of the way. */ + RX_OP_PKT (0, 0, 1) + RX_OP_CHECK_UNPROC (0, 0, 1) + RX_OP_CHECK_PROC (0, 1, 1) + RX_OP_CHECK_STATE (0, 1, 0) /* first packet always causes ACK */ + RX_OP_CHECK_ACKS (0, rx_ack_ranges_2a) /* clears packet counter */ + RX_OP_CHECK_STATE (0, 0, 0) /* desired state should have been cleared */ + + /* Second packet should not cause ACK-desired state */ + RX_OP_PKT (0, 1, 1) /* just one packet, threshold is 2 */ + RX_OP_CHECK_UNPROC (0, 0, 2) + RX_OP_CHECK_PROC (0, 2, 1) + RX_OP_CHECK_STATE (0, 0, 1) /* threshold not yet met, so deadline */ + /* Don't check ACKs here, as it would reset our threshold counter. */ + + /* Now receive a second packet, triggering the threshold */ + RX_OP_PKT (0, 2, 1) /* second packet meets threshold */ + RX_OP_CHECK_UNPROC (0, 0, 3) + RX_OP_CHECK_PROC (0, 3, 1) + RX_OP_CHECK_STATE (0, 1, 0) /* desired immediately */ + RX_OP_CHECK_ACKS (0, rx_ack_ranges_2b) + + /* At this point we would generate e.g. a packet with an ACK. */ + RX_OP_TX (0, 0, 2) /* ACKs all */ + RX_OP_CHECK_ACKS (0, rx_ack_ranges_2b) /* not provably ACKed yet */ + RX_OP_RX_ACK (0, 0, 1) /* TX'd packet is ACK'd */ + + RX_OP_CHECK_NO_ACKS (0) /* nothing more to ACK */ + RX_OP_CHECK_UNPROC (0, 0, 3) /* still unprocessable */ + RX_OP_CHECK_PROC (0, 3, 1) /* still processable */ + + RX_OP_END +}; + +/* RX 3. Simple Test with ACK Desired (Packet Threshold, Multiple Watermarks) */ +static const OSSL_QUIC_ACK_RANGE rx_ack_ranges_3a[] = { + { 0, 0 } +}; + +static const OSSL_QUIC_ACK_RANGE rx_ack_ranges_3b[] = { + { 0, 10 } +}; + +static const OSSL_QUIC_ACK_RANGE rx_ack_ranges_3c[] = { + { 6, 10 } +}; + +static const struct rx_test_op rx_script_3[] = { + RX_OP_CHECK_STATE (0, 0, 0) /* no threshold yet */ + RX_OP_CHECK_PROC (0, 0, 11) + + /* First packet always generates an ACK so get it out of the way. */ + RX_OP_PKT (0, 0, 1) + RX_OP_CHECK_UNPROC (0, 0, 1) + RX_OP_CHECK_PROC (0, 1, 1) + RX_OP_CHECK_STATE (0, 1, 0) /* first packet always causes ACK */ + RX_OP_CHECK_ACKS (0, rx_ack_ranges_3a) /* clears packet counter */ + RX_OP_CHECK_STATE (0, 0, 0) /* desired state should have been cleared */ + + /* Generate ten packets, exceeding the threshold. */ + RX_OP_PKT (0, 1, 10) /* ten packets, threshold is 2 */ + RX_OP_CHECK_UNPROC (0, 0, 11) + RX_OP_CHECK_PROC (0, 11, 1) + RX_OP_CHECK_STATE (0, 1, 0) /* threshold met, immediate */ + RX_OP_CHECK_ACKS (0, rx_ack_ranges_3b) + + /* + * Test TX'ing a packet which doesn't ACK anything. + */ + RX_OP_TX (0, 0, QUIC_PN_INVALID) + RX_OP_RX_ACK (0, 0, 1) + + /* + * At this point we would generate a packet with an ACK immediately. + * TX a packet which when ACKed makes [0,5] provably ACKed. + */ + RX_OP_TX (0, 1, 5) + RX_OP_CHECK_ACKS (0, rx_ack_ranges_3b) /* not provably ACKed yet */ + RX_OP_RX_ACK (0, 1, 1) + + RX_OP_CHECK_ACKS (0, rx_ack_ranges_3c) /* provably ACKed now gone */ + RX_OP_CHECK_UNPROC (0, 0, 11) /* still unprocessable */ + RX_OP_CHECK_PROC (0, 11, 1) /* still processable */ + + /* + * Now TX another packet which provably ACKs the rest when ACKed. + */ + RX_OP_TX (0, 2, 10) + RX_OP_CHECK_ACKS (0, rx_ack_ranges_3c) /* not provably ACKed yet */ + RX_OP_RX_ACK (0, 2, 1) + + RX_OP_CHECK_NO_ACKS (0) /* provably ACKed now gone */ + RX_OP_CHECK_UNPROC (0, 0, 11) /* still unprocessable */ + RX_OP_CHECK_PROC (0, 11, 1) /* still processable */ + + RX_OP_END +}; + +static const struct rx_test_op *const rx_test_scripts[] = { + rx_script_1, + rx_script_2, + rx_script_3 +}; + +static void on_ack_deadline_callback(OSSL_TIME deadline, + int pkt_space, void *arg) +{ + ((OSSL_TIME *)arg)[pkt_space] = deadline; +} + +static int test_rx_ack_actual(int tidx, int space) +{ + int testresult = 0; + struct helper h; + const struct rx_test_op *script = rx_test_scripts[tidx], *s; + size_t i, num_tx = 0, txi = 0; + const OSSL_QUIC_FRAME_ACK *ack; + OSSL_QUIC_FRAME_ACK rx_ack = {0}; + OSSL_QUIC_ACK_RANGE rx_ack_range = {0}; + struct pkt_info *pkts = NULL; + OSSL_ACKM_TX_PKT *txs = NULL, *tx; + OSSL_TIME ack_deadline[QUIC_PN_SPACE_NUM] = { + OSSL_TIME_INFINITY, OSSL_TIME_INFINITY, OSSL_TIME_INFINITY + }; + + /* Initialise ACK manager. */ + if (!TEST_int_eq(helper_init(&h, 0), 1)) + goto err; + + /* Arm callback for testing. */ + ossl_ackm_set_ack_deadline_callback(h.ackm, on_ack_deadline_callback, + ack_deadline); + + /* + * Determine how many packets we are TXing, and therefore how many packet + * structures we need. + */ + for (s = script; s->kind != RX_OPK_END; ++s) + if (s->kind == RX_OPK_TX) + num_tx += s->num_pn; + + /* Allocate packet information structures. */ + txs = OPENSSL_zalloc(sizeof(*txs) * num_tx); + if (!TEST_ptr(txs)) + goto err; + + pkts = OPENSSL_zalloc(sizeof(*pkts) * num_tx); + if (!TEST_ptr(pkts)) + goto err; + + /* Run script. */ + for (s = script; s->kind != RX_OPK_END; ++s) { + fake_time += s->time_advance; + switch (s->kind) { + case RX_OPK_PKT: + for (i = 0; i < s->num_pn; ++i) { + OSSL_ACKM_RX_PKT pkt = {0}; + + pkt.pkt_num = s->pn + i; + pkt.time = fake_time; + pkt.pkt_space = space; + pkt.is_ack_eliciting = 1; + + /* The packet should be processable before we feed it. */ + if (!TEST_int_eq(ossl_ackm_is_rx_pn_processable(h.ackm, + pkt.pkt_num, + pkt.pkt_space), 1)) + goto err; + + if (!TEST_int_eq(ossl_ackm_on_rx_packet(h.ackm, &pkt), 1)) + goto err; + } + + break; + + case RX_OPK_CHECK_UNPROC: + case RX_OPK_CHECK_PROC: + for (i = 0; i < s->num_pn; ++i) + if (!TEST_int_eq(ossl_ackm_is_rx_pn_processable(h.ackm, + s->pn + i, space), + (s->kind == RX_OPK_CHECK_PROC))) + goto err; + + break; + + case RX_OPK_CHECK_STATE: + if (!TEST_int_eq(ossl_ackm_is_ack_desired(h.ackm, space), + s->expect_desired)) + goto err; + + if (!TEST_int_eq(!ossl_time_is_infinity(ossl_ackm_get_ack_deadline(h.ackm, space)) + && !ossl_time_is_zero(ossl_ackm_get_ack_deadline(h.ackm, space)), + s->expect_deadline)) + goto err; + + for (i = 0; i < QUIC_PN_SPACE_NUM; ++i) { + if (i != (size_t)space + && !TEST_true(ossl_ackm_get_ack_deadline(h.ackm, i) + == OSSL_TIME_INFINITY)) + goto err; + + if (!TEST_true(ossl_ackm_get_ack_deadline(h.ackm, i) + == ack_deadline[i])) + goto err; + } + + break; + + case RX_OPK_CHECK_ACKS: + ack = ossl_ackm_get_ack_frame(h.ackm, space); + + /* Should always be able to get an ACK frame. */ + if (!TEST_ptr(ack)) + goto err; + + if (!TEST_size_t_eq(ack->num_ack_ranges, s->num_ack_ranges)) + goto err; + + for (i = 0; i < ack->num_ack_ranges; ++i) { + if (!TEST_uint64_t_eq(ack->ack_ranges[i].start, + s->ack_ranges[i].start)) + goto err; + if (!TEST_uint64_t_eq(ack->ack_ranges[i].end, + s->ack_ranges[i].end)) + goto err; + } + + break; + + case RX_OPK_TX: + pkts[txi].pkt = tx = &txs[txi]; + + tx->pkt_num = s->pn; + tx->pkt_space = space; + tx->num_bytes = 123; + tx->largest_acked = s->largest_acked; + tx->is_inflight = 1; + tx->is_ack_eliciting = 1; + tx->on_lost = on_lost; + tx->on_acked = on_acked; + tx->on_discarded = on_discarded; + tx->cb_arg = &pkts[txi]; + tx->time = fake_time; + + if (!TEST_int_eq(ossl_ackm_on_tx_packet(h.ackm, tx), 1)) + goto err; + + ++txi; + break; + + case RX_OPK_RX_ACK: + rx_ack.ack_ranges = &rx_ack_range; + rx_ack.num_ack_ranges = 1; + + rx_ack_range.start = s->pn; + rx_ack_range.end = s->pn + s->num_pn - 1; + + if (!TEST_int_eq(ossl_ackm_on_rx_ack_frame(h.ackm, &rx_ack, + space, fake_time), 1)) + goto err; + + break; + + default: + goto err; + } + } + + testresult = 1; +err: + helper_destroy(&h); + OPENSSL_free(pkts); + OPENSSL_free(txs); + return testresult; +} + +/* + * Driver + * ****************************************************************** + */ +static int test_tx_ack_case(int idx) +{ + int tidx, space; + + tidx = idx % OSSL_NELEM(tx_ack_cases); + idx /= OSSL_NELEM(tx_ack_cases); + + space = idx % QUIC_PN_SPACE_NUM; + idx /= QUIC_PN_SPACE_NUM; + + return test_tx_ack_case_actual(tidx, space, idx); +} + +static int test_rx_ack(int idx) +{ + int tidx; + + tidx = idx % OSSL_NELEM(rx_test_scripts); + idx /= OSSL_NELEM(rx_test_scripts); + + return test_rx_ack_actual(tidx, idx); +} + +int setup_tests(void) +{ + ADD_ALL_TESTS(test_tx_ack_case, + OSSL_NELEM(tx_ack_cases) * MODE_NUM * QUIC_PN_SPACE_NUM); + ADD_ALL_TESTS(test_tx_ack_time_script, OSSL_NELEM(tx_ack_time_scripts)); + ADD_ALL_TESTS(test_rx_ack, OSSL_NELEM(rx_test_scripts) * QUIC_PN_SPACE_NUM); + return 1; +} diff --git a/test/recipes/75-test_quic_ackm.t b/test/recipes/75-test_quic_ackm.t new file mode 100644 index 0000000000..8f61ed7eea --- /dev/null +++ b/test/recipes/75-test_quic_ackm.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_ackm"); + +plan skip_all => "QUIC protocol is not supported by this OpenSSL build" + if disabled('quic'); + +plan tests => 1; + +ok(run(test(["quic_ackm_test"])));