Skip to main content

cat_dev/fsemul/atapi/
server.rs

1//! The server implementation for handling ATAPI Emulation.
2
3use crate::{
4	errors::{APIError, CatBridgeError, FSError},
5	fsemul::{dlf::DiskLayoutFile, filesystem::host::HostFilesystem},
6	net::{
7		DEFAULT_CAT_DEV_CHUNK_SIZE, DEFAULT_CAT_DEV_SLOWDOWN,
8		additions::{RequestIDLayer, StreamIDLayer},
9		server::{
10			Router, TCPServer,
11			requestable::{Body, State},
12		},
13	},
14};
15use bytes::{BufMut, Bytes, BytesMut};
16use local_ip_address::local_ip;
17use std::{
18	net::{IpAddr, Ipv4Addr, SocketAddrV4},
19	time::Duration,
20};
21use tokio::{
22	fs::{File, read as fs_read},
23	io::{AsyncReadExt, AsyncSeekExt, SeekFrom},
24};
25use tower::ServiceBuilder;
26use tracing::{debug, warn};
27use valuable::{Fields, NamedField, NamedValues, StructDef, Structable, Valuable, Value, Visit};
28
29/// The default port to use for hosting the ATAPI Server.
30pub const DEFAULT_ATAPI_PORT: u16 = 7974_u16;
31
32/// A builder that is capable of building a brand new ATAPI server.
33///
34/// This is a small wrapper that tries to make setting the (many) amount of
35/// options easier. The shortest version you can do is call
36/// [`AtapiServerBuilder::new`], and [`AtapiServerBuilder::build`].
37#[derive(Clone, Debug)]
38pub struct AtapiServerBuilder {
39	/// The explicit bind address if we don't want to use the local address.
40	address: Option<Ipv4Addr>,
41	/// Allow overriding the amount of time that we wait to make sure everything
42	/// is safe for a cat-dev.
43	///
44	/// *note: `None` does not mean cat-dev sleep is disabled, that is controleld
45	/// through [`Self::fully_disable_cat_dev_sleep`]*.
46	cat_dev_sleep_override: Option<Duration>,
47	/// Override the chunking value, and how many bytes we'll purposefully chunk on
48	/// when sending out TCP data.
49	///
50	/// *note: `None` does not mean cat-dev sleep is disabled, that is controleld
51	/// through [`Self::fully_disable_chunk_override`]*.
52	chunk_override: Option<usize>,
53	/// An override to fully disable any cat-dev sleeping to prevent issues.
54	fully_disable_cat_dev_sleep: bool,
55	/// An override to fully disable any TCP level chunking.
56	fully_disable_chunk_override: bool,
57	/// The host filesystem to treat when serving ATAPI requests.
58	host_filesystem: HostFilesystem,
59	/// The explicit port to bind on, when not using default.
60	port: Option<u16>,
61	/// If we should trace when in debug mode.
62	trace_during_debug: bool,
63}
64
65impl AtapiServerBuilder {
66	/// Create a new ATAPI Server builder.
67	#[must_use]
68	pub const fn new(host_filesystem: HostFilesystem) -> Self {
69		Self {
70			address: None,
71			cat_dev_sleep_override: None,
72			chunk_override: None,
73			fully_disable_cat_dev_sleep: false,
74			fully_disable_chunk_override: false,
75			host_filesystem,
76			port: None,
77			trace_during_debug: false,
78		}
79	}
80
81	#[must_use]
82	pub const fn address(&self) -> Option<Ipv4Addr> {
83		self.address
84	}
85	#[must_use]
86	pub const fn set_address(mut self, new_address: Option<Ipv4Addr>) -> Self {
87		self.address = new_address;
88		self
89	}
90
91	#[must_use]
92	pub const fn cat_dev_sleep_override(&self) -> Option<Duration> {
93		self.cat_dev_sleep_override
94	}
95	#[must_use]
96	pub const fn set_cat_dev_sleep_override(mut self, new_override: Option<Duration>) -> Self {
97		self.cat_dev_sleep_override = new_override;
98		self
99	}
100
101	#[must_use]
102	pub const fn chunk_override(&self) -> Option<usize> {
103		self.chunk_override
104	}
105	#[must_use]
106	pub const fn set_chunk_override(mut self, new_override: Option<usize>) -> Self {
107		self.chunk_override = new_override;
108		self
109	}
110
111	#[must_use]
112	pub const fn fully_disable_cat_dev_sleep(&self) -> bool {
113		self.fully_disable_cat_dev_sleep
114	}
115	#[must_use]
116	pub const fn set_fully_disable_cat_dev_sleep(mut self, new_value: bool) -> Self {
117		self.fully_disable_cat_dev_sleep = new_value;
118		self
119	}
120
121	#[must_use]
122	pub const fn fully_disable_chunking(&self) -> bool {
123		self.fully_disable_chunk_override
124	}
125	#[must_use]
126	pub const fn set_fully_disable_chunking(mut self, disable_chunk: bool) -> Self {
127		self.fully_disable_chunk_override = disable_chunk;
128		self
129	}
130
131	#[must_use]
132	pub const fn host_filesystem(&self) -> &HostFilesystem {
133		&self.host_filesystem
134	}
135	#[must_use]
136	pub fn set_host_filesystem(mut self, new: HostFilesystem) -> Self {
137		self.host_filesystem = new;
138		self
139	}
140
141	#[must_use]
142	pub const fn port(&self) -> Option<u16> {
143		self.port
144	}
145	#[must_use]
146	pub const fn set_port(mut self, new: Option<u16>) -> Self {
147		self.port = new;
148		self
149	}
150
151	#[must_use]
152	pub const fn trace_during_debug(&self) -> bool {
153		self.trace_during_debug
154	}
155	#[must_use]
156	pub const fn set_trace_during_debug(mut self, trace: bool) -> Self {
157		self.trace_during_debug = trace;
158		self
159	}
160
161	/// Build this server, and get a TCP server that you can bind and use.
162	///
163	/// ## Errors
164	///
165	/// If we cannot find a host ip to bind too, or cannot spin up the workers for
166	/// the server.
167	pub async fn build(self) -> Result<TCPServer<HostFilesystem>, CatBridgeError> {
168		let ip = self
169			.address
170			.or_else(|| {
171				local_ip().ok().map(|ip| match ip {
172					IpAddr::V4(v4) => v4,
173					IpAddr::V6(_v6) => unreachable!(),
174				})
175			})
176			.ok_or(APIError::NoHostIpFound)?;
177		let bound_address = SocketAddrV4::new(ip, self.port.unwrap_or(DEFAULT_ATAPI_PORT));
178
179		let mut router = Router::<HostFilesystem>::new();
180		router.fallback_handler(temporary_fallback_handle_all)?;
181
182		let mut server = TCPServer::new_with_state(
183			"atapi",
184			bound_address,
185			router,
186			(None, None),
187			12,
188			self.host_filesystem,
189			self.trace_during_debug,
190		)
191		.await?;
192		if self.trace_during_debug {
193			server.layer_initial_service(
194				ServiceBuilder::new()
195					.layer(RequestIDLayer::new("atapi".to_owned()))
196					.layer(StreamIDLayer),
197			);
198		} else {
199			server.layer_initial_service(
200				ServiceBuilder::new().layer(RequestIDLayer::new("atapi".to_owned())),
201			);
202		}
203		server.set_chunk_output_at_size(if self.fully_disable_chunk_override {
204			None
205		} else if let Some(over_ride) = self.chunk_override {
206			Some(over_ride)
207		} else {
208			Some(DEFAULT_CAT_DEV_CHUNK_SIZE)
209		});
210		server.set_cat_dev_slowdown(if self.fully_disable_cat_dev_sleep {
211			None
212		} else if let Some(over_ride) = self.cat_dev_sleep_override {
213			Some(over_ride)
214		} else {
215			Some(DEFAULT_CAT_DEV_SLOWDOWN)
216		});
217
218		Ok(server)
219	}
220}
221
222const ATAPI_SERVER_BUILDER_FIELDS: &[NamedField<'static>] = &[
223	NamedField::new("address"),
224	NamedField::new("cat_dev_sleep_override"),
225	NamedField::new("chunk_override"),
226	NamedField::new("fully_disable_cat_dev_sleep"),
227	NamedField::new("fully_disable_chunk_override"),
228	NamedField::new("host_filesystem"),
229	NamedField::new("port"),
230	NamedField::new("trace_during_debug"),
231];
232
233impl Structable for AtapiServerBuilder {
234	fn definition(&self) -> StructDef<'_> {
235		StructDef::new_static(
236			"AtapiServerBuilder",
237			Fields::Named(ATAPI_SERVER_BUILDER_FIELDS),
238		)
239	}
240}
241
242impl Valuable for AtapiServerBuilder {
243	fn as_value(&self) -> Value<'_> {
244		Value::Structable(self)
245	}
246
247	fn visit(&self, visitor: &mut dyn Visit) {
248		visitor.visit_named_fields(&NamedValues::new(
249			ATAPI_SERVER_BUILDER_FIELDS,
250			&[
251				Valuable::as_value(
252					&self
253						.address
254						.map_or_else(|| "<none>".to_owned(), |ip| format!("{ip}")),
255				),
256				Valuable::as_value(
257					&self
258						.cat_dev_sleep_override
259						.map_or_else(|| "<none>".to_owned(), |dur| format!("{}s", dur.as_secs())),
260				),
261				Valuable::as_value(&self.chunk_override),
262				Valuable::as_value(&self.fully_disable_cat_dev_sleep),
263				Valuable::as_value(&self.fully_disable_chunk_override),
264				Valuable::as_value(&self.host_filesystem),
265				Valuable::as_value(&self.port),
266				Valuable::as_value(&self.trace_during_debug),
267			],
268		));
269	}
270}
271
272async fn temporary_fallback_handle_all(
273	State(fs): State<HostFilesystem>,
274	Body(packet): Body<Bytes>,
275) -> Result<Option<Bytes>, CatBridgeError> {
276	match &packet[..2] {
277		[0x3, _] => {
278			debug!("Would have sent 32 bytes of various descriptions back... not sure which...");
279		}
280		[0xCF, 0x80] => {
281			debug!("ATAPI Event packet sent");
282		}
283		[0xF0, _] => {
284			return Ok(Some(Bytes::from(vec![0x0; 4])));
285		}
286		[0xF1, 0x00 | 0x02] => {
287			// I think this is just random data?
288			return Ok(Some(Bytes::from(vec![0x69; 32])));
289		}
290		[0xF1, 0x01 | 0x03] => {
291			debug!("Unknown 0xF1 packet, doesn't do anything on the network...");
292		}
293		[0xF2, 0x01 | 0x07] => {
294			// Never seem to respond? probably seems to be some sort of "state change"
295			debug!("ATAPI 0xF201/ 0xF207 packet received?");
296			return Ok(None);
297		}
298		[0xF2, _] => {
299			warn!("Got unknown 0xF2 packet: [{packet:02X?}]");
300		}
301		[0xF3, 0x00] => {
302			return handle_read_dlf(packet, &fs).await;
303		}
304		[0xF3, 0x01] => {
305			let mut data = BytesMut::with_capacity(32);
306			data.extend_from_slice(b"PC SATA EMUL");
307			data.extend_from_slice(&[0_u8; 20]);
308			return Ok(Some(data.freeze()));
309		}
310		[0xF3, 0x02 | 0x03] | [0xF5 | 0xF7, _] => {
311			debug!("Sending empty 32 bytes!");
312			return Ok(Some(Bytes::from(vec![0x0; 32])));
313		}
314		[0xF6, _] => {
315			if packet[1] & 3 != 0 {
316				debug!("F6 second byte & 3 != 0, not sending reply!");
317			} else {
318				let mut data = BytesMut::with_capacity(4);
319				data.put_u32_le(1);
320				debug!("Sent F6 reply!");
321				return Ok(Some(data.freeze()));
322			}
323		}
324		[0x12, _] => {
325			debug!("Would have sent 96 bytes of various descriptions back... not sure which...");
326		}
327		_ => {
328			warn!("Got unknown ATAPI Packet: [{packet:02x?}]");
329		}
330	}
331
332	Ok(None)
333}
334
335async fn handle_read_dlf(
336	packet: Bytes,
337	host_filesystem: &HostFilesystem,
338) -> Result<Option<Bytes>, CatBridgeError> {
339	let read_address = u128::from(u32::from_be_bytes([
340		packet[0x4],
341		packet[0x5],
342		packet[0x6],
343		packet[0x7],
344	])) << 11_u128;
345	let read_length = u128::from(u32::from_be_bytes([
346		packet[0x8],
347		packet[0x9],
348		packet[0xA],
349		packet[0xB],
350	])) << 11_u128;
351	let rl_as_usize =
352		usize::try_from(read_length).map_err(|_| CatBridgeError::UnsupportedBitsPerCore)?;
353
354	debug!(
355		atapi.packet_type = "read_address",
356		atapi.read_address.address = %read_address,
357		atapi.read_address.length = %read_length,
358		"Handling atapi read request!"
359	);
360
361	let bytes_of_dlf = fs_read(host_filesystem.ppc_boot_dlf_path().await?)
362		.await
363		.map_err(FSError::from)?;
364	let dlf = DiskLayoutFile::try_from(Bytes::from(bytes_of_dlf))?;
365
366	if let Some((path, offset)) = dlf.get_path_and_offset_for_file(read_address).await {
367		// Read the file contents...
368		let buff = {
369			let mut handle = File::open(&path).await.map_err(FSError::from)?;
370			handle
371				.seek(SeekFrom::Start(offset))
372				.await
373				.map_err(FSError::from)?;
374
375			let mut file_buff = BytesMut::zeroed(rl_as_usize);
376			let mut bytes_read = 0;
377			while bytes_read < rl_as_usize {
378				let read_this_go = handle
379					.read(&mut file_buff[bytes_read..])
380					.await
381					.map_err(FSError::IO)?;
382				// EOF, rest of the buff is already 0's, so no need to pad.
383				if read_this_go == 0 {
384					break;
385				}
386				bytes_read += read_this_go;
387			}
388
389			file_buff
390		};
391
392		// Send!
393		Ok(Some(buff.freeze()))
394	} else {
395		Ok(Some(BytesMut::zeroed(rl_as_usize).freeze()))
396	}
397}
398
399// KNOWN PACKET HEADERS
400//
401//  - [0x3]
402//    - send 32 bytes of various describes, not quite sure
403//  - [0x12]
404//    - sends 96 bytes, seems to have some random spattering of fields
405//  - [0xCF, 0x80] -> Triggers Events in FSEmul, probably just call "EVentTrigger"
406//  - [0xF0] -> send back 4, 0x0 bytes
407//  - [0xF1]
408//    - [0xF1, 0x00]
409//      - seems to literally send 32 bytes of random data.... cool
410//    - [0xF1, 0x02]
411//      - seems to literally send 32 bytes of random data.... cool
412//    - [0xF1, 0x01] || [0xF1, 0x03]
413//      - seems to do nothing on hthe network
414//  - [0xF2]
415//    - [0xF2, 0x00]
416//    - [0xF2, 0x01]
417//    - [0xF2, 0x02]
418//    - [0xF2, 0x03]
419//      -> ??? calls a dynamically allocated thing
420//    - [0xF2, 0x06]
421//    - [0xF2, 0x07]
422//      -> ??? calls a dynamically allocated thing
423//      -> for f207 looks like we don't send _anything_ back by default
424//      -> seems some paths check for Dvdroot, so maybe dvd stuff?
425//  - [0xF3]
426//    - [0xF3, 0x0] -> seems to actually be real "read file"
427//      - not quite sure exactly how to parse this yet, but these are examples:
428//        - first 4 bytes are "packet id"
429//        - second 4 bytes are "read address" (calculate by: cast to u128 `<< 11`)
430//        - last 4 bytes are "read length" (calculate by: cast to u128 `<< 11`)
431//      - logs seem to indicate we:
432//         1. read a dlf file (dlf file is populated in cafe-tmp how get?)
433//         2. use that to get a max read address
434//         3. read from the file
435//         4. then pad
436//        see logs below:
437//          - `CSataProcessor::could not get lead out from dlffileobj {error code}`
438//          - `CSataProcessor::requested read address 0x%I64x is out of bounds.`
439//          - `CSataProcessor::could not read from file`
440//          - `CSataProcessor::padding`
441//          - `CSataProcessor::error writing to MION port`
442//          - `CSataProcessor::wrote %d bytes`
443//    - [0xF3, 0x1] -> send back "PC SATA EMUL" + 20 0's
444//    - [0xF3, 0x2] || [0xF3, 0x3] -> send back 32 0's - seems this is always set to 0. maybe a kind of ping?
445//  - [0xF5]
446//    - send 32 0's
447//  - [0xF6]
448//    - second byte &3 != 0 -> doesn't send reply
449//    - if not send what looks to be `1` encoded as 4 bytes
450//  - [0xF7]
451//    - send 32 0's