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