cat_dev/fsemul/pcfs/sata_proto/
open_folder.rs1use crate::{
6 errors::{CatBridgeError, NetworkParseError},
7 fsemul::{
8 host_filesystem::ResolvedLocation,
9 pcfs::sata_proto::{construct_sata_response, SataPacketHeader},
10 HostFilesystem,
11 },
12};
13use bytes::{BufMut, Bytes, BytesMut};
14use std::ffi::CStr;
15use valuable::{Fields, NamedField, NamedValues, StructDef, Structable, Valuable, Value, Visit};
16
17const FS_ERROR: u32 = 0xFFF0_FFE0;
19const PATH_NOT_EXIST_ERROR: u32 = 0xFFF0_FFE9;
25
26#[derive(Clone, Debug, PartialEq, Eq)]
28pub struct SataOpenFolderPacketBody {
29 path: String,
40}
41
42impl SataOpenFolderPacketBody {
43 #[must_use]
44 pub fn path(&self) -> &str {
45 self.path.as_str()
46 }
47
48 pub async fn handle(
55 &self,
56 request_header: &SataPacketHeader,
57 host_filesystem: &HostFilesystem,
58 ) -> Result<Bytes, CatBridgeError> {
59 let Ok(final_location) = host_filesystem.resolve_path(&self.path) else {
60 return Self::construct_error(request_header, PATH_NOT_EXIST_ERROR);
61 };
62 let ResolvedLocation::Filesystem(fs_location) = final_location else {
63 todo!("network shares not yet implemented!")
64 };
65
66 let Ok(fd) = host_filesystem
67 .open_folder(fs_location.resolved_path())
68 .await
69 else {
70 return Self::construct_error(request_header, FS_ERROR);
71 };
72
73 let mut buff = BytesMut::with_capacity(8);
74 buff.put_u32(0);
75 buff.put_i32(fd);
76 Ok(construct_sata_response(request_header, 0, buff.freeze())?)
77 }
78
79 fn construct_error(
80 packet_header: &SataPacketHeader,
81 error_code: u32,
82 ) -> Result<Bytes, CatBridgeError> {
83 let mut buff = BytesMut::with_capacity(8);
84 buff.put_u32(error_code);
85 buff.put_u32(0);
86
87 Ok(construct_sata_response(packet_header, 0, buff.freeze())?)
88 }
89}
90
91impl TryFrom<Bytes> for SataOpenFolderPacketBody {
92 type Error = NetworkParseError;
93
94 fn try_from(value: Bytes) -> Result<Self, Self::Error> {
95 if value.len() < 0x200 {
96 return Err(NetworkParseError::FieldNotLongEnough(
97 "SataOpenFolder",
98 "Body",
99 0x200,
100 value.len(),
101 value,
102 ));
103 }
104 if value.len() > 0x200 {
105 return Err(NetworkParseError::UnexpectedTrailer(
106 "SataOpenFolder",
107 value.slice(0x200..),
108 ));
109 }
110
111 let path_c_str =
112 CStr::from_bytes_until_nul(&value).map_err(NetworkParseError::BadCString)?;
113
114 Ok(Self {
115 path: path_c_str.to_str()?.to_owned(),
116 })
117 }
118}
119
120const SATA_OPEN_FOLDER_PACKET_BODY_FIELDS: &[NamedField<'static>] = &[NamedField::new("path")];
121
122impl Structable for SataOpenFolderPacketBody {
123 fn definition(&self) -> StructDef<'_> {
124 StructDef::new_static(
125 "SataOpenFolderPacketBody",
126 Fields::Named(SATA_OPEN_FOLDER_PACKET_BODY_FIELDS),
127 )
128 }
129}
130
131impl Valuable for SataOpenFolderPacketBody {
132 fn as_value(&self) -> Value<'_> {
133 Value::Structable(self)
134 }
135
136 fn visit(&self, visitor: &mut dyn Visit) {
137 visitor.visit_named_fields(&NamedValues::new(
138 SATA_OPEN_FOLDER_PACKET_BODY_FIELDS,
139 &[Valuable::as_value(&self.path)],
140 ));
141 }
142}
143
144#[cfg(test)]
145mod unit_tests {
146 use super::*;
147 use crate::fsemul::host_filesystem::test_helpers::{
148 create_temporary_host_filesystem, join_many,
149 };
150
151 #[tokio::test]
152 pub async fn simple_open_folder_request() {
153 let (tempdir, fs) = create_temporary_host_filesystem().await;
154 let request = SataOpenFolderPacketBody {
155 path: "/%SLC_EMU_DIR/to-query/".to_owned(),
156 };
157 let mocked_header = SataPacketHeader {
158 packet_data_len: 0,
159 packet_id: 0,
160 flags: 0,
161 version: 0,
162 timestamp_on_host: 0,
163 pid_on_host: 0,
164 };
165
166 let base_dir = join_many(tempdir.path(), ["data", "slc", "to-query"]);
167 tokio::fs::create_dir(&base_dir)
168 .await
169 .expect("Failed to create temporary directory for test!");
170
171 let mut response = request
172 .handle(&mocked_header, &fs)
173 .await
174 .expect("Failed to handle change mode!");
175 assert_eq!(response.len(), 8 + 0x20, "Packet is not correct size!");
176 _ = response.split_to(0x20);
178 assert_eq!(
179 &response[..4],
180 &[0x00, 0x00, 0x00, 0x00], );
182 assert_ne!(
183 &response[4..],
184 &[0x00, 0x00, 0x00, 0x00], );
186 fs.close_folder(i32::from_be_bytes([
187 response[4],
188 response[5],
189 response[6],
190 response[7],
191 ]))
192 .await;
193 }
194}