cat-dev 0.0.13

A library for interacting with the CAT-DEV hardware units distributed by Nintendo (i.e. a type of Wii-U DevKits).
Documentation
//! An SDIO 'read' request to read bytes from somewhere on the disk.

use crate::fsemul::sdio::{
	errors::{SdioApiError, SdioProtocolError},
	proto::SDIO_BLOCK_SIZE_AS_U32,
};
use bytes::{Buf, BufMut, Bytes, BytesMut};
use valuable::{Fields, NamedField, NamedValues, StructDef, Structable, Valuable, Value, Visit};

/// Handle a Read Request coming over the SDIO Control port.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SdioControlReadRequest {
	lba: u32,
	blocks: u32,
	channel: u32,
}

impl SdioControlReadRequest {
	/// Create a new SDIO Read Request to head to the `CONTROL` port.
	///
	/// This assumes you're passing in the LBA as it appears in the DLF file,
	/// (e.g. divisble by [`SDIO_BLOCK_SIZE_AS_U32`]). If you want the raw LBA
	/// you can call [`Self::new_with_raw_lba`].
	///
	/// ## Errors
	///
	/// - If the LBA address is not a possible block address (must be divisble by
	///   [`SDIO_BLOCK_SIZE_AS_U32`]).
	/// - If the channel's first byte is greater than or equal to `0xC` when
	///   encoded to little endian.
	pub fn new(lba: u32, blocks: u32, channel: u32) -> Result<Self, SdioApiError> {
		if lba < SDIO_BLOCK_SIZE_AS_U32 || !lba.is_multiple_of(SDIO_BLOCK_SIZE_AS_U32) {
			return Err(SdioApiError::InvalidLBA(lba));
		}
		let as_bytes = channel.to_le_bytes();
		if as_bytes[0] >= 0xC {
			return Err(SdioApiError::InvalidChannel(as_bytes[0], channel));
		}

		Ok(Self {
			lba: lba / SDIO_BLOCK_SIZE_AS_U32,
			blocks,
			channel,
		})
	}

	/// Create a new SDIO Read Request to head to the `CONTROL` port.
	///
	/// This assumes you're passing in the LBA as it appears on the network,
	/// (e.g. not the number being divisble by 512).
	///
	/// ## Errors
	///
	/// - If the channel's first byte is greater than or equal to `0xC` when
	///   encoded to little endian.
	pub fn new_with_raw_lba(raw_lba: u32, blocks: u32, channel: u32) -> Result<Self, SdioApiError> {
		let as_bytes = channel.to_le_bytes();
		if as_bytes[0] >= 0xC {
			return Err(SdioApiError::InvalidChannel(as_bytes[0], channel));
		}

		Ok(Self {
			lba: raw_lba,
			blocks,
			channel,
		})
	}

	/// Get the address to read from.
	#[must_use]
	pub const fn lba(&self) -> u32 {
		self.lba * SDIO_BLOCK_SIZE_AS_U32
	}

	/// Set the "raw" address to read from, this is not in the same form as
	/// `read_request.lba()`.
	///
	/// This is if we took the LBA returned by the LBA block method and divided
	/// it by block size.
	pub fn set_raw_lba(&mut self, new_lba: u32) {
		self.lba = new_lba;
	}

	/// Set the address to read from.
	///
	/// ## Errors
	///
	/// - If the LBA address is not a possible block address (must be divisble by
	///   [`SDIO_BLOCK_SIZE_AS_U32`]).
	pub fn set_lba(&mut self, new_lba: u32) -> Result<(), SdioApiError> {
		if new_lba < SDIO_BLOCK_SIZE_AS_U32 || !new_lba.is_multiple_of(SDIO_BLOCK_SIZE_AS_U32) {
			return Err(SdioApiError::InvalidLBA(new_lba));
		}

		self.lba = new_lba / SDIO_BLOCK_SIZE_AS_U32;
		Ok(())
	}

	/// Get the amount of blocks to read.
	#[must_use]
	pub const fn blocks(&self) -> u32 {
		self.blocks
	}

	/// Set the amount of blocks to read from SDIO.
	pub fn set_blocks(&mut self, new_blocks: u32) {
		self.blocks = new_blocks;
	}

	/// Get the channel to read from.
	#[must_use]
	pub const fn channel(&self) -> u32 {
		self.channel
	}

