cat_dev/fsemul/pcfs/sata_proto/
read_directory.rs

1//! Definitions, and handlers for the `ReadDirectory` packet type.
2//!
3//! This will return the file information for the next file present within a
4//! directory. This does not recurse.
5
6use crate::{
7	errors::{CatBridgeError, NetworkParseError},
8	fsemul::{
9		host_filesystem::HostFilesystem,
10		pcfs::sata_proto::{
11			construct_sata_response, SataGetInfoByQueryPacketBody, SataPacketHeader,
12		},
13	},
14};
15use bytes::{Buf, BufMut, Bytes, BytesMut};
16use std::path::PathBuf;
17use valuable::{Fields, NamedField, NamedValues, StructDef, Structable, Valuable, Value, Visit};
18
19/// A filesystem error occured.
20const FS_ERROR: u32 = 0xFFF0_FFE0;
21/// No more items in this directory! sorry!
22const NO_MORE_ITEMS: u32 = 0xFFF0_FFFC;
23
24/// A packet to get information about another file within a directory.
25#[derive(Clone, Debug, PartialEq, Eq)]
26pub struct SataReadDirPacketBody {
27	file_descriptor: i32,
28}
29
30impl SataReadDirPacketBody {
31	#[must_use]
32	pub const fn file_descriptor(&self) -> i32 {
33		self.file_descriptor
34	}
35
36	/// Create a packet to then get the file information in the next file in a
37	/// folder.
38	///
39	/// ## Errors
40	///
41	/// If we cannot construct a sata response packet, which shouldn't ever happen.
42	pub async fn handle(
43		&self,
44		request_header: &SataPacketHeader,
45		host_filesystem: &HostFilesystem,
46	) -> Result<Bytes, CatBridgeError> {
47		let Ok(optional_next_item) = host_filesystem.next_in_folder(self.file_descriptor).await
48		else {
49			return Self::construct_error_repsonse(request_header, FS_ERROR);
50		};
51		let Some((item, components_to_remove)) = optional_next_item else {
52			return Self::construct_error_repsonse(request_header, NO_MORE_ITEMS);
53		};
54		let Ok(info) = SataGetInfoByQueryPacketBody::info_for_path(&item) else {
55			return Self::construct_error_repsonse(request_header, FS_ERROR);
56		};
57		let utf8 = item
58			.components()
59			.skip(components_to_remove)
60			.collect::<PathBuf>()
61			.to_string_lossy()
62			.to_string();
63		if utf8.len() > 255 {
64			return Self::construct_error_repsonse(request_header, FS_ERROR);
65		}
66		let byte_len = utf8.len();
67
68		let mut buff = BytesMut::with_capacity(0x158);
69		buff.put_u32(0);
70		buff.extend(info);
71		buff.extend(utf8.bytes());
72		buff.extend(vec![0; 256 - byte_len]);
73
74		Ok(construct_sata_response(request_header, 0, buff.freeze())?)
75	}
76
77	fn construct_error_repsonse(
78		request_header: &SataPacketHeader,
79		error_code: u32,
80	) -> Result<Bytes, CatBridgeError> {
81		let mut buff = BytesMut::with_capacity(0x158);
82		buff.put_u32(error_code);
83		buff.extend_from_slice(&[0; 0x154]);
84		Ok(construct_sata_response(request_header, 0, buff.freeze())?)
85	}
86}
87
88impl TryFrom<Bytes> for SataReadDirPacketBody {
89	type Error = NetworkParseError;
90
91	fn try_from(mut value: Bytes) -> Result<Self, Self::Error> {
92		if value.len() < 0x4 {
93			return Err(NetworkParseError::FieldNotLongEnough(
94				"SataReadDir",
95				"Body",
96				0x4,
97				value.len(),
98				value,
99			));
100		}
101		if value.len() > 0x4 {
102			return Err(NetworkParseError::UnexpectedTrailer(
103				"SataReadDir",
104				value.slice(0x4..),
105			));
106		}
107
108		let fd = value.get_i32();
109
110		Ok(Self {
111			file_descriptor: fd,
112		})
113	}
114}
115
116const SATA_READ_DIRECTORY_PACKET_BODY_FIELDS: &[NamedField<'static>] = &[NamedField::new("fd")];
117
118impl Structable for SataReadDirPacketBody {
119	fn definition(&self) -> StructDef<'_> {
120		StructDef::new_static(
121			"SataReadDirPacketBody",
122			Fields::Named(SATA_READ_DIRECTORY_PACKET_BODY_FIELDS),
123		)
124	}
125}
126
127impl Valuable for SataReadDirPacketBody {
128	fn as_value(&self) -> Value<'_> {
129		Value::Structable(self)
130	}
131
132	fn visit(&self, visitor: &mut dyn Visit) {
133		visitor.visit_named_fields(&NamedValues::new(
134			SATA_READ_DIRECTORY_PACKET_BODY_FIELDS,
135			&[Valuable::as_value(&self.file_descriptor)],
136		));
137	}
138}
139
140#[cfg(test)]
141mod unit_tests {
142	use super::*;
143	use crate::fsemul::host_filesystem::test_helpers::{
144		create_temporary_host_filesystem, join_many,
145	};
146
147	#[tokio::test]
148	pub async fn can_handle_read_directory() {
149		let (tempdir, fs) = create_temporary_host_filesystem().await;
150		let mocked_header = SataPacketHeader {
151			packet_data_len: 0,
152			packet_id: 0,
153			flags: 0,
154			version: 0,
155			timestamp_on_host: 0,
156			pid_on_host: 0,
157		};
158
159		let base_dir = join_many(tempdir.path(), ["data", "slc", "to-query"]);
160		tokio::fs::create_dir(&base_dir)
161			.await
162			.expect("Failed to create temporary directory for test!");
163		// Create a file to be returned.
164		_ = tokio::fs::File::create(join_many(&base_dir, ["cafe.bat"]))
165			.await
166			.expect("Failed to create file to use!");
167
168		let dfd = fs
169			.open_folder(&base_dir)
170			.await
171			.expect("Failed to open existing directory!");
172		let request = SataReadDirPacketBody {
173			file_descriptor: dfd,
174		};
175
176		// First request should return file information, and path name.
177		let actual_file_response = request
178			.handle(&mocked_header, &fs)
179			.await
180			.expect("Failed to get file information back from directory that was opened!");
181		assert_eq!(
182			&actual_file_response[0x20..0x48],
183			&[
184				0x00, 0x00, 0x00, 0x00, // RC
185				0x2C, 0x00, 0x00, 0x00, // We should've gotten a file.
186				0x00, 0x00, 0x06, 0x66, // Permissions
187				0x00, 0x00, 0x00, 0x01, // Hardcoded 1
188				0x00, 0x00, 0x00, 0x01, // Hardcoded 1
189				0x00, 0x00, 0x00, 0x00, // File size.
190				0x00, 0x00, 0x00, 0x00, // Hardcoded 0.
191				0x00, 0x00, 0x00, 0xE8, // Hardcoded E8.
192				0xDA, 0x6F, 0xF0, 0x00, // Hardcoded da6ff000.
193				0x00, 0x00, 0x00, 0x00, // Hardcoded 0.
194			],
195		);
196		assert_ne!(
197			&actual_file_response[0x48..0x54],
198			&[0_u8, 0, 0, 0, 0, 0, 0, 0],
199		);
200		assert_ne!(
201			&actual_file_response[0x54..0x5C],
202			&[0_u8, 0, 0, 0, 0, 0, 0, 0],
203		);
204		assert_eq!(
205			&actual_file_response[0x5C..0x78],
206			&[
207				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
208				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
209			],
210		);
211		assert_eq!(
212			&actual_file_response[0x78..],
213			&[
214				0x63, 0x61, 0x66, 0x65, 0x2e, 0x62, 0x61, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
215				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
216				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
217				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
218				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
219				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
220				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
221				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
222				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
223				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
224				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
225				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
226				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
227				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
228				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
229				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
230				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
231				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
232				0x00, 0x00, 0x00, 0x00,
233			]
234		);
235
236		// Second one is an empty response.
237		let new_response = request
238			.handle(&mocked_header, &fs)
239			.await
240			.expect("Failed to get file information back from directory that was opened!");
241		assert_eq!(&new_response[0x20..0x24], &[0xFF, 0xF0, 0xFF, 0xFC]);
242		fs.close_folder(dfd).await;
243	}
244}