1use 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
19const FS_ERROR: u32 = 0xFFF0_FFE0;
21const NO_MORE_ITEMS: u32 = 0xFFF0_FFFC;
23
24#[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 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 _ = 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 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, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x66, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE8, 0xDA, 0x6F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, ],
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 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}