1mod change_mode;
8mod change_owner;
9mod close_file;
10mod close_folder;
11mod create_directory;
12mod get_info_by_query;
13mod open_file;
14mod open_folder;
15mod ping;
16mod read_directory;
17mod read_file;
18mod remove;
19mod rewind_directory;
20mod stat_file;
21mod write_file;
22
23use crate::{
24 errors::NetworkParseError,
25 fsemul::pcfs::errors::{PCFSApiError, SataProtocolError},
26};
27use bytes::{BufMut, Bytes, BytesMut};
28use std::{
29 fmt::{Display, Formatter, Result as FmtResult},
30 sync::{
31 atomic::{AtomicUsize, Ordering as AtomicOrdering},
32 Arc, LazyLock,
33 },
34 time::{Duration, SystemTime, UNIX_EPOCH},
35};
36use tokio::io::Error as IoError;
37use tokio_util::codec::{Decoder, Encoder};
38use tracing::debug;
39use valuable::{Fields, NamedField, NamedValues, StructDef, Structable, Valuable, Value, Visit};
40
41pub use crate::fsemul::pcfs::sata_proto::{
42 change_mode::*, change_owner::*, close_file::*, close_folder::*, create_directory::*,
43 get_info_by_query::*, open_file::*, open_folder::*, ping::*, read_directory::*, read_file::*,
44 remove::*, rewind_directory::*, stat_file::*, write_file::*,
45};
46
47const DEFAULT_PCFS_VERSION: u32 = 0x0200_0600;
49static PID: LazyLock<u32> = LazyLock::new(std::process::id);
51
52#[derive(Clone, Debug)]
61pub struct SataProtoChunker(pub Arc<AtomicUsize>);
62
63impl Decoder for SataProtoChunker {
64 type Item = BytesMut;
65 type Error = IoError;
66
67 fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
68 let num = self.0.load(AtomicOrdering::Acquire);
69 if num != 0 {
70 if src.len() < num {
71 return Ok(None);
72 }
73
74 self.0.store(0, AtomicOrdering::Release);
75 return Ok(Some(src.split_to(num)));
76 }
77 if src.len() < 0x20 {
79 return Ok(None);
80 }
81
82 let data_len = u32::from_be_bytes([src[0], src[1], src[2], src[3]]);
83 let final_len = usize::try_from(0x20 + data_len).unwrap_or(usize::MAX);
84
85 if src.len() < final_len {
86 Ok(None)
87 } else {
88 Ok(Some(src.split_to(final_len)))
89 }
90 }
91}
92
93impl Encoder<Bytes> for SataProtoChunker {
94 type Error = IoError;
95
96 fn encode(&mut self, item: Bytes, dst: &mut BytesMut) -> Result<(), Self::Error> {
97 dst.reserve(item.len());
103 dst.extend(item);
104 Ok(())
105 }
106}
107
108#[derive(Clone, Debug, PartialEq, Eq)]
110pub struct SataRequest {
111 header: SataPacketHeader,
113 command_info: SataCommandInfo,
115 body: SataRequestBody,
117}
118
119impl SataRequest {
120 #[must_use]
122 pub const fn header(&self) -> &SataPacketHeader {
123 &self.header
124 }
125
126 #[must_use]
128 pub const fn command_info(&self) -> &SataCommandInfo {
129 &self.command_info
130 }
131
132 #[must_use]
134 pub const fn body(&self) -> &SataRequestBody {
135 &self.body
136 }
137}
138
139impl TryFrom<Bytes> for SataRequest {
140 type Error = NetworkParseError;
141
142 fn try_from(mut value: Bytes) -> Result<Self, Self::Error> {
143 if value.len() < 0x34 {
149 return Err(NetworkParseError::FieldNotLongEnough(
150 "SataRequest",
151 "header",
152 0x34,
153 value.len(),
154 value,
155 ));
156 }
157
158 let mut ci_bytes = value.split_off(0x20);
159 let body = ci_bytes.split_off(0x14);
160 let as_header = SataPacketHeader::try_from(value)?;
161 let ci = SataCommandInfo::try_from(ci_bytes)?;
162 let body = SataRequestBody::parse_command(ci.command(), body)?;
163
164 Ok(Self {
165 header: as_header,
166 command_info: ci,
167 body,
168 })
169 }
170}
171
172const SATA_REQUEST_FIELDS: &[NamedField<'static>] = &[
173 NamedField::new("header"),
174 NamedField::new("command_info"),
175 NamedField::new("body"),
176];
177
178impl Structable for SataRequest {
179 fn definition(&self) -> StructDef<'_> {
180 StructDef::new_static("SataRequest", Fields::Named(SATA_REQUEST_FIELDS))
181 }
182}
183
184impl Valuable for SataRequest {
185 fn as_value(&self) -> Value<'_> {
186 Value::Structable(self)
187 }
188
189 fn visit(&self, visitor: &mut dyn Visit) {
190 visitor.visit_named_fields(&NamedValues::new(
191 SATA_REQUEST_FIELDS,
192 &[
193 Valuable::as_value(&self.header),
194 Valuable::as_value(&self.command_info),
195 Valuable::as_value(&self.body),
196 ],
197 ));
198 }
199}
200
201#[derive(Clone, Debug, PartialEq, Eq)]
203pub struct SataPacketHeader {
204 packet_data_len: u32,
206 packet_id: u32,
211 flags: u32,
215 version: u32,
220 timestamp_on_host: u32,
225 pid_on_host: u32,
227 }
229
230impl SataPacketHeader {
231 #[must_use]
232 pub const fn data_len(&self) -> u32 {
233 self.packet_data_len
234 }
235
236 #[must_use]
237 pub const fn id(&self) -> u32 {
238 self.packet_id
239 }
240
241 #[must_use]
242 pub const fn flags(&self) -> u32 {
243 self.flags
244 }
245
246 #[must_use]
247 pub const fn version(&self) -> u32 {
248 self.version
249 }
250
251 #[must_use]
252 pub const fn raw_timestamp_on_host(&self) -> u32 {
253 self.timestamp_on_host
254 }
255
256 #[must_use]
262 pub fn host_timestamp(&self) -> SystemTime {
263 UNIX_EPOCH
264 .checked_add(Duration::from_secs(u64::from(self.timestamp_on_host)))
265 .unwrap_or_else(SystemTime::now)
266 }
267
268 #[must_use]
269 pub const fn host_pid(&self) -> u32 {
270 self.pid_on_host
271 }
272
273 pub fn ensure_not_from_host(&self) -> Result<(), SataProtocolError> {
280 if self.pid_on_host != 0 || self.timestamp_on_host != 0 {
281 return Err(SataProtocolError::NonHostSetHostOnlyHeaderFields(
282 self.timestamp_on_host,
283 self.pid_on_host,
284 ));
285 }
286
287 Ok(())
288 }
289}
290
291impl TryFrom<Bytes> for SataPacketHeader {
292 type Error = NetworkParseError;
293
294 fn try_from(value: Bytes) -> Result<Self, Self::Error> {
295 if value.len() < 0x20 {
296 return Err(NetworkParseError::FieldNotLongEnough(
297 "SataPacket",
298 "Header",
299 0x20,
300 value.len(),
301 value,
302 ));
303 }
304 if value.len() > 0x20 {
305 return Err(NetworkParseError::UnexpectedTrailer(
306 "SataPacketHeader",
307 value.slice(0x20..),
308 ));
309 }
310
311 let packet_data_len = u32::from_be_bytes([value[0], value[1], value[2], value[3]]);
312 let packet_id = u32::from_be_bytes([value[4], value[5], value[6], value[7]]);
313 let flags = u32::from_be_bytes([value[8], value[9], value[10], value[11]]);
314 let version = u32::from_be_bytes([value[12], value[13], value[14], value[15]]);
315 let timestamp_on_host = u32::from_be_bytes([value[16], value[17], value[18], value[19]]);
316 let pid_on_host = u32::from_be_bytes([value[20], value[21], value[22], value[23]]);
317
318 let should_be_padding = [
319 value[24], value[25], value[26], value[27], value[28], value[29], value[30], value[31],
320 ];
321 if should_be_padding != [0x0; 8] {
322 return Err(SataProtocolError::HeaderBadPadding(should_be_padding).into());
323 }
324
325 Ok(Self {
326 packet_data_len,
327 packet_id,
328 flags,
329 version,
330 timestamp_on_host,
331 pid_on_host,
332 })
333 }
334}
335
336const SATA_PACKET_HEADER_FIELDS: &[NamedField<'static>] = &[
337 NamedField::new("data_len"),
338 NamedField::new("id"),
339 NamedField::new("flags"),
340 NamedField::new("version"),
341 NamedField::new("host_timestamp"),
342 NamedField::new("host_pid"),
343];
344
345impl Structable for SataPacketHeader {
346 fn definition(&self) -> StructDef<'_> {
347 StructDef::new_static("SataPacketHeader", Fields::Named(SATA_PACKET_HEADER_FIELDS))
348 }
349}
350
351impl Valuable for SataPacketHeader {
352 fn as_value(&self) -> Value<'_> {
353 Value::Structable(self)
354 }
355
356 fn visit(&self, visitor: &mut dyn Visit) {
357 visitor.visit_named_fields(&NamedValues::new(
358 SATA_PACKET_HEADER_FIELDS,
359 &[
360 Valuable::as_value(&self.packet_data_len),
361 Valuable::as_value(&self.packet_id),
362 Valuable::as_value(&self.flags),
363 Valuable::as_value(&self.version),
364 Valuable::as_value(&self.timestamp_on_host),
365 Valuable::as_value(&self.pid_on_host),
366 ],
367 ));
368 }
369}
370
371#[derive(Clone, Debug, PartialEq, Eq)]
372pub struct SataCommandInfo {
373 user: (u32, u32),
375 capabilities: (u32, u32),
377 command: u32,
379}
380
381impl SataCommandInfo {
382 #[must_use]
384 pub const fn user(&self) -> (u32, u32) {
385 self.user
386 }
387
388 #[must_use]
394 pub const fn capabilities(&self) -> (u32, u32) {
395 self.capabilities
396 }
397
398 #[must_use]
400 pub const fn command(&self) -> u32 {
401 self.command
402 }
403}
404
405impl TryFrom<Bytes> for SataCommandInfo {
406 type Error = NetworkParseError;
407
408 fn try_from(value: Bytes) -> Result<Self, Self::Error> {
409 if value.len() < 0x14 {
410 return Err(NetworkParseError::FieldNotLongEnough(
411 "SataPacket",
412 "CommandInfo",
413 0x14,
414 value.len(),
415 value,
416 ));
417 }
418 if value.len() > 0x14 {
419 return Err(NetworkParseError::UnexpectedTrailer(
420 "SataPacketCommandInfo",
421 value.slice(0x14..),
422 ));
423 }
424
425 let user0 = u32::from_be_bytes([value[0], value[1], value[2], value[3]]);
426 let user1 = u32::from_be_bytes([value[4], value[5], value[6], value[7]]);
427 let cap0 = u32::from_le_bytes([value[8], value[9], value[10], value[11]]);
428 let cap1 = u32::from_le_bytes([value[12], value[13], value[14], value[15]]);
429 let cmd = u32::from_be_bytes([value[16], value[17], value[18], value[19]]);
430
431 Ok(Self {
432 user: (user0, user1),
433 capabilities: (cap0, cap1),
434 command: cmd,
435 })
436 }
437}
438
439const SATA_COMMAND_INFO_FIELDS: &[NamedField<'static>] = &[
440 NamedField::new("user.0"),
441 NamedField::new("user.1"),
442 NamedField::new("cap.0"),
443 NamedField::new("cap.1"),
444 NamedField::new("cap.2"),
445 NamedField::new("cap.3"),
446 NamedField::new("cap.4"),
447 NamedField::new("command"),
448];
449
450impl Structable for SataCommandInfo {
451 fn definition(&self) -> StructDef<'_> {
452 StructDef::new_static("SataCommandInfo", Fields::Named(SATA_COMMAND_INFO_FIELDS))
453 }
454}
455
456impl Valuable for SataCommandInfo {
457 fn as_value(&self) -> Value<'_> {
458 Value::Structable(self)
459 }
460
461 fn visit(&self, visitor: &mut dyn Visit) {
462 visitor.visit_named_fields(&NamedValues::new(
463 SATA_COMMAND_INFO_FIELDS,
464 &[
465 Valuable::as_value(&self.user.0),
466 Valuable::as_value(&self.user.1),
467 Valuable::as_value(&(self.capabilities.0 & 0xFF)),
468 Valuable::as_value(&((self.capabilities.0 >> 8) & 0xFF)),
469 Valuable::as_value(&((self.capabilities.1) & 0xFF)),
470 Valuable::as_value(&((self.capabilities.0 >> 16) & 0xFF)),
471 Valuable::as_value(&((self.capabilities.0 >> 24) & 0xFF)),
472 Valuable::as_value(&self.command),
473 ],
474 ));
475 }
476}
477
478impl Display for SataCommandInfo {
479 fn fmt(&self, fmt: &mut Formatter<'_>) -> FmtResult {
480 write!(
481 fmt,
482 "Cmd: {}, user: {} {}, cap: {} {} {} {} {}",
483 self.command,
484 self.user.0,
485 self.user.1,
486 self.capabilities.0 & 0xFF,
487 (self.capabilities.0 >> 8) & 0xFF,
488 self.capabilities.1 & 0xFF,
489 (self.capabilities.0 >> 16) & 0xFF,
490 (self.capabilities.0 >> 24) & 0xFF,
491 )
492 }
493}
494
495#[derive(Clone, Debug, PartialEq, Eq, Valuable)]
497pub enum SataRequestBody {
498 ChangeMode(SataChangeModePacketBody),
500 ChangeOwner(SataChangeOwnerPacketBody),
502 CreateDirectory(SataCreateDirectoryPacketBody),
504 CloseFile(SataCloseFilePacketBody),
506 CloseFolder(SataCloseFolderPacketBody),
508 GetInfoByQuery(SataGetInfoByQueryPacketBody),
510 OpenFile(SataOpenFilePacketBody),
512 OpenFolder(SataOpenFolderPacketBody),
514 Ping(SataPingPacketBody),
516 ReadDirectory(SataReadDirPacketBody),
518 ReadFile(SataReadFilePacketBody),
520 Remove(SataRemovePacketBody),
522 Rewind(SataRewindDirPacketBody),
524 StatFile(SataStatFilePacketBody),
526 WriteFile(SataWriteFilePacketBody),
528}
529
530impl SataRequestBody {
531 pub fn parse_command(command: u32, body: Bytes) -> Result<SataRequestBody, NetworkParseError> {
538 match command {
539 0x0 => {
540 SataCreateDirectoryPacketBody::try_from(body).map(SataRequestBody::CreateDirectory)
541 }
542 0x1 => SataOpenFolderPacketBody::try_from(body).map(SataRequestBody::OpenFolder),
543 0x2 => SataReadDirPacketBody::try_from(body).map(SataRequestBody::ReadDirectory),
544 0x3 => SataRewindDirPacketBody::try_from(body).map(SataRequestBody::Rewind),
545 0x4 => SataCloseFolderPacketBody::try_from(body).map(SataRequestBody::CloseFolder),
546 0x5 => SataOpenFilePacketBody::try_from(body).map(SataRequestBody::OpenFile),
547 0x6 => SataReadFilePacketBody::try_from(body).map(SataRequestBody::ReadFile),
548 0x7 => SataWriteFilePacketBody::try_from(body).map(SataRequestBody::WriteFile),
549 0xB => SataStatFilePacketBody::try_from(body).map(SataRequestBody::StatFile),
550 0xD => SataCloseFilePacketBody::try_from(body).map(SataRequestBody::CloseFile),
551 0xE => SataRemovePacketBody::try_from(body).map(SataRequestBody::Remove),
552 0x10 => {
553 SataGetInfoByQueryPacketBody::try_from(body).map(SataRequestBody::GetInfoByQuery)
554 }
555 0x12 => SataChangeOwnerPacketBody::try_from(body).map(SataRequestBody::ChangeOwner),
556 0x13 => SataChangeModePacketBody::try_from(body).map(SataRequestBody::ChangeMode),
557 0x14 => SataPingPacketBody::try_from(body).map(SataRequestBody::Ping),
558 val => {
559 debug!(
560 command.id = command,
561 command.leftover = format!("{:02X?}", body),
562 "Unknown SATA Command ID..."
563 );
564 Err(SataProtocolError::UnknownPacketType(val).into())
565 }
566 }
567 }
568}
569
570fn construct_sata_response<Ty: Into<Bytes>>(
572 request_header: &SataPacketHeader,
573 flags: u32,
574 body: Ty,
575) -> Result<Bytes, PCFSApiError> {
576 let body_as_bytes: Bytes = body.into();
577 let mut new_buff = BytesMut::with_capacity(0x20 + body_as_bytes.len());
578
579 new_buff.put_u32(
580 u32::try_from(body_as_bytes.len())
581 .map_err(|_| PCFSApiError::PacketTooLargeForSata(body_as_bytes.len()))?,
582 );
583 new_buff.put_u32(request_header.packet_id);
584 new_buff.put_u32(flags);
585 new_buff.put_u32(if request_header.version != 0 {
587 request_header.version
588 } else {
589 DEFAULT_PCFS_VERSION
590 });
591 new_buff.put_u32(
593 u32::try_from(
594 SystemTime::now()
595 .duration_since(SystemTime::UNIX_EPOCH)
596 .unwrap_or(Duration::from_secs(0))
597 .as_secs()
598 .rem_euclid(u64::from(u32::MAX)),
599 )
600 .unwrap_or(u32::MAX),
601 );
602 new_buff.put_u32(*PID);
604 new_buff.extend([0; 8]);
605 new_buff.extend(body_as_bytes);
607
608 Ok(new_buff.freeze())
609}
610
611#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Valuable)]
613pub enum MoveToFileLocation {
614 Begin,
616 Current,
618 End,
620}
621
622impl From<&MoveToFileLocation> for u32 {
623 fn from(value: &MoveToFileLocation) -> u32 {
624 match *value {
625 MoveToFileLocation::Begin => 0,
626 MoveToFileLocation::Current => 1,
627 MoveToFileLocation::End => 2,
628 }
629 }
630}
631
632impl From<MoveToFileLocation> for u32 {
633 fn from(value: MoveToFileLocation) -> u32 {
634 Self::from(&value)
635 }
636}
637
638impl TryFrom<u32> for MoveToFileLocation {
639 type Error = SataProtocolError;
640
641 fn try_from(value: u32) -> Result<Self, Self::Error> {
642 match value {
643 0 => Ok(Self::Begin),
644 1 => Ok(Self::Current),
645 2 => Ok(Self::End),
646 val => Err(SataProtocolError::UnknownFileLocation(val)),
647 }
648 }
649}
650
651#[cfg(test)]
652mod unit_tests {
653 use super::*;
654 use crate::fsemul::pcfs::SataCapabilitiesFlags;
655
656 #[test]
657 pub fn move_to_file_location_conversions() {
658 for mtfl in vec![
659 MoveToFileLocation::Begin,
660 MoveToFileLocation::Current,
661 MoveToFileLocation::End,
662 ] {
663 assert_eq!(
664 mtfl,
665 MoveToFileLocation::try_from(u32::from(mtfl))
666 .expect("MTFL turned into u32 could not be parsed"),
667 "MoveToFileLocation wasn't the same after being converted back n forth!",
668 );
669 }
670 }
671
672 #[test]
673 pub fn decode_real_ping_packet() {
674 let packet = SataRequest::try_from(Bytes::from(vec![
675 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00,
676 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
677 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x40, 0x00, 0x03, 0x00, 0x20, 0x00, 0x00,
678 0x51, 0xAD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14,
679 ]))
680 .expect("Failed to parse ping request packet!");
681
682 assert_eq!(packet.header().data_len(), 0x14);
683 assert_eq!(packet.header().id(), 0);
684 assert_eq!(
685 packet.header().flags(),
686 (SataCapabilitiesFlags::FAST_FILE_IO_SUPPORTED
687 | SataCapabilitiesFlags::COMBINED_SEND_RECV_SUPPORTED)
688 .0
689 );
690 assert_eq!(packet.header().host_pid(), 0);
691 assert_eq!(packet.header().raw_timestamp_on_host(), 0);
692 assert_eq!(packet.header().version(), 0);
694 assert_eq!(packet.command_info().command(), 0x14);
695 assert!(matches!(packet.body(), &SataRequestBody::Ping(_)));
696 }
697
698 #[test]
699 pub fn decode_real_query_info_packet() {
700 let packet = SataRequest::try_from(Bytes::from(vec![
701 0x00, 0x00, 0x02, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x02, 0x00,
702 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
703 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01,
704 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x2f, 0x25, 0x53, 0x4c,
705 0x43, 0x5f, 0x45, 0x4d, 0x55, 0x5f, 0x44, 0x49, 0x52, 0x2f, 0x73, 0x79, 0x73, 0x00,
706 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
707 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
708 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
709 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
710 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
711 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
712 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
713 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
714 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
715 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
716 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
717 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
718 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
719 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
720 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
721 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
722 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
723 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
724 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
725 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
726 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
727 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
728 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
729 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
730 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
731 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
732 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
733 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
734 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
735 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
736 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
737 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
738 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
739 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
740 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
741 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05,
742 ]))
743 .expect("Failed to parse query info request packet!");
744
745 assert_eq!(packet.command_info().command(), 0x10);
746 let SataRequestBody::GetInfoByQuery(ref body) = packet.body() else {
747 panic!("GetInfoByQuery packet somehow wasn't a GetInfoByQuery??");
748 };
749 assert_eq!(body.query_type(), QueryType::FileDetails);
750 assert_eq!(body.path(), "/%SLC_EMU_DIR/sys");
751 }
752
753 #[test]
754 pub fn decode_real_change_mode_packet() {
755 let packet = SataRequest::try_from(Bytes::from(vec![
756 0x00, 0x00, 0x02, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00,
757 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
758 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01,
759 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x2f, 0x25, 0x53, 0x4c,
760 0x43, 0x5f, 0x45, 0x4d, 0x55, 0x5f, 0x44, 0x49, 0x52, 0x2f, 0x73, 0x79, 0x73, 0x2f,
761 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
762 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
763 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
764 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
765 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
766 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
767 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
768 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
769 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
770 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
771 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
772 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
773 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
774 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
775 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
776 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
777 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
778 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
779 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
780 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
781 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
782 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
783 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
784 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
785 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
786 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
787 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
788 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
789 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
790 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
791 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
792 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
793 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
794 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
795 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
796 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
797 ]))
798 .expect("Failed to parse change mode request packet!");
799
800 assert_eq!(packet.command_info().command(), 0x13);
801 let SataRequestBody::ChangeMode(ref body) = packet.body() else {
802 panic!("ChangeMode packet somehow wasn't a ChangeMode??");
803 };
804 assert_eq!(body.path(), "/%SLC_EMU_DIR/sys/config");
805 assert!(!body.set_write_mode());
806 }
807
808 #[test]
809 pub fn decode_open_file_packet() {
810 let packet = SataRequest::try_from(Bytes::from(vec![
811 0x00, 0x00, 0x02, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00,
812 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
813 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01,
814 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x72, 0x00, 0x53, 0x4c,
815 0x43, 0x5f, 0x45, 0x4d, 0x55, 0x5f, 0x44, 0x49, 0x52, 0x2f, 0x73, 0x79, 0x2f, 0x25,
816 0x53, 0x4c, 0x43, 0x5f, 0x45, 0x4d, 0x55, 0x5f, 0x44, 0x49, 0x52, 0x2f, 0x73, 0x79,
817 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2f, 0x73, 0x79, 0x73, 0x74, 0x65,
818 0x6d, 0x2e, 0x78, 0x6d, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
819 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
820 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
821 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
822 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
823 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
824 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
825 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
826 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
827 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
828 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
829 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
830 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
831 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
832 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
833 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
834 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
835 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
836 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
837 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
838 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
839 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
840 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
841 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
842 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
843 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
844 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
845 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
846 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
847 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
848 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
849 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
850 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
851 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
852 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
853 ]))
854 .expect("Failed to parse open file request packet!");
855
856 assert_eq!(packet.command_info().command(), 0x5);
857 let SataRequestBody::OpenFile(ref body) = packet.body() else {
858 panic!("OpenFile packet somehow wasn't an OpenFile??");
859 };
860
861 assert_eq!(body.mode(), "r");
862 assert_eq!(body.path(), "/%SLC_EMU_DIR/sys/config/system.xml");
863 }
864
865 #[test]
866 pub fn decode_read_file_packet() {
867 let packet = SataRequest::try_from(Bytes::from(vec![
868 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00,
869 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
870 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01,
871 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x01,
872 0x00, 0x00, 0x05, 0x1b, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
873 0x00, 0x00,
874 ]))
875 .expect("Failed to parse open file request packet!");
876
877 assert_eq!(packet.command_info().command(), 0x6);
878 let SataRequestBody::ReadFile(ref body) = packet.body() else {
879 panic!("ReadFile packet somehow wasn't an ReadFile??");
880 };
881
882 assert_eq!(body.block_count(), 1);
883 assert_eq!(body.block_size(), 0x51B);
884 assert_eq!(body.file_descriptor(), 2);
885 assert_eq!(body.move_to_pointer(), MoveToFileLocation::Begin);
886 assert_eq!(body.should_move(), false);
887 }
888
889 #[test]
890 pub fn decode_close_file_packet() {
891 let packet = SataRequest::try_from(Bytes::from(vec![
892 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00,
893 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
894 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01,
895 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x02,
896 ]))
897 .expect("Failed to parse close file request packet!");
898
899 assert_eq!(packet.command_info().command(), 0xD);
900 let SataRequestBody::CloseFile(ref body) = packet.body() else {
901 panic!("CloseFile packet somehow wasn't a CloseFile??");
902 };
903
904 assert_eq!(body.file_descriptor(), 2);
905 }
906
907 #[test]
908 pub fn decode_open_folder_packet() {
909 let packet = SataRequest::try_from(Bytes::from(vec![
910 0x00, 0x00, 0x02, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00,
911 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
912 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01,
913 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x2f, 0x25, 0x4d, 0x4c,
914 0x43, 0x5f, 0x45, 0x4d, 0x55, 0x5f, 0x44, 0x49, 0x52, 0x2f, 0x75, 0x73, 0x72, 0x2f,
915 0x74, 0x6d, 0x70, 0x00, 0x70, 0x2e, 0x78, 0x6d, 0x6c, 0x00, 0x52, 0x2f, 0x73, 0x79,
916 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2f, 0x73, 0x79, 0x73, 0x74, 0x65,
917 0x6d, 0x2e, 0x78, 0x6d, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
918 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
919 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
920 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
921 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
922 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
923 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
924 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
925 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
926 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
927 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
928 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
929 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
930 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
931 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
932 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
933 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
934 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
935 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
936 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
937 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
938 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
939 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
940 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
941 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
942 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
943 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
944 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
945 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
946 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
947 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
948 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
949 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
950 0x00, 0x00, 0x00, 0x00,
951 ]))
952 .expect("Failed to parse open folder request packet!");
953
954 assert_eq!(packet.command_info().command(), 0x1);
955 let SataRequestBody::OpenFolder(ref body) = packet.body() else {
956 panic!("OpenFolder packet somehow wasn't an OpenFolder??");
957 };
958 assert_eq!(body.path(), "/%MLC_EMU_DIR/usr/tmp");
959 }
960
961 #[test]
962 pub fn decode_read_folder_packet() {
963 let packet = SataRequest::try_from(Bytes::from(vec![
964 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00,
965 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
966 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01,
967 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01,
968 ]))
969 .expect("Failed to parse close file request packet!");
970
971 assert_eq!(packet.command_info().command(), 0x2);
972 let SataRequestBody::ReadDirectory(ref body) = packet.body() else {
973 panic!("ReadDirectory packet somehow wasn't a ReadDirectory??");
974 };
975
976 assert_eq!(body.file_descriptor(), 1);
977 }
978
979 #[test]
980 pub fn decode_close_folder_packet() {
981 let packet = SataRequest::try_from(Bytes::from(vec![
982 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00,
983 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
984 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01,
985 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01,
986 ]))
987 .expect("Failed to parse close folder request packet!");
988
989 assert_eq!(packet.command_info().command(), 0x04);
990 let SataRequestBody::CloseFolder(ref body) = packet.body() else {
991 panic!("CloseFolder packet somehow wasn't a CloseFolder??");
992 };
993
994 assert_eq!(body.file_descriptor(), 1);
995 }
996}