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