Skip to main content

simoom_quic/
vn.rs

1//! Compatible version negotiation (RFC 9368) between QUIC v1 and v2 (RFC 9369).
2//!
3//! The server reads the client's `version_information` transport parameter out of the
4//! ClientHello before it starts its TLS session, so that the session (and the transport
5//! parameters it sends) already uses the negotiated version.
6
7use crate::coding::Reader;
8use crate::packet::{VERSION_1, VERSION_2};
9
10/// Transport parameter `version_information` (RFC 9368 §3).
11pub const VERSION_INFORMATION: u64 = 0x11;
12
13/// Whether a connection that started in `from` may continue in `to` (RFC 9368 §2.3: the
14/// versions must be compatible; v1 and v2 are, in both directions).
15pub fn compatible(from: u32, to: u32) -> bool {
16    from == to || matches!((from, to), (VERSION_1, VERSION_2) | (VERSION_2, VERSION_1))
17}
18
19/// Encodes `version_information`: the chosen version, then the available versions.
20pub fn encode(chosen: u32, available: &[u32]) -> Vec<u8> {
21    let mut v = Vec::with_capacity(4 + 4 * available.len());
22    v.extend_from_slice(&chosen.to_be_bytes());
23    for a in available {
24        v.extend_from_slice(&a.to_be_bytes());
25    }
26    v
27}
28
29/// Decodes `version_information`: (chosen, available). `None` if malformed (RFC 9368 §3: a
30/// length that is not a multiple of 4, or a zero chosen version).
31pub fn decode(v: &[u8]) -> Option<(u32, Vec<u32>)> {
32    if v.len() < 4 || v.len() % 4 != 0 {
33        return None;
34    }
35    let mut r = Reader::new(v);
36    let chosen = r.u32()?;
37    if chosen == 0 {
38        return None;
39    }
40    let mut available = Vec::new();
41    while let Some(x) = r.u32() {
42        available.push(x);
43    }
44    Some((chosen, available))
45}
46
47/// Length of the complete ClientHello handshake message at the start of `buf`, once its
48/// header has arrived.
49pub fn hello_len(buf: &[u8]) -> Option<usize> {
50    if buf.len() < 4 {
51        return None;
52    }
53    Some(4 + ((buf[1] as usize) << 16 | (buf[2] as usize) << 8 | buf[3] as usize))
54}
55
56/// The client's `version_information` from a complete ClientHello, if it has one.
57pub fn client_versions(hello: &[u8]) -> Option<(u32, Vec<u32>)> {
58    let mut r = Reader::new(hello);
59    if r.u8()? != 1 {
60        return None;
61    }
62    let len = (r.u8()? as usize) << 16 | (r.u16()? as usize);
63    let mut r = Reader::new(r.bytes(len)?);
64    r.bytes(2 + 32)?; // legacy_version, random
65    let n = r.u8()? as usize;
66    r.bytes(n)?; // legacy_session_id
67    let n = r.u16()? as usize;
68    r.bytes(n)?; // cipher_suites
69    let n = r.u8()? as usize;
70    r.bytes(n)?; // legacy_compression_methods
71    let n = r.u16()? as usize;
72    let mut ext = Reader::new(r.bytes(n)?);
73    while !ext.is_empty() {
74        let ty = ext.u16()?;
75        let n = ext.u16()? as usize;
76        let body = ext.bytes(n)?;
77        if ty != 0x39 {
78            continue;
79        }
80        let mut tp = Reader::new(body);
81        while !tp.is_empty() {
82            let id = tp.varint()?;
83            let v = tp.vbytes()?;
84            if id == VERSION_INFORMATION {
85                return decode(v);
86            }
87        }
88        return None;
89    }
90    None
91}
92
93/// The version a server continues in: the first of its `preferred` versions that the client
94/// lists as available and that is compatible with the version the client started in.
95pub fn choose(started: u32, preferred: &[u32], client: Option<&(u32, Vec<u32>)>) -> u32 {
96    let Some((_, available)) = client else { return started };
97    preferred.iter().copied().find(|&v| compatible(started, v) && available.contains(&v)).unwrap_or(started)
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103
104    fn hello(tp: &[u8]) -> Vec<u8> {
105        let mut ext = Vec::new();
106        ext.extend_from_slice(&0x0010u16.to_be_bytes()); // ALPN, skipped
107        ext.extend_from_slice(&3u16.to_be_bytes());
108        ext.extend_from_slice(&[0, 1, b'h']);
109        ext.extend_from_slice(&0x39u16.to_be_bytes());
110        ext.extend_from_slice(&(tp.len() as u16).to_be_bytes());
111        ext.extend_from_slice(tp);
112        let mut body = vec![3, 3];
113        body.extend_from_slice(&[7; 32]);
114        body.push(0);
115        body.extend_from_slice(&[0, 2, 0x13, 0x01]);
116        body.extend_from_slice(&[1, 0]);
117        body.extend_from_slice(&(ext.len() as u16).to_be_bytes());
118        body.extend_from_slice(&ext);
119        let mut m = vec![1, 0, (body.len() >> 8) as u8, body.len() as u8];
120        m.extend_from_slice(&body);
121        m
122    }
123
124    #[test]
125    fn reads_version_information_from_a_client_hello() {
126        let vi = encode(VERSION_1, &[VERSION_1, VERSION_2]);
127        let mut tp = vec![0x0f, 0x01, 0x07, VERSION_INFORMATION as u8, vi.len() as u8];
128        tp.extend_from_slice(&vi);
129        let h = hello(&tp);
130        assert_eq!(hello_len(&h), Some(h.len()));
131        let got = client_versions(&h).unwrap();
132        assert_eq!(got, (VERSION_1, vec![VERSION_1, VERSION_2]));
133        assert_eq!(choose(VERSION_1, &[VERSION_2, VERSION_1], Some(&got)), VERSION_2);
134        assert_eq!(choose(VERSION_1, &[VERSION_1, VERSION_2], Some(&got)), VERSION_1);
135        assert_eq!(choose(VERSION_1, &[VERSION_2, VERSION_1], None), VERSION_1);
136        // A client that does not list v2 stays in v1.
137        let only = (VERSION_1, vec![VERSION_1]);
138        assert_eq!(choose(VERSION_1, &[VERSION_2, VERSION_1], Some(&only)), VERSION_1);
139        // No version_information, truncated or malformed: nothing.
140        assert_eq!(client_versions(&hello(&[0x0f, 0x01, 0x07])), None);
141        assert_eq!(client_versions(&h[..h.len() - 1]), None);
142        assert_eq!(decode(&[0, 0, 0, 1, 2]), None);
143        assert_eq!(decode(&[0, 0, 0, 0]), None);
144    }
145}