cat_dev/fsemul/pcfs/sata/proto/
get_info_by_query.rs1use crate::{
9 errors::NetworkParseError,
10 fsemul::{
11 filesystem::host::HostFilesystem,
12 pcfs::errors::{PcfsApiError, SataProtocolError},
13 },
14};
15use bytes::{Buf, BufMut, Bytes, BytesMut};
16use std::{
17 ffi::CStr,
18 fs::Metadata,
19 path::PathBuf,
20 sync::LazyLock,
21 time::{Duration, SystemTime},
22};
23use valuable::{Fields, NamedField, NamedValues, StructDef, Structable, Valuable, Value, Visit};
24
25#[cfg(feature = "nus")]
26use sachet::common::CafeContentFileInformation;
27
28static FAT_TIMESTAMP_START: LazyLock<SystemTime> = LazyLock::new(|| {
30 SystemTime::UNIX_EPOCH
31 .checked_add(Duration::from_hours(87650))
32 .expect("Failed to get timestamp for 1980! required!")
33});
34
35#[derive(Clone, Debug, PartialEq, Eq)]
40pub struct SataGetInfoByQueryPacketBody {
41 path: String,
52 typ: SataQueryType,
54}
55
56impl SataGetInfoByQueryPacketBody {
57 pub fn new(path: String, query_type: SataQueryType) -> Result<Self, PcfsApiError> {
68 if path.len() > 511 {
69 return Err(PcfsApiError::PathTooLong(path));
70 }
71
72 Ok(Self {
73 path,
74 typ: query_type,
75 })
76 }
77
78 #[must_use]
79 pub const fn query_type(&self) -> SataQueryType {
80 self.typ
81 }
82
83 pub const fn set_query_type(&mut self, new_type: SataQueryType) {
84 self.typ = new_type;
85 }
86
87 #[must_use]
88 pub fn path(&self) -> &str {
89 self.path.as_str()
90 }
91
92 pub fn set_path(&mut self, new_path: String) -> Result<(), PcfsApiError> {
103 if new_path.len() > 511 {
104 return Err(PcfsApiError::PathTooLong(new_path));
105 }
106
107 self.path = new_path;
108 Ok(())
109 }
110}
111
112impl From<&SataGetInfoByQueryPacketBody> for Bytes {
113 fn from(value: &SataGetInfoByQueryPacketBody) -> Self {
114 let mut result = BytesMut::with_capacity(0x204);
115 result.extend_from_slice(value.path.as_bytes());
116 result.extend(BytesMut::zeroed(0x200 - result.len()));
119 result.put_u32(u32::from(value.typ));
120 result.freeze()
121 }
122}
123
124impl From<SataGetInfoByQueryPacketBody> for Bytes {
125 fn from(value: SataGetInfoByQueryPacketBody) -> Self {
126 Self::from(&value)
127 }
128}
129
130impl TryFrom<Bytes> for SataGetInfoByQueryPacketBody {
131 type Error = NetworkParseError;
132
133 fn try_from(value: Bytes) -> Result<Self, Self::Error> {
134 if value.len() < 0x204 {
135 return Err(NetworkParseError::FieldNotLongEnough(
136 "SataGetInfoByQuery",
137 "Body",
138 0x204,
139 value.len(),
140 value,
141 ));
142 }
143 if value.len() > 0x204 {
144 return Err(NetworkParseError::UnexpectedTrailer(
145 "SataGetInfoByQueryBody",
146 value.slice(0x204..),
147 ));
148 }
149
150 let (path_bytes, num) = value.split_at(0x200);
151 let path_c_str =
152 CStr::from_bytes_until_nul(path_bytes).map_err(NetworkParseError::BadCString)?;
153 let query_type = u32::from_be_bytes([num[0], num[1], num[2], num[3]]);
154 let final_path = path_c_str.to_str()?.to_owned();
155
156 Ok(Self {
157 path: final_path,
158 typ: SataQueryType::try_from(query_type)?,
159 })
160 }
161}
162
163const SATA_GET_INFO_BY_QUERY_PACKET_BODY_FIELDS: &[NamedField<'static>] =
164 &[NamedField::new("path"), NamedField::new("type")];
165
166impl Structable for SataGetInfoByQueryPacketBody {
167 fn definition(&self) -> StructDef<'_> {
168 StructDef::new_static(
169 "SataGetInfoByQueryPacketBody",
170 Fields::Named(SATA_GET_INFO_BY_QUERY_PACKET_BODY_FIELDS),
171 )
172 }
173}
174
175impl Valuable for SataGetInfoByQueryPacketBody {
176 fn as_value(&self) -> Value<'_> {
177 Value::Structable(self)
178 }
179
180 fn visit(&self, visitor: &mut dyn Visit) {
181 visitor.visit_named_fields(&NamedValues::new(
182 SATA_GET_INFO_BY_QUERY_PACKET_BODY_FIELDS,
183 &[
184 Valuable::as_value(&self.path),
185 Valuable::as_value(&self.typ),
186 ],
187 ));
188 }
189}
190
191#[derive(Copy, Clone, Debug, PartialEq, Eq, Valuable)]
193pub enum SataQueryType {
194 FreeDiskSpace,
196 SizeOfFolder,
198 FileCount,
200 FileDetails,
202}
203
204impl From<SataQueryType> for u32 {
205 fn from(value: SataQueryType) -> Self {
206 match value {
207 SataQueryType::FreeDiskSpace => 0,
208 SataQueryType::SizeOfFolder => 1,
209 SataQueryType::FileCount => 2,
210 SataQueryType::FileDetails => 5,
211 }
212 }
213}
214
215impl TryFrom<u32> for SataQueryType {
216 type Error = SataProtocolError;
217
218 fn try_from(value: u32) -> Result<Self, Self::Error> {
219 match value {
220 0 => Ok(Self::FreeDiskSpace),
221 1 => Ok(Self::SizeOfFolder),
222 2 => Ok(Self::FileCount),
223 5 => Ok(Self::FileDetails),
224 val => Err(SataProtocolError::UnknownGetInfoQueryType(val)),
225 }
226 }
227}
228
229#[derive(Clone, Debug, Valuable, PartialEq, Eq)]
236pub enum SataQueryResponse {
237 ErrorCode(u32),
239 SmallSize(u32),
245 LargeSize(u64),
252 FDInfo(SataFDInfo),
258}
259
260impl SataQueryResponse {
261 pub fn try_from_small(mut value: Bytes) -> Result<Self, NetworkParseError> {
269 let rc = value.get_u32();
270 if rc != 0 {
271 return Err(NetworkParseError::ErrorCode(rc));
272 }
273
274 let smol = value.get_u32();
275
276 Ok(Self::SmallSize(smol))
277 }
278
279 pub fn try_from_large(mut value: Bytes) -> Result<Self, NetworkParseError> {
287 let rc = value.get_u32();
288 if rc != 0 {
289 return Err(NetworkParseError::ErrorCode(rc));
290 }
291
292 let larg = value.get_u64();
293
294 Ok(Self::LargeSize(larg))
295 }
296
297 pub fn try_from_fd_info(mut value: Bytes) -> Result<Self, NetworkParseError> {
305 let rc = value.get_u32();
306 if rc != 0 {
307 return Err(NetworkParseError::ErrorCode(rc));
308 }
309
310 let fd_info = SataFDInfo::try_from(value)?;
311
312 Ok(Self::FDInfo(fd_info))
313 }
314}
315
316impl From<&SataQueryResponse> for Bytes {
317 fn from(value: &SataQueryResponse) -> Self {
318 match value {
319 SataQueryResponse::FDInfo(fd_info) => {
320 let mut buff = BytesMut::with_capacity(88);
321 buff.put_u32(0);
322 buff.extend(Bytes::from(fd_info));
323 buff.freeze()
324 }
325 SataQueryResponse::LargeSize(lorg) => {
326 let mut buff = BytesMut::with_capacity(88);
327 buff.put_u32(0);
328 buff.put_u64(*lorg);
329 buff.extend([0; 76]);
330 buff.freeze()
331 }
332 SataQueryResponse::SmallSize(smol) => {
333 let mut buff = BytesMut::with_capacity(88);
334 buff.put_u32(0);
335 buff.put_u32(*smol);
336 buff.extend([0; 80]);
337 buff.freeze()
338 }
339 SataQueryResponse::ErrorCode(ec) => {
340 let mut buff = BytesMut::with_capacity(88);
341 buff.put_u32(*ec);
342 buff.extend_from_slice(&[0; 84]);
343 buff.freeze()
344 }
345 }
346 }
347}
348
349impl From<SataQueryResponse> for Bytes {
350 fn from(value: SataQueryResponse) -> Self {
351 Self::from(&value)
352 }
353}
354
355#[derive(Clone, Debug, PartialEq, Eq, Valuable)]
356pub struct SataFDInfo {
358 file_or_folder_flags: u32,
360 perms: u32,
362 file_length: u32,
365 created_timestamp: u64,
367 last_updated_timestamp: u64,
369}
370
371impl SataFDInfo {
372 #[must_use]
374 pub async fn get_info(
375 host_filesystem: &HostFilesystem,
376 metadata: &Metadata,
377 path: &PathBuf,
378 ) -> Self {
379 let is_read_only = if metadata.is_dir() {
380 host_filesystem.folder_is_read_only(path).await
381 } else {
382 metadata.permissions().readonly()
383 };
384
385 let file_or_folder_flags = if metadata.is_file() {
386 0x2C00_0000
387 } else {
388 0xAC00_0000
389 };
390 let perms = if is_read_only { 0x444 } else { 0x666 };
391 let file_length = if metadata.is_dir() {
392 0
393 } else {
394 u32::try_from(metadata.len()).unwrap_or(u32::MAX)
395 };
396 let created_timestamp = u64::try_from(
397 metadata
398 .created()
399 .unwrap_or(SystemTime::now())
400 .duration_since(*FAT_TIMESTAMP_START)
401 .unwrap_or(Duration::from_secs(0))
402 .as_millis(),
403 )
404 .unwrap_or(u64::MAX);
405 let updated_timestamp = u64::try_from(
406 metadata
407 .modified()
408 .unwrap_or(SystemTime::now())
409 .duration_since(*FAT_TIMESTAMP_START)
410 .unwrap_or(Duration::from_secs(0))
411 .as_millis(),
412 )
413 .unwrap_or(u64::MAX);
414
415 Self {
416 file_or_folder_flags,
417 perms,
418 file_length,
419 created_timestamp,
420 last_updated_timestamp: updated_timestamp,
421 }
422 }
423
424 #[cfg(feature = "nus")]
425 #[must_use]
427 pub async fn new_from_nus(
428 host_filesystem: &HostFilesystem,
429 disk_path: &PathBuf,
430 cafe_content_file_info: Option<CafeContentFileInformation>,
431 ) -> Self {
432 let is_read_only = if cafe_content_file_info.is_none() {
433 host_filesystem.folder_is_read_only(disk_path).await
434 } else {
435 false
436 };
437
438 let file_or_folder_flags = if cafe_content_file_info.is_some() {
439 0x2C00_0000
440 } else {
441 0xAC00_0000
442 };
443 let perms = if is_read_only { 0x444 } else { 0x666 };
444 let file_length = if let Some(fi) = cafe_content_file_info.as_ref() {
445 fi.file_size()
446 } else {
447 0_u32
448 };
449 let created_timestamp = u64::try_from(
450 SystemTime::now()
451 .duration_since(*FAT_TIMESTAMP_START)
452 .unwrap_or(Duration::from_secs(0))
453 .as_millis(),
454 )
455 .unwrap_or(u64::MAX);
456 let updated_timestamp = u64::try_from(
457 SystemTime::now()
458 .duration_since(*FAT_TIMESTAMP_START)
459 .unwrap_or(Duration::from_secs(0))
460 .as_millis(),
461 )
462 .unwrap_or(u64::MAX);
463
464 Self {
465 file_or_folder_flags,
466 perms,
467 file_length,
468 created_timestamp,
469 last_updated_timestamp: updated_timestamp,
470 }
471 }
472
473 #[must_use]
475 pub fn create_fake_info(
476 file_or_folder_flags: u32,
477 perms: u32,
478 file_length: u32,
479 created_timestamp: u64,
480 updated_timestamp: u64,
481 ) -> Self {
482 Self {
483 file_or_folder_flags,
484 perms,
485 file_length,
486 created_timestamp,
487 last_updated_timestamp: updated_timestamp,
488 }
489 }
490
491 #[must_use]
493 pub const fn flags(&self) -> u32 {
494 self.file_or_folder_flags
495 }
496
497 #[must_use]
499 pub const fn exists(&self) -> bool {
500 (self.file_or_folder_flags & 0x2000_0000) != 0
501 }
502
503 #[must_use]
505 pub const fn is_file(&self) -> bool {
506 (self.file_or_folder_flags & 0x8000_0000) == 0
507 }
508
509 #[must_use]
511 pub const fn is_directory(&self) -> bool {
512 !self.is_file()
513 }
514
515 #[must_use]
521 pub const fn permissions(&self) -> u32 {
522 self.perms
523 }
524
525 #[must_use]
527 pub const fn file_size(&self) -> Option<u32> {
528 if self.is_file() {
529 Some(self.file_length)
530 } else {
531 None
532 }
533 }
534
535 #[must_use]
539 pub const fn raw_created_timestamp(&self) -> u64 {
540 self.created_timestamp
541 }
542
543 #[must_use]
547 pub const fn raw_last_updated_timestamp(&self) -> u64 {
548 self.last_updated_timestamp
549 }
550}
551
552impl From<&SataFDInfo> for Bytes {
553 fn from(value: &SataFDInfo) -> Self {
554 let mut buff = BytesMut::with_capacity(84);
555 buff.put_u32(value.file_or_folder_flags);
556 buff.put_u32(value.perms);
557 buff.put_u32(1);
558 buff.put_u32(1);
559 buff.put_u32(value.file_length);
560 buff.put_u32(0);
561 buff.put_u32(0xE8);
562 buff.put_u32(0xDA6F_F000);
563 buff.put_u32(0);
564 buff.put_u64(value.created_timestamp);
565 buff.put_u64(value.last_updated_timestamp);
566 buff.extend_from_slice(&[0; 32]);
567 buff.freeze()
568 }
569}
570
571impl From<SataFDInfo> for Bytes {
572 fn from(value: SataFDInfo) -> Self {
573 Self::from(&value)
574 }
575}
576
577impl TryFrom<Bytes> for SataFDInfo {
578 type Error = NetworkParseError;
579
580 fn try_from(mut value: Bytes) -> Result<Self, Self::Error> {
581 if value.len() < 84 {
582 return Err(NetworkParseError::FieldNotLongEnough(
583 "SataFDInfo",
584 "Body",
585 84,
586 value.len(),
587 value,
588 ));
589 }
590 if value.len() > 84 {
591 return Err(NetworkParseError::UnexpectedTrailer(
592 "SataFDInfoBody",
593 value.slice(84..),
594 ));
595 }
596
597 let fd_flags = value.get_u32();
598 let unix_perms = value.get_u32();
599 _ = value.get_u32();
601 _ = value.get_u32();
602 let file_size = value.get_u32();
603 _ = value.get_u32();
605 _ = value.get_u32();
606 _ = value.get_u32();
607 _ = value.get_u32();
608 let created_ts = value.get_u64();
610 let updated_ts = value.get_u64();
611 Ok(Self {
614 file_or_folder_flags: fd_flags,
615 perms: unix_perms,
616 file_length: file_size,
617 created_timestamp: created_ts,
618 last_updated_timestamp: updated_ts,
619 })
620 }
621}
622
623#[cfg(test)]
624mod unit_tests {
625 use super::*;
626
627 #[test]
628 pub fn query_types_to_and_fro() {
629 for qt in vec![
630 SataQueryType::FreeDiskSpace,
631 SataQueryType::SizeOfFolder,
632 SataQueryType::FileCount,
633 SataQueryType::FileDetails,
634 ] {
635 assert_eq!(Ok(qt), SataQueryType::try_from(u32::from(qt)));
636 }
637 }
638}