1use crate::{contexts::WriteContext, transmission};
5use s2n_quic_core::{
6 ct::ConstantTimeEq,
7 event, frame,
8 time::{timer, Duration, Timer, Timestamp},
9};
10
11pub type Data = [u8; frame::path_challenge::DATA_LEN];
12const DISABLED_DATA: Data = [0; frame::path_challenge::DATA_LEN];
13
14#[derive(Clone, Debug)]
15pub struct Challenge {
16 state: State,
17 abandon_duration: Duration,
18 abandon_timer: Timer,
19 data: Data,
20}
21
22#[derive(Clone, Debug, PartialEq, Eq)]
23pub enum State {
24 InitialPathDisabled,
26
27 RequiresTransmission(u8),
29
30 PendingResponse,
32
33 Abandoned,
35
36 Validated,
38}
39
40impl transmission::interest::Provider for State {
41 #[inline]
42 fn transmission_interest<Q: transmission::interest::Query>(
43 &self,
44 query: &mut Q,
45 ) -> transmission::interest::Result {
46 match self {
47 State::RequiresTransmission(_) => query.on_interest(transmission::Interest::NewData),
48 _ => Ok(()),
49 }
50 }
51}
52
53impl Challenge {
54 pub fn new(abandon_duration: Duration, data: Data) -> Self {
55 Self {
56 state: State::RequiresTransmission(2),
68 abandon_duration,
69 abandon_timer: Timer::default(),
70 data,
71 }
72 }
73
74 pub fn disabled() -> Self {
75 Self {
76 state: State::InitialPathDisabled,
77 abandon_duration: Duration::ZERO,
78 abandon_timer: Timer::default(),
79 data: DISABLED_DATA,
80 }
81 }
82
83 pub fn on_transmit<W: WriteContext>(&mut self, context: &mut W) {
85 match self.state {
86 State::RequiresTransmission(0) => self.state = State::PendingResponse,
87 State::RequiresTransmission(remaining) => {
88 let frame = frame::PathChallenge { data: &self.data };
92
93 if context.write_frame(&frame).is_some() {
94 let remaining = remaining - 1;
95 self.state = State::RequiresTransmission(remaining);
96
97 if !self.abandon_timer.is_armed() {
98 self.abandon_timer
99 .set(context.current_time() + self.abandon_duration);
100 }
101 }
102 }
103 _ => {}
104 }
105 }
106
107 pub fn on_timeout<Pub: event::ConnectionPublisher>(
108 &mut self,
109 timestamp: Timestamp,
110 publisher: &mut Pub,
111 path: event::builder::Path,
112 ) {
113 if self.abandon_timer.poll_expiration(timestamp).is_ready() {
114 self.abandon(publisher, path);
115 }
116 }
117
118 pub fn abandon<Pub: event::ConnectionPublisher>(
119 &mut self,
120 publisher: &mut Pub,
121 path: event::builder::Path,
122 ) {
123 if self.is_pending() {
124 self.state = State::Abandoned;
125 self.abandon_timer.cancel();
126 publisher.on_path_challenge_updated(event::builder::PathChallengeUpdated {
127 path_challenge_status: event::builder::PathChallengeStatus::Abandoned,
128 path,
129 challenge_data: self.challenge_data(),
130 });
131 }
132 }
133
134 pub fn is_disabled(&self) -> bool {
135 matches!(self.state, State::InitialPathDisabled)
136 }
137
138 pub fn is_pending(&self) -> bool {
139 matches!(
140 self.state,
141 State::PendingResponse | State::RequiresTransmission(_)
142 )
143 }
144
145 pub fn on_validated(&mut self, data: &[u8]) -> bool {
146 if self.is_pending() && ConstantTimeEq::ct_eq(&self.data[..], data).into() {
147 self.state = State::Validated;
148 true
149 } else {
150 false
151 }
152 }
153
154 pub fn challenge_data(&self) -> &[u8] {
155 &self.data
156 }
157}
158
159impl timer::Provider for Challenge {
160 #[inline]
161 fn timers<Q: timer::Query>(&self, query: &mut Q) -> timer::Result {
162 self.abandon_timer.timers(query)?;
163
164 Ok(())
165 }
166}
167
168impl transmission::interest::Provider for Challenge {
169 #[inline]
170 fn transmission_interest<Q: transmission::interest::Query>(
171 &self,
172 query: &mut Q,
173 ) -> transmission::interest::Result {
174 self.state.transmission_interest(query)
175 }
176}
177
178#[cfg(any(test, feature = "testing"))]
179pub mod testing {
180 use super::*;
181 use s2n_quic_core::time::{Clock, Duration, NoopClock};
182
183 pub fn helper_challenge() -> Helper {
184 let now = NoopClock {}.get_time();
185 let abandon_duration = Duration::from_millis(10_000);
186 let expected_data: [u8; 8] = [0; 8];
187
188 let challenge = Challenge::new(abandon_duration, expected_data);
189
190 Helper {
191 now,
192 abandon_duration,
193 expected_data,
194 challenge,
195 }
196 }
197
198 #[allow(dead_code)]
199 pub struct Helper {
200 pub now: Timestamp,
201 pub abandon_duration: Duration,
202 pub expected_data: Data,
203 pub challenge: Challenge,
204 }
205}
206
207#[cfg(test)]
208mod tests {
209 use super::*;
210 use crate::contexts::testing::{MockWriteContext, OutgoingFrameBuffer};
211 use s2n_quic_core::{
212 endpoint,
213 time::{Clock, Duration, NoopClock},
214 };
215 use testing::*;
216
217 #[test]
222 fn create_challenge_that_requires_two_transmissions() {
223 let helper = helper_challenge();
224 assert_eq!(helper.challenge.state, State::RequiresTransmission(2));
225 }
226
227 #[test]
228 fn create_disabled_challenge() {
229 let challenge = Challenge::disabled();
230 assert_eq!(challenge.state, State::InitialPathDisabled);
231 assert!(!challenge.abandon_timer.is_armed());
232 }
233
234 #[test]
240 fn transmit_challenge_only_twice() {
241 let mut helper = helper_challenge();
243 let mut frame_buffer = OutgoingFrameBuffer::new();
244 let mut context = MockWriteContext::new(
245 helper.now,
246 &mut frame_buffer,
247 transmission::Constraint::None,
248 transmission::Mode::Normal,
249 endpoint::Type::Client,
250 );
251 assert_eq!(helper.challenge.state, State::RequiresTransmission(2));
252
253 helper.challenge.on_transmit(&mut context);
255
256 assert_eq!(context.frame_buffer.len(), 1);
262
263 assert_eq!(helper.challenge.state, State::RequiresTransmission(1));
264 let written_data = match context.frame_buffer.pop_front().unwrap().as_frame() {
265 frame::Frame::PathChallenge(frame) => Some(*frame.data),
266 _ => None,
267 }
268 .unwrap();
269 assert_eq!(written_data, helper.expected_data);
270
271 helper.challenge.on_transmit(&mut context);
273
274 assert_eq!(helper.challenge.state, State::RequiresTransmission(0));
276 let written_data = match context.frame_buffer.pop_front().unwrap().as_frame() {
277 frame::Frame::PathChallenge(frame) => Some(*frame.data),
278 _ => None,
279 }
280 .unwrap();
281 assert_eq!(written_data, helper.expected_data);
282
283 helper.challenge.on_transmit(&mut context);
285
286 assert_eq!(helper.challenge.state, State::PendingResponse);
288 assert_eq!(context.frame_buffer.len(), 0);
289 }
290
291 #[test]
292 fn successful_on_transmit_arms_the_timer() {
293 let mut helper = helper_challenge();
295 let mut frame_buffer = OutgoingFrameBuffer::new();
296 let mut context = MockWriteContext::new(
297 helper.now,
298 &mut frame_buffer,
299 transmission::Constraint::None,
300 transmission::Mode::Normal,
301 endpoint::Type::Client,
302 );
303 assert_eq!(helper.challenge.state, State::RequiresTransmission(2));
304 assert!(!helper.challenge.abandon_timer.is_armed());
305
306 helper.challenge.on_transmit(&mut context);
308
309 assert!(helper.challenge.abandon_timer.is_armed());
311 }
312
313 #[test]
314 fn maintain_idle_and_dont_transmit_when_pending_response_state() {
315 let mut helper = helper_challenge();
317 let mut frame_buffer = OutgoingFrameBuffer::new();
318 let mut context = MockWriteContext::new(
319 helper.now,
320 &mut frame_buffer,
321 transmission::Constraint::None,
322 transmission::Mode::Normal,
323 endpoint::Type::Client,
324 );
325 helper.challenge.state = State::PendingResponse;
326 assert_eq!(helper.challenge.state, State::PendingResponse);
327
328 helper.challenge.on_transmit(&mut context);
330
331 assert_eq!(helper.challenge.state, State::PendingResponse);
333 assert_eq!(context.frame_buffer.len(), 0);
334 }
335
336 #[test]
337 fn test_on_timeout() {
338 let mut helper = helper_challenge();
339 let expiration_time = helper.now + helper.abandon_duration;
340
341 let mut frame_buffer = OutgoingFrameBuffer::new();
342 let mut context = MockWriteContext::new(
343 helper.now,
344 &mut frame_buffer,
345 transmission::Constraint::None,
346 transmission::Mode::Normal,
347 endpoint::Type::Client,
348 );
349 helper.challenge.on_transmit(&mut context);
350
351 let mut publisher = event::testing::Publisher::snapshot();
352 let path = event::builder::Path::test();
353
354 helper.challenge.on_timeout(
355 expiration_time - Duration::from_millis(10),
356 &mut publisher,
357 path,
358 );
359 assert!(helper.challenge.is_pending());
360
361 let path = event::builder::Path::test();
362 helper.challenge.on_timeout(
363 expiration_time + Duration::from_millis(10),
364 &mut publisher,
365 path,
366 );
367 assert!(!helper.challenge.is_pending());
368 }
369
370 #[test]
371 fn challenge_must_remains_abandoned_once_abandoned() {
372 let mut helper = helper_challenge();
373 let expiration_time = helper.now + helper.abandon_duration;
374
375 let mut frame_buffer = OutgoingFrameBuffer::new();
376 let mut context = MockWriteContext::new(
377 helper.now,
378 &mut frame_buffer,
379 transmission::Constraint::None,
380 transmission::Mode::Normal,
381 endpoint::Type::Client,
382 );
383 helper.challenge.on_transmit(&mut context);
384
385 let mut publisher = event::testing::Publisher::snapshot();
386 let path = event::builder::Path::test();
387
388 helper.challenge.on_timeout(
390 expiration_time + Duration::from_millis(10),
391 &mut publisher,
392 path,
393 );
394
395 assert!(!helper.challenge.is_pending());
397
398 let path = event::builder::Path::test();
399
400 helper.challenge.on_timeout(
402 expiration_time - Duration::from_millis(10),
403 &mut publisher,
404 path,
405 );
406
407 assert!(!helper.challenge.is_pending());
409 }
410
411 #[test]
412 fn dont_abandon_disabled_state() {
413 let mut challenge = Challenge::disabled();
414 let now = NoopClock {}.get_time();
415
416 let mut frame_buffer = OutgoingFrameBuffer::new();
417 let mut context = MockWriteContext::new(
418 now,
419 &mut frame_buffer,
420 transmission::Constraint::None,
421 transmission::Mode::Normal,
422 endpoint::Type::Client,
423 );
424 challenge.on_transmit(&mut context);
425
426 assert_eq!(challenge.state, State::InitialPathDisabled);
427
428 let mut publisher = event::testing::Publisher::snapshot();
429 let path = event::builder::Path::test();
430
431 let large_expiration_time = now + Duration::from_secs(1_000_000);
432 challenge.on_timeout(large_expiration_time, &mut publisher, path);
433 assert_eq!(challenge.state, State::InitialPathDisabled);
434 }
435
436 #[test]
437 fn test_is_abandoned() {
438 let mut helper = helper_challenge();
439 let expiration_time = helper.now + helper.abandon_duration;
440
441 let mut frame_buffer = OutgoingFrameBuffer::new();
442 let mut context = MockWriteContext::new(
443 helper.now,
444 &mut frame_buffer,
445 transmission::Constraint::None,
446 transmission::Mode::Normal,
447 endpoint::Type::Client,
448 );
449 helper.challenge.on_transmit(&mut context);
450
451 let mut publisher = event::testing::Publisher::snapshot();
452 let path = event::builder::Path::test();
453
454 assert!(helper.challenge.is_pending());
455
456 helper.challenge.on_timeout(
457 expiration_time + Duration::from_millis(10),
458 &mut publisher,
459 path,
460 );
461 assert!(!helper.challenge.is_pending());
462 }
463
464 #[test]
465 fn test_on_validate() {
466 let mut helper = helper_challenge();
467
468 let wrong_data: [u8; 8] = [5; 8];
469 assert!(!helper.challenge.on_validated(&wrong_data));
470 assert!(helper.challenge.is_pending());
471
472 assert!(helper.challenge.on_validated(&helper.expected_data));
473 assert_eq!(helper.challenge.state, State::Validated);
474 }
475
476 #[test]
477 fn is_disabled() {
478 let challenge = Challenge::disabled();
479
480 assert_eq!(challenge.state, State::InitialPathDisabled);
481 assert!(challenge.is_disabled());
482 }
483
484 #[test]
485 fn dont_validate_disabled_state() {
486 let mut helper = helper_challenge();
487 helper.challenge.state = State::InitialPathDisabled;
488
489 assert!(!helper.challenge.on_validated(&helper.expected_data));
490 assert_eq!(helper.challenge.state, State::InitialPathDisabled);
491 }
492
493 #[test]
494 fn dont_abandon_a_validated_challenge() {
495 let mut helper = helper_challenge();
496 helper.challenge.state = State::Validated;
497 let mut publisher = event::testing::Publisher::snapshot();
498 let path = event::builder::Path::test();
499
500 helper.challenge.abandon(&mut publisher, path);
501
502 assert_eq!(helper.challenge.state, State::Validated);
503 }
504
505 #[test]
506 fn cancel_abandon_timer_on_abandon() {
507 let mut helper = helper_challenge();
508 let mut publisher = event::testing::Publisher::snapshot();
509 let path = event::builder::Path::test();
510
511 helper.challenge.abandon(&mut publisher, path);
512
513 assert!(!helper.challenge.abandon_timer.is_armed());
514 }
515}