1use std::ops::Deref;
8
9use binrw::{NullString, io::TakeSeekExt, prelude::*};
10
11use super::{
12 ChainedItemList, FileAccessMask, FileAttributes, FileBasicInformation, FileFullEaInformation,
13 FileModeInformation, FileNameInformation, FilePipeInformation, FilePositionInformation,
14};
15use crate::{ReparseTag, file_info_classes};
16use smb_dtyp::binrw_util::prelude::*;
17
18file_info_classes! {
19 pub QueryFileInfo {
21 pub Access = 8,
22 pub Alignment = 17,
23 pub All = 18,
24 pub AlternateName = 21,
25 pub AttributeTag = 35,
26 pub Basic = 4,
27 pub Compression = 28,
28 pub Ea = 7,
29 pub FullEa = 15,
30 pub Id = 59,
31 pub Internal = 6,
32 pub Mode = 16,
33 pub NetworkOpen = 34,
34 pub NormalizedName = 48,
35 pub Pipe = 23,
36 pub PipeLocal = 24,
37 pub PipeRemote = 25,
38 pub Position = 14,
39 pub Standard = 5,
40 pub Stream = 22,
41 }
42}
43
44pub type QueryFileFullEaInformation = FileFullEaInformation;
45
46pub type FileStreamInformation = ChainedItemList<FileStreamInformationInner, 8>;
47
48#[binrw::binrw]
52#[derive(Debug, PartialEq, Eq)]
53pub struct FileAccessInformation {
54 pub access_flags: FileAccessMask,
56}
57
58#[binrw::binrw]
62#[derive(Debug, PartialEq, Eq)]
63pub struct FileAllInformation {
64 pub basic: FileBasicInformation,
66 pub standard: FileStandardInformation,
68 pub internal: FileInternalInformation,
70 pub ea: FileEaInformation,
72 pub access: FileAccessInformation,
74 pub position: FilePositionInformation,
76 pub mode: FileModeInformation,
78 pub alignment: FileAlignmentInformation,
80 pub name: FileNameInformation,
82}
83
84#[binrw::binrw]
88#[derive(Debug, PartialEq, Eq)]
89#[brw(repr(u32))]
90pub enum FileAlignmentInformation {
91 Byte = 0,
93 Word = 1,
95 Long = 3,
97 Quad = 7,
99 Octa = 0xf,
101 _32Byte = 0x1f,
103 _64Byte = 0x3f,
105 _128Byte = 0x7f,
107 _256Byte = 0xff,
109 _512Byte = 0x1ff,
111}
112
113#[binrw::binrw]
117#[derive(Debug, PartialEq, Eq)]
118pub struct FileAlternateNameInformation {
119 inner: FileNameInformation,
121}
122
123impl Deref for FileAlternateNameInformation {
124 type Target = FileNameInformation;
125
126 fn deref(&self) -> &Self::Target {
127 &self.inner
128 }
129}
130
131impl From<&str> for FileAlternateNameInformation {
132 fn from(value: &str) -> Self {
133 Self {
134 inner: FileNameInformation::from(value),
135 }
136 }
137}
138
139#[binrw::binrw]
143#[derive(Debug, PartialEq, Eq)]
144pub struct FileAttributeTagInformation {
145 pub file_attributes: FileAttributes,
147 pub reparse_tag: ReparseTag,
149}
150
151#[binrw::binrw]
155#[derive(Debug, PartialEq, Eq)]
156pub struct FileCompressionInformation {
157 pub compressed_file_size: u64,
159 pub compression_format: FileCompressionFormat,
161 pub compression_unit: u8,
163 pub chunk_shift: u8,
165 pub cluster_shift: u8,
167
168 #[bw(calc = [0; 3])]
169 #[br(temp)]
170 _reserved: [u8; 3],
171}
172
173#[binrw::binrw]
175#[derive(Debug, PartialEq, Eq)]
176#[brw(repr(u16))]
177pub enum FileCompressionFormat {
178 None = 0,
180 Lznt1 = 2,
182}
183
184#[binrw::binrw]
188#[derive(Debug, PartialEq, Eq)]
189pub struct FileEaInformation {
190 pub ea_size: u32,
192}
193
194#[binrw::binrw]
198#[derive(Debug, PartialEq, Eq)]
199pub struct FileIdInformation {
200 pub volume_serial_number: u64,
202 pub file_id: u128,
204}
205
206#[binrw::binrw]
210#[derive(Debug, PartialEq, Eq)]
211pub struct FileInternalInformation {
212 pub index_number: u64,
214}
215
216#[binrw::binrw]
220#[derive(Debug, PartialEq, Eq)]
221pub struct FileNetworkOpenInformation {
222 pub creation_time: FileTime,
224 pub last_access_time: FileTime,
226 pub last_write_time: FileTime,
228 pub change_time: FileTime,
230 pub allocation_size: u64,
232 pub end_of_file: u64,
234 pub file_attributes: FileAttributes,
236 #[bw(calc = 0)]
237 #[br(temp)]
238 _reserved: u32,
239}
240
241#[binrw::binrw]
248#[derive(Debug, PartialEq, Eq)]
249pub struct FileNormalizedNameInformation {
250 inner: FileNameInformation,
252}
253
254impl Deref for FileNormalizedNameInformation {
255 type Target = FileNameInformation;
256
257 fn deref(&self) -> &Self::Target {
258 &self.inner
259 }
260}
261
262impl From<&str> for FileNormalizedNameInformation {
263 fn from(value: &str) -> Self {
264 Self {
265 inner: FileNameInformation::from(value),
266 }
267 }
268}
269
270#[binrw::binrw]
274#[derive(Debug, PartialEq, Eq)]
275pub struct FilePipeLocalInformation {
276 pub named_pipe_type: NamedPipeType,
278 pub named_pipe_configuration: NamedPipeConfiguration,
280 pub maximum_instances: u32,
282 pub current_instances: u32,
284 pub inbound_quota: u32,
286 pub read_data_available: u32,
288 pub outbound_quota: u32,
290 pub write_quota_available: u32,
292 pub named_pipe_state: NamedPipeState,
294 pub named_pipe_end: NamedPipeEnd,
296}
297
298#[binrw::binrw]
300#[derive(Debug, PartialEq, Eq)]
301#[brw(repr(u32))]
302pub enum NamedPipeType {
303 ByteStream = 0,
305 Message = 1,
307}
308
309#[binrw::binrw]
311#[derive(Debug, PartialEq, Eq)]
312#[brw(repr(u32))]
313pub enum NamedPipeConfiguration {
314 Inbound = 0,
316 Outbound = 1,
318 FullDuplex = 2,
320}
321
322#[binrw::binrw]
324#[derive(Debug, PartialEq, Eq)]
325#[brw(repr(u32))]
326pub enum NamedPipeState {
327 Disconnected = 1,
329 Listening = 2,
331 Connected = 3,
333 Closing = 4,
335}
336
337#[binrw::binrw]
339#[derive(Debug, PartialEq, Eq)]
340#[brw(repr(u32))]
341pub enum NamedPipeEnd {
342 Client = 0,
344 Server = 1,
346}
347
348#[binrw::binrw]
352#[derive(Debug, PartialEq, Eq)]
353pub struct FilePipeRemoteInformation {
354 pub collect_data_time: FileTime,
356 pub maximum_collection_count: u32,
358}
359
360#[binrw::binrw]
364#[derive(Debug, PartialEq, Eq)]
365pub struct FileStandardInformation {
366 pub allocation_size: u64,
368 pub end_of_file: u64,
370 pub number_of_links: u32,
372 pub delete_pending: Boolean,
374 pub directory: Boolean,
376 #[bw(calc = 0)]
377 #[br(temp)]
378 _reserved: u16,
379}
380
381#[binrw::binrw]
385#[derive(Debug, PartialEq, Eq)]
386pub struct FileStreamInformationInner {
387 #[bw(try_calc = stream_name.size().try_into())]
388 stream_name_length: u32,
389 pub stream_size: u64,
391 pub stream_allocation_size: u64,
393 #[br(args { size: SizedStringSize::bytes(stream_name_length)})]
395 pub stream_name: SizedWideString,
396}
397
398#[binrw::binrw]
400#[derive(Debug, PartialEq, Eq)]
401#[bw(import(has_next: bool))]
402pub struct FileGetEaInformation {
403 #[bw(try_calc = ea_name.len().try_into())]
405 ea_name_length: u8,
406 #[br(map_stream = |s| s.take_seek(ea_name_length as u64 + 1))]
408 pub ea_name: NullString,
409}
410
411impl FileGetEaInformation {
412 pub fn new<S: Into<String>>(name: S) -> Self {
413 Self {
414 ea_name: NullString::from(name.into()),
415 }
416 }
417}
418
419#[cfg(test)]
420mod tests {
421 use super::*;
422 use smb_tests::*;
423 use time::macros::datetime;
424
425 fn get_file_access_information_for_test() -> FileAccessInformation {
426 FileAccessInformation {
427 access_flags: FileAccessMask::new()
428 .with_file_read_data(true)
429 .with_file_write_data(true)
430 .with_file_append_data(true)
431 .with_file_read_ea(true)
432 .with_file_write_ea(true)
433 .with_file_execute(true)
434 .with_file_delete_child(true)
435 .with_file_read_attributes(true)
436 .with_file_write_attributes(true)
437 .with_delete(true)
438 .with_read_control(true)
439 .with_write_dacl(true)
440 .with_write_owner(true)
441 .with_synchronize(true),
442 }
443 }
444 const FILE_ACCESS_INFORMATION_FOR_TEST_STRING: &str = "ff011f00";
445 test_binrw! {
446 FileAccessInformation: get_file_access_information_for_test() => FILE_ACCESS_INFORMATION_FOR_TEST_STRING
447 }
448
449 fn get_file_alignment_information_for_test() -> FileAlignmentInformation {
450 FileAlignmentInformation::Byte
451 }
452 const FILE_ALIGNMENT_INFORMATION_FOR_TEST_STRING: &str = "00000000";
453 test_binrw! {
454 FileAlignmentInformation: get_file_alignment_information_for_test() => FILE_ALIGNMENT_INFORMATION_FOR_TEST_STRING
455 }
456
457 test_binrw! {
458 FileAlternateNameInformation: FileAlternateNameInformation::from("query_info_o") => "18000000710075006500720079005f0069006e0066006f005f006f00"
459 }
460
461 test_binrw! {
462 struct FileAttributeTagInformation {
464 file_attributes: FileAttributes::new()
465 .with_archive(true),
466 reparse_tag: ReparseTag::ReservedZero,
467 } => "2000000000000000"
468 }
469
470 fn get_file_basic_information_for_test() -> FileBasicInformation {
471 FileBasicInformation {
472 creation_time: datetime!(2025-10-17 10:35:07.801764000).into(),
473 last_access_time: datetime!(2025-10-17 10:35:07.801764000).into(),
474 last_write_time: datetime!(2025-10-17 10:35:07.801764000).into(),
475 change_time: datetime!(2025-10-17 10:35:07.801764000).into(),
476 file_attributes: FileAttributes::new().with_archive(true),
477 }
478 }
479 const FILE_BASIC_INFORMATION_FOR_TEST_STRING: &str =
480 "681621b5513fdc01681621b5513fdc01681621b5513fdc01681621b5513fdc012000000000000000";
481
482 test_binrw! {
483 FileBasicInformation: get_file_basic_information_for_test() => FILE_BASIC_INFORMATION_FOR_TEST_STRING
484 }
485
486 test_binrw! {
487 struct FileCompressionInformation => no {
489 compressed_file_size: 13,
490 compression_format: FileCompressionFormat::None,
491 compression_unit: 0,
492 chunk_shift: 0,
493 cluster_shift: 0,
494 } => "0d000000000000000000000000000000"
495 }
496
497 fn get_internal_information_for_test() -> FileInternalInformation {
498 FileInternalInformation {
499 index_number: 0x33b16,
500 }
501 }
502 const FILE_INTERNAL_INFORMATION_FOR_TEST_STRING: &str = "163b030000000000";
503
504 test_binrw! {
505 FileInternalInformation: get_internal_information_for_test() => FILE_INTERNAL_INFORMATION_FOR_TEST_STRING
506 }
507
508 fn get_file_mode_information_for_test() -> FileModeInformation {
509 FileModeInformation::new().with_synchronous_io_non_alert(true)
510 }
511
512 const FILE_MODE_INFORMATION_FOR_TEST_STRING: &str = "20000000";
513
514 test_binrw! {
515 FileModeInformation: get_file_mode_information_for_test() => FILE_MODE_INFORMATION_FOR_TEST_STRING
516 }
517
518 test_binrw! {
519 struct FileNetworkOpenInformation {
520 creation_time: datetime!(2025-10-17 12:44:04.747034).into(),
521 last_access_time: datetime!(2025-10-17 12:44:04.747034).into(),
522 last_write_time: datetime!(2025-10-17 12:44:04.747034).into(),
523 change_time: datetime!(2025-10-17 12:44:04.747034).into(),
524 allocation_size: 4096,
525 end_of_file: 13,
526 file_attributes: FileAttributes::new().with_archive(true),
527 } => "043fb5b8633fdc01043fb5b8633fdc01043fb5b8633fdc01043fb5b8633fdc0100100000000000000d000000000000002000000000000000"
528 }
529
530 test_binrw! {
531 FileNormalizedNameInformation: FileNormalizedNameInformation::from("query_info_on.txt") => "22000000710075006500720079005f0069006e0066006f005f006f006e002e00740078007400"
532 }
533
534 fn get_file_position_information_for_test() -> FilePositionInformation {
535 FilePositionInformation {
536 current_byte_offset: 1024,
537 }
538 }
539 const FILE_POSITION_INFORMATION_FOR_TEST_STRING: &str = "0004000000000000";
540
541 test_binrw! {
542 FilePositionInformation: get_file_position_information_for_test() => FILE_POSITION_INFORMATION_FOR_TEST_STRING
543 }
544
545 fn get_standard_information_for_test() -> FileStandardInformation {
546 FileStandardInformation {
547 allocation_size: 4096,
548 end_of_file: 13,
549 number_of_links: 0,
550 delete_pending: true.into(),
551 directory: false.into(),
552 }
553 }
554 const FILE_STANDARD_INFORMATION_FOR_TEST_STRING: &str =
555 "00100000000000000d000000000000000000000001000000";
556
557 test_binrw! {FileStandardInformation: get_standard_information_for_test() => FILE_STANDARD_INFORMATION_FOR_TEST_STRING}
558
559 fn get_file_name_information_for_test() -> FileNameInformation {
560 FileNameInformation::from("File_Name.txt")
561 }
562 const FILE_NAME_INFORMATION_FOR_TEST_STRING: &str =
563 "1a000000460069006c0065005f004e0061006d0065002e00740078007400";
564 test_binrw!(
565 FileNameInformation: get_file_name_information_for_test() =>
566 FILE_NAME_INFORMATION_FOR_TEST_STRING
567 );
568
569 fn get_file_ea_information_for_test() -> FileEaInformation {
570 FileEaInformation { ea_size: 208 }
571 }
572 const FILE_EA_INFORMATION_FOR_TEST_STRING: &str = "d0000000";
573 test_binrw!(
574 FileEaInformation: get_file_ea_information_for_test() =>
575 FILE_EA_INFORMATION_FOR_TEST_STRING
576 );
577
578 const FILE_ALL_INFORMATION_FOR_TEST_STRING: &str = const_format::concatcp!(
579 FILE_BASIC_INFORMATION_FOR_TEST_STRING,
580 FILE_STANDARD_INFORMATION_FOR_TEST_STRING,
581 FILE_INTERNAL_INFORMATION_FOR_TEST_STRING,
582 FILE_EA_INFORMATION_FOR_TEST_STRING,
583 FILE_ACCESS_INFORMATION_FOR_TEST_STRING,
584 FILE_POSITION_INFORMATION_FOR_TEST_STRING,
585 FILE_MODE_INFORMATION_FOR_TEST_STRING,
586 FILE_ALIGNMENT_INFORMATION_FOR_TEST_STRING,
587 FILE_NAME_INFORMATION_FOR_TEST_STRING
588 );
589 test_binrw! {
590 FileAllInformation: FileAllInformation {basic:get_file_basic_information_for_test(),
591 standard:get_standard_information_for_test(),
592 internal: get_internal_information_for_test(),
593 ea: get_file_ea_information_for_test(),
594 access: get_file_access_information_for_test(),
595 position: get_file_position_information_for_test(),
596 mode: get_file_mode_information_for_test(),
597 alignment: get_file_alignment_information_for_test(),
598 name: get_file_name_information_for_test(),
599 }
600 => FILE_ALL_INFORMATION_FOR_TEST_STRING
601 }
602
603 test_binrw! {
604 FileStreamInformation: FileStreamInformation::from(
605 vec![
606 FileStreamInformationInner { stream_size: 1096224, stream_allocation_size: 720896, stream_name: "::$DATA".into() },
607 FileStreamInformationInner { stream_size: 7, stream_allocation_size: 8, stream_name: ":SmartScreen:$DATA".into() },
608 FileStreamInformationInner { stream_size: 63, stream_allocation_size: 64, stream_name: ":Zone.Identifier:$DATA".into() },
609 ]
610 ) => "280000000e00000020ba10000000000000000b00000000003a003a002400440041005400410000004000000024000000070000000000000008000000000000003a0053006d00610072007400530063007200650065006e003a002400440041005400410000000000000000002c0000003f0000000000000040000000000000003a005a006f006e0065002e004900640065006e007400690066006900650072003a0024004400410054004100"
611 }
612
613 test_binrw! {
614 struct FileIdInformation {
615 volume_serial_number: 0xc86ef7996ef77f0e,
616 file_id: 0x0000000000000000006a00000000cd5a,
617 } => "0e7ff76e99f76ec85acd000000006a000000000000000000"
618 }
619
620 test_binrw! {
621 struct FilePipeLocalInformation {
622 named_pipe_type: NamedPipeType::Message,
623 named_pipe_configuration: NamedPipeConfiguration::FullDuplex,
624 maximum_instances: 0xffffffff,
625 current_instances: 4,
626 inbound_quota: 2048,
627 read_data_available: 0,
628 outbound_quota: 2048,
629 write_quota_available: 1024,
630 named_pipe_state: NamedPipeState::Connected,
631 named_pipe_end: NamedPipeEnd::Client,
632 } => "0100000002000000ffffffff04000000000800000000000000080000000400000300000000000000"
633 }
634
635 }