1use std::io;
2
3#[derive(Debug, thiserror::Error)]
4#[non_exhaustive]
5pub enum TlsError {
6 #[error("ssl config: {0}")]
7 SslConfig(String),
8
9 #[error("tls profile: {0}")]
10 Profile(String),
11
12 #[error("trust store: {0}")]
13 TrustStore(String),
14
15 #[error("tls handshake: {0}")]
16 Handshake(String),
17
18 #[error("tls handshake closed or reset by peer")]
19 Rejected(#[source] io::Error),
20
21 #[error("tls handshake io")]
22 HandshakeIo(#[source] io::Error),
23
24 #[error("certificate verification failed: {detail}")]
25 Certificate {
26 verify_code: Option<i32>,
27 reason: Option<&'static str>,
28 detail: String,
29 },
30
31 #[error("hostname: {0}")]
32 Hostname(String),
33
34 #[error("certificate pin: {0}")]
35 Pinning(String),
36
37 #[error("tcp connect")]
38 TcpConnect(#[source] io::Error),
39
40 #[error("dns resolution")]
41 Dns(#[source] io::Error),
42
43 #[error("proxy: {detail}")]
44 Proxy {
45 status: Option<u16>,
46 detail: String,
47 #[source]
48 source: Option<io::Error>,
49 },
50}
51
52fn peer_closed(kind: io::ErrorKind) -> bool {
53 matches!(
54 kind,
55 io::ErrorKind::UnexpectedEof
56 | io::ErrorKind::ConnectionReset
57 | io::ErrorKind::ConnectionAborted
58 | io::ErrorKind::BrokenPipe
59 )
60}
61
62fn transport_eof(msg: &str) -> bool {
63 let lower = msg.to_ascii_lowercase();
64 lower.contains("unexpected eof")
65 || lower.contains("connection reset")
66 || lower.contains("broken pipe")
67 || lower.contains("connection aborted")
68}
69
70impl TlsError {
71 pub(crate) fn from_handshake<S>(e: &leyline_bssl_tokio::HandshakeError<S>) -> Self {
72 if let Some(io) = e.as_io_error() {
73 let copy = io::Error::new(io.kind(), io.to_string());
74 if peer_closed(io.kind()) {
75 return Self::Rejected(copy);
76 }
77 return Self::HandshakeIo(copy);
78 }
79 let msg = e.to_string();
80 if transport_eof(&msg) {
81 Self::Rejected(io::Error::new(io::ErrorKind::UnexpectedEof, msg))
82 } else {
83 Self::Handshake(msg)
84 }
85 }
86
87 pub(crate) fn from_stack(e: leyline_bssl::error::ErrorStack) -> Self {
88 Self::SslConfig(e.to_string())
89 }
90
91 pub(crate) fn proxy(detail: impl Into<String>) -> Self {
92 Self::Proxy {
93 status: None,
94 detail: detail.into(),
95 source: None,
96 }
97 }
98
99 pub(crate) fn proxy_io(source: io::Error) -> Self {
100 Self::Proxy {
101 status: None,
102 detail: "proxy connection failed".into(),
103 source: Some(source),
104 }
105 }
106
107 pub(crate) fn into_proxy(self) -> Self {
108 match self {
109 Self::Proxy { .. } => self,
110 Self::Dns(e) | Self::TcpConnect(e) | Self::HandshakeIo(e) | Self::Rejected(e) => {
111 Self::proxy_io(e)
112 }
113 other => Self::proxy(other.to_string()),
114 }
115 }
116
117 pub(crate) fn io_source(&self) -> Option<&io::Error> {
118 match self {
119 Self::TcpConnect(e) | Self::Dns(e) | Self::HandshakeIo(e) | Self::Rejected(e) => {
120 Some(e)
121 }
122 Self::Proxy { source, .. } => source.as_ref(),
123 _ => None,
124 }
125 }
126
127 pub(crate) fn duplicate(&self) -> Self {
128 let io = |e: &io::Error| io::Error::new(e.kind(), e.to_string());
129 match self {
130 Self::SslConfig(m) => Self::SslConfig(m.clone()),
131 Self::Profile(m) => Self::Profile(m.clone()),
132 Self::TrustStore(m) => Self::TrustStore(m.clone()),
133 Self::Handshake(m) => Self::Handshake(m.clone()),
134 Self::Rejected(e) => Self::Rejected(io(e)),
135 Self::HandshakeIo(e) => Self::HandshakeIo(io(e)),
136 Self::Certificate {
137 verify_code,
138 reason,
139 detail,
140 } => Self::Certificate {
141 verify_code: *verify_code,
142 reason: *reason,
143 detail: detail.clone(),
144 },
145 Self::Hostname(m) => Self::Hostname(m.clone()),
146 Self::Pinning(m) => Self::Pinning(m.clone()),
147 Self::TcpConnect(e) => Self::TcpConnect(io(e)),
148 Self::Dns(e) => Self::Dns(io(e)),
149 Self::Proxy {
150 status,
151 detail,
152 source,
153 } => Self::Proxy {
154 status: *status,
155 detail: detail.clone(),
156 source: source.as_ref().map(io),
157 },
158 }
159 }
160}