Home | History | Annotate | Line # | Download | only in quic
      1      1.1  christos /*
      2      1.1  christos  * Copyright 2023-2024 The OpenSSL Project Authors. All Rights Reserved.
      3      1.1  christos  *
      4      1.1  christos  * Licensed under the Apache License 2.0 (the "License").  You may not use
      5      1.1  christos  * this file except in compliance with the License.  You can obtain a copy
      6      1.1  christos  * in the file LICENSE in the source distribution or at
      7      1.1  christos  * https://www.openssl.org/source/license.html
      8      1.1  christos  */
      9      1.1  christos 
     10      1.1  christos #include "internal/quic_rcidm.h"
     11      1.1  christos #include "internal/priority_queue.h"
     12      1.1  christos #include "internal/list.h"
     13      1.1  christos #include "internal/common.h"
     14      1.1  christos 
     15      1.1  christos /*
     16      1.1  christos  * QUIC Remote Connection ID Manager
     17      1.1  christos  * =================================
     18      1.1  christos  *
     19      1.1  christos  * We can receive an arbitrary number of RCIDs via NCID frames. Periodically, we
     20      1.1  christos  * may desire (for example for anti-connection fingerprinting reasons, etc.)
     21      1.1  christos  * to switch to a new RCID according to some arbitrary policy such as the number
     22      1.1  christos  * of packets we have sent.
     23      1.1  christos  *
     24      1.1  christos  * When we do this we should move to the next RCID in the sequence of received
     25      1.1  christos  * RCIDs ordered by sequence number. For example, if a peer sends us three NCID
     26      1.1  christos  * frames with sequence numbers 10, 11, 12, we should seek to consume these
     27      1.1  christos  * RCIDs in order.
     28      1.1  christos  *
     29      1.1  christos  * However, due to the possibility of packet reordering in the network, NCID
     30      1.1  christos  * frames might be received out of order. Thus if a peer sends us NCID frames
     31      1.1  christos  * with sequence numbers 12, 10, 11, we should still consume the RCID with
     32      1.1  christos  * sequence number 10 before consuming the RCIDs with sequence numbers 11 or 12.
     33      1.1  christos  *
     34      1.1  christos  * We use a priority queue for this purpose.
     35      1.1  christos  */
     36      1.1  christos static void rcidm_update(QUIC_RCIDM *rcidm);
     37      1.1  christos static void rcidm_set_preferred_rcid(QUIC_RCIDM *rcidm,
     38  1.1.1.2  christos     const QUIC_CONN_ID *rcid);
     39      1.1  christos 
     40  1.1.1.2  christos #define PACKETS_PER_RCID 10000
     41      1.1  christos 
     42  1.1.1.2  christos #define INITIAL_SEQ_NUM 0
     43  1.1.1.2  christos #define PREF_ADDR_SEQ_NUM 1
     44      1.1  christos 
     45      1.1  christos /*
     46      1.1  christos  * RCID
     47      1.1  christos  * ====
     48      1.1  christos  *
     49      1.1  christos  * The RCID structure is used to track RCIDs which have sequence numbers (i.e.,
     50      1.1  christos  * INITIAL, PREF_ADDR and NCID type RCIDs). The RCIDs without sequence numbers
     51      1.1  christos  * (Initial ODCIDs and Retry ODCIDs), hereafter referred to as unnumbered RCIDs,
     52      1.1  christos  * can logically be viewed as their own type of RCID but are tracked separately
     53      1.1  christos  * as singletons without needing a discrete structure.
     54      1.1  christos  *
     55      1.1  christos  * At any given time an RCID object is in one of these states:
     56      1.1  christos  *
     57      1.1  christos  *
     58      1.1  christos  *      (start)
     59      1.1  christos  *         |
     60      1.1  christos  *       [add]
     61      1.1  christos  *         |
     62      1.1  christos  *    _____v_____                 ___________                 ____________
     63      1.1  christos  *   |           |               |           |               |            |
     64      1.1  christos  *   |  PENDING  | --[select]--> |  CURRENT  | --[retire]--> |  RETIRING  |
     65      1.1  christos  *   |___________|               |___________|               |____________|
     66      1.1  christos  *                                                                  |
     67      1.1  christos  *                                                                [pop]
     68      1.1  christos  *                                                                  |
     69      1.1  christos  *                                                                  v
     70      1.1  christos  *                                                                (fin)
     71      1.1  christos  *
     72      1.1  christos  *   The transition through the states is monotonic and irreversible.
     73      1.1  christos  *   The RCID object is freed when it is popped.
     74      1.1  christos  *
     75      1.1  christos  *   PENDING
     76      1.1  christos  *     Invariants:
     77      1.1  christos  *       rcid->state == RCID_STATE_PENDING;
     78      1.1  christos  *       rcid->pq_idx != SIZE_MAX (debug assert only);
     79      1.1  christos  *       the RCID is not the current RCID, rcidm->cur_rcid != rcid;
     80      1.1  christos  *       the RCID is in the priority queue;
     81      1.1  christos  *       the RCID is not in the retiring_list.
     82      1.1  christos  *
     83      1.1  christos  *   CURRENT
     84      1.1  christos  *     Invariants:
     85      1.1  christos  *       rcid->state == RCID_STATE_CUR;
     86      1.1  christos  *       rcid->pq_idx == SIZE_MAX (debug assert only);
     87      1.1  christos  *       the RCID is the current RCID, rcidm->cur_rcid == rcid;
     88      1.1  christos  *       the RCID is not in the priority queue;
     89      1.1  christos  *       the RCID is not in the retiring_list.
     90      1.1  christos  *
     91      1.1  christos  *   RETIRING
     92      1.1  christos  *     Invariants:
     93      1.1  christos  *       rcid->state == RCID_STATE_RETIRING;
     94      1.1  christos  *       rcid->pq_idx == SIZE_MAX (debug assert only);
     95      1.1  christos  *       the RCID is not the current RCID, rcidm->cur_rcid != rcid;
     96      1.1  christos  *       the RCID is not in the priority queue;
     97      1.1  christos  *       the RCID is in the retiring_list.
     98      1.1  christos  *
     99      1.1  christos  *   Invariant: At most one RCID object is in the CURRENT state at any one time.
    100      1.1  christos  *
    101      1.1  christos  *      (If no RCID object is in the CURRENT state, this means either
    102      1.1  christos  *       an unnumbered RCID is being used as the preferred RCID
    103      1.1  christos  *       or we currently have no preferred RCID.)
    104      1.1  christos  *
    105      1.1  christos  *   All of the above states can be considered substates of the 'ACTIVE' state
    106      1.1  christos  *   for an RCID as specified in RFC 9000. A CID only ceases to be active
    107      1.1  christos  *   when we send a RETIRE_CONN_ID frame, which is the responsibility of the
    108      1.1  christos  *   user of the RCIDM and happens after the above state machine is terminated.
    109      1.1  christos  */
    110      1.1  christos enum {
    111      1.1  christos     RCID_STATE_PENDING,
    112      1.1  christos     RCID_STATE_CUR,
    113      1.1  christos     RCID_STATE_RETIRING
    114      1.1  christos };
    115      1.1  christos 
    116      1.1  christos enum {
    117  1.1.1.2  christos     RCID_TYPE_INITIAL, /* CID is from an peer INITIAL packet     (seq 0) */
    118  1.1.1.2  christos     RCID_TYPE_PREF_ADDR, /* CID is from a preferred_address TPARAM (seq 1) */
    119  1.1.1.2  christos     RCID_TYPE_NCID /* CID is from a NCID frame */
    120      1.1  christos     /*
    121      1.1  christos      * INITIAL_ODCID and RETRY_ODCID also conceptually exist but are tracked
    122      1.1  christos      * separately.
    123      1.1  christos      */
    124      1.1  christos };
    125      1.1  christos 
    126      1.1  christos typedef struct rcid_st {
    127      1.1  christos     OSSL_LIST_MEMBER(retiring, struct rcid_st); /* valid iff RETIRING */
    128      1.1  christos 
    129  1.1.1.2  christos     QUIC_CONN_ID cid; /* The actual CID string for this RCID */
    130  1.1.1.2  christos     uint64_t seq_num;
    131  1.1.1.2  christos     size_t pq_idx; /* Index of entry into priority queue */
    132  1.1.1.2  christos     unsigned int state : 2; /* RCID_STATE_* */
    133  1.1.1.2  christos     unsigned int type : 2; /* RCID_TYPE_* */
    134      1.1  christos } RCID;
    135      1.1  christos 
    136      1.1  christos DEFINE_PRIORITY_QUEUE_OF(RCID);
    137      1.1  christos DEFINE_LIST_OF(retiring, RCID);
    138      1.1  christos 
    139      1.1  christos /*
    140      1.1  christos  * RCID Manager
    141      1.1  christos  * ============
    142      1.1  christos  *
    143      1.1  christos  * The following "business logic" invariants also apply to the RCIDM
    144      1.1  christos  * as a whole:
    145      1.1  christos  *
    146      1.1  christos  *   Invariant: An RCID of INITIAL   type has a sequence number of 0.
    147      1.1  christos  *   Invariant: An RCID of PREF_ADDR type has a sequence number of 1.
    148      1.1  christos  *
    149      1.1  christos  *   Invariant: There is never more than one Initial ODCID
    150      1.1  christos  *              added throughout the lifetime of an RCIDM.
    151      1.1  christos  *   Invariant: There is never more than one Retry ODCID
    152      1.1  christos  *              added throughout the lifetime of an RCIDM.
    153      1.1  christos  *   Invariant: There is never more than one INITIAL RCID created
    154      1.1  christos  *              throughout the lifetime of an RCIDM.
    155      1.1  christos  *   Invariant: There is never more than one PREF_ADDR RCID created
    156      1.1  christos  *              throughout the lifetime of an RCIDM.
    157      1.1  christos  *   Invariant: No INITIAL or PREF_ADDR RCID may be added after
    158      1.1  christos  *              the handshake is completed.
    159      1.1  christos  *
    160      1.1  christos  */
    161      1.1  christos struct quic_rcidm_st {
    162      1.1  christos     /*
    163      1.1  christos      * The current RCID we prefer to use (value undefined if
    164      1.1  christos      * !have_preferred_rcid).
    165      1.1  christos      *
    166      1.1  christos      * This is preferentially set to a numbered RCID (represented by an RCID
    167      1.1  christos      * object) if we have one (in which case preferred_rcid == cur_rcid->cid);
    168      1.1  christos      * otherwise it is set to one of the unnumbered RCIDs (the Initial ODCID or
    169      1.1  christos      * Retry ODCID) if available (and cur_rcid == NULL).
    170      1.1  christos      */
    171  1.1.1.2  christos     QUIC_CONN_ID preferred_rcid;
    172      1.1  christos 
    173      1.1  christos     /*
    174      1.1  christos      * These are initialized if the corresponding added_ flags are set.
    175      1.1  christos      */
    176  1.1.1.2  christos     QUIC_CONN_ID initial_odcid, retry_odcid;
    177      1.1  christos 
    178      1.1  christos     /*
    179      1.1  christos      * Total number of packets sent since we last made a packet count-based RCID
    180      1.1  christos      * update decision.
    181      1.1  christos      */
    182  1.1.1.2  christos     uint64_t packets_sent;
    183      1.1  christos 
    184      1.1  christos     /* Number of post-handshake RCID changes we have performed. */
    185  1.1.1.2  christos     uint64_t num_changes;
    186      1.1  christos 
    187      1.1  christos     /*
    188      1.1  christos      * The Retire Prior To watermark value; max(retire_prior_to) of all received
    189      1.1  christos      * NCID frames.
    190      1.1  christos      */
    191  1.1.1.2  christos     uint64_t retire_prior_to;
    192      1.1  christos 
    193      1.1  christos     /* (SORT BY seq_num ASC) -> (RCID *) */
    194  1.1.1.2  christos     PRIORITY_QUEUE_OF(RCID) * rcids;
    195      1.1  christos 
    196      1.1  christos     /*
    197      1.1  christos      * Current RCID object we are using. This may differ from the first item in
    198      1.1  christos      * the priority queue if we received NCID frames out of order. For example
    199      1.1  christos      * if we get seq 5, switch to it immediately, then get seq 4, we want to
    200      1.1  christos      * keep using seq 5 until we decide to roll again rather than immediately
    201      1.1  christos      * switch to seq 4. Never points to an object on the retiring_list.
    202      1.1  christos      */
    203  1.1.1.2  christos     RCID *cur_rcid;
    204      1.1  christos 
    205      1.1  christos     /*
    206      1.1  christos      * When a RCID becomes pending-retirement, it is moved to the retiring_list,
    207      1.1  christos      * then freed when it is popped from the retired queue. We use a list for
    208      1.1  christos      * this rather than a priority queue as the order in which items are freed
    209      1.1  christos      * does not matter. We always append to the tail of the list in order to
    210      1.1  christos      * maintain the guarantee that the head (if present) only changes when a
    211      1.1  christos      * caller calls pop().
    212      1.1  christos      */
    213  1.1.1.2  christos     OSSL_LIST(retiring)
    214  1.1.1.2  christos     retiring_list;
    215      1.1  christos 
    216      1.1  christos     /* Number of entries on the retiring_list. */
    217  1.1.1.2  christos     size_t num_retiring;
    218      1.1  christos 
    219      1.1  christos     /* preferred_rcid has been changed? */
    220  1.1.1.2  christos     unsigned int preferred_rcid_changed : 1;
    221      1.1  christos 
    222      1.1  christos     /* Do we have any RCID we can use currently? */
    223  1.1.1.2  christos     unsigned int have_preferred_rcid : 1;
    224      1.1  christos 
    225      1.1  christos     /* QUIC handshake has been completed? */
    226  1.1.1.2  christos     unsigned int handshake_complete : 1;
    227      1.1  christos 
    228      1.1  christos     /* odcid was set (not necessarily still valid as a RCID)? */
    229  1.1.1.2  christos     unsigned int added_initial_odcid : 1;
    230      1.1  christos     /* retry_odcid was set (not necessarily still valid as a RCID?) */
    231  1.1.1.2  christos     unsigned int added_retry_odcid : 1;
    232      1.1  christos     /* An initial RCID was added as an RCID structure? */
    233  1.1.1.2  christos     unsigned int added_initial_rcid : 1;
    234      1.1  christos     /* Has a RCID roll been manually requested? */
    235  1.1.1.2  christos     unsigned int roll_requested : 1;
    236      1.1  christos };
    237      1.1  christos 
    238      1.1  christos /*
    239      1.1  christos  * Caller must periodically pop retired RCIDs and handle them. If the caller
    240      1.1  christos  * fails to do so, fail safely rather than start exhibiting integer rollover.
    241      1.1  christos  * Limit the total number of numbered RCIDs to an implausibly large but safe
    242      1.1  christos  * value.
    243      1.1  christos  */
    244  1.1.1.2  christos #define MAX_NUMBERED_RCIDS (SIZE_MAX / 2)
    245      1.1  christos 
    246      1.1  christos static void rcidm_transition_rcid(QUIC_RCIDM *rcidm, RCID *rcid,
    247  1.1.1.2  christos     unsigned int state);
    248      1.1  christos 
    249      1.1  christos /* Check invariants of an RCID */
    250      1.1  christos static void rcidm_check_rcid(QUIC_RCIDM *rcidm, RCID *rcid)
    251      1.1  christos {
    252      1.1  christos     assert(rcid->state == RCID_STATE_PENDING
    253  1.1.1.2  christos         || rcid->state == RCID_STATE_CUR
    254  1.1.1.2  christos         || rcid->state == RCID_STATE_RETIRING);
    255      1.1  christos     assert((rcid->state == RCID_STATE_PENDING)
    256  1.1.1.2  christos         == (rcid->pq_idx != SIZE_MAX));
    257      1.1  christos     assert((rcid->state == RCID_STATE_CUR)
    258  1.1.1.2  christos         == (rcidm->cur_rcid == rcid));
    259      1.1  christos     assert((ossl_list_retiring_next(rcid) != NULL
    260  1.1.1.2  christos                || ossl_list_retiring_prev(rcid) != NULL
    261  1.1.1.2  christos                || ossl_list_retiring_head(&rcidm->retiring_list) == rcid)
    262  1.1.1.2  christos         == (rcid->state == RCID_STATE_RETIRING));
    263      1.1  christos     assert(rcid->type != RCID_TYPE_INITIAL || rcid->seq_num == 0);
    264      1.1  christos     assert(rcid->type != RCID_TYPE_PREF_ADDR || rcid->seq_num == 1);
    265      1.1  christos     assert(rcid->seq_num <= OSSL_QUIC_VLINT_MAX);
    266      1.1  christos     assert(rcid->cid.id_len > 0 && rcid->cid.id_len <= QUIC_MAX_CONN_ID_LEN);
    267      1.1  christos     assert(rcid->seq_num >= rcidm->retire_prior_to
    268  1.1.1.2  christos         || rcid->state == RCID_STATE_RETIRING);
    269      1.1  christos     assert(rcidm->num_changes == 0 || rcidm->handshake_complete);
    270      1.1  christos     assert(rcid->state != RCID_STATE_RETIRING || rcidm->num_retiring > 0);
    271      1.1  christos }
    272      1.1  christos 
    273      1.1  christos static int rcid_cmp(const RCID *a, const RCID *b)
    274      1.1  christos {
    275      1.1  christos     if (a->seq_num < b->seq_num)
    276      1.1  christos         return -1;
    277      1.1  christos     if (a->seq_num > b->seq_num)
    278      1.1  christos         return 1;
    279      1.1  christos     return 0;
    280      1.1  christos }
    281      1.1  christos 
    282      1.1  christos QUIC_RCIDM *ossl_quic_rcidm_new(const QUIC_CONN_ID *initial_odcid)
    283      1.1  christos {
    284      1.1  christos     QUIC_RCIDM *rcidm;
    285      1.1  christos 
    286      1.1  christos     if ((rcidm = OPENSSL_zalloc(sizeof(*rcidm))) == NULL)
    287      1.1  christos         return NULL;
    288      1.1  christos 
    289      1.1  christos     if ((rcidm->rcids = ossl_pqueue_RCID_new(rcid_cmp)) == NULL) {
    290      1.1  christos         OPENSSL_free(rcidm);
    291      1.1  christos         return NULL;
    292      1.1  christos     }
    293      1.1  christos 
    294      1.1  christos     if (initial_odcid != NULL) {
    295  1.1.1.2  christos         rcidm->initial_odcid = *initial_odcid;
    296  1.1.1.2  christos         rcidm->added_initial_odcid = 1;
    297      1.1  christos     }
    298      1.1  christos 
    299      1.1  christos     rcidm_update(rcidm);
    300      1.1  christos     return rcidm;
    301      1.1  christos }
    302      1.1  christos 
    303      1.1  christos void ossl_quic_rcidm_free(QUIC_RCIDM *rcidm)
    304      1.1  christos {
    305      1.1  christos     RCID *rcid, *rnext;
    306      1.1  christos 
    307      1.1  christos     if (rcidm == NULL)
    308      1.1  christos         return;
    309      1.1  christos 
    310      1.1  christos     OPENSSL_free(rcidm->cur_rcid);
    311      1.1  christos     while ((rcid = ossl_pqueue_RCID_pop(rcidm->rcids)) != NULL)
    312      1.1  christos         OPENSSL_free(rcid);
    313      1.1  christos 
    314      1.1  christos     OSSL_LIST_FOREACH_DELSAFE(rcid, rnext, retiring, &rcidm->retiring_list)
    315  1.1.1.2  christos     OPENSSL_free(rcid);
    316      1.1  christos 
    317      1.1  christos     ossl_pqueue_RCID_free(rcidm->rcids);
    318      1.1  christos     OPENSSL_free(rcidm);
    319      1.1  christos }
    320      1.1  christos 
    321      1.1  christos static void rcidm_set_preferred_rcid(QUIC_RCIDM *rcidm,
    322  1.1.1.2  christos     const QUIC_CONN_ID *rcid)
    323      1.1  christos {
    324      1.1  christos     if (rcid == NULL) {
    325  1.1.1.2  christos         rcidm->preferred_rcid_changed = 1;
    326  1.1.1.2  christos         rcidm->have_preferred_rcid = 0;
    327      1.1  christos         return;
    328      1.1  christos     }
    329      1.1  christos 
    330      1.1  christos     if (ossl_quic_conn_id_eq(&rcidm->preferred_rcid, rcid))
    331      1.1  christos         return;
    332      1.1  christos 
    333  1.1.1.2  christos     rcidm->preferred_rcid = *rcid;
    334  1.1.1.2  christos     rcidm->preferred_rcid_changed = 1;
    335  1.1.1.2  christos     rcidm->have_preferred_rcid = 1;
    336      1.1  christos }
    337      1.1  christos 
    338      1.1  christos /*
    339      1.1  christos  * RCID Lifecycle Management
    340      1.1  christos  * =========================
    341      1.1  christos  */
    342      1.1  christos static RCID *rcidm_create_rcid(QUIC_RCIDM *rcidm, uint64_t seq_num,
    343  1.1.1.2  christos     const QUIC_CONN_ID *cid,
    344  1.1.1.2  christos     unsigned int type)
    345      1.1  christos {
    346      1.1  christos     RCID *rcid;
    347      1.1  christos 
    348      1.1  christos     if (cid->id_len < 1 || cid->id_len > QUIC_MAX_CONN_ID_LEN
    349      1.1  christos         || seq_num > OSSL_QUIC_VLINT_MAX
    350      1.1  christos         || ossl_pqueue_RCID_num(rcidm->rcids) + rcidm->num_retiring
    351      1.1  christos             > MAX_NUMBERED_RCIDS)
    352      1.1  christos         return NULL;
    353      1.1  christos 
    354      1.1  christos     if ((rcid = OPENSSL_zalloc(sizeof(*rcid))) == NULL)
    355      1.1  christos         return NULL;
    356      1.1  christos 
    357  1.1.1.2  christos     rcid->seq_num = seq_num;
    358  1.1.1.2  christos     rcid->cid = *cid;
    359  1.1.1.2  christos     rcid->type = type;
    360      1.1  christos 
    361      1.1  christos     if (rcid->seq_num >= rcidm->retire_prior_to) {
    362      1.1  christos         rcid->state = RCID_STATE_PENDING;
    363      1.1  christos 
    364      1.1  christos         if (!ossl_pqueue_RCID_push(rcidm->rcids, rcid, &rcid->pq_idx)) {
    365      1.1  christos             OPENSSL_free(rcid);
    366      1.1  christos             return NULL;
    367      1.1  christos         }
    368      1.1  christos     } else {
    369      1.1  christos         /* RCID is immediately retired upon creation. */
    370  1.1.1.2  christos         rcid->state = RCID_STATE_RETIRING;
    371  1.1.1.2  christos         rcid->pq_idx = SIZE_MAX;
    372      1.1  christos         ossl_list_retiring_insert_tail(&rcidm->retiring_list, rcid);
    373      1.1  christos         ++rcidm->num_retiring;
    374      1.1  christos     }
    375      1.1  christos 
    376      1.1  christos     rcidm_check_rcid(rcidm, rcid);
    377      1.1  christos     return rcid;
    378      1.1  christos }
    379      1.1  christos 
    380      1.1  christos static void rcidm_transition_rcid(QUIC_RCIDM *rcidm, RCID *rcid,
    381  1.1.1.2  christos     unsigned int state)
    382      1.1  christos {
    383      1.1  christos     unsigned int old_state = rcid->state;
    384      1.1  christos 
    385      1.1  christos     assert(state >= old_state && state <= RCID_STATE_RETIRING);
    386      1.1  christos     rcidm_check_rcid(rcidm, rcid);
    387      1.1  christos     if (state == old_state)
    388      1.1  christos         return;
    389      1.1  christos 
    390      1.1  christos     if (rcidm->cur_rcid != NULL && state == RCID_STATE_CUR) {
    391      1.1  christos         rcidm_transition_rcid(rcidm, rcidm->cur_rcid, RCID_STATE_RETIRING);
    392      1.1  christos         assert(rcidm->cur_rcid == NULL);
    393      1.1  christos     }
    394      1.1  christos 
    395      1.1  christos     if (old_state == RCID_STATE_PENDING) {
    396      1.1  christos         ossl_pqueue_RCID_remove(rcidm->rcids, rcid->pq_idx);
    397      1.1  christos         rcid->pq_idx = SIZE_MAX;
    398      1.1  christos     }
    399      1.1  christos 
    400      1.1  christos     rcid->state = state;
    401      1.1  christos 
    402      1.1  christos     if (state == RCID_STATE_CUR) {
    403      1.1  christos         rcidm->cur_rcid = rcid;
    404      1.1  christos     } else if (state == RCID_STATE_RETIRING) {
    405      1.1  christos         if (old_state == RCID_STATE_CUR)
    406      1.1  christos             rcidm->cur_rcid = NULL;
    407      1.1  christos 
    408      1.1  christos         ossl_list_retiring_insert_tail(&rcidm->retiring_list, rcid);
    409      1.1  christos         ++rcidm->num_retiring;
    410      1.1  christos     }
    411      1.1  christos 
    412      1.1  christos     rcidm_check_rcid(rcidm, rcid);
    413      1.1  christos }
    414      1.1  christos 
    415      1.1  christos static void rcidm_free_rcid(QUIC_RCIDM *rcidm, RCID *rcid)
    416      1.1  christos {
    417      1.1  christos     if (rcid == NULL)
    418      1.1  christos         return;
    419      1.1  christos 
    420      1.1  christos     rcidm_check_rcid(rcidm, rcid);
    421      1.1  christos 
    422      1.1  christos     switch (rcid->state) {
    423      1.1  christos     case RCID_STATE_PENDING:
    424      1.1  christos         ossl_pqueue_RCID_remove(rcidm->rcids, rcid->pq_idx);
    425      1.1  christos         break;
    426      1.1  christos     case RCID_STATE_CUR:
    427      1.1  christos         rcidm->cur_rcid = NULL;
    428      1.1  christos         break;
    429      1.1  christos     case RCID_STATE_RETIRING:
    430      1.1  christos         ossl_list_retiring_remove(&rcidm->retiring_list, rcid);
    431      1.1  christos         --rcidm->num_retiring;
    432      1.1  christos         break;
    433      1.1  christos     default:
    434      1.1  christos         assert(0);
    435      1.1  christos         break;
    436      1.1  christos     }
    437      1.1  christos 
    438      1.1  christos     OPENSSL_free(rcid);
    439      1.1  christos }
    440      1.1  christos 
    441      1.1  christos static void rcidm_handle_retire_prior_to(QUIC_RCIDM *rcidm,
    442  1.1.1.2  christos     uint64_t retire_prior_to)
    443      1.1  christos {
    444      1.1  christos     RCID *rcid;
    445      1.1  christos 
    446      1.1  christos     if (retire_prior_to <= rcidm->retire_prior_to)
    447      1.1  christos         return;
    448      1.1  christos 
    449      1.1  christos     /*
    450      1.1  christos      * Retire the current RCID (if any) if it is affected.
    451      1.1  christos      */
    452      1.1  christos     if (rcidm->cur_rcid != NULL && rcidm->cur_rcid->seq_num < retire_prior_to)
    453      1.1  christos         rcidm_transition_rcid(rcidm, rcidm->cur_rcid, RCID_STATE_RETIRING);
    454      1.1  christos 
    455      1.1  christos     /*
    456      1.1  christos      * Any other RCIDs needing retirement will be at the start of the priority
    457      1.1  christos      * queue, so just stop once we see a higher sequence number exceeding the
    458      1.1  christos      * threshold.
    459      1.1  christos      */
    460      1.1  christos     while ((rcid = ossl_pqueue_RCID_peek(rcidm->rcids)) != NULL
    461  1.1.1.2  christos         && rcid->seq_num < retire_prior_to)
    462      1.1  christos         rcidm_transition_rcid(rcidm, rcid, RCID_STATE_RETIRING);
    463      1.1  christos 
    464      1.1  christos     rcidm->retire_prior_to = retire_prior_to;
    465      1.1  christos }
    466      1.1  christos 
    467      1.1  christos /*
    468      1.1  christos  * Decision Logic
    469      1.1  christos  * ==============
    470      1.1  christos  */
    471      1.1  christos 
    472      1.1  christos static void rcidm_roll(QUIC_RCIDM *rcidm)
    473      1.1  christos {
    474      1.1  christos     RCID *rcid;
    475      1.1  christos 
    476      1.1  christos     if ((rcid = ossl_pqueue_RCID_peek(rcidm->rcids)) == NULL)
    477      1.1  christos         return;
    478      1.1  christos 
    479      1.1  christos     rcidm_transition_rcid(rcidm, rcid, RCID_STATE_CUR);
    480      1.1  christos 
    481      1.1  christos     ++rcidm->num_changes;
    482      1.1  christos     rcidm->roll_requested = 0;
    483      1.1  christos 
    484      1.1  christos     if (rcidm->packets_sent >= PACKETS_PER_RCID)
    485      1.1  christos         rcidm->packets_sent %= PACKETS_PER_RCID;
    486      1.1  christos     else
    487      1.1  christos         rcidm->packets_sent = 0;
    488      1.1  christos }
    489      1.1  christos 
    490      1.1  christos static void rcidm_update(QUIC_RCIDM *rcidm)
    491      1.1  christos {
    492      1.1  christos     RCID *rcid;
    493      1.1  christos 
    494      1.1  christos     /*
    495      1.1  christos      * If we have no current numbered RCID but have one or more pending, use it.
    496      1.1  christos      */
    497      1.1  christos     if (rcidm->cur_rcid == NULL
    498      1.1  christos         && (rcid = ossl_pqueue_RCID_peek(rcidm->rcids)) != NULL) {
    499      1.1  christos         rcidm_transition_rcid(rcidm, rcid, RCID_STATE_CUR);
    500      1.1  christos         assert(rcidm->cur_rcid != NULL);
    501      1.1  christos     }
    502      1.1  christos 
    503      1.1  christos     /* Prefer use of any current numbered RCID we have, if possible. */
    504      1.1  christos     if (rcidm->cur_rcid != NULL) {
    505      1.1  christos         rcidm_check_rcid(rcidm, rcidm->cur_rcid);
    506      1.1  christos         rcidm_set_preferred_rcid(rcidm, &rcidm->cur_rcid->cid);
    507      1.1  christos         return;
    508      1.1  christos     }
    509      1.1  christos 
    510      1.1  christos     /*
    511      1.1  christos      * If there are no RCIDs from NCID frames we can use, go through the various
    512      1.1  christos      * kinds of bootstrapping RCIDs we can use in order of priority.
    513      1.1  christos      */
    514      1.1  christos     if (rcidm->added_retry_odcid && !rcidm->handshake_complete) {
    515      1.1  christos         rcidm_set_preferred_rcid(rcidm, &rcidm->retry_odcid);
    516      1.1  christos         return;
    517      1.1  christos     }
    518      1.1  christos 
    519      1.1  christos     if (rcidm->added_initial_odcid && !rcidm->handshake_complete) {
    520      1.1  christos         rcidm_set_preferred_rcid(rcidm, &rcidm->initial_odcid);
    521      1.1  christos         return;
    522      1.1  christos     }
    523      1.1  christos 
    524      1.1  christos     /* We don't know of any usable RCIDs */
    525      1.1  christos     rcidm_set_preferred_rcid(rcidm, NULL);
    526      1.1  christos }
    527      1.1  christos 
    528      1.1  christos static int rcidm_should_roll(QUIC_RCIDM *rcidm)
    529      1.1  christos {
    530      1.1  christos     /*
    531      1.1  christos      * Always switch as soon as possible if handshake completes;
    532      1.1  christos      * and every n packets after handshake completes or the last roll; and
    533      1.1  christos      * whenever manually requested.
    534      1.1  christos      */
    535      1.1  christos     return rcidm->handshake_complete
    536      1.1  christos         && (rcidm->num_changes == 0
    537      1.1  christos             || rcidm->packets_sent >= PACKETS_PER_RCID
    538      1.1  christos             || rcidm->roll_requested);
    539      1.1  christos }
    540      1.1  christos 
    541      1.1  christos static void rcidm_tick(QUIC_RCIDM *rcidm)
    542      1.1  christos {
    543      1.1  christos     if (rcidm_should_roll(rcidm))
    544      1.1  christos         rcidm_roll(rcidm);
    545      1.1  christos 
    546      1.1  christos     rcidm_update(rcidm);
    547      1.1  christos }
    548      1.1  christos 
    549      1.1  christos /*
    550      1.1  christos  * Events
    551      1.1  christos  * ======
    552      1.1  christos  */
    553      1.1  christos void ossl_quic_rcidm_on_handshake_complete(QUIC_RCIDM *rcidm)
    554      1.1  christos {
    555      1.1  christos     if (rcidm->handshake_complete)
    556      1.1  christos         return;
    557      1.1  christos 
    558      1.1  christos     rcidm->handshake_complete = 1;
    559      1.1  christos     rcidm_tick(rcidm);
    560      1.1  christos }
    561      1.1  christos 
    562      1.1  christos void ossl_quic_rcidm_on_packet_sent(QUIC_RCIDM *rcidm, uint64_t num_packets)
    563      1.1  christos {
    564      1.1  christos     if (num_packets == 0)
    565      1.1  christos         return;
    566      1.1  christos 
    567      1.1  christos     rcidm->packets_sent += num_packets;
    568      1.1  christos     rcidm_tick(rcidm);
    569      1.1  christos }
    570      1.1  christos 
    571      1.1  christos void ossl_quic_rcidm_request_roll(QUIC_RCIDM *rcidm)
    572      1.1  christos {
    573      1.1  christos     rcidm->roll_requested = 1;
    574      1.1  christos     rcidm_tick(rcidm);
    575      1.1  christos }
    576      1.1  christos 
    577      1.1  christos /*
    578      1.1  christos  * Mutation Operations
    579      1.1  christos  * ===================
    580      1.1  christos  */
    581      1.1  christos int ossl_quic_rcidm_add_from_initial(QUIC_RCIDM *rcidm,
    582  1.1.1.2  christos     const QUIC_CONN_ID *rcid)
    583      1.1  christos {
    584      1.1  christos     RCID *rcid_obj;
    585      1.1  christos 
    586      1.1  christos     if (rcidm->added_initial_rcid || rcidm->handshake_complete)
    587      1.1  christos         return 0;
    588      1.1  christos 
    589      1.1  christos     rcid_obj = rcidm_create_rcid(rcidm, INITIAL_SEQ_NUM,
    590  1.1.1.2  christos         rcid, RCID_TYPE_INITIAL);
    591      1.1  christos     if (rcid_obj == NULL)
    592      1.1  christos         return 0;
    593      1.1  christos 
    594      1.1  christos     rcidm->added_initial_rcid = 1;
    595      1.1  christos     rcidm_tick(rcidm);
    596      1.1  christos     return 1;
    597      1.1  christos }
    598      1.1  christos 
    599      1.1  christos int ossl_quic_rcidm_add_from_server_retry(QUIC_RCIDM *rcidm,
    600  1.1.1.2  christos     const QUIC_CONN_ID *retry_odcid)
    601      1.1  christos {
    602      1.1  christos     if (rcidm->added_retry_odcid || rcidm->handshake_complete)
    603      1.1  christos         return 0;
    604      1.1  christos 
    605  1.1.1.2  christos     rcidm->retry_odcid = *retry_odcid;
    606  1.1.1.2  christos     rcidm->added_retry_odcid = 1;
    607      1.1  christos     rcidm_tick(rcidm);
    608      1.1  christos     return 1;
    609      1.1  christos }
    610      1.1  christos 
    611      1.1  christos int ossl_quic_rcidm_add_from_ncid(QUIC_RCIDM *rcidm,
    612  1.1.1.2  christos     const OSSL_QUIC_FRAME_NEW_CONN_ID *ncid)
    613      1.1  christos {
    614      1.1  christos     RCID *rcid;
    615      1.1  christos 
    616      1.1  christos     rcid = rcidm_create_rcid(rcidm, ncid->seq_num, &ncid->conn_id, RCID_TYPE_NCID);
    617      1.1  christos     if (rcid == NULL)
    618      1.1  christos         return 0;
    619      1.1  christos 
    620      1.1  christos     rcidm_handle_retire_prior_to(rcidm, ncid->retire_prior_to);
    621      1.1  christos     rcidm_tick(rcidm);
    622      1.1  christos     return 1;
    623      1.1  christos }
    624      1.1  christos 
    625      1.1  christos /*
    626      1.1  christos  * Queries
    627      1.1  christos  * =======
    628      1.1  christos  */
    629      1.1  christos 
    630      1.1  christos static int rcidm_get_retire(QUIC_RCIDM *rcidm, uint64_t *seq_num, int peek)
    631      1.1  christos {
    632      1.1  christos     RCID *rcid = ossl_list_retiring_head(&rcidm->retiring_list);
    633      1.1  christos 
    634      1.1  christos     if (rcid == NULL)
    635      1.1  christos         return 0;
    636      1.1  christos 
    637      1.1  christos     if (seq_num != NULL)
    638      1.1  christos         *seq_num = rcid->seq_num;
    639      1.1  christos 
    640      1.1  christos     if (!peek)
    641      1.1  christos         rcidm_free_rcid(rcidm, rcid);
    642      1.1  christos 
    643      1.1  christos     return 1;
    644      1.1  christos }
    645      1.1  christos 
    646      1.1  christos int ossl_quic_rcidm_pop_retire_seq_num(QUIC_RCIDM *rcidm,
    647  1.1.1.2  christos     uint64_t *seq_num)
    648      1.1  christos {
    649      1.1  christos     return rcidm_get_retire(rcidm, seq_num, /*peek=*/0);
    650      1.1  christos }
    651      1.1  christos 
    652      1.1  christos int ossl_quic_rcidm_peek_retire_seq_num(QUIC_RCIDM *rcidm,
    653  1.1.1.2  christos     uint64_t *seq_num)
    654      1.1  christos {
    655      1.1  christos     return rcidm_get_retire(rcidm, seq_num, /*peek=*/1);
    656      1.1  christos }
    657      1.1  christos 
    658      1.1  christos int ossl_quic_rcidm_get_preferred_tx_dcid(QUIC_RCIDM *rcidm,
    659  1.1.1.2  christos     QUIC_CONN_ID *tx_dcid)
    660      1.1  christos {
    661      1.1  christos     if (!rcidm->have_preferred_rcid)
    662      1.1  christos         return 0;
    663      1.1  christos 
    664      1.1  christos     *tx_dcid = rcidm->preferred_rcid;
    665      1.1  christos     return 1;
    666      1.1  christos }
    667      1.1  christos 
    668      1.1  christos int ossl_quic_rcidm_get_preferred_tx_dcid_changed(QUIC_RCIDM *rcidm,
    669  1.1.1.2  christos     int clear)
    670      1.1  christos {
    671      1.1  christos     int r = rcidm->preferred_rcid_changed;
    672      1.1  christos 
    673      1.1  christos     if (clear)
    674      1.1  christos         rcidm->preferred_rcid_changed = 0;
    675      1.1  christos 
    676      1.1  christos     return r;
    677      1.1  christos }
    678      1.1  christos 
    679      1.1  christos size_t ossl_quic_rcidm_get_num_active(const QUIC_RCIDM *rcidm)
    680      1.1  christos {
    681      1.1  christos     return ossl_pqueue_RCID_num(rcidm->rcids)
    682      1.1  christos         + (rcidm->cur_rcid != NULL ? 1 : 0)
    683      1.1  christos         + ossl_quic_rcidm_get_num_retiring(rcidm);
    684      1.1  christos }
    685      1.1  christos 
    686      1.1  christos size_t ossl_quic_rcidm_get_num_retiring(const QUIC_RCIDM *rcidm)
    687      1.1  christos {
    688      1.1  christos     return rcidm->num_retiring;
    689      1.1  christos }
    690