Skip to main content

tightbeam/transport/
error.rs

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