smb 0.9.0

A Pure Rust SMB Client implementation
Documentation
//! Directory-related messages.

use binrw::io::TakeSeekExt;
use std::io::SeekFrom;

use binrw::prelude::*;
use modular_bitfield::prelude::*;

use crate::packets::{binrw_util::prelude::*, fscc::*};

use super::FileId;

#[binrw::binrw]
#[derive(Debug)]
pub struct QueryDirectoryRequest {
    #[bw(calc = 33)]
    #[br(assert(_structure_size == 33))]
    _structure_size: u16,
    pub file_information_class: QueryDirectoryInfoClass,
    pub flags: QueryDirectoryFlags,
    // If SMB2_INDEX_SPECIFIED is set in Flags, this value MUST be supplied.
    // Otherwise, it MUST be set to zero and the server MUST ignore it.
    #[bw(assert(flags.index_specified() || *file_index == 0))]
    pub file_index: u32,
    pub file_id: FileId,
    #[bw(calc = PosMarker::default())]
    pub file_name_offset: PosMarker<u16>,
    #[bw(try_calc = file_name.size().try_into())]
    file_name_length: u16, // in bytes.
    pub output_buffer_length: u32,
    #[br(seek_before = SeekFrom::Start(file_name_offset.value as u64))]
    // map stream take until eof:
    #[br(args(file_name_length as u64))]
    #[bw(write_with = PosMarker::write_aoff, args(&file_name_offset))]
    pub file_name: SizedWideString,
}

#[bitfield]
#[derive(BinWrite, BinRead, Debug, Default, Clone, Copy, PartialEq, Eq)]
#[bw(map = |&x| Self::into_bytes(x))]
#[br(map = Self::from_bytes)]
pub struct QueryDirectoryFlags {
    pub restart_scans: bool,
    pub return_single_entry: bool,
    pub index_specified: bool,
    pub reopen: bool,
    #[skip]
    __: B4,
}

#[binrw::binrw]
#[derive(Debug)]
pub struct QueryDirectoryResponse {
    #[bw(calc = 9)]
    #[br(assert(_structure_size == 9))]
    _structure_size: u16,
    #[bw(calc = PosMarker::default())]
    output_buffer_offset: PosMarker<u16>,
    #[bw(try_calc = output_buffer.len().try_into())]
    output_buffer_length: u32,
    #[br(seek_before = SeekFrom::Start(output_buffer_offset.value as u64))]
    #[br(map_stream = |s| s.take_seek(output_buffer_length as u64), parse_with = binrw::helpers::until_eof)]
    #[bw(write_with = PosMarker::write_aoff, args(&output_buffer_offset))]
    pub output_buffer: Vec<u8>,
}

impl QueryDirectoryResponse {
    pub fn read_output<T>(&self) -> BinResult<Vec<T>>
    where
        T: QueryDirectoryInfoValue,
    {
        let mut reader = std::io::Cursor::new(&self.output_buffer);
        let mut result = vec![];
        while reader.position() < self.output_buffer.len() as u64 {
            let item = T::read_le(&mut reader)?;
            result.push(item);
        }
        Ok(result)
    }
}

#[cfg(test)]
mod tests {
    use time::macros::datetime;

