use std::ops::Deref;
use binrw::{NullString, io::TakeSeekExt, prelude::*};
use super::{
ChainedItemList, FileAccessMask, FileAttributes, FileBasicInformation, FileFullEaInformation,
FileModeInformation, FileNameInformation, FilePipeInformation, FilePositionInformation,
};
use crate::{ReparseTag, file_info_classes};
use smb_dtyp::binrw_util::prelude::*;
file_info_classes! {
pub QueryFileInfo {
pub Access = 8,
pub Alignment = 17,
pub All = 18,
pub AlternateName = 21,
pub AttributeTag = 35,
pub Basic = 4,
pub Compression = 28,
pub Ea = 7,
pub FullEa = 15,
pub Id = 59,
pub Internal = 6,
pub Mode = 16,
pub NetworkOpen = 34,
pub NormalizedName = 48,
pub Pipe = 23,
pub PipeLocal = 24,
pub PipeRemote = 25,
pub Position = 14,
pub Standard = 5,
pub Stream = 22,
}
}
pub type QueryFileFullEaInformation = FileFullEaInformation;
pub type FileStreamInformation = ChainedItemList<FileStreamInformationInner, 8>;
#[binrw::binrw]
#[derive(Debug, PartialEq, Eq)]
pub struct FileAccessInformation {
pub access_flags: FileAccessMask,
}
#[binrw::binrw]
#[derive(Debug, PartialEq, Eq)]
pub struct FileAllInformation {
pub basic: FileBasicInformation,
pub standard: FileStandardInformation,
pub internal: FileInternalInformation,
pub ea: FileEaInformation,
pub access: FileAccessInformation,
pub position: FilePositionInformation,
pub mode: FileModeInformation,
pub alignment: FileAlignmentInformation,
pub name: FileNameInformation,
}
#[binrw::binrw]
#[derive(Debug, PartialEq, Eq)]
#[brw(repr(u32))]
pub enum FileAlignmentInformation {
Byte = 0,
Word = 1,
Long = 3,
Quad = 7,
Octa = 0xf,
_32Byte = 0x1f,
_64Byte = 0x3f,
_128Byte = 0x7f,
_256Byte = 0xff,
_512Byte = 0x1ff,
}
#[binrw::binrw]
#[derive(Debug, PartialEq, Eq)]
pub struct FileAlternateNameInformation {
inner: FileNameInformation,
}
impl Deref for FileAlternateNameInformation {
type Target = FileNameInformation;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl From<&str> for FileAlternateNameInformation {
fn from(value: &str) -> Self {
Self {
inner: FileNameInformation::from(value),
}
}
}
#[binrw::binrw]
#[derive(Debug, PartialEq, Eq)]
pub struct FileAttributeTagInformation {
pub file_attributes: FileAttributes,
pub reparse_tag: ReparseTag,
}
#[binrw::binrw]
#[derive(Debug, PartialEq, Eq)]
pub struct FileCompressionInformation {
pub compressed_file_size: u64,
pub compression_format: FileCompressionFormat,
pub compression_unit: u8,
pub chunk_shift: u8,
pub cluster_shift: u8,
#[bw(calc = [0; 3])]
#[br(temp)]
_reserved: [u8; 3],
}
#[binrw::binrw]
#[derive(Debug, PartialEq, Eq)]
#[brw(repr(u16))]
pub enum FileCompressionFormat {
None = 0,
Lznt1 = 2,
}
#[binrw::binrw]
#[derive(Debug, PartialEq, Eq)]
pub struct FileEaInformation {
pub ea_size: u32,
}
#[binrw::binrw]
#[derive(Debug, PartialEq, Eq)]
pub struct FileIdInformation {
pub volume_serial_number: u64,
pub file_id: u128,
}
#[binrw::binrw]
#[derive(Debug, PartialEq, Eq)]
pub struct FileInternalInformation {
pub index_number: u64,
}
#[binrw::binrw]
#[derive(Debug, PartialEq, Eq)]
pub struct FileNetworkOpenInformation {
pub creation_time: FileTime,
pub last_access_time: FileTime,
pub last_write_time: FileTime,
pub change_time: FileTime,
pub allocation_size: u64,
pub end_of_file: u64,
pub file_attributes: FileAttributes,
#[bw(calc = 0)]
#[br(temp)]
_reserved: u32,
}
#[binrw::binrw]
#[derive(Debug, PartialEq, Eq)]
pub struct FileNormalizedNameInformation {
inner: FileNameInformation,
}
impl Deref for FileNormalizedNameInformation {
type Target = FileNameInformation;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl From<&str> for FileNormalizedNameInformation {
fn from(value: &str) -> Self {
Self {
inner: FileNameInformation::from(value),
}
}
}
#[binrw::binrw]
#[derive(Debug, PartialEq, Eq)]
pub struct FilePipeLocalInformation {
pub named_pipe_type: NamedPipeType,
pub named_pipe_configuration: NamedPipeConfiguration,
pub maximum_instances: u32,
pub current_instances: u32,
pub inbound_quota: u32,
pub read_data_available: u32,
pub outbound_quota: u32,
pub write_quota_available: u32,
pub named_pipe_state: NamedPipeState,
pub named_pipe_end: NamedPipeEnd,
}
#[binrw::binrw]
#[derive(Debug, PartialEq, Eq)]
#[brw(repr(u32))]
pub enum NamedPipeType {
ByteStream = 0,
Message = 1,
}
#[binrw::binrw]
#[derive(Debug, PartialEq, Eq)]
#[brw(repr(u32))]
pub enum NamedPipeConfiguration {
Inbound = 0,
Outbound = 1,
FullDuplex = 2,
}
#[binrw::binrw]
#[derive(Debug, PartialEq, Eq)]
#[brw(repr(u32))]
pub enum NamedPipeState {
Disconnected = 1,
Listening = 2,
Connected = 3,
Closing = 4,
}
#[binrw::binrw]
#[derive(Debug, PartialEq, Eq)]
#[brw(repr(u32))]
pub enum NamedPipeEnd {
Client = 0,
Server = 1,
}
#[binrw::binrw]
#[derive(Debug, PartialEq, Eq)]
pub struct FilePipeRemoteInformation {
pub collect_data_time: FileTime,
pub maximum_collection_count: u32,
}
#[binrw::binrw]
#[derive(Debug, PartialEq, Eq)]
pub struct FileStandardInformation {
pub allocation_size: u64,
pub end_of_file: u64,
pub number_of_links: u32,
pub delete_pending: Boolean,
pub directory: Boolean,
#[bw(calc = 0)]
#[br(temp)]
_reserved: u16,
}
#[binrw::binrw]
#[derive(Debug, PartialEq, Eq)]
pub struct FileStreamInformationInner {
#[bw(try_calc = stream_name.size().try_into())]
stream_name_length: u32,
pub stream_size: u64,
pub stream_allocation_size: u64,
#[br(args { size: SizedStringSize::bytes(stream_name_length)})]
pub stream_name: SizedWideString,
}
#[binrw::binrw]
#[derive(Debug, PartialEq, Eq)]
#[bw(import(has_next: bool))]
pub struct FileGetEaInformation {
#[bw(try_calc = ea_name.len().try_into())]
ea_name_length: u8,
#[br(map_stream = |s| s.take_seek(ea_name_length as u64 + 1))]
pub ea_name: NullString,
}
impl FileGetEaInformation {
pub fn new<S: Into<String>>(name: S) -> Self {
Self {
ea_name: NullString::from(name.into()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use smb_tests::*;
use time::macros::datetime;
fn get_file_access_information_for_test() -> FileAccessInformation {
FileAccessInformation {
access_flags: FileAccessMask::new()
.with_file_read_data(true)
.with_file_write_data(true)
.with_file_append_data(true)
.with_file_read_ea(true)
.with_file_write_ea(true)
.with_file_execute(true)
.with_file_delete_child(true)
.with_file_read_attributes(true)
.with_file_write_attributes(true)
.with_delete(true)
.with_read_control(true)
.with_write_dacl(true)
.with_write_owner(true)
.with_synchronize(true),
}
}
const FILE_ACCESS_INFORMATION_FOR_TEST_STRING: &str = "ff011f00";
test_binrw! {
FileAccessInformation: get_file_access_information_for_test() => FILE_ACCESS_INFORMATION_FOR_TEST_STRING
}
fn get_file_alignment_information_for_test() -> FileAlignmentInformation {
FileAlignmentInformation::Byte
}
const FILE_ALIGNMENT_INFORMATION_FOR_TEST_STRING: &str = "00000000";
test_binrw! {
FileAlignmentInformation: get_file_alignment_information_for_test() => FILE_ALIGNMENT_INFORMATION_FOR_TEST_STRING
}
test_binrw! {
FileAlternateNameInformation: FileAlternateNameInformation::from("query_info_o") => "18000000710075006500720079005f0069006e0066006f005f006f00"
}
test_binrw! {
struct FileAttributeTagInformation {
file_attributes: FileAttributes::new()
.with_archive(true),
reparse_tag: ReparseTag::ReservedZero,
} => "2000000000000000"
}
fn get_file_basic_information_for_test() -> FileBasicInformation {
FileBasicInformation {
creation_time: datetime!(2025-10-17 10:35:07.801764000).into(),
last_access_time: datetime!(2025-10-17 10:35:07.801764000).into(),
last_write_time: datetime!(2025-10-17 10:35:07.801764000).into(),
change_time: datetime!(2025-10-17 10:35:07.801764000).into(),
file_attributes: FileAttributes::new().with_archive(true),
}
}
const FILE_BASIC_INFORMATION_FOR_TEST_STRING: &str =
"681621b5513fdc01681621b5513fdc01681621b5513fdc01681621b5513fdc012000000000000000";
test_binrw! {
FileBasicInformation: get_file_basic_information_for_test() => FILE_BASIC_INFORMATION_FOR_TEST_STRING
}
test_binrw! {
struct FileCompressionInformation => no {
compressed_file_size: 13,
compression_format: FileCompressionFormat::None,
compression_unit: 0,
chunk_shift: 0,
cluster_shift: 0,
} => "0d000000000000000000000000000000"
}
fn get_internal_information_for_test() -> FileInternalInformation {
FileInternalInformation {
index_number: 0x33b16,
}
}
const FILE_INTERNAL_INFORMATION_FOR_TEST_STRING: &str = "163b030000000000";
test_binrw! {
FileInternalInformation: get_internal_information_for_test() => FILE_INTERNAL_INFORMATION_FOR_TEST_STRING
}
fn get_file_mode_information_for_test() -> FileModeInformation {
FileModeInformation::new().with_synchronous_io_non_alert(true)
}
const FILE_MODE_INFORMATION_FOR_TEST_STRING: &str = "20000000";
test_binrw! {
FileModeInformation: get_file_mode_information_for_test() => FILE_MODE_INFORMATION_FOR_TEST_STRING
}
test_binrw! {
struct FileNetworkOpenInformation {
creation_time: datetime!(2025-10-17 12:44:04.747034).into(),
last_access_time: datetime!(2025-10-17 12:44:04.747034).into(),
last_write_time: datetime!(2025-10-17 12:44:04.747034).into(),
change_time: datetime!(2025-10-17 12:44:04.747034).into(),
allocation_size: 4096,
end_of_file: 13,
file_attributes: FileAttributes::new().with_archive(true),
} => "043fb5b8633fdc01043fb5b8633fdc01043fb5b8633fdc01043fb5b8633fdc0100100000000000000d000000000000002000000000000000"
}
test_binrw! {
FileNormalizedNameInformation: FileNormalizedNameInformation::from("query_info_on.txt") => "22000000710075006500720079005f0069006e0066006f005f006f006e002e00740078007400"
}
fn get_file_position_information_for_test() -> FilePositionInformation {
FilePositionInformation {
current_byte_offset: 1024,
}
}
const FILE_POSITION_INFORMATION_FOR_TEST_STRING: &str = "0004000000000000";
test_binrw! {
FilePositionInformation: get_file_position_information_for_test() => FILE_POSITION_INFORMATION_FOR_TEST_STRING
}
fn get_standard_information_for_test() -> FileStandardInformation {
FileStandardInformation {
allocation_size: 4096,
end_of_file: 13,
number_of_links: 0,
delete_pending: true.into(),
directory: false.into(),
}
}
const FILE_STANDARD_INFORMATION_FOR_TEST_STRING: &str =
"00100000000000000d000000000000000000000001000000";
test_binrw! {FileStandardInformation: get_standard_information_for_test() => FILE_STANDARD_INFORMATION_FOR_TEST_STRING}
fn get_file_name_information_for_test() -> FileNameInformation {
FileNameInformation::from("File_Name.txt")
}
const FILE_NAME_INFORMATION_FOR_TEST_STRING: &str =
"1a000000460069006c0065005f004e0061006d0065002e00740078007400";
test_binrw!(
FileNameInformation: get_file_name_information_for_test() =>
FILE_NAME_INFORMATION_FOR_TEST_STRING
);
fn get_file_ea_information_for_test() -> FileEaInformation {
FileEaInformation { ea_size: 208 }
}
const FILE_EA_INFORMATION_FOR_TEST_STRING: &str = "d0000000";
test_binrw!(
FileEaInformation: get_file_ea_information_for_test() =>
FILE_EA_INFORMATION_FOR_TEST_STRING
);
const FILE_ALL_INFORMATION_FOR_TEST_STRING: &str = const_format::concatcp!(
FILE_BASIC_INFORMATION_FOR_TEST_STRING,
FILE_STANDARD_INFORMATION_FOR_TEST_STRING,
FILE_INTERNAL_INFORMATION_FOR_TEST_STRING,
FILE_EA_INFORMATION_FOR_TEST_STRING,
FILE_ACCESS_INFORMATION_FOR_TEST_STRING,
FILE_POSITION_INFORMATION_FOR_TEST_STRING,
FILE_MODE_INFORMATION_FOR_TEST_STRING,
FILE_ALIGNMENT_INFORMATION_FOR_TEST_STRING,
FILE_NAME_INFORMATION_FOR_TEST_STRING
);
test_binrw! {
FileAllInformation: FileAllInformation {basic:get_file_basic_information_for_test(),
standard:get_standard_information_for_test(),
internal: get_internal_information_for_test(),
ea: get_file_ea_information_for_test(),
access: get_file_access_information_for_test(),
position: get_file_position_information_for_test(),
mode: get_file_mode_information_for_test(),
alignment: get_file_alignment_information_for_test(),
name: get_file_name_information_for_test(),
}
=> FILE_ALL_INFORMATION_FOR_TEST_STRING
}
test_binrw! {
FileStreamInformation: FileStreamInformation::from(
vec![
FileStreamInformationInner { stream_size: 1096224, stream_allocation_size: 720896, stream_name: "::$DATA".into() },
FileStreamInformationInner { stream_size: 7, stream_allocation_size: 8, stream_name: ":SmartScreen:$DATA".into() },
FileStreamInformationInner { stream_size: 63, stream_allocation_size: 64, stream_name: ":Zone.Identifier:$DATA".into() },
]
) => "280000000e00000020ba10000000000000000b00000000003a003a002400440041005400410000004000000024000000070000000000000008000000000000003a0053006d00610072007400530063007200650065006e003a002400440041005400410000000000000000002c0000003f0000000000000040000000000000003a005a006f006e0065002e004900640065006e007400690066006900650072003a0024004400410054004100"
}
test_binrw! {
struct FileIdInformation {
volume_serial_number: 0xc86ef7996ef77f0e,
file_id: 0x0000000000000000006a00000000cd5a,
} => "0e7ff76e99f76ec85acd000000006a000000000000000000"
}
test_binrw! {
struct FilePipeLocalInformation {
named_pipe_type: NamedPipeType::Message,
named_pipe_configuration: NamedPipeConfiguration::FullDuplex,
maximum_instances: 0xffffffff,
current_instances: 4,
inbound_quota: 2048,
read_data_available: 0,
outbound_quota: 2048,
write_quota_available: 1024,
named_pipe_state: NamedPipeState::Connected,
named_pipe_end: NamedPipeEnd::Client,
} => "0100000002000000ffffffff04000000000800000000000000080000000400000300000000000000"
}
}