cat_dev/fsemul/pcfs/sata_proto/
mod.rs

1//! Protocols for the SATA 'PCFS' Protocol.
2//!
3//! This top level function is just all of the things that are common across
4//! the entire protocol, each packet serializer/deserializer/handler is kept
5//! in it's own sub-file.
6
7mod 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
47/// The Default PCFS Version we claim to be.
48const DEFAULT_PCFS_VERSION: u32 = 0x0200_0600;
49/// Our current process id.
50static PID: LazyLock<u32> = LazyLock::new(std::process::id);
51
52/// A codec that chunks a stream into full PCFS packets.
53///
54/// PCFS will have a data header of `0x20` bytes, and then a data length
55/// defined as the first four bytes in a packet.
56///
57/// This chunker also allows an `AtomicUsize` that allows bypassing ALL checks
58/// for a packet, and just returning N number of bytes. This is useful for when
59/// we are using Fast File I/O, and need to bypass all the shennagins going on.,
60#[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		// We don't yet have a complete header...
78		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		// It may be tempting to check for sizes here. In fact I had it do so
98		// originally.
99		//
100		// Unfortunately FFIO packets need to bypass these checks in order to send
101		// file contents, and there's no real good way to signify that.
102		dst.reserve(item.len());
103		dst.extend(item);
104		Ok(())
105	}
106}
107
108/// A full sata packet request, containing both the header + body.
109#[derive(Clone, Debug, PartialEq, Eq)]
110pub struct SataRequest {
111	/// The header of the SATA packet.
112	header: SataPacketHeader,
113	/// The information about the body that follows.
114	command_info: SataCommandInfo,
115	/// The body of the SATA packet.
116	body: SataRequestBody,
117}
118
119impl SataRequest {
120	/// Get the header of this particular request.
121	#[must_use]
122	pub const fn header(&self) -> &SataPacketHeader {
123		&self.header
124	}
125
126	/// Get the command information for this particular request.
127	#[must_use]
128	pub const fn command_info(&self) -> &SataCommandInfo {
129		&self.command_info
130	}
131
132	/// Get the body of this particular request.
133	#[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 we're less than 32 bytes long, we don't even have a header...
144		// who knows what we got.
145		//
146		// If we are less than 52 bytes, we're missing a 'command info' block.
147		// which makes it hard to know how to actually parse it.
148		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/// The header for all of our SATA PCFS packets.
202#[derive(Clone, Debug, PartialEq, Eq)]
203pub struct SataPacketHeader {
204	/// The length of the packet after the header.
205	packet_data_len: u32,
206	/// An "ID" for packets, THIS IS NOT THE PACKET TYPE.
207	///
208	/// This seemingly more used to correlate requests/responses on the client
209	/// side incase it sends multiple packets at once.
210	packet_id: u32,
211	/// A bitfield of various flags in the header.
212	///
213	/// These flags depend on the request command.
214	flags: u32,
215	/// The version of the PCFS protocol to use.
216	///
217	/// *note: this will be 0 at start, and then be filled in by the first
218	/// server response.*
219	version: u32,
220	/// The timestamp of the host. Yes, they are not ready for the 32 bit
221	/// overflow.
222	///
223	/// For us we actually end up *wrapping* around on our timestamp.
224	timestamp_on_host: u32,
225	/// The pid opf the running host process.
226	pid_on_host: u32,
227	// There are techincally 8 bytes of 0x0 that are used for 'padding'
228}
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	/// A "timestamp on the host".
257	///
258	/// Because this timestamp is *NOT* 32 bit epoch safe. These timestamps
259	/// may be *VERY WRONG* on purpose if it's past the year 2038. Don't come
260	/// blame me. Blame nintendo.
261	#[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	/// Non-host packets should have empty fields for timestamp on host, and
274	/// pid.
275	///
276	/// ## Errors
277	///
278	/// If the non host side of a connection error'd.
279	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	/// Logged as "user:" in various log messages.
374	user: (u32, u32),
375	/// Logged as "cap:" in various log messages.
376	capabilities: (u32, u32),
377	/// The 'command' to run, aka packet id.
378	command: u32,
379}
380
381impl SataCommandInfo {
382	/// Get the user bytes for this command.
383	#[must_use]
384	pub const fn user(&self) -> (u32, u32) {
385		self.user
386	}
387
388	/// Get the capabilities for this command.
389	///
390	/// In log messages the first byte covers capabilities:
391	/// 1,2,4,5. While the second byte contains "capability 3" which seems to be
392	/// forcing disabling of FFIO/CSR.
393	#[must_use]
394	pub const fn capabilities(&self) -> (u32, u32) {
395		self.capabilities
396	}
397
398	/// Get the actual 'packet id', or command.
399	#[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/// All of the potential types of bodies when reading from a PCFS Sata channel.
496#[derive(Clone, Debug, PartialEq, Eq, Valuable)]
497pub enum SataRequestBody {
498	/// A request to set readonly/not-readonly for a path.
499	ChangeMode(SataChangeModePacketBody),
500	/// A request to change the active owner for a path, always errors.
501	ChangeOwner(SataChangeOwnerPacketBody),
502	/// A request to create a directory.
503	CreateDirectory(SataCreateDirectoryPacketBody),
504	/// Close an already open file.
505	CloseFile(SataCloseFilePacketBody),
506	/// Close an already open directory.
507	CloseFolder(SataCloseFolderPacketBody),
508	/// A request to get information about a particular file path.
509	GetInfoByQuery(SataGetInfoByQueryPacketBody),
510	/// A request to open a file.
511	OpenFile(SataOpenFilePacketBody),
512	/// A request to open a folder, and it's directory contents.
513	OpenFolder(SataOpenFolderPacketBody),
514	/// A ping request coming in.
515	Ping(SataPingPacketBody),
516	/// List the files, and folders within a directory (non recursive).
517	ReadDirectory(SataReadDirPacketBody),
518	/// A request to read actual bytes from a file.
519	ReadFile(SataReadFilePacketBody),
520	/// A request to remove a file from the filesystem.
521	Remove(SataRemovePacketBody),
522	/// A request to rewind a directory iterator.
523	Rewind(SataRewindDirPacketBody),
524	/// Get file information about a particular file.
525	StatFile(SataStatFilePacketBody),
526	/// Write to a file that is already open.
527	WriteFile(SataWriteFilePacketBody),
528}
529
530impl SataRequestBody {
531	/// Parse the body of a SATA request coming in.
532	///
533	/// ## Errors
534	///
535	/// If we cannot successfully parse the command because it is corrupt, or
536	/// in another way invalid.
537	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
570/// Construct a response to send as a response to a SATA packet.
571fn 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	// Yeah bro we're totally the same version...
586	new_buff.put_u32(if request_header.version != 0 {
587		request_header.version
588	} else {
589		DEFAULT_PCFS_VERSION
590	});
591	// Calculate epoch, and wrap around...
592	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	// Put our PID, and appropriate padding.
603	new_buff.put_u32(*PID);
604	new_buff.extend([0; 8]);
605	// Now we can finally add our body!
606	new_buff.extend(body_as_bytes);
607
608	Ok(new_buff.freeze())
609}
610
611/// Move to a particular location inside of a file.
612#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Valuable)]
613pub enum MoveToFileLocation {
614	/// Move to the beginning of the file.
615	Begin,
616	/// Move nowhere.
617	Current,
618	/// Move to the end of a file.
619	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		// This ping packet was the first packet, so no version was set yet.
693		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}