cat_dev/fsemul/pcfs/sata_proto/
read_file.rs

1//! Definitions, and handlers for the `ReadFile` packet type.
2//!
3//! This is what actively handles reading bytes out of a file. With either
4//! FFIO, and Combined Send/Recv options being turned on/off.
5
6use crate::{
7	errors::{CatBridgeError, NetworkParseError},
8	fsemul::{
9		pcfs::sata_proto::{construct_sata_response, MoveToFileLocation, SataPacketHeader},
10		HostFilesystem,
11	},
12};
13use bytes::{Buf, BufMut, Bytes, BytesMut};
14use valuable::{Fields, NamedField, NamedValues, StructDef, Structable, Valuable, Value, Visit};
15
16/// A filesystem error occured.
17const FS_ERROR: u32 = 0xFFF0_FFE0;
18
19/// A packet to read the contents of an already open file.
20#[derive(Clone, Debug, PartialEq, Eq)]
21pub struct SataReadFilePacketBody {
22	block_count: u32,
23	block_size: u32,
24	handle: i32,
25	move_to_pointer: MoveToFileLocation,
26	should_move: bool,
27}
28
29impl SataReadFilePacketBody {
30	#[must_use]
31	pub const fn block_count(&self) -> u32 {
32		self.block_count
33	}
34	#[must_use]
35	pub const fn block_size(&self) -> u32 {
36		self.block_size
37	}
38	#[must_use]
39	pub const fn file_descriptor(&self) -> i32 {
40		self.handle
41	}
42	#[must_use]
43	pub const fn move_to_pointer(&self) -> MoveToFileLocation {
44		self.move_to_pointer
45	}
46	#[must_use]
47	pub const fn should_move(&self) -> bool {
48		self.should_move
49	}
50
51	/// Handle reading from a file that is already open.
52	///
53	/// ## Errors
54	///
55	/// If we cannot construct a sata response packet because our data to send
56	/// was somehow too large (this should ideally never happen), or if we're
57	/// running on a 16 bit system.
58	pub async fn handle(
59		&self,
60		request_header: &SataPacketHeader,
61		host_filesystem: &HostFilesystem,
62		ffio_supported: bool,
63	) -> Result<Bytes, CatBridgeError> {
64		if self.should_move {
65			match self.move_to_pointer {
66				MoveToFileLocation::Begin => {
67					if host_filesystem.seek_file(self.handle, true).await.is_err() {
68						return Self::construct_error(request_header, FS_ERROR);
69					}
70				}
71				MoveToFileLocation::Current => {
72					// Luckily to move to current, we don't need to move at all.
73				}
74				MoveToFileLocation::End => {
75					if host_filesystem.seek_file(self.handle, false).await.is_err() {
76						return Self::construct_error(request_header, FS_ERROR);
77					}
78				}
79			}
80		}
81
82		let Some(file_size) = host_filesystem.file_length(self.handle).await else {
83			return Self::construct_error(request_header, FS_ERROR);
84		};
85		let Ok(Some(read_file)) = host_filesystem
86			.read_file(
87				self.handle,
88				usize::try_from(self.block_size)
89					.map_err(|_| CatBridgeError::UnsupportedBitsPerCore)?
90					* usize::try_from(self.block_count)
91						.map_err(|_| CatBridgeError::UnsupportedBitsPerCore)?,
92			)
93			.await
94		else {
95			return Self::construct_error(request_header, FS_ERROR);
96		};
97
98		if ffio_supported {
99			let mut buff = BytesMut::with_capacity(read_file.len() + 0x24);
100			// The header is normally just 'malloc'd and not cleared between
101			// buffers. Luckily for us we can just zero it out, and it's easier than
102			// actually dealing with whatever random bytes PCFSServer would normally
103			// send.
104			buff.extend_from_slice(&[0; 0x20]);
105			buff.put_u32(u32::try_from(file_size).unwrap_or(u32::MAX));
106			buff.extend(read_file);
107			Ok(buff.freeze())
108		} else {
109			todo!("Implement non-FFIO support.")
110		}
111	}
112
113	fn construct_error(
114		packet_header: &SataPacketHeader,
115		error_code: u32,
116	) -> Result<Bytes, CatBridgeError> {
117		let mut buff = BytesMut::with_capacity(8);
118		buff.put_u32(error_code);
119		Ok(construct_sata_response(packet_header, 0, buff.freeze())?)
120	}
121}
122
123impl TryFrom<Bytes> for SataReadFilePacketBody {
124	type Error = NetworkParseError;
125
126	fn try_from(mut value: Bytes) -> Result<Self, Self::Error> {
127		if value.len() < 20 {
128			return Err(NetworkParseError::FieldNotLongEnough(
129				"SataReadFile",
130				"Body",
131				20,
132				value.len(),
133				value,
134			));
135		}
136		if value.len() > 20 {
137			return Err(NetworkParseError::UnexpectedTrailer(
138				"SataReadFile",
139				value.slice(20..),
140			));
141		}
142
143		let block_count = value.get_u32();
144		let block_length = value.get_u32();
145		let handle = value.get_i32();
146		let move_to_ptr = value.get_u32();
147		let should_move = value.get_u32();
148
149		Ok(Self {
150			block_count,
151			block_size: block_length,
152			handle,
153			move_to_pointer: MoveToFileLocation::try_from(move_to_ptr)?,
154			should_move: (should_move & 1) != 0,
155		})
156	}
157}
158
159const SATA_READ_FILE_PACKET_BODY_FIELDS: &[NamedField<'static>] = &[
160	NamedField::new("block_count"),
161	NamedField::new("block_size"),
162	NamedField::new("handle"),
163	NamedField::new("move_to_pointer"),
164	NamedField::new("should_move"),
165];
166
167impl Structable for SataReadFilePacketBody {
168	fn definition(&self) -> StructDef<'_> {
169		StructDef::new_static(
170			"SataReadFilePacketBody",
171			Fields::Named(SATA_READ_FILE_PACKET_BODY_FIELDS),
172		)
173	}
174}
175
176impl Valuable for SataReadFilePacketBody {
177	fn as_value(&self) -> Value<'_> {
178		Value::Structable(self)
179	}
180
181	fn visit(&self, visitor: &mut dyn Visit) {
182		visitor.visit_named_fields(&NamedValues::new(
183			SATA_READ_FILE_PACKET_BODY_FIELDS,
184			&[
185				Valuable::as_value(&self.block_count),
186				Valuable::as_value(&self.block_size),
187				Valuable::as_value(&self.handle),
188				Valuable::as_value(&self.move_to_pointer),
189				Valuable::as_value(&self.should_move),
190			],
191		));
192	}
193}
194
195#[cfg(test)]
196mod unit_tests {
197	use super::*;
198	use crate::fsemul::host_filesystem::test_helpers::{
199		create_temporary_host_filesystem, join_many,
200	};
201	use tokio::fs::OpenOptions;
202
203	#[tokio::test]
204	pub async fn simple_ffio_read_file_request() {
205		let (tempdir, fs) = create_temporary_host_filesystem().await;
206
207		let base_dir = join_many(tempdir.path(), ["data", "slc", "to-query"]);
208		tokio::fs::create_dir(&base_dir)
209			.await
210			.expect("Failed to create temporary directory for test!");
211		tokio::fs::write(join_many(&base_dir, ["file.txt"]), vec![0; 2])
212			.await
213			.expect("Failed to write test file!");
214		let mocked_header = SataPacketHeader {
215			packet_data_len: 0,
216			packet_id: 0,
217			flags: 0,
218			version: 0,
219			timestamp_on_host: 0,
220			pid_on_host: 0,
221		};
222
223		let mut open_options = OpenOptions::new();
224		open_options.read(true).create(false).write(false);
225		let fd = fs
226			.open_file(open_options, &join_many(&base_dir, ["file.txt"]))
227			.await
228			.expect("Failed to open file!");
229
230		let read_request = SataReadFilePacketBody {
231			block_count: 4,
232			block_size: 1,
233			handle: fd,
234			move_to_pointer: MoveToFileLocation::Begin,
235			should_move: false,
236		};
237
238		let response = read_request
239			.handle(&mocked_header, &fs, true)
240			.await
241			.expect("Failed to handle read request!");
242		let mut expected_response = BytesMut::new();
243		// Header
244		expected_response.extend_from_slice(&[0; 0x20]);
245		// File length.
246		expected_response.extend_from_slice(&2_u32.to_be_bytes());
247		// File data, and padding.
248		expected_response.extend_from_slice(&[0x00, 0x00, 0xCD, 0xCD]);
249		assert_eq!(response, expected_response.freeze());
250	}
251}