use crate::{
errors::NetworkParseError,
fsemul::{
HostFilesystem,
pcfs::errors::{PcfsApiError, SataProtocolError},
},
};
use bytes::{Buf, BufMut, Bytes, BytesMut};
use std::{
ffi::CStr,
fs::Metadata,
path::PathBuf,
sync::LazyLock,
time::{Duration, SystemTime},
};
use valuable::{Fields, NamedField, NamedValues, StructDef, Structable, Valuable, Value, Visit};
static FAT_TIMESTAMP_START: LazyLock<SystemTime> = LazyLock::new(|| {
SystemTime::UNIX_EPOCH
.checked_add(Duration::from_secs(315_540_000))
.expect("Failed to get timestamp for 1980! required!")
});
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SataGetInfoByQueryPacketBody {
path: String,
typ: SataQueryType,
}
impl SataGetInfoByQueryPacketBody {
pub fn new(path: String, query_type: SataQueryType) -> Result<Self, PcfsApiError> {
if path.len() > 511 {
return Err(PcfsApiError::PathTooLong(path));
}
Ok(Self {
path,
typ: query_type,
})
}
#[must_use]
pub const fn query_type(&self) -> SataQueryType {
self.typ
}
pub const fn set_query_type(&mut self, new_type: SataQueryType) {
self.typ = new_type;
}
#[must_use]
pub fn path(&self) -> &str {
self.path.as_str()
}
pub fn set_path(&mut self, new_path: String) -> Result<(), PcfsApiError> {
if new_path.len() > 511 {
return Err(PcfsApiError::PathTooLong(new_path));
}
self.path = new_path;
Ok(())
}
}
impl From<&SataGetInfoByQueryPacketBody> for Bytes {
fn from(value: &SataGetInfoByQueryPacketBody) -> Self {
let mut result = BytesMut::with_capacity(0x204);
result.extend_from_slice(value.path.as_bytes());
result.extend(BytesMut::zeroed(0x200 - result.len()));
result.put_u32(u32::from(value.typ));
result.freeze()
}
}
impl From<SataGetInfoByQueryPacketBody> for Bytes {
fn from(value: SataGetInfoByQueryPacketBody) -> Self {
Self::from(&value)
}
}
impl TryFrom<Bytes> for SataGetInfoByQueryPacketBody {
type Error = NetworkParseError;
fn try_from(value: Bytes) -> Result<Self, Self::Error> {
if value.len() < 0x204 {
return Err(NetworkParseError::FieldNotLongEnough(
"SataGetInfoByQuery",
"Body",
0x204,
value.len(),
value,
));
}
if value.len() > 0x204 {
return Err(NetworkParseError::UnexpectedTrailer(
"SataGetInfoByQueryBody",
value.slice(0x204..),
));
}
let (path_bytes, num) = value.split_at(0x200);
let path_c_str =
CStr::from_bytes_until_nul(path_bytes).map_err(NetworkParseError::BadCString)?;
let query_type = u32::from_be_bytes([num[0], num[1], num[2], num[3]]);
let final_path = path_c_str.to_str()?.to_owned();
Ok(Self {
path: final_path,
typ: SataQueryType::try_from(query_type)?,
})
}
}
const SATA_GET_INFO_BY_QUERY_PACKET_BODY_FIELDS: &[NamedField<'static>] =
&[NamedField::new("path"), NamedField::new("type")];
impl Structable for SataGetInfoByQueryPacketBody {
fn definition(&self) -> StructDef<'_> {
StructDef::new_static(
"SataGetInfoByQueryPacketBody",
Fields::Named(SATA_GET_INFO_BY_QUERY_PACKET_BODY_FIELDS),
)
}
}
impl Valuable for SataGetInfoByQueryPacketBody {
fn as_value(&self) -> Value<'_> {
Value::Structable(self)
}
fn visit(&self, visitor: &mut dyn Visit) {
visitor.visit_named_fields(&NamedValues::new(
SATA_GET_INFO_BY_QUERY_PACKET_BODY_FIELDS,
&[
Valuable::as_value(&self.path),
Valuable::as_value(&self.typ),
],
));
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Valuable)]
pub enum SataQueryType {
FreeDiskSpace,
SizeOfFolder,
FileCount,
FileDetails,
}
impl From<SataQueryType> for u32 {
fn from(value: SataQueryType) -> Self {
match value {
SataQueryType::FreeDiskSpace => 0,
SataQueryType::SizeOfFolder => 1,
SataQueryType::FileCount => 2,
SataQueryType::FileDetails => 5,
}
}
}
impl TryFrom<u32> for SataQueryType {
type Error = SataProtocolError;
fn try_from(value: u32) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::FreeDiskSpace),
1 => Ok(Self::SizeOfFolder),
2 => Ok(Self::FileCount),
5 => Ok(Self::FileDetails),
val => Err(SataProtocolError::UnknownGetInfoQueryType(val)),
}
}
}
#[derive(Clone, Debug, Valuable, PartialEq, Eq)]
pub enum SataQueryResponse {
ErrorCode(u32),
SmallSize(u32),
LargeSize(u64),
FDInfo(SataFDInfo),
}
impl SataQueryResponse {
pub fn try_from_small(mut value: Bytes) -> Result<Self, NetworkParseError> {
let rc = value.get_u32();
if rc != 0 {
return Err(NetworkParseError::ErrorCode(rc));
}
let smol = value.get_u32();
Ok(Self::SmallSize(smol))
}
pub fn try_from_large(mut value: Bytes) -> Result<Self, NetworkParseError> {
let rc = value.get_u32();
if rc != 0 {
return Err(NetworkParseError::ErrorCode(rc));
}
let larg = value.get_u64();
Ok(Self::LargeSize(larg))
}
pub fn try_from_fd_info(mut value: Bytes) -> Result<Self, NetworkParseError> {
let rc = value.get_u32();
if rc != 0 {
return Err(NetworkParseError::ErrorCode(rc));
}
let fd_info = SataFDInfo::try_from(value)?;
Ok(Self::FDInfo(fd_info))
}
}
impl From<&SataQueryResponse> for Bytes {
fn from(value: &SataQueryResponse) -> Self {
match value {
SataQueryResponse::FDInfo(fd_info) => {
let mut buff = BytesMut::with_capacity(88);
buff.put_u32(0);
buff.extend(Bytes::from(fd_info));
buff.freeze()
}
SataQueryResponse::LargeSize(lorg) => {
let mut buff = BytesMut::with_capacity(88);
buff.put_u32(0);
buff.put_u64(*lorg);
buff.extend([0; 76]);
buff.freeze()
}
SataQueryResponse::SmallSize(smol) => {
let mut buff = BytesMut::with_capacity(88);
buff.put_u32(0);
buff.put_u32(*smol);
buff.extend([0; 80]);
buff.freeze()
}
SataQueryResponse::ErrorCode(ec) => {
let mut buff = BytesMut::with_capacity(88);
buff.put_u32(*ec);
buff.extend_from_slice(&[0; 84]);
buff.freeze()
}
}
}
}
impl From<SataQueryResponse> for Bytes {
fn from(value: SataQueryResponse) -> Self {
Self::from(&value)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Valuable)]
pub struct SataFDInfo {
file_or_folder_flags: u32,
perms: u32,
file_length: u32,
created_timestamp: u64,
last_updated_timestamp: u64,
}
impl SataFDInfo {
#[must_use]
pub async fn get_info(
host_filesystem: &HostFilesystem,
metadata: &Metadata,
path: &PathBuf,
) -> Self {
let is_read_only = if metadata.is_dir() {
host_filesystem.folder_is_read_only(path).await
} else {
metadata.permissions().readonly()
};
let file_or_folder_flags = if metadata.is_file() {
0x2C00_0000
} else {
0xAC00_0000
};
let perms = if is_read_only { 0x444 } else { 0x666 };
let file_length = if metadata.is_dir() {
0
} else {
u32::try_from(metadata.len()).unwrap_or(u32::MAX)
};
let created_timestamp = u64::try_from(
metadata
.created()
.unwrap_or(SystemTime::now())
.duration_since(*FAT_TIMESTAMP_START)
.unwrap_or(Duration::from_secs(0))
.as_millis(),
)
.unwrap_or(u64::MAX);
let updated_timestamp = u64::try_from(
metadata
.modified()
.unwrap_or(SystemTime::now())
.duration_since(*FAT_TIMESTAMP_START)
.unwrap_or(Duration::from_secs(0))
.as_millis(),
)
.unwrap_or(u64::MAX);
Self {
file_or_folder_flags,
perms,
file_length,
created_timestamp,
last_updated_timestamp: updated_timestamp,
}
}
#[must_use]
pub fn create_fake_info(
file_or_folder_flags: u32,
perms: u32,
file_length: u32,
created_timestamp: u64,
updated_timestamp: u64,
) -> Self {
Self {
file_or_folder_flags,
perms,
file_length,
created_timestamp,
last_updated_timestamp: updated_timestamp,
}
}
#[must_use]
pub const fn flags(&self) -> u32 {
self.file_or_folder_flags
}
#[must_use]
pub const fn exists(&self) -> bool {
(self.file_or_folder_flags & 0x2000_0000) != 0
}
#[must_use]
pub const fn is_file(&self) -> bool {
(self.file_or_folder_flags & 0x8000_0000) == 0
}
#[must_use]
pub const fn is_directory(&self) -> bool {
!self.is_file()
}
#[must_use]
pub const fn permissions(&self) -> u32 {
self.perms
}
#[must_use]
pub const fn file_size(&self) -> Option<u32> {
if self.is_file() {
Some(self.file_length)
} else {
None
}
}
#[must_use]
pub const fn raw_created_timestamp(&self) -> u64 {
self.created_timestamp
}
#[must_use]
pub const fn raw_last_updated_timestamp(&self) -> u64 {
self.last_updated_timestamp
}
}
impl From<&SataFDInfo> for Bytes {
fn from(value: &SataFDInfo) -> Self {
let mut buff = BytesMut::with_capacity(84);
buff.put_u32(value.file_or_folder_flags);
buff.put_u32(value.perms);
buff.put_u32(1);
buff.put_u32(1);
buff.put_u32(value.file_length);
buff.put_u32(0);
buff.put_u32(0xE8);
buff.put_u32(0xDA6F_F000);
buff.put_u32(0);
buff.put_u64(value.created_timestamp);
buff.put_u64(value.last_updated_timestamp);
buff.extend_from_slice(&[0; 32]);
buff.freeze()
}
}
impl From<SataFDInfo> for Bytes {
fn from(value: SataFDInfo) -> Self {
Self::from(&value)
}
}
impl TryFrom<Bytes> for SataFDInfo {
type Error = NetworkParseError;
fn try_from(mut value: Bytes) -> Result<Self, Self::Error> {
if value.len() < 84 {
return Err(NetworkParseError::FieldNotLongEnough(
"SataFDInfo",
"Body",
84,
value.len(),
value,
));
}
if value.len() > 84 {
return Err(NetworkParseError::UnexpectedTrailer(
"SataFDInfoBody",
value.slice(84..),
));
}
let fd_flags = value.get_u32();
let unix_perms = value.get_u32();
_ = value.get_u32();
_ = value.get_u32();
let file_size = value.get_u32();
_ = value.get_u32();
_ = value.get_u32();
_ = value.get_u32();
_ = value.get_u32();
let created_ts = value.get_u64();
let updated_ts = value.get_u64();
Ok(Self {
file_or_folder_flags: fd_flags,
perms: unix_perms,
file_length: file_size,
created_timestamp: created_ts,
last_updated_timestamp: updated_ts,
})
}
}
#[cfg(test)]
mod unit_tests {
use super::*;
#[test]
pub fn query_types_to_and_fro() {
for qt in vec![
SataQueryType::FreeDiskSpace,
SataQueryType::SizeOfFolder,
SataQueryType::FileCount,
SataQueryType::FileDetails,
] {
assert_eq!(Ok(qt), SataQueryType::try_from(u32::from(qt)));
}
}
}