Skip to main content

tightbeam/transport/
error.rs

1use crate::asn1::Frame;
2use crate::policy::TransitStatus;
3
4#[cfg(feature = "derive")]
5use crate::Errorizable;
6#[cfg(not(feature = "std"))]
7use alloc::boxed::Box;
8
9pub type Result<T> = core::result::Result<T, TransportError>;
10
11/// Reasons why a message failed to be sent before network I/O
12#[derive(Debug, Copy, Clone, PartialEq, Eq)]
13pub enum TransportFailure {
14	/// DER encoding failed
15	EncodingFailed,
16	/// AEAD encryption failed
17	EncryptionFailed,
18	/// Message size exceeds configured limits
19	SizeExceeded,
20	/// Encryptor not available
21	EncryptorUnavailable,
22	/// Random nonce generation failed
23	NonceGenerationFailed,
24	/// Gate policy rejected (busy)
25	Busy,
26	/// Gate policy rejected (forbidden)
27	Forbidden,
28	/// Gate policy rejected (unauthorized)
29	Unauthorized,
30	/// Gate policy rejected (timeout)
31	Timeout,
32	/// Gate policy rejected (general)
33	PolicyRejection,
34}
35
36/// Transport error types
37#[cfg_attr(feature = "derive", derive(Errorizable))]
38#[derive(Debug)]
39pub enum TransportError {
40	#[cfg_attr(feature = "derive", error("Connection closed gracefully"))]
41	ConnectionClosed,
42	#[cfg_attr(feature = "derive", error("Connection failed"))]
43	ConnectionFailed,
44	#[cfg_attr(feature = "derive", error("Send failed"))]
45	SendFailed,
46	#[cfg_attr(feature = "derive", error("Encryption required but not provided"))]
47	MissingEncryption,
48	#[cfg_attr(
49		feature = "derive",
50		error("Handshake protocol not supported by this transport: {0:?}")
51	)]
52	UnsupportedHandshakeProtocol(crate::transport::handshake::HandshakeProtocolKind),
53	#[cfg_attr(
54		feature = "derive",
55		error("Server certificate chain required but not provisioned")
56	)]
57	MissingServerCertificateChain,
58	#[cfg_attr(feature = "derive", error("Invalid message"))]
59	InvalidMessage,
60	#[cfg_attr(feature = "derive", error("Invalid reply"))]
61	InvalidReply,
62	#[cfg_attr(feature = "derive", error("Missing request"))]
63	MissingRequest,
64	#[cfg_attr(feature = "derive", error("Max retries exceeded"))]
65	MaxRetriesExceeded,
66	#[cfg_attr(feature = "derive", error("Invalid address"))]
67	InvalidAddress,
68	#[cfg_attr(feature = "derive", error("Invalid state"))]
69	InvalidState,
70	#[cfg(feature = "x509")]
71	#[cfg_attr(feature = "derive", error("Invalid certificate: {0}"))]
72	#[cfg_attr(feature = "derive", from)]
73	InvalidCertificate(crate::crypto::x509::error::CertificateValidationError),
74	#[cfg_attr(feature = "derive", error("Message not sent: {1:?} - {0:?}"))]
75	MessageNotSent(Box<Frame>, TransportFailure),
76	#[cfg_attr(feature = "derive", error("Operation failed: {0:?}"))]
77	OperationFailed(TransportFailure),
78	#[cfg(feature = "x509")]
79	#[cfg_attr(feature = "derive", error("Handshake error: {0}"))]
80	#[cfg_attr(feature = "derive", from)]
81	HandshakeError(crate::transport::handshake::HandshakeError),
82	#[cfg_attr(feature = "derive", error("DER error: {0}"))]
83	#[cfg_attr(feature = "derive", from)]
84	DerError(der::Error),
85	#[cfg(feature = "std")]
86	#[cfg_attr(feature = "derive", error("I/O error: {0}"))]
87	#[cfg_attr(feature = "derive", from)]
88	IoError(std::io::Error),
89}
90
91crate::impl_error_display!(TransportError {
92	ConnectionClosed => "Connection closed gracefully",
93	ConnectionFailed => "Connection failed",
94	SendFailed => "Send failed",
95	MissingEncryption => "Encryption required but not provided",
96	UnsupportedHandshakeProtocol(kind) => "Handshake protocol not supported by this transport: {kind:?}",
97	MissingServerCertificateChain => "Server certificate chain required but not provisioned",
98	InvalidMessage => "Invalid message",
99	InvalidReply => "Invalid reply",
100	MissingRequest => "Missing request",
101	MaxRetriesExceeded => "Max retries exceeded",
102	InvalidAddress => "Invalid address",
103	InvalidState => "Invalid state",
104	MessageNotSent(frame, failure) => "Message not sent: {failure:?} - {frame:?}",
105	OperationFailed(failure) => "Operation failed: {failure:?}",
106	DerError(err) => "DER error: {err}",
107
108	#[cfg(feature = "x509")]
109	InvalidCertificate(err) => "Invalid certificate: {err}",
110	#[cfg(feature = "x509")]
111	HandshakeError(err) => "Handshake error: {err}",
112	#[cfg(feature = "std")]
113	IoError(err) => "I/O error: {err}",
114});
115
116/// Narrows [`TightBeamError`](crate::error::TightBeamError) into [`TransportError`];
117/// variants without a transport counterpart collapse to [`TransportError::InvalidMessage`].
118impl From<crate::error::TightBeamError> for TransportError {
119	fn from(err: crate::error::TightBeamError) -> Self {
120		use crate::error::TightBeamError;
121		match err {
122			TightBeamError::TransportError(t) => t,
123			TightBeamError::SerializationError(e) => TransportError::DerError(e),
124
125			#[cfg(feature = "x509")]
126			TightBeamError::HandshakeError(h) => TransportError::HandshakeError(h),
127			#[cfg(feature = "x509")]
128			TightBeamError::CertificateValidationError(e) => TransportError::InvalidCertificate(e),
129			#[cfg(feature = "std")]
130			TightBeamError::IoError(e) => TransportError::IoError(e),
131			_ => TransportError::InvalidMessage,
132		}
133	}
134}
135
136impl From<TransitStatus> for TransportError {
137	fn from(status: TransitStatus) -> Self {
138		match status {
139			TransitStatus::Request => TransportError::InvalidMessage,
140			TransitStatus::Accepted => TransportError::InvalidMessage,
141			TransitStatus::Busy => TransportError::OperationFailed(TransportFailure::Busy),
142			TransitStatus::Unauthorized => TransportError::OperationFailed(TransportFailure::Unauthorized),
143			TransitStatus::Forbidden => TransportError::OperationFailed(TransportFailure::Forbidden),
144			TransitStatus::Timeout => TransportError::OperationFailed(TransportFailure::Timeout),
145		}
146	}
147}
148
149#[cfg(all(feature = "std", not(feature = "derive")))]
150crate::impl_from!(std::io::Error => TransportError::IoError);
151#[cfg(not(feature = "derive"))]
152crate::impl_from!(der::Error => TransportError::DerError);
153#[cfg(all(feature = "x509", not(feature = "derive")))]
154crate::impl_from!(crate::transport::handshake::HandshakeError => TransportError::HandshakeError);
155#[cfg(all(feature = "x509", not(feature = "derive")))]
156crate::impl_from!(crate::crypto::x509::error::CertificateValidationError => TransportError::InvalidCertificate);
157
158crate::impl_from!(
159	spki::Error => TransportError::DerError extract spki::Error::Asn1(der_err) =>
160		der_err else der::Error::from(der::ErrorKind::Failed)
161);
162#[cfg(feature = "x509")]
163crate::impl_from!(
164	x509_cert::builder::Error => TransportError::DerError extract x509_cert::builder::Error::Asn1(der_err) =>
165		der_err else der::Error::from(der::ErrorKind::Failed)
166);
167
168// Wrap AddrParseError in IoError
169#[cfg(all(feature = "std", feature = "tcp"))]
170impl From<std::net::AddrParseError> for TransportError {
171	fn from(err: std::net::AddrParseError) -> Self {
172		TransportError::IoError(std::io::Error::new(std::io::ErrorKind::InvalidInput, err))
173	}
174}
175
176// Wrap JoinError in IoError
177#[cfg(feature = "tokio")]
178impl From<tokio::task::JoinError> for TransportError {
179	fn from(err: tokio::task::JoinError) -> Self {
180		TransportError::IoError(std::io::Error::other(err))
181	}
182}
183
184// Convert timeout errors
185#[cfg(feature = "tokio")]
186impl From<tokio::time::error::Elapsed> for TransportError {
187	fn from(_: tokio::time::error::Elapsed) -> Self {
188		TransportError::OperationFailed(TransportFailure::Timeout)
189	}
190}
191
192// Convert ecdsa::Error through HandshakeError
193#[cfg(all(feature = "x509", feature = "secp256k1"))]
194impl From<k256::ecdsa::Error> for TransportError {
195	fn from(err: k256::ecdsa::Error) -> Self {
196		TransportError::HandshakeError(crate::transport::handshake::HandshakeError::from(err))
197	}
198}
199
200impl TransportError {
201	pub fn from_failure(frame: Frame, failure: TransportFailure) -> Self {
202		TransportError::MessageNotSent(Box::new(frame), failure)
203	}
204
205	/// Extract Frame from error if present, otherwise returns None
206	pub fn take_frame(self) -> Option<crate::asn1::Frame> {
207		match self {
208			TransportError::MessageNotSent(frame, _) => Some(*frame),
209			_ => None,
210		}
211	}
212
213	/// Extract Frame from error if present without consuming the error
214	pub fn frame(&self) -> Option<&crate::asn1::Frame> {
215		match self {
216			TransportError::MessageNotSent(frame, _) => Some(frame),
217			_ => None,
218		}
219	}
220
221	/// Extract TransportFailure from error if present, otherwise returns None
222	pub fn failure_reason(&self) -> Option<&TransportFailure> {
223		match self {
224			TransportError::MessageNotSent(_, reason) => Some(reason),
225			_ => None,
226		}
227	}
228
229	/// Check if error indicates connection is dead (for auto-reconnect)
230	///
231	/// Returns true for errors that suggest the underlying connection
232	/// should be discarded and a new connection established.
233	pub fn is_connection_error(&self) -> bool {
234		matches!(
235			self,
236			TransportError::ConnectionClosed
237				| TransportError::ConnectionFailed
238				| TransportError::OperationFailed(TransportFailure::Timeout)
239		) || {
240			#[cfg(feature = "std")]
241			{
242				matches!(self, TransportError::IoError(_))
243			}
244			#[cfg(not(feature = "std"))]
245			{
246				false
247			}
248		}
249	}
250}
251
252impl From<TransportFailure> for TransportError {
253	fn from(failure: TransportFailure) -> Self {
254		match failure {
255			TransportFailure::EncodingFailed => TransportError::InvalidMessage,
256			TransportFailure::SizeExceeded => TransportError::InvalidMessage,
257			TransportFailure::PolicyRejection => TransportError::InvalidReply,
258			TransportFailure::NonceGenerationFailed => TransportError::SendFailed,
259			// All other failures map to OperationFailed
260			other => TransportError::OperationFailed(other),
261		}
262	}
263}
264
265impl TransportFailure {
266	pub fn with_frame(self, frame: Frame) -> TransportError {
267		TransportError::from_failure(frame, self)
268	}
269
270	pub fn with_optional_frame(self, frame: Option<Frame>) -> TransportError {
271		if let Some(frame) = frame {
272			self.with_frame(frame)
273		} else {
274			self.into()
275		}
276	}
277}