Skip to main content

cat_dev/
errors.rs

1//! A container for all the types of errors generated crate-wide.
2//!
3//! The top level error type is: [`CatBridgeError`], which wraps all the other
4//! types of errors. You can find more specific error types documented on each
5//! specific item.
6
7use crate::{
8	fsemul::errors::{FSEmulAPIError, FSEmulFSError, FSEmulNetworkError, FSEmulProtocolError},
9	mion::errors::{MionAPIError, MionProtocolError},
10};
11use bytes::Bytes;
12use miette::{Diagnostic, Report};
13use std::{
14	ffi::FromBytesUntilNulError, num::ParseIntError, str::Utf8Error, string::FromUtf8Error,
15	time::Duration,
16};
17use thiserror::Error;
18use tokio::{io::Error as IoError, task::JoinError};
19use walkdir::Error as WalkdirError;
20
21#[cfg(feature = "clients")]
22use crate::net::client::errors::CommonNetClientNetworkError;
23#[cfg(feature = "clients")]
24use local_ip_address::Error as LocalIpAddressError;
25#[cfg(feature = "clients")]
26use network_interface::Error as NetworkInterfaceError;
27
28#[cfg(any(feature = "clients", feature = "nus"))]
29use reqwest::{Error as ReqwestError, StatusCode};
30
31#[cfg(feature = "nus")]
32use sachet::{errors::SachetError, title::TitleID};
33
34#[cfg(any(feature = "clients", feature = "servers"))]
35use crate::net::errors::{CommonNetAPIError, CommonNetNetworkError};
36
37#[cfg(feature = "servers")]
38use crate::net::server::models::ResponseStreamMessage;
39#[cfg(feature = "servers")]
40use tokio::sync::mpsc::error::SendError;
41
42/// The 'top-level' error type for this entire crate, all error types
43/// wrap underneath this.
44#[derive(Error, Diagnostic, Debug)]
45pub enum CatBridgeError {
46	/// See [`APIError`] for details.
47	#[error(transparent)]
48	#[diagnostic(transparent)]
49	API(#[from] APIError),
50	/// We tried sending a message from one thread to another (within the same
51	/// process), but delivery could not be completed.
52	///
53	/// For more information on why this could fail please look at the associated
54	/// modules we may be using:
55	///
56	/// - [`std::sync::mpsc`]
57	/// - [`tokio::sync::mpsc`]
58	///
59	/// Each of these contain more information.
60	#[error(
61		"We could not send a message locally to another part of the process. This channel must've been closed unexpectedly."
62	)]
63	#[diagnostic(code(cat_dev::closed_channel))]
64	ClosedChannel,
65	/// See [`FSError`] for details.
66	#[error(transparent)]
67	#[diagnostic(transparent)]
68	FS(#[from] FSError),
69	/// We spawned a background task, and for whatever reason we could not
70	/// wait for it to finish.
71	///
72	/// For the potential reasons for this, take a peek at [`tokio`]'s
73	/// documentation. Which is our asynchronous runtime.
74	#[error("We could not await an asynchronous task we spawned: {0:?}")]
75	#[diagnostic(code(cat_dev::join_failure))]
76	JoinFailure(#[from] JoinError),
77	/// See [`NetworkError`] for details.
78	#[error(transparent)]
79	#[diagnostic(transparent)]
80	Network(#[from] NetworkError),
81	/// We tried to spawn a task to run in the background, but couldn't.
82	///
83	/// For the potential reasons for this, take a peek at [`tokio`]'s
84	/// documentation. Which is our asynchronous runtime.
85	#[error("We could not spawn a task (a lightweight thread) to do work on.")]
86	#[diagnostic(code(cat_dev::spawn_failure))]
87	SpawnFailure(IoError),
88	/// An unknown error occured.
89	#[error("An unknown error we couldn't pin down occured: {0:?}")]
90	#[diagnostic(code(cat_dev::unknown))]
91	UnknownError(Report),
92	#[error(
93		"This cat-dev API requires a 32 bit usize, and this machine does not have it, please upgrade your machine."
94	)]
95	#[diagnostic(code(cat_dev::unsupported_bits_per_core))]
96	UnsupportedBitsPerCore,
97}
98
99/// An error that comes from one of our APIs, e.g. passing in a parameter
100/// that wasn't expected.
101///
102/// All the APIs within this crate will have errors will be collapsed under
103/// this particular error type. There will be no inner separation between
104/// modules.
105#[non_exhaustive]
106#[derive(Error, Diagnostic, Debug)]
107pub enum APIError {
108	/// Common network related API errors.
109	#[cfg_attr(docsrs, doc(cfg(any(feature = "clients", feature = "servers"))))]
110	#[cfg(any(feature = "clients", feature = "servers"))]
111	#[error(transparent)]
112	#[diagnostic(transparent)]
113	CommonNet(#[from] CommonNetAPIError),
114	#[error(transparent)]
115	#[diagnostic(transparent)]
116	FSEmul(#[from] FSEmulAPIError),
117	#[error(transparent)]
118	#[diagnostic(transparent)]
119	Mion(#[from] MionAPIError),
120	/// Failed to parse a number.
121	#[error("Could not parse a number that you handed in as a string: {0:?}")]
122	#[diagnostic(code(cat_dev::api::cannot_parse_number))]
123	CannotParseNumber(#[from] ParseIntError),
124	/// We failed to find our own hosts local IP address.
125	///
126	/// This usually means we don't have a network interface we can communicate
127	/// on that has an IPv4 address assigned.
128	#[error(
129		"We could not find the local hosts ipv4 address which is needed if an ip isn't explicitly passed in."
130	)]
131	#[diagnostic(code(cat_dev::api::no_host_ip_found))]
132	NoHostIpFound,
133}
134
135#[cfg_attr(docsrs, doc(cfg(any(feature = "clients", feature = "servers"))))]
136#[cfg(any(feature = "clients", feature = "servers"))]
137impl From<CommonNetAPIError> for CatBridgeError {
138	fn from(value: CommonNetAPIError) -> Self {
139		Self::from(APIError::CommonNet(value))
140	}
141}
142
143/// Trying to interact with the filesystem has resulted in an error.
144#[derive(Error, Diagnostic, Debug)]
145pub enum FSError {
146	/// We need a place to read/store a list of all the bridges on your host.
147	///
148	/// However, if you see this we weren't able to automatically determine where
149	/// that file should go. Please either contribute a path for your OS to use,
150	/// or manually provide the host bridge path (this can only be done on the
151	/// newer versions of tools).
152	#[error(
153		"We can't find the path to store a complete list of host-bridges, please use explicit paths instead."
154	)]
155	#[diagnostic(code(cat_dev::fs::cant_find_hostenv_path))]
156	CantFindHostEnvPath,
157	#[error(transparent)]
158	#[diagnostic(transparent)]
159	FSEmul(#[from] FSEmulFSError),
160	/// We expected to parse file as an INI data, but it did not contain valid
161	/// INI data.
162	#[error("Data read from the filesystem was expected to be a valid INI file: {0}")]
163	#[diagnostic(code(cat_dev::fs::expected_ini))]
164	InvalidDataNeedsToBeINI(String),
165	/// File "magic" are generally constants that should always be true.
166	#[error("Expected file magic of: {0}, got {1} as magic bytes")]
167	#[diagnostic(code(cat_dev::fs::invalid_file_magic))]
168	InvalidFileMagic(u32, u32),
169	/// Expected a file sized a specific amount of bytes, and it wasn't.
170	#[error("Expected file size of: {0} bytes, got a file sized {1} bytes")]
171	#[diagnostic(code(cat_dev::fs::invalid_file_size))]
172	InvalidFileSize(usize, usize),
173	/// See [`tokio::io::Error`] for details.
174	#[error("Error writing/reading data from the filesystem: {0}")]
175	#[diagnostic(code(cat_dev::fs::io))]
176	IO(#[from] IoError),
177	#[error("Error iterating through folder: {0:?}")]
178	#[diagnostic(code(cat_dev::fs::iterating_folder_error))]
179	IteratingFolderError(#[from] WalkdirError),
180	#[error("Expect file to have at least: {0} line(s), but it was only: {1} line(s) long.")]
181	#[diagnostic(code(cat_dev::fs::too_few_lines))]
182	TooFewLines(usize, usize),
183	/// The file can't be larger than a certain amount of bytes, and it was.
184	#[error("File cannot be larger than: {0} bytes, is {1} bytes")]
185	#[diagnostic(code(cat_dev::fs::too_large))]
186	TooLarge(usize, usize),
187	/// The file needs to be a certain amount of bytes, and it wasn't.
188	#[error("File needs to be at least: {0} bytes, is {1} bytes")]
189	#[diagnostic(code(cat_dev::fs::too_small))]
190	TooSmall(usize, usize),
191	/// We expected to read UTF-8 data from the filesystem, but it wasn't UTF-8.
192	#[error("Data read from the filesystem was expected to be UTF-8, but was not: {0}")]
193	#[diagnostic(code(cat_dev::fs::utf8_expected))]
194	Utf8Expected(#[from] FromUtf8Error),
195}
196
197/// Trying to interact with the network has resulted in an error.
198///
199/// *NOTE: this does not cover bogus data coming in from the network. This only
200/// covers errors related to interacting with the network. If you're looking
201/// for bogus data from the network errors look at [`NetworkParseError`].*
202#[derive(Error, Diagnostic, Debug)]
203#[non_exhaustive]
204pub enum NetworkError {
205	/// We failed to bind to a local address to listen for packets from the
206	/// network.
207	///
208	/// This can happen for numerous reason, such as:
209	///
210	/// - The program does not have permission to listen on this specific port.
211	/// - The address is already being used by another process.
212	/// - The network interface returned some type of error.
213	///
214	/// There are multiple other cases, but in general they're pretty OS
215	/// specific.
216	#[error("Failed to bind to a local address to receive packets.")]
217	#[diagnostic(code(cat_dev::net::bind_failure))]
218	BindFailure,
219	#[cfg_attr(docsrs, doc(cfg(feature = "clients")))]
220	#[cfg(feature = "clients")]
221	#[error(transparent)]
222	#[diagnostic(transparent)]
223	CommonClient(#[from] CommonNetClientNetworkError),
224	/// An error has occurred in our common network framework.
225	#[cfg_attr(docsrs, doc(cfg(any(feature = "clients", feature = "servers"))))]
226	#[cfg(any(feature = "clients", feature = "servers"))]
227	#[error(transparent)]
228	#[diagnostic(transparent)]
229	CommonNet(#[from] CommonNetNetworkError),
230	#[error("Expected some sort of data from other side, but got none.")]
231	#[diagnostic(code(cat_dev::net::expected_data))]
232	ExpectedData,
233	#[error(transparent)]
234	#[diagnostic(transparent)]
235	FSEmul(#[from] FSEmulNetworkError),
236	#[cfg_attr(docsrs, doc(cfg(any(feature = "clients", feature = "nus"))))]
237	#[cfg(any(feature = "clients", feature = "nus"))]
238	/// See [`reqwest::Error`] for details.
239	#[error("Underlying HTTP client error: {0}")]
240	#[diagnostic(code(cat_dev::net::http_failure))]
241	HTTP(#[from] ReqwestError),
242	#[cfg_attr(docsrs, doc(cfg(any(feature = "clients", feature = "nus"))))]
243	#[cfg(any(feature = "clients", feature = "nus"))]
244	/// An HTTP request failed with a bad status code.
245	#[error("Bad status code from HTTP Server: {0}, Body: {1:?}")]
246	#[diagnostic(code(cat_dev::net::http_status_failure))]
247	HTTPStatusCode(StatusCode, Option<Bytes>),
248	/// See [`tokio::io::Error`] for details.
249	#[error("Error talking to the network could not send/receive data: {0}")]
250	#[diagnostic(code(cat_dev::net::io_error))]
251	IO(#[from] IoError),
252	#[cfg_attr(docsrs, doc(cfg(feature = "clients")))]
253	#[cfg(feature = "clients")]
254	/// See [`network_interface::Error::GetIfAddrsError`] for details.
255	#[error("Failed to list the network interfaces on your device: {0:?}.")]
256	#[diagnostic(code(cat_dev::net::list_interfaces_error))]
257	ListInterfacesFailure(NetworkInterfaceError),
258	#[cfg_attr(docsrs, doc(cfg(feature = "clients")))]
259	#[cfg(feature = "clients")]
260	/// See [`local_ip_address::Error`] for details.
261	#[error("Failure fetching local ip address: {0}")]
262	#[diagnostic(code(cat_dev::net::local_ip_failure))]
263	LocalIp(#[from] LocalIpAddressError),
264	#[cfg_attr(docsrs, doc(cfg(feature = "nus")))]
265	#[cfg(feature = "nus")]
266	#[error(transparent)]
267	#[diagnostic(code(cat_dev::nus))]
268	NUS(#[from] SachetError),
269	#[cfg_attr(docsrs, doc(cfg(feature = "nus")))]
270	#[cfg(feature = "nus")]
271	#[error("Title ID: {0:?} did not have FST table inside of it!")]
272	#[diagnostic(code(cat_dev::nus::missing_fst))]
273	NUSMissingFST(TitleID),
274	#[cfg_attr(docsrs, doc(cfg(feature = "nus")))]
275	#[cfg(feature = "nus")]
276	#[error("Title ID: {0:?} did not have a content id at: {1:04x}!")]
277	#[diagnostic(code(cat_dev::nus::missing_title_key))]
278	NUSInvalidContentID(TitleID, u16),
279	#[cfg_attr(docsrs, doc(cfg(feature = "nus")))]
280	#[cfg(feature = "nus")]
281	#[error("Title ID: {0:?} did not have any valid title keys!")]
282	#[diagnostic(code(cat_dev::nus::missing_title_key))]
283	NUSNoTitleKey(TitleID),
284	/// See [`NetworkParseError`] for details.
285	#[error(transparent)]
286	#[diagnostic(transparent)]
287	Parse(#[from] NetworkParseError),
288	/// If we failed to call `setsockopt` through libc.
289	///
290	/// For example if on linux see: <https://linux.die.net/man/2/setsockopt>
291	#[error(
292		"Failed to set the socket we're bound on as a broadcast address, this is needed to discover CAT devices."
293	)]
294	#[diagnostic(code(cat_dev::net::set_broadcast_failure))]
295	SetBroadcastFailure,
296	/// Error adding a packet to a queue to send.
297	#[cfg_attr(docsrs, doc(cfg(feature = "servers")))]
298	#[cfg(feature = "servers")]
299	#[error("Error queueing up packet to be sent out over a conenction: {0:?}")]
300	#[diagnostic(code(cat_dev::net::send_queue_failure))]
301	SendQueueMessageFailure(#[from] SendError<ResponseStreamMessage>),
302	/// We waited too long to send/receive data from the network.
303	///
304	/// There may be something wrong with our network connection, or the targets
305	/// network connection.
306	#[error(
307		"Timed out while writing/reading data from the network, failed to send and receive data."
308	)]
309	#[diagnostic(code(cat_dev::net::timeout))]
310	Timeout(Duration),
311}
312
313#[cfg_attr(docsrs, doc(cfg(feature = "clients")))]
314#[cfg(feature = "clients")]
315impl From<CommonNetClientNetworkError> for CatBridgeError {
316	fn from(value: CommonNetClientNetworkError) -> Self {
317		Self::Network(value.into())
318	}
319}
320
321#[cfg_attr(docsrs, doc(cfg(any(feature = "clients", feature = "servers"))))]
322#[cfg(any(feature = "clients", feature = "servers"))]
323impl From<CommonNetNetworkError> for CatBridgeError {
324	fn from(value: CommonNetNetworkError) -> Self {
325		Self::Network(value.into())
326	}
327}
328
329#[cfg_attr(docsrs, doc(cfg(any(feature = "clients", feature = "nus"))))]
330#[cfg(any(feature = "clients", feature = "nus"))]
331impl From<ReqwestError> for CatBridgeError {
332	fn from(value: ReqwestError) -> Self {
333		Self::Network(value.into())
334	}
335}
336
337/// We tried parsing some data from the network, but failed to do so, someone
338/// sent us some junk.
339#[derive(Error, Diagnostic, Debug, PartialEq, Eq)]
340pub enum NetworkParseError {
341	/// Failed reading C String with NUL bytes at the end.
342	#[error("Failed reading c style string from packet: {0:?}")]
343	#[diagnostic(code(cat_dev::net::parse::bad_c_string))]
344	BadCString(#[from] FromBytesUntilNulError),
345	/// We expected to read a packet containing exactly a set of bytes,
346	/// unfortunatley it did not contain those _Exact_ bytes.
347	#[error(
348		"Tried to read Packet of type ({0}) from network, must be encoded exactly as [{1:02x?}], but got [{2:02x?}]"
349	)]
350	#[diagnostic(code(cat_dev::net::parse::doesnt_match_static_data))]
351	DoesntMatchStaticPayload(&'static str, &'static [u8], Bytes),
352	#[error("Internal Protocol responded with an error code: {0}")]
353	#[diagnostic(code(cat_dev::net::parse::error_code))]
354	ErrorCode(u32),
355	/// A field encoded within a packet was not correct (e.g. a string wasn't
356	/// UTF-8).
357	#[error("Reading Field {1} from Packet {0}, was not encoded correctly must be encoded as {2}")]
358	#[diagnostic(code(cat_dev::net::parse::field_encoded_incorrectly))]
359	FieldEncodedIncorrectly(&'static str, &'static str, &'static str),
360	/// A field encoded within a packet requires a minimum number of bytes, but
361	/// the field was not long enough.
362	#[error(
363		"Tried Reading Field {1} from Packet {0}. This Field requires at least {2} bytes, but only had {3}, bytes: {4:02x?}"
364	)]
365	#[diagnostic(code(cat_dev::net::parse::field_not_long_enough))]
366	FieldNotLongEnough(&'static str, &'static str, usize, usize, Bytes),
367	#[error(transparent)]
368	#[diagnostic(transparent)]
369	FSEmul(#[from] FSEmulProtocolError),
370	/// Errors related to parsing MION specific protocols.
371	#[error(transparent)]
372	#[diagnostic(transparent)]
373	Mion(#[from] MionProtocolError),
374	/// The overall size of the packet was too short, and we cannot successfully
375	/// parse it.
376	#[error(
377		"Tried to read Packet of type ({0}) from network needs at least {1} bytes, but only got {2} bytes: {3:02x?}"
378	)]
379	#[diagnostic(code(cat_dev::net::parse::not_enough_data))]
380	NotEnoughData(&'static str, usize, usize, Bytes),
381	/// The overall size of the packet was too long, and there was unexpected
382	/// data at the end, a.k.a. the "Trailer".
383	#[error(
384		"Unexpected Trailer for Packet `{0}` received from the network (we're not sure what do with this extra data), extra bytes: {1:02x?}"
385	)]
386	#[diagnostic(code(cat_dev::net::parse::unexpected_trailer))]
387	UnexpectedTrailer(&'static str, Bytes),
388	/// We expected to read UTF-8 data from the network, but it wasn't UTF-8.
389	#[error("Data read from the network was expected to be UTF-8, but was not: {0}")]
390	#[diagnostic(code(cat_dev::net::parse::utf8_expected))]
391	Utf8Expected(#[from] FromUtf8Error),
392	/// We expected to read UTF-8 data from the network, but it wasn't UTF-8.
393	#[error("Data read from a network slice was expected to be UTF-8, but was not: {0}")]
394	#[diagnostic(code(cat_dev::net::parse::utf8_expected_slice))]
395	Utf8ExpectedSlice(#[from] Utf8Error),
396}
397impl From<NetworkParseError> for CatBridgeError {
398	fn from(value: NetworkParseError) -> Self {
399		Self::Network(value.into())
400	}
401}