use core::mem;
use arrayvec::ArrayVec;
use nt_string::u16strle::U16StrLe;
use zerocopy::{FromBytes, Immutable, KnownLayout, LittleEndian, U32, U64, Unaligned};
use crate::attribute::NtfsAttributeType;
use crate::attribute_value::NtfsAttributeValue;
use crate::error::{NtfsError, Result};
use crate::file_reference::NtfsFileReference;
use crate::helpers::{pod_from_prefix, read_pod};
use crate::indexes::NtfsIndexEntryKey;
use crate::io::{Read, Seek};
use crate::structured_values::{NtfsFileAttributeFlags, NtfsStructuredValue};
use crate::time::NtfsTime;
use crate::types::NtfsPosition;
const FILE_NAME_HEADER_SIZE: usize = 66;
const FILE_NAME_MIN_SIZE: usize = FILE_NAME_HEADER_SIZE + mem::size_of::<u16>();
const NAME_MAX_SIZE: usize = (u8::MAX as usize) * mem::size_of::<u16>();
#[derive(Clone, Debug, FromBytes, Immutable, KnownLayout, Unaligned)]
#[repr(C, packed)]
struct FileNameHeader {
parent_directory_reference: NtfsFileReference,
creation_time: NtfsTime,
modification_time: NtfsTime,
mft_record_modification_time: NtfsTime,
access_time: NtfsTime,
allocated_size: U64<LittleEndian>,
data_size: U64<LittleEndian>,
file_attributes: U32<LittleEndian>,
reparse_point_tag: U32<LittleEndian>,
name_length: u8,
namespace: u8,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(u8)]
pub enum NtfsFileNamespace {
Posix = 0,
Win32 = 1,
Dos = 2,
Win32AndDos = 3,
}
impl TryFrom<u8> for NtfsFileNamespace {
type Error = ();
fn try_from(value: u8) -> core::result::Result<Self, Self::Error> {
match value {
0 => Ok(Self::Posix),
1 => Ok(Self::Win32),
2 => Ok(Self::Dos),
3 => Ok(Self::Win32AndDos),
_ => Err(()),
}
}
}
#[derive(Clone, Debug)]
pub struct NtfsFileName {
header: FileNameHeader,
name: ArrayVec<u8, NAME_MAX_SIZE>,
}
#[derive(Clone, Debug)]
pub struct NtfsFileNameRef<'a> {
header: FileNameHeader,
name: &'a [u8],
}
impl<'a> NtfsFileNameRef<'a> {
pub(crate) fn new(slice: &'a [u8], position: NtfsPosition) -> Result<Self> {
if slice.len() < FILE_NAME_MIN_SIZE {
return Err(NtfsError::InvalidStructuredValueSize {
position,
ty: NtfsAttributeType::FileName,
expected: FILE_NAME_MIN_SIZE as u64,
actual: slice.len() as u64,
});
}
let header = pod_from_prefix::<FileNameHeader, FILE_NAME_HEADER_SIZE>(slice)
.expect("the file name header slice has a validated size");
NtfsFileName::validate_header(&header, slice.len() as u64, position)?;
let name_end = FILE_NAME_HEADER_SIZE + NtfsFileName::header_name_length(&header);
Ok(Self {
header,
name: &slice[FILE_NAME_HEADER_SIZE..name_end],
})
}
pub fn access_time(&self) -> NtfsTime {
self.header.access_time
}
pub fn allocated_size(&self) -> u64 {
self.header.allocated_size.get()
}
pub fn creation_time(&self) -> NtfsTime {
self.header.creation_time
}
pub fn file_attributes(&self) -> NtfsFileAttributeFlags {
NtfsFileAttributeFlags::from_bits_truncate(self.header.file_attributes.get())
}
pub fn mft_record_modification_time(&self) -> NtfsTime {
self.header.mft_record_modification_time
}
pub fn modification_time(&self) -> NtfsTime {
self.header.modification_time
}
pub fn name(&self) -> U16StrLe<'a> {
U16StrLe(self.name)
}
pub fn namespace(&self) -> NtfsFileNamespace {
NtfsFileNamespace::try_from(self.header.namespace).unwrap()
}
pub fn parent_directory_reference(&self) -> NtfsFileReference {
self.header.parent_directory_reference
}
}
impl NtfsFileName {
pub(crate) fn name_from_slice(slice: &[u8], position: NtfsPosition) -> Result<U16StrLe<'_>> {
if slice.len() < FILE_NAME_MIN_SIZE {
return Err(NtfsError::InvalidStructuredValueSize {
position,
ty: NtfsAttributeType::FileName,
expected: FILE_NAME_MIN_SIZE as u64,
actual: slice.len() as u64,
});
}
let header = pod_from_prefix::<FileNameHeader, FILE_NAME_HEADER_SIZE>(slice)
.expect("the file name header slice has a validated size");
Self::validate_header(&header, slice.len() as u64, position)?;
let name_end = FILE_NAME_HEADER_SIZE + Self::header_name_length(&header);
Ok(U16StrLe(&slice[FILE_NAME_HEADER_SIZE..name_end]))
}
fn new<T>(r: &mut T, position: NtfsPosition, value_length: u64) -> Result<Self>
where
T: Read,
{
if value_length < FILE_NAME_MIN_SIZE as u64 {
return Err(NtfsError::InvalidStructuredValueSize {
position,
ty: NtfsAttributeType::FileName,
expected: FILE_NAME_MIN_SIZE as u64,
actual: value_length,
});
}
let header = read_pod::<T, FileNameHeader, FILE_NAME_HEADER_SIZE>(r)?;
Self::validate_header(&header, value_length, position)?;
let mut name = ArrayVec::new();
name.extend(core::iter::repeat_n(0, Self::header_name_length(&header)));
let mut file_name = Self { header, name };
file_name.read_name(r)?;
Ok(file_name)
}
pub fn access_time(&self) -> NtfsTime {
self.header.access_time
}
pub fn allocated_size(&self) -> u64 {
self.header.allocated_size.get()
}
pub fn creation_time(&self) -> NtfsTime {
self.header.creation_time
}
pub fn data_size(&self) -> u64 {
self.header.data_size.get()
}
pub fn file_attributes(&self) -> NtfsFileAttributeFlags {
NtfsFileAttributeFlags::from_bits_truncate(self.header.file_attributes.get())
}
pub fn is_directory(&self) -> bool {
self.file_attributes()
.contains(NtfsFileAttributeFlags::IS_DIRECTORY)
}
pub fn mft_record_modification_time(&self) -> NtfsTime {
self.header.mft_record_modification_time
}
pub fn modification_time(&self) -> NtfsTime {
self.header.modification_time
}
pub fn name<'a>(&'a self) -> U16StrLe<'a> {
U16StrLe(&self.name)
}
pub fn name_length(&self) -> usize {
Self::header_name_length(&self.header)
}
pub fn namespace(&self) -> NtfsFileNamespace {
NtfsFileNamespace::try_from(self.header.namespace).unwrap()
}
pub fn parent_directory_reference(&self) -> NtfsFileReference {
self.header.parent_directory_reference
}
fn read_name<T>(&mut self, r: &mut T) -> Result<()>
where
T: Read,
{
debug_assert_eq!(self.name.len(), self.name_length());
let name_length = self.name_length();
r.read_exact(&mut self.name[..name_length])?;
self.name.truncate(name_length);
Ok(())
}
fn header_name_length(header: &FileNameHeader) -> usize {
header.name_length as usize * mem::size_of::<u16>()
}
fn validate_header(
header: &FileNameHeader,
data_size: u64,
position: NtfsPosition,
) -> Result<()> {
let total_size = (FILE_NAME_HEADER_SIZE + Self::header_name_length(header)) as u64;
if total_size > data_size {
return Err(NtfsError::InvalidStructuredValueSize {
position,
ty: NtfsAttributeType::FileName,
expected: data_size,
actual: total_size,
});
}
if NtfsFileNamespace::try_from(header.namespace).is_err() {
return Err(NtfsError::UnsupportedFileNamespace {
position,
actual: header.namespace,
});
}
Ok(())
}
}
impl<'n, 'f> NtfsStructuredValue<'n, 'f> for NtfsFileName {
const TY: NtfsAttributeType = NtfsAttributeType::FileName;
fn from_attribute_value<T>(fs: &mut T, value: NtfsAttributeValue<'n, 'f>) -> Result<Self>
where
T: Read + Seek,
{
let position = value.data_position();
let value_length = value.len();
let mut value_attached = value.attach(fs);
Self::new(&mut value_attached, position, value_length)
}
}
impl NtfsIndexEntryKey for NtfsFileName {
fn key_from_slice(slice: &[u8], position: NtfsPosition) -> Result<Self> {
let name = Self::name_from_slice(slice, position)?;
let header = pod_from_prefix::<FileNameHeader, FILE_NAME_HEADER_SIZE>(slice)
.expect("the file name header slice has a validated size");
let mut owned_name = ArrayVec::new();
owned_name
.try_extend_from_slice(name.0)
.expect("the validated file name fits in an ArrayVec");
Ok(Self {
header,
name: owned_name,
})
}
}