smb_fscc/
directory_info.rs

1//! File Information Classes for directory enumeration.
2//!
3//! This module mostly exports the [`QueryDirectoryInfo`] enum, which contains all directory information types,
4//! and all the structs for each information type.
5//!
6//! [MS-FSCC 2.4](<https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/4718fc40-e539-4014-8e33-b675af74e3e1>)
7
8use binrw::prelude::*;
9
10use crate::info_classes::file_info_classes;
11use smb_dtyp::binrw_util::{fixed_string::FixedWideString, prelude::*};
12
13use super::{FileAttributes, ReparseTag};
14
15// Note: here, the information types should be wrapped around [`ChainedItemList<T>`][crate::ChainedItemList]`.
16
17file_info_classes! {
18    /// Query (list) directory information classes.
19    pub QueryDirectoryInfo {
20        pub Directory = 0x01,
21        pub FullDirectory = 0x02,
22        pub IdFullDirectory = 0x26,
23        pub BothDirectory = 0x03,
24        pub IdBothDirectory = 0x25,
25        pub Names = 0x0c,
26        pub IdExtdDirectory = 0x3c,
27
28        pub Id64ExtdDirectory = 0x4e,
29        pub Id64ExtdBothDirectory = 0x4f,
30        pub IdAllExtdDirectory = 0x50,
31        pub IdAllExtdBothDirectory = 0x51,
32    }, Read
33}
34
35impl QueryDirectoryInfo {
36    /// All directory information structures must be aligned to 8-byte boundaries.
37    pub const CHAINED_ALIGNMENT: u32 = 8;
38}
39
40/// Since most of the directory information types are very similar (or at least share a lot of fields in their beginning),
41/// we use this macro to reduce code duplication when defining them.
42macro_rules! query_dir_type {
43    (
44    $(#[$meta:meta])*
45        $svis:vis struct $name:ident {
46            $(
47                $(#[$field_meta:meta])*
48                $vis:vis $field_name:ident : $field_ty:ty,
49            )*
50        }; $($actual_field:ident)*
51    ) => {
52        pastey::paste! {
53            #[binrw::binrw]
54            #[derive(Debug, PartialEq, Eq)]
55            $(#[$meta])*
56            ///
57            /// > Note: This should be wrapped in [`ChainedItemList<T>`][crate::ChainedItemList] to represent a list of these structures.
58            $svis struct $name {
59                /// The byte offset of the file within the parent directory. This member is undefined for file systems, such as NTFS, in which the position of a file within the parent directory is not fixed and can be changed at any time to maintain sort order.
60                pub file_index: u32,
61                /// The time when the file was created.
62                pub creation_time: FileTime,
63                /// The time when the file was last accessed.
64                pub last_access_time: FileTime,
65                /// The time when data was last written to the file.
66                pub last_write_time: FileTime,
67                /// The time when the file was last changed.
68                pub change_time: FileTime,
69                /// The absolute new end-of-file position as a byte offset from the start of the file.
70                pub end_of_file: u64,
71                /// The number of bytes allocated for the file.
72                pub allocation_size: u64,
73                /// The file attributes.
74                pub file_attributes: FileAttributes,
75                #[bw(try_calc = file_name.size().try_into())]
76                _file_name_length: u32, // bytes
77
78                #[br(if(!file_attributes.reparse_point()))]
79                // ea_size and reparse_tag are the same field, parsed differently, based on attributes.
80                #[bw(assert(reparse_tag.is_some() != ea_size.is_some()))]
81                /// The size of the extended attributes for the file.
82                pub ea_size: Option<u32>,
83                #[br(if(file_attributes.reparse_point()))]
84                // Must set file_attributes.reparse_point() to true for this to be some.
85                #[bw(assert(reparse_tag.is_some() == file_attributes.reparse_point()))]
86                /// The reparse point tag. If the file is not a reparse point, this value is 0.
87                pub reparse_tag: Option<ReparseTag>,
88
89                $(
90                    $(#[$field_meta])*
91                    $vis $field_name: $field_ty,
92                )*
93
94                /// The name of the file.
95                 #[br(args { size: SizedStringSize::bytes(_file_name_length)})]
96                pub file_name: SizedWideString,
97            }
98
99            impl $name {
100                #[cfg(test)]
101                /// This is a test helper function to quickly initialize common fields for test cases.
102                #[allow(dead_code)]
103                fn make_common_test_dir(file_index: u32, created: time::PrimitiveDateTime, access_time: time::PrimitiveDateTime, write_time: time::PrimitiveDateTime,
104                    change_time: time::PrimitiveDateTime, file_name: &str) -> Self {
105                    Self {
106                        file_index,
107                        creation_time: created.into(),
108                        last_access_time: access_time.into(),
109                        last_write_time: write_time.into(),
110                        change_time: change_time.into(),
111                        end_of_file: 0,
112                        allocation_size: 0,
113                        file_attributes: FileAttributes::new().with_directory(true),
114                        ea_size: Some(0),
115                        reparse_tag: None,
116                        $(
117                            $actual_field: Default::default(),
118                        )*
119                        file_name: SizedWideString::from(file_name),
120                    }
121                }
122            }
123        }
124    };
125}
126
127/// Fixed-size wide string for 8.3 filenames.
128type FileName83 = FixedWideString<12>; // 8.3 => 8+1+3 = 12
129
130/// Query detailed information for the files in a directory.
131///
132/// [MS-FSCC 2.4.10](<https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/b38bf518-9057-4c88-9ddd-5e2d3976a64b>)
133///
134/// This should be wrapped in [`ChainedItemList<T>`][crate::ChainedItemList] to represent a list of these structures.
135#[binrw::binrw]
136#[derive(Debug, PartialEq, Eq)]
137pub struct FileDirectoryInformation {
138    /// The byte offset of the file within the parent directory. This member is undefined for file systems, such as NTFS, in which the position of a file within the parent directory is not fixed and can be changed at any time to maintain sort order.
139    pub file_index: u32,
140    /// The time when the file was created.
141    pub creation_time: FileTime,
142    /// The time when the file was last accessed.
143    pub last_access_time: FileTime,
144    /// The time when data was last written to the file.
145    pub last_write_time: FileTime,
146    /// The time when the file was last changed.
147    pub change_time: FileTime,
148    /// The absolute new end-of-file position as a byte offset from the start of the file.
149    pub end_of_file: u64,
150    /// The number of bytes allocated for the file.
151    pub allocation_size: u64,
152    /// The file attributes.
153    pub file_attributes: FileAttributes,
154    #[bw(try_calc = file_name.size().try_into())]
155    _file_name_length: u32,
156    /// The name of the file.
157    #[br(args { size: SizedStringSize::bytes(_file_name_length)})]
158    pub file_name: SizedWideString,
159}
160
161query_dir_type! {
162    /// Query detailed information for the files in a directory.
163    ///
164    /// [MS-FSCC 2.4.17](<https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/46021e52-29b1-475c-b6d3-fe5497d23277>)
165    pub struct FileFullDirectoryInformation {};
166}
167
168query_dir_type! {
169    /// Query detailed information for the files in a directory.
170    ///
171    /// [MS-FSCC 2.4.18](<https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/b3a27a50-454f-4f8f-b8ea-decfedc5c454>)
172    pub struct FileId64ExtdBothDirectoryInformation {
173        /// The reparse point tag. If the file is not a reparse point, this value is 0.
174        pub reparse_point_tag: u32,
175        /// The file ID.
176        pub file_id: u64,
177        /// The length, in bytes, of the short name string.
178        pub short_name_length: u8,
179        #[bw(calc = 0)]
180        _reserved1: u8,
181        /// The short (8.3) name of the file.
182        pub short_name: FileName83, // 8.3
183    }; reparse_point_tag file_id short_name_length short_name
184}
185
186query_dir_type! {
187    /// Query detailed information for the files in a directory.
188    ///
189    /// [MS-FSCC 2.4.19](<https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/174921dd-9be2-42ed-8220-58c310b1b916>)
190    pub struct FileId64ExtdDirectoryInformation {
191        /// The reparse point tag. If the file is not a reparse point, this value is 0.
192        pub reparse_point_tag: u32,
193        /// The file ID.
194        pub file_id: u64,
195    }; reparse_point_tag file_id
196}
197
198query_dir_type! {
199    /// Query detailed information for the files in a directory.
200    ///
201    /// [MS-FSCC 2.4.20](<https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/1dbb9619-873e-4834-af01-849dcce87d7d>)
202    pub struct FileIdAllExtdBothDirectoryInformation {
203        /// The reparse point tag. If the file is not a reparse point, this value is 0.
204        pub reparse_point_tag: u32,
205        /// The file ID.
206        pub file_id: u64,
207        /// The 128-bit file identifier for the file.
208        pub file_id_128: u128,
209        /// The length, in bytes, of the short name string.
210        pub short_name_length: u8,
211        #[bw(calc = 0)]
212        _reserved1: u8,
213        /// The short (8.3) name of the file.
214        pub short_name: FileName83, // 8.3
215    }; reparse_point_tag file_id file_id_128 short_name_length short_name
216}
217
218query_dir_type! {
219    /// Query detailed information for the files in a directory.
220    ///
221    /// [MS-FSCC 2.4.21](<https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/02991a71-6610-4127-93ef-76b8ea80fef6>)
222    pub struct FileIdAllExtdDirectoryInformation {
223        /// The reparse point tag. If the file is not a reparse point, this value is 0.
224        pub reparse_point_tag: u32,
225        /// The file ID.
226        pub file_id: u64,
227        /// The 128-bit file identifier for the file.
228        pub file_id_128: u128,
229    }; reparse_point_tag file_id file_id_128
230}
231
232query_dir_type! {
233    /// Query detailed information for the files in a directory.
234    ///
235    /// [MS-FSCC 2.4.22](<https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/1e144bff-c056-45aa-bd29-c13d214ee2ba>)
236    pub struct FileIdBothDirectoryInformation {
237        /// The length, in bytes, of the short name string.
238        pub short_name_length: u8,
239        #[bw(calc = 0)]
240        _reserved1: u8,
241        /// The short (8.3) name of the file.
242        pub short_name: FileName83, // 8.3
243        #[bw(calc = 0)]
244        _reserved2: u16,
245        /// The file ID.
246        pub file_id: u64,
247    }; short_name_length short_name file_id
248}
249
250query_dir_type! {
251    /// Query detailed information for the files in a directory.
252    ///
253    /// [MS-FSCC 2.4.23](<https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/36172f0b-8dce-435a-8748-859978d632f8>)
254    pub struct FileIdExtdDirectoryInformation {
255        /// The reparse point tag. If the file is not a reparse point, this value is 0.
256        pub reparse_point_tag: u32,
257        /// The 128-bit file identifier for the file.
258        pub file_id: u128,
259    }; reparse_point_tag file_id
260}
261
262query_dir_type! {
263    /// Query detailed information for the files in a directory.
264    ///
265    /// [MS-FSCC 2.4.24](<https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/ab8e7558-899c-4be1-a7c5-3a9ae8ab76a0>)
266    pub struct FileIdFullDirectoryInformation {
267        #[bw(calc = 0)]
268        _reserved: u32,
269        /// The file ID.
270        pub file_id: u64,
271    }; file_id
272}
273
274/// Query the names of the files in a directory.
275///
276/// [MS-FSCC 2.4.33](<https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/a289f7a8-83d2-4927-8c88-b2d328dde5a5>)
277///
278/// This should be wrapped in [`ChainedItemList<T>`][crate::ChainedItemList] to represent a list of these structures.
279#[binrw::binrw]
280#[derive(Debug, PartialEq, Eq)]
281pub struct FileNamesInformation {
282    /// The byte offset of the file within the parent directory. This member is undefined for file systems, such as NTFS, in which the position of a file within the parent directory is not fixed and can be changed at any time to maintain sort order.
283    pub file_index: u32,
284    #[bw(try_calc = file_name.size().try_into())]
285    pub file_name_length: u32,
286    /// The name of the file.
287    #[br(args { size: SizedStringSize::bytes(file_name_length) })]
288    pub file_name: SizedWideString,
289}
290
291query_dir_type! {
292    /// Query detailed information for the files in a directory.
293    ///
294    /// [MS-FSCC 2.4.8](<https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/270df317-9ba5-4ccb-ba00-8d22be139bc5>)
295    pub struct FileBothDirectoryInformation {
296        /// The length, in bytes, of the short name string.
297        pub short_name_length: u8,
298        #[bw(calc = 0)]
299        _reserved1: u8,
300        /// The short (8.3) name of the file.
301        pub short_name: FileName83, // 8.3
302    }; short_name_length short_name
303}
304
305#[cfg(test)]
306mod tests {
307    use super::*;
308    use crate::ChainedItemList;
309    use smb_tests::test_binrw;
310    use time::macros::datetime;
311
312    macro_rules! make_id_all_extd_both_directory {
313        ($file_index:expr, $created:expr, $access_write_time:expr, $change_time:expr, $file_name:expr, $file_id:literal) => {{
314            let mut result = FileIdAllExtdBothDirectoryInformation::make_common_test_dir(
315                $file_index,
316                $created,
317                $access_write_time,
318                $access_write_time,
319                $change_time,
320                $file_name,
321            );
322
323            result.file_id = $file_id;
324            result.file_id_128 = $file_id;
325
326            result
327        }};
328        ($file_index:expr, $created:expr, $access_time:expr, $write_time:expr, $change_time:expr, $file_name:expr, $file_id:literal, $size:literal, $alloc_size:literal, $ea_size:literal) => {{
329            let mut result = make_id_all_extd_both_directory!(
330                $file_index,
331                $created,
332                $access_time,
333                $change_time,
334                $file_name,
335                $file_id
336            );
337            result.last_access_time = $access_time.into();
338            result.last_write_time = $write_time.into();
339            result.end_of_file = $size;
340            result.allocation_size = $alloc_size;
341            result.ea_size = Some($ea_size);
342            result.file_attributes = FileAttributes::new().with_archive(true);
343            result
344        }};
345    }
346
347    macro_rules! make_id_all_extd_directory {
348        ($file_index:expr, $created:expr, $access_write_time:expr, $change_time:expr, $file_name:expr, $file_id:literal) => {{
349            let mut result = FileIdAllExtdDirectoryInformation::make_common_test_dir(
350                $file_index,
351                $created,
352                $access_write_time,
353                $access_write_time,
354                $change_time,
355                $file_name,
356            );
357
358            result.file_id = $file_id;
359            result.file_id_128 = $file_id;
360
361            result
362        }};
363        ($file_index:expr, $created:expr, $access_time:expr, $write_time:expr, $change_time:expr, $file_name:expr, $file_id:literal, $size:literal, $alloc_size:literal, $ea_size:literal) => {{
364            let mut result = make_id_all_extd_directory!(
365                $file_index,
366                $created,
367                $access_time,
368                $change_time,
369                $file_name,
370                $file_id
371            );
372            result.last_access_time = $access_time.into();
373            result.last_write_time = $write_time.into();
374            result.end_of_file = $size;
375            result.allocation_size = $alloc_size;
376            result.ea_size = Some($ea_size);
377            result.file_attributes = FileAttributes::new().with_archive(true);
378            result
379        }};
380    }
381
382    macro_rules! make_id_both_directory {
383        ($file_index:expr, $created:expr, $access_write_time:expr, $change_time:expr, $file_name:expr, $file_id:literal) => {{
384            let mut result = FileIdBothDirectoryInformation::make_common_test_dir(
385                $file_index,
386                $created,
387                $access_write_time,
388                $access_write_time,
389                $change_time,
390                $file_name,
391            );
392
393            result.file_id = $file_id;
394
395            result
396        }};
397        ($file_index:expr, $created:expr, $access_time:expr, $write_time:expr, $change_time:expr, $file_name:expr, $file_id:literal, $size:literal, $alloc_size:literal, $ea_size:literal) => {{
398            let mut result = make_id_both_directory!(
399                $file_index,
400                $created,
401                $access_time,
402                $change_time,
403                $file_name,
404                $file_id
405            );
406            result.last_access_time = $access_time.into();
407            result.last_write_time = $write_time.into();
408            result.end_of_file = $size;
409            result.allocation_size = $alloc_size;
410            result.ea_size = Some($ea_size);
411            result.file_attributes = FileAttributes::new().with_archive(true);
412            result
413        }};
414    }
415
416    macro_rules! make_names {
417        ($file_index:expr, $created:expr, $access_write_time:expr, $change_time:expr, $file_name:expr, $file_id:literal) => {{
418            FileNamesInformation {
419                file_index: $file_index,
420                file_name: SizedWideString::from($file_name),
421            }
422        }};
423        ($file_index:expr, $created:expr, $access_time:expr, $write_time:expr, $change_time:expr, $file_name:expr, $file_id:literal, $size:literal, $alloc_size:literal, $ea_size:literal) => {{
424            make_names!(
425                $file_index,
426                $created,
427                $access_time,
428                $change_time,
429                $file_name,
430                $file_id
431            )
432        }};
433    }
434
435    macro_rules! make_directory {
436        ($file_index:expr, $created:expr, $access_write_time:expr, $change_time:expr, $file_name:expr, $file_id:literal) => {{
437            FileDirectoryInformation {
438                file_index: $file_index,
439                creation_time: $created.into(),
440                last_access_time: $access_write_time.into(),
441                last_write_time: $access_write_time.into(),
442                change_time: $change_time.into(),
443                end_of_file: 0,
444                allocation_size: 0,
445                file_attributes: FileAttributes::new().with_directory(true),
446                file_name: SizedWideString::from($file_name),
447            }
448        }};
449        ($file_index:expr, $created:expr, $access_time:expr, $write_time:expr, $change_time:expr, $file_name:expr, $file_id:literal, $size:literal, $alloc_size:literal, $ea_size:literal) => {{
450            let mut result = make_directory!(
451                $file_index,
452                $created,
453                $access_time,
454                $change_time,
455                $file_name,
456                $file_id
457            );
458            result.last_access_time = $access_time.into();
459            result.last_write_time = $write_time.into();
460            result.end_of_file = $size;
461            result.allocation_size = $alloc_size;
462            result.file_attributes = FileAttributes::new().with_archive(true);
463            result
464        }};
465    }
466
467    macro_rules! make_id64_extd_both_directory {
468        ($file_index:expr, $created:expr, $access_write_time:expr, $change_time:expr, $file_name:expr, $file_id:literal) => {{
469            let mut result = FileId64ExtdBothDirectoryInformation::make_common_test_dir(
470                $file_index,
471                $created,
472                $access_write_time,
473                $access_write_time,
474                $change_time,
475                $file_name,
476            );
477
478            result.file_id = $file_id;
479
480            result
481        }};
482        ($file_index:expr, $created:expr, $access_time:expr, $write_time:expr, $change_time:expr, $file_name:expr, $file_id:literal, $size:literal, $alloc_size:literal, $ea_size:literal) => {{
483            let mut result = make_id64_extd_both_directory!(
484                $file_index,
485                $created,
486                $access_time,
487                $change_time,
488                $file_name,
489                $file_id
490            );
491            result.last_access_time = $access_time.into();
492            result.last_write_time = $write_time.into();
493            result.end_of_file = $size;
494            result.allocation_size = $alloc_size;
495            result.ea_size = Some($ea_size);
496            result.file_attributes = FileAttributes::new().with_archive(true);
497            result
498        }};
499    }
500
501    macro_rules! make_id_extd_directory {
502        ($file_index:expr, $created:expr, $access_write_time:expr, $change_time:expr, $file_name:expr, $file_id:literal) => {{
503            let mut result = FileIdExtdDirectoryInformation::make_common_test_dir(
504                $file_index,
505                $created,
506                $access_write_time,
507                $access_write_time,
508                $change_time,
509                $file_name,
510            );
511
512            result.file_id = $file_id;
513
514            result
515        }};
516        ($file_index:expr, $created:expr, $access_time:expr, $write_time:expr, $change_time:expr, $file_name:expr, $file_id:literal, $size:literal, $alloc_size:literal, $ea_size:literal) => {{
517            let mut result = make_id_extd_directory!(
518                $file_index,
519                $created,
520                $access_time,
521                $change_time,
522                $file_name,
523                $file_id
524            );
525            result.last_access_time = $access_time.into();
526            result.last_write_time = $write_time.into();
527            result.end_of_file = $size;
528            result.allocation_size = $alloc_size;
529            result.ea_size = Some($ea_size);
530            result.file_attributes = FileAttributes::new().with_archive(true);
531            result
532        }};
533    }
534
535    macro_rules! make_id64_extd_directory {
536        ($file_index:expr, $created:expr, $access_write_time:expr, $change_time:expr, $file_name:expr, $file_id:literal) => {{
537            let mut result = FileId64ExtdDirectoryInformation::make_common_test_dir(
538                $file_index,
539                $created,
540                $access_write_time,
541                $access_write_time,
542                $change_time,
543                $file_name,
544            );
545
546            result.file_id = $file_id;
547
548            result
549        }};
550        ($file_index:expr, $created:expr, $access_time:expr, $write_time:expr, $change_time:expr, $file_name:expr, $file_id:literal, $size:literal, $alloc_size:literal, $ea_size:literal) => {{
551            let mut result = make_id64_extd_directory!(
552                $file_index,
553                $created,
554                $access_time,
555                $change_time,
556                $file_name,
557                $file_id
558            );
559            result.last_access_time = $access_time.into();
560            result.last_write_time = $write_time.into();
561            result.end_of_file = $size;
562            result.allocation_size = $alloc_size;
563            result.ea_size = Some($ea_size);
564            result.file_attributes = FileAttributes::new().with_archive(true);
565            result
566        }};
567    }
568
569    macro_rules! make_id_full_directory {
570        ($file_index:expr, $created:expr, $access_write_time:expr, $change_time:expr, $file_name:expr, $file_id:literal) => {{
571            let mut result = FileIdFullDirectoryInformation::make_common_test_dir(
572                $file_index,
573                $created,
574                $access_write_time,
575                $access_write_time,
576                $change_time,
577                $file_name,
578            );
579
580            result.file_id = $file_id;
581
582            result
583        }};
584        ($file_index:expr, $created:expr, $access_time:expr, $write_time:expr, $change_time:expr, $file_name:expr, $file_id:literal, $size:literal, $alloc_size:literal, $ea_size:literal) => {{
585            let mut result = make_id_full_directory!(
586                $file_index,
587                $created,
588                $access_time,
589                $change_time,
590                $file_name,
591                $file_id
592            );
593            result.last_access_time = $access_time.into();
594            result.last_write_time = $write_time.into();
595            result.end_of_file = $size;
596            result.allocation_size = $alloc_size;
597            result.ea_size = Some($ea_size);
598            result.file_attributes = FileAttributes::new().with_archive(true);
599            result
600        }};
601    }
602
603    macro_rules! make_full_directory {
604        ($file_index:expr, $created:expr, $access_write_time:expr, $change_time:expr, $file_name:expr, $file_id:literal) => {{
605            FileFullDirectoryInformation::make_common_test_dir(
606                $file_index,
607                $created,
608                $access_write_time,
609                $access_write_time,
610                $change_time,
611                $file_name,
612            )
613        }};
614        ($file_index:expr, $created:expr, $access_time:expr, $write_time:expr, $change_time:expr, $file_name:expr, $file_id:literal, $size:literal, $alloc_size:literal, $ea_size:literal) => {{
615            let mut result = make_full_directory!(
616                $file_index,
617                $created,
618                $access_time,
619                $change_time,
620                $file_name,
621                $file_id
622            );
623            result.last_access_time = $access_time.into();
624            result.last_write_time = $write_time.into();
625            result.end_of_file = $size;
626            result.allocation_size = $alloc_size;
627            result.ea_size = Some($ea_size);
628            result.file_attributes = FileAttributes::new().with_archive(true);
629            result
630        }};
631    }
632
633    macro_rules! make_both_directory {
634        ($file_index:expr, $created:expr, $access_write_time:expr, $change_time:expr, $file_name:expr, $file_id:literal) => {{
635            FileBothDirectoryInformation::make_common_test_dir(
636                $file_index,
637                $created,
638                $access_write_time,
639                $access_write_time,
640                $change_time,
641                $file_name,
642            )
643        }};
644        ($file_index:expr, $created:expr, $access_time:expr, $write_time:expr, $change_time:expr, $file_name:expr, $file_id:literal, $size:literal, $alloc_size:literal, $ea_size:literal) => {{
645            let mut result = make_both_directory!(
646                $file_index,
647                $created,
648                $access_time,
649                $change_time,
650                $file_name,
651                $file_id
652            );
653            result.last_access_time = $access_time.into();
654            result.last_write_time = $write_time.into();
655            result.end_of_file = $size;
656            result.allocation_size = $alloc_size;
657            result.ea_size = Some($ea_size);
658            result.file_attributes = FileAttributes::new().with_archive(true);
659            result
660        }};
661    }
662
663    // Some might think I'm more of a POSIX guy, since I use macOS,
664    // but tbh, I actually love windows, especially legacy edge DLLs,
665    // which are the content of this test directory listing dump!
666    // anyway, anybody who reads this code - I'm sorry.
667
668    /// Creates `test_binrw!` for directory information structures.
669    ///
670    /// Calls the appropriate `make_` macro to create test data.
671    /// The binary data matching each test must be taken on the same directory,
672    /// at the same time, so the hard-coded test data matches all the binary dumps.
673    macro_rules! make_dir_test {
674        ($struct_name:ident: $data:literal) => {
675
676    pastey::paste! {
677        type [<$struct_name TestList>] = ChainedItemList<
678            [<File $struct_name Information>],
679            { QueryDirectoryInfo::CHAINED_ALIGNMENT },
680        >;
681        test_binrw! {
682            [<$struct_name TestList>]: [<$struct_name TestList>]::from(vec![
683                [<make_ $struct_name:snake>]!(0, datetime!(2025-06-19 10:22:45.5282237), datetime!(2025-06-19 10:23:34.0915427), datetime!(2025-06-19 10:23:34.3246503), ".", 2814749767159075),
684                [<make_ $struct_name:snake>]!(0, datetime!(2025-04-04 22:18:11.7121314), datetime!(2025-10-13 17:58:05.9388514), datetime!(2025-10-13 17:58:05.9388514), "..", 1970324836975477),
685                [<make_ $struct_name:snake>]!(0, datetime!(2025-06-19 10:22:45.6273816), datetime!(2025-06-19 10:22:50.4411921), datetime!(2025-04-04 23:07:27.4722084), datetime!(2025-06-19 10:22:50.4411921),"BingMaps.dll",0x6900000000cd5a,16_757_760,16760832,128),
686                [<make_ $struct_name:snake>]!(0, datetime!(2025-06-19 10:22:50.8778222), datetime!(2025-06-19 10:22:54.6758575), datetime!(2025-04-13 23:00:30.4054831), datetime!(2025-10-17 16:01:03.3860342), "edgehtml.dll", 0x3300000000cd68, 51_103_232, 51105792, 120 ),
687                [<make_ $struct_name:snake>]!(0, datetime!(2025-06-19 10:23:09.8691232), datetime!(2025-06-19 10:23:14.1817596), datetime!(2025-04-13 23:00:31.9102213), datetime!(2025-06-19 10:23:14.1817596), "mshtml.dll", 0x1000000000ce21, 42_358_272, 42360832, 120),
688            ]) => $data
689        }
690    }
691
692        };
693        ($($struct_name:ident: $data:literal),+) => {
694            $(
695                make_dir_test!($struct_name: $data);
696            )+
697        };
698    }
699
700    make_dir_test!(
701        IdAllExtdBothDirectory: "80000000000000003d22211904e1db01e34e133604e1db01e34e133604e1db01a7e0363604e1db01000000000000000000000000000000001000000002000000000000000000000023cd000000000a0023cd000000000a00000000000000000000000000000000000000000000000000000000000000000000002e0000000000800000000000000022fdbb73afa5db0162f647ed6a3cdc0162f647ed6a3cdc0162f647ed6a3cdc01000000000000000000000000000000001000000004000000000000000000000075030000000007007503000000000700000000000000000000000000000000000000000000000000000000000000000000002e002e00000098000000000000009843301904e1db0111cb0e1c04e1db01242f8155b6a5db0111cb0e1c04e1db0100b4ff000000000000c0ff0000000000200000001800000080000000000000005acd0000000069005acd00000000690000000000000000000000000000000000000000000000000000000000000000000000420069006e0067004d006100700073002e0064006c006c000000000000009800000000000000ee6a511c04e1db01aff3941e04e1db012f9aa1dac7acdb01f6702a3d7f3fdc0100c60b030000000000d00b03000000002000000018000000780000000000000068cd00000000330068cd000000003300000000000000000000000000000000000000000000000000000000000000000000006500640067006500680074006d006c002e0064006c006c000000000000000000000000000000a042a32704e1db01fc50352a04e1db01053587dbc7acdb01fc50352a04e1db01005686020000000000608602000000002000000014000000780000000000000021ce00000000100021ce000000001000000000000000000000000000000000000000000000000000000000000000000000006d007300680074006d006c002e0064006c006c00",
702        IdBothDirectory: "70000000000000003d22211904e1db01e34e133604e1db01e34e133604e1db01a7e0363604e1db01000000000000000000000000000000001000000002000000000000000000000000000000000000000000000000000000000000000000000023cd000000000a002e00000000000000700000000000000022fdbb73afa5db0162f647ed6a3cdc0162f647ed6a3cdc0162f647ed6a3cdc01000000000000000000000000000000001000000004000000000000000000000000000000000000000000000000000000000000000000000075030000000007002e002e000000000080000000000000009843301904e1db0111cb0e1c04e1db01242f8155b6a5db0111cb0e1c04e1db0100b4ff000000000000c0ff0000000000200000001800000080000000000000000000000000000000000000000000000000000000000000005acd000000006900420069006e0067004d006100700073002e0064006c006c008000000000000000ee6a511c04e1db01aff3941e04e1db012f9aa1dac7acdb01f6702a3d7f3fdc0100c60b030000000000d00b03000000002000000018000000780000000000000000000000000000000000000000000000000000000000000068cd0000000033006500640067006500680074006d006c002e0064006c006c000000000000000000a042a32704e1db01fc50352a04e1db01053587dbc7acdb01fc50352a04e1db01005686020000000000608602000000002000000014000000780000000000000000000000000000000000000000000000000000000000000021ce0000000010006d007300680074006d006c002e0064006c006c00",
703        IdAllExtdDirectory: "68000000000000003d22211904e1db01e34e133604e1db01e34e133604e1db01a7e0363604e1db01000000000000000000000000000000001000000002000000000000000000000023cd000000000a0023cd000000000a0000000000000000002e00000000000000680000000000000022fdbb73afa5db0162f647ed6a3cdc0162f647ed6a3cdc0162f647ed6a3cdc0100000000000000000000000000000000100000000400000000000000000000007503000000000700750300000000070000000000000000002e002e000000000078000000000000009843301904e1db0111cb0e1c04e1db01242f8155b6a5db0111cb0e1c04e1db0100b4ff000000000000c0ff0000000000200000001800000080000000000000005acd0000000069005acd0000000069000000000000000000420069006e0067004d006100700073002e0064006c006c007800000000000000ee6a511c04e1db01aff3941e04e1db012f9aa1dac7acdb01f6702a3d7f3fdc0100c60b030000000000d00b03000000002000000018000000780000000000000068cd00000000330068cd00000000330000000000000000006500640067006500680074006d006c002e0064006c006c000000000000000000a042a32704e1db01fc50352a04e1db01053587dbc7acdb01fc50352a04e1db01005686020000000000608602000000002000000014000000780000000000000021ce00000000100021ce00000000100000000000000000006d007300680074006d006c002e0064006c006c00",
704        Names: "1000000000000000020000002e0000001000000000000000040000002e002e00280000000000000018000000420069006e0067004d006100700073002e0064006c006c00000000002800000000000000180000006500640067006500680074006d006c002e0064006c006c00000000000000000000000000140000006d007300680074006d006c002e0064006c006c00",
705        Id64ExtdBothDirectory: "70000000000000003d22211904e1db01e34e133604e1db01e34e133604e1db01a7e0363604e1db01000000000000000000000000000000001000000002000000000000000000000023cd000000000a0000000000000000000000000000000000000000000000000000002e0000000000700000000000000022fdbb73afa5db0162f647ed6a3cdc0162f647ed6a3cdc0162f647ed6a3cdc010000000000000000000000000000000010000000040000000000000000000000750300000000070000000000000000000000000000000000000000000000000000002e002e00000088000000000000009843301904e1db0111cb0e1c04e1db01242f8155b6a5db0111cb0e1c04e1db0100b4ff000000000000c0ff0000000000200000001800000080000000000000005acd0000000069000000000000000000000000000000000000000000000000000000420069006e0067004d006100700073002e0064006c006c000000000000008800000000000000ee6a511c04e1db01aff3941e04e1db012f9aa1dac7acdb01f6702a3d7f3fdc0100c60b030000000000d00b03000000002000000018000000780000000000000068cd00000000330000000000000000000000000000000000000000000000000000006500640067006500680074006d006c002e0064006c006c000000000000000000000000000000a042a32704e1db01fc50352a04e1db01053587dbc7acdb01fc50352a04e1db01005686020000000000608602000000002000000014000000780000000000000021ce00000000100000000000000000000000000000000000000000000000000000006d007300680074006d006c002e0064006c006c00",
706        Id64ExtdDirectory: "58000000000000003d22211904e1db01e34e133604e1db01e34e133604e1db01a7e0363604e1db01000000000000000000000000000000001000000002000000000000000000000023cd000000000a002e00000000000000580000000000000022fdbb73afa5db0162f647ed6a3cdc0162f647ed6a3cdc0162f647ed6a3cdc01000000000000000000000000000000001000000004000000000000000000000075030000000007002e002e000000000068000000000000009843301904e1db0111cb0e1c04e1db01242f8155b6a5db0111cb0e1c04e1db0100b4ff000000000000c0ff0000000000200000001800000080000000000000005acd000000006900420069006e0067004d006100700073002e0064006c006c006800000000000000ee6a511c04e1db01aff3941e04e1db012f9aa1dac7acdb01f6702a3d7f3fdc0100c60b030000000000d00b03000000002000000018000000780000000000000068cd0000000033006500640067006500680074006d006c002e0064006c006c000000000000000000a042a32704e1db01fc50352a04e1db01053587dbc7acdb01fc50352a04e1db01005686020000000000608602000000002000000014000000780000000000000021ce0000000010006d007300680074006d006c002e0064006c006c00",
707        IdExtdDirectory: "60000000000000003d22211904e1db01e34e133604e1db01e34e133604e1db01a7e0363604e1db01000000000000000000000000000000001000000002000000000000000000000023cd000000000a0000000000000000002e00000000000000600000000000000022fdbb73afa5db0162f647ed6a3cdc0162f647ed6a3cdc0162f647ed6a3cdc010000000000000000000000000000000010000000040000000000000000000000750300000000070000000000000000002e002e000000000070000000000000009843301904e1db0111cb0e1c04e1db01242f8155b6a5db0111cb0e1c04e1db0100b4ff000000000000c0ff0000000000200000001800000080000000000000005acd0000000069000000000000000000420069006e0067004d006100700073002e0064006c006c007000000000000000ee6a511c04e1db01aff3941e04e1db012f9aa1dac7acdb01f6702a3d7f3fdc0100c60b030000000000d00b03000000002000000018000000780000000000000068cd00000000330000000000000000006500640067006500680074006d006c002e0064006c006c000000000000000000a042a32704e1db01fc50352a04e1db01053587dbc7acdb01fc50352a04e1db01005686020000000000608602000000002000000014000000780000000000000021ce00000000100000000000000000006d007300680074006d006c002e0064006c006c00",
708        BothDirectory: "60000000000000003d22211904e1db01e34e133604e1db01e34e133604e1db01a7e0363604e1db010000000000000000000000000000000010000000020000000000000000000000000000000000000000000000000000000000000000002e00680000000000000022fdbb73afa5db0162f647ed6a3cdc0162f647ed6a3cdc0162f647ed6a3cdc010000000000000000000000000000000010000000040000000000000000000000000000000000000000000000000000000000000000002e002e0000000000000078000000000000009843301904e1db0111cb0e1c04e1db01242f8155b6a5db0111cb0e1c04e1db0100b4ff000000000000c0ff00000000002000000018000000800000000000000000000000000000000000000000000000000000000000420069006e0067004d006100700073002e0064006c006c0000007800000000000000ee6a511c04e1db01aff3941e04e1db012f9aa1dac7acdb01f6702a3d7f3fdc0100c60b030000000000d00b030000000020000000180000007800000000000000000000000000000000000000000000000000000000006500640067006500680074006d006c002e0064006c006c0000000000000000000000a042a32704e1db01fc50352a04e1db01053587dbc7acdb01fc50352a04e1db010056860200000000006086020000000020000000140000007800000000000000000000000000000000000000000000000000000000006d007300680074006d006c002e0064006c006c00",
709        IdFullDirectory: "58000000000000003d22211904e1db01e34e133604e1db01e34e133604e1db01a7e0363604e1db01000000000000000000000000000000001000000002000000000000000000000023cd000000000a002e00000000000000580000000000000022fdbb73afa5db0162f647ed6a3cdc0162f647ed6a3cdc0162f647ed6a3cdc01000000000000000000000000000000001000000004000000000000000000000075030000000007002e002e000000000068000000000000009843301904e1db0111cb0e1c04e1db01242f8155b6a5db0111cb0e1c04e1db0100b4ff000000000000c0ff0000000000200000001800000080000000000000005acd000000006900420069006e0067004d006100700073002e0064006c006c006800000000000000ee6a511c04e1db01aff3941e04e1db012f9aa1dac7acdb01f6702a3d7f3fdc0100c60b030000000000d00b03000000002000000018000000780000000000000068cd0000000033006500640067006500680074006d006c002e0064006c006c000000000000000000a042a32704e1db01fc50352a04e1db01053587dbc7acdb01fc50352a04e1db01005686020000000000608602000000002000000014000000780000000000000021ce0000000010006d007300680074006d006c002e0064006c006c00",
710        FullDirectory: "48000000000000003d22211904e1db01e34e133604e1db01e34e133604e1db01a7e0363604e1db01000000000000000000000000000000001000000002000000000000002e000000480000000000000022fdbb73afa5db0162f647ed6a3cdc0162f647ed6a3cdc0162f647ed6a3cdc01000000000000000000000000000000001000000004000000000000002e002e0060000000000000009843301904e1db0111cb0e1c04e1db01242f8155b6a5db0111cb0e1c04e1db0100b4ff000000000000c0ff0000000000200000001800000080000000420069006e0067004d006100700073002e0064006c006c00000000006000000000000000ee6a511c04e1db01aff3941e04e1db012f9aa1dac7acdb01f6702a3d7f3fdc0100c60b030000000000d00b03000000002000000018000000780000006500640067006500680074006d006c002e0064006c006c00000000000000000000000000a042a32704e1db01fc50352a04e1db01053587dbc7acdb01fc50352a04e1db01005686020000000000608602000000002000000014000000780000006d007300680074006d006c002e0064006c006c00",
711        Directory: "48000000000000003d22211904e1db01e34e133604e1db01e34e133604e1db01a7e0363604e1db010000000000000000000000000000000010000000020000002e00000000000000480000000000000022fdbb73afa5db0162f647ed6a3cdc0162f647ed6a3cdc0162f647ed6a3cdc010000000000000000000000000000000010000000040000002e002e000000000058000000000000009843301904e1db0111cb0e1c04e1db01242f8155b6a5db0111cb0e1c04e1db0100b4ff000000000000c0ff00000000002000000018000000420069006e0067004d006100700073002e0064006c006c005800000000000000ee6a511c04e1db01aff3941e04e1db012f9aa1dac7acdb01f6702a3d7f3fdc0100c60b030000000000d00b030000000020000000180000006500640067006500680074006d006c002e0064006c006c000000000000000000a042a32704e1db01fc50352a04e1db01053587dbc7acdb01fc50352a04e1db010056860200000000006086020000000020000000140000006d007300680074006d006c002e0064006c006c00"
712    );
713}