Skip to content

Commit b675e2f

Browse files
committed
feat: add event and function to wait for resumption tickets
1 parent 955c1ac commit b675e2f

File tree

9 files changed

+211
-9
lines changed

9 files changed

+211
-9
lines changed

Cargo.lock

Lines changed: 9 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ rand = "0.9"
3838
rcgen = "0.14"
3939
ring = "0.17"
4040
rustc-hash = "2"
41-
rustls = { version = "0.23.5", default-features = false, features = ["std"] }
41+
rustls = { version = "0.23.31", default-features = false, features = ["std"] }
4242
rustls-pemfile = "2"
4343
rustls-platform-verifier = "0.6"
4444
rustls-pki-types = "1.7"

perf/src/noprotection.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,10 @@ impl crypto::Session for NoProtectionSession {
127127
) -> Result<(), crypto::ExportKeyingMaterialError> {
128128
self.inner.export_keying_material(output, label, context)
129129
}
130+
131+
fn resumption_tickets_received(&self) -> Option<u32> {
132+
self.inner.resumption_tickets_received()
133+
}
130134
}
131135

132136
impl crypto::ClientConfig for NoProtectionClientConfig {

quinn-proto/src/connection/mod.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,8 @@ pub struct Connection {
234234
stats: ConnectionStats,
235235
/// QUIC version used for the connection.
236236
version: u32,
237+
/// True if we emitted the event for received resumption tickets
238+
resumption_tickets_received: bool,
237239
}
238240

239241
impl Connection {
@@ -350,6 +352,7 @@ impl Connection {
350352
rng,
351353
stats: ConnectionStats::default(),
352354
version,
355+
resumption_tickets_received: false,
353356
};
354357
if path_validated {
355358
this.on_path_validated();
@@ -2103,6 +2106,12 @@ impl Connection {
21032106
}
21042107
}
21052108

2109+
if !self.resumption_tickets_received && self.crypto.resumption_tickets_received() > Some(0)
2110+
{
2111+
self.resumption_tickets_received = true;
2112+
self.events.push_back(Event::ResumptionEnabled)
2113+
}
2114+
21062115
Ok(())
21072116
}
21082117

@@ -3989,6 +3998,13 @@ pub enum Event {
39893998
DatagramReceived,
39903999
/// One or more application datagrams have been sent after blocking
39914000
DatagramsUnblocked,
4001+
/// Resumption of the cryptographic session is now possible.
4002+
///
4003+
/// When using the rustls TLS session provider, this event is emitted when one or more
4004+
/// TLS session resumption tickets have been received.
4005+
///
4006+
/// It is only emitted on the client, and is emitted at most once per connection.
4007+
ResumptionEnabled,
39924008
}
39934009

39944010
fn instant_saturating_sub(x: Instant, y: Instant) -> Duration {

quinn-proto/src/crypto.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,11 @@ pub trait Session: Send + Sync + 'static {
9191
label: &[u8],
9292
context: &[u8],
9393
) -> Result<(), ExportKeyingMaterialError>;
94+
95+
/// Returns the number of TLS1.3 session resumption tickets that were received
96+
///
97+
/// Returns `None` on the server side.
98+
fn resumption_tickets_received(&self) -> Option<u32>;
9499
}
95100

96101
/// A pair of keys for bidirectional communication

quinn-proto/src/crypto/rustls.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,13 @@ impl crypto::Session for TlsSession {
201201
.map_err(|_| ExportKeyingMaterialError)?;
202202
Ok(())
203203
}
204+
205+
fn resumption_tickets_received(&self) -> Option<u32> {
206+
match &self.inner {
207+
Connection::Client(conn) => Some(conn.tls13_tickets_received()),
208+
Connection::Server(_) => None,
209+
}
210+
}
204211
}
205212

