Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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).
Expand Down
8 changes: 6 additions & 2 deletions crates/smb-dtyp/src/binrw_util.rs
Original file line number Diff line number Diff line change
@@ -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,
};
Expand Down
74 changes: 74 additions & 0 deletions crates/smb-dtyp/src/binrw_util/boolean.rs
Original file line number Diff line number Diff line change
@@ -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::<Boolean>() == 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::<Self>()];
}

impl BinRead for Boolean {
type Args<'a> = ();

fn read_options<R: Read + Seek>(
reader: &mut R,
_: Endian,
_: Self::Args<'_>,
) -> binrw::BinResult<Self> {
let value: u8 = u8::read_options(reader, Endian::Little, ())?;
Ok(Boolean(value != 0))
}
}

impl BinWrite for Boolean {
type Args<'a> = ();

fn write_options<W: Write + Seek>(
&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<bool> for Boolean {
fn from(value: bool) -> Self {
Boolean(value)
}
}

impl From<Boolean> 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"
}
}
4 changes: 2 additions & 2 deletions crates/smb-dtyp/src/binrw_util/fixed_string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,14 +129,14 @@ impl<const N: usize> std::fmt::Display for FixedAnsiString<N> {

impl<const N: usize> std::fmt::Display for FixedWideString<N> {
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>;

Expand Down
153 changes: 39 additions & 114 deletions crates/smb-dtyp/src/binrw_util/helpers.rs
Original file line number Diff line number Diff line change
@@ -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<()> {
Expand All @@ -23,6 +22,26 @@ pub fn read_u48() -> binrw::BinResult<u64> {
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<u32> it's 1 byte in the stream).
#[binrw::parser(reader, endian)]
pub fn binread_if_has_data<T>() -> BinResult<Option<T>>
where
for<'a> T: BinRead<Args<'a> = ()>,
{
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;
Expand Down Expand Up @@ -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::<Boolean>() == 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::<Self>()];
}

impl BinRead for Boolean {
type Args<'a> = ();

fn read_options<R: Read + Seek>(
reader: &mut R,
_: Endian,
_: Self::Args<'_>,
) -> binrw::BinResult<Self> {
let value: u8 = u8::read_options(reader, Endian::Little, ())?;
Ok(Boolean(value != 0))
}
}

impl BinWrite for Boolean {
type Args<'a> = ();

fn write_options<W: Write + Seek>(
&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<bool> for Boolean {
fn from(value: bool) -> Self {
Boolean(value)
}
}

impl From<Boolean> 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<NullWideString>);

impl BinRead for MultiSz {
type Args<'a> = ();

fn read_options<R: Read + Seek>(
reader: &mut R,
endian: Endian,
_: Self::Args<'_>,
) -> BinResult<Self> {
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<W: Write + Seek>(
&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<NullWideString>;

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<u8>,
}
}

impl From<Vec<NullWideString>> for MultiSz {
fn from(strings: Vec<NullWideString>) -> 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 });
}
}
Loading
Loading