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 _reserved: [u8; 3],
170}
171
172#[binrw::binrw]
174#[derive(Debug, PartialEq, Eq)]
175#[brw(repr(u16))]
176pub enum FileCompressionFormat {
177 None = 0,
179 Lznt1 = 2,
181}
182
183#[binrw::binrw]
187#[derive(Debug, PartialEq, Eq)]
188pub struct FileEaInformation {
189 pub ea_size: u32,
191}
192
193#[binrw::binrw]
197#[derive(Debug, PartialEq, Eq)]
198pub struct FileIdInformation {
199 pub volume_serial_number: u64,
201 pub file_id: u128,
203}
204
205#[binrw::binrw]
209#[derive(Debug, PartialEq, Eq)]
210pub struct FileInternalInformation {
211 pub index_number: u64,
213}
214
215#[binrw::binrw]
219#[derive(Debug, PartialEq, Eq)]
220pub struct FileNetworkOpenInformation {
221 pub creation_time: FileTime,
223 pub last_access_time: FileTime,
225 pub last_write_time: FileTime,
227 pub change_time: FileTime,
229 pub allocation_size: u64,
231 pub end_of_file: u64,
233 pub file_attributes: FileAttributes,
235 #[bw(calc = 0)]
236 _reserved: u32,
237}
238
239#[binrw::binrw]
246#[derive(Debug, PartialEq, Eq)]
247pub struct FileNormalizedNameInformation {
248 inner: FileNameInformation,
250}
251
252impl Deref for FileNormalizedNameInformation {
253 type Target = FileNameInformation;
254
255 fn deref(&self) -> &Self::Target {
256 &self.inner
257 }
258}
259
260impl From<&str> for FileNormalizedNameInformation {
261 fn from(value: &str) -> Self {
262 Self {
263 inner: FileNameInformation::from(value),
264 }
265 }
266}
267
268#[binrw::binrw]
272#[derive(Debug, PartialEq, Eq)]
273pub struct FilePipeLocalInformation {
274 pub named_pipe_type: NamedPipeType,
276 pub named_pipe_configuration: NamedPipeConfiguration,
278 pub maximum_instances: u32,
280 pub current_instances: u32,
282 pub inbound_quota: u32,
284 pub read_data_available: u32,
286 pub outbound_quota: u32,
288 pub write_quota_available: u32,
290 pub named_pipe_state: NamedPipeState,
292 pub named_pipe_end: NamedPipeEnd,
294}
295
296#[binrw::binrw]
298#[derive(Debug, PartialEq, Eq)]
299#[brw(repr(u32))]
300pub enum NamedPipeType {
301 ByteStream = 0,
303 Message = 1,
305}
306
307#[binrw::binrw]
309#[derive(Debug, PartialEq, Eq)]
310#[brw(repr(u32))]
311pub enum NamedPipeConfiguration {
312 Inbound = 0,
314 Outbound = 1,
316 FullDuplex = 2,
318}
319
320#[binrw::binrw]
322#[derive(Debug, PartialEq, Eq)]
323#[brw(repr(u32))]
324pub enum NamedPipeState {
325 Disconnected = 1,
327 Listening = 2,
329 Connected = 3,
331 Closing = 4,
333}
334
335#[binrw::binrw]
337#[derive(Debug, PartialEq, Eq)]
338#[brw(repr(u32))]
339pub enum NamedPipeEnd {
340 Client = 0,
342 Server = 1,
344}
345
346#[binrw::binrw]
350#[derive(Debug, PartialEq, Eq)]
351pub struct FilePipeRemoteInformation {
352 pub collect_data_time: FileTime,
354 pub maximum_collection_count: u32,
356}
357
358#[binrw::binrw]
362#[derive(Debug, PartialEq, Eq)]
363pub struct FileStandardInformation {
364 pub allocation_size: u64,
366 pub end_of_file: u64,
368 pub number_of_links: u32,
370 pub delete_pending: Boolean,
372 pub directory: Boolean,
374 #[bw(calc = 0)]
375 reserved: u16,
376}
377
378#[binrw::binrw]
382#[derive(Debug, PartialEq, Eq)]
383pub struct FileStreamInformationInner {
384 #[bw(try_calc = stream_name.size().try_into())]
385 stream_name_length: u32,
386 pub stream_size: u64,
388 pub stream_allocation_size: u64,
390 #[br(args { size: SizedStringSize::bytes(stream_name_length)})]
392 pub stream_name: SizedWideString,
393}
394
395#[binrw::binrw]
397#[derive(Debug, PartialEq, Eq)]
398#[bw(import(has_next: bool))]
399pub struct FileGetEaInformation {
400 #[bw(try_calc = ea_name.len().try_into())]
402 ea_name_length: u8,
403 #[br(map_stream = |s| s.take_seek(ea_name_length as u64 + 1))]
405 pub ea_name: NullString,
406}
407
408impl FileGetEaInformation {
409 pub fn new<S: Into<String>>(name: S) -> Self {
410 Self {
411 ea_name: NullString::from(name.into()),
412 }
413 }
414}
415
416#[cfg(test)]
417mod tests {
418 use super::*;
419 use smb_tests::*;
420 use time::macros::datetime;
421
422 fn get_file_access_information_for_test() -> FileAccessInformation {
423 FileAccessInformation {
424 access_flags: FileAccessMask::new()
425 .with_file_read_data(true)
426 .with_file_write_data(true)
427 .with_file_append_data(true)
428 .with_file_read_ea(true)
429 .with_file_write_ea(true)
430 .with_file_execute(true)
431 .with_file_delete_child(true)
432 .with_file_read_attributes(true)
433 .with_file_write_attributes(true)
434 .with_delete(true)
435 .with_read_control(true)
436 .with_write_dacl(true)
437 .with_write_owner(true)
438 .with_synchronize(true),
439 }
440 }
441 const FILE_ACCESS_INFORMATION_FOR_TEST_STRING: &str = "ff011f00";
442 test_binrw! {
443 FileAccessInformation: get_file_access_information_for_test() => FILE_ACCESS_INFORMATION_FOR_TEST_STRING
444 }
445
446 fn get_file_alignment_information_for_test() -> FileAlignmentInformation {
447 FileAlignmentInformation::Byte
448 }
449 const FILE_ALIGNMENT_INFORMATION_FOR_TEST_STRING: &str = "00000000";
450 test_binrw! {
451 FileAlignmentInformation: get_file_alignment_information_for_test() => FILE_ALIGNMENT_INFORMATION_FOR_TEST_STRING
452 }
453
454 test_binrw! {
455 FileAlternateNameInformation: FileAlternateNameInformation::from("query_info_o") => "18000000710075006500720079005f0069006e0066006f005f006f00"
456 }
457
458 test_binrw! {
459 struct FileAttributeTagInformation {
461 file_attributes: FileAttributes::new()
462 .with_archive(true),
463 reparse_tag: ReparseTag::ReservedZero,
464 } => "2000000000000000"
465 }
466
467 fn get_file_basic_information_for_test() -> FileBasicInformation {
468 FileBasicInformation {
469 creation_time: datetime!(2025-10-17 10:35:07.801764000).into(),
470 last_access_time: datetime!(2025-10-17 10:35:07.801764000).into(),
471 last_write_time: datetime!(2025-10-17 10:35:07.801764000).into(),
472 change_time: datetime!(2025-10-17 10:35:07.801764000).into(),
473 file_attributes: FileAttributes::new().with_archive(true),
474 }
475 }
476 const FILE_BASIC_INFORMATION_FOR_TEST_STRING: &str =
477 "681621b5513fdc01681621b5513fdc01681621b5513fdc01681621b5513fdc012000000000000000";
478
479 test_binrw! {
480 FileBasicInformation: get_file_basic_information_for_test() => FILE_BASIC_INFORMATION_FOR_TEST_STRING
481 }
482
483 test_binrw! {
484 struct FileCompressionInformation => no {
486 compressed_file_size: 13,
487 compression_format: FileCompressionFormat::None,
488 compression_unit: 0,
489 chunk_shift: 0,
490 cluster_shift: 0,
491 } => "0d000000000000000000000000000000"
492 }
493
494 fn get_internal_information_for_test() -> FileInternalInformation {
495 FileInternalInformation {
496 index_number: 0x33b16,
497 }
498 }
499 const FILE_INTERNAL_INFORMATION_FOR_TEST_STRING: &str = "163b030000000000";
500
501 test_binrw! {
502 FileInternalInformation: get_internal_information_for_test() => FILE_INTERNAL_INFORMATION_FOR_TEST_STRING
503 }
504
505 fn get_file_mode_information_for_test() -> FileModeInformation {
506 FileModeInformation::new().with_synchronous_io_non_alert(true)
507 }
508
509 const FILE_MODE_INFORMATION_FOR_TEST_STRING: &str = "20000000";
510
511 test_binrw! {
512 FileModeInformation: get_file_mode_information_for_test() => FILE_MODE_INFORMATION_FOR_TEST_STRING
513 }
514
515 test_binrw! {
516 struct FileNetworkOpenInformation {
517 creation_time: datetime!(2025-10-17 12:44:04.747034).into(),
518 last_access_time: datetime!(2025-10-17 12:44:04.747034).into(),
519 last_write_time: datetime!(2025-10-17 12:44:04.747034).into(),
520 change_time: datetime!(2025-10-17 12:44:04.747034).into(),
521 allocation_size: 4096,
522 end_of_file: 13,
523 file_attributes: FileAttributes::new().with_archive(true),
524 } => "043fb5b8633fdc01043fb5b8633fdc01043fb5b8633fdc01043fb5b8633fdc0100100000000000000d000000000000002000000000000000"
525 }
526
527 test_binrw! {
528 FileNormalizedNameInformation: FileNormalizedNameInformation::from("query_info_on.txt") => "22000000710075006500720079005f0069006e0066006f005f006f006e002e00740078007400"
529 }
530
531 fn get_file_position_information_for_test() -> FilePositionInformation {
532 FilePositionInformation {
533 current_byte_offset: 1024,
534 }
535 }
536 const FILE_POSITION_INFORMATION_FOR_TEST_STRING: &str = "0004000000000000";
537
538 test_binrw! {
539 FilePositionInformation: get_file_position_information_for_test() => FILE_POSITION_INFORMATION_FOR_TEST_STRING
540 }
541
542 fn get_standard_information_for_test() -> FileStandardInformation {
543 FileStandardInformation {
544 allocation_size: 4096,
545 end_of_file: 13,
546 number_of_links: 0,
547 delete_pending: true.into(),
548 directory: false.into(),
549 }
550 }
551 const FILE_STANDARD_INFORMATION_FOR_TEST_STRING: &str =
552 "00100000000000000d000000000000000000000001000000";
553
554 test_binrw! {FileStandardInformation: get_standard_information_for_test() => FILE_STANDARD_INFORMATION_FOR_TEST_STRING}
555
556 fn get_file_name_information_for_test() -> FileNameInformation {
557 FileNameInformation::from("File_Name.txt")
558 }
559 const FILE_NAME_INFORMATION_FOR_TEST_STRING: &str =
560 "1a000000460069006c0065005f004e0061006d0065002e00740078007400";
561 test_binrw!(
562 FileNameInformation: get_file_name_information_for_test() =>
563 FILE_NAME_INFORMATION_FOR_TEST_STRING
564 );
565
566 fn get_file_ea_information_for_test() -> FileEaInformation {
567 FileEaInformation { ea_size: 208 }
568 }
569 const FILE_EA_INFORMATION_FOR_TEST_STRING: &str = "d0000000";
570 test_binrw!(
571 FileEaInformation: get_file_ea_information_for_test() =>
572 FILE_EA_INFORMATION_FOR_TEST_STRING
573 );
574
575 const FILE_ALL_INFORMATION_FOR_TEST_STRING: &str = const_format::concatcp!(
576 FILE_BASIC_INFORMATION_FOR_TEST_STRING,
577 FILE_STANDARD_INFORMATION_FOR_TEST_STRING,
578 FILE_INTERNAL_INFORMATION_FOR_TEST_STRING,
579 FILE_EA_INFORMATION_FOR_TEST_STRING,
580 FILE_ACCESS_INFORMATION_FOR_TEST_STRING,
581 FILE_POSITION_INFORMATION_FOR_TEST_STRING,
582 FILE_MODE_INFORMATION_FOR_TEST_STRING,
583 FILE_ALIGNMENT_INFORMATION_FOR_TEST_STRING,
584 FILE_NAME_INFORMATION_FOR_TEST_STRING
585 );
586 test_binrw! {
587 FileAllInformation: FileAllInformation {basic:get_file_basic_information_for_test(),
588 standard:get_standard_information_for_test(),
589 internal: get_internal_information_for_test(),
590 ea: get_file_ea_information_for_test(),
591 access: get_file_access_information_for_test(),
592 position: get_file_position_information_for_test(),
593 mode: get_file_mode_information_for_test(),
594 alignment: get_file_alignment_information_for_test(),
595 name: get_file_name_information_for_test(),
596 }
597 => FILE_ALL_INFORMATION_FOR_TEST_STRING
598 }
599
600 test_binrw! {
601 FileStreamInformation: FileStreamInformation::from(
602 vec![
603 FileStreamInformationInner { stream_size: 1096224, stream_allocation_size: 720896, stream_name: "::$DATA".into() },
604 FileStreamInformationInner { stream_size: 7, stream_allocation_size: 8, stream_name: ":SmartScreen:$DATA".into() },
605 FileStreamInformationInner { stream_size: 63, stream_allocation_size: 64, stream_name: ":Zone.Identifier:$DATA".into() },
606 ]
607 ) => "280000000e00000020ba10000000000000000b00000000003a003a002400440041005400410000004000000024000000070000000000000008000000000000003a0053006d00610072007400530063007200650065006e003a002400440041005400410000000000000000002c0000003f0000000000000040000000000000003a005a006f006e0065002e004900640065006e007400690066006900650072003a0024004400410054004100"
608 }
609
610 test_binrw! {
611 struct FileIdInformation {
612 volume_serial_number: 0xc86ef7996ef77f0e,
613 file_id: 0x0000000000000000006a00000000cd5a,
614 } => "0e7ff76e99f76ec85acd000000006a000000000000000000"
615 }
616
617 test_binrw! {
618 struct FilePipeLocalInformation {
619 named_pipe_type: NamedPipeType::Message,
620 named_pipe_configuration: NamedPipeConfiguration::FullDuplex,
621 maximum_instances: 0xffffffff,
622 current_instances: 4,
623 inbound_quota: 2048,
624 read_data_available: 0,
625 outbound_quota: 2048,
626 write_quota_available: 1024,
627 named_pipe_state: NamedPipeState::Connected,
628 named_pipe_end: NamedPipeEnd::Client,
629 } => "0100000002000000ffffffff04000000000800000000000000080000000400000300000000000000"
630 }
631
632 }