QUIC CC: Major revisions to CC abstract interface
authorHugo Landau <hlandau@openssl.org>
Wed, 1 Mar 2023 16:52:40 +0000 (16:52 +0000)
committerMatt Caswell <matt@openssl.org>
Mon, 1 May 2023 10:03:54 +0000 (11:03 +0100)
Reviewed-by: Tomas Mraz <tomas@openssl.org>
Reviewed-by: Matt Caswell <matt@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/20423)

doc/designs/quic-design/congestion-control.md
include/internal/quic_cc.h
ssl/quic/cc_dummy.c
ssl/quic/quic_ackm.c
ssl/quic/quic_channel.c
ssl/quic/quic_txp.c
test/quic_ackm_test.c
test/quic_fifd_test.c
test/quic_txp_test.c

index d5bb4a1d7957b0e2494126b89c753e04493b496d..d2c9323695da876ff447b9591428495cb88110df 100644 (file)
 Congestion control API design
 =============================
 
-This is mostly inspired by the MSQUIC congestion control API
-as it was the most isolated one among the libraries.
-
-The API is designed in a way to be later easily transformed into a
-a fetchable implementation API.
-
-The API is centered around two structures - the `OSSL_CC_METHOD`
-structure holding the API calls into the congestion control module
-and `OSSL_CC_DATA` opaque type that holds the data of needed
-for the operation of the module.
-
-Most of the information when the functions of the API are
-supposed to be called and how the API is designed to operate
-should be clear from the comments for the individual
-functions in [Appendix A](#appendix-a).
-
-Changeables
------------
-
-As some parameters that the congestion control algorithm needs might
-be updated during the lifetime of the connection these parameters
-are called changeables.
-
-The CC implementation will save the pointers to these parameters'
-data and will use the current value of the data at the pointer when
-it needs to recalculate the allowance and whether the sending is
-blocked or not.
-
-Some examples of changeables
-
- - `payload_length` - the maximum length of payload that can be sent
-   in a single UDP packet (not counting UDP and IP headers).
- - `smoothed_rtt` - the current round trip time of the connection
-   as computed from the acked packets.
- - `rtt_variance` - the variance of the `smoothed_rtt` value.
-
-Thread handling
----------------
-
-The `OSSL_CC_DATA` is created with the `new` function per each
-connection. The expectation is that there is only a single thread
-accessing the `ccdata` which should not be a limitation as the calls
-into the module will be done from the packetizer layer of the QUIC
-connection handling and there the eventual writes from multiple threads
-handling individual streams of the connection have to be synchronized
-already to create the packets.
-
-Congestion event handling
--------------------------
-
-The congestion state of a connection can change only after some event
-happens - i.e., a packet is considered lost meaning the `on_data_lost()`
-is called, or an ack arrives for a packet causing calls to
-`on_data_acked()` or `on_spurious_congestion_event()` functions.
-The congestion control does not produce any timer events by itself.
-
-Exemptions
-----------
-
-To facilitate probing and to avoid having to always special-case
-probing packets when considering congestion on sending, the
-`set_exemption()` function allows setting a number of packets that are
-allowed to be sent even when forbidden by the eventual congestion state.
-
-The exemptions must be used if and only if a packet (or multiple packets)
-has to be sent as required by the protocol regardless of the congestion state.
-
-Paths
------
-
-Initially the design expects that only a single path per-connection is
-actively sending data. In future when multiple active paths sending data
-shall be supported the instances of `OSSL_CC_DATA` would be per-path.
-
-There might need to be further adjustments needed in that case. However
-at least initially this API is intended to be internal to the
-OpenSSL library allowing any necessary changes of the API.
-
-Appendix A
-----------
-
-Proposed header file with comments explaining the individual
-functions. The API is meant to be internal initially so the method
-accessors to set the individual functions will be added later once
-the API is public. Alternatively this might be also implemented
-as fetchable dispatch API.
-
-```C
-/*
- * 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 <openssl/ssl.h>
-
-typedef struct ossl_cc_method_st OSSL_CC_METHOD;
-/*
- * An OSSL_CC_DATA is an externally defined opaque pointer holding
- * the internal data of the congestion control method
- */
-typedef struct ossl_cc_data_st OSSL_CC_DATA;
-
-/*
- * Once this becomes public API then we will need functions to create and
- * free an OSSL_CC_METHOD, as well as functions to get/set the various
- * function pointers....unless we make it fetchable.
- */
-struct ossl_cc_method_st {
-    /*
-     * 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,
-                                 uint64_t 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);
-
-    /*
-     * Returns the next time at which the CC will release more budget for
-     * sending, or ossl_time_infinite().
-     */
-    OSSL_TIME (*get_next_credit_time)(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,
-                         uint64_t 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);
-};
-```
+We use an abstract interface for the QUIC congestion controller to facilitate
+use of pluggable QUIC congestion controllers in the future. The interface is
+based on interfaces suggested by RFC 9002 and MSQUIC's congestion control APIs.
+
+`OSSL_CC_METHOD` provides a vtable of function pointers to congestion controller
+methods. `OSSL_CC_DATA` is an opaque type representing a congestion controller
+instance.
+
+For details on the API, see the comments in `include/internal/quic_cc.h`.
+
+Congestion controllers are not thread safe; the caller is responsible for
+synchronisation.
+
+Congestion controllers may vary their state with respect to time. This is
+faciliated via the `get_wakeup_deadline` method and the `now` argument to the
+`new` method, which provides access to a clock. While no current congestion
+controller makes use of this facility, it can be used by future congestion
+controllers to implement packet pacing.
+
+Congestion controllers may expose integer configuration options via the
+`set_option_uint` and `get_option_uint` methods. These options may be specific
+to the congestion controller method, although there are some well known ones
+intended to be common to all congestion controllers. The use of strings for
+option names is avoided for performance reasons.
+
+Currently, the only dependency injected to a congestion controller is access to
+a clock. In the future it is likely that access at least to the statistics
+manager will be provided. Excessive futureproofing of the congestion controller
+interface has been avoided as this is currently an internal API for which no API
+stability guarantees are required; for example, no currently implemented
+congestion control algorithm requires access to the statistics manager, but such
+access can readily be added later as needed.
+
+QUIC congestion control state is per-path, per-connection. Currently we support
+only a single path per connection, so there is one congestion control instance
+per connection. This may change in future.
+
+While the congestion control API is roughly based around the arrangement of
+functions as described by the congestion control psuedocode in RFC 9002, there
+are some deliberate changes in order to obtain cleaner separation between the
+loss detection and congestion control functions. Where a literal option of RFC
+9002 psuedocode would require a congestion controller to access the ACK
+manager's internal state directly, the interface between the two has been
+changed to avoid this. This involves some small amounts of functionality which
+RFC 9002 considers part of the congestion controller being part of the ACK
+manager in our implementation. See the comments in `include/internal/quic_cc.h`
+and `ssl/quic/quic_ackm.c` for more information.
index 8e318e33ff45160f5c771d9ea254081a6f33d92e..2ddc86c7464650feed0b175316e8d1c6739dfb5b 100644 (file)
 
 # ifndef OPENSSL_NO_QUIC
 
-typedef struct ossl_cc_data_st *OSSL_CC_DATA;
+typedef struct ossl_cc_data_st OSSL_CC_DATA;
 
-typedef struct ossl_cc_method_st {
-    void *dummy;
+typedef struct ossl_cc_ack_info_st {
+    /* The time the packet being acknowledged was originally sent. */
+    OSSL_TIME   tx_time;
 
-    /*
-     * 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);
+    /* The size in bytes of the packet being acknowledged. */
+    size_t      tx_size;
+} OSSL_CC_ACK_INFO;
+
+typedef struct ossl_cc_loss_info_st {
+    /* The time the packet being lost was originally sent. */
+    OSSL_TIME   tx_time;
 
+    /* The size in bytes of the packet which has been determined lost. */
+    size_t      tx_size;
+} OSSL_CC_LOSS_INFO;
+
+typedef struct ossl_cc_ecn_info_st {
     /*
-     * Release the OSSL_CC_DATA.
+     * The time at which the largest acked PN (in the incoming ACK frame) was
+     * sent.
      */
-    void (*free)(OSSL_CC_DATA *ccdata);
+    OSSL_TIME   largest_acked_time;
+} OSSL_CC_ECN_INFO;
+
+/* Parameter (read-write): Maximum datagram payload length in bytes. */
+#define OSSL_CC_OPTION_MAX_DGRAM_PAYLOAD_LEN        1
+
+/* Diagnostic (read-only): current congestion window size in bytes. */
+#define OSSL_CC_OPTION_CUR_CWND_SIZE                2
 
+/* Diagnostic (read-only): minimum congestion window size in bytes. */
+#define OSSL_CC_OPTION_MIN_CWND_SIZE                3
+
+/* Diagnostic (read-only): current net bytes in flight. */
+#define OSSL_CC_OPTION_CUR_BYTES_IN_FLIGHT          4
+
+/*
+ * Congestion control abstract interface.
+ *
+ * This interface is broadly based on the design described in RFC 9002. However,
+ * the demarcation between the ACKM and the congestion controller does not
+ * exactly match that delineated in the RFC 9002 psuedocode. Where aspects of
+ * the demarcation involve the congestion controller accessing internal state of
+ * the ACKM, the interface has been revised where possible to provide the
+ * information needed by the congestion controller and avoid needing to give the
+ * congestion controller access to the ACKM's internal data structures.
+ *
+ * Particular changes include:
+ *
+ *   - In our implementation, it is the responsibility of the ACKM to determine
+ *     if a loss event constitutes persistent congestion.
+ *
+ *   - In our implementation, it is the responsibility of the ACKM to determine
+ *     if the ECN-CE counter has increased. The congestion controller is simply
+ *     informed when an ECN-CE event occurs.
+ *
+ * All of these changes are intended to avoid having a congestion controller
+ * have to access ACKM internal state.
+ *
+ */
+#define OSSL_CC_LOST_FLAG_PERSISTENT_CONGESTION     (1U << 0)
+
+typedef struct ossl_cc_method_st {
     /*
-     * Reset the congestion control state.
-     * |flags| to support different level of reset (partial/full).
+     * Instantiation.
      */
-    void (*reset)(OSSL_CC_DATA *ccdata, int flags);
+    OSSL_CC_DATA *(*new)(OSSL_TIME (*now_cb)(void *arg),
+                         void *now_cb_arg);
+
+    void (*free)(OSSL_CC_DATA *ccdata);
 
     /*
-     * Set number of packets exempted from CC - used for probing
-     * |numpackets| is a small value (2).
-     * Returns 0 on error, 1 otherwise.
+     * Reset of state.
      */
-    int (*set_exemption)(OSSL_CC_DATA *ccdata, int numpackets);
+    void (*reset)(OSSL_CC_DATA *ccdata);
 
     /*
-     * Get current number of packets exempted from CC.
-     * Returns negative value on error, the number otherwise.
+     * Escape hatch for option configuration.
+     *
+     * option_id: One of OSSL_CC_OPTION_*.
+     *
+     * value: The option value to set.
+     *
+     * Returns 1 on success and 0 on failure.
      */
-    int (*get_exemption)(OSSL_CC_DATA *ccdata);
+    int (*set_option_uint)(OSSL_CC_DATA *ccdata,
+                           uint32_t option_id,
+                           uint64_t value);
 
     /*
-     * Returns 1 if sending is allowed, 0 otherwise.
+     * On success, returns 1 and writes the current value of the given option to
+     * *value. Otherwise, returns 0.
      */
-    int (*can_send)(OSSL_CC_DATA *ccdata);
+    int (*get_option_uint)(OSSL_CC_DATA *ccdata,
+                           uint32_t option_id,
+                           uint64_t *value);
 
     /*
-     * 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.
+     * Returns the amount of additional data (above and beyond the data
+     * currently in flight) which can be sent in bytes. Returns 0 if no more
+     * data can be sent at this time. The return value of this method
+     * can vary as time passes.
      */
-    uint64_t (*get_send_allowance)(OSSL_CC_DATA *ccdata,
-                                   OSSL_TIME time_since_last_send,
-                                   int time_valid);
+    uint64_t (*get_tx_allowance)(OSSL_CC_DATA *ccdata);
 
     /*
-     * Returns the maximum number of bytes allowed to be in flight.
+     * Returns the time at which the return value of get_tx_allowance might be
+     * higher than its current value. This is not a guarantee and spurious
+     * wakeups are allowed. Returns ossl_time_infinite() if there is no current
+     * wakeup deadline.
      */
-    uint64_t (*get_bytes_in_flight_max)(OSSL_CC_DATA *ccdata);
+    OSSL_TIME (*get_wakeup_deadline)(OSSL_CC_DATA *ccdata);
 
     /*
-     * Returns the next time at which the CC will release more budget for
-     * sending, or ossl_time_infinite().
+     * The On Data Sent event. num_bytes should be the size of the packet in
+     * bytes (or the aggregate size of multiple packets which have just been
+     * sent).
      */
-    OSSL_TIME (*get_next_credit_time)(OSSL_CC_DATA *ccdata);
+    int (*on_data_sent)(OSSL_CC_DATA *ccdata,
+                        uint64_t num_bytes);
 
     /*
-     * 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.
+     * The On Data Acked event. See OSSL_CC_ACK_INFO structure for details
+     * of the information to be passed.
      */
-    int (*on_data_sent)(OSSL_CC_DATA *ccdata,
-                        uint64_t num_retransmittable_bytes);
+    int (*on_data_acked)(OSSL_CC_DATA *ccdata,
+                         const OSSL_CC_ACK_INFO *info);
 
     /*
-     * 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.
+     * The On Data Lost event. See OSSL_CC_LOSS_INFO structure for details
+     * of the information to be passed.
+     *
+     * Note: When the ACKM determines that a set of multiple packets has been
+     * lost, it is useful for a congestion control algorithm to be able to
+     * process this as a single loss event rather than multiple loss events.
+     * Thus, calling this function may cause the congestion controller to defer
+     * state updates under the assumption that subsequent calls to
+     * on_data_lost() representing further lost packets in the same loss event
+     * may be forthcoming. Always call on_data_lost_finished() after one or more
+     * calls to on_data_lost().
      */
-    int (*on_data_invalidated)(OSSL_CC_DATA *ccdata,
-                               uint64_t num_retransmittable_bytes);
+    int (*on_data_lost)(OSSL_CC_DATA *ccdata,
+                        const OSSL_CC_LOSS_INFO *info);
 
     /*
-     * 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.
+     * To be called after a sequence of one or more on_data_lost() calls
+     * representing multiple packets in a single loss detection incident.
+     *
+     * Flags may be 0 or OSSL_CC_LOST_FLAG_PERSISTENT_CONGESTION.
      */
-    int (*on_data_acked)(OSSL_CC_DATA *ccdata,
-                         OSSL_TIME time_now,
-                         uint64_t last_pn_acked,
-                         uint64_t num_retransmittable_bytes);
+    int (*on_data_lost_finished)(OSSL_CC_DATA *ccdata, uint32_t flags);
 
     /*
-     * 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.
+     * For use when a PN space is invalidated or a packet must otherwise be
+     * 'undone' for congestion control purposes without acting as a loss signal.
+     * Only the size of the packet is needed.
      */
-    void (*on_data_lost)(OSSL_CC_DATA *ccdata,
-                         uint64_t largest_pn_lost,
-                         uint64_t largest_pn_sent,
-                         uint64_t num_retransmittable_bytes,
-                         int persistent_congestion);
+    int (*on_data_invalidated)(OSSL_CC_DATA *ccdata,
+                               uint64_t num_bytes);
 
     /*
-     * 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.
+     * Called from the ACKM when detecting an increased ECN-CE value in an ACK
+     * frame. This indicates congestion.
+     *
+     * Note that this differs from the RFC's conceptual segregation of the loss
+     * detection and congestion controller functions, as in our implementation
+     * the ACKM is responsible for detecting increases to ECN-CE and simply
+     * tells the congestion controller when ECN-triggered congestion has
+     * occurred. This allows a slightly more efficient implementation and
+     * narrower interface between the ACKM and CC.
      */
-    int (*on_spurious_congestion_event)(OSSL_CC_DATA *ccdata);
+    int (*on_ecn)(OSSL_CC_DATA *ccdata,
+                  const OSSL_CC_ECN_INFO *info);
 } OSSL_CC_METHOD;
 
 extern const OSSL_CC_METHOD ossl_cc_dummy_method;
index 195c4324bf964daf15e0bb98197afb6f3c05b0b7..af0d5483791aada311e90e9bf58371578f14dbff 100644 (file)
@@ -8,15 +8,22 @@
  */
 
 #include "internal/quic_cc.h"
+#include "internal/quic_types.h"
 
 typedef struct ossl_cc_dummy_st {
-    char dummy;
+    size_t max_dgram_len;
 } OSSL_CC_DUMMY;
 
-static OSSL_CC_DATA *dummy_new(OSSL_PARAM *settings, OSSL_PARAM *options,
-                               OSSL_PARAM *changeables)
+static OSSL_CC_DATA *dummy_new(OSSL_TIME (*now_cb)(void *arg),
+                               void *now_cb_arg)
 {
-    return OPENSSL_zalloc(sizeof(OSSL_CC_DUMMY));
+    OSSL_CC_DUMMY *d = OPENSSL_zalloc(sizeof(*d));
+
+    if (d == NULL)
+        return NULL;
+
+    d->max_dgram_len = QUIC_MIN_INITIAL_DGRAM_LEN;
+    return (OSSL_CC_DATA *)d;
 }
 
 static void dummy_free(OSSL_CC_DATA *cc)
@@ -24,90 +31,97 @@ static void dummy_free(OSSL_CC_DATA *cc)
     OPENSSL_free(cc);
 }
 
-static void dummy_reset(OSSL_CC_DATA *cc, int flags)
+static void dummy_reset(OSSL_CC_DATA *cc)
 {
 
 }
 
-static int dummy_set_exemption(OSSL_CC_DATA *cc, int numpackets)
+static int dummy_set_option_uint(OSSL_CC_DATA *cc,
+                                 uint32_t option_id,
+                                 uint64_t value)
 {
-    return 1;
-}
+    OSSL_CC_DUMMY *d = (OSSL_CC_DUMMY *)cc;
 
-static int dummy_get_exemption(OSSL_CC_DATA *cc)
-{
-    return 0;
-}
+    switch (option_id) {
+    case OSSL_CC_OPTION_MAX_DGRAM_PAYLOAD_LEN:
+        if (value > SIZE_MAX)
+            return 0;
 
-static int dummy_can_send(OSSL_CC_DATA *cc)
-{
-    return 1;
+        d->max_dgram_len = (size_t)value;
+        return 1;
+
+    default:
+        return 0;
+    }
 }
 
-static uint64_t dummy_get_send_allowance(OSSL_CC_DATA *cc,
-                                       OSSL_TIME time_since_last_send,
-                                       int time_valid)
+static int dummy_get_option_uint(OSSL_CC_DATA *cc,
+                                 uint32_t option_id,
+                                 uint64_t *value)
 {
-    return SIZE_MAX;
+    OSSL_CC_DUMMY *d = (OSSL_CC_DUMMY *)cc;
+
+    switch (option_id) {
+    case OSSL_CC_OPTION_MAX_DGRAM_PAYLOAD_LEN:
+        *value = (uint64_t)d->max_dgram_len;
+        return 1;
+
+    default:
+        return 0;
+    }
 }
 
-static uint64_t dummy_get_bytes_in_flight_max(OSSL_CC_DATA *cc)
+static uint64_t dummy_get_tx_allowance(OSSL_CC_DATA *cc)
 {
     return SIZE_MAX;
 }
 
-static OSSL_TIME dummy_get_next_credit_time(OSSL_CC_DATA *cc_data)
+static OSSL_TIME dummy_get_wakeup_deadline(OSSL_CC_DATA *cc)
 {
     return ossl_time_infinite();
 }
 
 static int dummy_on_data_sent(OSSL_CC_DATA *cc,
-                              uint64_t num_retransmittable_bytes)
+                              uint64_t num_bytes)
 {
     return 1;
 }
 
