1use crate::{
5 application, connection, crypto::packet_protection, endpoint, frame::ConnectionClose, transport,
6};
7use core::{fmt, panic, time::Duration};
8
9#[derive(Debug, Copy, Clone)]
11#[non_exhaustive]
12pub enum Error {
13 #[non_exhaustive]
15 Closed {
16 initiator: endpoint::Location,
17 source: &'static panic::Location<'static>,
18 },
19
20 #[non_exhaustive]
26 Transport {
27 code: transport::error::Code,
28 frame_type: u64,
29 reason: &'static str,
30 initiator: endpoint::Location,
31 source: &'static panic::Location<'static>,
32 },
33
34 #[non_exhaustive]
40 Application {
41 error: application::Error,
42 initiator: endpoint::Location,
43 source: &'static panic::Location<'static>,
44 },
45
46 #[non_exhaustive]
48 StatelessReset {
49 source: &'static panic::Location<'static>,
50 },
51
52 #[non_exhaustive]
54 IdleTimerExpired {
55 source: &'static panic::Location<'static>,
56 },
57
58 #[non_exhaustive]
60 NoValidPath {
61 source: &'static panic::Location<'static>,
62 },
63
64 #[non_exhaustive]
66 StreamIdExhausted {
67 source: &'static panic::Location<'static>,
68 },
69
70 #[non_exhaustive]
72 MaxHandshakeDurationExceeded {
73 max_handshake_duration: Duration,
74 source: &'static panic::Location<'static>,
75 },
76
77 #[non_exhaustive]
79 ImmediateClose {
80 reason: &'static str,
81 source: &'static panic::Location<'static>,
82 },
83
84 #[non_exhaustive]
86 EndpointClosing {
87 source: &'static panic::Location<'static>,
88 },
89
90 #[non_exhaustive]
92 InvalidConfiguration {
93 reason: &'static str,
94 source: &'static panic::Location<'static>,
95 },
96
97 #[non_exhaustive]
99 Unspecified {
100 source: &'static panic::Location<'static>,
101 },
102}
103
104impl core::error::Error for Error {}
105
106impl fmt::Display for Error {
107 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
108 match self {
109 Self::Closed { initiator, .. } => write!(
110 f,
111 "The connection was closed without an error by {initiator}"
112 ),
113 Self::Transport { code, frame_type, reason, initiator, .. } => {
114 let error = transport::Error {
115 code: *code,
116 frame_type: (*frame_type).try_into().ok().unwrap_or_default(),
117 reason,
118 };
119 write!(
120 f,
121 "The connection was closed on the transport level with error {error} by {initiator}"
122 )
123 },
124 Self::Application { error, initiator, .. } => write!(
125 f,
126 "The connection was closed on the application level with error {error:?} by {initiator}"
127 ),
128 Self::StatelessReset { .. } => write!(
129 f,
130 "The connection was reset by a stateless reset by {}",
131 endpoint::Location::Remote
132 ),
133 Self::IdleTimerExpired {.. } => write!(
134 f,
135 "The connection was closed because the connection's idle timer expired by {}",
136 endpoint::Location::Local
137 ),
138 Self::NoValidPath { .. } => write!(
139 f,
140 "The connection was closed because there are no valid paths"
141 ),
142 Self::StreamIdExhausted { .. } => write!(
143 f,
144 "All Stream IDs for Streams on the given connection had been exhausted"
145 ),
146 Self::MaxHandshakeDurationExceeded { max_handshake_duration, .. } => write!(
147 f,
148 "The connection was closed because the handshake took longer than the max handshake \
149 duration of {max_handshake_duration:?}"
150 ),
151 Self::ImmediateClose { reason, .. } => write!(
152 f,
153 "The connection was closed due to: {reason}"
154 ),
155 Self::EndpointClosing { .. } => {
156 write!(f, "The connection attempt was rejected because the endpoint is closing")
157 }
158 Self::InvalidConfiguration {reason, ..} => write!(
159 f,
160 "The connection was closed due to: {reason}"
161 ),
162 Self::Unspecified { .. } => {
163 write!(f, "The connection was closed due to an unspecified reason")
164 }
165 }
166 }
167}
168
169impl PartialEq for Error {
170 #[inline]
171 fn eq(&self, other: &Self) -> bool {
172 match (self, other) {
174 (Error::Closed { initiator: a, .. }, Error::Closed { initiator: b, .. }) => a.eq(b),
175 (
176 Error::Transport {
177 code: a_code,
178 frame_type: a_frame_type,
179 reason: a_reason,
180 initiator: a_initiator,
181 ..
182 },
183 Error::Transport {
184 code: b_code,
185 frame_type: b_frame_type,
186 reason: b_reason,
187 initiator: b_initiator,
188 ..
189 },
190 ) => {
191 a_code.eq(b_code)
192 && a_frame_type.eq(b_frame_type)
193 && a_reason.eq(b_reason)
194 && a_initiator.eq(b_initiator)
195 }
196 (
197 Error::Application {
198 error: a_error,
199 initiator: a_initiator,
200 ..
201 },
202 Error::Application {
203 error: b_error,
204 initiator: b_initiator,
205 ..
206 },
207 ) => a_error.eq(b_error) && a_initiator.eq(b_initiator),
208 (Error::StatelessReset { .. }, Error::StatelessReset { .. }) => true,
209 (Error::IdleTimerExpired { .. }, Error::IdleTimerExpired { .. }) => true,
210 (Error::NoValidPath { .. }, Error::NoValidPath { .. }) => true,
211 (Error::StreamIdExhausted { .. }, Error::StreamIdExhausted { .. }) => true,
212 (
213 Error::MaxHandshakeDurationExceeded {
214 max_handshake_duration: a,
215 ..
216 },
217 Error::MaxHandshakeDurationExceeded {
218 max_handshake_duration: b,
219 ..
220 },
221 ) => a.eq(b),
222 (Error::ImmediateClose { reason: a, .. }, Error::ImmediateClose { reason: b, .. }) => {
223 a.eq(b)
224 }
225 (Error::EndpointClosing { .. }, Error::EndpointClosing { .. }) => true,
226 (
227 Error::InvalidConfiguration {
228 reason: a_reason, ..
229 },
230 Error::InvalidConfiguration {
231 reason: b_reason, ..
232 },
233 ) => a_reason.eq(b_reason),
234 (Error::Unspecified { .. }, Error::Unspecified { .. }) => true,
235 _ => false,
236 }
237 }
238}
239
240impl Eq for Error {}
241
242impl Error {
243 pub fn source(&self) -> &'static panic::Location<'static> {
245 match self {
246 Error::Closed { source, .. } => source,
247 Error::Transport { source, .. } => source,
248 Error::Application { source, .. } => source,
249 Error::StatelessReset { source } => source,
250 Error::IdleTimerExpired { source } => source,
251 Error::NoValidPath { source } => source,
252 Error::StreamIdExhausted { source } => source,
253 Error::MaxHandshakeDurationExceeded { source, .. } => source,
254 Error::ImmediateClose { source, .. } => source,
255 Error::EndpointClosing { source } => source,
256 Error::InvalidConfiguration { source, .. } => source,
257 Error::Unspecified { source } => source,
258 }
259 }
260
261 #[track_caller]
262 fn from_transport_error(error: transport::Error, initiator: endpoint::Location) -> Self {
263 let source = panic::Location::caller();
264 match error.code {
265 code if code == transport::Error::NO_ERROR.code => Self::Closed { initiator, source },
267 code if code == transport::Error::APPLICATION_ERROR.code && initiator.is_remote() => {
269 Self::Closed { initiator, source }
270 }
271 _ => Self::Transport {
273 code: error.code,
274 frame_type: error.frame_type.into(),
275 reason: error.reason,
276 initiator,
277 source,
278 },
279 }
280 }
281
282 #[inline]
283 #[track_caller]
284 #[doc(hidden)]
285 pub fn closed(initiator: endpoint::Location) -> Error {
286 let source = panic::Location::caller();
287 Error::Closed { initiator, source }
288 }
289
290 #[inline]
291 #[track_caller]
292 #[doc(hidden)]
293 pub fn immediate_close(reason: &'static str) -> Error {
294 let source = panic::Location::caller();
295 Error::ImmediateClose { reason, source }
296 }
297
298 #[inline]
299 #[track_caller]
300 #[doc(hidden)]
301 pub fn idle_timer_expired() -> Error {
302 let source = panic::Location::caller();
303 Error::IdleTimerExpired { source }
304 }
305
306 #[inline]
307 #[track_caller]
308 #[doc(hidden)]
309 pub fn stream_id_exhausted() -> Error {
310 let source = panic::Location::caller();
311 Error::StreamIdExhausted { source }
312 }
313
314 #[inline]
315 #[track_caller]
316 #[doc(hidden)]
317 pub fn no_valid_path() -> Error {
318 let source = panic::Location::caller();
319 Error::NoValidPath { source }
320 }
321
322 #[inline]
323 #[track_caller]
324 #[doc(hidden)]
325 pub fn stateless_reset() -> Error {
326 let source = panic::Location::caller();
327 Error::StatelessReset { source }
328 }
329
330 #[inline]
331 #[track_caller]
332 #[doc(hidden)]
333 pub fn max_handshake_duration_exceeded(max_handshake_duration: Duration) -> Error {
334 let source = panic::Location::caller();
335 Error::MaxHandshakeDurationExceeded {
336 max_handshake_duration,
337 source,
338 }
339 }
340
341 #[inline]
342 #[track_caller]
343 #[doc(hidden)]
344 pub fn application(error: application::Error) -> Error {
345 let source = panic::Location::caller();
346 Error::Application {
347 error,
348 initiator: endpoint::Location::Local,
349 source,
350 }
351 }
352
353 #[inline]
354 #[track_caller]
355 #[doc(hidden)]
356 pub fn endpoint_closing() -> Error {
357 let source = panic::Location::caller();
358 Error::EndpointClosing { source }
359 }
360
361 #[inline]
362 #[track_caller]
363 #[doc(hidden)]
364 pub fn invalid_configuration(reason: &'static str) -> Error {
365 let source = panic::Location::caller();
366 Error::InvalidConfiguration { source, reason }
367 }
368
369 #[inline]
370 #[track_caller]
371 #[doc(hidden)]
372 pub fn unspecified() -> Error {
373 let source = panic::Location::caller();
374 Error::Unspecified { source }
375 }
376
377 #[inline]
378 #[doc(hidden)]
379 pub fn into_accept_error(error: connection::Error) -> Result<(), connection::Error> {
380 match error {
381 connection::Error::Closed { .. } => Ok(()),
383 connection::Error::Transport { code, .. }
385 if code == transport::Error::APPLICATION_ERROR.code =>
386 {
387 Ok(())
388 }
389 connection::Error::IdleTimerExpired { .. } => Ok(()),
391 _ => Err(error),
393 }
394 }
395}
396
397pub fn as_frame<'a, F: connection::close::Formatter>(
402 error: Error,
403 formatter: &'a F,
404 context: &'a connection::close::Context<'a>,
405) -> Option<(ConnectionClose<'a>, ConnectionClose<'a>)> {
406 match error {
407 Error::Closed { initiator, .. } => {
408 if initiator.is_remote() {
410 return None;
411 }
412
413 let error = transport::Error::NO_ERROR;
414 let early = formatter.format_early_transport_error(context, error);
415 let one_rtt = formatter.format_transport_error(context, error);
416
417 Some((early, one_rtt))
418 }
419 Error::Transport {
420 code,
421 frame_type,
422 reason,
423 initiator,
424 ..
425 } => {
426 if initiator.is_remote() {
428 return None;
429 }
430
431 let error = transport::Error {
432 code,
433 frame_type: frame_type.try_into().unwrap_or_default(),
434 reason,
435 };
436
437 let early = formatter.format_early_transport_error(context, error);
438 let one_rtt = formatter.format_transport_error(context, error);
439 Some((early, one_rtt))
440 }
441 Error::Application {
442 error, initiator, ..
443 } => {
444 if initiator.is_remote() {
446 return None;
447 }
448
449 let early = formatter.format_early_application_error(context, error);
450 let one_rtt = formatter.format_application_error(context, error);
451 Some((early, one_rtt))
452 }
453 Error::StatelessReset { .. } => None,
455 Error::IdleTimerExpired { .. } => None,
457 Error::NoValidPath { .. } => None,
458 Error::StreamIdExhausted { .. } => {
459 let error =
460 transport::Error::PROTOCOL_VIOLATION.with_reason("stream IDs have been exhausted");
461
462 let early = formatter.format_early_transport_error(context, error);
463 let one_rtt = formatter.format_transport_error(context, error);
464
465 Some((early, one_rtt))
466 }
467 Error::MaxHandshakeDurationExceeded { .. } => None,
468 Error::ImmediateClose { .. } => None,
469 Error::EndpointClosing { .. } => None,
470 Error::InvalidConfiguration { .. } => None,
471 Error::Unspecified { .. } => {
472 let error =
473 transport::Error::INTERNAL_ERROR.with_reason("an unspecified error occurred");
474
475 let early = formatter.format_early_transport_error(context, error);
476 let one_rtt = formatter.format_transport_error(context, error);
477
478 Some((early, one_rtt))
479 }
480 }
481}
482
483impl application::error::TryInto for Error {
484 fn application_error(&self) -> Option<application::Error> {
485 if let Self::Application { error, .. } = self {
486 Some(*error)
487 } else {
488 None
489 }
490 }
491}
492
493impl From<transport::Error> for Error {
494 #[track_caller]
495 fn from(error: transport::Error) -> Self {
496 Self::from_transport_error(error, endpoint::Location::Local)
497 }
498}
499
500impl From<ConnectionClose<'_>> for Error {
501 #[track_caller]
502 fn from(error: ConnectionClose) -> Self {
503 if let Some(frame_type) = error.frame_type {
504 let error = transport::Error {
505 code: transport::error::Code::new(error.error_code),
506 reason: "",
509 frame_type,
510 };
511 Self::from_transport_error(error, endpoint::Location::Remote)
512 } else {
513 let source = panic::Location::caller();
514 Self::Application {
515 error: error.error_code.into(),
516 initiator: endpoint::Location::Remote,
517 source,
518 }
519 }
520 }
521}
522
523#[cfg(feature = "std")]
524impl From<Error> for std::io::Error {
525 fn from(error: Error) -> Self {
526 let kind = error.into();
527 std::io::Error::new(kind, error)
528 }
529}
530
531#[cfg(feature = "std")]
532impl From<Error> for std::io::ErrorKind {
533 fn from(error: Error) -> Self {
534 use std::io::ErrorKind;
535 match error {
536 Error::Closed { .. } => ErrorKind::ConnectionAborted,
537 Error::Transport { code, .. } if code == transport::Error::CONNECTION_REFUSED.code => {
538 ErrorKind::ConnectionRefused
539 }
540 Error::Transport { .. } => ErrorKind::ConnectionReset,
541 Error::Application { .. } => ErrorKind::ConnectionReset,
542 Error::StatelessReset { .. } => ErrorKind::ConnectionReset,
543 Error::IdleTimerExpired { .. } => ErrorKind::TimedOut,
544 Error::NoValidPath { .. } => ErrorKind::Other,
545 Error::StreamIdExhausted { .. } => ErrorKind::Other,
546 Error::MaxHandshakeDurationExceeded { .. } => ErrorKind::TimedOut,
547 Error::ImmediateClose { .. } => ErrorKind::Other,
548 Error::EndpointClosing { .. } => ErrorKind::Other,
549 Error::InvalidConfiguration { .. } => ErrorKind::Other,
550 Error::Unspecified { .. } => ErrorKind::Other,
551 }
552 }
553}
554
555#[derive(Clone, Copy, Debug, PartialEq)]
558pub enum ProcessingError {
559 ConnectionError(Error),
560 DecryptError,
561 Other,
562}
563
564impl From<Error> for ProcessingError {
565 fn from(inner_error: Error) -> Self {
566 ProcessingError::ConnectionError(inner_error)
567 }
568}
569
570impl From<crate::transport::Error> for ProcessingError {
571 #[track_caller]
572 fn from(inner_error: crate::transport::Error) -> Self {
573 Self::ConnectionError(inner_error.into())
574 }
575}
576
577impl From<packet_protection::Error> for ProcessingError {
578 fn from(_: packet_protection::Error) -> Self {
579 Self::DecryptError
580 }
581}
582
583mod metrics {
584 use super::{transport, Error};
585 use crate::event::metrics::aggregate::{
586 info::{Str, Variant},
587 AsVariant,
588 };
589
590 macro_rules! impl_variants {
591 ($($name:ident => $name_str:literal),* $(,)?) => {
592 impl AsVariant for Error {
593 const VARIANTS: &'static [Variant] = &{
594 const fn count(_id: &str) -> usize {
595 1
596 }
597
598 const VARIANTS: usize = 0 $( + count($name_str))*;
599
600 const TRANSPORT: &'static [Variant] = transport::error::Code::VARIANTS;
601
602 let mut array = [
603 Variant { name: Str::new("\0"), id: 0 };
604 VARIANTS + TRANSPORT.len()
605 ];
606
607 let mut id = 0;
608
609 $(
610 array[id] = Variant {
611 name: Str::new(concat!($name_str, "\0")),
612 id,
613 };
614 id += 1;
615 )*
616
617 let mut transport_idx = 0;
618 while transport_idx < TRANSPORT.len() {
619 let variant = TRANSPORT[transport_idx];
620 array[id] = Variant {
621 name: variant.name,
622 id,
623 };
624 id += 1;
625 transport_idx += 1;
626 }
627
628 array
629 };
630
631 #[inline]
632 fn variant_idx(&self) -> usize {
633 let mut idx = 0;
634
635 $(
636 if matches!(self, Error::$name { .. }) {
637 return idx;
638 }
639 idx += 1;
640 )*
641
642 if let Error::Transport { code, ..} = self {
643 code.variant_idx() + idx
644 } else {
645 panic!()
646 }
647 }
648 }
649
650 #[allow(dead_code)]
651 fn exhaustive_test(error: &Error) {
652 match error {
653 $(
654 Error::$name { .. } => {},
655 )*
656 Error::Transport { .. } => {},
657 }
658 }
659
660 #[test]
661 #[cfg_attr(miri, ignore)]
662 fn variants_test() {
663 insta::assert_debug_snapshot!(Error::VARIANTS);
664
665 let mut seen = std::collections::HashSet::new();
666 for variant in Error::VARIANTS {
667 assert!(seen.insert(variant.id));
668 }
669 }
670 };
671 }
672
673 impl_variants!(
674 Closed => "CLOSED",
675 Application => "APPLICATION",
676 StatelessReset => "STATELESS_RESET",
677 IdleTimerExpired => "IDLE_TIMER_EXPIRED",
678 NoValidPath => "NO_VALID_PATH",
679 StreamIdExhausted => "STREAM_ID_EXHAUSTED",
680 MaxHandshakeDurationExceeded => "MAX_HANDSHAKE_DURATION_EXCEEDED",
681 ImmediateClose => "IMMEDIATE_CLOSE",
682 EndpointClosing => "ENDPOINT_CLOSING",
683 InvalidConfiguration => "INVALID_CONFIGURATION",
684 Unspecified => "UNSPECIFIED",
685 );
686}