1use crate::client::ClientConfig;
4use crate::quic::CongestionControl;
5use crate::quic::Resolved;
6use crate::quic::ServerId;
7use crate::server::ServerConfig;
8use crate::tls::{FingerprintVerifier, ServeCerts};
9use std::net;
10use std::sync::Arc;
11use std::time::Duration;
12use url::Url;
13
14pub use web_transport_quinn;
15
16fn apply_qlog(transport: &mut quinn::TransportConfig, quic: &Resolved, role: &str) -> Result<()> {
22 let Some(dir) = quic.qlog_dir() else {
25 return Ok(());
26 };
27
28 #[cfg(feature = "qlog")]
29 {
30 let path = dir.join(format!("moq-{role}-{}.sqlog", std::process::id()));
32 let file = std::fs::File::create(&path).map_err(Error::CreateQlog)?;
33
34 let mut config = quinn::QlogConfig::default();
39 config.writer(Box::new(file)).title(Some(format!("moq-native {role}")));
40
41 transport.qlog_stream(config.into_stream());
42 tracing::info!(path = %path.display(), "writing qlog");
43 }
44
45 #[cfg(not(feature = "qlog"))]
46 let _ = (transport, dir, role);
47
48 Ok(())
49}
50
51fn apply_transport(transport: &mut quinn::TransportConfig, quic: &Resolved) {
53 transport.max_idle_timeout(Some(quic.idle_timeout.try_into().expect("idle timeout out of range")));
54 transport.keep_alive_interval(quic.keep_alive);
55
56 if !quic.mtu_discovery {
58 transport.mtu_discovery_config(None);
59 }
60
61 let max_streams = quinn::VarInt::from_u64(quic.max_streams).unwrap_or(quinn::VarInt::MAX);
62 transport.max_concurrent_bidi_streams(max_streams);
63 transport.max_concurrent_uni_streams(max_streams);
64
65 if let Some(gso) = quic.gso {
67 transport.enable_segmentation_offload(gso);
68 }
69
70 transport.congestion_controller_factory(congestion_factory(congestion_control(quic)));
71}
72
73fn congestion_control(quic: &Resolved) -> CongestionControl {
78 quic.congestion_control.unwrap_or(CongestionControl::Delay)
79}
80
81fn congestion_factory(family: CongestionControl) -> Arc<dyn quinn::congestion::ControllerFactory + Send + Sync> {
83 match family {
84 CongestionControl::Loss => Arc::new(quinn::congestion::CubicConfig::default()),
85 CongestionControl::Delay => Arc::new(quinn::congestion::BbrConfig::default()),
86 }
87}
88
89#[derive(Debug, thiserror::Error)]
91#[non_exhaustive]
92pub enum Error {
93 #[error("failed to bind UDP socket")]
95 BindSocket(#[source] std::io::Error),
96
97 #[error("failed to create QUIC endpoint")]
99 CreateEndpoint(#[source] std::io::Error),
100
101 #[error("failed to create qlog file")]
103 CreateQlog(#[source] std::io::Error),
104
105 #[error("no async runtime")]
107 NoRuntime,
108
109 #[error("failed to get local address")]
111 LocalAddr(#[source] std::io::Error),
112
113 #[error("failed to resolve bind address")]
115 ResolveBind(#[source] std::io::Error),
116
117 #[error("invalid DNS name")]
119 InvalidDnsName,
120
121 #[error("failed DNS lookup")]
123 DnsLookup(#[source] std::io::Error),
124
125 #[error("no DNS entries")]
127 NoDnsEntries,
128
129 #[error("failed to fetch fingerprint")]
131 FetchFingerprint(#[source] reqwest::Error),
132
133 #[error("fingerprint request failed")]
135 FingerprintStatus(#[source] reqwest::Error),
136
137 #[error("failed to read fingerprint")]
139 ReadFingerprint(#[source] reqwest::Error),
140
141 #[error("invalid fingerprint")]
143 InvalidFingerprint(#[from] hex::FromHexError),
144
145 #[error("url scheme must be 'https', 'moqt', or 'moql'")]
147 InvalidScheme,
148
149 #[error("unsupported URL scheme: {0}")]
151 UnsupportedScheme(String),
152
153 #[error("missing handshake data")]
155 MissingHandshake,
156
157 #[error("missing ALPN")]
159 MissingAlpn,
160
161 #[error("failed to decode ALPN")]
163 DecodeAlpn(#[from] std::string::FromUtf8Error),
164
165 #[error("unsupported ALPN: {0}")]
167 UnsupportedAlpn(String),
168
169 #[error("missing server name for raw QUIC connection")]
171 MissingServerName,
172
173 #[error("failed to construct URL from server name")]
175 BuildUrl(#[source] url::ParseError),
176
177 #[error("quic_lb_nonce must be at least 4")]
179 QuicLbNonceTooSmall,
180
181 #[error("connection ID length ({0}) exceeds maximum of 20")]
183 QuicLbCidTooLong(usize),
184
185 #[error("failed to build client certificate verifier")]
187 ClientVerifier(#[source] rustls::server::VerifierBuilderError),
188
189 #[error(transparent)]
191 NoInitialCipherSuite(#[from] quinn::crypto::rustls::NoInitialCipherSuite),
192
193 #[error(transparent)]
195 Connect(#[from] quinn::ConnectError),
196
197 #[error(transparent)]
199 Connection(#[from] quinn::ConnectionError),
200
201 #[error(transparent)]
203 Client(#[from] web_transport_quinn::ClientError),
204
205 #[error(transparent)]
207 ConnectRejected(#[from] crate::ConnectError),
208
209 #[error(transparent)]
211 Server(#[from] web_transport_quinn::ServerError),
212
213 #[error("failed to establish QUIC connection")]
215 Establish(#[source] quinn::ConnectionError),
216
217 #[error("failed to receive WebTransport request")]
219 RecvRequest(#[source] web_transport_quinn::ServerError),
220
221 #[error(transparent)]
223 Tls(#[from] crate::tls::Error),
224
225 #[error("all {} connection attempts failed: {}", .0.len(), crate::failover::describe(.0))]
231 Failover(Vec<crate::failover::Failure<Error>>),
232}
233
234impl crate::failover::Aggregate for Error {
235 fn aggregate(failures: Vec<crate::failover::Failure<Self>>) -> Self {
236 Self::Failover(failures)
237 }
238
239 fn resolve(error: Option<std::io::Error>) -> Self {
240 match error {
241 Some(error) => Self::DnsLookup(error),
242 None => Self::NoDnsEntries,
243 }
244 }
245}
246
247type Result<T> = std::result::Result<T, Error>;
248
249#[derive(Clone)]
252pub(crate) struct QuinnClient {
253 pub quic: quinn::Endpoint,
254 pub transport: Arc<quinn::TransportConfig>,
255 pub http_bootstrap: bool,
257 pub host_name: Option<String>,
259 pub failover_delay: Duration,
261 pub resolution_delay: Duration,
264 dual_stack: bool,
268}
269
270impl QuinnClient {
271 pub fn new(config: &ClientConfig) -> Result<Self> {
272 let socket = crate::bind::udp(config.bind).map_err(Error::BindSocket)?;
273 let dual_stack = crate::bind::udp_is_dual_stack(&socket);
274
275 let quic = config.quic.resolve();
276 let mut transport = quinn::TransportConfig::default();
277 apply_transport(&mut transport, &quic);
278 apply_qlog(&mut transport, &quic, "client")?;
279 let transport = Arc::new(transport);
280
281 let runtime = quinn::default_runtime().ok_or(Error::NoRuntime)?;
283 let endpoint_config = quinn::EndpointConfig::default();
284
285 let quic = quinn::Endpoint::new(endpoint_config, None, socket, runtime).map_err(Error::CreateEndpoint)?;
287
288 Ok(Self {
289 quic,
290 transport,
291 http_bootstrap: config.tls.allows_http_bootstrap(),
292 host_name: config.tls.host_name.clone(),
293 failover_delay: config.resolved_failover_delay(),
294 resolution_delay: config.resolved_resolution_delay(),
295 dual_stack,
296 })
297 }
298
299 pub async fn connect(
300 &self,
301 tls: &rustls::ClientConfig,
302 url: Url,
303 versions: &moq_net::Versions,
304 ) -> Result<web_transport_quinn::Session> {
305 let mut url = url;
306 let mut config = tls.clone();
307
308 let target = url.host().ok_or(Error::InvalidDnsName)?;
309 let host = target.to_string();
310 let port = url.port().unwrap_or(443);
311
312 let local = self.quic.local_addr().map_err(Error::LocalAddr)?;
316 let candidates =
317 crate::resolve::Candidates::resolve(target, port, self.resolution_delay).with_local(local, self.dual_stack);
318
319 if url.scheme() == "http" {
320 if self.http_bootstrap {
324 let mut fingerprint = url.clone();
326 fingerprint.set_path("/certificate.sha256");
327 fingerprint.set_query(None);
328 fingerprint.set_fragment(None);
329
330 tracing::warn!(url = %fingerprint, "performing insecure HTTP request for certificate");
331
332 let resp = reqwest::get(fingerprint.as_str())
333 .await
334 .map_err(Error::FetchFingerprint)?
335 .error_for_status()
336 .map_err(Error::FingerprintStatus)?;
337
338 let fingerprint = resp.text().await.map_err(Error::ReadFingerprint)?;
339 let fingerprint = hex::decode(fingerprint.trim())?;
340
341 let verifier = FingerprintVerifier::new(config.crypto_provider().clone(), vec![fingerprint]);
342 config.dangerous().set_certificate_verifier(Arc::new(verifier));
343 } else {
344 tracing::warn!(
345 "ignoring insecure http:// fingerprint bootstrap; using the configured TLS verification"
346 );
347 }
348
349 url.set_scheme("https").expect("failed to set scheme");
350 }
351
352 let alpns: Vec<Vec<u8>> = match url.scheme() {
353 "https" => vec![web_transport_quinn::ALPN.as_bytes().to_vec()],
354 "moqt" | "moql" => versions.alpns().iter().map(|alpn| alpn.as_bytes().to_vec()).collect(),
355 _ => return Err(Error::InvalidScheme),
356 };
357
358 config.alpn_protocols = alpns;
359 config.key_log = Arc::new(rustls::KeyLogFile::new());
360
361 let config: quinn::crypto::rustls::QuicClientConfig = config.try_into()?;
362 let mut config = quinn::ClientConfig::new(Arc::new(config));
363 config.transport_config(self.transport.clone());
364
365 tracing::debug!(%url, "connecting");
366
367 let host_name = self.host_name.clone().unwrap_or(host);
369
370 let connection = crate::failover::race(candidates, self.failover_delay, |addr| {
374 let endpoint = self.quic.clone();
375 let config = config.clone();
376 let host_name = host_name.clone();
377 async move { Ok::<_, Error>(endpoint.connect_with(config, addr, &host_name)?.await?) }
378 })
379 .await?;
380 tracing::Span::current().record("id", connection.stable_id());
381
382 let mut request = web_transport_quinn::proto::ConnectRequest::new(url.clone());
383 for alpn in versions.alpns() {
384 request = request.with_protocol(alpn.to_string());
385 }
386
387 let session = match url.scheme() {
388 "https" => web_transport_quinn::Session::connect(connection, request)
389 .await
390 .map_err(map_client_error)?,
391 "moqt" | "moql" => {
392 let handshake = connection
393 .handshake_data()
394 .ok_or(Error::MissingHandshake)?
395 .downcast::<quinn::crypto::rustls::HandshakeData>()
396 .unwrap();
397
398 let alpn = handshake.protocol.ok_or(Error::MissingAlpn)?;
399 let alpn = String::from_utf8(alpn)?;
400
401 let response = web_transport_quinn::proto::ConnectResponse::OK.with_protocol(alpn);
402 web_transport_quinn::Session::raw(connection, request, response)
403 }
404 _ => return Err(Error::UnsupportedScheme(url.scheme().to_string())),
405 };
406
407 Ok(session)
408 }
409}
410
411impl Error {
412 pub(crate) fn connect_error(&self) -> Option<crate::ConnectError> {
413 match self {
414 Self::ConnectRejected(err) => Some(*err),
415 Self::Client(err) => classify_client_error(err),
416 Self::Failover(failures) => failures.iter().find_map(|failure| failure.error.connect_error()),
417 _ => None,
418 }
419 }
420
421 pub(crate) fn status(&self) -> Option<u16> {
426 match self {
427 Self::FetchFingerprint(err) | Self::FingerprintStatus(err) | Self::ReadFingerprint(err) => {
428 err.status().map(|status| status.as_u16())
429 }
430 Self::Client(err) => client_status(err),
431 Self::Failover(failures) => {
435 let mut settled = None;
436 for failure in failures {
437 match failure.error.status() {
438 Some(status) if !crate::error::status_retryable(status) => settled = Some(status),
439 _ => return None,
440 }
441 }
442 settled
443 }
444 _ => None,
445 }
446 }
447}
448
449fn map_client_error(err: web_transport_quinn::ClientError) -> Error {
450 if let Some(err) = classify_client_error(&err) {
451 return err.into();
452 }
453
454 err.into()
455}
456
457fn classify_client_error(err: &web_transport_quinn::ClientError) -> Option<crate::ConnectError> {
458 client_status(err).and_then(crate::ConnectError::from_status_u16)
459}
460
461fn client_status(err: &web_transport_quinn::ClientError) -> Option<u16> {
469 match err {
470 web_transport_quinn::ClientError::HttpError(err) => connect_status(err),
471 _ => None,
472 }
473}
474
475fn connect_status(err: &web_transport_quinn::ConnectError) -> Option<u16> {
476 match err {
477 web_transport_quinn::ConnectError::ErrorStatus(status) => Some(status.as_u16()),
478 web_transport_quinn::ConnectError::ProtoError(err) => proto_status(err),
479 _ => None,
480 }
481}
482
483fn proto_status(err: &web_transport_quinn::proto::ConnectError) -> Option<u16> {
484 match err {
485 web_transport_quinn::proto::ConnectError::ErrorStatus(status)
486 | web_transport_quinn::proto::ConnectError::WrongStatus(Some(status)) => Some(status.as_u16()),
487 _ => None,
488 }
489}
490
491pub(crate) struct QuinnServer {
494 pub quic: quinn::Endpoint,
495 pub certs: Arc<ServeCerts>,
496}
497
498impl QuinnServer {
499 pub fn new(config: ServerConfig) -> Result<Self> {
500 let quic = config.quic.resolve();
501 let mut transport = quinn::TransportConfig::default();
502 apply_transport(&mut transport, &quic);
503 apply_qlog(&mut transport, &quic, "server")?;
504 let transport = Arc::new(transport);
505
506 let provider = crate::crypto::provider();
507
508 let certs = ServeCerts::new(provider.clone());
509 certs.load_certs(&config.tls)?;
510 let certs = Arc::new(certs);
511
512 let tls_builder = rustls::ServerConfig::builder_with_provider(provider.clone())
513 .with_protocol_versions(&[&rustls::version::TLS13])
514 .map_err(crate::tls::Error::from)?;
515
516 let mut tls = if config.tls.root.is_empty() {
517 tls_builder.with_no_client_auth().with_cert_resolver(certs.clone())
518 } else {
519 let roots = config.tls.load_roots()?;
520 let verifier = rustls::server::WebPkiClientVerifier::builder_with_provider(Arc::new(roots), provider)
521 .allow_unauthenticated()
522 .build()
523 .map_err(Error::ClientVerifier)?;
524 tls_builder
525 .with_client_cert_verifier(verifier)
526 .with_cert_resolver(certs.clone())
527 };
528
529 let mut alpns: Vec<Vec<u8>> = config
531 .versions()
532 .alpns()
533 .iter()
534 .map(|alpn| alpn.as_bytes().to_vec())
535 .collect();
536 alpns.push(web_transport_quinn::ALPN.as_bytes().to_vec());
537
538 tls.alpn_protocols = alpns;
539 tls.key_log = Arc::new(rustls::KeyLogFile::new());
540
541 let tls: quinn::crypto::rustls::QuicServerConfig = tls.try_into()?;
542 let mut tls = quinn::ServerConfig::with_crypto(Arc::new(tls));
543 tls.transport_config(transport);
544
545 if let Some(addr) = config.quic.preferred_v4 {
548 tls.preferred_address_v4(Some(addr));
549 }
550 if let Some(addr) = config.quic.preferred_v6 {
551 tls.preferred_address_v6(Some(addr));
552 }
553
554 let runtime = quinn::default_runtime().ok_or(Error::NoRuntime)?;
556
557 let listen =
558 crate::util::resolve(config.bind.as_deref(), crate::server::DEFAULT_BIND).map_err(Error::ResolveBind)?;
559
560 let mut endpoint_config = quinn::EndpointConfig::default();
562 if let Some(server_id) = config.quic.quic_lb_id {
563 let nonce_len = config.quic.quic_lb_nonce.unwrap_or(8);
564 if nonce_len < 4 {
565 return Err(Error::QuicLbNonceTooSmall);
566 }
567
568 let cid_len = 1 + server_id.len() + nonce_len;
569 if cid_len > 20 {
570 return Err(Error::QuicLbCidTooLong(cid_len));
571 }
572
573 tracing::info!(
574 ?server_id,
575 nonce_len,
576 "using QUIC-LB compatible connection ID generation"
577 );
578 endpoint_config.cid_generator(move || Box::new(ServerIdGenerator::new(server_id.clone(), nonce_len)));
579 }
580
581 let socket = crate::bind::udp(listen).map_err(Error::BindSocket)?;
582
583 let quic = quinn::Endpoint::new(endpoint_config, Some(tls), socket, runtime).map_err(Error::CreateEndpoint)?;
585
586 tokio::spawn(crate::tls::reload_certs(certs.clone(), config.tls.clone()));
589
590 Ok(Self { quic, certs })
591 }
592
593 pub fn accept(&self) -> impl std::future::Future<Output = Option<quinn::Incoming>> + '_ {
594 self.quic.accept()
595 }
596
597 pub fn certificates(&self) -> crate::tls::Certificates {
598 crate::tls::Certificates::new(self.certs.info.clone())
599 }
600
601 pub fn local_addr(&self) -> Result<net::SocketAddr> {
602 self.quic.local_addr().map_err(Error::LocalAddr)
603 }
604
605 pub fn close(&self) {
606 self.quic.close(quinn::VarInt::from_u32(0), b"server shutdown");
607 }
608}
609
610pub(crate) async fn accept(
617 conn: quinn::Incoming,
618 alpns: Vec<&'static str>,
619) -> Result<(
620 web_transport_quinn::Session,
621 Option<Url>,
622 Option<crate::tls::PeerIdentity>,
623)> {
624 let mut conn = conn.accept()?;
625
626 let handshake = conn
627 .handshake_data()
628 .await?
629 .downcast::<quinn::crypto::rustls::HandshakeData>()
630 .unwrap();
631
632 let alpn = handshake.protocol.ok_or(Error::MissingAlpn)?;
633 let alpn = String::from_utf8(alpn)?;
634 let host = handshake.server_name.unwrap_or_default();
635
636 tracing::debug!(%host, ip = %conn.remote_address(), %alpn, "accepting");
637
638 let conn = conn.await.map_err(Error::Establish)?;
640
641 let span = tracing::Span::current();
642 span.record("id", conn.stable_id()); tracing::debug!(%host, ip = %conn.remote_address(), %alpn, "accepted");
644
645 match alpn.as_str() {
646 web_transport_quinn::ALPN => {
647 let request = web_transport_quinn::Request::accept(conn)
650 .await
651 .map_err(Error::RecvRequest)?;
652 let url = Some(request.url.clone());
653 let identity = crate::tls::PeerIdentity::from_any(request.conn().peer_identity());
654
655 let mut response = web_transport_quinn::proto::ConnectResponse::OK;
656 if let Some(protocol) = request.protocols.iter().find(|p| alpns.contains(&p.as_str())) {
662 response = response.with_protocol(protocol);
663 }
664 let session = request.respond(response).await.map_err(Error::Server)?;
665 Ok((session, url, identity))
666 }
667 alpn if alpns.contains(&alpn) => {
672 let host_str = if host.contains(':') {
678 format!("[{}]", host)
679 } else {
680 host.clone()
681 };
682 let url = format!("moqt://{}", host_str).parse::<Url>().map_err(Error::BuildUrl)?;
683 let request = web_transport_quinn::proto::ConnectRequest::new(url);
684 let response = web_transport_quinn::proto::ConnectResponse::OK.with_protocol(alpn);
685 let identity = crate::tls::PeerIdentity::from_any(conn.peer_identity());
686 let session = web_transport_quinn::Session::raw(conn, request, response);
688 Ok((session, None, identity))
689 }
690 _ => Err(Error::UnsupportedAlpn(alpn)),
691 }
692}
693
694struct ServerIdGenerator {
697 server_id: ServerId,
698 nonce_len: usize,
699}
700
701impl ServerIdGenerator {
702 fn new(server_id: ServerId, nonce_len: usize) -> Self {
703 Self { server_id, nonce_len }
704 }
705}
706
707impl quinn::ConnectionIdGenerator for ServerIdGenerator {
708 fn generate_cid(&mut self) -> quinn::ConnectionId {
709 use rand::RngExt;
710 let cid_len = self.cid_len();
711 let mut cid = Vec::with_capacity(cid_len);
712 cid.push((cid_len - 1) as u8);
714 cid.extend(self.server_id.0.iter());
715 cid.extend(rand::rng().random_iter::<u8>().take(self.nonce_len));
716 quinn::ConnectionId::new(cid.as_slice())
717 }
718
719 fn cid_len(&self) -> usize {
720 1 + self.server_id.len() + self.nonce_len
721 }
722
723 fn cid_lifetime(&self) -> Option<Duration> {
724 None
725 }
726}
727
728#[cfg(test)]
729mod tests {
730 use super::*;
731
732 fn connect_rejected(status: u16) -> Error {
733 Error::Client(web_transport_quinn::ClientError::HttpError(
734 web_transport_quinn::ConnectError::ErrorStatus(
735 web_transport_quinn::http::StatusCode::from_u16(status).unwrap(),
736 ),
737 ))
738 }
739
740 #[test]
744 fn a_rejected_connect_reports_its_status() {
745 for status in [400, 404, 405, 410, 501] {
746 assert_eq!(connect_rejected(status).status(), Some(status));
747 assert!(
748 !crate::error::status_retryable(status),
749 "{status} should stop the reconnect loop"
750 );
751 }
752
753 for status in [408, 429, 502, 503, 504] {
754 assert_eq!(connect_rejected(status).status(), Some(status));
755 assert!(crate::error::status_retryable(status), "{status} should be retried");
756 }
757
758 assert_eq!(
760 connect_rejected(401).connect_error(),
761 Some(crate::ConnectError::Unauthorized)
762 );
763 assert_eq!(
764 connect_rejected(403).connect_error(),
765 Some(crate::ConnectError::Forbidden)
766 );
767 }
768
769 #[test]
772 fn congestion_factory_maps_each_family() {
773 let now = std::time::Instant::now();
774 let mtu = 1200;
775
776 let loss = congestion_factory(CongestionControl::Loss).build(now, mtu);
777 assert!(loss.into_any().downcast::<quinn::congestion::Cubic>().is_ok());
778
779 let delay = congestion_factory(CongestionControl::Delay).build(now, mtu);
780 assert!(delay.into_any().downcast::<quinn::congestion::Bbr>().is_ok());
781 }
782
783 #[test]
785 fn congestion_control_defaults_to_delay() {
786 let mut quic = crate::quic::Client::default();
787 assert_eq!(congestion_control(&quic.resolve()), CongestionControl::Delay);
788
789 quic.congestion_control = Some(CongestionControl::Loss);
791 assert_eq!(congestion_control(&quic.resolve()), CongestionControl::Loss);
792 }
793
794 #[tokio::test]
797 async fn delay_reaches_the_live_connection() {
798 let server_config = ServerConfig {
799 bind: Some("127.0.0.1:0".to_string()),
800 tls: crate::tls::Server {
801 generate: vec!["localhost".into()],
802 ..Default::default()
803 },
804 quic: crate::quic::Server {
805 congestion_control: Some(CongestionControl::Delay),
806 ..Default::default()
807 },
808 ..Default::default()
809 };
810
811 let server = QuinnServer::new(server_config).expect("server init");
812 let addr = server.local_addr().expect("local addr");
813
814 let accepted = tokio::spawn(async move {
815 let incoming = server.accept().await.expect("no incoming connection");
816 let conn = incoming.accept().expect("accept").await.expect("handshake");
817 conn.congestion_state()
818 .into_any()
819 .downcast::<quinn::congestion::Bbr>()
820 .is_ok()
821 });
822
823 let mut tls_config = crate::tls::Client::default();
825 tls_config.disable_verify = Some(true);
826
827 let client_config = ClientConfig {
828 bind: "127.0.0.1:0".parse().unwrap(),
829 tls: tls_config,
830 quic: crate::quic::Client {
831 congestion_control: Some(CongestionControl::Delay),
832 ..Default::default()
833 },
834 ..Default::default()
835 };
836
837 let tls = client_config.tls.build().expect("tls config");
838 let client = QuinnClient::new(&client_config).expect("client init");
839 let url: Url = format!("moqt://127.0.0.1:{}", addr.port()).parse().unwrap();
841
842 tokio::time::timeout(Duration::from_secs(5), async move {
845 let session = client
846 .connect(&tls, url, &moq_net::Versions::default())
847 .await
848 .expect("connect failed");
849
850 assert!(
852 session
853 .congestion_state()
854 .into_any()
855 .downcast::<quinn::congestion::Bbr>()
856 .is_ok(),
857 "client connection is not running BBR"
858 );
859 assert!(
860 accepted.await.expect("server task panicked"),
861 "server connection is not running BBR"
862 );
863 })
864 .await
865 .expect("test timed out");
866 }
867}