--- /dev/null
+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.
--- /dev/null
+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);
+```
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
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
returning I<a> - I<b>.
If the difference would be negative, B<0> is returned.
+B<ossl_time_multiply> performs a saturating multiplication of a time value by a
+given integer multiplier.
+
+B<ossl_time_divide> performs floor division of a time value by a given integer
+divisor.
+
+B<ossl_time_abs_difference> returns the magnitude of the difference between two
+time values.
+
+B<ossl_time_min> returns the lesser of two time values.
+
+B<ossl_time_max> returns the greater of two time values.
+
=head1 NOTES
The largest representable duration is guaranteed to be at least 500 years.
B<ossl_time_subtract> returns the difference of the two times or the
time of the Epoch on underflow.
+B<ossl_time_multiply> returns the result of multiplying the given time by the
+given integral multiplier, or B<OSSL_TIME_INFINITY> on overflow.
+
+B<ossl_time_divide> returns the result of dividing the given time by the given
+integral divisor.
+
+B<ossl_time_abs_difference> returns the magnitude of the difference between the
+input time values.
+
+B<ossl_time_min> and B<ossl_time_max> choose and return one of the input values.
+
=head1 HISTORY
This functionality was added in OpenSSL 3.1.
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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 <openssl/ssl.h>
+# 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
# include <openssl/ssl.h>
+/* 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
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
$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
--- /dev/null
+/*
+ * 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,
+};
--- /dev/null
+/*
+ * 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 <assert.h>
+
+/*
+ * 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;
+}
--- /dev/null
+/*
+ * 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;
+}
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;
--- /dev/null
+/*
+ * 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 <openssl/ssl.h>
+#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;
+}
--- /dev/null
+#! /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"])));