cat-dev 0.0.14

A library for interacting with the CAT-DEV hardware units distributed by Nintendo (i.e. a type of Wii-U DevKits).
Documentation
//! Process a request to get the current file position.

use crate::{
	fsemul::pcfs::sata::{
		proto::{
			SataGetFilePositionPacketBody, SataGetFilePositionResult, SataRequest, SataResponse,
		},
		server::PcfsServerState,
	},
	net::{
		additions::StreamID,
		server::requestable::{Body, State},
	},
};
use tracing::debug;

/// A filesystem error occured.
const FS_ERROR: u32 = 0xFFF0_FFE0;

/// Handle closing a file that was previously open.
pub async fn handle_get_file_position(
	stream: StreamID,
	State(state): State<PcfsServerState>,
	// Validate that the body is actually a change owner packet.
	Body(request): Body<SataRequest<SataGetFilePositionPacketBody>>,
) -> SataResponse<SataGetFilePositionResult> {
	let packet = request.body();

	SataResponse::new(
		state.pid(),
		request.header().clone(),
		match state
			.host_filesystem()
			.get_file_position(packet.file_descriptor(), Some(stream.to_raw()))
			.await
		{
			Ok(s) => SataGetFilePositionResult::success(u32::try_from(s).unwrap_or(u32::MAX)),
			Err(cause) => {
				debug!(
					?cause,
					packet.fd = packet.file_descriptor(),
					packet.typ = "PcfsSrvGetFilePosition",
					"Failed to get a file's position!",
				);

				SataGetFilePositionResult::error(FS_ERROR)
			}
		},
	)
}

#[cfg(test)]
mod unit_tests {
	use super::*;
	use crate::fsemul::{
		filesystem::host::test_helpers::{create_temporary_host_filesystem, join_many},
		pcfs::sata::{
			proto::{SataCommandInfo, SataPacketHeader, SataReadFilePacketBody, SataRequest},
			server::{SataConnectionFlags, read_file::handle_read_file},
		},
	};
	use bytes::Bytes;
	use tokio::fs::OpenOptions;

	#[tokio::test]
	pub async fn simple_close_file_request() {
		let (tempdir, fs) = create_temporary_host_filesystem().await;

		let base_dir = join_many(tempdir.path(), ["data", "slc", "to-query"]);
		tokio::fs::create_dir(&base_dir)
			.await
			.expect("Failed to create temporary directory for test!");
		tokio::fs::write(join_many(&base_dir, ["file.txt"]), vec![0; 16])
			.await
			.expect("Failed to write test file!");
		let mocked_header = SataPacketHeader::new(0);
		let mocked_ci = SataCommandInfo::new((0, 0), (0, 0), 0);
		let mut open_options = OpenOptions::new();
		open_options.read(true).create(false).write(false);
		let fd = fs
			.open_file(open_options, &join_many(&base_dir, ["file.txt"]), Some(0))
			.await
			.expect("Failed to open file!");

		let state = PcfsServerState::new(true, fs.clone(), 0);
		let get_file_position_request = SataGetFilePositionPacketBody::new(fd);
		let response: Bytes = handle_get_file_position(
			StreamID::from_existing(0),
			State(state.clone()),
			Body(SataRequest::new(
				mocked_header.clone(),
				mocked_ci.clone(),
				get_file_position_request.clone(),
			)),
		)
		.await
		.try_into()
		.expect("Failed to serialize get file position response!");
		// We're at bytes 0.
		assert_eq!(&response[0x20..0x24], &[0; 4]);
		assert_eq!(&response[0x24..], &[0; 4]);

		// Read 4 bytes.
		let read_request = SataReadFilePacketBody::new(4, 1, fd, None);
		let conn_flags = SataConnectionFlags::new_with_flags(true, true);
		conn_flags.set_first_read_size(1000);
		conn_flags.set_first_write_size(1000);
		_ = handle_read_file(
			StreamID::from_existing(0),
			conn_flags,
			State(fs),
			Body(SataRequest::new(
				SataPacketHeader::new(0),
				SataCommandInfo::new((0, 0), (0, 0), 0),
				read_request,
			)),
		)
		.await
		.expect("Failed to handle read file!");

		// Get file position again. We should be at byte position 4.
		let response: Bytes = handle_get_file_position(
			StreamID::from_existing(0),
			State(state),
			Body(SataRequest::new(
				mocked_header,
				mocked_ci,
				get_file_position_request,
			)),
		)
		.await
		.try_into()
		.expect("Failed to serialize get file position response!");
		// We're at bytes 0.
		assert_eq!(&response[0x20..0x24], &[0; 4]);
		assert_eq!(&response[0x24..], 4_u32.to_be_bytes());
	}
}