Skip to main content

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())
573						.map_err(|_| CatBridgeError::UnsupportedBitsPerCore)?;
574				}
575				final_body.extend(full_body);
576				left_to_read -= read_in_this_go;
577			}
578
579			Ok((file_size, final_body.freeze()))
580		} else {
581			todo!("Implement non-FFIO file support.")
582		}
583	}
584
585	async fn do_file_write(
586		&self,
587		block_count: u32,
588		block_size: u32,
589		file_descriptor: i32,
590		move_to: Option<MoveToFileLocation>,
591		raw_trusted_data: Bytes,
592		timeout: Option<Duration>,
593	) -> Result<(), CatBridgeError> {
594		if self.supports_ffio.load(Ordering::Acquire) {
595			let base_req_bytes = Bytes::from(Self::construct(
596				0x7,
597				SataWriteFilePacketBody::new(block_count, block_size, file_descriptor, move_to),
598			));
599			let mut final_req =
600				BytesMut::with_capacity(base_req_bytes.len() + raw_trusted_data.len());
601			final_req.extend(base_req_bytes);
602			final_req.extend(raw_trusted_data);
603
604			let resp = self
605				.underlying_client
606				.send(final_req, Some(timeout.unwrap_or(DEFAULT_CLIENT_TIMEOUT)))
607				.await?
608				.2
609				.ok_or(NetworkError::ExpectedData)?
610				.take_body()
611				.ok_or(NetworkError::ExpectedData)?;
612
613			let sata_resp = SataResponse::<SataResultCode>::try_from(resp)?;
614			if sata_resp.body().0 != 0 {
615				return Err(NetworkParseError::ErrorCode(sata_resp.body().0).into());
616			}
617			Ok(())
618		} else {
619			todo!("Implement non-FFIO file support.")
620		}
621	}
622
623	async fn stat_file(
624		&self,
625		file_descriptor: i32,
626		timeout: Option<Duration>,
627	) -> Result<SataFDInfo, CatBridgeError> {
628		let resp = self
629			.underlying_client
630			.send(
631				Self::construct(0xB, SataStatFilePacketBody::new(file_descriptor)),
632				Some(timeout.unwrap_or(DEFAULT_CLIENT_TIMEOUT)),
633			)
634			.await?
635			.2
636			.ok_or(NetworkError::ExpectedData)?
637			.take_body()
638			.ok_or(NetworkError::ExpectedData)?;
639
640		let (_header, bytes) = SataResponse::<Bytes>::parse_opaque(resp)?.to_parts();
641		let typed_response = SataQueryResponse::try_from_fd_info(bytes)?;
642		if let SataQueryResponse::ErrorCode(ec) = typed_response {
643			return Err(NetworkParseError::ErrorCode(ec).into());
644		}
645		match typed_response {
646			SataQueryResponse::FDInfo(info) => Ok(info),
647			_ => unreachable!("Not reachable from try_from_fd_info"),
648		}
649	}
650
651	async fn do_file_move(
652		&self,
653		file_descriptor: i32,
654		move_to: MoveToFileLocation,
655		timeout: Option<Duration>,
656	) -> Result<(), CatBridgeError> {
657		let resp = self
658			.underlying_client
659			.send(
660				Self::construct(
661					0x9,
662					SataSetFilePositionPacketBody::new(file_descriptor, move_to),
663				),
664				Some(timeout.unwrap_or(DEFAULT_CLIENT_TIMEOUT)),
665			)
666			.await?
667			.2
668			.ok_or(NetworkError::ExpectedData)?
669			.take_body()
670			.ok_or(NetworkError::ExpectedData)?;
671
672		let sata_resp = SataResponse::<SataResultCode>::try_from(resp)?;
673		if sata_resp.body().0 != 0 {
674			return Err(NetworkParseError::ErrorCode(sata_resp.body().0).into());
675		}
676		Ok(())
677	}
678
679	async fn close_file(
680		&self,
681		file_descriptor: i32,
682		timeout: Option<Duration>,
683	) -> Result<(), CatBridgeError> {
684		let resp = self
685			.underlying_client
686			.send(
687				Self::construct(0xD, SataCloseFilePacketBody::new(file_descriptor)),
688				Some(timeout.unwrap_or(DEFAULT_CLIENT_TIMEOUT)),
689			)
690			.await?
691			.2
692			.ok_or(NetworkError::ExpectedData)?
693			.take_body()
694			.ok_or(NetworkError::ExpectedData)?;
695
696		let sata_resp = SataResponse::<SataResultCode>::try_from(resp)?;
697		if sata_resp.body().0 != 0 {
698			return Err(NetworkParseError::ErrorCode(sata_resp.body().0).into());
699		}
700		Ok(())
701	}
702
703	async fn read_folder(
704		&self,
705		folder_descriptor: i32,
706		timeout: Option<Duration>,
707	) -> Result<Option<(SataFDInfo, String)>, CatBridgeError> {
708		let resp = self
709			.underlying_client
710			.send(
711				Self::construct(0x2, SataReadFolderPacketBody::new(folder_descriptor)),
712				Some(timeout.unwrap_or(DEFAULT_CLIENT_TIMEOUT)),
713			)
714			.await?
715			.2
716			.ok_or(NetworkError::ExpectedData)?
717			.take_body()
718			.ok_or(NetworkError::ExpectedData)?;
719
720		let sata_resp = SataResponse::<DirectoryItemResponse>::try_from(resp)?;
721		let directory_item = sata_resp.take_body();
722		if !directory_item.is_successful() {
723			return Err(NetworkParseError::ErrorCode(directory_item.return_code()).into());
724		}
725		Ok(directory_item.take_file_info())
726	}
727
728	async fn rewind_folder(
729		&self,
730		folder_descriptor: i32,
731		timeout: Option<Duration>,
732	) -> Result<(), CatBridgeError> {
733		let resp = self
734			.underlying_client
735			.send(
736				Self::construct(0x3, SataRewindFolderPacketBody::new(folder_descriptor)),
737				Some(timeout.unwrap_or(DEFAULT_CLIENT_TIMEOUT)),
738			)
739			.await?
740			.2
741			.ok_or(NetworkError::ExpectedData)?
742			.take_body()
743			.ok_or(NetworkError::ExpectedData)?;
744
745		let sata_resp = SataResponse::<SataResultCode>::try_from(resp)?;
746		if sata_resp.body().0 != 0 {
747			return Err(NetworkParseError::ErrorCode(sata_resp.body().0).into());
748		}
749		Ok(())
750	}
751
752	async fn close_folder(
753		&self,
754		folder_descriptor: i32,
755		timeout: Option<Duration>,
756	) -> Result<(), CatBridgeError> {
757		let resp = self
758			.underlying_client
759			.send(
760				Self::construct(0x4, SataCloseFolderPacketBody::new(folder_descriptor)),
761				Some(timeout.unwrap_or(DEFAULT_CLIENT_TIMEOUT)),
762			)
763			.await?
764			.2
765			.ok_or(NetworkError::ExpectedData)?
766			.take_body()
767			.ok_or(NetworkError::ExpectedData)?;
768
769		let sata_resp = SataResponse::<SataResultCode>::try_from(resp)?;
770		if sata_resp.body().0 != 0 {
771			return Err(NetworkParseError::ErrorCode(sata_resp.body().0).into());
772		}
773		Ok(())
774	}
775
776	/// Construct a new sata request with a series of default values.
777	#[must_use]
778	fn construct<InnerTy: Into<Bytes>>(command: u32, body: InnerTy) -> SataRequest<Bytes> {
779		let ci = SataCommandInfo::new((0, 0), (0, 0), command);
780		let body: Bytes = body.into();
781		let mut header = SataPacketHeader::new(0);
782		header.set_data_len(0x14_u32 + u32::try_from(body.len()).unwrap_or(u32::MAX));
783
784		SataRequest::new(header, ci, body)
785	}
786}
787
788/// A file that is actively open on an existing sata client.
789///
790/// This is called with [`SataClient::open_file`], and ensures that methods
791/// like "reading a file", can only ever be done on an open file handle.
792#[derive(Debug, Valuable)]
793pub struct SataClientFileHandle<'client> {
794	/// The actual open file descriptor.
795	file_descriptor: i32,
796	/// The Sata Client to use.
797	underlying_client: &'client SataClient,
798}
799
800impl SataClientFileHandle<'_> {
801	/// Close this file, and ensure it can't be used anymore.
802	///
803	/// ## Errors
804	///
805	/// - If we cannot send the request to the PCFS Sata server.
806	/// - If we cannot receive a response from the PCFS Sata server.
807	/// - If we cannot parse the response from the PCFS Sata server.
808	/// - If the return code of the response was not successful.
809	pub async fn close(self, timeout: Option<Duration>) -> Result<(), CatBridgeError> {
810		self.underlying_client
811			.close_file(self.file_descriptor, timeout)
812			.await
813	}
814
815	/// Read N amounts of bytes from a file.
816	///
817	/// You can optionally move the file pointer around before doing the
818	/// read. This will return the total file length, along with the bytes
819	/// that you actually read.
820	///
821	/// ## Errors
822	///
823	/// - If we cannot send the request to the PCFS Sata server.
824	/// - If we cannot receive a response from the PCFS Sata server.
825	/// - If we cannot parse the response from the PCFS Sata server.
826	/// - If the return code of the response was not successful.
827	pub async fn read_file(
828		&self,
829		amount: usize,
830		move_to: Option<MoveToFileLocation>,
831		timeout: Option<Duration>,
832	) -> Result<(usize, Bytes), CatBridgeError> {
833		let (block_size, block_len) = Self::calculate_ideal_block_size_count(amount);
834
835		self.underlying_client
836			.do_file_read(
837				block_len,
838				block_size,
839				self.file_descriptor,
840				move_to,
841				timeout,
842			)
843			.await
844	}
845
846	/// Get information about the current open file.
847	///
848	/// ## Errors
849	///
850	/// - If we cannot send the request to the PCFS Sata server.
851	/// - If we cannot receive a response from the PCFS Sata server.
852	/// - If we cannot parse the response from the PCFS Sata server.
853	/// - If the return code of the response was not successful.
854	pub async fn stat(&self, timeout: Option<Duration>) -> Result<SataFDInfo, CatBridgeError> {
855		self.underlying_client
856			.stat_file(self.file_descriptor, timeout)
857			.await
858	}
859
860	/// Move to a new location within this file.
861	///
862	/// ## Errors
863	///
864	/// - If we cannot send the request to the PCFS Sata server.
865	/// - If we cannot receive a response from the PCFS Sata server.
866	/// - If we cannot parse the response from the PCFS Sata server.
867	/// - If the return code of the response was not successful.
868	pub async fn move_to(
869		&self,
870		move_to: MoveToFileLocation,
871		timeout: Option<Duration>,
872	) -> Result<(), CatBridgeError> {
873		self.underlying_client
874			.do_file_move(self.file_descriptor, move_to, timeout)
875			.await
876	}
877
878	/// Write N amounts of bytes from a file.
879	///
880	/// You can optionally move the file pointer around before doing the
881	/// write.
882	///
883	/// *note: in cases of extreme latency a file write may succeed, but reutrn
884	/// failure because we could not parse the response in the timeout.*
885	///
886	/// ## Errors
887	///
888	/// - If we cannot send the request to the PCFS Sata server.
889	/// - If we cannot receive a response from the PCFS Sata server.
890	/// - If we cannot parse the response from the PCFS Sata server.
891	/// - If the return code of the response was not successful.
892	pub async fn write_file(
893		&self,
894		to_write: Bytes,
895		move_to: Option<MoveToFileLocation>,
896		timeout: Option<Duration>,
897	) -> Result<(), CatBridgeError> {
898		let (block_size, block_len) = Self::calculate_ideal_block_size_count(to_write.len());
899
900		self.underlying_client
901			.do_file_write(
902				block_len,
903				block_size,
904				self.file_descriptor,
905				move_to,
906				to_write,
907				timeout,
908			)
909			.await
910	}
911
912	fn calculate_ideal_block_size_count(amount: usize) -> (u32, u32) {
913		if amount < 512 {
914			(u32::try_from(amount).expect("unreachable()"), 1)
915		} else if amount.is_multiple_of(512) {
916			(512, u32::try_from(amount / 512).unwrap_or(u32::MAX))
917		} else {
918			let mut count = 511;
919			while !amount.is_multiple_of(count) {
920				count -= 1;
921			}
922
923			(
924				u32::try_from(count).expect("unreachable()"),
925				u32::try_from(amount / count).unwrap_or(u32::MAX),
926			)
927		}
928	}
929}
930
931/// A folder that is actively open on an existing sata client.
932///
933/// This is called with [`SataClient::open_folder`], and ensures that methods
934/// like "reading a directory", can only ever be done on an open folder handle.
935#[derive(Debug, Valuable)]
936pub struct SataClientFolderHandle<'client> {
937	/// The folder handle and descriptor.
938	folder_descriptor: i32,
939	/// The underlying sata client to call methods on.
940	underlying_client: &'client SataClient,
941}
942
943impl SataClientFolderHandle<'_> {
944	/// Close this folder, and ensure it can't be used anymore.
945	///
946	/// ## Errors
947	///
948	/// - If we cannot send the request to the PCFS Sata server.
949	/// - If we cannot receive a response from the PCFS Sata server.
950	/// - If we cannot parse the response from the PCFS Sata server.
951	/// - If the return code of the response was not successful.
952	pub async fn close(self, timeout: Option<Duration>) -> Result<(), CatBridgeError> {
953		self.underlying_client
954			.close_folder(self.folder_descriptor, timeout)
955			.await
956	}
957
958	/// Read the next item within a folder.
959	///
960	/// This will return the next file name/file info if there is another item in
961	/// this directory.
962	///
963	/// ## Errors
964	///
965	/// - If we cannot send the request to the PCFS Sata server.
966	/// - If we cannot receive a response from the PCFS Sata server.
967	/// - If we cannot parse the response from the PCFS Sata server.
968	/// - If the return code of the response was not successful.
969	pub async fn next_in_folder(
970		&self,
971		timeout: Option<Duration>,
972	) -> Result<Option<(SataFDInfo, String)>, CatBridgeError> {
973		self.underlying_client
974			.read_folder(self.folder_descriptor, timeout)
975			.await
976	}
977
978	/// Rewind the directory iterator by one (so next in folder returns the
979	/// previous item it returned).
980	///
981	/// ## Errors
982	///
983	/// - If we cannot send the request to the PCFS Sata server.
984	/// - If we cannot receive a response from the PCFS Sata server.
985	/// - If we cannot parse the response from the PCFS Sata server.
986	/// - If the return code of the response was not successful.
987	pub async fn rewind_iterator(&self, timeout: Option<Duration>) -> Result<(), CatBridgeError> {
988		self.underlying_client
989			.rewind_folder(self.folder_descriptor, timeout)
990			.await
991	}
992}
993// Folder Open Handle (OpenFolder):
994//   - CloseFolder