cat_dev/fsemul/atapi/
server.rs

1//! The server implementation for handling ATAPI Emulation.
2
3use crate::{
4	errors::{APIError, CatBridgeError, FSError},
5	fsemul::{HostFilesystem, dlf::DiskLayoutFile},
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;
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, _] => {
294			debug!("Got unknown 0xF2 packet: [{packet:02X?}]");
295		}
296		[0xF3, 0x00] => {
297			return handle_read_dlf(packet, &fs).await;
298		}
299		[0xF3, 0x01] => {
300			let mut data = BytesMut::with_capacity(32);
301			data.extend_from_slice(b"PC SATA EMUL");
302			data.extend_from_slice(&[0_u8; 20]);
303			return Ok(Some(data.freeze()));
304		}
305		[0xF3, 0x02 | 0x03] | [0xF5 | 0xF7, _] => {
306			debug!("Sending empty 32 bytes!");
307			return Ok(Some(Bytes::from(vec![0x0; 32])));
308		}
309		[0xF6, _] => {
310			if packet[1] & 3 != 0 {
311				debug!("F6 second byte & 3 != 0, not sending reply!");
312			} else {
313				let mut data = BytesMut::with_capacity(4);
314				data.put_u32_le(1);
315				debug!("Sent F6 reply!");
316				return Ok(Some(data.freeze()));
317			}
318		}
319		[0x12, _] => {
320			debug!("Would have sent 96 bytes of various descriptions back... not sure which...");
321		}
322		_ => {}
323	}
324
325	Ok(None)
326}
327
328async fn handle_read_dlf(
329	packet: Bytes,
330	host_filesystem: &HostFilesystem,
331) -> Result<Option<Bytes>, CatBridgeError> {
332	let read_address = u128::from(u32::from_be_bytes([
333		packet[0x4],
334		packet[0x5],
335		packet[0x6],
336		packet[0x7],
337	])) << 11_u128;
338	let read_length = u128::from(u32::from_be_bytes([
339		packet[0x8],
340		packet[0x9],
341		packet[0xA],
342		packet[0xB],
343	])) << 11_u128;
344	let rl_as_usize =
345		usize::try_from(read_length).map_err(|_| CatBridgeError::UnsupportedBitsPerCore)?;
346
347	debug!(
348		atapi.packet_type = "read_address",
349		atapi.read_address.address = %read_address,
350		atapi.read_address.length = %read_length,
351		"Handling atapi read request!"
352	);
353
354	let bytes_of_dlf = fs_read(host_filesystem.ppc_boot_dlf_path().await?)
355		.await
356		.map_err(FSError::from)?;
357	let dlf = DiskLayoutFile::try_from(Bytes::from(bytes_of_dlf))?;
358
359	if let Some((path, offset)) = dlf.get_path_and_offset_for_file(read_address).await {
360		// Read the file contents...
361		let buff = {
362			let mut handle = File::open(&path).await.map_err(FSError::from)?;
363			handle
364				.seek(SeekFrom::Start(offset))
365				.await
366				.map_err(FSError::from)?;
367
368			let mut file_buff = BytesMut::zeroed(rl_as_usize);
369			let mut bytes_read = 0;
370			while bytes_read < rl_as_usize {
371				let read_this_go = handle
372					.read(&mut file_buff[bytes_read..])
373					.await
374					.map_err(FSError::IO)?;
375				// EOF, rest of the buff is already 0's, so no need to pad.
376				if read_this_go == 0 {
377					break;
378				}
379				bytes_read += read_this_go;
380			}
381
382			file_buff
383		};
384
385		// Send!
386		Ok(Some(buff.freeze()))
387	} else {
388		Ok(Some(BytesMut::zeroed(rl_as_usize).freeze()))
389	}
390}
391
392// KNOWN PACKET HEADERS
393//
394//  - [0x3]
395//    - send 32 bytes of various describes, not quite sure
396//  - [0x12]
397//    - sends 96 bytes, seems to have some random spattering of fields
398//  - [0xCF, 0x80] -> Triggers Events in FSEmul, probably just call "EVentTrigger"
399//  - [0xF0] -> send back 4, 0x0 bytes
400//  - [0xF1]
401//    - [0xF1, 0x00]
402//      - seems to literally send 32 bytes of random data.... cool
403//    - [0xF1, 0x02]
404//      - seems to literally send 32 bytes of random data.... cool
405//    - [0xF1, 0x01] || [0xF1, 0x03]
406//      - seems to do nothing on hthe network
407//  - [0xF2]
408//    - [0xF2, 0x00]
409//    - [0xF2, 0x01]
410//    - [0xF2, 0x02]
411//    - [0xF2, 0x03]
412//      -> ??? calls a dynamically allocated thing
413//    - [0xF2, 0x06]
414//    - [0xF2, 0x07]
415//      -> ??? calls a dynamically allocated thing
416//      -> for f207 looks like we don't send _anything_ back by default
417//      -> seems some paths check for Dvdroot, so maybe dvd stuff?
418//  - [0xF3]
419//    - [0xF3, 0x0] -> seems to actually be real "read file"
420//      - not quite sure exactly how to parse this yet, but these are examples:
421//        - first 4 bytes are "packet id"
422//        - second 4 bytes are "read address" (calculate by: cast to u128 `<< 11`)
423//        - last 4 bytes are "read length" (calculate by: cast to u128 `<< 11`)
424//      - logs seem to indicate we:
425//         1. read a dlf file (dlf file is populated in cafe-tmp how get?)
426//         2. use that to get a max read address
427//         3. read from the file
428//         4. then pad
429//        see logs below:
430//          - `CSataProcessor::could not get lead out from dlffileobj {error code}`
431//          - `CSataProcessor::requested read address 0x%I64x is out of bounds.`
432//          - `CSataProcessor::could not read from file`
433//          - `CSataProcessor::padding`
434//          - `CSataProcessor::error writing to MION port`
435//          - `CSataProcessor::wrote %d bytes`
436//    - [0xF3, 0x1] -> send back "PC SATA EMUL" + 20 0's
437//    - [0xF3, 0x2] || [0xF3, 0x3] -> send back 32 0's - seems this is always set to 0. maybe a kind of ping?
438//  - [0xF5]
439//    - send 32 0's
440//  - [0xF6]
441//    - second byte &3 != 0 -> doesn't send reply
442//    - if not send what looks to be `1` encoded as 4 bytes
443//  - [0xF7]
444//    - send 32 0's