cat_dev/fsemul/pcfs/sata_proto/
rewind_directory.rs

1//! Definitions, and handlers for the `RewindDirectory` packet type.
2//!
3//! This will return the directory iterator to the very beginning of the
4//! directory regardless of where it is.
5
6use crate::{
7	errors::{CatBridgeError, NetworkParseError},
8	fsemul::{
9		host_filesystem::HostFilesystem,
10		pcfs::sata_proto::{construct_sata_response, SataPacketHeader},
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 rewind to the beginning of a directory.
20#[derive(Clone, Debug, PartialEq, Eq)]
21pub struct SataRewindDirPacketBody {
22	file_descriptor: i32,
23}
24
25impl SataRewindDirPacketBody {
26	#[must_use]
27	pub const fn file_descriptor(&self) -> i32 {
28		self.file_descriptor
29	}
30
31	/// Actually process by rewinding an open directory iterator.
32	///
33	/// ## Errors
34	///
35	/// If we cannot construct a sata response packet, which shouldn't ever happen.
36	pub async fn handle(
37		&self,
38		request_header: &SataPacketHeader,
39		host_filesystem: &HostFilesystem,
40	) -> Result<Bytes, CatBridgeError> {
41		if host_filesystem
42			.reverse_directory(self.file_descriptor)
43			.await
44			.is_err()
45		{
46			return Self::construct_error_repsonse(request_header, FS_ERROR);
47		}
48
49		Ok(construct_sata_response(
50			request_header,
51			0,
52			BytesMut::zeroed(4).freeze(),
53		)?)
54	}
55
56	fn construct_error_repsonse(
57		request_header: &SataPacketHeader,
58		error_code: u32,
59	) -> Result<Bytes, CatBridgeError> {
60		let mut buff = BytesMut::with_capacity(4);
61		buff.put_u32(error_code);
62		Ok(construct_sata_response(request_header, 0, buff.freeze())?)
63	}
64}
65
66impl TryFrom<Bytes> for SataRewindDirPacketBody {
67	type Error = NetworkParseError;
68
69	fn try_from(mut value: Bytes) -> Result<Self, Self::Error> {
70		if value.len() < 0x4 {
71			return Err(NetworkParseError::FieldNotLongEnough(
72				"SataRewindDir",
73				"Body",
74				0x4,
75				value.len(),
76				value,
77			));
78		}
79		if value.len() > 0x4 {
80			return Err(NetworkParseError::UnexpectedTrailer(
81				"SataRewindDir",
82				value.slice(0x4..),
83			));
84		}
85
86		let fd = value.get_i32();
87
88		Ok(Self {
89			file_descriptor: fd,
90		})
91	}
92}
93
94const SATA_REWIND_DIRECTORY_PACKET_BODY_FIELDS: &[NamedField<'static>] = &[NamedField::new("fd")];
95
96impl Structable for SataRewindDirPacketBody {
97	fn definition(&self) -> StructDef<'_> {
98		StructDef::new_static(
99			"SataRewindDirPacketBody",
100			Fields::Named(SATA_REWIND_DIRECTORY_PACKET_BODY_FIELDS),
101		)
102	}
103}
104
105impl Valuable for SataRewindDirPacketBody {
106	fn as_value(&self) -> Value<'_> {
107		Value::Structable(self)
108	}
109
110	fn visit(&self, visitor: &mut dyn Visit) {
111		visitor.visit_named_fields(&NamedValues::new(
112			SATA_REWIND_DIRECTORY_PACKET_BODY_FIELDS,
113			&[Valuable::as_value(&self.file_descriptor)],
114		));
115	}
116}
117
118#[cfg(test)]
119mod unit_tests {
120	use super::*;
121	use crate::fsemul::host_filesystem::test_helpers::{
122		create_temporary_host_filesystem, join_many,
123	};
124
125	#[tokio::test]
126	pub async fn can_handle_rewind_directory() {
127		let (tempdir, fs) = create_temporary_host_filesystem().await;
128		let mocked_header = SataPacketHeader {
129			packet_data_len: 0,
130			packet_id: 0,
131			flags: 0,
132			version: 0,
133			timestamp_on_host: 0,
134			pid_on_host: 0,
135		};
136
137		let base_dir = join_many(tempdir.path(), ["data", "slc", "to-query"]);
138		tokio::fs::create_dir(&base_dir)
139			.await
140			.expect("Failed to create temporary directory for test!");
141		// Create a file to be returned.
142		_ = tokio::fs::File::create(join_many(&base_dir, ["cafe.bat"]))
143			.await
144			.expect("Failed to create file to use!");
145
146		let dfd = fs
147			.open_folder(&base_dir)
148			.await
149			.expect("Failed to open existing directory!");
150		let request = SataRewindDirPacketBody {
151			file_descriptor: dfd,
152		};
153
154		// First request should return file information, and path name.
155		let actual_response = request
156			.handle(&mocked_header, &fs)
157			.await
158			.expect("Failed to get file information back from directory that was opened!");
159		assert_eq!(&actual_response[0x20..], &[0x00, 0x00, 0x00, 0x00]);
160		fs.close_folder(dfd).await;
161	}
162}