cat_dev/fsemul/atapi/
mod.rs

1//! Servers, and protocols related to ATAPI emulation.
2//!
3//! ATAPI Emulation is the actual hdd emulation parts of the CAT-DEV.
4//! Unlike SDIO, ATAPI acts like EXI and initiates a connection from the CAT-DEV
5//! to a host machine.
6//!
7//! This protocol much like SDIO though, ***IS NOT STANDARD***. I'm sorry if
8//! you got a search request for ATAPI, and were looking for actual real ATAPI
9//! code. Not this weird nintendo variant.
10
11pub mod proto;
12mod reads;
13
14use crate::{
15	errors::{APIError, CatBridgeError, NetworkError},
16	fsemul::{
17		atapi::{proto::ChunkATAPIEmulatorCodec, reads::handle_read_dlf},
18		HostFilesystem,
19	},
20};
21use bytes::{BufMut, Bytes, BytesMut};
22use futures::{SinkExt, StreamExt};
23use local_ip_address::local_ip;
24use std::net::{IpAddr, Ipv4Addr, SocketAddrV4};
25use tokio::{
26	net::{TcpListener, TcpStream},
27	task::Builder as TaskBuilder,
28};
29use tokio_util::codec::Framed;
30use tracing::{debug, error, error_span, Instrument};
31
32/// The default port to use for hosting the ATAPI Server.
33pub const DEFAULT_ATAPI_PORT: u16 = 7974_u16;
34
35/// An ATAPI Server capable of acting like an HDD for a cat-dev.
36#[derive(Debug)]
37pub struct AtapiServer<'fs> {
38	/// The address we're actively bound and listening on.
39	bound_address: SocketAddrV4,
40	/// A pointer to our integration with the host filesystem.
41	///
42	/// This let's us read data from the cafe directory and otherwise.
43	host_filesystem: &'fs HostFilesystem,
44	/// The listener to serve traffic on.
45	server: TcpListener,
46}
47
48impl<'fs> AtapiServer<'fs> {
49	/// Create and bind a new ATAPI Server to a port on an address.
50	///
51	/// ## Errors
52	///
53	/// If we cannot bind to the request host ip address.
54	pub async fn new(
55		host_filesystem: &'fs HostFilesystem,
56		address: Option<Ipv4Addr>,
57		port: Option<u16>,
58	) -> Result<Self, CatBridgeError> {
59		let Some(ip) = address.or_else(|| {
60			// This always returns an ipv4 address, but is still returning
61			// an ip address for legacy reasons.
62			local_ip().ok().map(|ip| match ip {
63				IpAddr::V4(v4) => v4,
64				IpAddr::V6(_v6) => unreachable!(),
65			})
66		}) else {
67			return Err(APIError::NoHostIpFound.into());
68		};
69
70		let bound_address = SocketAddrV4::new(ip, port.unwrap_or(DEFAULT_ATAPI_PORT));
71		let server = TcpListener::bind(bound_address)
72			.await
73			.map_err(NetworkError::IO)?;
74
75		Ok(Self {
76			bound_address,
77			host_filesystem,
78			server,
79		})
80	}
81
82	/// Actually end up serving connections to clients.
83	///
84	/// This will continue serving for as long as it is able. Although it doesn't
85	/// do this as 'effeciently' because it cannot server multiple clients
86	/// concurrently itself.
87	///
88	/// In order to do this, we would need a `'static` lifetime'd host filesystem
89	/// which this method works without even `'static` filesystems. If you do
90	/// have a `'static` host filesystem, you should prefer to use the method
91	/// [`AtapiServer::serve_concurrently`] to more effeciently serve many
92	/// clients at once.
93	pub async fn serve(self) {
94		let host_filesystem = self.host_filesystem;
95
96		loop {
97			match self.server.accept().await {
98				Ok((stream, address)) => {
99					let client = address;
100					let bound = self.bound_address;
101
102					let result = Self::serve_connection(host_filesystem, stream)
103						.instrument(error_span!(
104						  "cat_dev::fsemul::atapi::serve_connection",
105						  server.address = %bound,
106						  client.address = %client,
107						))
108						.await;
109
110					if let Err(cause) = result {
111						error!(
112						  ?cause,
113						  server.address = %bound,
114						  client.address = %client,
115						  "Failed to actually handle packets from client connection",
116						);
117					}
118				}
119				Err(cause) => {
120					error!(
121					  ?cause,
122					  server.address = %self.bound_address,
123					  "Failed to accept ATAPI Connection from client, cannot serve itself.",
124					);
125				}
126			}
127		}
128	}
129
130	/// Serve a connection non-concurrently (E.g. when not dealing with a static
131	/// filesystem).
132	async fn serve_connection(
133		host_filesystem: &'fs HostFilesystem,
134		connection: TcpStream,
135	) -> Result<(), CatBridgeError> {
136		connection.set_nodelay(true).map_err(NetworkError::IO)?;
137		let (mut sink, mut stream) = Framed::new(connection, ChunkATAPIEmulatorCodec).split();
138
139		loop {
140			while let Some(result) = stream.next().await {
141				let packet = result.map_err(NetworkError::IO)?.freeze();
142
143				match &packet[..2] {
144					[0x3, _] => {
145						debug!("Would have sent 32 bytes of various descriptions back... not sure which...");
146					}
147					[0x12, _] => {
148						debug!("Would have sent 96 bytes of various descriptions back... not sure which...");
149					}
150					[0xCF, 0x80] => {
151						debug!("ATAPI Event packet sent!");
152					}
153					[0xF0, _] => {
154						debug!("ATAPI 0xF0 called");
155						sink.send(Bytes::from(vec![0x0; 4]))
156							.await
157							.map_err(NetworkError::IO)?;
158					}
159					[0xF1, 0x00 | 0x02] => {
160						debug!("ATAPI 0xF1, 0x0 | 0x02 random data called");
161						// I think this is just random data?
162						sink.send(Bytes::from(vec![0x69; 32]))
163							.await
164							.map_err(NetworkError::IO)?;
165					}
166					[0xF1, 0x01 | 0x03] => {
167						debug!("Unknown 0xF1 packet, doesn't do anything on the network...");
168					}
169					[0xF2, _] => {
170						debug!("Got unknown 0xF2 packet: [{packet:02X?}]");
171					}
172					[0xF3, 0x00] => {
173						handle_read_dlf(packet, host_filesystem, &mut sink).await?;
174					}
175					[0xF3, 0x01] => {
176						let mut data = BytesMut::with_capacity(32);
177						data.extend_from_slice(b"PC SATA EMUL");
178						data.extend_from_slice(&[0_u8; 20]);
179						sink.send(data.freeze()).await.map_err(NetworkError::IO)?;
180						debug!("Sent `PC SATA EMUL` header!");
181					}
182					[0xF3, 0x02 | 0x03] | [0xF5 | 0xF7, _] => {
183						sink.send(Bytes::from(vec![0x0; 32]))
184							.await
185							.map_err(NetworkError::IO)?;
186						debug!("Sent empty 32 bytes!");
187					}
188					[0xF6, _] => {
189						if packet[1] & 3 != 0 {
190							debug!("F6 second byte & 3 != 0, not sending reply!");
191						} else {
192							let mut data = BytesMut::with_capacity(4);
193							data.put_u32_le(1);
194							sink.send(data.freeze()).await.map_err(NetworkError::IO)?;
195							debug!("Sent F6 reply!");
196						}
197					}
198					_ => {
199						debug!("Blackhole-ing: [{packet:02X?}]");
200					}
201				}
202			}
203		}
204	}
205}
206
207impl AtapiServer<'static> {
208	/// Actually end up serving connections to clients.
209	///
210	/// This will continue serving for as long as it is able. This is the
211	/// significantly more efficient than just [`AtapiServer::serve`] as we can
212	/// confirm that host filesystem will ive long enough for all of our
213	/// connections.
214	pub async fn serve_concurrently(self) {
215		let host_filesystem: &'static HostFilesystem = self.host_filesystem;
216
217		loop {
218			match self.server.accept().await {
219				Ok((stream, address)) => {
220					let client = address;
221					let bound = self.bound_address;
222					let spawn_result = TaskBuilder::new()
223						.name("cat_dev::fsemul::atapi::serve_client_connection_concurrently")
224						.spawn(async move {
225							let result =
226								Self::serve_connection_concurrently(host_filesystem, stream)
227									.instrument(error_span!(
228									  "cat_dev::fsemul::atapi::serve_connection_concurrently",
229									  server.address = %bound,
230									  client.address = %client,
231									))
232									.await;
233
234							if let Err(cause) = result {
235								error!(
236								  ?cause,
237								  server.address = %bound,
238								  client.address = %client,
239								  "Failed to actually handle packets from client connection",
240								);
241							}
242						});
243
244					if let Err(cause) = spawn_result {
245						error!(
246						  ?cause,
247						  server.address = %self.bound_address,
248						  client.address = %address,
249						  "Failed to spawn handler for ATAPI Connection, cannot serve task.",
250						);
251					}
252				}
253				Err(cause) => {
254					error!(
255					  ?cause,
256					  server.address = %self.bound_address,
257					  "Failed to accept ATAPI Connection from client, cannot serve itself.",
258					);
259				}
260			}
261		}
262	}
263
264	async fn serve_connection_concurrently(
265		host_filesystem: &'static HostFilesystem,
266		connection: TcpStream,
267	) -> Result<(), CatBridgeError> {
268		connection.set_nodelay(true).map_err(NetworkError::IO)?;
269		let (mut sink, mut stream) = Framed::new(connection, ChunkATAPIEmulatorCodec).split();
270
271		loop {
272			while let Some(result) = stream.next().await {
273				let packet = result.map_err(NetworkError::IO)?.freeze();
274
275				match &packet[..2] {
276					[0x3, _] => {
277						debug!("Would have sent 32 bytes of various descriptions back... not sure which...");
278					}
279					[0x12, _] => {
280						debug!("Would have sent 96 bytes of various descriptions back... not sure which...");
281					}
282					[0xCF, 0x80] => {
283						debug!("ATAPI Event packet sent!");
284					}
285					[0xF0, _] => {
286						sink.send(Bytes::from(vec![0x0; 4]))
287							.await
288							.map_err(NetworkError::IO)?;
289					}
290					[0xF1, 0x00 | 0x02] => {
291						// I think this is just random data?
292						sink.send(Bytes::from(vec![0x69; 32]))
293							.await
294							.map_err(NetworkError::IO)?;
295					}
296					[0xF1, 0x01 | 0x03] => {
297						debug!("Unknown 0xF1 packet, doesn't do anything on the network...");
298					}
299					[0xF2, _] => {
300						debug!("Got unknown 0xF2 packet: [{packet:02X?}]");
301					}
302					[0xF3, 0x00] => {
303						handle_read_dlf(packet, host_filesystem, &mut sink).await?;
304					}
305					[0xF3, 0x01] => {
306						let mut data = BytesMut::with_capacity(32);
307						data.extend_from_slice(b"PC SATA EMUL");
308						data.extend_from_slice(&[0_u8; 20]);
309						sink.send(data.freeze()).await.map_err(NetworkError::IO)?;
310						debug!("Sent `PC SATA EMUL` header!");
311					}
312					[0xF3, 0x02 | 0x03] | [0xF5 | 0xF7, _] => {
313						sink.send(Bytes::from(vec![0x0; 32]))
314							.await
315							.map_err(NetworkError::IO)?;
316						debug!("Sent empty 32 bytes!");
317					}
318					[0xF6, _] => {
319						if packet[1] & 3 != 0 {
320							debug!("F6 second byte & 3 != 0, not sending reply!");
321						} else {
322							let mut data = BytesMut::with_capacity(4);
323							data.put_u32_le(1);
324							sink.send(data.freeze()).await.map_err(NetworkError::IO)?;
325							debug!("Sent F6 reply!");
326						}
327					}
328					_ => {}
329				}
330			}
331		}
332	}
333}