cat_dev/fsemul/pcfs/sata_proto/
close_folder.rs

1//! Definitions, and handlers for the `CloseFolder` packet type.
2//!
3//! This closes an already existing open folder given just a handle.
4
5use crate::{
6	errors::NetworkParseError,
7	fsemul::{
8		host_filesystem::HostFilesystem,
9		pcfs::{
10			errors::PCFSApiError,
11			sata_proto::{construct_sata_response, SataPacketHeader},
12		},
13	},
14};
15use bytes::{Buf, Bytes, BytesMut};
16use valuable::{Fields, NamedField, NamedValues, StructDef, Structable, Valuable, Value, Visit};
17
18/// A packet to close a particular folder given a file descriptor.
19#[derive(Clone, Debug, PartialEq, Eq)]
20pub struct SataCloseFolderPacketBody {
21	file_descriptor: i32,
22}
23
24impl SataCloseFolderPacketBody {
25	#[must_use]
26	pub const fn file_descriptor(&self) -> i32 {
27		self.file_descriptor
28	}
29
30	/// Handle closing a folder that was previously open.
31	///
32	/// ## Errors
33	///
34	/// If we cannot construct a response.
35	pub async fn handle(
36		&self,
37		request_header: &SataPacketHeader,
38		host_filesystem: &HostFilesystem,
39	) -> Result<Bytes, PCFSApiError> {
40		host_filesystem.close_folder(self.file_descriptor).await;
41
42		construct_sata_response(request_header, 0, BytesMut::zeroed(4).freeze())
43	}
44}
45
46impl TryFrom<Bytes> for SataCloseFolderPacketBody {
47	type Error = NetworkParseError;
48
49	fn try_from(mut value: Bytes) -> Result<Self, Self::Error> {
50		if value.len() < 0x4 {
51			return Err(NetworkParseError::FieldNotLongEnough(
52				"SataCloseFolder",
53				"Body",
54				0x4,
55				value.len(),
56				value,
57			));
58		}
59		if value.len() > 0x4 {
60			return Err(NetworkParseError::UnexpectedTrailer(
61				"SataCloseFolder",
62				value.slice(0x4..),
63			));
64		}
65
66		let fd = value.get_i32();
67
68		Ok(Self {
69			file_descriptor: fd,
70		})
71	}
72}
73
74const SATA_CLOSE_FOLDER_PACKET_BODY_FIELDS: &[NamedField<'static>] = &[NamedField::new("fd")];
75
76impl Structable for SataCloseFolderPacketBody {
77	fn definition(&self) -> StructDef<'_> {
78		StructDef::new_static(
79			"SataCloseFolderPacketBody",
80			Fields::Named(SATA_CLOSE_FOLDER_PACKET_BODY_FIELDS),
81		)
82	}
83}
84
85impl Valuable for SataCloseFolderPacketBody {
86	fn as_value(&self) -> Value<'_> {
87		Value::Structable(self)
88	}
89
90	fn visit(&self, visitor: &mut dyn Visit) {
91		visitor.visit_named_fields(&NamedValues::new(
92			SATA_CLOSE_FOLDER_PACKET_BODY_FIELDS,
93			&[Valuable::as_value(&self.file_descriptor)],
94		));
95	}
96}
97
98#[cfg(test)]
99mod unit_tests {
100	use super::*;
101	use crate::fsemul::host_filesystem::test_helpers::{
102		create_temporary_host_filesystem, join_many,
103	};
104	use tokio::fs::OpenOptions;
105
106	#[tokio::test]
107	pub async fn simple_ffio_read_file_request() {
108		let (tempdir, fs) = create_temporary_host_filesystem().await;
109
110		let base_dir = join_many(tempdir.path(), ["data", "slc", "to-query"]);
111		tokio::fs::create_dir(&base_dir)
112			.await
113			.expect("Failed to create temporary directory for test!");
114		tokio::fs::write(join_many(&base_dir, ["file.txt"]), vec![0; 2])
115			.await
116			.expect("Failed to write test file!");
117		let mocked_header = SataPacketHeader {
118			packet_data_len: 0,
119			packet_id: 0,
120			flags: 0,
121			version: 0,
122			timestamp_on_host: 0,
123			pid_on_host: 0,
124		};
125
126		let mut open_options = OpenOptions::new();
127		open_options.read(true).create(false).write(false);
128		let fd = fs
129			.open_file(open_options, &join_many(&base_dir, ["file.txt"]))
130			.await
131			.expect("Failed to open file!");
132
133		let close_request = SataCloseFolderPacketBody {
134			file_descriptor: fd,
135		};
136
137		let response = close_request
138			.handle(&mocked_header, &fs)
139			.await
140			.expect("Failed to handle read request!");
141		assert_eq!(&response[0x20..], &[0; 4]);
142	}
143}