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    }
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        #[br(temp)]
181        _reserved1: u8,
182        /// The short (8.3) name of the file.
183        pub short_name: FileName83, // 8.3
184    }; reparse_point_tag file_id short_name_length short_name
185}
186
187query_dir_type! {
188    /// Query detailed information for the files in a directory.
189    ///
190    /// [MS-FSCC 2.4.19](<https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/174921dd-9be2-42ed-8220-58c310b1b916>)
191    pub struct FileId64ExtdDirectoryInformation {
192        /// The reparse point tag. If the file is not a reparse point, this value is 0.
193        pub reparse_point_tag: u32,
194        /// The file ID.
195        pub file_id: u64,
196    }; reparse_point_tag file_id
197}
198
199query_dir_type! {
200    /// Query detailed information for the files in a directory.
201    ///
202    /// [MS-FSCC 2.4.20](<https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/1dbb9619-873e-4834-af01-849dcce87d7d>)
203    pub struct FileIdAllExtdBothDirectoryInformation {
204        /// The reparse point tag. If the file is not a reparse point, this value is 0.
205        pub reparse_point_tag: u32,
206        /// The file ID.
207        pub file_id: u64,
208        /// The 128-bit file identifier for the file.
209        pub file_id_128: u128,
210        /// The length, in bytes, of the short name string.
211        pub short_name_length: u8,
212        #[bw(calc = 0)]
213        #[br(temp)]
214        _reserved1: u8,
215        /// The short (8.3) name of the file.
216        pub short_name: FileName83, // 8.3
217    }; reparse_point_tag file_id file_id_128 short_name_length short_name
218}
219
220query_dir_type! {
221    /// Query detailed information for the files in a directory.
222    ///
223    /// [MS-FSCC 2.4.21](<https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/02991a71-6610-4127-93ef-76b8ea80fef6>)
224    pub struct FileIdAllExtdDirectoryInformation {
225        /// The reparse point tag. If the file is not a reparse point, this value is 0.
226        pub reparse_point_tag: u32,
227        /// The file ID.
228        pub file_id: u64,
229        /// The 128-bit file identifier for the file.
230        pub file_id_128: u128,
231    }; reparse_point_tag file_id file_id_128
232}
233
234query_dir_type! {
235    /// Query detailed information for the files in a directory.
236    ///
237    /// [MS-FSCC 2.4.22](<https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/1e144bff-c056-45aa-bd29-c13d214ee2ba>)
238    pub struct FileIdBothDirectoryInformation {
239        /// The length, in bytes, of the short name string.
240        pub short_name_length: u8,
241        #[bw(calc = 0)]
242        #[br(temp)]
243        _reserved1: u8,
244        /// The short (8.3) name of the file.
245        pub short_name: FileName83, // 8.3
246        #[bw(calc = 0)]
247        #[br(temp)]
248        _reserved2: u16,
249        /// The file ID.
250        pub file_id: u64,
251    }; short_name_length short_name file_id
252}
253
254query_dir_type! {
255    /// Query detailed information for the files in a directory.
256    ///
257    /// [MS-FSCC 2.4.23](<https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/36172f0b-8dce-435a-8748-859978d632f8>)
258    pub struct FileIdExtdDirectoryInformation {
259        /// The reparse point tag. If the file is not a reparse point, this value is 0.
260        pub reparse_point_tag: u32,
261        /// The 128-bit file identifier for the file.
262        pub file_id: u128,
263    }; reparse_point_tag file_id
264}
265
266query_dir_type! {
267    /// Query detailed information for the files in a directory.
268    ///
269    /// [MS-FSCC 2.4.24](<https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/ab8e7558-899c-4be1-a7c5-3a9ae8ab76a0>)
270    pub struct FileIdFullDirectoryInformation {
271        #[bw(calc = 0)]
272        #[br(temp)]
273        _reserved: u32,
274        /// The file ID.
275        pub file_id: u64,
276    }; file_id
277}
278
279/// Query the names of the files in a directory.
280///
281/// [MS-FSCC 2.4.33](<https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/a289f7a8-83d2-4927-8c88-b2d328dde5a5>)
282///
283/// This should be wrapped in [`ChainedItemList<T>`][crate::ChainedItemList] to represent a list of these structures.
284#[binrw::binrw]
285#[derive(Debug, PartialEq, Eq)]
286pub struct FileNamesInformation {
287    /// 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.
288    pub file_index: u32,
289    #[bw(try_calc = file_name.size().try_into())]
290    pub file_name_length: u32,
291    /// The name of the file.
292    #[br(args { size: SizedStringSize::bytes(file_name_length) })]
293    pub file_name: SizedWideString,
294}
295
296query_dir_type! {
297    /// Query detailed information for the files in a directory.
298    ///
299    /// [MS-FSCC 2.4.8](<https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/270df317-9ba5-4ccb-ba00-8d22be139bc5>)
300    pub struct FileBothDirectoryInformation {
301        /// The length, in bytes, of the short name string.
302        pub short_name_length: u8,
303        #[bw(calc = 0)]
304        #[br(temp)]
305        _reserved1: u8,
306        /// The short (8.3) name of the file.
307        pub short_name: FileName83, // 8.3
308    }; short_name_length short_name
309}
310
311#[cfg(test)]
312mod tests {
313    use super::*;
314    use crate::ChainedItemList;
315    use smb_tests::*;
316    use time::macros::datetime;
317
318    macro_rules! make_id_all_extd_both_directory {
319        ($file_index:expr, $created:expr, $access_write_time:expr, $change_time:expr, $file_name:expr, $file_id:literal) => {{
320            let mut result = FileIdAllExtdBothDirectoryInformation::make_common_test_dir(
321                $file_index,
322                $created,
323                $access_write_time,
324                $access_write_time,
325                $change_time,
326                $file_name,
327            );
328
329            result.file_id = $file_id;
330            result.file_id_128 = $file_id;
331
332            result
333        }};
334        ($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) => {{
335            let mut result = make_id_all_extd_both_directory!(
336                $file_index,
337                $created,
338                $access_time,
339                $change_time,
340                $file_name,
341                $file_id
342            );
343            result.last_access_time = $access_time.into();
344            result.last_write_time = $write_time.into();
345            result.end_of_file = $size;
346            result.allocation_size = $alloc_size;
347            result.ea_size = Some($ea_size);
348            result.file_attributes = FileAttributes::new().with_archive(true);
349            result
350        }};
351    }
352
353    macro_rules! make_id_all_extd_directory {
354        ($file_index:expr, $created:expr, $access_write_time:expr, $change_time:expr, $file_name:expr, $file_id:literal) => {{
355            let mut result = FileIdAllExtdDirectoryInformation::make_common_test_dir(
356                $file_index,
357                $created,
358                $access_write_time,
359                $access_write_time,
360                $change_time,
361                $file_name,
362            );
363
364            result.file_id = $file_id;
365            result.file_id_128 = $file_id;
366
367            result
368        }};
369        ($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) => {{
370            let mut result = make_id_all_extd_directory!(
371                $file_index,
372                $created,
373                $access_time,
374                $change_time,
375                $file_name,
376                $file_id
377            );
378            result.last_access_time = $access_time.into();
379            result.last_write_time = $write_time.into();
380            result.end_of_file = $size;
381            result.allocation_size = $alloc_size;
382            result.ea_size = Some($ea_size);
383            result.file_attributes = FileAttributes::new().with_archive(true);
384            result
385        }};
386    }
387
388    macro_rules! make_id_both_directory {
389        ($file_index:expr, $created:expr, $access_write_time:expr, $change_time:expr, $file_name:expr, $file_id:literal) => {{
390            let mut result = FileIdBothDirectoryInformation::make_common_test_dir(
391                $file_index,
392                $created,
393                $access_write_time,
394                $access_write_time,
395                $change_time,
396                $file_name,
397            );
398
399            result.file_id = $file_id;
400
401            result
402        }};
403        ($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) => {{
404            let mut result = make_id_both_directory!(
405                $file_index,
406                $created,
407                $access_time,
408                $change_time,
409                $file_name,
410                $file_id
411            );
412            result.last_access_time = $access_time.into();
413            result.last_write_time = $write_time.into();
414            result.end_of_file = $size;
415            result.allocation_size = $alloc_size;
416            result.ea_size = Some($ea_size);
417            result.file_attributes = FileAttributes::new().with_archive(true);
418            result
419        }};
420    }
421
422    macro_rules! make_names {
423        ($file_index:expr, $created:expr, $access_write_time:expr, $change_time:expr, $file_name:expr, $file_id:literal) => {{
424            FileNamesInformation {
425                file_index: $file_index,
426                file_name: SizedWideString::from($file_name),
427            }
428        }};
429        ($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) => {{
430            make_names!(
431                $file_index,
432                $created,
433                $access_time,
434                $change_time,
435                $file_name,
436                $file_id
437            )
438        }};
439    }
440
441    macro_rules! make_directory {
442        ($file_index:expr, $created:expr, $access_write_time:expr, $change_time:expr, $file_name:expr, $file_id:literal) => {{
443            FileDirectoryInformation {
444                file_index: $file_index,
445                creation_time: $created.into(),
446                last_access_time: $access_write_time.into(),
447                last_write_time: $access_write_time.into(),
448                change_time: $change_time.into(),
449                end_of_file: 0,
450                allocation_size: 0,
451                file_attributes: FileAttributes::new().with_directory(true),
452                file_name: SizedWideString::from($file_name),
453            }
454        }};
455        ($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) => {{
456            let mut result = make_directory!(
457                $file_index,
458                $created,
459                $access_time,
460                $change_time,
461                $file_name,
462                $file_id
463            );
464            result.last_access_time = $access_time.into();
465            result.last_write_time = $write_time.into();
466            result.end_of_file = $size;
467            result.allocation_size = $alloc_size;
468            result.file_attributes = FileAttributes::new().with_archive(true);
469            result
470        }};
471    }
472
473    macro_rules! make_id64_extd_both_directory {
474        ($file_index:expr, $created:expr, $access_write_time:expr, $change_time:expr, $file_name:expr, $file_id:literal) => {{
475            let mut result = FileId64ExtdBothDirectoryInformation::make_common_test_dir(
476                $file_index,
477                $created,
478                $access_write_time,
479                $access_write_time,
480                $change_time,
481                $file_name,
482            );
483
484            result.file_id = $file_id;
485
486            result
487        }};
488        ($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) => {{
489            let mut result = make_id64_extd_both_directory!(
490                $file_index,
491                $created,
492                $access_time,
493                $change_time,
494                $file_name,
495                $file_id
496            );
497            result.last_access_time = $access_time.into();
498            result.last_write_time = $write_time.into();
499            result.end_of_file = $size;
500            result.allocation_size = $alloc_size;
501            result.ea_size = Some($ea_size);
502            result.file_attributes = FileAttributes::new().with_archive(true);
503            result
504        }};
505    }
506
507    macro_rules! make_id_extd_directory {
508        ($file_index:expr, $created:expr, $access_write_time:expr, $change_time:expr, $file_name:expr, $file_id:literal) => {{
509            let mut result = FileIdExtdDirectoryInformation::make_common_test_dir(
510                $file_index,
511                $created,
512                $access_write_time,
513                $access_write_time,
514                $change_time,
515                $file_name,
516            );
517
518            result.file_id = $file_id;
519
520            result
521        }};
522        ($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) => {{
523            let mut result = make_id_extd_directory!(
524                $file_index,
525                $created,
526                $access_time,
527                $change_time,
528                $file_name,
529                $file_id
530            );
531            result.last_access_time = $access_time.into();
532            result.last_write_time = $write_time.into();
533            result.end_of_file = $size;
534            result.allocation_size = $alloc_size;
535            result.ea_size = Some($ea_size);
536            result.file_attributes = FileAttributes::new().with_archive(true);
537            result
538        }};
539    }
540
541    macro_rules! make_id64_extd_directory {
542        ($file_index:expr, $created:expr, $access_write_time:expr, $change_time:expr, $file_name:expr, $file_id:literal) => {{
543            let mut result = FileId64ExtdDirectoryInformation::make_common_test_dir(
544                $file_index,
545                $created,
546                $access_write_time,
547                $access_write_time,
548                $change_time,
549                $file_name,
550            );
551
552            result.file_id = $file_id;
553
554            result
555        }};
556        ($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) => {{
557            let mut result = make_id64_extd_directory!(
558                $file_index,
559                $created,
560                $access_time,
561                $change_time,
562                $file_name,
563                $file_id
564            );
565            result.last_access_time = $access_time.into();
566            result.last_write_time = $write_time.into();
567            result.end_of_file = $size;
568            result.allocation_size = $alloc_size;
569            result.ea_size = Some($ea_size);
570            result.file_attributes = FileAttributes::new().with_archive(true);
571            result
572        }};
573    }
574
575    macro_rules! make_id_full_directory {
576        ($file_index:expr, $created:expr, $access_write_time:expr, $change_time:expr, $file_name:expr, $file_id:literal) => {{
577            let mut result = FileIdFullDirectoryInformation::make_common_test_dir(
578                $file_index,
579                $created,
580                $access_write_time,
581                $access_write_time,
582                $change_time,
583                $file_name,
584            );
585
586            result.file_id = $file_id;
587
588            result
589        }};
590        ($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) => {{
591            let mut result = make_id_full_directory!(
592                $file_index,
593                $created,
594                $access_time,
595                $change_time,
596                $file_name,
597                $file_id
598            );
599            result.last_access_time = $access_time.into();
600            result.last_write_time = $write_time.into();
601            result.end_of_file = $size;
602            result.allocation_size = $alloc_size;
603            result.ea_size = Some($ea_size);
604            result.file_attributes = FileAttributes::new().with_archive(true);
605            result
606        }};
607    }
608
609    macro_rules! make_full_directory {
610        ($file_index:expr, $created:expr, $access_write_time:expr, $change_time:expr, $file_name:expr, $file_id:literal) => {{
611            FileFullDirectoryInformation::make_common_test_dir(
612                $file_index,
613                $created,
614                $access_write_time,
615                $access_write_time,
616                $change_time,
617                $file_name,
618            )
619        }};
620        ($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) => {{
621            let mut result = make_full_directory!(
622                $file_index,
623                $created,
624                $access_time,
625                $change_time,
626                $file_name,
627                $file_id
628            );
629            result.last_access_time = $access_time.into();
630            result.last_write_time = $write_time.into();
631            result.end_of_file = $size;
632            result.allocation_size = $alloc_size;
633            result.ea_size = Some($ea_size);
634            result.file_attributes = FileAttributes::new().with_archive(true);
635            result
636        }};
637    }
638
639    macro_rules! make_both_directory {
640        ($file_index:expr, $created:expr, $access_write_time:expr, $change_time:expr, $file_name:expr, $file_id:literal) => {{
641            FileBothDirectoryInformation::make_common_test_dir(
642                $file_index,
643                $created,
644                $access_write_time,
645                $access_write_time,
646                $change_time,
647                $file_name,
648            )
649        }};
650        ($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) => {{
651            let mut result = make_both_directory!(
652                $file_index,
653                $created,
654                $access_time,
655                $change_time,
656                $file_name,
657                $file_id
658            );
659            result.last_access_time = $access_time.into();
660            result.last_write_time = $write_time.into();
661            result.end_of_file = $size;
662            result.allocation_size = $alloc_size;
663            result.ea_size = Some($ea_size);
664            result.file_attributes = FileAttributes::new().with_archive(true);
665            result
666        }};
667    }
668
669    // Some might think I'm more of a POSIX guy, since I use macOS,
670    // but tbh, I actually love windows, especially legacy edge DLLs,
671    // which are the content of this test directory listing dump!
672    // anyway, anybody who reads this code - I'm sorry.
673
674    /// Creates `test_binrw!` for directory information structures.
675    ///
676    /// Calls the appropriate `make_` macro to create test data.
677    /// The binary data matching each test must be taken on the same directory,
678    /// at the same time, so the hard-coded test data matches all the binary dumps.
679    macro_rules! make_dir_test {
680        ($struct_name:ident: $data:literal) => {
681
682    pastey::paste! {
683        type [<$struct_name TestList>] = ChainedItemList<
684            [<File $struct_name Information>],
685            { QueryDirectoryInfo::CHAINED_ALIGNMENT },
686        >;
687        test_binrw! {
688            [<$struct_name TestList>]: [<$struct_name TestList>]::from(vec![
689                [<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),
690                [<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),
691                [<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),
692                [<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 ),
693                [<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),
694            ]) => $data
695        }
696    }
697
698        };
699        ($($struct_name:ident: $data:literal),+) => {
700            $(
701                make_dir_test!($struct_name: $data);
702            )+
703        };
704    }
705
706    make_dir_test!(
707        IdAllExtdBothDirectory: "80000000000000003d22211904e1db01e34e133604e1db01e34e133604e1db01a7e0363604e1db01000000000000000000000000000000001000000002000000000000000000000023cd000000000a0023cd000000000a00000000000000000000000000000000000000000000000000000000000000000000002e0000000000800000000000000022fdbb73afa5db0162f647ed6a3cdc0162f647ed6a3cdc0162f647ed6a3cdc01000000000000000000000000000000001000000004000000000000000000000075030000000007007503000000000700000000000000000000000000000000000000000000000000000000000000000000002e002e00000098000000000000009843301904e1db0111cb0e1c04e1db01242f8155b6a5db0111cb0e1c04e1db0100b4ff000000000000c0ff0000000000200000001800000080000000000000005acd0000000069005acd00000000690000000000000000000000000000000000000000000000000000000000000000000000420069006e0067004d006100700073002e0064006c006c000000000000009800000000000000ee6a511c04e1db01aff3941e04e1db012f9aa1dac7acdb01f6702a3d7f3fdc0100c60b030000000000d00b03000000002000000018000000780000000000000068cd00000000330068cd000000003300000000000000000000000000000000000000000000000000000000000000000000006500640067006500680074006d006c002e0064006c006c000000000000000000000000000000a042a32704e1db01fc50352a04e1db01053587dbc7acdb01fc50352a04e1db01005686020000000000608602000000002000000014000000780000000000000021ce00000000100021ce000000001000000000000000000000000000000000000000000000000000000000000000000000006d007300680074006d006c002e0064006c006c00",
708        IdBothDirectory: "70000000000000003d22211904e1db01e34e133604e1db01e34e133604e1db01a7e0363604e1db01000000000000000000000000000000001000000002000000000000000000000000000000000000000000000000000000000000000000000023cd000000000a002e00000000000000700000000000000022fdbb73afa5db0162f647ed6a3cdc0162f647ed6a3cdc0162f647ed6a3cdc01000000000000000000000000000000001000000004000000000000000000000000000000000000000000000000000000000000000000000075030000000007002e002e000000000080000000000000009843301904e1db0111cb0e1c04e1db01242f8155b6a5db0111cb0e1c04e1db0100b4ff000000000000c0ff0000000000200000001800000080000000000000000000000000000000000000000000000000000000000000005acd000000006900420069006e0067004d006100700073002e0064006c006c008000000000000000ee6a511c04e1db01aff3941e04e1db012f9aa1dac7acdb01f6702a3d7f3fdc0100c60b030000000000d00b03000000002000000018000000780000000000000000000000000000000000000000000000000000000000000068cd0000000033006500640067006500680074006d006c002e0064006c006c000000000000000000a042a32704e1db01fc50352a04e1db01053587dbc7acdb01fc50352a04e1db01005686020000000000608602000000002000000014000000780000000000000000000000000000000000000000000000000000000000000021ce0000000010006d007300680074006d006c002e0064006c006c00",
709        IdAllExtdDirectory: "68000000000000003d22211904e1db01e34e133604e1db01e34e133604e1db01a7e0363604e1db01000000000000000000000000000000001000000002000000000000000000000023cd000000000a0023cd000000000a0000000000000000002e00000000000000680000000000000022fdbb73afa5db0162f647ed6a3cdc0162f647ed6a3cdc0162f647ed6a3cdc0100000000000000000000000000000000100000000400000000000000000000007503000000000700750300000000070000000000000000002e002e000000000078000000000000009843301904e1db0111cb0e1c04e1db01242f8155b6a5db0111cb0e1c04e1db0100b4ff000000000000c0ff0000000000200000001800000080000000000000005acd0000000069005acd0000000069000000000000000000420069006e0067004d006100700073002e0064006c006c007800000000000000ee6a511c04e1db01aff3941e04e1db012f9aa1dac7acdb01f6702a3d7f3fdc0100c60b030000000000d00b03000000002000000018000000780000000000000068cd00000000330068cd00000000330000000000000000006500640067006500680074006d006c002e0064006c006c000000000000000000a042a32704e1db01fc50352a04e1db01053587dbc7acdb01fc50352a04e1db01005686020000000000608602000000002000000014000000780000000000000021ce00000000100021ce00000000100000000000000000006d007300680074006d006c002e0064006c006c00",
710        Names: "1000000000000000020000002e0000001000000000000000040000002e002e00280000000000000018000000420069006e0067004d006100700073002e0064006c006c00000000002800000000000000180000006500640067006500680074006d006c002e0064006c006c00000000000000000000000000140000006d007300680074006d006c002e0064006c006c00",
711        Id64ExtdBothDirectory: "70000000000000003d22211904e1db01e34e133604e1db01e34e133604e1db01a7e0363604e1db01000000000000000000000000000000001000000002000000000000000000000023cd000000000a0000000000000000000000000000000000000000000000000000002e0000000000700000000000000022fdbb73afa5db0162f647ed6a3cdc0162f647ed6a3cdc0162f647ed6a3cdc010000000000000000000000000000000010000000040000000000000000000000750300000000070000000000000000000000000000000000000000000000000000002e002e00000088000000000000009843301904e1db0111cb0e1c04e1db01242f8155b6a5db0111cb0e1c04e1db0100b4ff000000000000c0ff0000000000200000001800000080000000000000005acd0000000069000000000000000000000000000000000000000000000000000000420069006e0067004d006100700073002e0064006c006c000000000000008800000000000000ee6a511c04e1db01aff3941e04e1db012f9aa1dac7acdb01f6702a3d7f3fdc0100c60b030000000000d00b03000000002000000018000000780000000000000068cd00000000330000000000000000000000000000000000000000000000000000006500640067006500680074006d006c002e0064006c006c000000000000000000000000000000a042a32704e1db01fc50352a04e1db01053587dbc7acdb01fc50352a04e1db01005686020000000000608602000000002000000014000000780000000000000021ce00000000100000000000000000000000000000000000000000000000000000006d007300680074006d006c002e0064006c006c00",
712        Id64ExtdDirectory: "58000000000000003d22211904e1db01e34e133604e1db01e34e133604e1db01a7e0363604e1db01000000000000000000000000000000001000000002000000000000000000000023cd000000000a002e00000000000000580000000000000022fdbb73afa5db0162f647ed6a3cdc0162f647ed6a3cdc0162f647ed6a3cdc01000000000000000000000000000000001000000004000000000000000000000075030000000007002e002e000000000068000000000000009843301904e1db0111cb0e1c04e1db01242f8155b6a5db0111cb0e1c04e1db0100b4ff000000000000c0ff0000000000200000001800000080000000000000005acd000000006900420069006e0067004d006100700073002e0064006c006c006800000000000000ee6a511c04e1db01aff3941e04e1db012f9aa1dac7acdb01f6702a3d7f3fdc0100c60b030000000000d00b03000000002000000018000000780000000000000068cd0000000033006500640067006500680074006d006c002e0064006c006c000000000000000000a042a32704e1db01fc50352a04e1db01053587dbc7acdb01fc50352a04e1db01005686020000000000608602000000002000000014000000780000000000000021ce0000000010006d007300680074006d006c002e0064006c006c00",
713        IdExtdDirectory: "60000000000000003d22211904e1db01e34e133604e1db01e34e133604e1db01a7e0363604e1db01000000000000000000000000000000001000000002000000000000000000000023cd000000000a0000000000000000002e00000000000000600000000000000022fdbb73afa5db0162f647ed6a3cdc0162f647ed6a3cdc0162f647ed6a3cdc010000000000000000000000000000000010000000040000000000000000000000750300000000070000000000000000002e002e000000000070000000000000009843301904e1db0111cb0e1c04e1db01242f8155b6a5db0111cb0e1c04e1db0100b4ff000000000000c0ff0000000000200000001800000080000000000000005acd0000000069000000000000000000420069006e0067004d006100700073002e0064006c006c007000000000000000ee6a511c04e1db01aff3941e04e1db012f9aa1dac7acdb01f6702a3d7f3fdc0100c60b030000000000d00b03000000002000000018000000780000000000000068cd00000000330000000000000000006500640067006500680074006d006c002e0064006c006c000000000000000000a042a32704e1db01fc50352a04e1db01053587dbc7acdb01fc50352a04e1db01005686020000000000608602000000002000000014000000780000000000000021ce00000000100000000000000000006d007300680074006d006c002e0064006c006c00",
714        BothDirectory: "60000000000000003d22211904e1db01e34e133604e1db01e34e133604e1db01a7e0363604e1db010000000000000000000000000000000010000000020000000000000000000000000000000000000000000000000000000000000000002e00680000000000000022fdbb73afa5db0162f647ed6a3cdc0162f647ed6a3cdc0162f647ed6a3cdc010000000000000000000000000000000010000000040000000000000000000000000000000000000000000000000000000000000000002e002e0000000000000078000000000000009843301904e1db0111cb0e1c04e1db01242f8155b6a5db0111cb0e1c04e1db0100b4ff000000000000c0ff00000000002000000018000000800000000000000000000000000000000000000000000000000000000000420069006e0067004d006100700073002e0064006c006c0000007800000000000000ee6a511c04e1db01aff3941e04e1db012f9aa1dac7acdb01f6702a3d7f3fdc0100c60b030000000000d00b030000000020000000180000007800000000000000000000000000000000000000000000000000000000006500640067006500680074006d006c002e0064006c006c0000000000000000000000a042a32704e1db01fc50352a04e1db01053587dbc7acdb01fc50352a04e1db010056860200000000006086020000000020000000140000007800000000000000000000000000000000000000000000000000000000006d007300680074006d006c002e0064006c006c00",
715        IdFullDirectory: "58000000000000003d22211904e1db01e34e133604e1db01e34e133604e1db01a7e0363604e1db01000000000000000000000000000000001000000002000000000000000000000023cd000000000a002e00000000000000580000000000000022fdbb73afa5db0162f647ed6a3cdc0162f647ed6a3cdc0162f647ed6a3cdc01000000000000000000000000000000001000000004000000000000000000000075030000000007002e002e000000000068000000000000009843301904e1db0111cb0e1c04e1db01242f8155b6a5db0111cb0e1c04e1db0100b4ff000000000000c0ff0000000000200000001800000080000000000000005acd000000006900420069006e0067004d006100700073002e0064006c006c006800000000000000ee6a511c04e1db01aff3941e04e1db012f9aa1dac7acdb01f6702a3d7f3fdc0100c60b030000000000d00b03000000002000000018000000780000000000000068cd0000000033006500640067006500680074006d006c002e0064006c006c000000000000000000a042a32704e1db01fc50352a04e1db01053587dbc7acdb01fc50352a04e1db01005686020000000000608602000000002000000014000000780000000000000021ce0000000010006d007300680074006d006c002e0064006c006c00",
716        FullDirectory: "48000000000000003d22211904e1db01e34e133604e1db01e34e133604e1db01a7e0363604e1db01000000000000000000000000000000001000000002000000000000002e000000480000000000000022fdbb73afa5db0162f647ed6a3cdc0162f647ed6a3cdc0162f647ed6a3cdc01000000000000000000000000000000001000000004000000000000002e002e0060000000000000009843301904e1db0111cb0e1c04e1db01242f8155b6a5db0111cb0e1c04e1db0100b4ff000000000000c0ff0000000000200000001800000080000000420069006e0067004d006100700073002e0064006c006c00000000006000000000000000ee6a511c04e1db01aff3941e04e1db012f9aa1dac7acdb01f6702a3d7f3fdc0100c60b030000000000d00b03000000002000000018000000780000006500640067006500680074006d006c002e0064006c006c00000000000000000000000000a042a32704e1db01fc50352a04e1db01053587dbc7acdb01fc50352a04e1db01005686020000000000608602000000002000000014000000780000006d007300680074006d006c002e0064006c006c00",
717        Directory: "48000000000000003d22211904e1db01e34e133604e1db01e34e133604e1db01a7e0363604e1db010000000000000000000000000000000010000000020000002e00000000000000480000000000000022fdbb73afa5db0162f647ed6a3cdc0162f647ed6a3cdc0162f647ed6a3cdc010000000000000000000000000000000010000000040000002e002e000000000058000000000000009843301904e1db0111cb0e1c04e1db01242f8155b6a5db0111cb0e1c04e1db0100b4ff000000000000c0ff00000000002000000018000000420069006e0067004d006100700073002e0064006c006c005800000000000000ee6a511c04e1db01aff3941e04e1db012f9aa1dac7acdb01f6702a3d7f3fdc0100c60b030000000000d00b030000000020000000180000006500640067006500680074006d006c002e0064006c006c000000000000000000a042a32704e1db01fc50352a04e1db01053587dbc7acdb01fc50352a04e1db010056860200000000006086020000000020000000140000006d007300680074006d006c002e0064006c006c00"
718    );
719}