-static int dummy_on_data_invalidated(OSSL_CC_DATA *cc,
-                                     uint64_t num_retransmittable_bytes)
+static int dummy_on_data_acked(OSSL_CC_DATA *cc,
+                               const OSSL_CC_ACK_INFO *info)
 {
     return 1;
 }
 
-static int dummy_on_data_acked(OSSL_CC_DATA *cc, OSSL_TIME time_now,
-                               uint64_t last_pn_acked,
-                               uint64_t num_retransmittable_bytes)
+static int dummy_on_data_lost(OSSL_CC_DATA *cc,
+                              const OSSL_CC_LOSS_INFO *info)
 {
     return 1;
 }
 
-static void dummy_on_data_lost(OSSL_CC_DATA *cc,
-                              uint64_t largest_pn_lost,
-                              uint64_t largest_pn_sent,
-                              uint64_t num_retransmittable_bytes,
-                              int persistent_congestion)
+static int dummy_on_data_lost_finished(OSSL_CC_DATA *cc,
+                                       uint32_t flags)
 {
-
+    return 1;
 }
 
-static int dummy_on_spurious_congestion_event(OSSL_CC_DATA *cc)
+static int dummy_on_data_invalidated(OSSL_CC_DATA *cc,
+                                     uint64_t num_bytes)
 {
     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_get_next_credit_time,
+    dummy_set_option_uint,
+    dummy_get_option_uint,
+    dummy_get_tx_allowance,
+    dummy_get_wakeup_deadline,
     dummy_on_data_sent,
-    dummy_on_data_invalidated,
     dummy_on_data_acked,
     dummy_on_data_lost,
-    dummy_on_spurious_congestion_event,
+    dummy_on_data_lost_finished,
+    dummy_on_data_invalidated,
 };