	/// Set the channel this read request is on.
	///
	/// ## Errors
	///
	/// - If the channel's first byte is greater than or equal to `0xC` when
	///   encoded to little endian.
	pub fn set_channel(&mut self, new_channel: u32) -> Result<(), SdioApiError> {
		let as_bytes = new_channel.to_le_bytes();
		if as_bytes[0] >= 0xC {
			return Err(SdioApiError::InvalidChannel(as_bytes[0], new_channel));
		}

		self.channel = new_channel;
		Ok(())
	}
}

impl TryFrom<Bytes> for SdioControlReadRequest {
	type Error = SdioProtocolError;

	fn try_from(mut value: Bytes) -> Result<Self, Self::Error> {
		if value.len() != 512 {
			return Err(SdioProtocolError::PrintfInvalidSize(value.len()));
		}
		let packet_type = value.get_u16_le();
		if packet_type != 0 {
			return Err(SdioProtocolError::UnknownPrintfPacketType(packet_type));
		}
		_ = value.get_u16_le();

		let lba = value.get_u32_le();
		let blocks = value.get_u32_le();
		if value[0] >= 0xC {
			return Err(SdioProtocolError::PrintfInvalidChannel(
				value[0],
				u32::from_le_bytes([value[0], value[1], value[2], value[3]]),
			));
		}
		let channel = value.get_u32_le();

		Ok(Self {
			lba,
			blocks,
			channel,
		})
	}
}

impl TryFrom<&SdioControlReadRequest> for Bytes {
	type Error = SdioProtocolError;

	fn try_from(value: &SdioControlReadRequest) -> Result<Self, Self::Error> {
		let channel_first_byte = value.channel.to_le_bytes()[0];
		if channel_first_byte >= 0xC {
			return Err(SdioProtocolError::PrintfInvalidChannel(
				channel_first_byte,
				value.channel,
			));
		}
		let mut serialized = BytesMut::with_capacity(512);
		serialized.put_u32_le(0);
		serialized.put_u32_le(value.lba);
		serialized.put_u32_le(value.blocks);
		serialized.put_u32_le(value.channel);
		serialized.extend_from_slice(&[0; 0x1F0]);
		Ok(serialized.freeze())
	}
}

impl TryFrom<SdioControlReadRequest> for Bytes {
	type Error = SdioProtocolError;

	fn try_from(value: SdioControlReadRequest) -> Result<Self, Self::Error> {
		Self::try_from(&value)
	}
}

const CONTROL_READ_REQUEST_FIELDS: &[NamedField<'static>] = &[
	NamedField::new("raw_lba"),
	NamedField::new("blocks"),
	NamedField::new("channel"),
];

impl Structable for SdioControlReadRequest {
	fn definition(&self) -> StructDef<'_> {
		StructDef::new_static(
			"SdioControlReadRequest",
			Fields::Named(CONTROL_READ_REQUEST_FIELDS),
		)
	}
}

impl Valuable for SdioControlReadRequest {
	fn as_value(&self) -> Value<'_> {
		Value::Structable(self)
	}

	fn visit(&self, visitor: &mut dyn Visit) {
		visitor.visit_named_fields(&NamedValues::new(
			CONTROL_READ_REQUEST_FIELDS,
			&[
				Valuable::as_value(&self.lba),
				Valuable::as_value(&self.blocks),
				Valuable::as_value(&self.channel),
			],
		));
	}
}

#[cfg(test)]
mod unit_tests {
	use super::*;

	#[test]
	pub fn parse_read_request() {
		// Real life read request packet being parsed.
		{
			let read_request = SdioControlReadRequest::try_from(Bytes::from(vec![
				0x00, 0x00, 0x00, 0x00, 0x80, 0xff, 0x7f, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
			]))
			.expect("Failed to parse real life SDIO Control Read Request");

			assert_eq!(
				read_request.lba(),
				0xFFFF_0000,
				"Failed to parse correct address to read from on a real life read request.",
			);
			assert_eq!(
				read_request.blocks(),
				2,
				"Failed to parse correct amount of blocks to read from real life read request.",
			);
			assert_eq!(
				read_request.channel(),
				0,
				"Failed to parse the correct channel to read from on a real life read request.",
			);
		}
	}
}