Skip to main content

tokio_quiche/quic/connection/
map.rs

1// Copyright (C) 2025, Cloudflare, Inc.
2// All rights reserved.
3//
4// Redistribution and use in source and binary forms, with or without
5// modification, are permitted provided that the following conditions are
6// met:
7//
8//     * Redistributions of source code must retain the above copyright notice,
9//       this list of conditions and the following disclaimer.
10//
11//     * Redistributions in binary form must reproduce the above copyright
12//       notice, this list of conditions and the following disclaimer in the
13//       documentation and/or other materials provided with the distribution.
14//
15// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
16// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
17// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
19// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
24// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
27use super::Incoming;
28use super::InitialQuicConnection;
29use crate::metrics::Metrics;
30
31use datagram_socket::DatagramSocketSend;
32use quiche::ConnectionId;
33use quiche::MAX_CONN_ID_LEN;
34use std::collections::BTreeMap;
35use tokio::sync::mpsc;
36
37const U64_SZ: usize = std::mem::size_of::<u64>();
38const MAX_CONN_ID_QUADS: usize = MAX_CONN_ID_LEN.div_ceil(U64_SZ);
39const CONN_ID_USABLE_LEN: usize = min_usize(
40    // Last byte in CidOwned::Optimized stores CID length
41    MAX_CONN_ID_QUADS * U64_SZ - 1,
42    // CID length must fit in 1 byte
43    min_usize(MAX_CONN_ID_LEN, u8::MAX as _),
44);
45
46const fn min_usize(v1: usize, v2: usize) -> usize {
47    if v1 < v2 {
48        v1
49    } else {
50        v2
51    }
52}
53
54/// A non unique connection identifier, multiple Cids can map to the same
55/// conenction.
56#[derive(PartialEq, Eq, PartialOrd, Ord)]
57enum CidOwned {
58    /// The QUIC connections IDs theoretically have unbounded length, so for the
59    /// generic case a boxed slice is used to store the ID.
60    Generic(Box<[u8]>),
61    /// For QUIC version 1 (the one that actually exists) the maximal ID size is
62    /// `20`, which should correspond to the `MAX_CONN_ID_LEN` value. For
63    /// this common case, we store the ID in a u64 array for faster
64    /// comparison (and therefore BTreeMap lookups).
65    Optimized([u64; MAX_CONN_ID_QUADS]),
66}
67
68impl From<&ConnectionId<'_>> for CidOwned {
69    #[inline(always)]
70    fn from(value: &ConnectionId<'_>) -> Self {
71        if value.len() > CONN_ID_USABLE_LEN {
72            return CidOwned::Generic(value.as_ref().into());
73        }
74
75        let mut cid = [0; MAX_CONN_ID_QUADS];
76
77        value
78            .chunks(U64_SZ)
79            .map(|c| match c.try_into() {
80                Ok(v) => u64::from_le_bytes(v),
81                Err(_) => {
82                    let mut remainder = [0u8; U64_SZ];
83                    remainder[..c.len()].copy_from_slice(c);
84                    u64::from_le_bytes(remainder)
85                },
86            })
87            .enumerate()
88            .for_each(|(i, v)| cid[i] = v);
89
90        // Append its length to distinguish a short CID from trailing zeroes.
91        *cid.last_mut().unwrap() |= (value.len() as u64) << 56;
92
93        CidOwned::Optimized(cid)
94    }
95}
96
97/// A map for QUIC connections.
98///
99/// Due to the fact that QUIC connections can be identified by multiple QUIC
100/// connection IDs, we have to be able to map multiple IDs to the same
101/// connection.
102#[derive(Default)]
103pub(crate) struct ConnectionMap {
104    quic_id_map: BTreeMap<CidOwned, mpsc::Sender<Incoming>>,
105}
106
107impl ConnectionMap {
108    pub(crate) fn insert<Tx, M>(
109        &mut self, cid: &ConnectionId<'_>, conn: &InitialQuicConnection<Tx, M>,
110    ) where
111        Tx: DatagramSocketSend + Send + 'static,
112        M: Metrics,
113    {
114        let ev_sender = conn.incoming_ev_sender.clone();
115        self.quic_id_map.insert(cid.into(), ev_sender);
116    }
117
118    pub(crate) fn map_cid(
119        &mut self, existing_cid: &ConnectionId<'_>, new_cid: &ConnectionId<'_>,
120    ) {
121        if let Some(ev_sender) = self.quic_id_map.get(&existing_cid.into()) {
122            self.quic_id_map.insert(new_cid.into(), ev_sender.clone());
123        }
124    }
125
126    pub(crate) fn unmap_cid(&mut self, cid: &ConnectionId<'_>) {
127        self.quic_id_map.remove(&cid.into());
128    }
129
130    pub(crate) fn get(
131        &self, id: &ConnectionId,
132    ) -> Option<&mpsc::Sender<Incoming>> {
133        if id.len() == MAX_CONN_ID_LEN {
134            // Although both branches run the same code, the one here will
135            // generate an optimized version for the length we are
136            // using, as opposed to temporary cids sent by clients.
137            self.quic_id_map.get(&id.into())
138        } else {
139            self.quic_id_map.get(&id.into())
140        }
141    }
142}
143
144#[cfg(test)]
145mod tests {
146    use super::*;
147    use quiche::ConnectionId;
148
149    #[test]
150    fn cid_storage() {
151        let max_v1_cid = ConnectionId::from_ref(&[0xfa; MAX_CONN_ID_LEN]);
152        let optimized = CidOwned::from(&max_v1_cid);
153        assert!(
154            matches!(optimized, CidOwned::Optimized(_)),
155            "QUIC v1 CID is not stored inline"
156        );
157
158        let oversize_cid = ConnectionId::from_ref(&[0x1b; MAX_CONN_ID_LEN + 20]);
159        let boxed = CidOwned::from(&oversize_cid);
160        assert!(
161            matches!(boxed, CidOwned::Generic(_)),
162            "Oversized CID is not boxed"
163        );
164    }
165}