206213
const RETRY_INTEGRITY_KEY_DRAFT: [u8; 16] = [

quinn-proto/src/tests/mod.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,10 @@ fn lifecycle() {
128128
let _guard = subscribe();
129129
let mut pair = Pair::default();
130130
let (client_ch, server_ch) = pair.connect();
131+
assert_matches!(
132+
pair.client_conn_mut(client_ch).poll(),
133+
Some(Event::ResumptionEnabled)
134+
);
131135
assert_matches!(pair.client_conn_mut(client_ch).poll(), None);
132136
assert!(pair.client_conn_mut(client_ch).using_ecn());
133137
assert!(pair.server_conn_mut(server_ch).using_ecn());
@@ -161,6 +165,10 @@ fn draft_version_compat() {
161165
let mut pair = Pair::default();
162166
let (client_ch, server_ch) = pair.connect_with(client_config);
163167

168+
assert_matches!(
169+
pair.client_conn_mut(client_ch).poll(),
170+
Some(Event::ResumptionEnabled)
171+
);
164172
assert_matches!(pair.client_conn_mut(client_ch).poll(), None);
165173
assert!(pair.client_conn_mut(client_ch).using_ecn());
166174
assert!(pair.server_conn_mut(server_ch).using_ecn());
@@ -206,6 +214,10 @@ fn server_stateless_reset() {
206214
pair.client.connections.get_mut(&client_ch).unwrap().ping();
207215
info!("resetting");
208216
pair.drive();
217+
assert_matches!(
218+
pair.client_conn_mut(client_ch).poll(),
219+
Some(Event::ResumptionEnabled)
220+
);
209221
assert_matches!(
210222
pair.client_conn_mut(client_ch).poll(),
211223
Some(Event::ConnectionLost {
@@ -327,6 +339,10 @@ fn finish_stream_simple() {
327339
pair.client_send(client_ch, s).finish().unwrap();
328340
pair.drive();
329341

342+
assert_matches!(
343+
pair.client_conn_mut(client_ch).poll(),
344+
Some(Event::ResumptionEnabled)
345+
);
330346
assert_matches!(
331347
pair.client_conn_mut(client_ch).poll(),
332348
Some(Event::Stream(StreamEvent::Finished { id })) if id == s
@@ -379,6 +395,10 @@ fn reset_stream() {
379395
let mut chunks = recv.read(false).unwrap();
380396
assert_matches!(chunks.next(usize::MAX), Err(ReadError::Reset(ERROR)));
381397
let _ = chunks.finalize();
398+
assert_matches!(
399+
pair.client_conn_mut(client_ch).poll(),
400+
Some(Event::ResumptionEnabled)
401+
);
382402
assert_matches!(pair.client_conn_mut(client_ch).poll(), None);
383403
}
384404

@@ -597,6 +617,10 @@ fn zero_rtt_happypath() {
597617
);
598618
let _ = chunks.finalize();
599619
assert_eq!(pair.client_conn_mut(client_ch).stats().path.lost_packets, 0);
620+
assert_matches!(
621+
pair.client_conn_mut(client_ch).poll(),
622+
Some(Event::ResumptionEnabled)
623+
);
600624
}
601625

602626
#[test]
@@ -905,6 +929,10 @@ fn stream_id_limit() {
905929
pair.client_send(client_ch, s).write(MSG).unwrap();
906930
pair.client_send(client_ch, s).finish().unwrap();
907931
pair.drive();
932+
assert_matches!(
933+
pair.client_conn_mut(client_ch).poll(),
934+
Some(Event::ResumptionEnabled)
935+
);
908936
assert_matches!(
909937
pair.client_conn_mut(client_ch).poll(),
910938
Some(Event::Stream(StreamEvent::Finished { id })) if id == s
@@ -1192,6 +1220,10 @@ fn idle_timeout() {
11921220
}
11931221

11941222
assert!(pair.time - start < Duration::from_millis(2 * IDLE_TIMEOUT));
1223+
assert_matches!(
1224+
pair.client_conn_mut(client_ch).poll(),
1225+
Some(Event::ResumptionEnabled)
1226+
);
11951227
assert_matches!(
11961228
pair.client_conn_mut(client_ch).poll(),
11971229
Some(Event::ConnectionLost {
@@ -1271,6 +1303,10 @@ fn migration() {
12711303
assert_ne!(pair.server_conn_mut(server_ch).total_recvd(), 0);
12721304

12731305
pair.drive();
1306+
assert_matches!(
1307+
pair.client_conn_mut(client_ch).poll(),
1308+
Some(Event::ResumptionEnabled)
1309+
);
12741310
assert_matches!(pair.client_conn_mut(client_ch).poll(), None);
12751311
assert_eq!(
12761312
pair.server_conn_mut(server_ch).remote_address(),
@@ -1657,6 +1693,10 @@ fn finish_stream_flow_control_reordered() {
16571693
pair.server.finish_delay(); // Add flow control packets after
16581694
pair.drive();
16591695

1696+
assert_matches!(
1697+
pair.client_conn_mut(client_ch).poll(),
1698+
Some(Event::ResumptionEnabled)
1699+
);
16601700
assert_matches!(
16611701
pair.client_conn_mut(client_ch).poll(),
16621702
Some(Event::Stream(StreamEvent::Finished { id })) if id == s
@@ -1749,6 +1789,10 @@ fn stop_during_finish() {
17491789
pair.drive_server();
17501790
pair.client_send(client_ch, s).finish().unwrap();
17511791
pair.drive_client();
1792+
assert_matches!(
1793+
pair.client_conn_mut(client_ch).poll(),
1794+
Some(Event::ResumptionEnabled)
1795+
);
17521796
assert_matches!(
17531797
pair.client_conn_mut(client_ch).poll(),
17541798
Some(Event::Stream(StreamEvent::Stopped { id, error_code: ERROR })) if id == s
@@ -2036,6 +2080,10 @@ fn finish_acked() {
20362080
// Send FIN, receive data ack
20372081
info!("client receives ACK, sends FIN");
20382082
pair.drive_client();
2083+
assert_matches!(
2084+
pair.client_conn_mut(client_ch).poll(),
2085+
Some(Event::ResumptionEnabled)
2086+
);
20392087
// Check for premature finish from data ack
20402088
assert_matches!(pair.client_conn_mut(client_ch).poll(), None);
20412089
// Process FIN ack
@@ -2074,6 +2122,10 @@ fn finish_retransmit() {
20742122
// Receive FIN ack, but no data ack
20752123
pair.drive_client();
20762124
// Check for premature finish from FIN ack
2125+
assert_matches!(
2126+
pair.client_conn_mut(client_ch).poll(),
2127+
Some(Event::ResumptionEnabled)
2128+
);
20772129
assert_matches!(pair.client_conn_mut(client_ch).poll(), None);
20782130
// Recover
20792131
pair.drive();

quinn/src/connection.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -644,6 +644,37 @@ impl Connection {
644644
// May need to send MAX_STREAMS to make progress
645645
conn.wake();
646646
}
647+
648+
/// Waits until the connection received TLS resumption tickets
649+
///
650+
/// Yields `true` once resumption tickets were received. Resolves immediately
651+
/// if tickets were already received, otherwise it resolves once tickets arrive.
652+
/// If the server does not send any tickets, the returned future will remain pending forever.
653+
///
654+
/// This should only be used on the client side. On the server side, it will
655+
/// always resolve immediately and yield `false`.
656+
pub fn resumption_tickets_received(&self) -> impl Future<Output = bool> + Send + 'static {
657+
let conn = self.0.clone();
658+
async move {
659+
let notify;
660+
let (mut notified, out) = {
661+
let conn = conn.state.lock("resumption_tickets_received");
662+
let (notified, out) = match conn.resumption_tickets.as_ref() {
663+
Some(ResumptionTicketState::Received) => (None, true),
664+
Some(ResumptionTicketState::Pending(n)) => {
665+
notify = n.clone();
666+
(Some(notify.notified()), true)
667+
}
668+
None => (None, false),
669+
};
670+
(notified, out)
671+
};
672+
if let Some(notified) = notified.take() {
673+
notified.await;
674+
}
675+
out
676+
}
677+
}
647678
}
648679

649680
pin_project! {
@@ -878,6 +909,10 @@ impl ConnectionRef {
878909
socket: Arc<dyn AsyncUdpSocket>,
879910
runtime: Arc<dyn Runtime>,
880911
) -> Self {
912+
let resumption_tickets = match conn.side() {
913+
Side::Client => Some(ResumptionTicketState::Pending(Default::default())),
914+
Side::Server => None,
915+
};
881916
Self(Arc::new(ConnectionInner {
882917
state: Mutex::new(State {
883918
inner: conn,
@@ -900,6 +935,7 @@ impl ConnectionRef {
900935
runtime,
901936
send_buffer: Vec::new(),
902937
buffered_transmit: None,
938+
resumption_tickets,
903939
}),
904940
shared: Shared::default(),
905941
}))
@@ -982,6 +1018,8 @@ pub(crate) struct State {
9821018
send_buffer: Vec<u8>,
9831019
/// We buffer a transmit when the underlying I/O would block
9841020
buffered_transmit: Option<proto::Transmit>,
1021+
/// Whether we received resumption tickets. None on the server side.
1022+
resumption_tickets: Option<ResumptionTicketState>,
9851023
}
9861024

9871025
impl State {
@@ -1116,6 +1154,14 @@ impl State {
11161154
wake_all_notify(&mut self.stopped);
11171155
}
11181156
}
1157+
ResumptionEnabled => {
1158+
if let Some(ResumptionTicketState::Pending(notify)) =
1159+
self.resumption_tickets.as_mut()
1160+
{
1161+
notify.notify_waiters();
1162+
self.resumption_tickets = Some(ResumptionTicketState::Received);
1163+
}
1164+
}
11191165
ConnectionLost { reason } => {
11201166
self.terminate(reason, shared);
11211167
}
@@ -1286,6 +1332,12 @@ fn wake_all_notify(wakers: &mut FxHashMap<StreamId, Arc<Notify>>) {
12861332
.for_each(|(_, notify)| notify.notify_waiters())
12871333
}
12881334

1335+
#[derive(Debug)]
1336+
enum ResumptionTicketState {
1337+
Received,
1338+
Pending(Arc<Notify>),
1339+
}
1340+
12891341
/// Errors that can arise when sending a datagram
12901342
#[derive(Debug, Error, Clone, Eq, PartialEq)]
12911343
pub enum SendDatagramError {

0 commit comments

Comments
 (0)