cat_dev/fsemul/pcfs/sata/
client.rs

1//! Client implementation for SATA over PCFS.
2
3use crate::{
4	errors::{CatBridgeError, NetworkError, NetworkParseError},
5	fsemul::pcfs::{
6		errors::SataProtocolError,
7		sata::proto::{
8			DirectoryItemResponse, MoveToFileLocation, SataCapabilitiesFlags,
9			SataChangeModePacketBody, SataChangeOwnerPacketBody, SataCloseFilePacketBody,
10			SataCloseFolderPacketBody, SataCommandInfo, SataCreateFolderPacketBody, SataFDInfo,
11			SataFileDescriptorResult, SataGetInfoByQueryPacketBody, SataOpenFilePacketBody,
12			SataPacketHeader, SataPingPacketBody, SataPongBody, SataQueryResponse, SataQueryType,
13			SataReadFilePacketBody, SataReadFolderPacketBody, SataRemovePacketBody, SataRequest,
14			SataResponse, SataResultCode, SataRewindFolderPacketBody,
15			SataSetFilePositionPacketBody, SataStatFilePacketBody, SataWriteFilePacketBody,
16		},
17	},
18	net::{
19		client::TCPClient,
20		models::{Endianness, NagleGuard},
21	},
22};
23use bytes::{Buf, Bytes, BytesMut};
24use std::{
25	sync::{
26		Arc,
27		atomic::{AtomicBool, AtomicU32, Ordering},
28	},
29	time::Duration,
30};
31use tokio::net::ToSocketAddrs;
32use valuable::Valuable;
33
34/// Default PCFS-SATA Client timeout to use when we don't know where one is.
35pub const DEFAULT_CLIENT_TIMEOUT: Duration = Duration::from_secs(30);
36
37/// A connection to a sata pcfs server.
38#[derive(Debug, Valuable)]
39pub struct SataClient {
40	/// If we're actively supporting "CSR", or "Combined Send/Recv".
41	supports_csr: Arc<AtomicBool>,
42	/// If we're acctively supporting "FFIO", or "Fast File I/O".
43	supports_ffio: Arc<AtomicBool>,
44	/// Means something different to the official server, but for us means
45	/// when padding stops being added.
46	first_read_size: Arc<AtomicU32>,
47	/// Means something different to the official server, but for us means
48	/// when padding stops being added.
49	first_write_size: Arc<AtomicU32>,
50	/// The TCP Client we're warpping.
51	underlying_client: TCPClient,
52}
53
54impl SataClient {
55	/// Connect to a PCFS Sata server.
56	///
57	/// ## Errors
58	///
59	/// This errors when the client cannot connect to the server.
60	pub async fn connect<AddrTy: ToSocketAddrs>(
61		address: AddrTy,
62		supports_csr: bool,
63		supports_ffio: bool,
64		first_read_size: u32,
65		first_write_size: u32,
66		trace_io_during_debug: bool,
67	) -> Result<Self, CatBridgeError> {
68		let client = TCPClient::new(
69			"pcfs-sata",
70			NagleGuard::U32LengthPrefixed(Endianness::Big, None),
71			(None, None),
72			trace_io_during_debug,
73		);
74		client.connect(address).await?;
75
76		let this = Self {
77			supports_csr: Arc::new(AtomicBool::new(supports_csr)),
78			supports_ffio: Arc::new(AtomicBool::new(supports_ffio)),
79			underlying_client: client,
80			first_read_size: Arc::new(AtomicU32::new(first_read_size)),
81			first_write_size: Arc::new(AtomicU32::new(first_write_size)),
82		};
83		this.ping(Some(DEFAULT_CLIENT_TIMEOUT)).await?;
84
85		Ok(this)
86	}
87
88	/// Attempt to update Combined Send/Recv & Fast File I/O flags.
89	///
90	/// This may fail if setting to 'true', as both the server, and client have
91	/// to support, and agree to these flags in order for them to be supported.
92	///
93	/// ## Errors
94	///
95	/// If we cannot negotiate with the server to update combined send/recv.
96	pub async fn try_set_csr_ffio(&self, csr: bool, ffio: bool) -> Result<(), CatBridgeError> {
97		self.supports_csr.store(csr, Ordering::Release);
98		self.supports_ffio.store(ffio, Ordering::Release);
99		self.ping(None).await?;
100		Ok(())
101	}
102
103	/// Attempt to update combined send/recv flag or "CSR".
104	///
105	/// This may fail if setting to 'true', as both the server, and client have
106	/// to support, and agree to support Combined Send/Recv.
107	///
108	/// ## Errors
109	///
110	/// If we cannot negotiate with the server to update combined send/recv.
111	pub async fn try_set_csr(&self, csr: bool) -> Result<(), CatBridgeError> {
112		self.supports_csr.store(csr, Ordering::Release);
113		self.ping(None).await?;
114		Ok(())
115	}
116
117	/// Attempt to update fast-file i/o or "FFIO".
118	///
119	/// This may fail if setting to 'true', as both the server, and client have
120	/// to support, and agree to support FFIO.
121	///
122	/// ## Errors
123	///
124	/// If we cannot negotiate with the server to update combined send/recv.
125	pub async fn try_set_ffio(&self, ffio: bool) -> Result<(), CatBridgeError> {
126		self.supports_ffio.store(ffio, Ordering::Release);
127		self.ping(None).await?;
128		Ok(())
129	}
130
131	/// Perform a 'ping' on the remote host, and update our CSR/FFIO flag state.
132	///
133	/// ## Errors
134	///
135	/// If we cannot send a request to our upstream PCFS server, or if we cannot
136	/// read a response back in the timeout specified.
137	pub async fn ping(&self, timeout: Option<Duration>) -> Result<(), CatBridgeError> {
138		let mut flags = SataCapabilitiesFlags::empty();
139		if self.supports_csr.load(Ordering::Acquire) {
140			flags = flags.union(SataCapabilitiesFlags::COMBINED_SEND_RECV_SUPPORTED);
141		}
142		if self.supports_ffio.load(Ordering::Acquire) {
143			flags = flags.union(SataCapabilitiesFlags::FAST_FILE_IO_SUPPORTED);
144		}
145
146		let mut req = Self::construct(0x14, SataPingPacketBody::new());
147		req.command_info_mut().set_user((
148			self.first_read_size.load(Ordering::Acquire),
149			self.first_write_size.load(Ordering::Acquire),
150		));
151		req.command_info_mut().set_capabilities((u32::MAX, 0));
152		req.header_mut().set_flags(flags.0);
153		let (_stream_id, _req_id, opt_response) = self
154			.underlying_client
155			.send(req, Some(timeout.unwrap_or(DEFAULT_CLIENT_TIMEOUT)))
156			.await?;
157		let response = opt_response.ok_or(NetworkError::ExpectedData)?;
158		let pong = SataResponse::<SataPongBody>::try_from(
159			response.take_body().ok_or(NetworkError::ExpectedData)?,
160		)?;
161		self.supports_ffio
162			.store(pong.body().ffio_enabled(), Ordering::Release);
163		self.supports_csr
164			.store(pong.body().combined_send_recv_enabled(), Ordering::Release);
165
166		Ok(())
167	}
168
169	/// Mark a file as 'read-only', or 'writable'.
170	///
171	/// In the future someday we may allow full change mode flags of unix-style
172	/// flags. Unfortunately because this is based off of windows code you have
173	/// only a read-only vs writable flag.
174	///
175	/// ## Errors
176	///
177	/// - If the path name is too long to be serialized.
178	/// - If we cannot send the request to the PCFS Sata server.
179	/// - If we cannot receive a response from the PCFS Sata server.
180	/// - If we cannot parse the response from the PCFS Sata server.
181	/// - If the return code of the response was not successful.
182	pub async fn change_mode(
183		&self,
184		path: String,
185		writable: bool,
186		timeout: Option<Duration>,
187	) -> Result<(), CatBridgeError> {
188		let resp = self
189			.underlying_client
190			.send(
191				Self::construct(0x13, SataChangeModePacketBody::new(path, writable)?),
192				Some(timeout.unwrap_or(DEFAULT_CLIENT_TIMEOUT)),
193			)
194			.await?
195			.2
196			.ok_or(NetworkError::ExpectedData)?
197			.take_body()
198			.ok_or(NetworkError::ExpectedData)?;
199
200		let sata_resp = SataResponse::<SataResultCode>::try_from(resp)?;
201		if sata_resp.body().0 != 0 {
202			return Err(NetworkParseError::ErrorCode(sata_resp.body().0).into());
203		}
204		Ok(())
205	}
206
207	/// Change the user owner, and group owner of a file.
208	///
209	/// NOTE(mythra): this will always error when talking with a PCFS compatible
210	/// server.
211	///
212	/// ## Errors
213	///
214	/// - If the path name is too long to be serialized.
215	/// - If we cannot send the request to the PCFS Sata server.
216	/// - If we cannot receive a response from the PCFS Sata server.
217	/// - If we cannot parse the response from the PCFS Sata server.
218	/// - If the return code of the response was not successful.
219	pub async fn change_owner(
220		&self,
221		path: String,
222		owner: u32,
223		group: u32,
224		timeout: Option<Duration>,
225	) -> Result<(), CatBridgeError> {
226		let resp = self
227			.underlying_client
228			.send(
229				Self::construct(0x12, SataChangeOwnerPacketBody::new(path, owner, group)?),
230				Some(timeout.unwrap_or(DEFAULT_CLIENT_TIMEOUT)),
231			)
232			.await?
233			.2
234			.ok_or(NetworkError::ExpectedData)?
235			.take_body()
236			.ok_or(NetworkError::ExpectedData)?;
237
238		let sata_resp = SataResponse::<SataResultCode>::try_from(resp)?;
239		if sata_resp.body().0 != 0 {
240			return Err(NetworkParseError::ErrorCode(sata_resp.body().0).into());
241		}
242		Ok(())
243	}
244
245	/// Create a new folder on the remote PCFS server.
246	///
247	/// ## Errors
248	///
249	/// - If the path name is too long to be serialized.
250	/// - If we cannot send the request to the PCFS Sata server.
251	/// - If we cannot receive a response from the PCFS Sata server.
252	/// - If we cannot parse the response from the PCFS Sata server.
253	/// - If the return code of the response was not successful.
254	pub async fn create_folder(
255		&self,
256		path: String,
257		writable: bool,
258		timeout: Option<Duration>,
259	) -> Result<(), CatBridgeError> {
260		let resp = self
261			.underlying_client
262			.send(
263				Self::construct(0x0, SataCreateFolderPacketBody::new(path, writable)?),
264				Some(timeout.unwrap_or(DEFAULT_CLIENT_TIMEOUT)),
265			)
266			.await?
267			.2
268			.ok_or(NetworkError::ExpectedData)?
269			.take_body()
270			.ok_or(NetworkError::ExpectedData)?;
271
272		let sata_resp = SataResponse::<SataResultCode>::try_from(resp)?;
273		if sata_resp.body().0 != 0 {
274			return Err(NetworkParseError::ErrorCode(sata_resp.body().0).into());
275		}
276		Ok(())
277	}
278
279	/// Query a particular path for information about it.
280	///
281	/// In general prefer more specific query types, as opposed to this
282	/// particular "grab-bag" of an interface, which will safely wrap this
283	/// function and choose the right kind of return type.
284	///
285	/// ## Errors
286	///
287	/// - If the path name is too long to be serialized.
288	/// - If we cannot send the request to the PCFS Sata server.
289	/// - If we cannot receive a response from the PCFS Sata server.
290	/// - If we cannot parse the response from the PCFS Sata server.
291	/// - If the return code of the response was not successful.
292	pub async fn info_by_query(
293		&self,
294		path: String,
295		query_type: SataQueryType,
296		timeout: Option<Duration>,
297	) -> Result<SataQueryResponse, CatBridgeError> {
298		let resp = self
299			.underlying_client
300			.send(
301				Self::construct(0x10, SataGetInfoByQueryPacketBody::new(path, query_type)?),
302				Some(timeout.unwrap_or(DEFAULT_CLIENT_TIMEOUT)),
303			)
304			.await?
305			.2
306			.ok_or(NetworkError::ExpectedData)?
307			.take_body()
308			.ok_or(NetworkError::ExpectedData)?;
309
310		let (_header, bytes) = SataResponse::<Bytes>::parse_opaque(resp)?.to_parts();
311
312		let typed_response = match query_type {
313			SataQueryType::FileCount => SataQueryResponse::try_from_small(bytes)?,
314			SataQueryType::FileDetails => SataQueryResponse::try_from_fd_info(bytes)?,
315			SataQueryType::FreeDiskSpace | SataQueryType::SizeOfFolder => {
316				SataQueryResponse::try_from_large(bytes)?
317			}
318		};
319		if let SataQueryResponse::ErrorCode(ec) = typed_response {
320			return Err(NetworkParseError::ErrorCode(ec).into());
321		}
322
323		Ok(typed_response)
324	}
325
326	/// Get the amount of files within a particular folder.
327	///
328	/// ## Errors
329	///
330	/// - If the path name is too long to be serialized.
331	/// - If we cannot send the request to the PCFS Sata server.
332	/// - If we cannot receive a response from the PCFS Sata server.
333	/// - If we cannot parse the response from the PCFS Sata server.
334	/// - If the return code of the response was not successful.
335	pub async fn file_count(
336		&self,
337		path: String,
338		timeout: Option<Duration>,
339	) -> Result<u32, CatBridgeError> {
340		let final_response = self
341			.info_by_query(path, SataQueryType::FileCount, timeout)
342			.await?;
343
344		match final_response {
345			SataQueryResponse::ErrorCode(_) => unreachable!("Checked in info_by_query"),
346			SataQueryResponse::FDInfo(_) | SataQueryResponse::LargeSize(_) => {
347				Err(SataProtocolError::WrongSataQueryResponse(final_response).into())
348			}
349			SataQueryResponse::SmallSize(smol) => Ok(smol),
350		}
351	}
352
353	/// Get the amount of free disk space left on whatever disk the path is on.
354	///
355	/// ## Errors
356	///
357	/// - If the path name is too long to be serialized.
358	/// - If we cannot send the request to the PCFS Sata server.
359	/// - If we cannot receive a response from the PCFS Sata server.
360	/// - If we cannot parse the response from the PCFS Sata server.
361	/// - If the return code of the response was not successful.
362	pub async fn free_disk_space(
363		&self,
364		path: String,
365		timeout: Option<Duration>,
366	) -> Result<u64, CatBridgeError> {
367		let final_response = self
368			.info_by_query(path, SataQueryType::FreeDiskSpace, timeout)
369			.await?;
370
371		match final_response {
372			SataQueryResponse::ErrorCode(_) => unreachable!("Checked in info_by_query"),
373			SataQueryResponse::FDInfo(_) | SataQueryResponse::SmallSize(_) => {
374				Err(SataProtocolError::WrongSataQueryResponse(final_response).into())
375			}
376			SataQueryResponse::LargeSize(lorg) => Ok(lorg),
377		}
378	}
379
380	/// Get the total amount of space being taken by a folder.
381	///
382	/// ## Errors
383	///
384	/// - If the path name is too long to be serialized.
385	/// - If we cannot send the request to the PCFS Sata server.
386	/// - If we cannot receive a response from the PCFS Sata server.
387	/// - If we cannot parse the response from the PCFS Sata server.
388	/// - If the return code of the response was not successful.
389	pub async fn folder_size(
390		&self,
391		path: String,
392		timeout: Option<Duration>,
393	) -> Result<u64, CatBridgeError> {
394		let final_response = self
395			.info_by_query(path, SataQueryType::SizeOfFolder, timeout)
396			.await?;
397
398		match final_response {
399			SataQueryResponse::ErrorCode(_) => unreachable!("Checked in info_by_query"),
400			SataQueryResponse::FDInfo(_) | SataQueryResponse::SmallSize(_) => {
401				Err(SataProtocolError::WrongSataQueryResponse(final_response).into())
402			}
403			SataQueryResponse::LargeSize(lorg) => Ok(lorg),
404		}
405	}
406
407	/// Get the information about a particular file path.
408	///
409	/// ## Errors
410	///
411	/// - If the path name is too long to be serialized.
412	/// - If we cannot send the request to the PCFS Sata server.
413	/// - If we cannot receive a response from the PCFS Sata server.
414	/// - If we cannot parse the response from the PCFS Sata server.
415	/// - If the return code of the response was not successful.
416	pub async fn path_info(
417		&self,
418		path: String,
419		timeout: Option<Duration>,
420	) -> Result<SataFDInfo, CatBridgeError> {
421		let final_response = self
422			.info_by_query(path, SataQueryType::FileDetails, timeout)
423			.await?;
424
425		match final_response {
426			SataQueryResponse::ErrorCode(_) => unreachable!("Checked in info_by_query"),
427			SataQueryResponse::LargeSize(_) | SataQueryResponse::SmallSize(_) => {
428				Err(SataProtocolError::WrongSataQueryResponse(final_response).into())
429			}
430			SataQueryResponse::FDInfo(info) => Ok(info),
431		}
432	}
433
434	/// Remove a file or folder on the host filesystem.
435	///
436	/// ## Errors
437	///
438	/// - If the path name is too long to be serialized.
439	/// - If we cannot send the request to the PCFS Sata server.
440	/// - If we cannot receive a response from the PCFS Sata server.
441	/// - If we cannot parse the response from the PCFS Sata server.
442	/// - If the return code of the response was not successful.
443	pub async fn remove(
444		&self,
445		path: String,
446		timeout: Option<Duration>,
447	) -> Result<(), CatBridgeError> {
448		let resp = self
449			.underlying_client
450			.send(
451				Self::construct(0xE, SataRemovePacketBody::new(path)?),
452				Some(timeout.unwrap_or(DEFAULT_CLIENT_TIMEOUT)),
453			)
454			.await?
455			.2
456			.ok_or(NetworkError::ExpectedData)?
457			.take_body()
458			.ok_or(NetworkError::ExpectedData)?;
459
460		let sata_resp = SataResponse::<SataResultCode>::try_from(resp)?;
461		if sata_resp.body().0 != 0 {
462			return Err(NetworkParseError::ErrorCode(sata_resp.body().0).into());
463		}
464		Ok(())
465	}
466
467	/// Attempt to open a file on the remote host.
468	///
469	/// This will return a file handle that you can then call read, write, etc.
470	/// on. You will have to call `close()` on the file handle specifically.
471	///
472	/// ## Errors
473	///
474	/// - If the path name is too long to be serialized.
475	/// - If the mode setring is not formatted correctly.
476	/// - If we cannot send the request to the PCFS Sata server.
477	/// - If we cannot receive a response from the PCFS Sata server.
478	/// - If we cannot parse the response from the PCFS Sata server.
479	/// - If the return code of the response was not successful.
480	pub async fn open_file(
481		&self,
482		path: String,
483		mode_string: String,
484		timeout: Option<Duration>,
485	) -> Result<SataClientFileHandle<'_>, CatBridgeError> {
486		let resp = self
487			.underlying_client
488			.send(
489				Self::construct(0x5, SataOpenFilePacketBody::new(path, mode_string)?),
490				Some(timeout.unwrap_or(DEFAULT_CLIENT_TIMEOUT)),
491			)
492			.await?
493			.2
494			.ok_or(NetworkError::ExpectedData)?
495			.take_body()
496			.ok_or(NetworkError::ExpectedData)?;
497		let fd_result = SataResponse::<SataFileDescriptorResult>::try_from(resp)?;
498		let fd = match fd_result.take_body().result() {
499			Ok(fd) => fd,
500			Err(code) => {
501				return Err(NetworkParseError::ErrorCode(code).into());
502			}
503		};
504
505		Ok(SataClientFileHandle {
506			file_descriptor: fd,
507			underlying_client: self,
508		})
509	}
510
511	/// Raw API call for doing a file read.
512	///
513	/// ## Errors
514	///
515	/// - If the path name is too long to be serialized.
516	/// - If we cannot send the request to the PCFS Sata server.
517	/// - If we cannot receive a response from the PCFS Sata server.
518	/// - If we cannot parse the response from the PCFS Sata server.
519	/// - If the return code of the response was not successful.
520	/// - If you're requesting to read too much at once.
521	async fn do_file_read(
522		&self,
523		block_count: u32,
524		block_size: u32,
525		file_descriptor: i32,
526		move_to: Option<MoveToFileLocation>,
527		timeout: Option<Duration>,
528	) -> Result<(usize, Bytes), CatBridgeError> {
529		if self.supports_ffio.load(Ordering::Acquire) {
530			let mut left_to_read = block_size * block_count;
531
532			let mut file_size = 0_usize;
533			let mut final_body = BytesMut::with_capacity(
534				usize::try_from(left_to_read)
535					.map_err(|_| CatBridgeError::UnsupportedBitsPerCore)?,
536			);
537			while left_to_read > 0 {
538				// This keeps us within 'padding' range, which is easier to parse
539				// responses out of.
540				let read_in_this_go = std::cmp::min(
541					left_to_read,
542					self.first_read_size.load(Ordering::Acquire) - 0x25,
543				);
544				let read_in_this_go_size = usize::try_from(read_in_this_go)
545					.map_err(|_| CatBridgeError::UnsupportedBitsPerCore)?;
546				// For FFIO our normal nagle split doesn't really work well.
547				let (_stream_id, _req_id, opt_response) = self
548					.underlying_client
549					.send_with_read_amount(
550						Self::construct(
551							0x6,
552							SataReadFilePacketBody::new(
553								1,
554								read_in_this_go,
555								file_descriptor,
556								move_to,
557							),
558						),
559						Some(timeout.unwrap_or(DEFAULT_CLIENT_TIMEOUT)),
560						// 0x20 emptied PCFS header, 0x4 final file length + (block_size * block_count)
561						0x20_usize + 0x4_usize + read_in_this_go_size,
562					)
563					.await?;
564
565				let mut full_body = opt_response
566					.ok_or(NetworkError::ExpectedData)?
567					.take_body()
568					.ok_or(NetworkError::ExpectedData)?;
569				// Remove 'blank' header.
570				full_body.advance(0x20);
571				if file_size == 0 {
572					file_size = usize::try_from(full_body.get_u32()).unwrap_or(usize::MAX);
573				}
574				final_body.extend(full_body);
575				left_to_read -= read_in_this_go;
576			}
577
578			Ok((file_size, final_body.freeze()))
579		} else {
580			todo!("Implement non-FFIO file support.")
581		}
582	}
583
584	async fn do_file_write(
585		&self,
586		block_count: u32,
587		block_size: u32,
588		file_descriptor: i32,
589		move_to: Option<MoveToFileLocation>,
590		raw_trusted_data: Bytes,
591		timeout: Option<Duration>,
592	) -> Result<(), CatBridgeError> {
593		if self.supports_ffio.load(Ordering::Acquire) {
594			let base_req_bytes = Bytes::from(Self::construct(
595				0x7,
596				SataWriteFilePacketBody::new(block_count, block_size, file_descriptor, move_to),
597			));
598			let mut final_req =
599				BytesMut::with_capacity(base_req_bytes.len() + raw_trusted_data.len());
600			final_req.extend(base_req_bytes);
601			final_req.extend(raw_trusted_data);
602
603			let resp = self
604				.underlying_client
605				.send(final_req, Some(timeout.unwrap_or(DEFAULT_CLIENT_TIMEOUT)))
606				.await?
607				.2
608				.ok_or(NetworkError::ExpectedData)?
609				.take_body()
610				.ok_or(NetworkError::ExpectedData)?;
611
612			let sata_resp = SataResponse::<SataResultCode>::try_from(resp)?;
613			if sata_resp.body().0 != 0 {
614				return Err(NetworkParseError::ErrorCode(sata_resp.body().0).into());
615			}
616			Ok(())
617		} else {
618			todo!("Implement non-FFIO file support.")
619		}
620	}
621
622	async fn stat_file(
623		&self,
624		file_descriptor: i32,
625		timeout: Option<Duration>,
626	) -> Result<SataFDInfo, CatBridgeError> {
627		let resp = self
628			.underlying_client
629			.send(
630				Self::construct(0xB, SataStatFilePacketBody::new(file_descriptor)),
631				Some(timeout.unwrap_or(DEFAULT_CLIENT_TIMEOUT)),
632			)
633			.await?
634			.2
635			.ok_or(NetworkError::ExpectedData)?
636			.take_body()
637			.ok_or(NetworkError::ExpectedData)?;
638
639		let (_header, bytes) = SataResponse::<Bytes>::parse_opaque(resp)?.to_parts();
640		let typed_response = SataQueryResponse::try_from_fd_info(bytes)?;
641		if let SataQueryResponse::ErrorCode(ec) = typed_response {
642			return Err(NetworkParseError::ErrorCode(ec).into());
643		}
644		match typed_response {
645			SataQueryResponse::FDInfo(info) => Ok(info),
646			_ => unreachable!("Not reachable from try_from_fd_info"),
647		}
648	}
649
650	async fn do_file_move(
651		&self,
652		file_descriptor: i32,
653		move_to: MoveToFileLocation,
654		timeout: Option<Duration>,
655	) -> Result<(), CatBridgeError> {
656		let resp = self
657			.underlying_client
658			.send(
659				Self::construct(
660					0x9,
661					SataSetFilePositionPacketBody::new(file_descriptor, move_to),
662				),
663				Some(timeout.unwrap_or(DEFAULT_CLIENT_TIMEOUT)),
664			)
665			.await?
666			.2
667			.ok_or(NetworkError::ExpectedData)?
668			.take_body()
669			.ok_or(NetworkError::ExpectedData)?;
670
671		let sata_resp = SataResponse::<SataResultCode>::try_from(resp)?;
672		if sata_resp.body().0 != 0 {
673			return Err(NetworkParseError::ErrorCode(sata_resp.body().0).into());
674		}
675		Ok(())
676	}
677
678	async fn close_file(
679		&self,
680		file_descriptor: i32,
681		timeout: Option<Duration>,
682	) -> Result<(), CatBridgeError> {
683		let resp = self
684			.underlying_client
685			.send(
686				Self::construct(0xD, SataCloseFilePacketBody::new(file_descriptor)),
687				Some(timeout.unwrap_or(DEFAULT_CLIENT_TIMEOUT)),
688			)
689			.await?
690			.2
691			.ok_or(NetworkError::ExpectedData)?
692			.take_body()
693			.ok_or(NetworkError::ExpectedData)?;
694
695		let sata_resp = SataResponse::<SataResultCode>::try_from(resp)?;
696		if sata_resp.body().0 != 0 {
697			return Err(NetworkParseError::ErrorCode(sata_resp.body().0).into());
698		}
699		Ok(())
700	}
701
702	async fn read_folder(
703		&self,
704		folder_descriptor: i32,
705		timeout: Option<Duration>,
706	) -> Result<Option<(SataFDInfo, String)>, CatBridgeError> {
707		let resp = self
708			.underlying_client
709			.send(
710				Self::construct(0x2, SataReadFolderPacketBody::new(folder_descriptor)),
711				Some(timeout.unwrap_or(DEFAULT_CLIENT_TIMEOUT)),
712			)
713			.await?
714			.2
715			.ok_or(NetworkError::ExpectedData)?
716			.take_body()
717			.ok_or(NetworkError::ExpectedData)?;
718
719		let sata_resp = SataResponse::<DirectoryItemResponse>::try_from(resp)?;
720		let directory_item = sata_resp.take_body();
721		if !directory_item.is_successful() {
722			return Err(NetworkParseError::ErrorCode(directory_item.return_code()).into());
723		}
724		Ok(directory_item.take_file_info())
725	}
726
727	async fn rewind_folder(
728		&self,
729		folder_descriptor: i32,
730		timeout: Option<Duration>,
731	) -> Result<(), CatBridgeError> {
732		let resp = self
733			.underlying_client
734			.send(
735				Self::construct(0x3, SataRewindFolderPacketBody::new(folder_descriptor)),
736				Some(timeout.unwrap_or(DEFAULT_CLIENT_TIMEOUT)),
737			)
738			.await?
739			.2
740			.ok_or(NetworkError::ExpectedData)?
741			.take_body()
742			.ok_or(NetworkError::ExpectedData)?;
743
744		let sata_resp = SataResponse::<SataResultCode>::try_from(resp)?;
745		if sata_resp.body().0 != 0 {
746			return Err(NetworkParseError::ErrorCode(sata_resp.body().0).into());
747		}
748		Ok(())
749	}
750
751	async fn close_folder(
752		&self,
753		folder_descriptor: i32,
754		timeout: Option<Duration>,
755	) -> Result<(), CatBridgeError> {
756		let resp = self
757			.underlying_client
758			.send(
759				Self::construct(0x4, SataCloseFolderPacketBody::new(folder_descriptor)),
760				Some(timeout.unwrap_or(DEFAULT_CLIENT_TIMEOUT)),
761			)
762			.await?
763			.2
764			.ok_or(NetworkError::ExpectedData)?
765			.take_body()
766			.ok_or(NetworkError::ExpectedData)?;
767
768		let sata_resp = SataResponse::<SataResultCode>::try_from(resp)?;
769		if sata_resp.body().0 != 0 {
770			return Err(NetworkParseError::ErrorCode(sata_resp.body().0).into());
771		}
772		Ok(())
773	}
774
775	/// Construct a new sata request with a series of default values.
776	#[must_use]
777	fn construct<InnerTy: Into<Bytes>>(command: u32, body: InnerTy) -> SataRequest<Bytes> {
778		let ci = SataCommandInfo::new((0, 0), (0, 0), command);
779		let body: Bytes = body.into();
780		let mut header = SataPacketHeader::new(0);
781		header.set_data_len(0x14_u32 + u32::try_from(body.len()).unwrap_or(u32::MAX));
782
783		SataRequest::new(header, ci, body)
784	}
785}
786
787/// A file that is actively open on an existing sata client.
788///
789/// This is called with [`SataClient::open_file`], and ensures that methods
790/// like "reading a file", can only ever be done on an open file handle.
791#[derive(Debug, Valuable)]
792pub struct SataClientFileHandle<'client> {
793	/// The actual open file descriptor.
794	file_descriptor: i32,
795	/// The Sata Client to use.
796	underlying_client: &'client SataClient,
797}
798
799impl SataClientFileHandle<'_> {
800	/// Close this file, and ensure it can't be used anymore.
801	///
802	/// ## Errors
803	///
804	/// - If we cannot send the request to the PCFS Sata server.
805	/// - If we cannot receive a response from the PCFS Sata server.
806	/// - If we cannot parse the response from the PCFS Sata server.
807	/// - If the return code of the response was not successful.
808	pub async fn close(self, timeout: Option<Duration>) -> Result<(), CatBridgeError> {
809		self.underlying_client
810			.close_file(self.file_descriptor, timeout)
811			.await
812	}
813
814	/// Read N amounts of bytes from a file.
815	///
816	/// You can optionally move the file pointer around before doing the
817	/// read. This will return the total file length, along with the bytes
818	/// that you actually read.
819	///
820	/// ## Errors
821	///
822	/// - If we cannot send the request to the PCFS Sata server.
823	/// - If we cannot receive a response from the PCFS Sata server.
824	/// - If we cannot parse the response from the PCFS Sata server.
825	/// - If the return code of the response was not successful.
826	pub async fn read_file(
827		&self,
828		amount: usize,
829		move_to: Option<MoveToFileLocation>,
830		timeout: Option<Duration>,
831	) -> Result<(usize, Bytes), CatBridgeError> {
832		let (block_size, block_len) = Self::calculate_ideal_block_size_count(amount);
833
834		self.underlying_client
835			.do_file_read(
836				block_len,
837				block_size,
838				self.file_descriptor,
839				move_to,
840				timeout,
841			)
842			.await
843	}
844
845	/// Get information about the current open file.
846	///
847	/// ## Errors
848	///
849	/// - If we cannot send the request to the PCFS Sata server.
850	/// - If we cannot receive a response from the PCFS Sata server.
851	/// - If we cannot parse the response from the PCFS Sata server.
852	/// - If the return code of the response was not successful.
853	pub async fn stat(&self, timeout: Option<Duration>) -> Result<SataFDInfo, CatBridgeError> {
854		self.underlying_client
855			.stat_file(self.file_descriptor, timeout)
856			.await
857	}
858
859	/// Move to a new location within this file.
860	///
861	/// ## Errors
862	///
863	/// - If we cannot send the request to the PCFS Sata server.
864	/// - If we cannot receive a response from the PCFS Sata server.
865	/// - If we cannot parse the response from the PCFS Sata server.
866	/// - If the return code of the response was not successful.
867	pub async fn move_to(
868		&self,
869		move_to: MoveToFileLocation,
870		timeout: Option<Duration>,
871	) -> Result<(), CatBridgeError> {
872		self.underlying_client
873			.do_file_move(self.file_descriptor, move_to, timeout)
874			.await
875	}
876
877	/// Write N amounts of bytes from a file.
878	///
879	/// You can optionally move the file pointer around before doing the
880	/// write.
881	///
882	/// *note: in cases of extreme latency a file write may succeed, but reutrn
883	/// failure because we could not parse the response in the timeout.*
884	///
885	/// ## Errors
886	///
887	/// - If we cannot send the request to the PCFS Sata server.
888	/// - If we cannot receive a response from the PCFS Sata server.
889	/// - If we cannot parse the response from the PCFS Sata server.
890	/// - If the return code of the response was not successful.
891	pub async fn write_file(
892		&self,
893		to_write: Bytes,
894		move_to: Option<MoveToFileLocation>,
895		timeout: Option<Duration>,
896	) -> Result<(), CatBridgeError> {
897		let (block_size, block_len) = Self::calculate_ideal_block_size_count(to_write.len());
898
899		self.underlying_client
900			.do_file_write(
901				block_len,
902				block_size,
903				self.file_descriptor,
904				move_to,
905				to_write,
906				timeout,
907			)
908			.await
909	}
910
911	fn calculate_ideal_block_size_count(amount: usize) -> (u32, u32) {
912		if amount < 512 {
913			(u32::try_from(amount).expect("unreachable()"), 1)
914		} else if amount.is_multiple_of(512) {
915			(512, u32::try_from(amount / 512).unwrap_or(u32::MAX))
916		} else {
917			let mut count = 511;
918			while !amount.is_multiple_of(count) {
919				count -= 1;
920			}
921
922			(
923				u32::try_from(count).expect("unreachable()"),
924				u32::try_from(amount / count).unwrap_or(u32::MAX),
925			)
926		}
927	}
928}
929
930/// A folder that is actively open on an existing sata client.
931///
932/// This is called with [`SataClient::open_folder`], and ensures that methods
933/// like "reading a directory", can only ever be done on an open folder handle.
934#[derive(Debug, Valuable)]
935pub struct SataClientFolderHandle<'client> {
936	/// The folder handle and descriptor.
937	folder_descriptor: i32,
938	/// The underlying sata client to call methods on.
939	underlying_client: &'client SataClient,
940}
941
942impl SataClientFolderHandle<'_> {
943	/// Close this folder, and ensure it can't be used anymore.
944	///
945	/// ## Errors
946	///
947	/// - If we cannot send the request to the PCFS Sata server.
948	/// - If we cannot receive a response from the PCFS Sata server.
949	/// - If we cannot parse the response from the PCFS Sata server.
950	/// - If the return code of the response was not successful.
951	pub async fn close(self, timeout: Option<Duration>) -> Result<(), CatBridgeError> {
952		self.underlying_client
953			.close_folder(self.folder_descriptor, timeout)
954			.await
955	}
956
957	/// Read the next item within a folder.
958	///
959	/// This will return the next file name/file info if there is another item in
960	/// this directory.
961	///
962	/// ## Errors
963	///
964	/// - If we cannot send the request to the PCFS Sata server.
965	/// - If we cannot receive a response from the PCFS Sata server.
966	/// - If we cannot parse the response from the PCFS Sata server.
967	/// - If the return code of the response was not successful.
968	pub async fn next_in_folder(
969		&self,
970		timeout: Option<Duration>,
971	) -> Result<Option<(SataFDInfo, String)>, CatBridgeError> {
972		self.underlying_client
973			.read_folder(self.folder_descriptor, timeout)
974			.await
975	}
976
977	/// Rewind the directory iterator by one (so next in folder returns the
978	/// previous item it returned).
979	///
980	/// ## Errors
981	///
982	/// - If we cannot send the request to the PCFS Sata server.
983	/// - If we cannot receive a response from the PCFS Sata server.
984	/// - If we cannot parse the response from the PCFS Sata server.
985	/// - If the return code of the response was not successful.
986	pub async fn rewind_iterator(&self, timeout: Option<Duration>) -> Result<(), CatBridgeError> {
987		self.underlying_client
988			.rewind_folder(self.folder_descriptor, timeout)
989			.await
990	}
991}
992// Folder Open Handle (OpenFolder):
993//   - CloseFolder