index 6061fb29f59c0920c4086817b93dc89a5d7305e8..b79863657199f9bd6f3a661a7ce9cba67df3146e 100644 (file)
@@ -911,7 +911,7 @@ static int ackm_set_loss_detection_timer(OSSL_ACKM *ackm)
 static int ackm_in_persistent_congestion(OSSL_ACKM *ackm,
                                          const OSSL_ACKM_TX_PKT *lpkt)
 {
-    /* Persistent congestion not currently implemented. */
+    /* TODO(QUIC): Persistent congestion not currently implemented. */
     return 0;
 }
 
@@ -922,6 +922,8 @@ static void ackm_on_pkts_lost(OSSL_ACKM *ackm, int pkt_space,
     OSSL_RTT_INFO rtt;
     QUIC_PN largest_pn_lost = 0;
     uint64_t num_bytes = 0;
+    OSSL_CC_LOSS_INFO loss_info = {0};
+    uint32_t flags = 0;
 
     for (p = lpkt; p != NULL; p = pnext) {
         pnext = p->lnext;
@@ -938,41 +940,38 @@ static void ackm_on_pkts_lost(OSSL_ACKM *ackm, int pkt_space,
             num_bytes += p->num_bytes;
         }
 
+        if (!pseudo) {
+            /*
+             * If this is pseudo-loss (e.g. during connection retry) we do not
+             * inform the CC as it is not a real loss and not reflective of
+             * network conditions.
+             */
+            loss_info.tx_time = p->time;
+            loss_info.tx_size = p->num_bytes;
+
+            ackm->cc_method->on_data_lost(ackm->cc_data, &loss_info);
+        }
+
         p->on_lost(p->cb_arg);
     }
 
-    if (pseudo)
-        /*
-         * If this is pseudo-loss (e.g. during connection retry) we do not
-         * inform the CC as it is not a real loss and not reflective of network
-         * conditions.
-         */
-        return;
-
     /*
-     * Only consider lost packets with regards to congestion after getting an
-     * RTT sample.
+     * Persistent congestion can only be considered if we have gotten at least
+     * one RTT sample.
      */
     ossl_statm_get_rtt_info(ackm->statm, &rtt);
+    if (!ossl_time_is_zero(ackm->first_rtt_sample)
+        && ackm_in_persistent_congestion(ackm, lpkt))
+        flags |= OSSL_CC_LOST_FLAG_PERSISTENT_CONGESTION;
 
-    if (ossl_time_is_zero(ackm->first_rtt_sample))
-        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));
+    ackm->cc_method->on_data_lost_finished(ackm->cc_data, flags);
 }
 
 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);
