Skip to main content

quiche/
test_utils.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::*;
28
29use smallvec::smallvec;
30
31use crate::recovery::Sent;
32
33/// Curve preference list that excludes post-quantum groups.
34///
35/// Several tests were written against a pre-PQ world where the ClientHello
36/// fit in a single Initial packet; those tests opt out of PQ to keep that
37/// invariant. See [`config_no_pq`] and
38/// [`Config::set_curves_list`](crate::Config::set_curves_list).
39const NO_PQ_CURVES: &str = "X25519:P-256:P-384";
40
41/// Returns a `Config` equivalent to `Config::new(version)` but with
42/// post-quantum TLS curves disabled.
43///
44/// Use this as a drop-in replacement for `Config::new(version)` in tests
45/// whose expected packet counts/sizes were calibrated against a
46/// single-Initial ClientHello. A post-quantum keyshare pushes the
47/// ClientHello across two Initial packets, which perturbs those
48/// expectations.
49pub fn config_no_pq(version: u32) -> Result<Config> {
50    let mut config = Config::new(version)?;
51    config.set_curves_list(NO_PQ_CURVES)?;
52    Ok(config)
53}
54
55pub struct Pipe<F = DefaultBufFactory>
56where
57    F: BufFactory,
58{
59    pub client: Connection<F>,
60    pub server: Connection<F>,
61}
62
63impl Pipe {
64    pub fn default_config(cc_algorithm_name: &str) -> Result<Config> {
65        let mut config = Config::new(PROTOCOL_VERSION)?;
66        assert_eq!(config.set_cc_algorithm_name(cc_algorithm_name), Ok(()));
67        config.load_cert_chain_from_pem_file("examples/cert.crt")?;
68        config.load_priv_key_from_pem_file("examples/cert.key")?;
69        config.set_application_protos(&[b"proto1", b"proto2"])?;
70        config.set_initial_max_data(30);
71        config.set_initial_max_stream_data_bidi_local(15);
72        config.set_initial_max_stream_data_bidi_remote(15);
73        config.set_initial_max_stream_data_uni(10);
74        config.set_initial_max_streams_bidi(3);
75        config.set_initial_max_streams_uni(3);
76        config.set_max_idle_timeout(180_000);
77        config.verify_peer(false);
78        config.set_ack_delay_exponent(8);
79        Ok(config)
80    }
81
82    /// Like [`default_config`](Self::default_config) but with post-quantum
83    /// TLS curves disabled. See [`config_no_pq`] for the rationale.
84    pub fn default_config_no_pq(cc_algorithm_name: &str) -> Result<Config> {
85        let mut config = Self::default_config(cc_algorithm_name)?;
86        config.set_curves_list(NO_PQ_CURVES)?;
87        Ok(config)
88    }
89
90    #[cfg(feature = "boringssl-boring-crate")]
91    pub fn default_tls_ctx_builder() -> boring::ssl::SslContextBuilder {
92        let mut ctx_builder =
93            boring::ssl::SslContextBuilder::new(boring::ssl::SslMethod::tls())
94                .unwrap();
95        ctx_builder
96            .set_certificate_chain_file("examples/cert.crt")
97            .unwrap();
98        ctx_builder
99            .set_private_key_file(
100                "examples/cert.key",
101                boring::ssl::SslFiletype::PEM,
102            )
103            .unwrap();
104
105        ctx_builder
106    }
107
108    pub fn client_addr() -> SocketAddr {
109        "127.0.0.1:1234".parse().unwrap()
110    }
111
112    pub fn server_addr() -> SocketAddr {
113        "127.0.0.1:4321".parse().unwrap()
114    }
115
116    pub fn new(cc_algorithm_name: &str) -> Result<Pipe> {
117        let mut config = Self::default_config(cc_algorithm_name)?;
118        Pipe::with_config(&mut config)
119    }
120
121    pub fn with_config(config: &mut Config) -> Result<Pipe> {
122        Pipe::<DefaultBufFactory>::with_config_and_buf(config)
123    }
124
125    pub fn with_config_and_scid_lengths(
126        config: &mut Config, client_scid_len: usize, server_scid_len: usize,
127    ) -> Result<Pipe> {
128        Pipe::<DefaultBufFactory>::with_config_and_scid_lengths_and_buf(
129            config,
130            client_scid_len,
131            server_scid_len,
132        )
133    }
134
135    pub fn with_client_config(client_config: &mut Config) -> Result<Pipe> {
136        Pipe::<DefaultBufFactory>::with_client_config_and_buf(client_config)
137    }
138
139    pub fn with_server_config(server_config: &mut Config) -> Result<Pipe> {
140        Pipe::<DefaultBufFactory>::with_server_config_and_buf(server_config)
141    }
142
143    pub fn with_client_and_server_config(
144        client_config: &mut Config, server_config: &mut Config,
145    ) -> Result<Pipe> {
146        Pipe::<DefaultBufFactory>::with_client_and_server_config_and_buf(
147            client_config,
148            server_config,
149        )
150    }
151}
152
153impl<F: BufFactory> Pipe<F> {
154    pub fn new_with_buf(cc_algorithm_name: &str) -> Result<Pipe<F>> {
155        let mut config = Pipe::default_config(cc_algorithm_name)?;
156        Pipe::with_config_and_buf(&mut config)
157    }
158
159    pub fn with_config_and_buf(config: &mut Config) -> Result<Pipe<F>> {
160        let mut client_scid = [0; 16];
161        rand::rand_bytes(&mut client_scid[..]);
162        let client_scid = ConnectionId::from_ref(&client_scid);
163        let client_addr = Pipe::client_addr();
164
165        let mut server_scid = [0; 16];
166        rand::rand_bytes(&mut server_scid[..]);
167        let server_scid = ConnectionId::from_ref(&server_scid);
168        let server_addr = Pipe::server_addr();
169
170        Ok(Pipe {
171            client: connect_with_buffer_factory(
172                Some("quic.tech"),
173                &client_scid,
174                client_addr,
175                server_addr,
176                config,
177            )?,
178            server: accept_with_buf_factory(
179                &server_scid,
180                None,
181                server_addr,
182                client_addr,
183                config,
184            )?,
185        })
186    }
187
188    pub fn with_config_and_scid_lengths_and_buf(
189        config: &mut Config, client_scid_len: usize, server_scid_len: usize,
190    ) -> Result<Pipe<F>> {
191        let mut client_scid = vec![0; client_scid_len];
192        rand::rand_bytes(&mut client_scid[..]);
193        let client_scid = ConnectionId::from_ref(&client_scid);
194        let client_addr = Pipe::client_addr();
195
196        let mut server_scid = vec![0; server_scid_len];
197        rand::rand_bytes(&mut server_scid[..]);
198        let server_scid = ConnectionId::from_ref(&server_scid);
199        let server_addr = Pipe::server_addr();
200
201        Ok(Pipe {
202            client: connect_with_buffer_factory(
203                Some("quic.tech"),
204                &client_scid,
205                client_addr,
206                server_addr,
207                config,
208            )?,
209            server: accept_with_buf_factory(
210                &server_scid,
211                None,
212                server_addr,
213                client_addr,
214                config,
215            )?,
216        })
217    }
218
219    pub fn with_client_config_and_buf(
220        client_config: &mut Config,
221    ) -> Result<Pipe<F>> {
222        let mut client_scid = [0; 16];
223        rand::rand_bytes(&mut client_scid[..]);
224        let client_scid = ConnectionId::from_ref(&client_scid);
225        let client_addr = Pipe::client_addr();
226
227        let mut server_scid = [0; 16];
228        rand::rand_bytes(&mut server_scid[..]);
229        let server_scid = ConnectionId::from_ref(&server_scid);
230        let server_addr = Pipe::server_addr();
231
232        let mut config = Config::new(PROTOCOL_VERSION)?;
233        config.load_cert_chain_from_pem_file("examples/cert.crt")?;
234        config.load_priv_key_from_pem_file("examples/cert.key")?;
235        config.set_application_protos(&[b"proto1", b"proto2"])?;
236        config.set_initial_max_data(30);
237        config.set_initial_max_stream_data_bidi_local(15);
238        config.set_initial_max_stream_data_bidi_remote(15);
239        config.set_initial_max_streams_bidi(3);
240        config.set_initial_max_streams_uni(3);
241        config.set_ack_delay_exponent(8);
242
243        Ok(Pipe {
244            client: connect_with_buffer_factory(
245                Some("quic.tech"),
246                &client_scid,
247                client_addr,
248                server_addr,
249                client_config,
250            )?,
251            server: accept_with_buf_factory(
252                &server_scid,
253                None,
254                server_addr,
255                client_addr,
256                &mut config,
257            )?,
258        })
259    }
260
261    pub fn with_server_config_and_buf(
262        server_config: &mut Config,
263    ) -> Result<Pipe<F>> {
264        let mut client_scid = [0; 16];
265        rand::rand_bytes(&mut client_scid[..]);
266        let client_scid = ConnectionId::from_ref(&client_scid);
267        let client_addr = Pipe::client_addr();
268
269        let mut server_scid = [0; 16];
270        rand::rand_bytes(&mut server_scid[..]);
271        let server_scid = ConnectionId::from_ref(&server_scid);
272        let server_addr = Pipe::server_addr();
273
274        let mut config = Config::new(PROTOCOL_VERSION)?;
275        config.set_application_protos(&[b"proto1", b"proto2"])?;
276        config.set_initial_max_data(30);
277        config.set_initial_max_stream_data_bidi_local(15);
278        config.set_initial_max_stream_data_bidi_remote(15);
279        config.set_initial_max_streams_bidi(3);
280        config.set_initial_max_streams_uni(3);
281        config.set_ack_delay_exponent(8);
282
283        Ok(Pipe {
284            client: connect_with_buffer_factory(
285                Some("quic.tech"),
286                &client_scid,
287                client_addr,
288                server_addr,
289                &mut config,
290            )?,
291            server: accept_with_buf_factory(
292                &server_scid,
293                None,
294                server_addr,
295                client_addr,
296                server_config,
297            )?,
298        })
299    }
300
301    pub fn with_client_and_server_config_and_buf(
302        client_config: &mut Config, server_config: &mut Config,
303    ) -> Result<Pipe<F>> {
304        let mut client_scid = [0; 16];
305        rand::rand_bytes(&mut client_scid[..]);
306        let client_scid = ConnectionId::from_ref(&client_scid);
307        let client_addr = Pipe::client_addr();
308
309        let mut server_scid = [0; 16];
310        rand::rand_bytes(&mut server_scid[..]);
311        let server_scid = ConnectionId::from_ref(&server_scid);
312        let server_addr = Pipe::server_addr();
313
314        Ok(Pipe {
315            client: connect_with_buffer_factory(
316                Some("quic.tech"),
317                &client_scid,
318                client_addr,
319                server_addr,
320                client_config,
321            )?,
322            server: accept_with_buf_factory(
323                &server_scid,
324                None,
325                server_addr,
326                client_addr,
327                server_config,
328            )?,
329        })
330    }
331
332    pub fn handshake(&mut self) -> Result<()> {
333        while !self.client.is_established() || !self.server.is_established() {
334            let flight = emit_flight(&mut self.client)?;
335            process_flight(&mut self.server, flight)?;
336
337            let flight = emit_flight(&mut self.server)?;
338            process_flight(&mut self.client, flight)?;
339        }
340
341        Ok(())
342    }
343
344    pub fn advance(&mut self) -> Result<()> {
345        let mut client_done = false;
346        let mut server_done = false;
347
348        while !client_done || !server_done {
349            match emit_flight(&mut self.client) {
350                Ok(flight) => process_flight(&mut self.server, flight)?,
351
352                Err(Error::Done) => client_done = true,
353
354                Err(e) => return Err(e),
355            };
356
357            match emit_flight(&mut self.server) {
358                Ok(flight) => process_flight(&mut self.client, flight)?,
359
360                Err(Error::Done) => server_done = true,
361
362                Err(e) => return Err(e),
363            };
364        }
365
366        Ok(())
367    }
368
369    pub fn client_recv(&mut self, buf: &mut [u8]) -> Result<usize> {
370        let server_path = &self.server.paths.get_active().unwrap();
371        let info = RecvInfo {
372            to: server_path.peer_addr(),
373            from: server_path.local_addr(),
374        };
375
376        self.client.recv(buf, info)
377    }
378
379    pub fn server_recv(&mut self, buf: &mut [u8]) -> Result<usize> {
380        let client_path = &self.client.paths.get_active().unwrap();
381        let info = RecvInfo {
382            to: client_path.peer_addr(),
383            from: client_path.local_addr(),
384        };
385
386        self.server.recv(buf, info)
387    }
388
389    pub fn send_pkt_to_server(
390        &mut self, pkt_type: Type, frames: &[frame::Frame], buf: &mut [u8],
391    ) -> Result<usize> {
392        let written = encode_pkt(&mut self.client, pkt_type, frames, buf)?;
393        recv_send(&mut self.server, buf, written)
394    }
395
396    pub fn client_update_key(&mut self) -> Result<()> {
397        let crypto_ctx = &mut self.client.crypto_ctx[packet::Epoch::Application];
398
399        let open_next = crypto_ctx
400            .crypto_open
401            .as_ref()
402            .unwrap()
403            .derive_next_packet_key()
404            .unwrap();
405
406        let seal_next = crypto_ctx
407            .crypto_seal
408            .as_ref()
409            .unwrap()
410            .derive_next_packet_key()?;
411
412        let open_prev = crypto_ctx.crypto_open.replace(open_next);
413        crypto_ctx.crypto_seal.replace(seal_next);
414
415        crypto_ctx.key_update = Some(packet::KeyUpdate {
416            crypto_open: open_prev.unwrap(),
417            pn_on_update: self.client.next_pkt_num,
418            update_acked: true,
419            timer: Instant::now(),
420        });
421
422        self.client.key_phase = !self.client.key_phase;
423
424        Ok(())
425    }
426}
427
428pub fn recv_send<F: BufFactory>(
429    conn: &mut Connection<F>, buf: &mut [u8], len: usize,
430) -> Result<usize> {
431    let active_path = conn.paths.get_active()?;
432    let info = RecvInfo {
433        to: active_path.local_addr(),
434        from: active_path.peer_addr(),
435    };
436
437    conn.recv(&mut buf[..len], info)?;
438
439    let mut off = 0;
440
441    match conn.send(&mut buf[off..]) {
442        Ok((write, _)) => off += write,
443
444        Err(Error::Done) => (),
445
446        Err(e) => return Err(e),
447    }
448
449    Ok(off)
450}
451
452pub fn process_flight<F: BufFactory>(
453    conn: &mut Connection<F>, flight: Vec<(Vec<u8>, SendInfo)>,
454) -> Result<()> {
455    for (mut pkt, si) in flight {
456        let info = RecvInfo {
457            to: si.to,
458            from: si.from,
459        };
460
461        conn.recv(&mut pkt, info)?;
462    }
463
464    Ok(())
465}
466
467pub fn emit_flight_with_max_buffer<F: BufFactory>(
468    conn: &mut Connection<F>, out_size: usize, from: Option<SocketAddr>,
469    to: Option<SocketAddr>,
470) -> Result<Vec<(Vec<u8>, SendInfo)>> {
471    let mut flight = Vec::new();
472
473    loop {
474        let mut out = vec![0u8; out_size];
475
476        let info = match conn.send_on_path(&mut out, from, to) {
477            Ok((written, info)) => {
478                out.truncate(written);
479                info
480            },
481
482            Err(Error::Done) => break,
483
484            Err(e) => return Err(e),
485        };
486
487        flight.push((out, info));
488    }
489
490    if flight.is_empty() {
491        return Err(Error::Done);
492    }
493
494    Ok(flight)
495}
496
497pub fn emit_flight_on_path<F: BufFactory>(
498    conn: &mut Connection<F>, from: Option<SocketAddr>, to: Option<SocketAddr>,
499) -> Result<Vec<(Vec<u8>, SendInfo)>> {
500    emit_flight_with_max_buffer(conn, 65535, from, to)
501}
502
503pub fn emit_flight<F: BufFactory>(
504    conn: &mut Connection<F>,
505) -> Result<Vec<(Vec<u8>, SendInfo)>> {
506    emit_flight_on_path(conn, None, None)
507}
508
509pub fn encode_pkt<F: BufFactory>(
510    conn: &mut Connection<F>, pkt_type: Type, frames: &[frame::Frame],
511    buf: &mut [u8],
512) -> Result<usize> {
513    let mut b = octets::OctetsMut::with_slice(buf);
514
515    let epoch = pkt_type.to_epoch()?;
516
517    let crypto_ctx = &mut conn.crypto_ctx[epoch];
518
519    let pn = conn.next_pkt_num;
520    let pn_len = 4;
521
522    let send_path = conn.paths.get_active()?;
523    let active_dcid_seq = send_path
524        .active_dcid_seq
525        .as_ref()
526        .ok_or(Error::InvalidState)?;
527    let active_scid_seq = send_path
528        .active_scid_seq
529        .as_ref()
530        .ok_or(Error::InvalidState)?;
531
532    let hdr = Header {
533        ty: pkt_type,
534        version: conn.version,
535        dcid: ConnectionId::from_ref(
536            conn.ids.get_dcid(*active_dcid_seq)?.cid.as_ref(),
537        ),
538        scid: ConnectionId::from_ref(
539            conn.ids.get_scid(*active_scid_seq)?.cid.as_ref(),
540        ),
541        pkt_num: pn,
542        pkt_num_len: pn_len,
543        token: conn.token.clone(),
544        versions: None,
545        key_phase: conn.key_phase,
546    };
547
548    hdr.to_bytes(&mut b)?;
549
550    let payload_len = frames.iter().fold(0, |acc, x| acc + x.wire_len());
551
552    if pkt_type != Type::Short {
553        let len = pn_len + payload_len + crypto_ctx.crypto_overhead().unwrap();
554        b.put_varint(len as u64)?;
555    }
556
557    // Always encode packet number in 4 bytes, to allow encoding packets
558    // with empty payloads.
559    b.put_u32(pn as u32)?;
560
561    let payload_offset = b.off();
562
563    for frame in frames {
564        frame.to_bytes(&mut b)?;
565    }
566
567    let aead = match crypto_ctx.crypto_seal {
568        Some(ref mut v) => v,
569        None => return Err(Error::InvalidState),
570    };
571
572    let written = packet::encrypt_pkt(
573        &mut b,
574        pn,
575        pn_len,
576        payload_len,
577        payload_offset,
578        None,
579        aead,
580    )?;
581
582    conn.next_pkt_num += 1;
583
584    Ok(written)
585}
586
587pub fn decode_pkt<F: BufFactory>(
588    conn: &mut Connection<F>, buf: &mut [u8],
589) -> Result<Vec<frame::Frame>> {
590    let mut b = octets::OctetsMut::with_slice(buf);
591
592    let mut hdr = Header::from_bytes(&mut b, conn.source_id().len()).unwrap();
593
594    let epoch = hdr.ty.to_epoch()?;
595
596    let aead = conn.crypto_ctx[epoch].crypto_open.as_ref().unwrap();
597
598    let payload_len = b.cap();
599
600    packet::decrypt_hdr(&mut b, &mut hdr, aead).unwrap();
601
602    let pn = packet::decode_pkt_num(
603        conn.pkt_num_spaces[epoch].largest_rx_pkt_num,
604        hdr.pkt_num,
605        hdr.pkt_num_len,
606    );
607
608    let mut payload =
609        packet::decrypt_pkt(&mut b, pn, hdr.pkt_num_len, payload_len, aead)
610            .unwrap();
611
612    let mut frames = Vec::new();
613
614    while payload.cap() > 0 {
615        let frame = frame::Frame::from_bytes(&mut payload, hdr.ty)?;
616        frames.push(frame);
617    }
618
619    Ok(frames)
620}
621
622pub fn create_cid_and_reset_token(
623    cid_len: usize,
624) -> (ConnectionId<'static>, u128) {
625    let mut cid = vec![0; cid_len];
626    rand::rand_bytes(&mut cid[..]);
627    let cid = ConnectionId::from(cid);
628
629    let mut reset_token = [0; 16];
630    rand::rand_bytes(&mut reset_token);
631    let reset_token = u128::from_be_bytes(reset_token);
632
633    (cid, reset_token)
634}
635
636pub fn helper_packet_sent(pkt_num: u64, now: Instant, size: usize) -> Sent {
637    Sent {
638        pkt_num,
639        frames: smallvec![],
640        time_sent: now,
641        time_acked: None,
642        time_lost: None,
643        size,
644        ack_eliciting: true,
645        in_flight: true,
646        delivered: 0,
647        delivered_time: now,
648        first_sent_time: now,
649        is_app_limited: false,
650        tx_in_flight: 0,
651        lost: 0,
652        has_data: true,
653        is_pmtud_probe: false,
654    }
655}
656
657// Helper function for testing either stream receive or discard.
658pub fn stream_recv_discard<F: BufFactory>(
659    conn: &mut Connection<F>, discard: bool, stream_id: u64,
660) -> Result<(usize, bool)> {
661    let mut buf = [0; 65535];
662    if discard {
663        conn.stream_discard(stream_id, 65535)
664    } else {
665        conn.stream_recv(stream_id, &mut buf)
666    }
667}
668
669/// Triggers ACK-based loss detection for packets sent by `sender` before this
670/// call.
671///
672/// This works by sending multiple PING packets from the sender and having the
673/// receiver ACK them. Since loss detection uses a packet threshold, this
674/// function sends as many packets as needed to ensure any previously
675/// unacknowledged packets from the sender are detected as lost.
676#[cfg(test)]
677pub fn trigger_ack_based_loss<F: BufFactory>(
678    sender: &mut Connection<F>, receiver: &mut Connection<F>,
679) {
680    let mut buf = [0; 65535];
681
682    // Use the active path's packet loss threshold.
683    let pkt_thresh = sender
684        .paths
685        .get_active()
686        .unwrap()
687        .recovery
688        .pkt_thresh()
689        .unwrap();
690
691    for _ in 0..pkt_thresh {
692        sender.send_ack_eliciting().unwrap();
693        let (len, _) = sender.send(&mut buf).unwrap();
694
695        let info = RecvInfo {
696            to: receiver.paths.get_active().unwrap().local_addr(),
697            from: receiver.paths.get_active().unwrap().peer_addr(),
698        };
699        receiver.recv(&mut buf[..len], info).unwrap();
700    }
701
702    // Receiver sends ACK for the new packets.
703    let (ack_len, _) = receiver.send(&mut buf).unwrap();
704
705    // Sender receives ACK, triggering loss detection.
706    let info = RecvInfo {
707        to: sender.paths.get_active().unwrap().local_addr(),
708        from: sender.paths.get_active().unwrap().peer_addr(),
709    };
710    sender.recv(&mut buf[..ack_len], info).unwrap();
711}