    use super::*;
    #[test]
    pub fn test_both_directory_information_attribute_parse() {
        let data = [
            0x70, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x39, 0x75, 0x91, 0xbf, 0xc8, 0x4b, 0xdb, 0x1,
            0xe7, 0xb8, 0x48, 0xcd, 0xc8, 0x5d, 0xdb, 0x1, 0xe7, 0x1b, 0xed, 0xd4, 0x6a, 0x58,
            0xdb, 0x1, 0xe7, 0x1b, 0xed, 0xd4, 0x6a, 0x58, 0xdb, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
            0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0,
            0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
            0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7b,
            0x80, 0x0, 0x0, 0x0, 0x0, 0x2, 0x0, 0x2e, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x70, 0x0,
            0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3c, 0x8, 0x38, 0x96, 0xae, 0x4b, 0xdb, 0x1, 0x10, 0x6a,
            0x87, 0x4b, 0x49, 0x5d, 0xdb, 0x1, 0x62, 0xc, 0xcd, 0xc1, 0xc8, 0x4b, 0xdb, 0x1, 0x62,
            0xc, 0xcd, 0xc1, 0xc8, 0x4b, 0xdb, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
            0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0,
            0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
            0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2a, 0xe7, 0x1, 0x0,
            0x0, 0x0, 0x4, 0x0, 0x2e, 0x0, 0x2e, 0x0, 0x0, 0x0, 0x0, 0x0, 0x78, 0x0, 0x0, 0x0, 0x0,
            0x0, 0x0, 0x0, 0x5b, 0x6c, 0x44, 0xce, 0x6a, 0x58, 0xdb, 0x1, 0x5b, 0x6c, 0x44, 0xce,
            0x6a, 0x58, 0xdb, 0x1, 0x5b, 0x6c, 0x44, 0xce, 0x6a, 0x58, 0xdb, 0x1, 0x5f, 0xd9, 0xd5,
            0xce, 0x6a, 0x58, 0xdb, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
            0x0, 0x0, 0x0, 0x0, 0x0, 0x20, 0x0, 0x0, 0x0, 0xa, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
            0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
            0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf0, 0xa4, 0x0, 0x0, 0x0, 0x0,
            0xa, 0x0, 0x61, 0x0, 0x2e, 0x0, 0x74, 0x0, 0x78, 0x0, 0x74, 0x0, 0x0, 0x0, 0x0, 0x0,
            0x0, 0x0, 0x78, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xd8, 0xce, 0xec, 0xcf, 0x6a, 0x58,
            0xdb, 0x1, 0x7e, 0xc, 0x17, 0xd9, 0x6a, 0x58, 0xdb, 0x1, 0x7e, 0xc, 0x17, 0xd9, 0x6a,
            0x58, 0xdb, 0x1, 0x7e, 0xc, 0x17, 0xd9, 0x6a, 0x58, 0xdb, 0x1, 0x6, 0x0, 0x0, 0x0, 0x0,
            0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20, 0x0, 0x0, 0x0, 0xa, 0x0,
            0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
            0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
            0xb9, 0xf8, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x62, 0x0, 0x2e, 0x0, 0x74, 0x0, 0x78, 0x0,
            0x74, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x78, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x57,
            0x8e, 0x2f, 0xd0, 0x6a, 0x58, 0xdb, 0x1, 0xe2, 0xa8, 0xc1, 0xdd, 0x6a, 0x58, 0xdb, 0x1,
            0xe2, 0xa8, 0xc1, 0xdd, 0x6a, 0x58, 0xdb, 0x1, 0xe2, 0xa8, 0xc1, 0xdd, 0x6a, 0x58,
            0xdb, 0x1, 0xe6, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xe8, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0,
            0x0, 0x20, 0x0, 0x0, 0x0, 0xa, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
            0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
            0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xbb, 0xf8, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x63, 0x0,
            0x2e, 0x0, 0x74, 0x0, 0x78, 0x0, 0x74, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
            0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x32, 0x66, 0x47, 0xd0, 0x6a, 0x58, 0xdb, 0x1, 0x3, 0xc,
            0x39, 0x53, 0x49, 0x5d, 0xdb, 0x1, 0x3, 0xc, 0x39, 0x53, 0x49, 0x5d, 0xdb, 0x1, 0x3,
            0xc, 0x39, 0x53, 0x49, 0x5d, 0xdb, 0x1, 0x26, 0x3e, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
            0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20, 0x0, 0x0, 0x0, 0xa, 0x0, 0x0, 0x0, 0x0, 0x0,
            0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
            0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xbc, 0xf8, 0x0, 0x0,
            0x0, 0x0, 0x4, 0x0, 0x64, 0x0, 0x2e, 0x0, 0x74, 0x0, 0x78, 0x0, 0x74, 0x0,
        ];

        let _res: Vec<FileIdBothDirectoryInformation> = QueryDirectoryResponse {
            output_buffer: data.to_vec(),
        }
        .read_output()
        .unwrap();

        assert_eq!(
            vec![
                ChainedItem::new(FileIdBothDirectoryInformationInner {
                    file_index: 0,
                    creation_time: FileTime::from(datetime!(2024-12-11 12:32:31.7084985)),
                    last_access_time: FileTime::from(datetime!(2025-01-03 10:18:15.6499175)),
                    last_write_time: FileTime::from(datetime!(2024-12-27 14:22:59.9648231)),
                    change_time: FileTime::from(datetime!(2024-12-27 14:22:59.9648231)),
                    end_of_file: 0,
                    allocation_size: 0,
                    file_attributes: FileAttributes::new().with_directory(true),
                    ea_size: Some(0),
                    reparse_tag: None,
                    short_name_length: 0,
                    short_name: [0; 12],
                    fild_id: 562949953454203,
                    file_name: ".".into(),
                }),
                ChainedItem::new(FileIdBothDirectoryInformationInner {
                    file_index: 0,
                    creation_time: FileTime::from(datetime!(2024-12-11 9:25:15.4208828)),
                    last_access_time: FileTime::from(datetime!(2025-01-02 19:05:31.8723088)),
                    last_write_time: FileTime::from(datetime!(2024-12-11 12:32:35.4544738)),
                    change_time: FileTime::from(datetime!(2024-12-11 12:32:35.4544738)),
                    end_of_file: 0,
                    allocation_size: 0,
                    file_attributes: FileAttributes::new().with_directory(true),
                    ea_size: Some(0),
                    reparse_tag: None,
                    short_name_length: 0,
                    short_name: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,],
                    fild_id: 1125899906967338,
                    file_name: "..".into(),
                }),
                ChainedItem::new(FileIdBothDirectoryInformationInner {
                    file_index: 0,
                    creation_time: FileTime::from(datetime!(2024-12-27 14:22:48.7929947)),
                    last_access_time: FileTime::from(datetime!(2024-12-27 14:22:48.7929947)),
                    last_write_time: FileTime::from(datetime!(2024-12-27 14:22:48.7929947)),
                    change_time: FileTime::from(datetime!(2024-12-27 14:22:49.7460575)),
                    end_of_file: 0,
                    allocation_size: 0,
                    file_attributes: FileAttributes::new().with_archive(true),
                    ea_size: Some(0),
                    reparse_tag: None,
                    short_name_length: 0,
                    short_name: [0; 12],
                    fild_id: 2814749767148784,
                    file_name: "a.txt".into(),
                }),
                ChainedItem::new(FileIdBothDirectoryInformationInner {
                    file_index: 0,
                    creation_time: FileTime::from(datetime!(2024-12-27 14:22:51.5742424)),
                    last_access_time: FileTime::from(datetime!(2024-12-27 14:23:06.9505662)),
                    last_write_time: FileTime::from(datetime!(2024-12-27 14:23:06.9505662)),
                    change_time: FileTime::from(datetime!(2024-12-27 14:23:06.9505662)),
                    end_of_file: 6,
                    allocation_size: 8,
                    file_attributes: FileAttributes::new().with_archive(true),
                    ea_size: Some(0),
                    reparse_tag: None,
                    short_name_length: 0,
                    short_name: [0; 12],
                    fild_id: 1125899906906297,
                    file_name: "b.txt".into(),
                }),
                ChainedItem::new(FileIdBothDirectoryInformationInner {
                    file_index: 0,
                    creation_time: FileTime::from(datetime!(2024-12-27 14:22:52.0116823)),
                    last_access_time: FileTime::from(datetime!(2024-12-27 14:23:14.7795682)),
                    last_write_time: FileTime::from(datetime!(2024-12-27 14:23:14.7795682)),
                    change_time: FileTime::from(datetime!(2024-12-27 14:23:14.7795682)),
                    end_of_file: 486,
                    allocation_size: 488,
                    file_attributes: FileAttributes::new().with_archive(true),
                    ea_size: Some(0),
                    reparse_tag: None,
                    short_name_length: 0,
                    short_name: [0; 12],
                    fild_id: 1125899906906299,
                    file_name: "c.txt".into(),
                },),
                ChainedItem::new(FileIdBothDirectoryInformationInner {
                    file_index: 0,
                    creation_time: FileTime::from(datetime!(2024-12-27 14:22:52.167941),),
                    last_access_time: FileTime::from(datetime!(2025-01-02 19:05:44.7804931),),
                    last_write_time: FileTime::from(datetime!(2025-01-02 19:05:44.7804931),),
                    change_time: FileTime::from(datetime!(2025-01-02 19:05:44.7804931),),
                    end_of_file: 15910,
                    allocation_size: 16384,
                    file_attributes: FileAttributes::new().with_archive(true),
                    ea_size: Some(0),
                    reparse_tag: None,
                    short_name_length: 0,
                    short_name: [0; 12],
                    fild_id: 1125899906906300,
                    file_name: "d.txt".into(),
                })
            ],
            _res
        );
    }
}