+    OSSL_CC_ACK_INFO ainfo = {0};
 
     for (; apkt != NULL; apkt = anext) {
         if (apkt->is_inflight) {
@@ -981,7 +980,6 @@ static void ackm_on_pkts_acked(OSSL_ACKM *ackm, const OSSL_ACKM_TX_PKT *apkt)
                 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;
 
@@ -997,10 +995,11 @@ static void ackm_on_pkts_acked(OSSL_ACKM *ackm, const OSSL_ACKM_TX_PKT *apkt)
 
         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);
+        ainfo.tx_time = apkt->time;
+        ainfo.tx_size = apkt->num_bytes;
+        ackm->cc_method->on_data_acked(ackm->cc_data, &ainfo);
+    }
 }
 
 OSSL_ACKM *ossl_ackm_new(OSSL_TIME (*now)(void *arg),
@@ -1096,16 +1095,12 @@ int ossl_ackm_on_rx_datagram(OSSL_ACKM *ackm, size_t num_bytes)
     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;
+    OSSL_CC_ECN_INFO ecn_info = {0};
 
     /*
      * If the ECN-CE counter reported by the peer has increased, this could
@@ -1119,7 +1114,8 @@ static void ackm_process_ecn(OSSL_ACKM *ackm, const OSSL_QUIC_FRAME_ACK *ack,
         if (pkt == NULL)
             return;
 
-        ackm_on_congestion(ackm, pkt->time);
+        ecn_info.largest_acked_time = pkt->time;
+        ackm->cc_method->on_ecn(ackm->cc_data, &ecn_info);
     }
 }
 
@@ -1182,7 +1178,13 @@ int ossl_ackm_on_rx_ack_frame(OSSL_ACKM *ackm, const OSSL_QUIC_FRAME_ACK *ack,
                               ossl_time_subtract(now, na_pkts->time));
     }
 
-    /* Process ECN information if present. */
+    /*
+     * Process ECN information if present.
+     *
+     * We deliberately do most ECN processing in the ACKM rather than the
+     * congestion controller to avoid having to give the congestion controller
+     * access to ACKM internal state.
+     */
     if (ack->ecn_present)
         ackm_process_ecn(ackm, ack, pkt_space);
 
index 460a7d77dbed59de22b4e3928270a1bad1857656..349e2f105524a675129241f2a6e0e0d488f30eee 100644 (file)
@@ -150,7 +150,7 @@ static int ch_init(QUIC_CHANNEL *ch)
 
     ch->have_statm = 1;
     ch->cc_method = &ossl_cc_dummy_method;
-    if ((ch->cc_data = ch->cc_method->new(NULL, NULL, NULL)) == NULL)
+    if ((ch->cc_data = ch->cc_method->new(get_time, NULL)) == NULL)
         goto err;
 
     if ((ch->ackm = ossl_ackm_new(get_time, ch, &ch->statm,
@@ -1687,7 +1687,7 @@ static OSSL_TIME ch_determine_next_tick_deadline(QUIC_CHANNEL *ch)
     if (ossl_quic_tx_packetiser_has_pending(ch->txp, TX_PACKETISER_ARCHETYPE_NORMAL,
                                             TX_PACKETISER_BYPASS_CC))
         deadline = ossl_time_min(deadline,
-                                 ch->cc_method->get_next_credit_time(ch->cc_data));
+                                 ch->cc_method->get_wakeup_deadline(ch->cc_data));
 
     /* Is the terminating timer armed? */
     if (ossl_quic_channel_is_terminating(ch))
index 2d49e64f7ffd7c24ed195ef1dac6c9493322f78b..786d25ecf639d56b19cf503f19e4d87d93f029d1 100644 (file)
@@ -483,7 +483,8 @@ int ossl_quic_tx_packetiser_has_pending(OSSL_QUIC_TX_PACKETISER *txp,
     int cc_can_send;
 
     cc_can_send
-        = (bypass_cc || txp->args.cc_method->can_send(txp->args.cc_data));
+        = (bypass_cc
+           || txp->args.cc_method->get_tx_allowance(txp->args.cc_data) > 0);
 
     for (enc_level = QUIC_ENC_LEVEL_INITIAL;
          enc_level < QUIC_ENC_LEVEL_NUM;
@@ -512,7 +513,7 @@ int ossl_quic_tx_packetiser_generate(OSSL_QUIC_TX_PACKETISER *txp,
     /*
      * If CC says we cannot send we still may be able to send any queued probes.
      */
-    cc_can_send = txp->args.cc_method->can_send(txp->args.cc_data);
+    cc_can_send = (txp->args.cc_method->get_tx_allowance(txp->args.cc_data) > 0);
 
     for (enc_level = QUIC_ENC_LEVEL_INITIAL;
          enc_level < QUIC_ENC_LEVEL_NUM;
@@ -900,15 +901,8 @@ static int txp_generate_for_el(OSSL_QUIC_TX_PACKETISER *txp, uint32_t enc_level,
     size_t mdpl, hdr_len, pkt_overhead, cc_limit;
     uint64_t cc_limit_;
     QUIC_PKT_HDR phdr;
-    OSSL_TIME time_since_last;
 
     /* Determine the limit CC imposes on what we can send. */
-    if (ossl_time_is_zero(txp->last_tx_time))
-        time_since_last = ossl_time_zero();
-    else
-        time_since_last = ossl_time_subtract(txp->args.now(txp->args.now_arg),
-                                             txp->last_tx_time);
-
     if (!cc_can_send) {
         /*
          * If we are called when we cannot send, this must be because we want
@@ -917,10 +911,7 @@ static int txp_generate_for_el(OSSL_QUIC_TX_PACKETISER *txp, uint32_t enc_level,
         cc_limit = SIZE_MAX;
     } else {
         /* Allow CC to clamp how much we can send. */
-        cc_limit_ = txp->args.cc_method->get_send_allowance(txp->args.cc_data,
-                                                            time_since_last,
-                                                            ossl_time_is_zero(time_since_last));
-
+        cc_limit_ = txp->args.cc_method->get_tx_allowance(txp->args.cc_data);
         cc_limit = (cc_limit_ > SIZE_MAX ? SIZE_MAX : (size_t)cc_limit_);
     }
 
index a743ecb5eeaee10804c90a4875929ac96e805f64..b92e153d1a1cee85baf6e44d8e01960f42707855 100644 (file)
@@ -98,7 +98,7 @@ static int helper_init(struct helper *h, size_t num_pkts)
     h->have_statm = 1;
 
     /* Initialise congestion controller. */
-    h->ccdata = ossl_cc_dummy_method.new(NULL, NULL, NULL);
+    h->ccdata = ossl_cc_dummy_method.new(fake_now, NULL);
     if (!TEST_ptr(h->ccdata))
         goto err;
 
index c86ee83860626ee4f924627a381d56212bbf2352..d3d13bf53f92f9f65a6dd2b4a62ba48b5c1e8d8d 100644 (file)
@@ -318,7 +318,7 @@ static int test_fifd(int idx)
     cb_fail = 0;
 
     if (!TEST_true(ossl_statm_init(&info.statm))
-        || !TEST_ptr(info.ccdata = ossl_cc_dummy_method.new(NULL, NULL, NULL))
+        || !TEST_ptr(info.ccdata = ossl_cc_dummy_method.new(fake_now, NULL))
         || !TEST_ptr(info.ackm = ossl_ackm_new(fake_now, NULL,
                                                &info.statm,
                                                &ossl_cc_dummy_method,
index f06002f26ac9ad82dd367fbfc27e6c1e1e019565..265c23fabcb0ef1089bc225f793cdb6dfbabbbef 100644 (file)
@@ -153,7 +153,7 @@ static int helper_init(struct helper *h)
     h->have_statm = 1;
 
     h->cc_method = &ossl_cc_dummy_method;
-    if (!TEST_ptr(h->cc_data = h->cc_method->new(NULL, NULL, NULL)))
+    if (!TEST_ptr(h->cc_data = h->cc_method->new(fake_now, NULL)))
         goto err;
 
     if (!TEST_ptr(h->args.ackm = ossl_ackm_new(fake_now, NULL,