diff --git a/Cargo.lock b/Cargo.lock index 3a13eb99..156f8926 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2553,10 +2553,13 @@ name = "smb-msg" version = "0.10.3" dependencies = [ "binrw", + "const_format", "modular-bitfield", "pastey", "smb-dtyp", "smb-fscc", + "smb-msg", + "smb-tests", "thiserror 2.0.17", "time", ] @@ -2570,6 +2573,7 @@ dependencies = [ "modular-bitfield", "pastey", "smb-dtyp", + "smb-tests", "thiserror 2.0.17", "time", ] @@ -2598,6 +2602,7 @@ dependencies = [ "rustls-platform-verifier", "smb-dtyp", "smb-msg", + "smb-tests", "thiserror 2.0.17", "time", "tokio", diff --git a/README.md b/README.md index 1be5bc8e..9f614429 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ This project is the first rust implementation of the protocol that powers Windows file sharing and remote services. The project is designed to be used as a crate, but also includes a CLI tool for basic operations. -While most current implementations are mostly bindings to C libraries (such as libsmb2, samba, or windows' own libraries), this project is a full implementation in Rust, with no dependencies on C libraries! +While most current implementations are mostly bindings to C libraries (such as libsmb2, samba, or windows' own libraries), this project is a full implementation in Rust, with no _direct_ dependencies on C libraries. ## Getting started @@ -23,7 +23,8 @@ Check out the `info` and the `copy` sub-commands for more information. ## Features -- ✅ SMB 2.X & 3.X support. +- ✅ All SMB 2.X & 3.X dialects support. +- ✅ Wire message parsing is fully safe, using the `binrw` crate. - ✅ Async (`tokio`), Multi-threaded, or Single-threaded client. - ✅ Compression & Encryption support. - ✅ Transport using SMB over TCP (445), over NetBIOS (139), and over QUIC (443). diff --git a/crates/smb-dtyp/src/binrw_util.rs b/crates/smb-dtyp/src/binrw_util.rs index c07ae3a7..efaa2783 100644 --- a/crates/smb-dtyp/src/binrw_util.rs +++ b/crates/smb-dtyp/src/binrw_util.rs @@ -1,19 +1,23 @@ //! This module contains utility types for the binrw crate. +pub mod boolean; pub mod debug; pub mod file_time; pub mod fixed_string; pub mod helpers; +pub mod multi_sz; pub mod pos_marker; -pub mod sized_wide_string; +pub mod sized_string; pub mod prelude { + pub use super::boolean::Boolean; #[cfg(debug_assertions)] pub use super::debug::LogLocation; pub use super::file_time::FileTime; pub use super::helpers::*; + pub use super::multi_sz::MultiWSz; pub use super::pos_marker::PosMarker; - pub use super::sized_wide_string::{ + pub use super::sized_string::{ BaseSizedString, BaseSizedStringReadArgs, BaseSizedStringReadArgsBuilder, SizedAnsiString, SizedStringSize, SizedWideString, }; diff --git a/crates/smb-dtyp/src/binrw_util/boolean.rs b/crates/smb-dtyp/src/binrw_util/boolean.rs new file mode 100644 index 00000000..efbd2217 --- /dev/null +++ b/crates/smb-dtyp/src/binrw_util/boolean.rs @@ -0,0 +1,74 @@ +//! [`Boolean`][crate::Boolean] implementation for binrw. + +use binrw::{Endian, prelude::*}; +use std::io::{Read, Seek, Write}; + +/// A simple Boolean type that reads and writes as a single byte. +/// Any non-zero value is considered `true`, as defined by MS-FSCC 2.1.8. +/// Similar to the WinAPI `BOOL` type. +/// +/// This type supports `std::size_of::() == 1`, ensuring it is 1 byte in size. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Boolean(bool); + +impl Boolean { + const _VALIDATE_SIZE_OF: [u8; 1] = [0; size_of::()]; +} + +impl BinRead for Boolean { + type Args<'a> = (); + + fn read_options( + reader: &mut R, + _: Endian, + _: Self::Args<'_>, + ) -> binrw::BinResult { + let value: u8 = u8::read_options(reader, Endian::Little, ())?; + Ok(Boolean(value != 0)) + } +} + +impl BinWrite for Boolean { + type Args<'a> = (); + + fn write_options( + &self, + writer: &mut W, + _: Endian, + _: Self::Args<'_>, + ) -> binrw::BinResult<()> { + let value: u8 = if self.0 { 1 } else { 0 }; + value.write_options(writer, Endian::Little, ()) + } +} + +impl From for Boolean { + fn from(value: bool) -> Self { + Boolean(value) + } +} + +impl From for bool { + fn from(val: Boolean) -> Self { + val.0 + } +} + +#[cfg(test)] +mod tests { + use super::*; + use smb_tests::*; + + test_binrw! { + Boolean => true: Boolean::from(true) => "01" + } + + test_binrw! { + Boolean => false: Boolean::from(false) => "00" + } + + // Non-zero is considered true! + test_binrw_read! { + Boolean => true_non_zero: Boolean::from(true) => "17" + } +} diff --git a/crates/smb-dtyp/src/binrw_util/fixed_string.rs b/crates/smb-dtyp/src/binrw_util/fixed_string.rs index 9c981730..3a7e7f90 100644 --- a/crates/smb-dtyp/src/binrw_util/fixed_string.rs +++ b/crates/smb-dtyp/src/binrw_util/fixed_string.rs @@ -129,14 +129,14 @@ impl std::fmt::Display for FixedAnsiString { impl std::fmt::Display for FixedWideString { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - super::sized_wide_string::display_utf16(self.as_slice(), f, core::iter::once) + super::sized_string::display_utf16(self.as_slice(), f, core::iter::once) } } #[cfg(test)] mod tests { use super::*; - use smb_tests::{test_binrw, test_binrw_read_fail}; + use smb_tests::*; type Ansi6 = FixedAnsiString<6>; diff --git a/crates/smb-dtyp/src/binrw_util/helpers.rs b/crates/smb-dtyp/src/binrw_util/helpers.rs index 5d1cb42f..da7fd074 100644 --- a/crates/smb-dtyp/src/binrw_util/helpers.rs +++ b/crates/smb-dtyp/src/binrw_util/helpers.rs @@ -1,6 +1,5 @@ -use binrw::{Endian, NullWideString, prelude::*}; -use std::io::{Read, Seek, Write}; -use std::ops::{Deref, DerefMut}; +use binrw::{Endian, prelude::*}; +use std::io::SeekFrom; #[binrw::writer(writer, endian)] pub fn write_u48(value: &u64) -> binrw::BinResult<()> { @@ -23,6 +22,26 @@ pub fn read_u48() -> binrw::BinResult { Ok(conv(buf)) } +/// Utility binrw parser function that reads an optional value of type `T` +/// if there is _ANY_ data left in the stream +/// (any data, not enough data - for Option it's 1 byte in the stream). +#[binrw::parser(reader, endian)] +pub fn binread_if_has_data() -> BinResult> +where + for<'a> T: BinRead = ()>, +{ + let current_pos = reader.stream_position()?; + let stream_len = reader.seek(SeekFrom::End(0))?; + reader.seek(SeekFrom::Start(current_pos))?; + + let data_left = stream_len - current_pos; + if data_left > 0 { + Ok(Some(T::read_options(reader, endian, ())?)) + } else { + Ok(None) + } +} + #[cfg(test)] mod test { use std::io::Cursor; @@ -78,119 +97,25 @@ mod test { PARSED_BE.write_be(&mut Cursor::new(&mut buf)).unwrap(); assert_eq!(buf, DATA_BYTES); } -} - -/// A simple Boolean type that reads and writes as a single byte. -/// Any non-zero value is considered `true`, as defined by MS-FSCC 2.1.8. -/// Similar to the WinAPI `BOOL` type. -/// -/// This type supports `std::size_of::() == 1`, ensuring it is 1 byte in size. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct Boolean(bool); - -impl Boolean { - const _VALIDATE_SIZE_OF: [u8; 1] = [0; size_of::()]; -} - -impl BinRead for Boolean { - type Args<'a> = (); - - fn read_options( - reader: &mut R, - _: Endian, - _: Self::Args<'_>, - ) -> binrw::BinResult { - let value: u8 = u8::read_options(reader, Endian::Little, ())?; - Ok(Boolean(value != 0)) - } -} - -impl BinWrite for Boolean { - type Args<'a> = (); - - fn write_options( - &self, - writer: &mut W, - _: Endian, - _: Self::Args<'_>, - ) -> binrw::BinResult<()> { - let value: u8 = if self.0 { 1 } else { 0 }; - value.write_options(writer, Endian::Little, ()) - } -} -impl From for Boolean { - fn from(value: bool) -> Self { - Boolean(value) - } -} - -impl From for bool { - fn from(val: Boolean) -> Self { - val.0 - } -} - -/// A MultiSz (Multiple Null-terminated Wide Strings) type that reads and writes a sequence of -/// null-terminated wide strings, ending with an additional null string. -/// -/// Similar to the Registry [`REG_MULTI_SZ`](https://learn.microsoft.com/en-us/windows/win32/sysinfo/registry-value-types) type. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct MultiSz(Vec); - -impl BinRead for MultiSz { - type Args<'a> = (); - - fn read_options( - reader: &mut R, - endian: Endian, - _: Self::Args<'_>, - ) -> BinResult { - let mut strings = Vec::new(); - loop { - let string: NullWideString = NullWideString::read_options(reader, endian, ())?; - if string.is_empty() { - break; - } - strings.push(string); - } - Ok(MultiSz(strings)) - } -} - -impl BinWrite for MultiSz { - type Args<'a> = (); - - fn write_options( - &self, - writer: &mut W, - endian: Endian, - _: Self::Args<'_>, - ) -> BinResult<()> { - for string in &self.0 { - string.write_options(writer, endian, ())?; - } - NullWideString::default().write_options(writer, endian, ())?; - Ok(()) - } -} - -impl Deref for MultiSz { - type Target = Vec; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for MultiSz { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 + #[binrw::binrw] + #[derive(Debug, PartialEq, Eq)] + struct TestBinReadIfHasData { + #[br(parse_with = super::binread_if_has_data)] + pub val1: Option, } -} -impl From> for MultiSz { - fn from(strings: Vec) -> Self { - MultiSz(strings) + #[test] + fn test_if_has_data() { + // with data + let data_with = [0x42u8]; + let mut reader = Cursor::new(&data_with); + let parsed = TestBinReadIfHasData::read_le(&mut reader).unwrap(); + assert_eq!(parsed, TestBinReadIfHasData { val1: Some(0x42) }); + // without data + let data_without: [u8; 0] = []; + let mut reader = Cursor::new(&data_without); + let parsed = TestBinReadIfHasData::read_le(&mut reader).unwrap(); + assert_eq!(parsed, TestBinReadIfHasData { val1: None }); } } diff --git a/crates/smb-dtyp/src/binrw_util/multi_sz.rs b/crates/smb-dtyp/src/binrw_util/multi_sz.rs new file mode 100644 index 00000000..b2bfd6d8 --- /dev/null +++ b/crates/smb-dtyp/src/binrw_util/multi_sz.rs @@ -0,0 +1,101 @@ +//! [MultiWSz](MultiWSz) type for reading and writing multiple null-terminated wide strings. +//! +use std::io::Read; +use std::io::Seek; +use std::io::Write; +use std::ops::Deref; +use std::ops::DerefMut; + +use binrw::prelude::*; +use binrw::{Endian, NullWideString}; + +/// A MultiWSz (Multiple Null-terminated Wide Strings) type that reads and writes a sequence of +/// null-terminated wide strings, ending with an additional null string. +/// +/// Similar to the Registry [`REG_MULTI_SZ`](https://learn.microsoft.com/en-us/windows/win32/sysinfo/registry-value-types) type. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct MultiWSz(Vec); + +impl BinRead for MultiWSz { + type Args<'a> = (); + + fn read_options( + reader: &mut R, + endian: Endian, + _: Self::Args<'_>, + ) -> BinResult { + let mut strings = Vec::new(); + loop { + let string: NullWideString = NullWideString::read_options(reader, endian, ())?; + if string.is_empty() { + break; + } + strings.push(string); + } + Ok(MultiWSz(strings)) + } +} + +impl BinWrite for MultiWSz { + type Args<'a> = (); + + fn write_options( + &self, + writer: &mut W, + endian: Endian, + _: Self::Args<'_>, + ) -> BinResult<()> { + for string in &self.0 { + string.write_options(writer, endian, ())?; + } + NullWideString::default().write_options(writer, endian, ())?; + Ok(()) + } +} + +impl Deref for MultiWSz { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for MultiWSz { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl<'a> FromIterator<&'a str> for MultiWSz { + fn from_iter>(iter: T) -> Self { + MultiWSz(iter.into_iter().map(NullWideString::from).collect()) + } +} + +impl IntoIterator for MultiWSz { + type Item = String; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0 + .into_iter() + .map(|s| s.to_string()) + .collect::>() + .into_iter() + } +} + +#[cfg(test)] +mod tests { + use crate::binrw_util::prelude::MultiWSz; + use smb_tests::*; + + test_binrw! { + MultiWSz: (vec![ + "FirstS", + "AnOther", + "ThirdS", + ]).iter().copied().collect::() => "460069007200730074005300000041006e004f007400680065007200000054006800690072006400530000000000" + } +} diff --git a/crates/smb-dtyp/src/binrw_util/pos_marker.rs b/crates/smb-dtyp/src/binrw_util/pos_marker.rs index 9c11c6aa..24c4ff1e 100644 --- a/crates/smb-dtyp/src/binrw_util/pos_marker.rs +++ b/crates/smb-dtyp/src/binrw_util/pos_marker.rs @@ -20,8 +20,18 @@ impl PosMarker { } /// Returns a [SeekFrom] that seeks relative from the position of the PosMarker. + #[inline] pub fn seek_from(&self, offset: u64) -> SeekFrom { - SeekFrom::Start(self.pos.get().unwrap() + offset) + self.seek_from_if(offset, true) + } + + /// Returns a [SeekFrom] that seeks relative from the position of the PosMarker if condition is true, + pub fn seek_from_if(&self, offset: u64, condition: bool) -> SeekFrom { + if condition { + SeekFrom::Start(self.pos.get().unwrap() + offset) + } else { + SeekFrom::Current(0) + } } fn get_pos(&self) -> binrw::BinResult { @@ -434,6 +444,35 @@ where ) } + /// Writer for value + /// * fill absolute offset to offset location. + /// * add extra size to the written **offset**. + #[binrw::writer(writer, endian)] + pub fn write_roff_b_plus( + value: &U, + write_offset_to: &Self, + offset_relative_to: &PosMarker, + offset_plus: u64, + ) -> BinResult<()> + where + U: BinWrite = ()>, + { + let no_size: Option<&PosMarker> = None; + Self::write_hero( + value, + writer, + endian, + ( + no_size, + Some(write_offset_to), + Some(offset_relative_to), + (), + Self::NO_EXTRA_SIZE, + offset_plus, + ), + ) + } + /// Writer for value /// * fill absolute offset to offset location. /// * fill written size to size location. diff --git a/crates/smb-dtyp/src/binrw_util/sized_wide_string.rs b/crates/smb-dtyp/src/binrw_util/sized_string.rs similarity index 100% rename from crates/smb-dtyp/src/binrw_util/sized_wide_string.rs rename to crates/smb-dtyp/src/binrw_util/sized_string.rs diff --git a/crates/smb-dtyp/src/guid.rs b/crates/smb-dtyp/src/guid.rs index aa44b251..7927646b 100644 --- a/crates/smb-dtyp/src/guid.rs +++ b/crates/smb-dtyp/src/guid.rs @@ -78,6 +78,16 @@ impl Guid { ], )) } + + /// Returns the GUID as a `u128` value. + pub fn as_u128(&self) -> u128 { + let mut bytes = [0u8; 16]; + { + let mut cursor = Cursor::new(&mut bytes[..]); + self.write(&mut cursor).unwrap(); + } + u128::from_le_bytes(bytes) + } } /// A macro to create a `Guid` from a string literal at compile time. @@ -91,7 +101,7 @@ impl Guid { /// ``` #[macro_export] macro_rules! guid { - ($s:literal) => {{ + ($s:expr) => {{ match $crate::Guid::parse_uuid($s) { Ok(guid) => guid, Err(_) => panic!("Invalid GUID format"), @@ -160,6 +170,8 @@ impl std::fmt::Debug for Guid { #[cfg(test)] mod tests { + use smb_tests::*; + use super::*; const TEST_GUID_STR: &str = "065eadf1-6daf-1543-b04f-10e69084c9ae"; @@ -169,10 +181,7 @@ mod tests { 0x1543, [0xb0, 0x4f, 0x10, 0xe6, 0x90, 0x84, 0xc9, 0xae], ); - const TEST_GUID_BYTES: [u8; 16] = [ - 0xf1u8, 0xad, 0x5e, 0x06, 0xaf, 0x6d, 0x43, 0x15, 0xb0, 0x4f, 0x10, 0xe6, 0x90, 0x84, 0xc9, - 0xae, - ]; + const TEST_GUID_BYTES: &'static str = "f1ad5e06af6d4315b04f10e69084c9ae"; #[test] pub fn test_guid_parse_runtime() { @@ -183,25 +192,14 @@ mod tests { #[test] pub fn test_const_guid() { + assert_eq!(make_guid!(TEST_GUID_STR), PARSED_GUID_VALUE); assert_eq!( - make_guid!("065eadf1-6daf-1543-b04f-10e69084c9ae"), + make_guid!(format!("{{{TEST_GUID_STR}}}").as_str()), PARSED_GUID_VALUE ); - assert_eq!( - make_guid!("{065eadf1-6daf-1543-b04f-10e69084c9ae}"), - PARSED_GUID_VALUE - ); - } - - #[test] - pub fn test_guid_parse_bytes() { - assert_eq!(Guid::try_from(&TEST_GUID_BYTES).unwrap(), PARSED_GUID_VALUE); } - #[test] - pub fn test_guid_write_bytes() { - let mut cursor = Cursor::new(Vec::new()); - PARSED_GUID_VALUE.write(&mut cursor).unwrap(); - assert_eq!(cursor.into_inner(), TEST_GUID_BYTES); + test_binrw! { + Guid: PARSED_GUID_VALUE => TEST_GUID_BYTES } } diff --git a/crates/smb-dtyp/src/security/sid.rs b/crates/smb-dtyp/src/security/sid.rs index 7bfb4190..17056e71 100644 --- a/crates/smb-dtyp/src/security/sid.rs +++ b/crates/smb-dtyp/src/security/sid.rs @@ -111,6 +111,7 @@ impl std::fmt::Display for SID { #[cfg(test)] mod tests { use super::*; + use smb_tests::*; const SID_STRING: &str = "S-1-5-21-782712087-4182988437-2163400469-1002"; @@ -122,17 +123,15 @@ mod tests { }; assert_eq!(SID_STRING.parse::().unwrap(), sid_value); assert_eq!(sid_value.to_string(), SID_STRING); + + let invalid_sids = ["", "S-1", "S-1-", "S-1-2-", "S-1-4f4"]; + for sid in invalid_sids { + assert!(sid.parse::().is_err()) + } } - #[test] - fn test_sid_to_from_bin() { - let sid_value = [ - 0x1, 0x5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5, 0x15, 0x0, 0x0, 0x0, 0x17, 0x3d, 0xa7, 0x2e, - 0x95, 0x56, 0x53, 0xf9, 0x15, 0xdf, 0xf2, 0x80, 0xea, 0x3, 0x0, 0x0, - ]; - let mut cursor = std::io::Cursor::new(&sid_value); - assert_eq!( - SID::read_le(&mut cursor).unwrap(), - SID_STRING.parse().unwrap() - ); + + test_binrw! { + SID: SID_STRING.parse::().unwrap() + => "010500000000000515000000173da72e955653f915dff280ea030000" } } diff --git a/crates/smb-dtyp/src/security/tests.rs b/crates/smb-dtyp/src/security/tests.rs index 14d1ce90..906796d6 100644 --- a/crates/smb-dtyp/src/security/tests.rs +++ b/crates/smb-dtyp/src/security/tests.rs @@ -1,17 +1,11 @@ use super::*; -use std::{io::Cursor, str::FromStr}; +use std::str::FromStr; use binrw::prelude::*; +use smb_tests::*; -const OWNER_GROUP_SD: &[u8] = &[ - 0x1, 0x0, 0x0, 0x80, 0x14, 0x0, 0x0, 0x0, 0x30, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x1, 0x5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5, 0x15, 0x0, 0x0, 0x0, 0x17, 0x3d, 0xa7, 0x2e, - 0x95, 0x56, 0x53, 0xf9, 0x15, 0xdf, 0xf2, 0x80, 0xe9, 0x3, 0x0, 0x0, 0x1, 0x5, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x5, 0x15, 0x0, 0x0, 0x0, 0x17, 0x3d, 0xa7, 0x2e, 0x95, 0x56, 0x53, 0xf9, 0x15, 0xdf, - 0xf2, 0x80, 0xe9, 0x3, 0x0, 0x0, -]; - -fn owner_group_sd() -> SecurityDescriptor { +test_binrw! { + SecurityDescriptor => owner_group: SecurityDescriptor { sbz1: 0, control: SecurityDescriptorControl::new().with_self_relative(true), @@ -19,24 +13,12 @@ fn owner_group_sd() -> SecurityDescriptor { group_sid: Some(SID::from_str("S-1-5-21-782712087-4182988437-2163400469-1001").unwrap()), sacl: None, dacl: None, - } -} - -#[test] -fn test_owner_group_parse() { - let sd = SecurityDescriptor::read(&mut std::io::Cursor::new(OWNER_GROUP_SD)).unwrap(); - assert_eq!(sd, owner_group_sd()) + } => "0100008014000000300000000000000000000000010500000000000515000000173da72e955653f915dff280 + e9030000010500000000000515000000173da72e955653f915dff280e9030000" } -#[test] -fn test_owner_group_write() { - let mut written = Cursor::new(vec![]); - owner_group_sd().write(&mut written).unwrap(); - assert_eq!(written.into_inner(), OWNER_GROUP_SD); -} - -fn dacl_only_sd() -> SecurityDescriptor { - SecurityDescriptor { +test_binrw! { + SecurityDescriptor => dacl_only_sd: SecurityDescriptor { sbz1: 0, control: SecurityDescriptorControl::new() .with_self_relative(true) @@ -103,31 +85,10 @@ fn dacl_only_sd() -> SecurityDescriptor { ], } .into(), - } -} - -const DACL_ONLY_DATA: &[u8] = &[ - 0x1, 0x0, 0x4, 0x84, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x14, 0x0, - 0x0, 0x0, 0x2, 0x0, 0x90, 0x0, 0x5, 0x0, 0x0, 0x0, 0x0, 0x13, 0x24, 0x0, 0xff, 0x1, 0x1f, 0x0, - 0x1, 0x5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5, 0x15, 0x0, 0x0, 0x0, 0x17, 0x3d, 0xa7, 0x2e, 0x95, - 0x56, 0x53, 0xf9, 0x15, 0xdf, 0xf2, 0x80, 0xe9, 0x3, 0x0, 0x0, 0x0, 0x13, 0x18, 0x0, 0xff, 0x1, - 0x1f, 0x0, 0x1, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5, 0x20, 0x0, 0x0, 0x0, 0x20, 0x2, 0x0, 0x0, - 0x0, 0x13, 0x14, 0x0, 0xff, 0x1, 0x1f, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5, 0x12, 0x0, - 0x0, 0x0, 0x0, 0x13, 0x14, 0x0, 0xa9, 0x0, 0x12, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x13, 0x24, 0x0, 0xff, 0x1, 0x1f, 0x0, 0x1, 0x5, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x5, 0x15, 0x0, 0x0, 0x0, 0x17, 0x3d, 0xa7, 0x2e, 0x95, 0x56, 0x53, 0xf9, 0x15, 0xdf, - 0xf2, 0x80, 0xea, 0x3, 0x0, 0x0, -]; - -#[test] -fn test_dacl_only_parse() { - let sd = SecurityDescriptor::read(&mut std::io::Cursor::new(DACL_ONLY_DATA)).unwrap(); - assert_eq!(sd, dacl_only_sd()) -} - -#[test] -fn test_dacl_only_write() { - let mut written = Cursor::new(vec![]); - dacl_only_sd().write(&mut written).unwrap(); - assert_eq!(written.into_inner(), DACL_ONLY_DATA); + } => "0100048400000000000000000000000014000000020090000500000000 + 132400ff011f00010500000000000515000000173da72e955653f915dff280 + e903000000131800ff011f0001020000000000052000000020020000001314 + 00ff011f0001010000000000051200000000131400a9001200010100000000 + 00010000000000132400ff011f00010500000000000515000000173da72e95 + 5653f915dff280ea030000" } diff --git a/crates/smb-fscc/src/common_info.rs b/crates/smb-fscc/src/common_info.rs index 5d4e1eeb..c326af50 100644 --- a/crates/smb-fscc/src/common_info.rs +++ b/crates/smb-fscc/src/common_info.rs @@ -363,7 +363,7 @@ pub enum ReparseTag { #[cfg(test)] mod tests { use super::*; - use smb_tests::test_binrw; + use smb_tests::*; test_binrw! { FileFullEaInformation: FileFullEaInformation::from(vec![ diff --git a/crates/smb-fscc/src/directory_info.rs b/crates/smb-fscc/src/directory_info.rs index 57af05f0..509f4383 100644 --- a/crates/smb-fscc/src/directory_info.rs +++ b/crates/smb-fscc/src/directory_info.rs @@ -29,7 +29,7 @@ file_info_classes! { pub Id64ExtdBothDirectory = 0x4f, pub IdAllExtdDirectory = 0x50, pub IdAllExtdBothDirectory = 0x51, - }, Read + } } impl QueryDirectoryInfo { @@ -306,7 +306,7 @@ query_dir_type! { mod tests { use super::*; use crate::ChainedItemList; - use smb_tests::test_binrw; + use smb_tests::*; use time::macros::datetime; macro_rules! make_id_all_extd_both_directory { diff --git a/crates/smb-fscc/src/filesystem_info.rs b/crates/smb-fscc/src/filesystem_info.rs index 26834899..0e78d44a 100644 --- a/crates/smb-fscc/src/filesystem_info.rs +++ b/crates/smb-fscc/src/filesystem_info.rs @@ -21,7 +21,7 @@ file_info_classes! { pub FsSectorSize = 11, pub FsSize = 3, pub FsVolume = 1, - }, Read + } } file_info_classes! { @@ -29,7 +29,7 @@ file_info_classes! { pub SetFileSystemInfo { pub FsControl = 6, pub FsObjectId = 8, - }, Write + } } /// Query attribute information for a file system. @@ -376,7 +376,7 @@ pub struct FileFsVolumeInformation { mod tests { use super::*; use smb_dtyp::make_guid; - use smb_tests::test_binrw; + use smb_tests::*; use time::macros::datetime; test_binrw! { diff --git a/crates/smb-fscc/src/info_classes.rs b/crates/smb-fscc/src/info_classes.rs index 23799986..48c5802e 100644 --- a/crates/smb-fscc/src/info_classes.rs +++ b/crates/smb-fscc/src/info_classes.rs @@ -1,6 +1,9 @@ //! Framework for implementing FSCC Info Classes -use binrw::{meta::ReadEndian, prelude::*}; +use binrw::{ + meta::{ReadEndian, WriteEndian}, + prelude::*, +}; /// Trait for file information types. /// This trait contains all types of all file info types and classes, specified in MS-FSCC. @@ -8,7 +11,12 @@ use binrw::{meta::ReadEndian, prelude::*}; /// It's role is to allow converting an instance of a file information type to a class, /// and to provide the class type from the file information type. pub trait FileInfoType: - Sized + for<'a> BinRead = (Self::Class,)> + ReadEndian + std::fmt::Debug + Sized + + for<'a> BinRead = (Self::Class,)> + + ReadEndian + + for<'a> BinWrite = ()> + + WriteEndian + + std::fmt::Debug { /// The class of the file information. type Class; @@ -25,7 +33,7 @@ macro_rules! file_info_classes { $(#[doc = $docstring:literal])* $svis:vis $name:ident { $($vis:vis $field_name:ident = $cid:literal,)+ - }, $brw_ty:ty + } ) => { #[allow(unused_imports)] use binrw::prelude::*; @@ -41,7 +49,9 @@ macro_rules! file_info_classes { TryFrom<$name, Error = $crate::SmbFsccError> + Send + 'static + Into<$name> - + for <'a> [] = ()> { + + for <'a> [] = ()> + + for <'b> [] = ()> + { const CLASS_ID: [<$name Class>]; } diff --git a/crates/smb-fscc/src/query_file_info.rs b/crates/smb-fscc/src/query_file_info.rs index 2a2a13bb..91d931a7 100644 --- a/crates/smb-fscc/src/query_file_info.rs +++ b/crates/smb-fscc/src/query_file_info.rs @@ -38,7 +38,7 @@ file_info_classes! { pub Position = 14, pub Standard = 5, pub Stream = 22, - }, Read + } } pub type QueryFileFullEaInformation = FileFullEaInformation; @@ -398,10 +398,11 @@ pub struct FileStreamInformationInner { #[derive(Debug, PartialEq, Eq)] #[bw(import(has_next: bool))] pub struct FileGetEaInformation { + // Length does NOT include the null terminator. #[bw(try_calc = ea_name.len().try_into())] ea_name_length: u8, /// The name of the extended attribute. - #[br(map_stream = |s| s.take_seek(ea_name_length as u64))] + #[br(map_stream = |s| s.take_seek(ea_name_length as u64 + 1))] pub ea_name: NullString, } @@ -416,7 +417,7 @@ impl FileGetEaInformation { #[cfg(test)] mod tests { use super::*; - use smb_tests::test_binrw; + use smb_tests::*; use time::macros::datetime; fn get_file_access_information_for_test() -> FileAccessInformation { diff --git a/crates/smb-fscc/src/quota.rs b/crates/smb-fscc/src/quota.rs index cd8d909b..dcb17ce7 100644 --- a/crates/smb-fscc/src/quota.rs +++ b/crates/smb-fscc/src/quota.rs @@ -15,7 +15,7 @@ use smb_dtyp::binrw_util::prelude::*; /// _Note_: This structure is partial: it does not contain the NextEntryOffset field, as it is intended to be used /// in a chained list, see [`ChainedItemList`][crate::ChainedItemList]. #[binrw::binrw] -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] pub struct FileQuotaInformation { #[bw(calc = PosMarker::default())] sid_length: PosMarker, @@ -45,7 +45,7 @@ impl FileQuotaInformation { /// _Note_: This structure is partial: it does not contain the NextEntryOffset field, as it is intended to be used /// in a chained list, see [`ChainedItemList`][crate::ChainedItemList]. #[binrw::binrw] -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] pub struct FileGetQuotaInformation { #[bw(calc = PosMarker::default())] sid_length: PosMarker, diff --git a/crates/smb-fscc/src/set_file_info.rs b/crates/smb-fscc/src/set_file_info.rs index 037e2c1e..8c0cac64 100644 --- a/crates/smb-fscc/src/set_file_info.rs +++ b/crates/smb-fscc/src/set_file_info.rs @@ -30,7 +30,7 @@ file_info_classes! { pub Rename = 10, pub ShortName = 40, pub ValidDataLength = 39, - }, Write + } } /// Set end-of-file information for a file. @@ -173,7 +173,7 @@ mod tests { use crate::FileAttributes; use super::*; - use smb_tests::{test_binrw, test_binrw_read, test_binrw_write}; + use smb_tests::*; use time::macros::datetime; test_binrw! { diff --git a/crates/smb-msg/Cargo.toml b/crates/smb-msg/Cargo.toml index ce5fa929..384bd158 100644 --- a/crates/smb-msg/Cargo.toml +++ b/crates/smb-msg/Cargo.toml @@ -11,6 +11,12 @@ authors.workspace = true keywords.workspace = true categories.workspace = true +[features] +default = ["both"] +client = [] +server = [] +both = ["client", "server"] + [dependencies] smb-dtyp = { path = "../smb-dtyp", version = "0.10.3" } smb-fscc = { path = "../smb-fscc", version = "0.10.3" } @@ -20,3 +26,8 @@ modular-bitfield = { workspace = true } time = { workspace = true } pastey = { workspace = true } thiserror = { workspace = true } + +[dev-dependencies] +smb-msg = { path = ".", version = "0.10.2", features = ["both"] } +smb-tests = { path = "../smb-tests", version = "0.10.2" } +const_format = { workspace = true } diff --git a/crates/smb-msg/README.md b/crates/smb-msg/README.md index 7c646fc4..1b1b9eb3 100644 --- a/crates/smb-msg/README.md +++ b/crates/smb-msg/README.md @@ -8,3 +8,10 @@ It also contains additional structures that are used by SMB specifically (for example, DFS referrals), but common structures (such as GUID) are found in the `smb-types` crate. > This crate is a part of the `smb-rs` project + +## Usage + +This crate is meant to be used with anyone who wants to implement SMB-related functionality in Rust. +See the documentation of the crate for more information. + +Configure the features to your use case: use `server`, `client`, or `both`. diff --git a/crates/smb-msg/src/cancel.rs b/crates/smb-msg/src/cancel.rs index 13dc6ada..49ca60ca 100644 --- a/crates/smb-msg/src/cancel.rs +++ b/crates/smb-msg/src/cancel.rs @@ -3,7 +3,7 @@ use binrw::prelude::*; #[binrw::binrw] -#[derive(Debug, Default)] +#[derive(Debug, Default, PartialEq, Eq)] pub struct CancelRequest { #[br(assert(_structure_size == 4))] #[bw(calc = 4)] @@ -15,13 +15,10 @@ pub struct CancelRequest { #[cfg(test)] mod tests { - use crate::*; - use super::*; + use smb_tests::*; - #[test] - pub fn test_cancel_req_write() { - let data = encode_content(RequestContent::Cancel(CancelRequest::default())); - assert_eq!(data, [0x4, 0x0, 0x0, 0x0]) + test_binrw! { + struct CancelRequest {} => "04000000" } } diff --git a/crates/smb-msg/src/compressed.rs b/crates/smb-msg/src/compressed.rs index 5dd3e207..671fca00 100644 --- a/crates/smb-msg/src/compressed.rs +++ b/crates/smb-msg/src/compressed.rs @@ -70,6 +70,14 @@ impl CompressedChainedMessage { pub const STRUCT_SIZE: usize = std::mem::size_of::() + 4; } +fn add_original_size_to_total_length(algo: &CompressionAlgorithm) -> u64 { + if algo.original_size_required() { + std::mem::size_of::() as u64 + } else { + 0 + } +} + #[binrw::binrw] #[derive(Debug, PartialEq, Eq)] pub struct CompressedChainedItem { @@ -82,8 +90,8 @@ pub struct CompressedChainedItem { #[bw(assert(original_size.is_none() ^ compression_algorithm.original_size_required()))] pub original_size: Option, // The length specified in `length` also includes `original_size` if present! - #[br(map_stream = |s| s.take_seek(length.value as u64 - (if compression_algorithm.original_size_required() {4} else {0})), parse_with = binrw::helpers::until_eof)] - #[bw(write_with = PosMarker::write_size, args(&length))] + #[br(map_stream = |s| s.take_seek(length.value as u64 - (add_original_size_to_total_length(&compression_algorithm))), parse_with = binrw::helpers::until_eof)] + #[bw(write_with = PosMarker::write_size_plus, args(&length, add_original_size_to_total_length(compression_algorithm)))] pub payload_data: Vec, } @@ -96,34 +104,13 @@ pub struct CompressedData { #[cfg(test)] mod tests { - use std::io::Cursor; - use super::*; + use smb_tests::*; - #[test] - pub fn test_comp_chained_simple() { - // This is a simple compressed chained message. - // No special compression (LZ??) is used. - // Does not test the presence of original_size. - let data_bytes = [ - 0xfcu8, 0x53, 0x4d, 0x42, 0x70, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x68, 0x0, 0x0, 0x0, - 0xfe, 0x53, 0x4d, 0x42, 0x40, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x0, 0x1, 0x0, - 0x30, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x91, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, - 0xfe, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x7d, 0x0, 0x0, 0x28, 0x0, 0x30, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x29, 0x0, - 0x1, 0xf, 0x2a, 0x2, 0x0, 0x0, 0x68, 0x0, 0x0, 0x0, 0x8, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x3, 0x0, 0x0, 0x0, 0xee, 0x5, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0, 0x8d, 0x0, 0x0, 0x0, - 0xc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x75, 0xb9, 0x1a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x15, 0x24, 0x4d, 0x70, 0x45, 0x61, 0x5f, 0x44, 0x32, 0x36, 0x32, 0x41, 0x43, 0x36, - 0x32, 0x34, 0x34, 0x35, 0x31, 0x32, 0x39, 0x35, 0x4, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0xee, 0x0, 0x0, 0x0, - ]; - - let mut cursor = Cursor::new(data_bytes); + // TODO(TEST): unchained - assert_eq!( - CompressedMessage::read_le(&mut cursor).unwrap(), - CompressedMessage::Chained(CompressedChainedMessage { + test_binrw! { + CompressedMessage => chained0: CompressedMessage::Chained(CompressedChainedMessage { original_size: 368, items: vec![ CompressedChainedItem { @@ -158,111 +145,52 @@ mod tests { payload_data: vec![0x0, 0x0, 0x0, 0x0, 0xee, 0x0, 0x0, 0x0] } ] - }) - ); + }) => "fc534d42700100000000010068000000fe534d4240000100000000001000010030000000000000009100000000000000fffe0000010000007d00002800300000000000000000000000000000000000002900010f2a02000068000000080100000000000003000000ee0500000c0000008d0000000c000000000075b91a0000000000000015244d7045615f443236324143363234343531323935040000000800000000000000ee000000" } - #[test] - pub fn test_comp_chained_with_orig_size() { - // as opposed to the first test, this DOES test original_size field! - let data = vec![ - 0xfc, 0x53, 0x4d, 0x42, 0x50, 0x10, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x50, 0x0, 0x0, 0x0, - 0xfe, 0x53, 0x4d, 0x42, 0x40, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x0, 0x1, 0x0, - 0x19, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1d, 0x0, 0x0, 0x0, 0x0, 0x60, 0x0, 0x0, 0x25, - 0x16, 0x98, 0xbc, 0x89, 0x8e, 0x3e, 0x86, 0xae, 0xb7, 0x13, 0x55, 0x7c, 0xfa, 0xf1, - 0xbb, 0x11, 0x0, 0x50, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x5, 0x0, 0x0, 0x0, 0xf7, 0x4, 0x0, 0x0, 0xc8, 0x7, 0x0, 0x0, 0xf2, 0x3, 0x4d, - 0x5a, 0x90, 0x0, 0x3, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0xb8, - 0x0, 0x1, 0x0, 0x12, 0x40, 0x7, 0x0, 0xf, 0x2, 0x0, 0xa, 0xf3, 0x2e, 0x20, 0x1, 0x0, - 0x0, 0xe, 0x1f, 0xba, 0xe, 0x0, 0xb4, 0x9, 0xcd, 0x21, 0xb8, 0x1, 0x4c, 0xcd, 0x21, - 0x54, 0x68, 0x69, 0x73, 0x20, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x20, 0x63, - 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x20, 0x62, 0x65, 0x20, 0x72, 0x75, 0x6e, 0x20, 0x69, - 0x6e, 0x20, 0x44, 0x4f, 0x53, 0x20, 0x6d, 0x6f, 0x64, 0x65, 0x2e, 0xd, 0xd, 0xa, 0x24, - 0x5a, 0x0, 0x84, 0xa9, 0x8e, 0xee, 0xb9, 0xed, 0xef, 0x80, 0xea, 0x4, 0x0, 0xd1, 0x99, - 0x6e, 0x86, 0xeb, 0xdd, 0xef, 0x80, 0xea, 0x9f, 0x6e, 0x83, 0xeb, 0xe8, 0x10, 0x0, - 0xb1, 0x81, 0xeb, 0xe1, 0xef, 0x80, 0xea, 0xbe, 0x90, 0x84, 0xeb, 0xec, 0x8, 0x0, 0x31, - 0x83, 0xeb, 0xe3, 0x20, 0x0, 0x31, 0x81, 0xeb, 0xef, 0x38, 0x0, 0xb1, 0x81, 0xea, 0xad, - 0xef, 0x80, 0xea, 0xe4, 0x97, 0x13, 0xea, 0xf0, 0x10, 0x0, 0x31, 0x80, 0xea, 0x7f, - 0x38, 0x0, 0x40, 0x88, 0xeb, 0x5e, 0xe9, 0x40, 0x0, 0x31, 0x85, 0xeb, 0xf3, 0x10, 0x0, - 0x31, 0x84, 0xeb, 0x90, 0x8, 0x0, 0x31, 0x83, 0xeb, 0xa9, 0x8, 0x0, 0x11, 0x80, 0x50, - 0x0, 0x40, 0x99, 0x6e, 0x7f, 0xea, 0x58, 0x0, 0x31, 0x99, 0x6e, 0x82, 0x10, 0x0, 0x40, - 0x52, 0x69, 0x63, 0x68, 0x44, 0x0, 0x3, 0x9f, 0x0, 0xd4, 0x0, 0x50, 0x45, 0x0, 0x0, - 0x64, 0x86, 0x24, 0x0, 0xbb, 0xf4, 0xba, 0x23, 0x14, 0x0, 0xf1, 0xb, 0xf0, 0x0, 0x22, - 0x0, 0xb, 0x2, 0xe, 0x26, 0x0, 0x90, 0xa6, 0x0, 0x0, 0xe0, 0x1d, 0x0, 0x0, 0xf0, 0x61, - 0x0, 0x90, 0x2, 0xb1, 0x0, 0x0, 0x10, 0x22, 0x0, 0x20, 0x40, 0x1, 0x7, 0x0, 0x0, 0xc, - 0x0, 0x56, 0x10, 0x0, 0x0, 0xa, 0x0, 0x4, 0x0, 0x1, 0x2, 0x0, 0x30, 0xf0, 0x44, 0x1, - 0x28, 0x0, 0xb1, 0x26, 0xd1, 0xc2, 0x0, 0x1, 0x0, 0x60, 0x41, 0x0, 0x0, 0x8, 0x17, 0x0, - 0x22, 0x0, 0x20, 0x7, 0x0, 0x1, 0x35, 0x0, 0x0, 0x2, 0x0, 0x0, 0x40, 0x0, 0x3, 0x2, - 0x0, 0x1, 0xb, 0x0, 0xc1, 0x70, 0x14, 0x0, 0x8f, 0xb3, 0x1, 0x0, 0x28, 0x47, 0x14, 0x0, - 0xb8, 0x61, 0x0, 0xf3, 0x14, 0x40, 0x1, 0x28, 0x8e, 0x3, 0x0, 0x0, 0x70, 0xd, 0x0, - 0xb4, 0xcf, 0x6, 0x0, 0x0, 0x70, 0xc2, 0x0, 0xb8, 0x25, 0x0, 0x0, 0x0, 0x90, 0x43, 0x1, - 0xdc, 0x5f, 0x0, 0x0, 0x10, 0x10, 0x4, 0x0, 0x70, 0x40, 0x0, 0xf, 0x2, 0x0, 0x1, 0x20, - 0xa0, 0x5b, 0x42, 0x0, 0x7, 0x1a, 0x0, 0x57, 0x40, 0x14, 0x0, 0xf8, 0x6, 0x10, 0x0, - 0xb, 0x2, 0x0, 0xb2, 0x2e, 0x72, 0x64, 0x61, 0x74, 0x61, 0x0, 0x0, 0x30, 0x5c, 0xd, - 0x91, 0x0, 0x13, 0x60, 0x8, 0x0, 0x7, 0x2, 0x0, 0x62, 0x40, 0x0, 0x0, 0x48, 0x2e, 0x70, - 0x28, 0x0, 0x2, 0x94, 0x0, 0x40, 0xd, 0x0, 0x0, 0xd0, 0x9c, 0x0, 0x17, 0xd, 0x26, 0x0, - 0x3, 0x28, 0x0, 0x12, 0x69, 0x28, 0x0, 0x22, 0x24, 0x26, 0x7c, 0x0, 0x23, 0x0, 0x30, - 0x8, 0x0, 0x7, 0x2, 0x0, 0x1, 0x50, 0x0, 0x12, 0x65, 0x28, 0x0, 0x0, 0xfc, 0x0, 0x63, - 0x0, 0x70, 0x14, 0x0, 0x0, 0xc0, 0x8, 0x0, 0x7, 0x2, 0x0, 0xc1, 0x40, 0x0, 0x0, 0x40, - 0x50, 0x52, 0x4f, 0x54, 0x44, 0x41, 0x54, 0x41, 0x1b, 0x1, 0x22, 0x30, 0x16, 0xa4, 0x0, - 0x0, 0x8, 0x0, 0x7, 0x2, 0x0, 0x0, 0x50, 0x0, 0xa0, 0x47, 0x46, 0x49, 0x44, 0x53, 0x0, - 0x0, 0x0, 0x2c, 0xa9, 0x70, 0x0, 0x43, 0x16, 0x0, 0x0, 0xb0, 0x8, 0x0, 0x7, 0x2, 0x0, - 0x81, 0x40, 0x0, 0x0, 0x42, 0x50, 0x61, 0x64, 0x31, 0x13, 0x0, 0x61, 0x10, 0x9, 0x0, - 0x0, 0xf0, 0x16, 0xb, 0x0, 0xc, 0x2, 0x0, 0xf2, 0x0, 0x80, 0x0, 0x0, 0x42, 0x2e, 0x74, - 0x65, 0x78, 0x74, 0x0, 0x0, 0x0, 0xb, 0xa1, 0x4c, 0xc5, 0x1, 0x2d, 0xb0, 0x4c, 0x30, - 0x0, 0x80, 0x20, 0x0, 0x0, 0x68, 0x50, 0x41, 0x47, 0x45, 0x40, 0x0, 0xf0, 0x0, 0x80, - 0x42, 0x44, 0x0, 0x0, 0xb0, 0x6c, 0x0, 0x0, 0x50, 0x44, 0x0, 0x0, 0xa0, 0x63, 0x13, - 0x0, 0x5, 0x2, 0x0, 0x40, 0x20, 0x0, 0x0, 0x60, 0x28, 0x0, 0xf5, 0x4, 0x4c, 0x4b, 0x0, - 0x0, 0x1c, 0x64, 0x2, 0x0, 0x0, 0x0, 0xb1, 0x0, 0x0, 0x70, 0x2, 0x0, 0x0, 0xf0, 0xa7, - 0x24, 0x0, 0x0, 0x2, 0x0, 0x1, 0x28, 0x0, 0x80, 0x4f, 0x4f, 0x4c, 0x43, 0x4f, 0x44, - 0x45, 0xbe, 0xf4, 0x1, 0x22, 0x70, 0xb3, 0x40, 0x1, 0x20, 0x60, 0xaa, 0x1f, 0x0, 0x5, - 0x2, 0x0, 0x4, 0x78, 0x0, 0xe0, 0x4b, 0x44, 0x0, 0x0, 0xea, 0x5d, 0x0, 0x0, 0x0, 0xa0, - 0xb3, 0x0, 0x0, 0x60, 0x24, 0x2, 0xd, 0x28, 0x0, 0x1, 0x78, 0x0, 0x60, 0x56, 0x52, - 0x46, 0x59, 0x19, 0x15, 0xe, 0x4, 0x8f, 0xb4, 0x0, 0x0, 0x20, 0x3, 0x0, 0x0, 0xf0, - 0x28, 0x0, 0x3, 0x50, 0x48, 0x44, 0x4c, 0x53, 0x76, 0xe, 0x3, 0x22, 0x20, 0xb7, 0x78, - 0x0, 0x25, 0x10, 0xae, 0x74, 0x0, 0x0, 0x2, 0x0, 0x1, 0xa0, 0x0, 0x90, 0x41, 0x47, - 0x45, 0x42, 0x47, 0x46, 0x58, 0x68, 0x69, 0x45, 0x3, 0x21, 0xb7, 0x0, 0x8d, 0x2, 0x1e, - 0x40, 0x28, 0x0, 0xf1, 0x0, 0x54, 0x52, 0x41, 0x43, 0x45, 0x53, 0x55, 0x50, 0xa3, 0x19, - 0x0, 0x0, 0x0, 0xc0, 0xb7, 0x3d, 0x0, 0x2d, 0x0, 0xb0, 0x28, 0x0, 0x1, 0x40, 0x1, 0xb2, - 0x43, 0x4d, 0x52, 0x43, 0xf3, 0xe, 0x0, 0x0, 0x0, 0xe0, 0xb7, 0xe0, 0x1, 0x1d, 0xd0, - 0x28, 0x0, 0x50, 0x60, 0x4b, 0x56, 0x41, 0x53, 0x18, 0x1, 0x10, 0x7e, 0x61, 0x4, 0x13, - 0xf0, 0xa0, 0x0, 0x1d, 0xe0, 0x28, 0x0, 0x50, 0x68, 0x4b, 0x53, 0x43, 0x50, 0xac, 0x0, - 0x10, 0x60, 0x7f, 0x3, 0x22, 0x20, 0xb8, 0x50, 0x0, 0x20, 0x10, 0xaf, 0x13, 0x0, 0x5, - 0x2, 0x0, 0x0, 0x40, 0x1, 0x90, 0x44, 0x52, 0x56, 0x50, 0x52, 0x58, 0x0, 0x0, 0xb7, - 0x16, 0x0, 0x13, 0x30, 0x28, 0x0, 0x1e, 0x20, 0x28, 0x0, 0x50, 0x66, 0x6f, 0x74, 0x68, - 0x6b, 0x24, 0x0, 0x0, 0xad, 0x3, 0x13, 0x40, 0x28, 0x0, 0x1e, 0x30, 0x28, 0x0, 0xe0, - 0x49, 0x4e, 0x49, 0x54, 0x4b, 0x44, 0x42, 0x47, 0xa6, 0xf1, 0x1, 0x0, 0x0, 0x50, 0x6e, - 0x5, 0x4e, 0x2, 0x0, 0x0, 0x40, 0x28, 0x0, 0x90, 0x4d, 0x49, 0x4e, 0x49, 0x45, 0x58, - 0x0, 0x0, 0xbc, 0x20, 0x3, 0x22, 0x50, 0xba, 0x68, 0x1, 0x20, 0x40, 0xb1, 0x62, 0x0, - 0x5, 0x2, 0x0, 0x40, 0x20, 0x0, 0x0, 0x62, 0x50, 0x0, 0x0, 0x11, 0x0, 0xee, 0x1b, 0xe0, - 0x9, 0x0, 0x0, 0x80, 0xba, 0x0, 0x0, 0xf0, 0x9, 0x0, 0x0, 0x70, 0x28, 0x0, 0x40, 0x50, - 0x61, 0x64, 0x32, 0x28, 0x0, 0x71, 0x0, 0x90, 0x1b, 0x0, 0x0, 0x70, 0xc4, 0xb, 0x0, - 0xc, 0x2, 0x0, 0x52, 0x80, 0x0, 0x0, 0x62, 0x2e, 0x6f, 0x3, 0x40, 0x0, 0x80, 0x29, - 0x1c, 0x69, 0x1, 0x0, 0xed, 0x4, 0x49, 0x0, 0x0, 0x60, 0xbb, 0x2b, 0x0, 0xf1, 0x4, - 0x40, 0x0, 0x0, 0xc8, 0x41, 0x4c, 0x4d, 0x4f, 0x53, 0x54, 0x52, 0x4f, 0x40, 0x9c, 0x0, - 0x0, 0x0, 0x30, 0xfc, 0x8d, 0x0, 0x3d, 0x0, 0x50, 0xbc, 0x28, 0x0, 0xf2, 0x0, 0x43, - 0x41, 0x43, 0x48, 0x45, 0x41, 0x4c, 0x49, 0x0, 0x8e, 0x0, 0x0, 0x0, 0xd0, 0xfc, 0x20, - 0x1, 0x1e, 0x70, 0x28, 0x0, 0x0, 0xf8, 0x2, 0x0, 0xc0, 0x3, 0x72, 0x50, 0xb4, 0x1, 0x0, - 0x0, 0x60, 0xfd, 0x50, 0x0, 0x1d, 0x80, 0x28, 0x0, 0x10, 0xc0, 0x28, 0x0, 0xe0, 0x56, - 0x52, 0x46, 0x44, 0x50, 0x3c, 0x1, 0x0, 0x0, 0x20, 0xff, 0x0, 0x0, 0xa0, 0xd8, 0x2, - 0xe, 0x28, 0x0, 0x0, 0x18, 0x1, 0x0, 0x50, 0x0, 0x30, 0xb4, 0x14, 0x2, 0xf4, 0x2, 0x1, - 0x74, 0x5, 0x39, 0x0, 0x40, 0xbd, 0xa0, 0x0, 0x81, 0x20, 0x0, 0x0, 0xc2, 0x50, 0x61, - 0x64, 0x33, 0x15, 0x0, 0x61, 0x80, 0x1d, 0x0, 0x0, 0x80, 0x2, 0x3f, 0x4, 0xc, 0x2, 0x0, - 0x90, 0x80, 0x0, 0x0, 0xc2, 0x43, 0x46, 0x47, 0x52, 0x4f, 0xe3, 0x0, 0x1, 0xa8, 0x5, - 0x21, 0x20, 0x1, 0x70, 0x3, 0x1a, 0x50, 0x50, 0x0, 0x0, 0x18, 0x1, 0x41, 0x50, 0x61, - 0x64, 0x34, 0x40, 0x0, 0x50, 0xd0, 0x1f, 0x0, 0x0, 0x30, 0x22, 0x7, 0xe, 0x2, 0x0, - 0x80, 0x80, 0x0, 0x0, 0xca, 0x2e, 0x72, 0x73, 0x72, 0xfe, 0x3, 0x1, 0xc4, 0x5, 0x9d, - 0x0, 0x40, 0x1, 0x0, 0x90, 0x3, 0x0, 0x0, 0x80, 0x50, 0x0, 0xc1, 0x42, 0x2e, 0x72, - 0x65, 0x6c, 0x6f, 0x63, 0x0, 0x0, 0xdc, 0x55, 0x1, 0xdc, 0x5, 0x78, 0x0, 0x60, 0x1, - 0x0, 0x0, 0x10, 0xc1, 0x55, 0x0, 0x50, 0x0, 0x40, 0x0, 0x0, 0x42, 0x4, 0x0, 0x0, 0x0, - 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x38, 0x8, 0x0, 0x0, - ]; - let mut cursor = Cursor::new(data); - let comp_msg = CompressedMessage::read_le(&mut cursor).unwrap(); - assert_eq!( - comp_msg, - CompressedMessage::Chained(CompressedChainedMessage { + /// I do it for the sake of some real, big data. + const CHAINED1_ITEM2_DATA: &'static str = "f2034d5a90000300000004000000ffff0000b8000\ + 100124007000f02000af32e200100000e1fba0e00b409cd21b8014ccd21546869732070726f677\ + 2616d2063616e6e6f742062652072756e20696e20444f53206d6f64652e0d0d0a245a0084a98ee\ + eb9edef80ea0400d1996e86ebddef80ea9f6e83ebe81000b181ebe1ef80eabe9084ebec0800318\ + 3ebe320003181ebef3800b181eaadef80eae49713eaf010003180ea7f38004088eb5ee94000318\ + 5ebf310003184eb9008003183eba908001180500040996e7fea580031996e82100040526963684\ + 400039f00d4005045000064862400bbf4ba231400f10bf00022000b020e260090a60000e01d000\ + 0f061009002b100001022002040010700000c00561000000a00040001020030f044012800b126d\ + 1c2000100604100000817002200200700013500000200004000030200010b00c17014008fb3010\ + 028471400b86100f3144001288e030000700d00b4cf06000070c200b825000000904301dc5f000\ + 0101004007040000f02000120a05b4200071a0057401400f80610000b0200b22e7264617461000\ + 0305c0d91001360080007020062400000482e702800029400400d0000d09c00170d26000328001\ + 26928002224267c0023003008000702000150001265280000fc00630070140000c00800070200c\ + 14000004050524f54444154411b01223016a400000800070200005000a047464944530000002ca\ + 9700043160000b0080007020081400000425061643113006110090000f0160b000c0200f200800\ + 000422e746578740000000ba14cc5012db04c30008020000068504147454000f0008042440000b\ + 06c000050440000a063130005020040200000602800f5044c4b00001c6402000000b1000070020\ + 000f0a72400000200012800804f4f4c434f4445bef4012270b340012060aa1f00050200047800e\ + 04b440000ea5d000000a0b300006024020d2800017800605652465919150e048fb400002003000\ + 0f02800035048444c53760e032220b778002510ae740000020001a000904147454247465868694\ + 50321b7008d021e402800f1005452414345535550a319000000c0b73d002d00b02800014001b24\ + 34d5243f30e000000e0b7e0011dd0280050604b5641531801107e610413f0a0001de0280050684\ + b534350ac0010607f032220b850002010af1300050200004001904452565052580000b71600133\ + 028001e20280050666f74686b240000ad03134028001e302800e0494e49544b444247a6f101000\ + 0506e054e020000402800904d494e4945580000bc20032250ba68012040b162000502004020000\ + 0625000001100ee1be009000080ba0000f0090000702800405061643228007100901b000070c40\ + b000c020052800000622e6f03400080291c690100ed0449000060bb2b00f104400000c8414c4d4\ + f5354524f409c00000030fc8d003d0050bc2800f2004341434845414c49008e000000d0fc20011\ + e70280000f80200c0037250b401000060fd50001d80280010c02800e056524644503c01000020f\ + f0000a0d8020e280000180100500030b41402f402017405390040bda00081200000c2506164331\ + 50061801d000080023f040c020090800000c2434647524fe30001a80521200170031a505000001\ + 8014150616434400050d01f00003022070e020080800000ca2e727372fe0301c4059d004001009\ + 0030000805000c1422e72656c6f630000dc5501dc0578006001000010c15500500040000042"; + + const CHAINED1_TEST_DATA: &'static str = const_format::concatcp!( + "fc534d42501000000000010050000000fe534d424000010000000000080001001900000000000000070000000000000000000000010000001d00000000600000251698bc898e3e86aeb713557cfaf1bb1100500000100000000000000000000005000000f7040000c8070000", + CHAINED1_ITEM2_DATA, + "04000000080000000000000038080000" + ); + + test_binrw! { + CompressedMessage => chained1: CompressedMessage::Chained(CompressedChainedMessage { original_size: 4176, items: vec![ CompressedChainedItem { @@ -282,110 +210,7 @@ mod tests { compression_algorithm: CompressionAlgorithm::LZ4, flags: 0, original_size: Some(0x7c8), - payload_data: vec![ - 0xf2, 0x3, 0x4d, 0x5a, 0x90, 0x0, 0x3, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, - 0x0, 0xff, 0xff, 0x0, 0x0, 0xb8, 0x0, 0x1, 0x0, 0x12, 0x40, 0x7, 0x0, - 0xf, 0x2, 0x0, 0xa, 0xf3, 0x2e, 0x20, 0x1, 0x0, 0x0, 0xe, 0x1f, 0xba, - 0xe, 0x0, 0xb4, 0x9, 0xcd, 0x21, 0xb8, 0x1, 0x4c, 0xcd, 0x21, 0x54, - 0x68, 0x69, 0x73, 0x20, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x20, - 0x63, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x20, 0x62, 0x65, 0x20, 0x72, 0x75, - 0x6e, 0x20, 0x69, 0x6e, 0x20, 0x44, 0x4f, 0x53, 0x20, 0x6d, 0x6f, 0x64, - 0x65, 0x2e, 0xd, 0xd, 0xa, 0x24, 0x5a, 0x0, 0x84, 0xa9, 0x8e, 0xee, - 0xb9, 0xed, 0xef, 0x80, 0xea, 0x4, 0x0, 0xd1, 0x99, 0x6e, 0x86, 0xeb, - 0xdd, 0xef, 0x80, 0xea, 0x9f, 0x6e, 0x83, 0xeb, 0xe8, 0x10, 0x0, 0xb1, - 0x81, 0xeb, 0xe1, 0xef, 0x80, 0xea, 0xbe, 0x90, 0x84, 0xeb, 0xec, 0x8, - 0x0, 0x31, 0x83, 0xeb, 0xe3, 0x20, 0x0, 0x31, 0x81, 0xeb, 0xef, 0x38, - 0x0, 0xb1, 0x81, 0xea, 0xad, 0xef, 0x80, 0xea, 0xe4, 0x97, 0x13, 0xea, - 0xf0, 0x10, 0x0, 0x31, 0x80, 0xea, 0x7f, 0x38, 0x0, 0x40, 0x88, 0xeb, - 0x5e, 0xe9, 0x40, 0x0, 0x31, 0x85, 0xeb, 0xf3, 0x10, 0x0, 0x31, 0x84, - 0xeb, 0x90, 0x8, 0x0, 0x31, 0x83, 0xeb, 0xa9, 0x8, 0x0, 0x11, 0x80, - 0x50, 0x0, 0x40, 0x99, 0x6e, 0x7f, 0xea, 0x58, 0x0, 0x31, 0x99, 0x6e, - 0x82, 0x10, 0x0, 0x40, 0x52, 0x69, 0x63, 0x68, 0x44, 0x0, 0x3, 0x9f, - 0x0, 0xd4, 0x0, 0x50, 0x45, 0x0, 0x0, 0x64, 0x86, 0x24, 0x0, 0xbb, - 0xf4, 0xba, 0x23, 0x14, 0x0, 0xf1, 0xb, 0xf0, 0x0, 0x22, 0x0, 0xb, 0x2, - 0xe, 0x26, 0x0, 0x90, 0xa6, 0x0, 0x0, 0xe0, 0x1d, 0x0, 0x0, 0xf0, 0x61, - 0x0, 0x90, 0x2, 0xb1, 0x0, 0x0, 0x10, 0x22, 0x0, 0x20, 0x40, 0x1, 0x7, - 0x0, 0x0, 0xc, 0x0, 0x56, 0x10, 0x0, 0x0, 0xa, 0x0, 0x4, 0x0, 0x1, 0x2, - 0x0, 0x30, 0xf0, 0x44, 0x1, 0x28, 0x0, 0xb1, 0x26, 0xd1, 0xc2, 0x0, - 0x1, 0x0, 0x60, 0x41, 0x0, 0x0, 0x8, 0x17, 0x0, 0x22, 0x0, 0x20, 0x7, - 0x0, 0x1, 0x35, 0x0, 0x0, 0x2, 0x0, 0x0, 0x40, 0x0, 0x3, 0x2, 0x0, 0x1, - 0xb, 0x0, 0xc1, 0x70, 0x14, 0x0, 0x8f, 0xb3, 0x1, 0x0, 0x28, 0x47, - 0x14, 0x0, 0xb8, 0x61, 0x0, 0xf3, 0x14, 0x40, 0x1, 0x28, 0x8e, 0x3, - 0x0, 0x0, 0x70, 0xd, 0x0, 0xb4, 0xcf, 0x6, 0x0, 0x0, 0x70, 0xc2, 0x0, - 0xb8, 0x25, 0x0, 0x0, 0x0, 0x90, 0x43, 0x1, 0xdc, 0x5f, 0x0, 0x0, 0x10, - 0x10, 0x4, 0x0, 0x70, 0x40, 0x0, 0xf, 0x2, 0x0, 0x1, 0x20, 0xa0, 0x5b, - 0x42, 0x0, 0x7, 0x1a, 0x0, 0x57, 0x40, 0x14, 0x0, 0xf8, 0x6, 0x10, 0x0, - 0xb, 0x2, 0x0, 0xb2, 0x2e, 0x72, 0x64, 0x61, 0x74, 0x61, 0x0, 0x0, - 0x30, 0x5c, 0xd, 0x91, 0x0, 0x13, 0x60, 0x8, 0x0, 0x7, 0x2, 0x0, 0x62, - 0x40, 0x0, 0x0, 0x48, 0x2e, 0x70, 0x28, 0x0, 0x2, 0x94, 0x0, 0x40, 0xd, - 0x0, 0x0, 0xd0, 0x9c, 0x0, 0x17, 0xd, 0x26, 0x0, 0x3, 0x28, 0x0, 0x12, - 0x69, 0x28, 0x0, 0x22, 0x24, 0x26, 0x7c, 0x0, 0x23, 0x0, 0x30, 0x8, - 0x0, 0x7, 0x2, 0x0, 0x1, 0x50, 0x0, 0x12, 0x65, 0x28, 0x0, 0x0, 0xfc, - 0x0, 0x63, 0x0, 0x70, 0x14, 0x0, 0x0, 0xc0, 0x8, 0x0, 0x7, 0x2, 0x0, - 0xc1, 0x40, 0x0, 0x0, 0x40, 0x50, 0x52, 0x4f, 0x54, 0x44, 0x41, 0x54, - 0x41, 0x1b, 0x1, 0x22, 0x30, 0x16, 0xa4, 0x0, 0x0, 0x8, 0x0, 0x7, 0x2, - 0x0, 0x0, 0x50, 0x0, 0xa0, 0x47, 0x46, 0x49, 0x44, 0x53, 0x0, 0x0, 0x0, - 0x2c, 0xa9, 0x70, 0x0, 0x43, 0x16, 0x0, 0x0, 0xb0, 0x8, 0x0, 0x7, 0x2, - 0x0, 0x81, 0x40, 0x0, 0x0, 0x42, 0x50, 0x61, 0x64, 0x31, 0x13, 0x0, - 0x61, 0x10, 0x9, 0x0, 0x0, 0xf0, 0x16, 0xb, 0x0, 0xc, 0x2, 0x0, 0xf2, - 0x0, 0x80, 0x0, 0x0, 0x42, 0x2e, 0x74, 0x65, 0x78, 0x74, 0x0, 0x0, 0x0, - 0xb, 0xa1, 0x4c, 0xc5, 0x1, 0x2d, 0xb0, 0x4c, 0x30, 0x0, 0x80, 0x20, - 0x0, 0x0, 0x68, 0x50, 0x41, 0x47, 0x45, 0x40, 0x0, 0xf0, 0x0, 0x80, - 0x42, 0x44, 0x0, 0x0, 0xb0, 0x6c, 0x0, 0x0, 0x50, 0x44, 0x0, 0x0, 0xa0, - 0x63, 0x13, 0x0, 0x5, 0x2, 0x0, 0x40, 0x20, 0x0, 0x0, 0x60, 0x28, 0x0, - 0xf5, 0x4, 0x4c, 0x4b, 0x0, 0x0, 0x1c, 0x64, 0x2, 0x0, 0x0, 0x0, 0xb1, - 0x0, 0x0, 0x70, 0x2, 0x0, 0x0, 0xf0, 0xa7, 0x24, 0x0, 0x0, 0x2, 0x0, - 0x1, 0x28, 0x0, 0x80, 0x4f, 0x4f, 0x4c, 0x43, 0x4f, 0x44, 0x45, 0xbe, - 0xf4, 0x1, 0x22, 0x70, 0xb3, 0x40, 0x1, 0x20, 0x60, 0xaa, 0x1f, 0x0, - 0x5, 0x2, 0x0, 0x4, 0x78, 0x0, 0xe0, 0x4b, 0x44, 0x0, 0x0, 0xea, 0x5d, - 0x0, 0x0, 0x0, 0xa0, 0xb3, 0x0, 0x0, 0x60, 0x24, 0x2, 0xd, 0x28, 0x0, - 0x1, 0x78, 0x0, 0x60, 0x56, 0x52, 0x46, 0x59, 0x19, 0x15, 0xe, 0x4, - 0x8f, 0xb4, 0x0, 0x0, 0x20, 0x3, 0x0, 0x0, 0xf0, 0x28, 0x0, 0x3, 0x50, - 0x48, 0x44, 0x4c, 0x53, 0x76, 0xe, 0x3, 0x22, 0x20, 0xb7, 0x78, 0x0, - 0x25, 0x10, 0xae, 0x74, 0x0, 0x0, 0x2, 0x0, 0x1, 0xa0, 0x0, 0x90, 0x41, - 0x47, 0x45, 0x42, 0x47, 0x46, 0x58, 0x68, 0x69, 0x45, 0x3, 0x21, 0xb7, - 0x0, 0x8d, 0x2, 0x1e, 0x40, 0x28, 0x0, 0xf1, 0x0, 0x54, 0x52, 0x41, - 0x43, 0x45, 0x53, 0x55, 0x50, 0xa3, 0x19, 0x0, 0x0, 0x0, 0xc0, 0xb7, - 0x3d, 0x0, 0x2d, 0x0, 0xb0, 0x28, 0x0, 0x1, 0x40, 0x1, 0xb2, 0x43, - 0x4d, 0x52, 0x43, 0xf3, 0xe, 0x0, 0x0, 0x0, 0xe0, 0xb7, 0xe0, 0x1, - 0x1d, 0xd0, 0x28, 0x0, 0x50, 0x60, 0x4b, 0x56, 0x41, 0x53, 0x18, 0x1, - 0x10, 0x7e, 0x61, 0x4, 0x13, 0xf0, 0xa0, 0x0, 0x1d, 0xe0, 0x28, 0x0, - 0x50, 0x68, 0x4b, 0x53, 0x43, 0x50, 0xac, 0x0, 0x10, 0x60, 0x7f, 0x3, - 0x22, 0x20, 0xb8, 0x50, 0x0, 0x20, 0x10, 0xaf, 0x13, 0x0, 0x5, 0x2, - 0x0, 0x0, 0x40, 0x1, 0x90, 0x44, 0x52, 0x56, 0x50, 0x52, 0x58, 0x0, - 0x0, 0xb7, 0x16, 0x0, 0x13, 0x30, 0x28, 0x0, 0x1e, 0x20, 0x28, 0x0, - 0x50, 0x66, 0x6f, 0x74, 0x68, 0x6b, 0x24, 0x0, 0x0, 0xad, 0x3, 0x13, - 0x40, 0x28, 0x0, 0x1e, 0x30, 0x28, 0x0, 0xe0, 0x49, 0x4e, 0x49, 0x54, - 0x4b, 0x44, 0x42, 0x47, 0xa6, 0xf1, 0x1, 0x0, 0x0, 0x50, 0x6e, 0x5, - 0x4e, 0x2, 0x0, 0x0, 0x40, 0x28, 0x0, 0x90, 0x4d, 0x49, 0x4e, 0x49, - 0x45, 0x58, 0x0, 0x0, 0xbc, 0x20, 0x3, 0x22, 0x50, 0xba, 0x68, 0x1, - 0x20, 0x40, 0xb1, 0x62, 0x0, 0x5, 0x2, 0x0, 0x40, 0x20, 0x0, 0x0, 0x62, - 0x50, 0x0, 0x0, 0x11, 0x0, 0xee, 0x1b, 0xe0, 0x9, 0x0, 0x0, 0x80, 0xba, - 0x0, 0x0, 0xf0, 0x9, 0x0, 0x0, 0x70, 0x28, 0x0, 0x40, 0x50, 0x61, 0x64, - 0x32, 0x28, 0x0, 0x71, 0x0, 0x90, 0x1b, 0x0, 0x0, 0x70, 0xc4, 0xb, 0x0, - 0xc, 0x2, 0x0, 0x52, 0x80, 0x0, 0x0, 0x62, 0x2e, 0x6f, 0x3, 0x40, 0x0, - 0x80, 0x29, 0x1c, 0x69, 0x1, 0x0, 0xed, 0x4, 0x49, 0x0, 0x0, 0x60, - 0xbb, 0x2b, 0x0, 0xf1, 0x4, 0x40, 0x0, 0x0, 0xc8, 0x41, 0x4c, 0x4d, - 0x4f, 0x53, 0x54, 0x52, 0x4f, 0x40, 0x9c, 0x0, 0x0, 0x0, 0x30, 0xfc, - 0x8d, 0x0, 0x3d, 0x0, 0x50, 0xbc, 0x28, 0x0, 0xf2, 0x0, 0x43, 0x41, - 0x43, 0x48, 0x45, 0x41, 0x4c, 0x49, 0x0, 0x8e, 0x0, 0x0, 0x0, 0xd0, - 0xfc, 0x20, 0x1, 0x1e, 0x70, 0x28, 0x0, 0x0, 0xf8, 0x2, 0x0, 0xc0, 0x3, - 0x72, 0x50, 0xb4, 0x1, 0x0, 0x0, 0x60, 0xfd, 0x50, 0x0, 0x1d, 0x80, - 0x28, 0x0, 0x10, 0xc0, 0x28, 0x0, 0xe0, 0x56, 0x52, 0x46, 0x44, 0x50, - 0x3c, 0x1, 0x0, 0x0, 0x20, 0xff, 0x0, 0x0, 0xa0, 0xd8, 0x2, 0xe, 0x28, - 0x0, 0x0, 0x18, 0x1, 0x0, 0x50, 0x0, 0x30, 0xb4, 0x14, 0x2, 0xf4, 0x2, - 0x1, 0x74, 0x5, 0x39, 0x0, 0x40, 0xbd, 0xa0, 0x0, 0x81, 0x20, 0x0, 0x0, - 0xc2, 0x50, 0x61, 0x64, 0x33, 0x15, 0x0, 0x61, 0x80, 0x1d, 0x0, 0x0, - 0x80, 0x2, 0x3f, 0x4, 0xc, 0x2, 0x0, 0x90, 0x80, 0x0, 0x0, 0xc2, 0x43, - 0x46, 0x47, 0x52, 0x4f, 0xe3, 0x0, 0x1, 0xa8, 0x5, 0x21, 0x20, 0x1, - 0x70, 0x3, 0x1a, 0x50, 0x50, 0x0, 0x0, 0x18, 0x1, 0x41, 0x50, 0x61, - 0x64, 0x34, 0x40, 0x0, 0x50, 0xd0, 0x1f, 0x0, 0x0, 0x30, 0x22, 0x7, - 0xe, 0x2, 0x0, 0x80, 0x80, 0x0, 0x0, 0xca, 0x2e, 0x72, 0x73, 0x72, - 0xfe, 0x3, 0x1, 0xc4, 0x5, 0x9d, 0x0, 0x40, 0x1, 0x0, 0x90, 0x3, 0x0, - 0x0, 0x80, 0x50, 0x0, 0xc1, 0x42, 0x2e, 0x72, 0x65, 0x6c, 0x6f, 0x63, - 0x0, 0x0, 0xdc, 0x55, 0x1, 0xdc, 0x5, 0x78, 0x0, 0x60, 0x1, 0x0, 0x0, - 0x10, 0xc1, 0x55, 0x0, 0x50, 0x0, 0x40, 0x0, 0x0, 0x42 - ] + payload_data: smb_tests::hex_to_u8_array! {CHAINED1_ITEM2_DATA} }, CompressedChainedItem { compression_algorithm: CompressionAlgorithm::PatternV1, @@ -394,13 +219,11 @@ mod tests { payload_data: vec![0x0, 0x0, 0x0, 0x0, 0x38, 0x8, 0x0, 0x0] }, ] - }) - ) + }) => CHAINED1_TEST_DATA } - #[test] - pub fn test_compressed_data_chained_write() { - let value = CompressedMessage::Chained(CompressedChainedMessage { + test_binrw! { + CompressedMessage => multiple2: CompressedMessage::Chained(CompressedChainedMessage { original_size: 368, items: vec![ CompressedChainedItem { @@ -435,26 +258,6 @@ mod tests { payload_data: vec![0x0, 0x0, 0x0, 0x0, 0xee, 0x0, 0x0, 0x0], }, ], - }); - - let mut cursor = Cursor::new(Vec::new()); - value.write_le(&mut cursor).unwrap(); - - assert_eq!( - cursor.into_inner(), - [ - 0xfc, 0x53, 0x4d, 0x42, 0x70, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x68, 0x0, 0x0, - 0x0, 0xfe, 0x53, 0x4d, 0x42, 0x40, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x0, - 0x1, 0x0, 0x30, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1e, 0x3, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0xff, 0xfe, 0x0, 0x0, 0x5, 0x0, 0x0, 0x0, 0x9, 0x0, 0x0, 0x2c, 0x0, 0x30, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x29, 0x0, 0x1, 0xf, 0x2a, 0x2, 0x0, 0x0, 0x68, 0x0, 0x0, 0x0, 0x8, 0x1, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x11, 0x7, 0x0, 0x0, 0xc, 0x0, - 0x0, 0x0, 0x69, 0x0, 0x20, 0x0, 0xc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1a, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x15, 0x24, 0x4d, 0x70, 0x45, 0x61, 0x5f, 0x44, 0x32, - 0x36, 0x32, 0x41, 0x43, 0x36, 0x32, 0x34, 0x34, 0x35, 0x31, 0x32, 0x39, 0x35, 0x4, - 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xee, 0x0, 0x0, 0x0 - ] - ); + }) => "fc534d42700100000000010068000000fe534d4240000100000000001000010030000000000000001e03000000000000fffe0000050000000900002c00300000000000000000000000000000000000002900010f2a02000068000000080100000000000003000000110700000c000000690020000c000000000000001a0000000000000015244d7045615f443236324143363234343531323935040000000800000000000000ee000000" } } diff --git a/crates/smb-msg/src/create.rs b/crates/smb-msg/src/create.rs index 4c758fc8..068986c8 100644 --- a/crates/smb-msg/src/create.rs +++ b/crates/smb-msg/src/create.rs @@ -61,7 +61,7 @@ impl Debug for FileId { } #[binrw::binrw] -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] pub struct CreateRequest { #[bw(calc = 57)] #[br(assert(_structure_size == 57))] @@ -96,15 +96,15 @@ pub struct CreateRequest { #[br(args { size: SizedStringSize::bytes16(name_length) })] pub name: SizedWideString, - /// Use the `CreateContextReqData::first_...` function family to get the first context of a specific type. + /// Use the [`CreateContextRequestData`]`::first_...` function family to get the first context of a specific type. #[brw(align_before = 8)] #[br(map_stream = |s| s.take_seek(_create_contexts_length.value.into()))] #[bw(write_with = PosMarker::write_roff_size, args(&_create_contexts_offset, &_create_contexts_length))] - pub contexts: ChainedItemList, + pub contexts: ChainedItemList, } #[binrw::binrw] -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq, Copy, Clone)] #[brw(repr(u32))] pub enum ImpersonationLevel { Anonymous = 0x0, @@ -127,7 +127,7 @@ pub enum CreateDisposition { } #[bitfield] -#[derive(BinWrite, BinRead, Default, Debug, Clone, Copy)] +#[derive(BinWrite, BinRead, Default, Debug, Clone, Copy, PartialEq, Eq)] #[bw(map = |&x| Self::into_bytes(x))] #[br(map = Self::from_bytes)] pub struct CreateOptions { @@ -206,11 +206,11 @@ pub struct CreateResponse { #[bw(calc = PosMarker::default())] create_contexts_length: PosMarker, // bytes - /// Use the `CreateContextRespData::first_...` function family to get the first context of a specific type. + /// Use the [`CreateContextResponseData`]`::first_...` function family to get the first context of a specific type. #[br(seek_before = SeekFrom::Start(create_contexts_offset.value as u64))] #[br(map_stream = |s| s.take_seek(create_contexts_length.value.into()))] #[bw(write_with = PosMarker::write_roff_size, args(&create_contexts_offset, &create_contexts_length))] - pub create_contexts: ChainedItemList, + pub create_contexts: ChainedItemList, } #[bitfield] @@ -234,6 +234,8 @@ pub enum CreateAction { Overwritten = 0x3, } +/// The common definition that wrap around all create contexts, for both request and response. +/// /// This is meant to be used within a [`ChainedItemList`][smb_fscc::ChainedItemList]! #[binrw::binrw] #[derive(Debug, PartialEq, Eq)] @@ -244,7 +246,7 @@ where for<'a> T: BinRead = (&'a Vec,)> + BinWrite = ()>, { #[bw(calc = PosMarker::default())] - _name_offset: PosMarker, + _name_offset: PosMarker, // relative to ChainedItem (any access must consider +CHAINED_ITEM_PREFIX_SIZE from start of item) #[bw(calc = u16::try_from(name.len()).unwrap())] name_length: u16, #[bw(calc = 0)] @@ -257,11 +259,14 @@ where #[brw(align_before = 8)] #[br(count = name_length)] + #[br(seek_before = _name_offset.seek_from(_name_offset.value as u64 - CHAINED_ITEM_PREFIX_SIZE as u64))] #[bw(write_with = PosMarker::write_roff_plus, args(&_name_offset, CHAINED_ITEM_PREFIX_SIZE as u64))] pub name: Vec, - #[brw(align_before = 8)] + #[bw(align_before = 8)] + #[br(assert(_data_offset.value % 8 == 0))] #[bw(write_with = PosMarker::write_roff_size_b_plus, args(&_data_offset, &_data_length, &_name_offset, CHAINED_ITEM_PREFIX_SIZE as u64))] + #[br(seek_before = _name_offset.seek_from_if(_data_offset.value as u64 - CHAINED_ITEM_PREFIX_SIZE as u64, _data_length.value > 0))] #[br(map_stream = |s| s.take_seek(_data_length.value.into()), args(&name))] pub data: T, } @@ -276,11 +281,14 @@ macro_rules! create_context_half { ) => { pastey::paste! { +/// This trait is automatically implemented for all +#[doc = concat!("[`Create", stringify!($struct_name), "`]")] +/// create context values. pub trait [] : Into]>> { const CONTEXT_NAME: &'static [u8]; } -#[doc = concat!("The `", stringify!($struct_name), "` Create Context data enum. This contains all the possible context types for ", stringify!($struct_name))] +#[doc = concat!("The [`Create", stringify!($struct_name), "`] Context data enum. ")] #[binrw::binrw] #[derive(Debug, PartialEq, Eq)] #[br(import(name: &Vec))] @@ -352,20 +360,27 @@ pub type [<$struct_name CreateContext>] = CreateContext<[ { pastey::paste!{ +/// This enum contains all the types of create contexts. pub enum CreateContextType { $( + $(#[doc = $docstring])* [<$context_type:upper>], )+ } impl CreateContextType { $( + #[doc = concat!("The name for the `", stringify!($context_type), "` create context.")] pub const [<$context_type:upper _NAME>]: &[u8] = $class_name; )+ @@ -389,34 +404,49 @@ impl CreateContextType { } create_context_half! { - Req { + Request { $($context_type: $req_type,)+ } } create_context_half! { - Resp { - $($context_type: $res_type,)+ + Response { + $($($context_type: $res_type,)?)+ } } } } make_create_context!( - exta: b"ExtA", ChainedItemList, ChainedItemList, - secd: b"SecD", SdBuffer, SdBuffer, - dhnq: b"DHnQ", DurableHandleRequest, DurableHandleResponse, - dhnc: b"DHNc", DurableHandleReconnect, DurableHandleReconnect, - alsi: b"AlSi", AllocationSize, AllocationSize, - mxac: b"MxAc", QueryMaximalAccessRequest, QueryMaximalAccessResponse, - twrp: b"TWrp", TimewarpToken, TimewarpToken, - qfid: b"QFid", QueryOnDiskIdReq, QueryOnDiskIdResp, - rqls: b"RqLs", RequestLease, RequestLease, // v1+2 - dh2q: b"DH2Q", DurableHandleRequestV2, DH2QResp, - dh2c: b"DH2C", DurableHandleReconnectV2, DurableHandleReconnectV2, - appinstid: b"\x45\xBC\xA6\x6A\xEF\xA7\xF7\x4A\x90\x08\xFA\x46\x2E\x14\x4D\x74", AppInstanceId, AppInstanceId, - appinstver: b"\xB9\x82\xD0\xB7\x3B\x56\x07\x4F\xA0\x7B\x52\x4A\x81\x16\xA0\x10", AppInstanceVersion, AppInstanceVersion, - svhdxopendev: b"\x9C\xCB\xCF\x9E\x04\xC1\xE6\x43\x98\x0E\x15\x8D\xA1\xF6\xEC\x83", SvhdxOpenDeviceContext, SvhdxOpenDeviceContext, + /// The data contains the extended attributes that MUST be stored on the created file. + exta: b"ExtA", ChainedItemList; + /// The data contains a security descriptor that MUST be stored on the created file. + secd: b"SecD", SecurityDescriptor; + /// The client is requesting the open to be durable + dhnq: b"DHnQ", DurableHandleRequest, DurableHandleResponse; + /// The client is requesting to reconnect to a durable open after being disconnected + dhnc: b"DHNc", DurableHandleReconnect; + /// The data contains the required allocation size of the newly created file. + alsi: b"AlSi", AllocationSize; + /// The client is requesting that the server return maximal access information. + mxac: b"MxAc", QueryMaximalAccessRequest, QueryMaximalAccessResponse; + /// The client is requesting that the server open an earlier version of the file identified by the provided time stamp. + twrp: b"TWrp", TimewarpToken; + /// The client is requesting that the server return a 32-byte opaque BLOB that uniquely identifies the file being opened on disk. + qfid: b"QFid", QueryOnDiskIdReq, QueryOnDiskIdResp; + /// The client is requesting that the server return a lease. This value is only supported for the SMB 2.1 and 3.x dialect family. + rqls: b"RqLs", RequestLease, RequestLease; // v1+2, request & response are the same + /// The client is requesting the open to be durable. This value is only supported for the SMB 3.x dialect family. + dh2q: b"DH2Q", DurableHandleRequestV2, DH2QResp; + /// The client is requesting to reconnect to a durable open after being disconnected. This value is only supported for the SMB 3.x dialect family. + dh2c: b"DH2C", DurableHandleReconnectV2; + /// The client is supplying an identifier provided by an application instance while opening a file. This value is only supported for the SMB 3.x dialect family. + appinstid: b"\x45\xBC\xA6\x6A\xEF\xA7\xF7\x4A\x90\x08\xFA\x46\x2E\x14\x4D\x74", AppInstanceId, AppInstanceId; + /// The client is supplying a version to correspond to the application instance identifier. This value is only supported for SMB 3.1.1 dialect. + appinstver: b"\xB9\x82\xD0\xB7\x3B\x56\x07\x4F\xA0\x7B\x52\x4A\x81\x16\xA0\x10", AppInstanceVersion, AppInstanceVersion; + /// Provided by an application while opening a shared virtual disk file. + /// This Create Context value is not valid for the SMB 2.002, SMB 2.1, and SMB 3.0 dialects + svhdxopendev: b"\x9C\xCB\xCF\x9E\x04\xC1\xE6\x43\x98\x0E\x15\x8D\xA1\xF6\xEC\x83", SvhdxOpenDeviceContext, SvhdxOpenDeviceContext; ); macro_rules! empty_req { @@ -427,8 +457,6 @@ macro_rules! empty_req { }; } -pub type SdBuffer = SecurityDescriptor; - #[binrw::binrw] #[derive(Debug, PartialEq, Eq, Default)] pub struct DurableHandleRequest { @@ -452,6 +480,7 @@ pub struct DurableHandleReconnect { #[binrw::binrw] #[derive(Debug, PartialEq, Eq, Default)] pub struct QueryMaximalAccessRequest { + #[br(parse_with = binread_if_has_data)] pub timestamp: Option, } @@ -464,7 +493,7 @@ pub struct AllocationSize { #[binrw::binrw] #[derive(Debug, PartialEq, Eq)] pub struct TimewarpToken { - pub tiemstamp: FileTime, + pub timestamp: FileTime, } #[binrw::binrw] @@ -491,8 +520,7 @@ pub struct RequestLeaseV1 { pub struct RequestLeaseV2 { pub lease_key: u128, pub lease_state: LeaseState, - #[br(assert(lease_flags == 0 || lease_flags == 4))] - pub lease_flags: u32, + pub lease_flags: LeaseFlags, #[bw(calc = 0)] #[br(assert(lease_duration == 0))] lease_duration: u64, @@ -503,6 +531,18 @@ pub struct RequestLeaseV2 { reserved: u16, } +#[bitfield] +#[derive(BinWrite, BinRead, Debug, Default, Clone, Copy, PartialEq, Eq)] +#[bw(map = |&x| Self::into_bytes(x))] +#[br(map = Self::from_bytes)] +pub struct LeaseFlags { + #[skip] + __: B2, + pub parent_lease_key_set: bool, + #[skip] + __: B29, +} + empty_req!(QueryOnDiskIdReq); #[binrw::binrw] @@ -547,6 +587,7 @@ pub struct AppInstanceId { _reserved: u16, pub app_instance_id: Guid, } + #[binrw::binrw] #[derive(Debug, PartialEq, Eq)] pub struct AppInstanceVersion { @@ -688,10 +729,8 @@ mod tests { use super::*; - #[test] - pub fn test_create_request_written_correctly() { - let file_name = "hello"; - let request = CreateRequest { + test_request! { + Create { requested_oplock_level: OplockLevel::None, impersonation_level: ImpersonationLevel::Impersonation, desired_access: FileAccessMask::from_bytes(0x00100081u32.to_le_bytes()), @@ -704,7 +743,7 @@ mod tests { create_options: CreateOptions::new() .with_synchronous_io_nonalert(true) .with_disallow_exclusive(true), - name: file_name.into(), + name: "hello".into(), contexts: vec![ DurableHandleRequestV2 { timeout: 0, @@ -716,53 +755,14 @@ mod tests { QueryOnDiskIdReq.into(), ] .into(), - }; - let data_without_header = encode_content(request.into()); - assert_eq!( - data_without_header, - vec![ - 0x39, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x81, 0x0, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x7, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x20, 0x0, 0x2, 0x0, 0x78, 0x0, 0xa, 0x0, - 0x88, 0x0, 0x0, 0x0, 0x68, 0x0, 0x0, 0x0, 0x68, 0x0, 0x65, 0x0, 0x6c, 0x0, 0x6c, - 0x0, 0x6f, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x38, 0x0, 0x0, 0x0, 0x10, 0x0, 0x4, - 0x0, 0x0, 0x0, 0x18, 0x0, 0x20, 0x0, 0x0, 0x0, 0x44, 0x48, 0x32, 0x51, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x20, 0xa3, 0x79, 0xc6, 0xa0, 0xc0, 0xef, 0x11, 0x8b, 0x7b, 0x0, 0xc, - 0x29, 0x80, 0x16, 0x82, 0x18, 0x0, 0x0, 0x0, 0x10, 0x0, 0x4, 0x0, 0x0, 0x0, 0x18, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x4d, 0x78, 0x41, 0x63, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x10, 0x0, 0x4, 0x0, 0x0, 0x0, 0x18, 0x0, 0x0, 0x0, 0x0, 0x0, 0x51, 0x46, - 0x69, 0x64, 0x0, 0x0, 0x0, 0x0 - ] - ) + } => "390000000200000000000000000000000000000000000000810010000000000007000000010000002000020078000a008800000068 + 000000680065006c006c006f0000000000000038000000100004000000180020000000444832510000000000000000000000000000000000 + 00000020a379c6a0c0ef118b7b000c29801682180000001000040000001800000000004d7841630000000000000000100004000000180000 + 0000005146696400000000" } - #[test] - pub fn test_create_response_parsed_correctly() { - let data: [u8; 240] = [ - 0xfe, 0x53, 0x4d, 0x42, 0x40, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, - 0x01, 0x00, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0xff, 0xfe, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x61, 0x00, - 0x00, 0x14, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x00, 0x00, 0x00, 0x01, 0x00, - 0x00, 0x00, 0x3c, 0x08, 0x38, 0x96, 0xae, 0x4b, 0xdb, 0x01, 0xc8, 0x55, 0x4b, 0x70, - 0x6b, 0x58, 0xdb, 0x01, 0x62, 0x0c, 0xcd, 0xc1, 0xc8, 0x4b, 0xdb, 0x01, 0x62, 0x0c, - 0xcd, 0xc1, 0xc8, 0x4b, 0xdb, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x49, 0x01, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, - 0x0c, 0x00, 0x00, 0x00, 0x98, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, 0x20, 0x00, - 0x00, 0x00, 0x10, 0x00, 0x04, 0x00, 0x00, 0x00, 0x18, 0x00, 0x08, 0x00, 0x00, 0x00, - 0x4d, 0x78, 0x41, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x01, - 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x04, 0x00, 0x00, 0x00, 0x18, 0x00, - 0x20, 0x00, 0x00, 0x00, 0x51, 0x46, 0x69, 0x64, 0x00, 0x00, 0x00, 0x00, 0x2a, 0xe7, - 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0xd9, 0xcf, 0x17, 0xb0, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, - ]; - let m = decode_content(&data).content.to_create().unwrap(); - assert_eq!( - m, - CreateResponse { + test_response! { + Create { oplock_level: OplockLevel::None, flags: CreateResponseFlags::new(), create_action: CreateAction::Opened, @@ -787,7 +787,104 @@ mod tests { .into(), ] .into() - } - ) + } => "59000000010000003c083896ae4bdb01c8554b706b58db01620ccdc1c84bdb01620ccdc1c84bdb0100000000000000000000 + 0000000000001000000000000000490100000c000000090000000c0000009800000058000000200000001000040000001800080000 + 004d7841630000000000000000ff011f000000000010000400000018002000000051466964000000002ae7010000000400d9cf17b0 + 0000000000000000000000000000000000000000" + } + + /* + Tests to add for contexts: + dhnc: b"DHNc", DurableHandleReconnect, DurableHandleReconnect, + dh2c: b"DH2C", DurableHandleReconnectV2, DurableHandleReconnectV2, + appinstid: b"\x45\xBC\xA6\x6A\xEF\xA7\xF7\x4A\x90\x08\xFA\x46\x2E\x14\x4D\x74", AppInstanceId, AppInstanceId, + appinstver: b"\xB9\x82\xD0\xB7\x3B\x56\x07\x4F\xA0\x7B\x52\x4A\x81\x16\xA0\x10", AppInstanceVersion, AppInstanceVersion, + svhdxopendev: b"\x9C\xCB\xCF\x9E\x04\xC1\xE6\x43\x98\x0E\x15\x8D\xA1\xF6\xEC\x83", SvhdxOpenDeviceContext, SvhdxOpenDeviceContext, + */ + + use smb_dtyp::guid; + use smb_tests::*; + use time::macros::datetime; + + // Tests for the following contexts are not implemented here: + // - ExtA - already tested in smb-fscc & query info/ea tests + // - SecD - already tested in smb-dtyp tests + + test_binrw! { + struct DurableHandleRequest {} => "00000000000000000000000000000000" + } + + test_binrw! { + struct DurableHandleResponse {} => "0000000000000000" + } + + test_binrw! { + struct QueryMaximalAccessRequest { + timestamp: None, + } => "" + } + + test_binrw! { + struct QueryMaximalAccessResponse { + query_status: Status::Success, + maximal_access: FileAccessMask::from_bytes(0x001f01ffu32.to_le_bytes()), + } => "00000000ff011f00" + } + + test_binrw! { + struct QueryOnDiskIdReq {} => "" + } + + test_binrw! { + struct QueryOnDiskIdResp { + file_id: 0x2ae7010000000400, + volume_id: 0xd9cf17b000000000, + } => "000400000001e72a 00000000b017cfd9 00000000000000000000000000000000" + } + + // TODO(TEST): RqLsV1 + test_binrw! { + RequestLease => rqlsv2: RequestLease::RqLsReqv2(RequestLeaseV2 { + lease_key: guid!("b69d8fd8-184b-7c4d-a359-40c8a53cd2b7").as_u128(), + lease_state: LeaseState::new().with_read_caching(true).with_handle_caching(true), + lease_flags: LeaseFlags::new().with_parent_lease_key_set(true), + parent_lease_key: guid!("2d158ea3-55db-f749-9cd1-095496a06627").as_u128(), + epoch: 0 + }) => "d88f9db64b184d7ca35940c8a53cd2b703000000040000000000000000000000a38e152ddb5549f79cd1095496a0662700000000" + } + + test_binrw! { + struct AllocationSize { + allocation_size: 0xebfef0d4c000, + } => "00c0d4f0feeb0000" + } + + test_binrw! { + struct DurableHandleRequestV2 { + create_guid: guid!("5a08e844-45c3-234d-87c6-596d2bc8bca5"), + flags: DurableHandleV2Flags::new(), + timeout: 0, + } => "0000000000000000000000000000000044e8085ac3454d2387c6596d2bc8bca5" + } + + test_binrw! { + struct DH2QResp { + timeout: 180000, + flags: DurableHandleV2Flags::new(), + } => "20bf020000000000" + } + + test_binrw! { + struct TimewarpToken { + timestamp: datetime!(2025-01-20 15:36:20.277632400).into(), + } => "048fa10d516bdb01" + } + + test_binrw! { + struct DurableHandleReconnectV2 { + file_id: guid!("000000b3-0008-0000-dd00-000008000000").into(), + create_guid: guid!("a23e428c-1bac-7e43-8451-91f9f2277a95"), + flags: DurableHandleV2Flags::new(), + } => "b300000008000000dd000000080000008c423ea2ac1b437e845191f9f2277a9500000000" } } diff --git a/crates/smb-msg/src/dfsc.rs b/crates/smb-msg/src/dfsc.rs index bb4bf329..8d068334 100644 --- a/crates/smb-msg/src/dfsc.rs +++ b/crates/smb-msg/src/dfsc.rs @@ -76,11 +76,13 @@ impl DfsRequestData { } } -#[binrw::binrw] +/// NOTE: This struct currently implements [`BinWrite`] only as a placeholder (calling it will panic). +#[binrw::binread] #[derive(Debug, PartialEq, Eq)] pub struct RespGetDfsReferral { pub path_consumed: u16, - #[bw(try_calc = referral_entries.len().try_into())] + // #[bw(try_calc = referral_entries.len().try_into())] + #[br(temp)] number_of_referrals: u16, pub referral_header_flags: ReferralHeaderFlags, #[br(count = number_of_referrals)] @@ -88,6 +90,21 @@ pub struct RespGetDfsReferral { // string_buffer is here, but it's use is to provide a buffer for the strings in the referral entries. } +impl BinWrite for RespGetDfsReferral { + type Args<'a> = (); + + fn write_options( + &self, + _writer: &mut W, + _endian: binrw::Endian, + _args: Self::Args<'_>, + ) -> binrw::BinResult<()> { + unimplemented!( + "Placeholder trait implementation for RespGetDfsReferral - writing is currently not supported" + ); + } +} + #[bitfield] #[derive(BinWrite, BinRead, Debug, Default, Clone, Copy, PartialEq, Eq)] #[bw(map = |&x| Self::into_bytes(x))] @@ -106,12 +123,15 @@ pub struct ReferralHeaderFlags { #[binrw::binrw] #[derive(Debug, PartialEq, Eq)] pub struct ReferralEntry { + /* All entry types share the same fields in their beginnings, so we split it */ #[bw(calc = value.get_version())] pub version: u16, #[bw(calc = PosMarker::default())] _size: PosMarker, + #[br(args(version))] - #[bw(write_with = PosMarker::write_size, args(&_size))] + // map_stream is not used here because we seek manually in the inner structs. + #[bw(write_with = PosMarker::write_size_plus, args(&_size, Self::COMMON_PART_SIZE as u64))] pub value: ReferralEntryValue, } @@ -204,12 +224,15 @@ pub struct ReferralEntryValueV2 { /// The DFS path that corresponds to the DFS root or the DFS link for which target information is returned. #[br(seek_before = _start.seek_from((dfs_path_offset.value as usize - ReferralEntry::COMMON_PART_SIZE).try_into().unwrap()))] + #[bw(write_with = PosMarker::write_roff_b_plus, args(&dfs_path_offset, &_start, ReferralEntry::COMMON_PART_SIZE as u64))] pub dfs_path: NullWideString, /// The DFS path that corresponds to the DFS root or the DFS link for which target information is returned. #[br(seek_before = _start.seek_from((dfs_alternate_path_offset.value as usize - ReferralEntry::COMMON_PART_SIZE).try_into().unwrap()))] + #[bw(write_with = PosMarker::write_roff_b_plus, args(&dfs_alternate_path_offset, &_start, ReferralEntry::COMMON_PART_SIZE as u64))] pub dfs_alternate_path: NullWideString, /// The DFS target that corresponds to this entry. #[br(seek_before = _start.seek_from((network_address_offset.value as usize - ReferralEntry::COMMON_PART_SIZE).try_into().unwrap()))] + #[bw(write_with = PosMarker::write_roff_b_plus, args(&network_address_offset, &_start, ReferralEntry::COMMON_PART_SIZE as u64))] pub network_address: NullWideString, #[br(seek_before = _restore_position.seek_from(0))] @@ -290,12 +313,15 @@ pub struct EntryV3V4DfsPaths { /// The DFS path that corresponds to the DFS root or the DFS link for which target information is returned. #[br(seek_before = _start.seek_from((dfs_path_offset.value - EntryV3Value::OFFSET_FROM_ENTRY_START).into()))] + #[bw(write_with = PosMarker::write_roff_b_plus, args(&dfs_path_offset, &_start, ReferralEntry::COMMON_PART_SIZE as u64))] pub dfs_path: NullWideString, /// The DFS path that corresponds to the DFS root or the DFS link for which target information is returned. #[br(seek_before = _start.seek_from((dfs_alternate_path_offset.value - EntryV3Value::OFFSET_FROM_ENTRY_START).into()))] + #[bw(write_with = PosMarker::write_roff_b_plus, args(&dfs_alternate_path_offset, &_start, ReferralEntry::COMMON_PART_SIZE as u64))] pub dfs_alternate_path: NullWideString, /// The DFS target that corresponds to this entry. #[br(seek_before = _start.seek_from((network_address_offset.value - EntryV3Value::OFFSET_FROM_ENTRY_START).into()))] + #[bw(write_with = PosMarker::write_roff_b_plus, args(&network_address_offset, &_start, ReferralEntry::COMMON_PART_SIZE as u64))] pub network_address: NullWideString, #[br(seek_before = _restore_position.seek_from(0))] @@ -362,87 +388,53 @@ struct ReferralEntryFlagsV4 { #[cfg(test)] mod tests { use super::*; - use crate::IoctlRequestContent; - use std::io::Cursor; + use smb_tests::*; - #[test] - pub fn test_write_req() { - let req = ReqGetDfsReferral { + test_binrw! { + struct ReqGetDfsReferral { max_referral_level: ReferralLevel::V4, request_file_name: r"\ADC.aviv.local\dfs\Docs".into(), - }; - let mut buf = Vec::new(); - req.write_le(&mut Cursor::new(&mut buf)).unwrap(); - assert_eq!(buf.len() as u32, req.get_bin_size()); - assert_eq!( - buf, - &[ - 0x4, 0x0, 0x5c, 0x0, 0x41, 0x0, 0x44, 0x0, 0x43, 0x0, 0x2e, 0x0, 0x61, 0x0, 0x76, - 0x0, 0x69, 0x0, 0x76, 0x0, 0x2e, 0x0, 0x6c, 0x0, 0x6f, 0x0, 0x63, 0x0, 0x61, 0x0, - 0x6c, 0x0, 0x5c, 0x0, 0x64, 0x0, 0x66, 0x0, 0x73, 0x0, 0x5c, 0x0, 0x44, 0x0, 0x6f, - 0x0, 0x63, 0x0, 0x73, 0x0, 0x0, 0x0 - ] - ); + } => "04005c004100440043002e0061007600690076002e006c006f00630061006c005c006400660073005c0044006f00630073000000" } - #[test] - pub fn test_v4_parses_properly() { - let bytes = [ - 0x30, 0x0, 0x2, 0x0, 0x2, 0x0, 0x0, 0x0, 0x4, 0x0, 0x22, 0x0, 0x0, 0x0, 0x4, 0x0, 0x8, - 0x7, 0x0, 0x0, 0x44, 0x0, 0x76, 0x0, 0xa8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x22, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, - 0x7, 0x0, 0x0, 0x22, 0x0, 0x54, 0x0, 0xa8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5c, 0x0, 0x41, 0x0, 0x44, 0x0, 0x43, 0x0, - 0x2e, 0x0, 0x61, 0x0, 0x76, 0x0, 0x69, 0x0, 0x76, 0x0, 0x2e, 0x0, 0x6c, 0x0, 0x6f, 0x0, - 0x63, 0x0, 0x61, 0x0, 0x6c, 0x0, 0x5c, 0x0, 0x64, 0x0, 0x66, 0x0, 0x73, 0x0, 0x5c, 0x0, - 0x44, 0x0, 0x6f, 0x0, 0x63, 0x0, 0x73, 0x0, 0x0, 0x0, 0x5c, 0x0, 0x41, 0x0, 0x44, 0x0, - 0x43, 0x0, 0x2e, 0x0, 0x61, 0x0, 0x76, 0x0, 0x69, 0x0, 0x76, 0x0, 0x2e, 0x0, 0x6c, 0x0, - 0x6f, 0x0, 0x63, 0x0, 0x61, 0x0, 0x6c, 0x0, 0x5c, 0x0, 0x64, 0x0, 0x66, 0x0, 0x73, 0x0, - 0x5c, 0x0, 0x44, 0x0, 0x6f, 0x0, 0x63, 0x0, 0x73, 0x0, 0x0, 0x0, 0x5c, 0x0, 0x41, 0x0, - 0x44, 0x0, 0x43, 0x0, 0x5c, 0x0, 0x53, 0x0, 0x68, 0x0, 0x61, 0x0, 0x72, 0x0, 0x65, 0x0, - 0x73, 0x0, 0x5c, 0x0, 0x44, 0x0, 0x6f, 0x0, 0x63, 0x0, 0x73, 0x0, 0x0, 0x0, 0x5c, 0x0, - 0x46, 0x0, 0x53, 0x0, 0x52, 0x0, 0x56, 0x0, 0x5c, 0x0, 0x53, 0x0, 0x68, 0x0, 0x61, 0x0, - 0x72, 0x0, 0x65, 0x0, 0x73, 0x0, 0x5c, 0x0, 0x4d, 0x0, 0x79, 0x0, 0x53, 0x0, 0x68, 0x0, - 0x61, 0x0, 0x72, 0x0, 0x65, 0x0, 0x0, 0x0, - ]; - - let r = RespGetDfsReferral::read_le(&mut Cursor::new(&bytes)).unwrap(); - assert_eq!( - r, - RespGetDfsReferral { - path_consumed: 48, - referral_header_flags: ReferralHeaderFlags::new().with_storage_servers(true), - referral_entries: vec![ - ReferralEntry { - value: ReferralEntryValue::V4(ReferralEntryValueV4 { - server_type: DfsServerType::NonRoot, - referral_entry_flags: u16::from_le_bytes( - ReferralEntryFlagsV4::new() - .with_target_set_boundary(true) - .into_bytes() - ), - time_to_live: 1800, - refs: EntryV3V4DfsPaths { - dfs_path: r"\ADC.aviv.local\dfs\Docs".into(), - dfs_alternate_path: r"\ADC.aviv.local\dfs\Docs".into(), - network_address: r"\ADC\Shares\Docs".into() - } - }) - }, - ReferralEntry { - value: ReferralEntryValue::V4(ReferralEntryValueV4 { - server_type: DfsServerType::NonRoot, - referral_entry_flags: 0, - time_to_live: 1800, - refs: EntryV3V4DfsPaths { - dfs_path: r"\ADC.aviv.local\dfs\Docs".into(), - dfs_alternate_path: r"\ADC.aviv.local\dfs\Docs".into(), - network_address: r"\FSRV\Shares\MyShare".into() - } - }) - } - ] - } - ) + test_binrw_read! { + struct RespGetDfsReferral { + path_consumed: 48, + referral_header_flags: ReferralHeaderFlags::new().with_storage_servers(true), + referral_entries: vec![ + ReferralEntry { + value: ReferralEntryValue::V4(ReferralEntryValueV4 { + server_type: DfsServerType::NonRoot, + referral_entry_flags: u16::from_le_bytes( + ReferralEntryFlagsV4::new() + .with_target_set_boundary(true) + .into_bytes() + ), + time_to_live: 1800, + refs: EntryV3V4DfsPaths { + dfs_path: r"\ADC.aviv.local\dfs\Docs".into(), + dfs_alternate_path: r"\ADC.aviv.local\dfs\Docs".into(), + network_address: r"\ADC\Shares\Docs".into() + } + }) + }, + ReferralEntry { + value: ReferralEntryValue::V4(ReferralEntryValueV4 { + server_type: DfsServerType::NonRoot, + referral_entry_flags: 0, + time_to_live: 1800, + refs: EntryV3V4DfsPaths { + dfs_path: r"\ADC.aviv.local\dfs\Docs".into(), + dfs_alternate_path: r"\ADC.aviv.local\dfs\Docs".into(), + network_address: r"\FSRV\Shares\MyShare".into() + } + }) + } + ], + } => "300002000200000004002200000004000807000044007600a8000000000000000000000000000000000004002200000000000807000022005400a + 800000000000000000000000000000000005c004100440043002e0061007600690076002e006c006f00630061006c005c006400660073005c0044006f00 + 6300730000005c004100440043002e0061007600690076002e006c006f00630061006c005c006400660073005c0044006f006300730000005c004100440 + 043005c005300680061007200650073005c0044006f006300730000005c0046005300520056005c005300680061007200650073005c004d007900530068 + 006100720065000000" } } diff --git a/crates/smb-msg/src/echo.rs b/crates/smb-msg/src/echo.rs index 65a0031d..5406560e 100644 --- a/crates/smb-msg/src/echo.rs +++ b/crates/smb-msg/src/echo.rs @@ -17,20 +17,15 @@ pub type EchoResponse = EchoMesasge; #[cfg(test)] mod tests { + use smb_tests::*; + use super::*; - #[test] - pub fn test_echo_req_write() { - let mut cursor = std::io::Cursor::new(Vec::new()); - let echo_req = EchoRequest::default(); - echo_req.write_le(&mut cursor).unwrap(); - assert_eq!(cursor.into_inner(), [0x4, 0x0, 0x0, 0x0]) + test_binrw! { + struct EchoRequest {} => "04000000" } - #[test] - pub fn test_echo_resp_parse() { - let data = [0x4, 0x0, 0x0, 0x0]; - let echo_resp = EchoResponse::read_le(&mut std::io::Cursor::new(&data)).unwrap(); - assert_eq!(echo_resp, EchoResponse::default()); + test_binrw! { + struct EchoResponse {} => "04000000" } } diff --git a/crates/smb-msg/src/encrypted.rs b/crates/smb-msg/src/encrypted.rs index b2d29946..78b04b01 100644 --- a/crates/smb-msg/src/encrypted.rs +++ b/crates/smb-msg/src/encrypted.rs @@ -60,38 +60,27 @@ pub struct EncryptedMessage { #[cfg(test)] mod tests { - use std::io::Cursor; + use smb_tests::*; use super::*; - #[test] - fn test_parse_encrypted_header() { - let header = [ - 0xfdu8, 0x53, 0x4d, 0x42, 0x92, 0x2e, 0xe8, 0xf2, 0xa0, 0x6e, 0x7a, 0xd4, 0x70, 0x22, - 0xd7, 0x1d, 0xb, 0x2, 0x6b, 0x11, 0xa, 0x57, 0x67, 0x55, 0x6d, 0xa0, 0x23, 0x73, 0x1, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x68, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x55, 0x0, - 0x0, 0x24, 0x0, 0x30, 0x0, 0x0, - ]; - assert_eq!( - EncryptedHeader::read(&mut Cursor::new(header)).unwrap(), - EncryptedHeader { - signature: u128::from_le_bytes([ - 0x92, 0x2e, 0xe8, 0xf2, 0xa0, 0x6e, 0x7a, 0xd4, 0x70, 0x22, 0xd7, 0x1d, 0xb, - 0x2, 0x6b, 0x11, - ]), - nonce: [ - 0xa, 0x57, 0x67, 0x55, 0x6d, 0xa0, 0x23, 0x73, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, - ], - original_message_size: 104, - session_id: 0x300024000055 - } - ) + test_binrw! { + EncryptedHeader => e0: EncryptedHeader { + signature: u128::from_le_bytes([ + 0x92, 0x2e, 0xe8, 0xf2, 0xa0, 0x6e, 0x7a, 0xd4, 0x70, 0x22, 0xd7, 0x1d, 0xb, + 0x2, 0x6b, 0x11, + ]), + nonce: [ + 0xa, 0x57, 0x67, 0x55, 0x6d, 0xa0, 0x23, 0x73, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, + ], + original_message_size: 104, + session_id: 0x300024000055, + } => "fd534d42922ee8f2a06e7ad47022d71d0b026b110a5767556da02373010000000000000068000000000001005500002400300000" } - #[test] - fn test_write_encrypted_header() { - let header = EncryptedHeader { + test_binrw! { + EncryptedHeader => e1: EncryptedHeader { signature: u128::from_le_bytes([ 0x2a, 0x45, 0x6c, 0x5d, 0xd0, 0xc3, 0x2d, 0xd4, 0x47, 0x85, 0x21, 0xf7, 0xf6, 0xa8, 0x87, 0x5b, @@ -102,17 +91,6 @@ mod tests { ], original_message_size: 248, session_id: 0x0000300024000055, - }; - let mut buffer = Vec::new(); - header.write(&mut Cursor::new(&mut buffer)).unwrap(); - assert_eq!( - buffer, - [ - 0xfd, 0x53, 0x4d, 0x42, 0x2a, 0x45, 0x6c, 0x5d, 0xd0, 0xc3, 0x2d, 0xd4, 0x47, 0x85, - 0x21, 0xf7, 0xf6, 0xa8, 0x87, 0x5b, 0xbe, 0xe6, 0xbf, 0xe5, 0xa1, 0xe6, 0x7b, 0xb1, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, - 0x55, 0x0, 0x0, 0x24, 0x0, 0x30, 0x0, 0x0 - ] - ); + } => "fd534d422a456c5dd0c32dd4478521f7f6a8875bbee6bfe5a1e67bb10000000000000000f8000000000001005500002400300000" } } diff --git a/crates/smb-msg/src/error.rs b/crates/smb-msg/src/error.rs index b1b0b755..b4a58114 100644 --- a/crates/smb-msg/src/error.rs +++ b/crates/smb-msg/src/error.rs @@ -89,22 +89,7 @@ pub enum ErrorId { mod tests { use crate::*; - use super::*; - - #[test] - pub fn test_simple_error_pasrsed() { - let msg = decode_content(&[ - 0xfe, 0x53, 0x4d, 0x42, 0x40, 0x0, 0x1, 0x0, 0x34, 0x0, 0x0, 0xc0, 0x5, 0x0, 0x1, 0x0, - 0x19, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x71, 0x0, 0x0, 0x28, 0x0, 0x30, 0x0, 0x0, 0xf7, - 0xd, 0xa6, 0x1d, 0x9b, 0x2c, 0x43, 0xd3, 0x26, 0x88, 0x74, 0xf, 0xdf, 0x47, 0x59, 0x24, - 0x9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - ]); - assert_eq!(msg.header.status, Status::ObjectNameNotFound as u32); - let msg = match msg.content { - ResponseContent::Error(msg) => msg, - _ => panic!("Unexpected response"), - }; - assert_eq!(msg, ErrorResponse { error_data: vec![] }) + test_response! { + error_simple, Command::Cancel => Error { error_data: vec![], } => "0900000000000000" } } diff --git a/crates/smb-msg/src/file.rs b/crates/smb-msg/src/file.rs index 363c6e40..5c8bf27e 100644 --- a/crates/smb-msg/src/file.rs +++ b/crates/smb-msg/src/file.rs @@ -10,7 +10,7 @@ use super::header::Header; use smb_dtyp::binrw_util::prelude::*; #[binrw::binrw] -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] pub struct FlushRequest { #[bw(calc = 24)] #[br(assert(_structure_size == 24))] @@ -36,7 +36,7 @@ pub struct FlushResponse { } #[binrw::binrw] -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] pub struct ReadRequest { #[bw(calc = 49)] #[br(assert(_structure_size == 49))] @@ -108,7 +108,7 @@ impl ReadResponse { } #[bitfield] -#[derive(BinWrite, BinRead, Debug, Default, Clone, Copy)] +#[derive(BinWrite, BinRead, Debug, Default, Clone, Copy, PartialEq, Eq)] #[bw(map = |&x| Self::into_bytes(x))] #[br(map = Self::from_bytes)] pub struct ReadFlags { @@ -134,7 +134,7 @@ pub enum CommunicationChannel { /// /// **note:** it is currently assumed that the data is sent immediately after the message. #[binrw::binrw] -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] #[allow(clippy::manual_non_exhaustive)] pub struct WriteRequest { #[bw(calc = 49)] @@ -202,7 +202,7 @@ pub struct WriteResponse { } #[bitfield] -#[derive(BinWrite, BinRead, Debug, Default, Clone, Copy)] +#[derive(BinWrite, BinRead, Debug, Default, Clone, Copy, PartialEq, Eq)] #[bw(map = |&x| Self::into_bytes(x))] #[br(map = Self::from_bytes)] pub struct WriteFlags { @@ -214,44 +214,27 @@ pub struct WriteFlags { #[cfg(test)] mod tests { - use std::io::Cursor; - use crate::*; use super::*; + use smb_tests::*; - #[test] - pub fn test_flush_req_write() { - let mut cursor = Cursor::new(Vec::new()); - FlushRequest { + test_binrw! { + struct FlushRequest { file_id: [ 0x14, 0x04, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x51, 0x00, 0x10, 0x00, 0x0c, 0x00, 0x00, 0x00, ] .into(), - } - .write_le(&mut cursor) - .unwrap(); - assert_eq!( - cursor.into_inner(), - [ - 0x18, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x14, 0x4, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0, - 0x51, 0x0, 0x10, 0x0, 0xc, 0x0, 0x0, 0x0 - ] - ) + } => "1800000000000000140400000c000000510010000c000000" } - #[test] - pub fn test_flush_res_parse() { - let data = [0x4u8, 0, 0, 0, 0, 0, 0, 0]; - let mut cursor = Cursor::new(data); - let resp = FlushResponse::read_le(&mut cursor).unwrap(); - assert_eq!(resp, FlushResponse {}); + test_binrw! { + struct FlushResponse { } => "04 00 00 00" } - #[test] - pub fn test_read_req_write() { - let req = ReadRequest { + test_request! { + Read { flags: ReadFlags::new(), length: 0x10203040, offset: 0x5060708090a0b0c, @@ -261,73 +244,30 @@ mod tests { ] .into(), minimum_count: 1, - }; - let data = encode_content(req.into()); - assert_eq![ - data, - [ - 0x31, 0x0, 0x0, 0x0, 0x40, 0x30, 0x20, 0x10, 0x0c, 0x0b, 0x0a, 0x09, 0x08, 0x07, - 0x06, 0x05, 0x3, 0x3, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0, 0xc5, 0x0, 0x0, 0x0, 0xc, 0x0, - 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, // The famous padding byte. - 0x0 - ] - ] + } => "31000000403020100c0b0a0908070605030300000c000000c50000000c0000000100000000000000000000000000000000" } - #[test] - pub fn test_read_resp_parse() { - let data = [ - 0xfeu8, 0x53, 0x4d, 0x42, 0x40, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x0, 0x1, 0x0, - 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xd4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, - 0xfe, 0x0, 0x0, 0x5, 0x0, 0x0, 0x0, 0x31, 0x0, 0x0, 0x20, 0x0, 0x30, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x11, 0x0, - 0x50, 0x0, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x62, 0x62, - 0x62, 0x62, 0x62, 0x62, - ]; - - let resp = decode_content(&data).content.to_read().unwrap(); - assert_eq!( - resp, - ReadResponse { - buffer: b"bbbbbb".to_vec(), - } - ); + test_response! { + Read { + buffer: b"bbbbbb".to_vec(), + } => "11005000060000000000000000000000626262626262" } - #[test] - pub fn test_write_req_write() { - let data = encode_content( - WriteRequest::new( - 0x1234abcd, - [ - 0x14, 0x04, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x51, 0x00, 0x10, 0x00, 0x0c, - 0x00, 0x00, 0x00, - ] - .into(), - WriteFlags::new(), - "MeFriend!THIS IS FINE!".as_bytes().to_vec().len() as u32, - ) - .into(), - ); - assert_eq!( - data, - [ - 0x31, 0x0, 0x70, 0x0, 0x16, 0x0, 0x0, 0x0, 0xcd, 0xab, 0x34, 0x12, 0x0, 0x0, 0x0, - 0x0, 0x14, 0x4, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0, 0x51, 0x0, 0x10, 0x0, 0xc, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0 + test_request! { + Write { + offset: 0x1234abcd, + file_id: [ + 0x14, 0x04, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x51, 0x00, 0x10, 0x00, 0x0c, 0x00, + 0x00, 0x00, ] - ); + .into(), + flags: WriteFlags::new(), + length: "MeFriend!THIS IS FINE!".as_bytes().to_vec().len() as u32, + _write_offset: (), + } => "3100700016000000cdab341200000000140400000c000000510010000c00000000000000000000000000000000000000" } - #[test] - pub fn test_write_resp_parse() { - let data = [ - 0x11u8, 0x0, 0x0, 0x0, 0xaf, 0xba, 0xef, 0xbe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - ]; - let mut cursor = Cursor::new(data); - let resp = WriteResponse::read_le(&mut cursor).unwrap(); - assert_eq!(resp, WriteResponse { count: 0xbeefbaaf }); + test_binrw! { + struct WriteResponse { count: 0xbeefbaaf, } => "11000000afbaefbe0000000000000000" } } diff --git a/crates/smb-msg/src/header.rs b/crates/smb-msg/src/header.rs index 7cde620b..eeb961ea 100644 --- a/crates/smb-msg/src/header.rs +++ b/crates/smb-msg/src/header.rs @@ -237,40 +237,28 @@ pub struct HeaderFlags { #[cfg(test)] mod tests { + use smb_tests::*; + use super::*; - use std::io::Cursor; - #[test] - pub fn test_async_header_parse() { - let arr = &[ - 0xfe, 0x53, 0x4d, 0x42, 0x40, 0x0, 0x0, 0x0, 0x3, 0x1, 0x0, 0x0, 0xf, 0x0, 0x1, 0x0, - 0x13, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xd7, 0x27, 0x53, 0x8, 0x0, 0x0, 0x0, 0x0, 0x63, - 0xf8, 0x25, 0xde, 0xae, 0x2, 0x95, 0x2f, 0xa3, 0xd8, 0xc8, 0xaa, 0xf4, 0x6e, 0x7c, - 0x99, - ]; - let mut cursor = Cursor::new(arr); - let header = Header::read_le(&mut cursor).unwrap(); - assert_eq!( - header, - Header { - credit_charge: 0, - status: Status::Pending as u32, - command: Command::ChangeNotify, - credit_request: 1, - flags: HeaderFlags::new() - .with_async_command(true) - .with_server_to_redir(true) - .with_priority_mask(1), - next_command: 0, - message_id: 8, - tree_id: None, - async_id: Some(8), - session_id: 0x00000000085327d7, - signature: u128::from_le_bytes(u128::to_be_bytes( - 0x63f825deae02952fa3d8c8aaf46e7c99 - )), - } - ) + test_binrw! { + Header => async: Header { + credit_charge: 0, + status: Status::Pending as u32, + command: Command::ChangeNotify, + credit_request: 1, + flags: HeaderFlags::new() + .with_async_command(true) + .with_server_to_redir(true) + .with_priority_mask(1), + next_command: 0, + message_id: 8, + tree_id: None, + async_id: Some(8), + session_id: 0x00000000085327d7, + signature: u128::from_le_bytes(u128::to_be_bytes( + 0x63f825deae02952fa3d8c8aaf46e7c99 + )), + } => "fe534d4240000000030100000f000100130000000000000008000000000000000800000000000000d72753080000000063f825deae02952fa3d8c8aaf46e7c99" } } diff --git a/crates/smb-msg/src/info/common.rs b/crates/smb-msg/src/info/common.rs index b6c44593..b37aa80c 100644 --- a/crates/smb-msg/src/info/common.rs +++ b/crates/smb-msg/src/info/common.rs @@ -55,7 +55,7 @@ macro_rules! query_info_data { #[doc = concat!("Enum to hold the different info types for ", stringify!($name), ", that are used within SMB requests for querying or setting information.")] #[binrw::binrw] - #[derive(Debug)] + #[derive(Debug, PartialEq, Eq)] #[brw(little)] #[br(import(info_type: InfoType))] pub enum $name { @@ -99,7 +99,7 @@ macro_rules! query_info_data { } } - /// Content to enum conversions: + // Content to enum conversions: $( impl From<$content> for $name { fn from(value: $content) -> Self { @@ -108,9 +108,8 @@ macro_rules! query_info_data { } )+ - // All same for filesystem: #[binrw::binrw] - #[derive(Debug)] + #[derive(Debug, PartialEq, Eq)] pub struct [] where T: Sized, diff --git a/crates/smb-msg/src/info/query.rs b/crates/smb-msg/src/info/query.rs index c95b4007..c28b5b30 100644 --- a/crates/smb-msg/src/info/query.rs +++ b/crates/smb-msg/src/info/query.rs @@ -11,7 +11,7 @@ use super::common::*; use smb_fscc::*; #[binrw::binrw] -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] pub struct QueryInfoRequest { #[bw(calc = 41)] #[br(assert(_structure_size == 41))] @@ -96,7 +96,7 @@ pub struct QueryInfoFlags { /// when asking for information about Quota or Extended Attributes. /// In other cases, it is empty. #[binrw::binrw] -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] #[brw(import(file_info_class: &QueryInfoClass, query_info_type: InfoType))] pub enum GetInfoRequestData { /// The query quota to perform. @@ -115,7 +115,7 @@ pub enum GetInfoRequestData { } #[binrw::binrw] -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] pub struct QueryQuotaInfo { pub return_single: Boolean, pub restart_scan: Boolean, @@ -175,7 +175,7 @@ impl QueryQuotaInfo { } } -#[derive(BinRead, BinWrite, Debug)] +#[derive(BinRead, BinWrite, Debug, PartialEq, Eq)] pub struct GetEaInfoList { pub values: ChainedItemList, } @@ -246,37 +246,26 @@ mod tests { use super::*; - #[test] - pub fn test_query_info_req_short_write() { - let data = encode_content( - QueryInfoRequest { - info_type: InfoType::File, - info_class: QueryInfoClass::File(QueryFileInfoClass::NetworkOpenInformation), - output_buffer_length: 56, - additional_info: AdditionalInfo::new(), - flags: QueryInfoFlags::new(), - file_id: [ - 0x77, 0x5, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0, 0xc5, 0x0, 0x10, 0x0, 0xc, 0x0, 0x0, - 0x0, - ] - .into(), - data: GetInfoRequestData::None(()), - } - .into(), - ); - assert_eq!( - data, - [ - 0x29, 0x0, 0x1, 0x22, 0x38, 0x0, 0x0, 0x0, 0x68, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x77, 0x5, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0, - 0xc5, 0x0, 0x10, 0x0, 0xc, 0x0, 0x0, 0x0 + const QUERY_INFO_HEADER_DATA: &'static str = ""; + + test_request! { + query_info_basic: QueryInfo { + info_type: InfoType::File, + info_class: QueryInfoClass::File(QueryFileInfoClass::NetworkOpenInformation), + output_buffer_length: 56, + additional_info: AdditionalInfo::new(), + flags: QueryInfoFlags::new(), + file_id: [ + 0x77, 0x5, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0, 0xc5, 0x0, 0x10, 0x0, 0xc, 0x0, 0x0, + 0x0, ] - ) + .into(), + data: GetInfoRequestData::None(()), + } => const_format::concatcp!(QUERY_INFO_HEADER_DATA, "290001223800000068000000000000000000000000000000770500000c000000c50010000c000000") } - #[test] - pub fn test_query_info_ea_request() { - let req = QueryInfoRequest { + test_request! { + query_info_get_ea: QueryInfo { info_type: InfoType::File, info_class: QueryInfoClass::File(QueryFileInfoClass::FullEaInformation), additional_info: AdditionalInfo::new(), @@ -291,73 +280,35 @@ mod tests { values: vec![FileGetEaInformation::new("$MpEa_D262AC624451295")].into(), }), output_buffer_length: 554, - }; - let content_data = encode_content(req.into()); - assert_eq!( - content_data, - [ - 0x29, 0x0, 0x1, 0xf, 0x2a, 0x2, 0x0, 0x0, 0x68, 0x0, 0x0, 0x0, 0x1b, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x7a, 0x5, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0, - 0xd1, 0x0, 0x10, 0x0, 0xc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x15, 0x24, 0x4d, - 0x70, 0x45, 0x61, 0x5f, 0x44, 0x32, 0x36, 0x32, 0x41, 0x43, 0x36, 0x32, 0x34, 0x34, - 0x35, 0x31, 0x32, 0x39, 0x35, 0x0 - ] - ) + } => const_format::concatcp!(QUERY_INFO_HEADER_DATA, "2900010f2a020000680000001b00000000000000030000007a0500000c000000d10010000c0000000000000015244d7045615f44323632414336323434353132393500") } - #[test] - pub fn test_query_security_request() { - let res = encode_content( - QueryInfoRequest { - info_type: InfoType::Security, - info_class: Default::default(), - output_buffer_length: 0, - additional_info: AdditionalInfo::new() - .with_owner_security_information(true) - .with_group_security_information(true) - .with_dacl_security_information(true) - .with_sacl_security_information(true), - flags: QueryInfoFlags::new(), - file_id: make_guid!("0000002b-000d-0000-3100-00000d000000").into(), - data: GetInfoRequestData::None(()), - } - .into(), - ); - assert_eq!( - res, - &[ - 0x29, 0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x68, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0xf, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2b, 0x0, 0x0, 0x0, 0xd, 0x0, 0x0, 0x0, - 0x31, 0x0, 0x0, 0x0, 0xd, 0x0, 0x0, 0x0, - ] - ); + test_request! { + query_security: QueryInfo { + info_type: InfoType::Security, + info_class: Default::default(), + output_buffer_length: 0, + additional_info: AdditionalInfo::new() + .with_owner_security_information(true) + .with_group_security_information(true) + .with_dacl_security_information(true) + .with_sacl_security_information(true), + flags: QueryInfoFlags::new(), + file_id: make_guid!("0000002b-000d-0000-3100-00000d000000").into(), + data: GetInfoRequestData::None(()), + } => const_format::concatcp!(QUERY_INFO_HEADER_DATA, "290003000000000068000000000000000f000000000000002b0000000d000000310000000d000000") } - #[test] - pub fn test_query_info_resp_parse_basic() { - let parsed = decode_content(&[ - 0xfe, 0x53, 0x4d, 0x42, 0x40, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x0, 0x1, 0x0, - 0x19, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x69, 0x0, 0x0, 0x28, 0x0, 0x30, 0x0, 0x0, 0x77, - 0x53, 0x6f, 0xb5, 0x9c, 0x21, 0xa4, 0xcd, 0x99, 0x9b, 0xc0, 0x87, 0xb9, 0x6, 0x83, - 0xa3, 0x9, 0x0, 0x48, 0x0, 0x28, 0x0, 0x0, 0x0, 0x5b, 0x6c, 0x44, 0xce, 0x6a, 0x58, - 0xdb, 0x1, 0x4, 0x8f, 0xa1, 0xd, 0x51, 0x6b, 0xdb, 0x1, 0x4, 0x8f, 0xa1, 0xd, 0x51, - 0x6b, 0xdb, 0x1, 0x4, 0x8f, 0xa1, 0xd, 0x51, 0x6b, 0xdb, 0x1, 0x20, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, - ]); - let parsed = parsed.content.to_queryinfo().unwrap(); - assert_eq!( - parsed, - QueryInfoResponse { - data: [ - 0x5b, 0x6c, 0x44, 0xce, 0x6a, 0x58, 0xdb, 0x1, 0x4, 0x8f, 0xa1, 0xd, 0x51, - 0x6b, 0xdb, 0x1, 0x4, 0x8f, 0xa1, 0xd, 0x51, 0x6b, 0xdb, 0x1, 0x4, 0x8f, 0xa1, - 0xd, 0x51, 0x6b, 0xdb, 0x1, 0x20, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - ] - .to_vec() - .into(), - } - ); + test_response! { + QueryInfo { + data: [ + 0x5b, 0x6c, 0x44, 0xce, 0x6a, 0x58, 0xdb, 0x1, 0x4, 0x8f, 0xa1, 0xd, 0x51, + 0x6b, 0xdb, 0x1, 0x4, 0x8f, 0xa1, 0xd, 0x51, 0x6b, 0xdb, 0x1, 0x4, 0x8f, 0xa1, + 0xd, 0x51, 0x6b, 0xdb, 0x1, 0x20, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + ] + .to_vec() + .into() + } => "09004800280000005b6c44ce6a58db01048fa10d516bdb01048fa10d516bdb01048fa10d516bdb012000000000000000" } #[test] diff --git a/crates/smb-msg/src/info/set.rs b/crates/smb-msg/src/info/set.rs index 5d9f947a..c526277d 100644 --- a/crates/smb-msg/src/info/set.rs +++ b/crates/smb-msg/src/info/set.rs @@ -9,7 +9,7 @@ use smb_dtyp::{SecurityDescriptor, binrw_util::prelude::*}; use smb_fscc::*; #[binrw::binrw] -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] pub struct SetInfoRequest { #[bw(calc = 33)] #[br(assert(_structure_size == 33))] @@ -103,39 +103,22 @@ mod tests { use super::*; use crate::*; use smb_dtyp::*; + use smb_tests::*; - #[test] - fn test_set_info_request_write() { - let set_info = SetFileInfo::RenameInformation(FileRenameInformation { - replace_if_exists: false.into(), - root_directory: 0, - file_name: "hello\\myNewFile.txt".into(), - }); - - let cls = set_info.class(); - let req = SetInfoData::from(RawSetInfoData::::from(set_info)).to_req( - cls.into(), - make_guid!("00000042-000e-0000-0500-10000e000000").into(), - AdditionalInfo::new(), - ); - let req_data = encode_content(req.into()); - assert_eq!( - req_data, - [ - 0x21, 0x0, 0x1, 0xa, 0x3a, 0x0, 0x0, 0x0, 0x60, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x42, 0x0, 0x0, 0x0, 0xe, 0x0, 0x0, 0x0, 0x5, 0x0, 0x10, 0x0, 0xe, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x26, 0x0, 0x0, 0x0, 0x68, 0x0, 0x65, 0x0, 0x6c, 0x0, 0x6c, 0x0, 0x6f, 0x0, 0x5c, - 0x0, 0x6d, 0x0, 0x79, 0x0, 0x4e, 0x0, 0x65, 0x0, 0x77, 0x0, 0x46, 0x0, 0x69, 0x0, - 0x6c, 0x0, 0x65, 0x0, 0x2e, 0x0, 0x74, 0x0, 0x78, 0x0, 0x74, 0x0 - ] - ); + test_request! { + SetInfo { + info_class: SetInfoClass::File(SetFileInfoClass::RenameInformation), + data: SetInfoData::from(RawSetInfoData::from(SetFileInfo::RenameInformation(FileRenameInformation { + replace_if_exists: false.into(), + root_directory: 0, + file_name: "hello\\myNewFile.txt".into(), + }))), + file_id: make_guid!("00000042-000e-0000-0500-10000e000000").into(), + additional_information: AdditionalInfo::new(), + } => "2100010a3a0000006000000000000000420000000e000000050010000e0000000000000000000000000000000000000026000000680065006c006c006f005c006d0079004e0065007700460069006c0065002e00740078007400" } - #[test] - fn test_set_info_response_parse() { - let data = [0x2, 0x0, 0x0, 0x0]; - let response = SetInfoResponse::read_le(&mut std::io::Cursor::new(&data)).unwrap(); - assert_eq!(response, SetInfoResponse {}); + test_binrw! { + struct SetInfoResponse {} => "0200" } } diff --git a/crates/smb-msg/src/ioctl/common.rs b/crates/smb-msg/src/ioctl/common.rs index 1f6ea2ed..69b3f40f 100644 --- a/crates/smb-msg/src/ioctl/common.rs +++ b/crates/smb-msg/src/ioctl/common.rs @@ -13,6 +13,8 @@ impl IoctlRequestContent for () { } } +/// Utility structure to represent inner value of Ioctl requests +/// that have no defined struct (i.e. they are treated as raw byte buffers). #[binrw::binrw] #[derive(Debug, PartialEq, Eq)] pub struct IoctlBuffer { diff --git a/crates/smb-msg/src/ioctl/fsctl.rs b/crates/smb-msg/src/ioctl/fsctl.rs index cb8a7478..17602651 100644 --- a/crates/smb-msg/src/ioctl/fsctl.rs +++ b/crates/smb-msg/src/ioctl/fsctl.rs @@ -519,7 +519,8 @@ pub struct SrvEnumerateSnapshotsResponse { snap_shot_array_size: PosMarker, /// A list of snapshots, described as strings, that take on the following form: @GMT-YYYY.MM.DD-HH.MM.SS #[br(map_stream = |s| s.take_seek(snap_shot_array_size.value as u64))] - pub snap_shots: MultiSz, + #[bw(write_with = PosMarker::write_size, args(&snap_shot_array_size))] + pub snap_shots: MultiWSz, } impl_fsctl_response!(SrvEnumerateSnapshots, SrvEnumerateSnapshotsResponse); @@ -671,171 +672,81 @@ make_res_newtype!( #[cfg(test)] mod tests { use super::*; + use smb_tests::*; - #[test] - fn test_fsctl_request_offload_write() { - let mut cursor = std::io::Cursor::new(Vec::new()); - let req = OffloadReadRequest { + test_binrw! { + struct OffloadReadRequest { flags: 0, token_time_to_live: 0, file_offset: 0, copy_length: 10485760, - }; - req.write_le(&mut cursor).unwrap(); - assert_eq!(cursor.position(), req.get_bin_size() as u64); - assert_eq!( - cursor.into_inner(), - [ - 0x20, 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, 0xa0, 0x0, 0x0, 0x0, 0x0, 0x0 - ] - ); + } => "2000000000000000000000000000000000000000000000000000a00000000000" } - #[test] - fn test_fsctl_request_resumekey_read() { - let data = [ - 0x2d, 0x3, 0x0, 0x0, 0x1c, 0x0, 0x0, 0x0, 0x27, 0x11, 0x6a, 0x26, 0x30, 0xd2, 0xdb, - 0x1, 0xff, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - ]; - let mut cursor = std::io::Cursor::new(data); - let req: SrvRequestResumeKey = SrvRequestResumeKey::read_le(&mut cursor).unwrap(); - assert_eq!( - req, - SrvRequestResumeKey { - resume_key: [ - 0x2d, 0x3, 0x0, 0x0, 0x1c, 0x0, 0x0, 0x0, 0x27, 0x11, 0x6a, 0x26, 0x30, 0xd2, - 0xdb, 0x1, 0xff, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 - ], - context: vec![], - } - ); + test_binrw! { + struct SrvRequestResumeKey { + resume_key: [ + 0x2d, 0x3, 0x0, 0x0, 0x1c, 0x0, 0x0, 0x0, 0x27, 0x11, 0x6a, 0x26, 0x30, 0xd2, + 0xdb, 0x1, 0xff, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 + ], + context: vec![], + } => "2d0300001c00000027116a2630d2db01fffe00000000000000000000" } - #[test] - fn test_fsctl_copychunk_write() { - let mut chunks = vec![]; - const CHUNK_SIZE: u32 = 1048576; // 1 MiB - const TOTAL_SIZE: u32 = 10417096; - let block_num = u32::div_ceil(TOTAL_SIZE, CHUNK_SIZE); - for i in 0..block_num { - chunks.push(SrvCopychunkItem { + const CHUNK_SIZE: u32 = 1 << 20; // 1 MiB + const TOTAL_SIZE: u32 = 10417096; + const BLOCK_NUM: u32 = (TOTAL_SIZE + CHUNK_SIZE - 1) / CHUNK_SIZE; + + test_binrw! { + struct SrvCopychunkCopy { + source_key: [ + 0x2d, 0x3, 0x0, 0x0, 0x1c, 0x0, 0x0, 0x0, 0x27, 0x11, 0x6a, 0x26, 0x30, 0xd2, 0xdb, + 0x1, 0xff, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + ], + chunks: (0..BLOCK_NUM).map(|i| SrvCopychunkItem { source_offset: (i * CHUNK_SIZE) as u64, target_offset: (i * CHUNK_SIZE) as u64, - length: if i == block_num - 1 { + length: if i == BLOCK_NUM - 1 { TOTAL_SIZE % CHUNK_SIZE } else { CHUNK_SIZE }, - }); - } - let req = SrvCopychunkCopy { - source_key: [ - 0x2d, 0x3, 0x0, 0x0, 0x1c, 0x0, 0x0, 0x0, 0x27, 0x11, 0x6a, 0x26, 0x30, 0xd2, 0xdb, - 0x1, 0xff, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - ], - chunks, - }; - let mut cursor = std::io::Cursor::new(Vec::new()); - req.write_le(&mut cursor).unwrap(); - assert_eq!(cursor.position(), req.get_bin_size() as u64); - assert_eq!( - cursor.into_inner(), - [ - 0x2d, 0x3, 0x0, 0x0, 0x1c, 0x0, 0x0, 0x0, 0x27, 0x11, 0x6a, 0x26, 0x30, 0xd2, 0xdb, - 0x1, 0xff, 0xfe, 0x0, 0x0, 0x0, 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, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x20, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x30, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x30, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x50, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x50, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x60, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x60, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x70, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x70, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x90, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x90, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc8, 0xf3, 0xe, 0x0, 0x0, 0x0, 0x0, - 0x0 - ] - ) + }).collect(), + } => "2d0300001c00000027116a2630d2db01fffe0000000000000a000000000000000 + 00000000000000000000000000000000000100000000000000010000000000000001000 + 00000000000010000000000000002000000000000000200000000000000010000000000 + 00000300000000000000030000000000000001000000000000000400000000000000040 + 00000000000000100000000000000050000000000000005000000000000000100000000 + 00000006000000000000000600000000000000010000000000000007000000000000000 + 70000000000000001000000000000000800000000000000080000000000000001000000 + 0000000009000000000000000900000000000c8f30e0000000000" } - #[test] - fn test_fsctl_copychunk_reponse_read() { - let data = [ - 0xa, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc8, 0xf3, 0x9e, 0x0, - ]; - let mut cursor = std::io::Cursor::new(data); - let res: SrvCopychunkResponse = SrvCopychunkResponse::read_le(&mut cursor).unwrap(); - assert_eq!( - res, - SrvCopychunkResponse { - chunks_written: 10, - chunk_bytes_written: 0, - total_bytes_written: 10417096, - } - ); + test_binrw! { + struct SrvCopychunkResponse { + chunks_written: 10, + chunk_bytes_written: 0, + total_bytes_written: 10417096, + } => "0a00000000000000c8f39e00" } - #[test] - fn test_fsctl_query_alloc_ranges_resp() { - let data = [ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd1, 0xb6, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - ]; - - let mut cursor = std::io::Cursor::new(data); - let res = QueryAllocRangesResult::read_le(&mut cursor).unwrap(); - assert_eq!( - res, - QueryAllocRangesResult { - values: vec![ - QueryAllocRangesItem { - offset: 0, - len: 4096, - }, - QueryAllocRangesItem { - offset: 8192, - len: 46801, - }, - ], - } - ); + test_binrw! { + struct QueryAllocRangesResult { + values: vec![ + QueryAllocRangesItem { + offset: 0, + len: 4096, + }, + QueryAllocRangesItem { + offset: 8192, + len: 46801, + }, + ], + } => "000000000000000000100000000000000020000000000000d1b6000000000000" } - #[test] - fn test_fsctl_query_network_interfaces_response_parse() { - let data = [ - 0x98, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0xca, 0x9a, 0x3b, 0x0, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0xac, 0x10, 0xcc, 0x84, 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, - 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, 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, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0xca, 0x9a, 0x3b, 0x0, 0x0, 0x0, 0x0, 0x17, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xfe, - 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xc, 0x29, 0xff, 0xfe, 0x9f, 0x8b, 0xf3, 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, - 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, 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, - ]; - - let mut cursor = std::io::Cursor::new(data); - let res = NetworkInterfacesInfo::read_le(&mut cursor).unwrap(); - assert_eq!( - NetworkInterfacesInfo::from(vec![ + test_binrw! { + NetworkInterfacesInfo: NetworkInterfacesInfo::from(vec![ NetworkInterfaceInfo { if_index: 2, capability: NetworkInterfaceCapability::new().with_rdma(true), @@ -856,8 +767,15 @@ mod tests { scope_id: 0, }) }, - ]), - res - ); + ]) => "9800000002000000020000000000000000ca9a3b0000000002000000ac10cc8400000000000000 + 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000020000000200000000000 + 00000ca9a3b000000001700000000000000fe80000000000000020c29fffe9f8bf3000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + 00000000000000000000" } + + // TODO(TEST): Add missing tests. Consider testing size calc as well. } diff --git a/crates/smb-msg/src/ioctl/msg.rs b/crates/smb-msg/src/ioctl/msg.rs index a9ef0089..4f2b25f1 100644 --- a/crates/smb-msg/src/ioctl/msg.rs +++ b/crates/smb-msg/src/ioctl/msg.rs @@ -142,7 +142,7 @@ pub struct IoctlResponse { pub file_id: FileId, #[bw(calc = PosMarker::default())] input_offset: PosMarker, - #[bw(assert(out_buffer.is_empty()))] // there is an exception for pass-through operations. + #[bw(assert(in_buffer.is_empty()))] // there is an exception for pass-through operations. #[bw(try_calc = in_buffer.len().try_into())] #[br(assert(input_count == 0))] input_count: u32, @@ -163,10 +163,12 @@ pub struct IoctlResponse { #[br(seek_before = SeekFrom::Start(input_offset.value.into()))] #[br(count = input_count)] + #[bw(write_with = PosMarker::write_aoff, args(&input_offset))] pub in_buffer: Vec, #[br(seek_before = SeekFrom::Start(output_offset.value.into()))] #[br(count = output_count)] + #[bw(write_with = PosMarker::write_aoff, args(&output_offset))] pub out_buffer: Vec, } @@ -186,15 +188,17 @@ impl IoctlResponse { #[cfg(test)] mod tests { + use smb_tests::*; + use crate::*; use super::*; - #[test] - pub fn test_ioctl_req_write() { - let encoded = encode_content( - IoctlRequest { - ctl_code: FsctlCodes::PipeTransceive as u32, + const REQ_IOCTL_BUFFER_CONTENT: &'static str = "0500000310000000980000000300000080000000010039000000000013f8a58f166fb54482c28f2dae140df50000000001000000000000000000020000000000010000000000000000000200000000000500000000000000010500000000000515000000173da72e955653f915dff280e9030000000000000000000000000000000000000000000001000000000000000000000002000000"; + + test_request! { + Ioctl { + ctl_code: FsctlCodes::PipeTransceive as u32, file_id: [ 0x28, 0x5, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0, 0x85, 0x0, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0, @@ -204,81 +208,18 @@ mod tests { max_output_response: 1024, flags: IoctlRequestFlags::new().with_is_fsctl(true), buffer: IoctlReqData::FsctlPipeTransceive( - Into::::into( - [ - 0x5, 0x0, 0x0, 0x3, 0x10, 0x0, 0x0, 0x0, 0x98, 0x0, 0x0, 0x0, 0x3, 0x0, - 0x0, 0x0, 0x80, 0x0, 0x0, 0x0, 0x1, 0x0, 0x39, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x13, 0xf8, 0xa5, 0x8f, 0x16, 0x6f, 0xb5, 0x44, 0x82, 0xc2, 0x8f, 0x2d, - 0xae, 0x14, 0xd, 0xf5, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x5, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x5, 0x15, 0x0, 0x0, 0x0, 0x17, 0x3d, 0xa7, 0x2e, 0x95, 0x56, 0x53, - 0xf9, 0x15, 0xdf, 0xf2, 0x80, 0xe9, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x2, 0x0, 0x0, 0x0, - ] - .as_ref(), - ) - .into(), + IoctlBuffer::from( + hex_to_u8_array! {REQ_IOCTL_BUFFER_CONTENT} + ).into(), ), - } - .into(), - ); - assert_eq!( - encoded, - &[ - 0x39, 0x0, 0x0, 0x0, 0x17, 0xc0, 0x11, 0x0, 0x28, 0x5, 0x0, 0x0, 0xc, 0x0, 0x0, - 0x0, 0x85, 0x0, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0, 0x78, 0x0, 0x0, 0x0, 0x98, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, - 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5, 0x0, 0x0, 0x3, 0x10, 0x0, 0x0, - 0x0, 0x98, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x80, 0x0, 0x0, 0x0, 0x1, 0x0, 0x39, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x13, 0xf8, 0xa5, 0x8f, 0x16, 0x6f, 0xb5, 0x44, 0x82, - 0xc2, 0x8f, 0x2d, 0xae, 0x14, 0xd, 0xf5, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x1, 0x5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5, 0x15, 0x0, 0x0, 0x0, - 0x17, 0x3d, 0xa7, 0x2e, 0x95, 0x56, 0x53, 0xf9, 0x15, 0xdf, 0xf2, 0x80, 0xe9, 0x3, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x2, 0x0, 0x0, 0x0 - ] - ) + } => const_format::concatcp!("3900000017c01100280500000c000000850000000c0000007800000098000000000000000000000000000000000400000100000000000000", REQ_IOCTL_BUFFER_CONTENT) } - #[test] - pub fn test_ioctl_res_parse() { - let data = [ - 0xfe, 0x53, 0x4d, 0x42, 0x40, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0xb, 0x0, 0x1, 0x0, - 0x31, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, - 0xfe, 0x0, 0x0, 0x5, 0x0, 0x0, 0x0, 0x31, 0x0, 0x0, 0x28, 0x0, 0x30, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x31, 0x0, - 0x0, 0x0, 0x17, 0xc0, 0x11, 0x0, 0x28, 0x5, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0, 0x85, 0x0, - 0x0, 0x0, 0xc, 0x0, 0x0, 0x0, 0x70, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x70, 0x0, 0x0, - 0x0, 0x4, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5, 0x0, 0x2, 0x3, - 0x10, 0x0, 0x0, 0x0, 0x4, 0x1, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0xec, 0x0, 0x0, 0x0, 0x1, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc, 0x0, 0xe, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x41, 0x0, 0x56, 0x0, 0x49, 0x0, 0x56, 0x0, 0x56, 0x0, - 0x4d, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x4, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x5, 0x15, 0x0, 0x0, 0x0, 0x17, 0x3d, 0xa7, 0x2e, 0x95, 0x56, 0x53, - 0xf9, 0x15, 0xdf, 0xf2, 0x80, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0xa, 0x0, 0xc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x61, 0x0, 0x76, 0x0, 0x69, 0x0, 0x76, 0x0, 0x6e, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, - ]; - let message = decode_content(&data); - let message = message.content.to_ioctl().unwrap(); - assert_eq!( - message, - IoctlResponse { + // Just to make things pretty; do NOT edit. + const IOCTL_TEST_BUFFER_CONTENT: &'static str = "05000203100000000401000003000000ec00000001000000000002000000000001000000000000000000020000000000200000000000000001000000000000000c000e000000000000000200000000000000020000000000070000000000000000000000000000000600000000000000410056004900560056004d00000000000400000000000000010400000000000515000000173da72e955653f915dff28001000000000000000000020000000000010000000000000001000000000000000a000c00000000000000020000000000000000000000000006000000000000000000000000000000050000000000000061007600690076006e0000000100000000000000"; + + test_response! { + Ioctl { ctl_code: FsctlCodes::PipeTransceive as u32, file_id: [ 0x28, 0x5, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0, 0x85, 0x0, 0x0, 0x0, 0xc, 0x0, 0x0, @@ -286,27 +227,7 @@ mod tests { ] .into(), in_buffer: vec![], - out_buffer: [ - 0x5, 0x0, 0x2, 0x3, 0x10, 0x0, 0x0, 0x0, 0x4, 0x1, 0x0, 0x0, 0x3, 0x0, 0x0, - 0x0, 0xec, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x20, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0xc, 0x0, 0xe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x41, 0x0, 0x56, 0x0, 0x49, 0x0, 0x56, 0x0, 0x56, 0x0, 0x4d, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x4, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x5, 0x15, 0x0, 0x0, 0x0, 0x17, 0x3d, 0xa7, 0x2e, 0x95, - 0x56, 0x53, 0xf9, 0x15, 0xdf, 0xf2, 0x80, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, 0x0, 0xc, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x61, 0x0, 0x76, 0x0, 0x69, 0x0, - 0x76, 0x0, 0x6e, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 - ] - .to_vec(), - } - ); + out_buffer: smb_tests::hex_to_u8_array! {IOCTL_TEST_BUFFER_CONTENT}, + } => const_format::concatcp!("3100000017c01100280500000c000000850000000c000000700000000000000070000000040100000000000000000000",IOCTL_TEST_BUFFER_CONTENT) } } diff --git a/crates/smb-msg/src/message.rs b/crates/smb-msg/src/message.rs index bae7b6c5..6a08e364 100644 --- a/crates/smb-msg/src/message.rs +++ b/crates/smb-msg/src/message.rs @@ -1,22 +1,33 @@ -use super::compressed::*; -use super::encrypted::*; -use super::plain::*; use binrw::prelude::*; macro_rules! make_message { - ($name:ident, $derive_attr:ty, $plain_type:ty) => { - #[derive($derive_attr, Debug)] + ($name:ident, $binrw_type:ident, $plain_type:ty) => { + #[binrw::$binrw_type] + #[derive(Debug)] #[brw(little)] pub enum $name { Plain($plain_type), - Encrypted(EncryptedMessage), - Compressed(CompressedMessage), + Encrypted($crate::EncryptedMessage), + Compressed($crate::CompressedMessage), } }; } -make_message!(Request, BinWrite, PlainRequest); -make_message!(Response, BinRead, PlainResponse); +macro_rules! make_messages { + ($req_type:ident, $res_type:ident) => { + make_message!(Request, $req_type, $crate::PlainRequest); + make_message!(Response, $res_type, $crate::PlainResponse); + }; +} + +#[cfg(all(feature = "client", not(feature = "server")))] +make_messages!(binwrite, binread); + +#[cfg(all(feature = "server", not(feature = "client")))] +make_messages!(binread, binwrite); + +#[cfg(all(feature = "server", feature = "client"))] +make_messages!(binrw, binrw); impl TryFrom<&[u8]> for Response { type Error = binrw::Error; diff --git a/crates/smb-msg/src/negotiate.rs b/crates/smb-msg/src/negotiate.rs index d4a3210c..54a6d5f3 100644 --- a/crates/smb-msg/src/negotiate.rs +++ b/crates/smb-msg/src/negotiate.rs @@ -5,7 +5,7 @@ use modular_bitfield::prelude::*; use smb_dtyp::{binrw_util::prelude::*, guid::Guid}; #[binrw::binrw] -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] pub struct NegotiateRequest { #[bw(calc = 0x24)] #[br(assert(_structure_size == 0x24))] @@ -219,6 +219,16 @@ impl TryFrom for Dialect { } } +/// Represent a single negotiation context item. +/// +/// Note: This struct should usually be NOT used directly. +/// To construct it, use `impl From for NegotiateContext`: +/// ``` +/// # use smb_msg::*; +/// let signing_ctx: NegotiateContext = SigningCapabilities { +/// signing_algorithms: vec![SigningAlgorithmId::AesGmac] +/// }.into(); +/// ``` #[binrw::binrw] #[derive(Debug, PartialEq, Eq)] pub struct NegotiateContext { @@ -266,6 +276,17 @@ impl NegotiateContextValue { } } } + +$( + impl From<$name> for NegotiateContext { + fn from(val: $name) -> Self { + NegotiateContext { + context_type: NegotiateContextType::$name, + data: NegotiateContextValue::$name(val), + } + } + } +)+ }; } @@ -279,15 +300,6 @@ negotiate_context_type!( SigningCapabilities = 0x0008, ); -impl From for NegotiateContext { - fn from(val: NegotiateContextValue) -> Self { - NegotiateContext { - context_type: val.get_matching_type(), - data: val, - } - } -} - // u16 enum hash algorithms binrw 0x01 is sha512. #[derive(BinRead, BinWrite, Debug, PartialEq, Eq, Clone, Copy)] #[brw(repr(u16))] @@ -448,98 +460,129 @@ pub enum SigningAlgorithmId { #[cfg(test)] mod tests { + use smb_dtyp::make_guid; + use smb_tests::hex_to_u8_array; use time::macros::datetime; use super::*; use crate::*; - #[test] - pub fn test_negotiate_res_parse() { - let data = [ - 0xfe, 0x53, 0x4d, 0x42, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, - 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, - 0xfe, 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, 0x41, 0x0, 0x1, - 0x0, 0x11, 0x3, 0x5, 0x0, 0xb9, 0x21, 0xf8, 0xe0, 0x15, 0x7, 0xaa, 0x41, 0xbe, 0x38, - 0x67, 0xfe, 0xbf, 0x5e, 0x2e, 0x11, 0x2f, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80, 0x0, 0x0, 0x0, - 0x80, 0x0, 0x0, 0x0, 0x80, 0x0, 0xa8, 0x76, 0xd8, 0x78, 0xc5, 0x69, 0xdb, 0x1, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80, 0x0, 0x2a, 0x0, 0xb0, 0x0, 0x0, 0x0, 0x60, - 0x28, 0x6, 0x6, 0x2b, 0x6, 0x1, 0x5, 0x5, 0x2, 0xa0, 0x1e, 0x30, 0x1c, 0xa0, 0x1a, - 0x30, 0x18, 0x6, 0xa, 0x2b, 0x6, 0x1, 0x4, 0x1, 0x82, 0x37, 0x2, 0x2, 0x1e, 0x6, 0xa, - 0x2b, 0x6, 0x1, 0x4, 0x1, 0x82, 0x37, 0x2, 0x2, 0xa, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, - 0x0, 0x26, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x20, 0x0, 0x1, 0x0, 0xd5, 0x67, 0x1b, - 0x24, 0xa1, 0xe9, 0xcc, 0xc8, 0x93, 0xf5, 0x55, 0x5a, 0x31, 0x3, 0x43, 0x5a, 0x85, - 0x2b, 0xc3, 0xcb, 0x1a, 0xd3, 0x2d, 0xc5, 0x1f, 0x92, 0x80, 0x6e, 0xf3, 0xfb, 0x4d, - 0xd4, 0x0, 0x0, 0x2, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x2, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x8, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x7, 0x0, 0xc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0x0, 0xc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, - 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x2, 0x0, 0x4, 0x0, - ]; - - let response = decode_content(&data).content.to_negotiate().unwrap(); - - assert_eq!( - response, - NegotiateResponse { - security_mode: NegotiateSecurityMode::new().with_signing_enabled(true), - dialect_revision: NegotiateDialect::Smb0311, - server_guid: Guid::from([ - 0xb9, 0x21, 0xf8, 0xe0, 0x15, 0x7, 0xaa, 0x41, 0xbe, 0x38, 0x67, 0xfe, 0xbf, - 0x5e, 0x2e, 0x11 - ]), - capabilities: GlobalCapabilities::new() - .with_dfs(true) - .with_leasing(true) - .with_large_mtu(true) - .with_multi_channel(true) - .with_directory_leasing(true), - max_transact_size: 8388608, - max_read_size: 8388608, - max_write_size: 8388608, - system_time: datetime!(2025-01-18 16:24:39.448746400).into(), - server_start_time: FileTime::default(), - buffer: [ - 0x60, 0x28, 0x6, 0x6, 0x2b, 0x6, 0x1, 0x5, 0x5, 0x2, 0xa0, 0x1e, 0x30, 0x1c, - 0xa0, 0x1a, 0x30, 0x18, 0x6, 0xa, 0x2b, 0x6, 0x1, 0x4, 0x1, 0x82, 0x37, 0x2, - 0x2, 0x1e, 0x6, 0xa, 0x2b, 0x6, 0x1, 0x4, 0x1, 0x82, 0x37, 0x2, 0x2, 0xa - ] - .to_vec(), - negotiate_context_list: Some(vec![ - NegotiateContextValue::PreauthIntegrityCapabilities( - PreauthIntegrityCapabilities { - hash_algorithms: vec![HashAlgorithm::Sha512], - salt: [ - 0xd5, 0x67, 0x1b, 0x24, 0xa1, 0xe9, 0xcc, 0xc8, 0x93, 0xf5, 0x55, - 0x5a, 0x31, 0x3, 0x43, 0x5a, 0x85, 0x2b, 0xc3, 0xcb, 0x1a, 0xd3, - 0x2d, 0xc5, 0x1f, 0x92, 0x80, 0x6e, 0xf3, 0xfb, 0x4d, 0xd4 - ] - .to_vec() - } - ) - .into(), - NegotiateContextValue::EncryptionCapabilities(EncryptionCapabilities { - ciphers: vec![EncryptionCipher::Aes128Gcm] - }) - .into(), - NegotiateContextValue::SigningCapabilities(SigningCapabilities { - signing_algorithms: vec![SigningAlgorithmId::AesGmac] - }) - .into(), - NegotiateContextValue::RdmaTransformCapabilities(RdmaTransformCapabilities { - transforms: vec![RdmaTransformId::Encryption, RdmaTransformId::Signing] - }) - .into(), - NegotiateContextValue::CompressionCapabilities(CompressionCapabilities { - flags: CompressionCapsFlags::new().with_chained(true), - compression_algorithms: vec![ - CompressionAlgorithm::LZ77, - CompressionAlgorithm::PatternV1 + test_request! { + Negotiate { + security_mode: NegotiateSecurityMode::new().with_signing_enabled(true), + capabilities: GlobalCapabilities::new() + .with_dfs(true) + .with_leasing(true) + .with_large_mtu(true) + .with_multi_channel(true) + .with_persistent_handles(true) + .with_directory_leasing(true) + .with_encryption(true) + .with_notifications(true), + client_guid: make_guid!("{c12e0ddf-43dd-11f0-8b87-000c29801682}"), + dialects: vec![ + Dialect::Smb0202, + Dialect::Smb021, + Dialect::Smb030, + Dialect::Smb0302, + Dialect::Smb0311, + ], + negotiate_context_list: Some(vec![ + PreauthIntegrityCapabilities { + hash_algorithms: vec![HashAlgorithm::Sha512], + salt: hex_to_u8_array! {"ed006c304e332890b2bd98617b5ad9ef075994154673696280ffcc0f1291a15d"} + }.into(), + EncryptionCapabilities { ciphers: vec![ + EncryptionCipher::Aes128Gcm, + EncryptionCipher::Aes128Ccm, + EncryptionCipher::Aes256Gcm, + EncryptionCipher::Aes256Ccm, + ] }.into(), + CompressionCapabilities { + flags: CompressionCapsFlags::new().with_chained(true), + compression_algorithms: vec![ + CompressionAlgorithm::PatternV1, + CompressionAlgorithm::LZ77, + CompressionAlgorithm::LZ77Huffman, + CompressionAlgorithm::LZNT1, + CompressionAlgorithm::LZ4, + ] + }.into(), + SigningCapabilities { signing_algorithms: vec![ + SigningAlgorithmId::AesGmac, + SigningAlgorithmId::AesCmac, + SigningAlgorithmId::HmacSha256, + ] }.into(), + NetnameNegotiateContextId { netname: "localhost".into() }.into(), + RdmaTransformCapabilities { transforms: vec![RdmaTransformId::Encryption, RdmaTransformId::Signing] }.into() + ]) + } => "2400050001000000ff000000df0d2ec1dd43f0118b87000c298 + 016827000000006000000020210020003020311030000010026000000 + 0000010020000100ed006c304e332890b2bd98617b5ad9ef075994154 + 673696280ffcc0f1291a15d000002000a000000000004000200010004 + 000300000000000000030012000000000005000000010000000400020 + 003000100050000000000000008000800000000000300020001000000 + 05001200000000006c006f00630061006c0068006f007300740000000 + 000000007000c0000000000020000000000000001000200" + } + + test_response! { + Negotiate { + security_mode: NegotiateSecurityMode::new().with_signing_enabled(true), + dialect_revision: NegotiateDialect::Smb0311, + server_guid: Guid::from([ + 0xb9, 0x21, 0xf8, 0xe0, 0x15, 0x7, 0xaa, 0x41, 0xbe, 0x38, 0x67, 0xfe, 0xbf, + 0x5e, 0x2e, 0x11 + ]), + capabilities: GlobalCapabilities::new() + .with_dfs(true) + .with_leasing(true) + .with_large_mtu(true) + .with_multi_channel(true) + .with_directory_leasing(true), + max_transact_size: 8388608, + max_read_size: 8388608, + max_write_size: 8388608, + system_time: datetime!(2025-01-18 16:24:39.448746400).into(), + server_start_time: FileTime::default(), + buffer: [ + 0x60, 0x28, 0x6, 0x6, 0x2b, 0x6, 0x1, 0x5, 0x5, 0x2, 0xa0, 0x1e, 0x30, 0x1c, + 0xa0, 0x1a, 0x30, 0x18, 0x6, 0xa, 0x2b, 0x6, 0x1, 0x4, 0x1, 0x82, 0x37, 0x2, + 0x2, 0x1e, 0x6, 0xa, 0x2b, 0x6, 0x1, 0x4, 0x1, 0x82, 0x37, 0x2, 0x2, 0xa + ] + .to_vec(), + negotiate_context_list: Some(vec![ + PreauthIntegrityCapabilities { + hash_algorithms: vec![HashAlgorithm::Sha512], + salt: [ + 0xd5, 0x67, 0x1b, 0x24, 0xa1, 0xe9, 0xcc, 0xc8, 0x93, 0xf5, 0x55, + 0x5a, 0x31, 0x3, 0x43, 0x5a, 0x85, 0x2b, 0xc3, 0xcb, 0x1a, 0xd3, + 0x2d, 0xc5, 0x1f, 0x92, 0x80, 0x6e, 0xf3, 0xfb, 0x4d, 0xd4 ] - }) - .into(), - ]) - } - ) + .to_vec() + } + .into(), + EncryptionCapabilities { + ciphers: vec![EncryptionCipher::Aes128Gcm] + } + .into(), + SigningCapabilities { + signing_algorithms: vec![SigningAlgorithmId::AesGmac] + } + .into(), + RdmaTransformCapabilities { + transforms: vec![RdmaTransformId::Encryption, RdmaTransformId::Signing] + } + .into(), + CompressionCapabilities { + flags: CompressionCapsFlags::new().with_chained(true), + compression_algorithms: vec![ + CompressionAlgorithm::LZ77, + CompressionAlgorithm::PatternV1 + ] + } + .into(), + ]) + } => "4100010011030500b921f8e01507aa41be3867febf5e2e112f000000000080000000800000008000a876d878c569db01000000000000000080002a00b0000000602806062b0601050502a01e301ca01a3018060a2b06010401823702021e060a2b06010401823702020a0000000000000100260000000000010020000100d5671b24a1e9ccc893f5555a3103435a852bc3cb1ad32dc51f92806ef3fb4dd40000020004000000000001000200000000000800040000000000010002000000000007000c00000000000200000000000000010002000000000003000c0000000000020000000100000002000400" } } diff --git a/crates/smb-msg/src/notify.rs b/crates/smb-msg/src/notify.rs index 38c49ddc..50b7fe18 100644 --- a/crates/smb-msg/src/notify.rs +++ b/crates/smb-msg/src/notify.rs @@ -10,7 +10,7 @@ use smb_dtyp::binrw_util::prelude::*; use smb_fscc::*; #[binrw::binrw] -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] pub struct ChangeNotifyRequest { #[bw(calc = 32)] #[br(assert(_structure_size == 32))] @@ -87,6 +87,8 @@ pub struct ChangeNotifyResponse { _output_buffer_length: PosMarker, #[br(seek_before = SeekFrom::Start(_output_buffer_offset.value.into()))] #[br(map_stream = |s| s.take_seek(_output_buffer_length.value.into()))] + #[bw(if(!buffer.is_empty()))] + #[bw(write_with = PosMarker::write_aoff_size, args(&_output_buffer_offset, &_output_buffer_length))] pub buffer: ChainedItemList, } @@ -134,16 +136,14 @@ pub struct NotifySessionClosed { #[cfg(test)] mod tests { - use std::io::Cursor; - use crate::*; use smb_dtyp::guid::Guid; + use smb_tests::*; use super::*; - #[test] - pub fn change_notify_request_write() { - let request = ChangeNotifyRequest { + test_binrw! { + struct ChangeNotifyRequest { flags: NotifyFlags::new(), output_buffer_length: 2048, file_id: "000005d1-000c-0000-1900-00000c000000" @@ -155,129 +155,56 @@ mod tests { .with_dir_name(true) .with_attributes(true) .with_last_write(true), - }; - - let mut cursor = Cursor::new(Vec::new()); - request.write_le(&mut cursor).unwrap(); - assert_eq!( - cursor.into_inner(), - [ - 0x20, 0x0, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0xd1, 0x5, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0, - 0x19, 0x0, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0, 0x17, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 - ] - ); + } => "2000000000080000d10500000c000000190000000c0000001700000000000000" } - #[test] - pub fn test_change_notify_response_pending_parse() { - let data = [0x9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0]; - let response = ChangeNotifyResponse::read_le(&mut Cursor::new(&data)).unwrap(); - assert_eq!( - response, - ChangeNotifyResponse { - buffer: Default::default() - } - ); + test_binrw! { + struct ChangeNotifyResponse => pending { + buffer: Default::default(), + } => "0900000000000000" } - #[test] - pub fn test_change_notify_response_with_data_parse() { - let data = [ - 0xfe, 0x53, 0x4d, 0x42, 0x40, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf, 0x0, 0x0, 0x0, - 0x33, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x56, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, - 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x25, 0x0, 0x0, 0x0, 0x0, 0x38, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9, 0x0, 0x48, - 0x0, 0x34, 0x0, 0x0, 0x0, 0x20, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x14, 0x0, 0x0, 0x0, - 0x4e, 0x0, 0x65, 0x0, 0x77, 0x0, 0x20, 0x0, 0x66, 0x0, 0x6f, 0x0, 0x6c, 0x0, 0x64, 0x0, - 0x65, 0x0, 0x72, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x6a, - 0x0, 0x64, 0x0, 0x73, 0x0, 0x61, 0x0, - ]; - - let parsed = decode_content(&data); - let notify_response = parsed.content.to_changenotify().unwrap(); - - assert_eq!( - notify_response, - ChangeNotifyResponse { - buffer: vec![ - FileNotifyInformation { - action: NotifyAction::RenamedOldName, - file_name: "New folder".into() - }, - FileNotifyInformation { - action: NotifyAction::RenamedNewName, - file_name: "jdsa".into() - } - ] - .into() - } - ); + test_response! { + change_notify_with_data: ChangeNotify { + buffer: vec![ + FileNotifyInformation { + action: NotifyAction::RenamedOldName, + file_name: "New folder".into() + }, + FileNotifyInformation { + action: NotifyAction::RenamedNewName, + file_name: "jdsa".into() + } + ] + .into() + } => "09004800340000002000000004000000140000004e0065007700200066006f006c006400650072000000000005000000080000006a00640073006100" } - #[test] - pub fn test_change_notify_response_azure_files() { - let data = [ - 0xfe, 0x53, 0x4d, 0x42, 0x40, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, - 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5d, 0x00, - 0x00, 0x68, 0x6c, 0x30, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x48, 0x00, 0x60, 0x01, - 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, - 0x31, 0x00, 0x31, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x78, 0x00, 0x74, 0x00, 0x28, 0x00, - 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x6b, 0x00, 0x65, 0x00, - 0x72, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x2e, 0x00, 0x62, 0x00, 0x69, 0x00, - 0x6e, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x69, 0x00, 0x6c, 0x00, 0x78, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x65, 0x00, 0x63, 0x00, 0x32, 0x00, - 0x2d, 0x00, 0x33, 0x00, 0x2d, 0x00, 0x37, 0x00, 0x30, 0x00, 0x2d, 0x00, 0x32, 0x00, - 0x32, 0x00, 0x32, 0x00, 0x2d, 0x00, 0x36, 0x00, 0x39, 0x00, 0x2e, 0x00, 0x65, 0x00, - 0x75, 0x00, 0x2d, 0x00, 0x63, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x74, 0x00, 0x72, 0x00, - 0x61, 0x00, 0x6c, 0x00, 0x2d, 0x00, 0x31, 0x00, 0x2e, 0x00, 0x63, 0x00, 0x6f, 0x00, - 0x6d, 0x00, 0x70, 0x00, 0x75, 0x00, 0x74, 0x00, 0x65, 0x00, 0x2e, 0x00, 0x61, 0x00, - 0x6d, 0x00, 0x61, 0x00, 0x7a, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x61, 0x00, 0x77, 0x00, - 0x73, 0x00, 0x2e, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x2e, 0x00, 0x72, 0x00, - 0x64, 0x00, 0x70, 0x00, 0x80, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x6e, 0x00, - 0x00, 0x00, 0x65, 0x00, 0x63, 0x00, 0x32, 0x00, 0x2d, 0x00, 0x31, 0x00, 0x38, 0x00, - 0x2d, 0x00, 0x31, 0x00, 0x39, 0x00, 0x38, 0x00, 0x2d, 0x00, 0x35, 0x00, 0x31, 0x00, - 0x2d, 0x00, 0x39, 0x00, 0x38, 0x00, 0x2e, 0x00, 0x65, 0x00, 0x75, 0x00, 0x2d, 0x00, - 0x63, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x74, 0x00, 0x72, 0x00, 0x61, 0x00, 0x6c, 0x00, - 0x2d, 0x00, 0x31, 0x00, 0x2e, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x70, 0x00, - 0x75, 0x00, 0x74, 0x00, 0x65, 0x00, 0x2e, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x61, 0x00, - 0x7a, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x61, 0x00, 0x77, 0x00, 0x73, 0x00, 0x2e, 0x00, - 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x2e, 0x00, 0x72, 0x00, 0x64, 0x00, 0x70, 0x00, - 0x6f, 0x55, 0x73, 0x61, 0x67, 0x65, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x16, 0x00, 0x00, 0x00, 0x54, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00, 0x20, 0x00, - 0x44, 0x00, 0x43, 0x00, 0x2e, 0x00, 0x72, 0x00, 0x64, 0x00, 0x70, 0x00, 0x72, 0x6e, - 0x65, 0x74, 0x45, 0x67, - ]; - let parsed = decode_content(&data); - let notify_response = parsed.content.to_changenotify().unwrap(); - assert_eq!( - notify_response, - ChangeNotifyResponse { - buffer: vec![ - FileNotifyInformation { - action: NotifyAction::Added, - file_name: "11.txt".into() - }, - FileNotifyInformation { - action: NotifyAction::Added, - file_name: "kernel.bin.til".into() - }, - FileNotifyInformation { - action: NotifyAction::Added, - file_name: "ec2-3-70-222-69.eu-central-1.compute.amazonaws.com.rdp".into() - }, - FileNotifyInformation { - action: NotifyAction::Added, - file_name: "ec2-18-198-51-98.eu-central-1.compute.amazonaws.com.rdp".into() - }, - FileNotifyInformation { - action: NotifyAction::Added, - file_name: "Test DC.rdp".into() - } - ] - .into() - } - ); + test_response_read! { + change_notify_azure: ChangeNotify { + buffer: vec![ + FileNotifyInformation { + action: NotifyAction::Added, + file_name: "11.txt".into() + }, + FileNotifyInformation { + action: NotifyAction::Added, + file_name: "kernel.bin.til".into() + }, + FileNotifyInformation { + action: NotifyAction::Added, + file_name: "ec2-3-70-222-69.eu-central-1.compute.amazonaws.com.rdp".into() + }, + FileNotifyInformation { + action: NotifyAction::Added, + file_name: "ec2-18-198-51-98.eu-central-1.compute.amazonaws.com.rdp".into() + }, + FileNotifyInformation { + action: NotifyAction::Added, + file_name: "Test DC.rdp".into() + } + ] + .into() + } => "090048006001000018000000010000000c000000310031002e0074007800740028000000010000001c0000006b00650072006e0065006c002e00620069006e002e00740069006c0078000000010000006c0000006500630032002d0033002d00370030002d003200320032002d00360039002e00650075002d00630065006e007400720061006c002d0031002e0063006f006d0070007500740065002e0061006d0061007a006f006e006100770073002e0063006f006d002e0072006400700080000000010000006e0000006500630032002d00310038002d003100390038002d00350031002d00390038002e00650075002d00630065006e007400720061006c002d0031002e0063006f006d0070007500740065002e0061006d0061007a006f006e006100770073002e0063006f006d002e007200640070006f557361676500000000010000001600000054006500730074002000440043002e00720064007000726e65744567" } } diff --git a/crates/smb-msg/src/oplock.rs b/crates/smb-msg/src/oplock.rs index 9c7455c5..59569c57 100644 --- a/crates/smb-msg/src/oplock.rs +++ b/crates/smb-msg/src/oplock.rs @@ -92,64 +92,32 @@ pub type LeaseBreakResponse = LeaseBreakAckResponse; #[cfg(test)] mod tests { - use crate::*; - use std::io::Cursor; - use super::*; - #[test] - pub fn test_lease_break_notify_parses() { - let data = [ - 0x2c, 0x0, 0x2, 0x0, 0x1, 0x0, 0x0, 0x0, 0x9e, 0x61, 0xc8, 0x70, 0x5d, 0x16, 0x5e, - 0x31, 0xd4, 0x92, 0xa0, 0x1b, 0xc, 0xbb, 0x3a, 0xf2, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - ]; + use smb_tests::*; - let parsed = LeaseBreakNotify::read_le(&mut Cursor::new(&data)).unwrap(); - assert_eq!( - parsed, - LeaseBreakNotify { - new_epoch: 2, - ack_required: 1, - lease_key: "70c8619e-165d-315e-d492-a01b0cbb3af2".parse().unwrap(), - current_lease_state: LeaseState::new() - .with_read_caching(true) - .with_handle_caching(true), - new_lease_state: LeaseState::new() - } - ) + test_binrw! { + struct LeaseBreakNotify { + new_epoch: 2, + ack_required: 1, + lease_key: "70c8619e-165d-315e-d492-a01b0cbb3af2".parse().unwrap(), + current_lease_state: LeaseState::new() + .with_read_caching(true) + .with_handle_caching(true), + new_lease_state: LeaseState::new(), + } => "2c000200010000009e61c8705d165e31d492a01b0cbb3af20300000000000000000000000000000000000000" } - #[test] - pub fn test_lease_break_ack_response_write() { - let req_data = encode_content(RequestContent::LeaseBreakAck(LeaseBreakAck { + test_binrw! { + struct LeaseBreakAck { lease_key: "70c8619e-165d-315e-d492-a01b0cbb3af2".parse().unwrap(), lease_state: LeaseState::new(), - })); - - assert_eq!( - req_data, - [ - 0x24, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9e, 0x61, 0xc8, 0x70, 0x5d, 0x16, 0x5e, - 0x31, 0xd4, 0x92, 0xa0, 0x1b, 0xc, 0xbb, 0x3a, 0xf2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 - ] - ) + } => "24000000000000009e61c8705d165e31d492a01b0cbb3af2000000000000000000000000" } - #[test] - pub fn test_lease_break_ack_response_parses() { - let data = [ - 0x24, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9e, 0x61, 0xc8, 0x70, 0x5d, 0x16, 0x5e, - 0x31, 0xd4, 0x92, 0xa0, 0x1b, 0xc, 0xbb, 0x3a, 0xf2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, - ]; - let parsed = LeaseBreakAckResponse::read_le(&mut Cursor::new(&data)).unwrap(); - assert_eq!( - parsed, - LeaseBreakAckResponse { - lease_key: "70c8619e-165d-315e-d492-a01b0cbb3af2".parse().unwrap(), - lease_state: LeaseState::new(), - } - ) + test_binrw! { + struct LeaseBreakAckResponse { + lease_key: "70c8619e-165d-315e-d492-a01b0cbb3af2".parse().unwrap(), + lease_state: LeaseState::new(), + } => "24000000000000009e61c8705d165e31d492a01b0cbb3af2000000000000000000000000" } } diff --git a/crates/smb-msg/src/plain.rs b/crates/smb-msg/src/plain.rs index a482df62..a55b4f05 100644 --- a/crates/smb-msg/src/plain.rs +++ b/crates/smb-msg/src/plain.rs @@ -179,6 +179,14 @@ impl From } } +impl From + for ResponseContent +{ + fn from(resp: error::ErrorResponse) -> Self { + ResponseContent::Error(resp) + } +} + make_content_impl!{ RequestContent, $( @@ -275,12 +283,17 @@ macro_rules! make_plain { impl [] { pub fn new(content: [<$suffix Content>]) -> [] { + let cmd = content.associated_cmd(); + Self::new_with_command(content, cmd) + } + + pub fn new_with_command(content: [<$suffix Content>], command: Command) -> [] { [] { // default is a sync command, so `tree_id` must be set, and `HeaderFlags::async_command` is false header: Header { credit_charge: 0, status: Status::Success as u32, - command: content.associated_cmd(), + command, credit_request: 0, flags: HeaderFlags::new(), next_command: 0, @@ -298,31 +311,16 @@ macro_rules! make_plain { }; } -make_plain!(Request, false, binrw::binwrite); -make_plain!(Response, true, binrw::binread); - -/// Contains both tests and test helpers for other modules' tests requiring this module. -#[cfg(test)] -pub mod tests { - use std::io::Cursor; - - use super::*; - - /// Given a content, encode it into a Vec as if it were a full message, - /// But return only the content bytes. - /// - /// This is useful when encoding structs with offsets relative to the beginning of the SMB header. - pub fn encode_content(content: RequestContent) -> Vec { - let mut cursor = Cursor::new(Vec::new()); - let msg = PlainRequest::new(content); - msg.write(&mut cursor).unwrap(); - let bytes_of_msg = cursor.into_inner(); - // We only want to return the content of the message, not the header. So cut the HEADER_SIZE bytes: - bytes_of_msg[Header::STRUCT_SIZE..].to_vec() - } - - pub fn decode_content(bytes: &[u8]) -> PlainResponse { - let mut cursor = Cursor::new(bytes); - cursor.read_le().unwrap() - } +macro_rules! gen_req_resp { + ($req_attr:ty, $res_attr:ty) => { + make_plain!(Request, false, $req_attr); + make_plain!(Response, true, $res_attr); + }; } + +#[cfg(all(feature = "server", feature = "client"))] +gen_req_resp!(binrw::binrw, binrw::binrw); +#[cfg(all(feature = "client", not(feature = "server")))] +gen_req_resp!(binrw::binwrite, binrw::binread); +#[cfg(all(feature = "server", not(feature = "client")))] +gen_req_resp!(binrw::binread, binrw::binwrite); diff --git a/crates/smb-msg/src/session_setup.rs b/crates/smb-msg/src/session_setup.rs index 6ff4139b..0475d97c 100644 --- a/crates/smb-msg/src/session_setup.rs +++ b/crates/smb-msg/src/session_setup.rs @@ -4,7 +4,7 @@ use modular_bitfield::prelude::*; use smb_dtyp::binrw_util::prelude::*; #[binrw::binrw] -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] pub struct SessionSetupRequest { #[bw(calc = 25)] #[br(assert(_structure_size == 25))] @@ -60,11 +60,12 @@ impl SessionSetupRequest { buffer: Vec, security_mode: SessionSecurityMode, flags: SetupRequestFlags, + capabilities: NegotiateCapabilities, ) -> SessionSetupRequest { SessionSetupRequest { flags, security_mode, - capabilities: NegotiateCapabilities::new().with_dfs(true), + capabilities, previous_session_id: 0, buffer, } @@ -127,89 +128,28 @@ pub struct LogoffResponse { #[cfg(test)] mod tests { + use smb_tests::*; + use crate::*; use super::*; - #[test] - pub fn test_setup_req_write() { - let data = encode_content( - SessionSetupRequest::new( - [ - 0x60, 0x57, 0x6, 0x6, 0x2b, 0x6, 0x1, 0x5, 0x5, 0x2, 0xa0, 0x4d, 0x30, 0x4b, - 0xa0, 0xe, 0x30, 0xc, 0x6, 0xa, 0x2b, 0x6, 0x1, 0x4, 0x1, 0x82, 0x37, 0x2, 0x2, - 0xa, 0xa2, 0x39, 0x4, 0x37, 0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x0, 0x1, - 0x0, 0x0, 0x0, 0x97, 0xb2, 0x8, 0xe2, 0x9, 0x0, 0x9, 0x0, 0x2e, 0x0, 0x0, 0x0, - 0x6, 0x0, 0x6, 0x0, 0x28, 0x0, 0x0, 0x0, 0xa, 0x0, 0x5d, 0x58, 0x0, 0x0, 0x0, - 0xf, 0x41, 0x56, 0x49, 0x56, 0x56, 0x4d, 0x57, 0x4f, 0x52, 0x4b, 0x47, 0x52, - 0x4f, 0x55, 0x50, - ] - .to_vec(), - SessionSecurityMode::new().with_signing_enabled(true), - SetupRequestFlags::new(), - ) - .into(), - ); - - assert_eq!( - data, - [ - 0x19, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x58, 0x0, 0x59, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x60, 0x57, 0x6, 0x6, 0x2b, 0x6, 0x1, 0x5, - 0x5, 0x2, 0xa0, 0x4d, 0x30, 0x4b, 0xa0, 0xe, 0x30, 0xc, 0x6, 0xa, 0x2b, 0x6, 0x1, - 0x4, 0x1, 0x82, 0x37, 0x2, 0x2, 0xa, 0xa2, 0x39, 0x4, 0x37, 0x4e, 0x54, 0x4c, 0x4d, - 0x53, 0x53, 0x50, 0x0, 0x1, 0x0, 0x0, 0x0, 0x97, 0xb2, 0x8, 0xe2, 0x9, 0x0, 0x9, - 0x0, 0x2e, 0x0, 0x0, 0x0, 0x6, 0x0, 0x6, 0x0, 0x28, 0x0, 0x0, 0x0, 0xa, 0x0, 0x5d, - 0x58, 0x0, 0x0, 0x0, 0xf, 0x41, 0x56, 0x49, 0x56, 0x56, 0x4d, 0x57, 0x4f, 0x52, - 0x4b, 0x47, 0x52, 0x4f, 0x55, 0x50, - ], - ) + const SETUP_REQUEST_DATA: &'static str = "605706062b0601050502a04d304ba00e300c060a2b06010401823702020aa23904374e544c4d535350000100000097b208e2090009002e00000006000600280000000a005d580000000f41564956564d574f524b47524f5550"; + test_request! { + SessionSetup { + flags: SetupRequestFlags::new(), + security_mode: SessionSecurityMode::new().with_signing_enabled(true), + buffer: hex_to_u8_array! {SETUP_REQUEST_DATA}, + previous_session_id: 0, + capabilities: NegotiateCapabilities::new().with_dfs(true), + } => const_format::concatcp!("190000010100000000000000580059000000000000000000", SETUP_REQUEST_DATA) } - #[test] - pub fn test_setup_resp_parse() { - let data = [ - 0xfe, 0x53, 0x4d, 0x42, 0x40, 0x0, 0x1, 0x0, 0x16, 0x0, 0x0, 0xc0, 0x1, 0x0, 0x1, 0x0, - 0x11, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, - 0xfe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x31, 0x0, 0x0, 0x28, 0x0, 0x30, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9, 0x0, - 0x0, 0x0, 0x48, 0x0, 0xb3, 0x0, 0xa1, 0x81, 0xb0, 0x30, 0x81, 0xad, 0xa0, 0x3, 0xa, - 0x1, 0x1, 0xa1, 0xc, 0x6, 0xa, 0x2b, 0x6, 0x1, 0x4, 0x1, 0x82, 0x37, 0x2, 0x2, 0xa, - 0xa2, 0x81, 0x97, 0x4, 0x81, 0x94, 0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x0, 0x2, - 0x0, 0x0, 0x0, 0xc, 0x0, 0xc, 0x0, 0x38, 0x0, 0x0, 0x0, 0x15, 0xc2, 0x8a, 0xe2, 0xab, - 0xf1, 0x94, 0xbd, 0xb7, 0x56, 0xda, 0xa9, 0x14, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x50, 0x0, 0x50, 0x0, 0x44, 0x0, 0x0, 0x0, 0xa, 0x0, 0x5d, 0x58, 0x0, 0x0, 0x0, 0xf, - 0x41, 0x0, 0x56, 0x0, 0x49, 0x0, 0x56, 0x0, 0x56, 0x0, 0x4d, 0x0, 0x2, 0x0, 0xc, 0x0, - 0x41, 0x0, 0x56, 0x0, 0x49, 0x0, 0x56, 0x0, 0x56, 0x0, 0x4d, 0x0, 0x1, 0x0, 0xc, 0x0, - 0x41, 0x0, 0x56, 0x0, 0x49, 0x0, 0x56, 0x0, 0x56, 0x0, 0x4d, 0x0, 0x4, 0x0, 0xc, 0x0, - 0x41, 0x0, 0x76, 0x0, 0x69, 0x0, 0x76, 0x0, 0x56, 0x0, 0x6d, 0x0, 0x3, 0x0, 0xc, 0x0, - 0x41, 0x0, 0x76, 0x0, 0x69, 0x0, 0x76, 0x0, 0x56, 0x0, 0x6d, 0x0, 0x7, 0x0, 0x8, 0x0, - 0xa8, 0x76, 0xd8, 0x78, 0xc5, 0x69, 0xdb, 0x1, 0x0, 0x0, 0x0, 0x0, - ]; - - let response = decode_content(&data).content.to_sessionsetup().unwrap(); - - assert_eq!( - response, - SessionSetupResponse { - session_flags: SessionFlags::new(), - buffer: [ - 0xa1, 0x81, 0xb0, 0x30, 0x81, 0xad, 0xa0, 0x3, 0xa, 0x1, 0x1, 0xa1, 0xc, 0x6, - 0xa, 0x2b, 0x6, 0x1, 0x4, 0x1, 0x82, 0x37, 0x2, 0x2, 0xa, 0xa2, 0x81, 0x97, - 0x4, 0x81, 0x94, 0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, 0x0, 0x2, 0x0, 0x0, - 0x0, 0xc, 0x0, 0xc, 0x0, 0x38, 0x0, 0x0, 0x0, 0x15, 0xc2, 0x8a, 0xe2, 0xab, - 0xf1, 0x94, 0xbd, 0xb7, 0x56, 0xda, 0xa9, 0x14, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x50, 0x0, 0x50, 0x0, 0x44, 0x0, 0x0, 0x0, 0xa, 0x0, 0x5d, 0x58, 0x0, 0x0, - 0x0, 0xf, 0x41, 0x0, 0x56, 0x0, 0x49, 0x0, 0x56, 0x0, 0x56, 0x0, 0x4d, 0x0, - 0x2, 0x0, 0xc, 0x0, 0x41, 0x0, 0x56, 0x0, 0x49, 0x0, 0x56, 0x0, 0x56, 0x0, - 0x4d, 0x0, 0x1, 0x0, 0xc, 0x0, 0x41, 0x0, 0x56, 0x0, 0x49, 0x0, 0x56, 0x0, - 0x56, 0x0, 0x4d, 0x0, 0x4, 0x0, 0xc, 0x0, 0x41, 0x0, 0x76, 0x0, 0x69, 0x0, - 0x76, 0x0, 0x56, 0x0, 0x6d, 0x0, 0x3, 0x0, 0xc, 0x0, 0x41, 0x0, 0x76, 0x0, - 0x69, 0x0, 0x76, 0x0, 0x56, 0x0, 0x6d, 0x0, 0x7, 0x0, 0x8, 0x0, 0xa8, 0x76, - 0xd8, 0x78, 0xc5, 0x69, 0xdb, 0x1, 0x0, 0x0, 0x0, 0x0 - ] - .to_vec() - } - ) + const SETUP_RESPONSE_DATA: &'static str = "a181b03081ada0030a0101a10c060a2b06010401823702020aa281970481944e544c4d53535000020000000c000c003800000015c28ae2abf194bdb756daa9140001000000000050005000440000000a005d580000000f410056004900560056004d0002000c00410056004900560056004d0001000c00410056004900560056004d0004000c00410076006900760056006d0003000c00410076006900760056006d0007000800a876d878c569db0100000000"; + test_response! { + SessionSetup { + session_flags: SessionFlags::new(), + buffer: hex_to_u8_array! {SETUP_RESPONSE_DATA} + } => const_format::concatcp!("090000004800b300", SETUP_RESPONSE_DATA) } } diff --git a/crates/smb-msg/src/smb1.rs b/crates/smb-msg/src/smb1.rs index aab202b1..e0dfd078 100644 --- a/crates/smb-msg/src/smb1.rs +++ b/crates/smb-msg/src/smb1.rs @@ -94,19 +94,7 @@ impl TryInto> for SMB1NegotiateMessage { mod tests { use super::*; - #[test] - pub fn test_smb1_negotiate_req_write() { - let msg = SMB1NegotiateMessage::default(); - let buf: Result, binrw::Error> = msg.try_into(); - assert_eq!( - buf.unwrap(), - [ - 0xff, 0x53, 0x4d, 0x42, 0x72, 0x0, 0x0, 0x0, 0x0, 0x18, 0x53, 0xc8, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0x01, 0x00, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x22, 0x0, 0x2, 0x4e, 0x54, 0x20, 0x4c, 0x4d, 0x20, 0x30, 0x2e, 0x31, - 0x32, 0x0, 0x2, 0x53, 0x4d, 0x42, 0x20, 0x32, 0x2e, 0x30, 0x30, 0x32, 0x0, 0x2, - 0x53, 0x4d, 0x42, 0x20, 0x32, 0x2e, 0x3f, 0x3f, 0x3f, 0x0 - ] - ) + smb_tests::test_binrw_write! { + SMB1NegotiateMessage: SMB1NegotiateMessage::default() => "ff534d4272000000001853c8000000000000000000000000ffff010000000000002200024e54204c4d20302e31320002534d4220322e3030320002534d4220322e3f3f3f00" } } diff --git a/crates/smb-msg/src/test.rs b/crates/smb-msg/src/test.rs index 973b2259..1f9415d5 100644 --- a/crates/smb-msg/src/test.rs +++ b/crates/smb-msg/src/test.rs @@ -2,4 +2,198 @@ //! Any `pub use` here is also imported in the [super] module. //! It may only be used inside tests. -pub use super::plain::tests::{decode_content, encode_content}; +use binrw::prelude::*; + +/// Implementation of reading plain content test +macro_rules! _test_generic_read { + ( + $req_or_resp:ident => $test_name:ident, $command:expr => $struct_name:ident { + $($field_name:ident : $field_value:expr),* $(,)? + } => $hex:expr + ) => { + pastey::paste! { + #[test] + fn []() { + use ::binrw::{io::{Cursor, Write, Seek}, prelude::*}; + + // Build some fake header bytes and concat before actual content bytes. + + let fake_header_for_test = Header { + async_id: None, + tree_id: Some(0), + command: $command, + flags: HeaderFlags::default().with_server_to_redir(stringify!([<$req_or_resp:lower>]) == "response"), + status: 0, + session_id: 0, + credit_charge: 0, + credit_request: 0, + message_id: 0, + signature: 0, + next_command: 0 + }; + let mut cursor = Cursor::new(Vec::new()); + fake_header_for_test.write(&mut cursor).unwrap(); + + cursor.write(::smb_tests::hex_to_u8_array! { $hex }.as_slice()).unwrap(); + cursor.seek(std::io::SeekFrom::Start(0)).unwrap(); + + let msg: [] = cursor.read_le().unwrap(); + let msg: [<$struct_name $req_or_resp:camel>] = msg.content.[]().unwrap(); + assert_eq!(msg, [<$struct_name $req_or_resp:camel>] { + $( + $field_name: $field_value, + )* + }); + } + } + }; +} + +/// Implementation of writing plain content test +macro_rules! _test_generic_write { + ( + $req_or_resp:ident => $test_name:ident, $command:expr => $struct_name:ident { + $($field_name:ident : $field_value:expr),* $(,)? + } => $hex:expr + ) => { + pastey::paste! { + #[test] + fn []() { + use ::binrw::{io::Cursor, prelude::*}; + let response = [<$struct_name $req_or_resp:camel>] { + $( + $field_name: $field_value, + )* + }; + let mut cursor = Cursor::new(Vec::new()); + let mut msg = []::new_with_command(response.into(), $command); + + msg.header.flags.set_server_to_redir(stringify!([<$req_or_resp:lower>]) == "response"); // Since we're writing a response, we must set this flag + + msg.write(&mut cursor).unwrap(); + let written_bytes = cursor.into_inner(); + let expected_bytes = ::smb_tests::hex_to_u8_array! { $hex }; + assert_eq!(&written_bytes[Header::STRUCT_SIZE..], &expected_bytes); + } + } + } +} + +/// This macro calls other macros to implement both read and write tests +/// It has all the variants of test macros in this module, eventually calling `$impl_macro`. +macro_rules! _test_generic_impl { + ( + $impl_macro:ident, $req_or_resp:ident => $struct_name:ident { + $($field_name:ident : $field_value:expr),* $(,)? + } => $hex:expr + ) => { + _test_generic_impl! { + $impl_macro, $req_or_resp => + $struct_name: $struct_name { + $($field_name : $field_value),* + } => $hex + } + }; + ( + $impl_macro:ident, $req_or_resp:ident => $test_name:ident: $struct_name:ident { + $($field_name:ident : $field_value:expr),* $(,)? + } => $hex:expr + ) => { + _test_generic_impl! { + $impl_macro, $req_or_resp => + $test_name, Command::$struct_name => $struct_name { + $($field_name : $field_value),* + } => $hex + } + }; + ( + $impl_macro:ident, $($v:tt)+ + ) => { + $impl_macro! { + $($v)+ + } + }; +} + +/// Internal macro, do not use directly. See [test_request] and [test_response]. +/// +/// - This macro expands to test impl for read and write, +/// through [`_test_generic_impl`] using [`_test_generic_read`] and [`_test_generic_write`]. +macro_rules! _test_read_write_generic { + ( + $req_or_resp:ident => $($v:tt)+ + ) => { + _test_generic_impl! { + _test_generic_write, $req_or_resp => $($v)* + } + _test_generic_impl! { + _test_generic_read, $req_or_resp => $($v)* + } + } +} + +pub(crate) use _test_generic_impl; +pub(crate) use _test_generic_read; +pub(crate) use _test_generic_write; +pub(crate) use _test_read_write_generic; + +macro_rules! test_request { + ($($v:tt)+) => { + _test_read_write_generic! { + Request => $($v)+ + } + }; +} + +macro_rules! test_response { + ($($v:tt)+) => { + _test_read_write_generic! { + Response => $($v)+ + } + }; +} + +#[allow(unused_macros)] +macro_rules! test_request_read { + ($($v:tt)+) => { + _test_generic_impl! { + _test_generic_read, Request => $($v)* + } + }; +} + +macro_rules! test_response_read { + ($($v:tt)+) => { + _test_generic_impl! { + _test_generic_read, Response => $($v)* + } + }; +} + +#[allow(unused_macros)] +macro_rules! test_request_write { + ($($v:tt)+) => { + _test_generic_impl! { + _test_generic_write, Request => $($v)* + } + }; +} + +#[allow(unused_macros)] +macro_rules! test_response_write { + ($($v:tt)+) => { + _test_generic_impl! { + _test_generic_write, Response => $($v)* + } + }; +} + +pub(crate) use test_request; +#[allow(unused_imports)] +pub(crate) use test_request_read; +#[allow(unused_imports)] +pub(crate) use test_request_write; +pub(crate) use test_response; +pub(crate) use test_response_read; +#[allow(unused_imports)] +pub(crate) use test_response_write; diff --git a/crates/smb-msg/src/tree_connect.rs b/crates/smb-msg/src/tree_connect.rs index 21928901..5be699a0 100644 --- a/crates/smb-msg/src/tree_connect.rs +++ b/crates/smb-msg/src/tree_connect.rs @@ -24,7 +24,7 @@ pub struct TreeConnectRequestFlags { /// - On read, uses extension iff `flags.extension_present()` - parses just like the server intends. /// - On write, uses extension iff `tree_connect_contexts` is non-empty. #[binrw::binrw] -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] pub struct TreeConnectRequest { #[bw(calc = 9)] #[br(assert(_structure_size == 9))] @@ -39,10 +39,12 @@ pub struct TreeConnectRequest { #[br(if(flags.extension_present()))] #[bw(calc = if tree_connect_contexts.is_empty() { None } else { Some(PosMarker::default()) })] tree_connect_context_offset: Option>, + #[br(if(flags.extension_present()))] #[bw(if(!tree_connect_contexts.is_empty()))] #[bw(calc = if tree_connect_contexts.is_empty() { None } else { Some(tree_connect_contexts.len().try_into().unwrap()) })] tree_connect_context_count: Option, + #[br(if(flags.extension_present()))] #[bw(if(!tree_connect_contexts.is_empty()))] #[bw(calc = Some([0u8; 10]))] @@ -58,14 +60,14 @@ pub struct TreeConnectRequest { // -- Extension -- #[br(if(flags.extension_present()))] #[br(seek_before = tree_connect_context_offset.unwrap().seek_relative(true))] - #[br(count = tree_connect_context_count.unwrap())] + #[br(count = tree_connect_context_count.unwrap_or(0))] #[bw(if(!tree_connect_contexts.is_empty()))] #[bw(write_with = PosMarker::write_aoff_m, args(tree_connect_context_offset.as_ref()))] tree_connect_contexts: Vec, } #[binrw::binrw] -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] pub struct TreeConnectContext { /// MS-SMB2 2.2.9.2: Must be set to SMB2_REMOTED_IDENTITY_TREE_CONNECT_CONTEXT_ID = 1. #[bw(calc = 1)] @@ -83,7 +85,7 @@ macro_rules! make_remoted_identity_connect{ pastey::paste! { #[binwrite] -#[derive(Debug, BinRead)] +#[derive(Debug, BinRead, PartialEq, Eq)] pub struct RemotedIdentityTreeConnect { // MS-SMB2 2.2.9.2.1: Must be set to 0x1. #[bw(calc = PosMarker::new(1))] @@ -321,40 +323,27 @@ pub struct TreeDisconnectResponse { #[cfg(test)] mod tests { - use std::io::Cursor; + use smb_tests::*; use crate::*; use super::*; - #[test] - pub fn test_tree_connect_req_write() { - let result = encode_content(TreeConnectRequest::new(&r"\\127.0.0.1\MyShare").into()); - assert_eq!( - result, - [ - 0x9, 0x0, 0x0, 0x0, 0x48, 0x0, 0x26, 0x0, 0x5c, 0x0, 0x5c, 0x0, 0x31, 0x0, 0x32, - 0x0, 0x37, 0x0, 0x2e, 0x0, 0x30, 0x0, 0x2e, 0x0, 0x30, 0x0, 0x2e, 0x0, 0x31, 0x0, - 0x5c, 0x0, 0x4d, 0x0, 0x79, 0x0, 0x53, 0x0, 0x68, 0x0, 0x61, 0x0, 0x72, 0x0, 0x65, - 0x0 - ] - ); + // TODO(test): Add tests with tree connect contexts. + test_request! { + TreeConnect { + flags: TreeConnectRequestFlags::new(), + buffer: r"\\adc.aviv.local\IPC$".into(), + tree_connect_contexts: vec![], + } => "0900000048002a005c005c006100640063002e0061007600690076002e006c006f00630061006c005c004900500043002400" } - #[test] - pub fn test_tree_connect_res_parse() { - let mut cursor = Cursor::new(&[ - 0x10, 0x0, 0x1, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0x1, 0x1f, 0x0, - ]); - let content_parsed = TreeConnectResponse::read_le(&mut cursor).unwrap(); - assert_eq!( - content_parsed, - TreeConnectResponse { - share_type: ShareType::Disk, - share_flags: ShareFlags::new().with_access_based_directory_enum(true), - capabilities: TreeCapabilities::new(), - maximal_access: 0x001f01ff, - } - ) + test_binrw! { + struct TreeConnectResponse { + share_type: ShareType::Disk, + share_flags: ShareFlags::new().with_access_based_directory_enum(true), + capabilities: TreeCapabilities::new(), + maximal_access: 0x001f01ff, + } => "100001000008000000000000ff011f00" } } diff --git a/crates/smb-rpc/Cargo.toml b/crates/smb-rpc/Cargo.toml index 58a9bf26..0abf2cd4 100644 --- a/crates/smb-rpc/Cargo.toml +++ b/crates/smb-rpc/Cargo.toml @@ -21,6 +21,9 @@ pastey = { workspace = true } maybe-async = { workspace = true } thiserror = { workspace = true } +[dev-dependencies] +smb-tests = { path = "../smb-tests", version = "0.10.2" } + [features] default = [] is_sync = ["maybe-async/is_sync"] diff --git a/crates/smb-rpc/src/interface/srvsvc.rs b/crates/smb-rpc/src/interface/srvsvc.rs index ab183100..728db87b 100644 --- a/crates/smb-rpc/src/interface/srvsvc.rs +++ b/crates/smb-rpc/src/interface/srvsvc.rs @@ -239,61 +239,12 @@ where #[cfg(test)] mod test { - use std::io::Cursor; + use smb_tests::*; use super::*; - #[test] - fn test_netrshareenumout_read() { - let data = [ - 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x3, 0x0, 0x0, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x41, 0x0, 0x44, 0x0, 0x4d, - 0x0, 0x49, 0x0, 0x4e, 0x0, 0x24, 0x0, 0x0, 0x0, 0x0, 0x0, 0xd, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x52, 0x0, 0x65, 0x0, 0x6d, 0x0, 0x6f, 0x0, 0x74, 0x0, 0x65, 0x0, 0x20, 0x0, 0x41, - 0x0, 0x64, 0x0, 0x6d, 0x0, 0x69, 0x0, 0x6e, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x43, 0x0, 0x24, 0x0, 0x0, 0x0, 0x0, 0x0, 0xe, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xe, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x44, 0x0, 0x65, 0x0, 0x66, 0x0, 0x61, 0x0, 0x75, 0x0, - 0x6c, 0x0, 0x74, 0x0, 0x20, 0x0, 0x73, 0x0, 0x68, 0x0, 0x61, 0x0, 0x72, 0x0, 0x65, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x49, 0x0, 0x50, 0x0, - 0x43, 0x0, 0x24, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xb, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xb, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x52, 0x0, 0x65, 0x0, 0x6d, 0x0, 0x6f, 0x0, 0x74, 0x0, 0x65, 0x0, 0x20, 0x0, - 0x49, 0x0, 0x50, 0x0, 0x43, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x4c, 0x0, 0x6f, 0x0, 0x63, 0x0, 0x61, 0x0, 0x6c, 0x0, 0x41, 0x0, 0x64, 0x0, 0x6d, - 0x0, 0x69, 0x0, 0x6e, 0x0, 0x53, 0x0, 0x68, 0x0, 0x61, 0x0, 0x72, 0x0, 0x65, 0x0, 0x0, - 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4d, 0x0, 0x79, 0x0, 0x53, 0x0, 0x68, 0x0, 0x61, 0x0, - 0x72, 0x0, 0x65, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x50, 0x0, 0x75, 0x0, 0x62, 0x0, - 0x6c, 0x0, 0x69, 0x0, 0x63, 0x0, 0x53, 0x0, 0x68, 0x0, 0x61, 0x0, 0x72, 0x0, 0x65, 0x0, - 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - ]; - let mut cursor = Cursor::new(data); - let result = NetrShareEnumOut::read_le(&mut cursor).unwrap(); - assert_eq!( - result, - NetrShareEnumOut { + + test_binrw_read! { + struct NetrShareEnumOut { info_struct: ShareEnumStruct { share_info: ShareEnumUnion::Info1( ShareInfoContainer:: { @@ -353,13 +304,11 @@ mod test { .into(), total_entries: 6.into(), resume_handle: NdrPtr::::from(None).into(), - } - ); + } => "010000000000000001000000000000000000020000000000060000000000000000000200000000000600000000000000000002000000000000000080000000000000020000000000000002000000000000000080000000000000020000000000000002000000000003000080000000000000020000000000000002000000000000000000000000000000020000000000000002000000000000000000000000000000020000000000000002000000000000000000000000000000020000000000070000000000000000000000000000000700000000000000410044004d0049004e002400000000000d0000000000000000000000000000000d00000000000000520065006d006f00740065002000410064006d0069006e00000000000000000003000000000000000000000000000000030000000000000043002400000000000e0000000000000000000000000000000e00000000000000440065006600610075006c007400200073006800610072006500000000000000050000000000000000000000000000000500000000000000490050004300240000000000000000000b0000000000000000000000000000000b00000000000000520065006d006f00740065002000490050004300000000001000000000000000000000000000000010000000000000004c006f00630061006c00410064006d0069006e0053006800610072006500000001000000000000000000000000000000010000000000000000000000000000000800000000000000000000000000000008000000000000004d00790053006800610072006500000001000000000000000000000000000000010000000000000000000000000000000c0000000000000000000000000000000c000000000000005000750062006c00690063005300680061007200650000000100000000000000000000000000000001000000000000000000000006000000000000000000000000000000" } - #[test] - fn test_netrshareenumin_write() { - let val = NetrShareEnumIn { + smb_tests::test_binrw_write! { + struct NetrShareEnumIn { server_name: Into::>::into(r"\\localhost".parse::>().unwrap()) .into(), info_struct: ShareEnumStruct { @@ -371,22 +320,6 @@ mod test { .into(), prefered_maximum_length: 4294967295.into(), resume_handle: NdrPtr::::from(None).into(), - }; - let mut cursor = Cursor::new(Vec::new()); - val.write_le(&mut cursor).unwrap(); - let data = cursor.into_inner(); - assert_eq!( - data, - [ - 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x5c, 0x0, 0x5c, 0x0, 0x6c, 0x0, 0x6f, 0x0, 0x63, 0x0, 0x61, 0x0, 0x6c, 0x0, 0x68, - 0x0, 0x6f, 0x0, 0x73, 0x0, 0x74, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 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, 0xff, 0xff, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0 - ] - ) + } => "00000200000000000c0000000000000000000000000000000c000000000000005c005c006c006f00630061006c0068006f0073007400000001000000000000000100000000000000000002000000000000000000000000000000000000000000ffffffff000000000000000000000000" } } diff --git a/crates/smb-rpc/src/ndr64.rs b/crates/smb-rpc/src/ndr64.rs index e79d287a..c94d414f 100644 --- a/crates/smb-rpc/src/ndr64.rs +++ b/crates/smb-rpc/src/ndr64.rs @@ -14,33 +14,19 @@ pub use consts::*; #[cfg(test)] mod tests { - use std::io::Cursor; + use smb_tests::*; use super::*; - #[test] - fn test_string_ptr() { - #[binrw::binrw] - #[derive(Debug, PartialEq, Eq)] - struct TestNdrStringPtr { - string: NdrPtr>, - } + #[binrw::binrw] + #[derive(Debug, PartialEq, Eq)] + struct TestNdrStringPtr { + string: NdrPtr>, + } - let data = TestNdrStringPtr { + test_binrw! { + struct TestNdrStringPtr { string: r"\\localhostt".parse::>().unwrap().into(), - }; - - let mut cursor = Cursor::new(vec![]); - data.write_le(&mut cursor).unwrap(); - let write_result = cursor.into_inner(); - assert_eq!( - write_result, - [ - 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0xd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, - 0x5c, 0x0, 0x5c, 0x0, 0x6c, 0x0, 0x6f, 0x0, 0x63, 0x0, 0x61, 0x0, 0x6c, 0x0, 0x68, - 0x0, 0x6f, 0x0, 0x73, 0x0, 0x74, 0x0, 0x74, 0x0, 0x0, 0x0, - ] - ); + } => "00000200000000000d0000000000000000000000000000000d000000000000005c005c006c006f00630061006c0068006f007300740074000000" } } diff --git a/crates/smb-rpc/src/ndr64/align.rs b/crates/smb-rpc/src/ndr64/align.rs index fcd15956..2e558d95 100644 --- a/crates/smb-rpc/src/ndr64/align.rs +++ b/crates/smb-rpc/src/ndr64/align.rs @@ -87,55 +87,30 @@ pub type Ndr64Align = NdrAlign; #[cfg(test)] mod tests { - use std::io::Cursor; + use smb_tests::*; use super::*; - const ALIGNED_MAGIC: u32 = 0x12345678; + #[binrw::binrw] + #[derive(Debug, PartialEq, Eq)] + struct TestNdrAlign { + unalign: u8, + unalign2: u16, + should_align: NdrAlign, + } - #[test] - fn test_align() { - #[binrw::binrw] - #[derive(Debug, PartialEq, Eq)] + test_binrw! { struct TestNdrAlign { - unalign: u8, - unalign2: u16, - should_align: NdrAlign, - } - - let data = TestNdrAlign { unalign: 0, unalign2: 0, should_align: NdrAlign { - value: ALIGNED_MAGIC, + value: 0x12345678, }, - }; - - let mut cursor = Cursor::new(vec![]); - data.write_le(&mut cursor).unwrap(); - - let write_result = cursor.into_inner(); - assert_eq!( - write_result, - [ - 0x00, // unalign - 0x00, 0x00, // unalign2 - 0x00, 0x00, 0x00, 0x00, 0x00, // alignment - 0x78, 0x56, 0x34, 0x12, // aligned value - ] - ); - - let mut cursor = Cursor::new(&write_result); - let read_result: TestNdrAlign = TestNdrAlign::read_le(&mut cursor).unwrap(); - assert_eq!( - read_result, - TestNdrAlign { - unalign: 0, - unalign2: 0, - should_align: NdrAlign { - value: ALIGNED_MAGIC - } - } - ) + } => " + 00 + 00 00 + 00 00 00 00 00 + 78 56 34 12 + " // unalign; unalign2; alignment; aligned value } } diff --git a/crates/smb-rpc/src/ndr64/arrays.rs b/crates/smb-rpc/src/ndr64/arrays.rs index 4a230061..a9b158a4 100644 --- a/crates/smb-rpc/src/ndr64/arrays.rs +++ b/crates/smb-rpc/src/ndr64/arrays.rs @@ -231,39 +231,39 @@ where #[cfg(test)] mod tests { + use smb_tests::*; + use crate::ndr64::NdrString; use super::*; - use std::io::Cursor; - #[test] - fn test_array_structure_with_ptrs() { - #[binrw::binrw] - #[derive(Debug, PartialEq, Eq)] - #[bw(import(stage: NdrPtrWriteStage))] - #[br(import(prev: Option<&Self>))] - struct InArrayElement { - #[bw(args_raw(NdrPtrWriteArgs(stage, ())))] - #[br(args(prev.map(|x| &x.ptr_to_value), NdrPtrReadMode::WithArraySupport, ()))] - ptr_to_value: NdrPtr, - #[bw(if(stage == NdrPtrWriteStage::ArraySupportWriteRefId))] - #[br(args(prev.map(|x| &**x.random_byte)))] - random_byte: NdrArrayStructureElement, - #[bw(args_raw(NdrPtrWriteArgs(stage, ())))] - #[br(args(prev.map(|x| &x.string_val), NdrPtrReadMode::WithArraySupport, ()))] - string_val: NdrPtr>, - } + #[binrw::binrw] + #[derive(Debug, PartialEq, Eq)] + #[bw(import(stage: NdrPtrWriteStage))] + #[br(import(prev: Option<&Self>))] + struct InArrayElement { + #[bw(args_raw(NdrPtrWriteArgs(stage, ())))] + #[br(args(prev.map(|x| &x.ptr_to_value), NdrPtrReadMode::WithArraySupport, ()))] + ptr_to_value: NdrPtr, + #[bw(if(stage == NdrPtrWriteStage::ArraySupportWriteRefId))] + #[br(args(prev.map(|x| &**x.random_byte)))] + random_byte: NdrArrayStructureElement, + #[bw(args_raw(NdrPtrWriteArgs(stage, ())))] + #[br(args(prev.map(|x| &x.string_val), NdrPtrReadMode::WithArraySupport, ()))] + string_val: NdrPtr>, + } - #[binrw::binrw] - #[derive(Debug, PartialEq, Eq)] - struct WithArray { - #[bw(calc = (array.len() as u32).into())] - size: NdrAlign, - #[br(args(*size as u64))] // TODO: prevent default to 0 - array: NdrArray, - } + #[binrw::binrw] + #[derive(Debug, PartialEq, Eq)] + struct WithArray { + #[bw(calc = (array.len() as u32).into())] + size: NdrAlign, + #[br(args(*size as u64))] // TODO: prevent default to 0 + array: NdrArray, + } - let array = WithArray { + test_binrw! { + struct WithArray { array: vec![ InArrayElement { ptr_to_value: 42.into(), @@ -277,66 +277,6 @@ mod tests { }, ] .into(), - }; - let mut cursor = Cursor::new(vec![]); - array.write_le(&mut cursor).unwrap(); - - let (hello_data, world_data) = { - let mut cursor = Cursor::new(vec![]); - Into::>>::into("Hello".parse::>().unwrap()) - .write_le(&mut cursor) - .unwrap(); - let hello_data = cursor.into_inner(); - let mut cursor = Cursor::new(vec![]); - Into::>>::into("World".parse::>().unwrap()) - .write_le(&mut cursor) - .unwrap(); - - let world_data = cursor.into_inner(); - (hello_data, world_data) - }; - - let exp_data = [ - // our size - 0x02, 0x00, 0x00, 0x00, // size of array (2 elements) - 0x00, 0x00, 0x00, 0x00, // aligned to 8 bytes - // array max_count - 0x02, 0x00, 0x00, 0x00, // size of array (2 elements) - 0x00, 0x00, 0x00, 0x00, // aligned to 8 bytes - // struct#1 - 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, - 0x00, // ptr ref to first element's dword ptr - 0x01, // random byte of first element - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // aligned to 8 bytes - 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, - 0x00, // ptr ref to first element's string - // struct#2 - 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, - 0x00, // ptr ref to second element's dword ptr - 0x02, // random byte of second element - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // aligned to 8 bytes - 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, - 0x00, // ptr ref to second element's string - 42, 0, 0, 0, // value of first element - 0, 0, 0, 0, // aligned to 8 bytes - ] - .into_iter() - .chain(hello_data) - .collect::>(); - let pad_to_ndr = exp_data.len() % 8; - let exp_data = exp_data - .into_iter() - .chain(std::iter::repeat(0).take(pad_to_ndr)) - .chain([ - 84, 0, 0, 0, 0, 0, 0, 0, // aligned to 8 bytes - ]) - .chain(world_data) - .collect::>(); - - assert_eq!(cursor.into_inner(), exp_data); - - let mut cursor = Cursor::new(exp_data); - let read_array: WithArray = BinRead::read_le(&mut cursor).unwrap(); - assert_eq!(read_array, array); + } => "020000000000000002000000000000000000020000000000010000000000000000000200000000000000020000000000020000000000000000000200000000002a00000000000000060000000000000000000000000000000600000000000000480065006c006c006f00000000000000540000000000000006000000000000000000000000000000060000000000000057006f0072006c0064000000" } } diff --git a/crates/smb-rpc/src/ndr64/ptr.rs b/crates/smb-rpc/src/ndr64/ptr.rs index 4eadb5bb..79dcb105 100644 --- a/crates/smb-rpc/src/ndr64/ptr.rs +++ b/crates/smb-rpc/src/ndr64/ptr.rs @@ -240,8 +240,9 @@ where #[cfg(test)] mod tests { + use smb_tests::*; + use super::*; - use std::io::Cursor; #[binrw::binrw] #[derive(Debug, PartialEq, Eq)] @@ -251,40 +252,25 @@ mod tests { other: u32, } - #[test] - fn test_nullptr_no_array() { - let data = TestNdrU32Ptr { + test_binrw! { + TestNdrU32Ptr => nullptr: TestNdrU32Ptr { unalign: 0x1, null_ptr: None.into(), other: 0x12345678, - }; - - let mut cursor = Cursor::new(vec![]); - data.write_le(&mut cursor).unwrap(); - let write_result = cursor.into_inner(); - assert_eq!( - write_result, - [ + } => [ 0x1, // unaligned byte 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // alignment padding 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // null pointer, no data! 0x78, 0x56, 0x34, 0x12 // other value ] - ); } - #[test] - fn test_value_no_array() { - let data = TestNdrU32Ptr { + test_binrw! { + TestNdrU32Ptr => with_data: TestNdrU32Ptr { unalign: 0x2, null_ptr: Some(0xdeadbeef).into(), other: 0x12345678, - }; - let mut cursor = Cursor::new(vec![]); - data.write_le(&mut cursor).unwrap(); - let write_result = cursor.into_inner(); - assert_eq!( - write_result, + } => [ 0x2, // unaligned byte 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // alignment padding @@ -292,6 +278,5 @@ mod tests { 0xef, 0xbe, 0xad, 0xde, // value data 0x78, 0x56, 0x34, 0x12 // other value ] - ); } } diff --git a/crates/smb-tests/src/binrw.rs b/crates/smb-tests/src/binrw.rs index bf6593b7..a05b7e96 100644 --- a/crates/smb-tests/src/binrw.rs +++ b/crates/smb-tests/src/binrw.rs @@ -28,7 +28,7 @@ macro_rules! hex_to_u8_array { ) => { { let s = $expr_for_string; - $crate::binrw::__hex_stream_decode(s) + $crate::__hex_stream_decode(s) } } } @@ -214,10 +214,3 @@ macro_rules! test_binrw_read_fail { } }; } - -pub use binrw_read_and_assert_eq; -pub use binrw_write_and_assert_eq; -pub use test_binrw; -pub use test_binrw_read; -pub use test_binrw_read_fail; -pub use test_binrw_write; diff --git a/crates/smb-tests/src/lib.rs b/crates/smb-tests/src/lib.rs index 3643631e..0fbf35cd 100644 --- a/crates/smb-tests/src/lib.rs +++ b/crates/smb-tests/src/lib.rs @@ -1 +1,3 @@ -pub mod binrw; +mod binrw; + +pub use binrw::*; diff --git a/crates/smb-transport/Cargo.toml b/crates/smb-transport/Cargo.toml index 27978aa6..d6c5d9df 100644 --- a/crates/smb-transport/Cargo.toml +++ b/crates/smb-transport/Cargo.toml @@ -44,6 +44,9 @@ pastey = { workspace = true } maybe-async = { workspace = true } thiserror = { workspace = true } +[dev-dependencies] +smb-tests = { path = "../smb-tests", version = "0.10.2" } + [features] default = ["async", "netbios-transport"] async = ["tokio", "tokio-util", "futures-core", "futures-util"] @@ -51,4 +54,4 @@ is_sync = ["maybe-async/is_sync"] netbios-transport = [] quic = ["dep:quinn", "dep:rustls", "dep:rustls-platform-verifier"] -rdma = ["dep:smb-msg"] # , "dep:async-rdma"] +rdma = ["dep:smb-msg"] # , "dep:async-rdma"] diff --git a/crates/smb-transport/src/netbios/msg.rs b/crates/smb-transport/src/netbios/msg.rs index 203e6897..fe8576d4 100644 --- a/crates/smb-transport/src/netbios/msg.rs +++ b/crates/smb-transport/src/netbios/msg.rs @@ -57,7 +57,7 @@ pub enum NBSSTrailer { } #[binrw::binrw] -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] #[brw(big)] pub struct NBSessionRequest { pub called_name: NetBiosName, @@ -267,77 +267,28 @@ pub struct NBSSSessionRetargetResponse { #[cfg(test)] mod tests { + use smb_tests::*; + use super::*; - use std::io::Cursor; - - #[test] - fn test_nb_name_rw() { - let data = [ - 0x20u8, 0x43, 0x4b, 0x46, 0x44, 0x45, 0x4e, 0x45, 0x43, 0x46, 0x44, 0x45, 0x46, 0x46, - 0x43, 0x46, 0x47, 0x45, 0x46, 0x46, 0x43, 0x43, 0x41, 0x43, 0x41, 0x43, 0x41, 0x43, - 0x41, 0x43, 0x41, 0x43, 0x41, 0x0, - ]; - let name = NetBiosName::read(&mut Cursor::new(&data)).unwrap(); - assert_eq!(name.name, "*SMBSERVER "); - assert_eq!(name.suffix, 0x20); - assert_eq!(name.to_string(), "*SMBSERVER<20>"); - - let mut buf = Vec::new(); - NetBiosName::new(name.name.to_string(), name.suffix) - .write(&mut Cursor::new(&mut buf)) - .unwrap(); - assert_eq!(buf.len(), data.len()); - let parsed: Result = "*SMBSERVER<20>".parse(); - assert_eq!(parsed.unwrap(), name); + + test_binrw! { + NetBiosName: NetBiosName::from_str("*SMBSERVER<20>").unwrap() + => "20434b4644454e454346444546464346474546464343414341434143414341434100" } - #[test] - fn test_nbss_session_request_write() { - let request = NBSessionRequest { + test_binrw! { + struct NBSessionRequest { called_name: NetBiosName::new("*SMBSERVER".to_string(), 0x20), calling_name: NetBiosName::new("MACBOOKPRO-AF8A".to_string(), 0x0), - }; - let mut buf = Vec::new(); - request - .write(&mut Cursor::new(&mut buf)) - .expect("Failed to write NBSessionRequest"); - assert_eq!( - buf, - &[ - 0x20, 0x43, 0x4b, 0x46, 0x44, 0x45, 0x4e, 0x45, 0x43, 0x46, 0x44, 0x45, 0x46, 0x46, - 0x43, 0x46, 0x47, 0x45, 0x46, 0x46, 0x43, 0x43, 0x41, 0x43, 0x41, 0x43, 0x41, 0x43, - 0x41, 0x43, 0x41, 0x43, 0x41, 0x0, 0x20, 0x45, 0x4e, 0x45, 0x42, 0x45, 0x44, 0x45, - 0x43, 0x45, 0x50, 0x45, 0x50, 0x45, 0x4c, 0x46, 0x41, 0x46, 0x43, 0x45, 0x50, 0x43, - 0x4e, 0x45, 0x42, 0x45, 0x47, 0x44, 0x49, 0x45, 0x42, 0x41, 0x41, 0x0, - ] - ); - } - - #[test] - fn test_nbss_header_read() { - let data = [0x82u8, 0x0, 0x0, 0x0]; - let header = NBSSPacketHeader::read(&mut Cursor::new(&data)).unwrap(); - assert_eq!( - header, - NBSSPacketHeader { - ptype: NBSSPacketType::PositiveSessionResponse, - flags: 0x00, - length: 0x0000, - } - ); + } => "20434b4644454e45434644454646434647454646434341434143414341434143410020454e + 45424544454345504550454c464146434550434e4542454744494542414100" } - #[test] - fn test_nbss_header_write() { - let header = NBSSPacketHeader { + test_binrw! { + struct NBSSPacketHeader { ptype: NBSSPacketType::PositiveSessionResponse, flags: 0x00, length: 0x0000, - }; - let mut buf = Vec::new(); - header - .write(&mut Cursor::new(&mut buf)) - .expect("Failed to write NBSSPacketHeader"); - assert_eq!(buf, [0x82, 0x0, 0x0, 0x0]); + } => "82000000" } } diff --git a/crates/smb-transport/src/tcp/msg.rs b/crates/smb-transport/src/tcp/msg.rs index 7f1b286e..67bee89d 100644 --- a/crates/smb-transport/src/tcp/msg.rs +++ b/crates/smb-transport/src/tcp/msg.rs @@ -1,7 +1,7 @@ use binrw::prelude::*; #[binrw::binrw] -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] #[brw(big, magic(b"\x00"))] pub struct SmbTcpMessageHeader { #[br(parse_with = binrw::helpers::read_u24)] @@ -16,23 +16,13 @@ impl SmbTcpMessageHeader { #[cfg(test)] mod tests { - use std::io::Cursor; + use smb_tests::*; use super::*; - #[test] - fn test_transport_header_write() { - let header = SmbTcpMessageHeader { - stream_protocol_length: 0x123456, - }; - let mut buf = Vec::new(); - header.write(&mut Cursor::new(&mut buf)).unwrap(); - assert_eq!(&[0x00, 0x12, 0x34, 0x56], &buf.as_ref()); - } - #[test] - fn test_transport_header_read() { - let buf = [0x00, 0x12, 0x34, 0x56]; - let header = SmbTcpMessageHeader::read(&mut Cursor::new(&buf)).unwrap(); - assert_eq!(header.stream_protocol_length, 0x123456); + test_binrw! { + struct SmbTcpMessageHeader { + stream_protocol_length: 0x123456, + } => "00 12 34 56" } } diff --git a/crates/smb/Cargo.toml b/crates/smb/Cargo.toml index a66b7755..38aef124 100644 --- a/crates/smb/Cargo.toml +++ b/crates/smb/Cargo.toml @@ -12,11 +12,11 @@ categories.workspace = true readme.workspace = true [dependencies] -smb-msg = { path = "../smb-msg", version = "0.10.3" } +smb-msg = { path = "../smb-msg", version = "0.10.3", default-features = false, features = ["client"]} smb-dtyp = { path = "../smb-dtyp", version = "0.10.3" } smb-rpc = { path = "../smb-rpc", version = "0.10.3" } smb-fscc = { path = "../smb-fscc", version = "0.10.3" } -smb-transport = { path = "../smb-transport", version = "0.10.3", default-features = false} +smb-transport = { path = "../smb-transport", version = "0.10.3", default-features = false } # Encoding/Decoding binrw = { workspace = true } diff --git a/crates/smb/src/connection.rs b/crates/smb/src/connection.rs index a7c03289..08f3548c 100644 --- a/crates/smb/src/connection.rs +++ b/crates/smb/src/connection.rs @@ -373,43 +373,26 @@ impl Connection { let mut preauth_integrity_hash = [0u8; 32]; OsRng.fill_bytes(&mut preauth_integrity_hash); let mut ctx_list = vec![ - NegotiateContext { - context_type: NegotiateContextType::PreauthIntegrityCapabilities, - data: NegotiateContextValue::PreauthIntegrityCapabilities( - PreauthIntegrityCapabilities { - hash_algorithms: vec![HashAlgorithm::Sha512], - salt: preauth_integrity_hash.to_vec(), - }, - ), - }, - NegotiateContext { - context_type: NegotiateContextType::NetnameNegotiateContextId, - data: NegotiateContextValue::NetnameNegotiateContextId( - NetnameNegotiateContextId { - netname: client_netname.into(), - }, - ), - }, - NegotiateContext { - context_type: NegotiateContextType::EncryptionCapabilities, - data: NegotiateContextValue::EncryptionCapabilities(EncryptionCapabilities { - ciphers: encrypting_algorithms, - }), - }, - NegotiateContext { - context_type: NegotiateContextType::CompressionCapabilities, - data: NegotiateContextValue::CompressionCapabilities(CompressionCapabilities { - flags: CompressionCapsFlags::new() - .with_chained(!compression_algorithms.is_empty()), - compression_algorithms, - }), - }, - NegotiateContext { - context_type: NegotiateContextType::SigningCapabilities, - data: NegotiateContextValue::SigningCapabilities(SigningCapabilities { - signing_algorithms, - }), - }, + PreauthIntegrityCapabilities { + hash_algorithms: vec![HashAlgorithm::Sha512], + salt: preauth_integrity_hash.to_vec(), + } + .into(), + NetnameNegotiateContextId { + netname: client_netname.into(), + } + .into(), + EncryptionCapabilities { + ciphers: encrypting_algorithms, + } + .into(), + CompressionCapabilities { + flags: CompressionCapsFlags::new() + .with_chained(!compression_algorithms.is_empty()), + compression_algorithms, + } + .into(), + SigningCapabilities { signing_algorithms }.into(), ]; // QUIC #[cfg(feature = "quic")] diff --git a/crates/smb/src/resource.rs b/crates/smb/src/resource.rs index 2785e251..78932b40 100644 --- a/crates/smb/src/resource.rs +++ b/crates/smb/src/resource.rs @@ -154,7 +154,7 @@ impl Resource { let is_dir = response.file_attributes.directory(); // Get maximal access - let access = match CreateContextRespData::first_mxac(&response.create_contexts.into()) { + let access = match CreateContextResponseData::first_mxac(&response.create_contexts.into()) { Some(response) => response.maximal_access, _ => { log::debug!( diff --git a/crates/smb/src/resource/file_util.rs b/crates/smb/src/resource/file_util.rs index 92a154e6..fe2f60f6 100644 --- a/crates/smb/src/resource/file_util.rs +++ b/crates/smb/src/resource/file_util.rs @@ -517,7 +517,7 @@ mod copy { .await?; if bytes_read < chunk_size { log::warn!( - "Task {task_id}@{channel_id:?}: Read less bytes than expected. File might be corrupt. Expected: {chunk_size}, Read: {bytes_read}" + "Task {task_id}@{channel_id:?}: Read less bytes than expected. File might be corrupt. Expected: {chunk_size}: {bytes_read}" ); } let valid_chunk_end = bytes_read; @@ -588,7 +588,7 @@ mod copy { from.read_at_channel(&mut curr_chunk[..chunk_size], offset, channel)?; if bytes_read < chunk_size { log::warn!( - "Read less bytes than expected. File might be corrupt. Expected: {chunk_size}, Read: {bytes_read}" + "Read less bytes than expected. File might be corrupt. Expected: {chunk_size}: {bytes_read}" ); } to.write_at(&curr_chunk[..bytes_read], offset)?; diff --git a/crates/smb/src/session/setup.rs b/crates/smb/src/session/setup.rs index f22e2331..0e392077 100644 --- a/crates/smb/src/session/setup.rs +++ b/crates/smb/src/session/setup.rs @@ -314,12 +314,13 @@ pub(crate) trait SessionSetupProperties { where T: SessionSetupProperties; - fn _make_default_request(buffer: Vec) -> OutgoingMessage { + fn _make_default_request(buffer: Vec, dfs: bool) -> OutgoingMessage { OutgoingMessage::new( SessionSetupRequest::new( buffer, SessionSecurityMode::new().with_signing_enabled(true), SetupRequestFlags::new(), + NegotiateCapabilities::new().with_dfs(dfs), ) .into(), ) @@ -333,7 +334,8 @@ pub(crate) trait SessionSetupProperties { where T: SessionSetupProperties, { - Ok(Self::_make_default_request(buffer)) + let has_dfs = _setup.conn_info().negotiation.caps.dfs(); + Ok(Self::_make_default_request(buffer, has_dfs)) } async fn init_session( @@ -367,7 +369,9 @@ impl SessionSetupProperties for SmbSessionBind { where T: SessionSetupProperties, { - let mut request = Self::_make_default_request(buffer); + // TODO: what about DFS in previous session? + let has_dfs = _setup.conn_info().negotiation.caps.dfs(); + let mut request = Self::_make_default_request(buffer, has_dfs); request .message .content