#[cfg(feature = "client")]
use binrw::io::TakeSeekExt;
use binrw::{NullWideString, prelude::*};
use modular_bitfield::prelude::*;
use smb_dtyp::binrw_util::prelude::*;
use smb_msg_derive::{smb_message_binrw, smb_request_binrw, smb_response_binrw};
use crate::{Dialect, NegotiateSecurityMode};
use crate::dfsc::{ReqGetDfsReferral, ReqGetDfsReferralEx, RespGetDfsReferral};
use smb_dtyp::*;
use smb_fscc::*;
use super::common::IoctlRequestContent;
use crate::IoctlBuffer;
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
use std::ops::{Deref, DerefMut};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum FsctlCodes {
DfsGetReferrals = 0x00060194,
OffloadRead = 0x00094264,
PipePeek = 0x0011400C,
PipeWait = 0x00110018,
PipeTransceive = 0x0011C017,
SrvCopychunk = 0x001440F2,
SrvEnumerateSnapshots = 0x00144064,
SrvRequestResumeKey = 0x00140078,
SrvReadHash = 0x001441bb,
SrvCopychunkWrite = 0x001480F2,
LmrRequestResiliency = 0x001401D4,
QueryNetworkInterfaceInfo = 0x001401FC,
SetReparsePoint = 0x000900A4,
DfsGetReferralsEx = 0x000601B0,
FileLevelTrim = 0x00098208,
ValidateNegotiateInfo = 0x00140204,
QueryAllocatedRanges = 0x000940CF,
}
#[smb_message_binrw]
pub struct SrvCopychunkCopy {
pub source_key: [u8; SrvCopychunkCopy::SRV_KEY_LENGTH],
#[bw(try_calc = chunks.len().try_into())]
chunk_count: u32,
reserved: u32,
#[br(count = chunk_count)]
pub chunks: Vec<SrvCopychunkItem>,
}
impl SrvCopychunkCopy {
pub const SRV_KEY_LENGTH: usize = 24;
pub const SIZE: usize = Self::SRV_KEY_LENGTH + 4 + 4;
}
#[smb_message_binrw]
pub struct SrvCopychunkItem {
pub source_offset: u64,
pub target_offset: u64,
pub length: u32,
reserved: u32,
}
impl SrvCopychunkItem {
pub const SIZE: usize = size_of::<u64>() * 2 + size_of::<u32>() * 2;
}
impl IoctlRequestContent for SrvCopychunkCopy {
fn get_bin_size(&self) -> u32 {
(Self::SIZE + self.chunks.len() * SrvCopychunkItem::SIZE) as u32
}
}
#[smb_request_binrw]
pub struct SrvReadHashReq {
#[bw(calc = 1)]
#[br(assert(hash_type == 1))]
pub hash_type: u32,
#[br(assert((1..=2).contains(&hash_version)))]
#[bw(assert((1..=2).contains(hash_version)))]
pub hash_version: u32,
pub hash_retrieval_type: SrvHashRetrievalType,
}
impl IoctlRequestContent for SrvReadHashReq {
fn get_bin_size(&self) -> u32 {
size_of::<u32>() as u32 * 3
}
}
#[smb_request_binrw]
#[brw(repr(u32))]
pub enum SrvHashRetrievalType {
HashBased = 1,
FileBased = 2,
}
#[smb_request_binrw]
pub struct NetworkResiliencyRequest {
pub timeout: u32,
reserved: u32,
}
impl IoctlRequestContent for NetworkResiliencyRequest {
fn get_bin_size(&self) -> u32 {
size_of::<u32>() as u32 * 2
}
}
#[smb_request_binrw]
pub struct ValidateNegotiateInfoRequest {
pub capabilities: u32,
pub guid: Guid,
pub security_mode: NegotiateSecurityMode,
#[bw(try_calc = dialects.len().try_into())]
dialect_count: u16,
#[br(count = dialect_count)]
pub dialects: Vec<Dialect>,
}
impl IoctlRequestContent for ValidateNegotiateInfoRequest {
fn get_bin_size(&self) -> u32 {
(size_of::<u32>()
+ Guid::GUID_SIZE
+ 2
+ size_of::<u16>()
+ self.dialects.len() * size_of::<u16>()) as u32
}
}
#[smb_response_binrw]
pub struct SrvSnapshotArray {
pub number_of_snap_shots: u32,
pub number_of_snap_shots_returned: u32,
#[bw(calc = PosMarker::default())]
#[br(temp)]
pub snap_shot_array_size: PosMarker<u32>,
#[br(parse_with = binrw::helpers::until_eof, map_stream = |s| s.take_seek(snap_shot_array_size.value as u64))]
#[bw(write_with = PosMarker::write_size, args(&snap_shot_array_size))]
pub snap_shots: Vec<NullWideString>,
}
#[cfg(all(feature = "client", not(feature = "server")))]
pub trait FsctlResponseContent: for<'a> BinRead<Args<'a> = ()> + std::fmt::Debug {
const FSCTL_CODES: &'static [FsctlCodes];
}
#[cfg(all(feature = "server", not(feature = "client")))]
pub trait FsctlResponseContent: for<'a> BinWrite<Args<'a> = ()> + std::fmt::Debug {
const FSCTL_CODES: &'static [FsctlCodes];
}
#[cfg(all(feature = "client", feature = "server"))]
pub trait FsctlResponseContent:
for<'a> BinRead<Args<'a> = ()> + for<'b> BinWrite<Args<'b> = ()> + std::fmt::Debug
{
const FSCTL_CODES: &'static [FsctlCodes];
}
macro_rules! impl_fsctl_response {
($code:ident, $type:ty) => {
impl FsctlResponseContent for $type {
const FSCTL_CODES: &'static [FsctlCodes] = &[FsctlCodes::$code];
}
};
}
#[smb_response_binrw]
pub struct SrvRequestResumeKey {
pub resume_key: [u8; SrvCopychunkCopy::SRV_KEY_LENGTH],
#[bw(calc = 0)]
#[br(temp)]
context_length: u32,
#[br(count = context_length)]
#[bw(assert(context.len() == context_length as usize))]
pub context: Vec<u8>,
}
impl_fsctl_response!(SrvRequestResumeKey, SrvRequestResumeKey);
#[smb_response_binrw]
pub struct SrvCopychunkResponse {
pub chunks_written: u32,
pub chunk_bytes_written: u32,
pub total_bytes_written: u32,
}
impl_fsctl_response!(SrvCopychunk, SrvCopychunkResponse);
#[smb_response_binrw]
pub struct SrvReadHashRes {
#[bw(calc = 1)]
#[br(assert(hash_type == 1))]
hash_type: u32,
#[br(assert((1..=2).contains(&hash_version)))]
#[bw(assert((1..=2).contains(hash_version)))]
hash_version: u32,
source_file_change_time: FileTime,
source_file_size: u64,
hash_blob_length: PosMarker<u32>,
hash_blob_offset: PosMarker<u32>,
dirty: u16,
#[bw(try_calc = source_file_name.len().try_into())]
source_file_name_length: u16,
#[br(count = source_file_name_length)]
source_file_name: Vec<u8>,
}
impl_fsctl_response!(SrvReadHash, SrvReadHashRes);
#[smb_response_binrw]
pub struct SrvHashRetrieveHashBased {
pub offset: u64,
#[bw(try_calc = blob.len().try_into())]
buffer_length: u32,
reserved: u32,
#[br(count = buffer_length)]
blob: Vec<u8>,
}
impl_fsctl_response!(SrvReadHash, SrvHashRetrieveHashBased);
#[smb_response_binrw]
pub struct SrvHashRetrieveFileBased {
pub file_data_offset: u64,
pub file_data_length: u64,
#[bw(try_calc = buffer.len().try_into())]
buffer_length: u32,
reserved: u32,
#[br(count = buffer_length)]
pub buffer: Vec<u8>,
}
pub type NetworkInterfacesInfo = ChainedItemList<NetworkInterfaceInfo>;
impl_fsctl_response!(QueryNetworkInterfaceInfo, NetworkInterfacesInfo);
#[smb_response_binrw]
pub struct NetworkInterfaceInfo {
pub if_index: u32,
pub capability: NetworkInterfaceCapability,
reserved: u32,
pub link_speed: u64,
pub sockaddr: SocketAddrStorage,
}
#[smb_dtyp::mbitfield]
pub struct NetworkInterfaceCapability {
pub rss: bool,
pub rdma: bool,
#[skip]
__: B30,
}
#[smb_response_binrw]
pub enum SocketAddrStorage {
V4(SocketAddrStorageV4),
V6(SocketAddrStorageV6),
}
impl SocketAddrStorage {
pub fn socket_addr(&self) -> SocketAddr {
match self {
SocketAddrStorage::V4(v4) => SocketAddr::V4(v4.to_addr()),
SocketAddrStorage::V6(v6) => SocketAddr::V6(v6.to_addr()),
}
}
}
#[smb_response_binrw]
#[brw(magic(b"\x02\x00"))] pub struct SocketAddrStorageV4 {
pub port: u16,
pub address: u32,
reserved: [u8; 128 - (2 + 2 + 4)],
}
impl SocketAddrStorageV4 {
fn to_addr(&self) -> SocketAddrV4 {
SocketAddrV4::new(Ipv4Addr::from(self.address.to_be()), self.port)
}
}
#[smb_response_binrw]
#[brw(magic(b"\x17\x00"))] pub struct SocketAddrStorageV6 {
pub port: u16,
pub flow_info: u32,
pub address: u128,
pub scope_id: u32,
reserved: [u8; 128 - (2 + 2 + 4 + 16 + 4)],
}
impl SocketAddrStorageV6 {
fn to_addr(&self) -> SocketAddrV6 {
SocketAddrV6::new(
Ipv6Addr::from(self.address.to_be()),
self.port,
self.flow_info,
self.scope_id,
)
}
}
#[smb_response_binrw]
pub struct ValidateNegotiateInfoResponse {
pub capabilities: u32,
pub guid: Guid,
pub security_mode: NegotiateSecurityMode,
pub dialect: Dialect,
}
impl_fsctl_response!(ValidateNegotiateInfo, ValidateNegotiateInfoResponse);
impl FsctlResponseContent for RespGetDfsReferral {
const FSCTL_CODES: &'static [FsctlCodes] =
&[FsctlCodes::DfsGetReferrals, FsctlCodes::DfsGetReferralsEx];
}
impl IoctlRequestContent for ReqGetDfsReferral {
fn get_bin_size(&self) -> u32 {
(size_of::<u16>() + (self.request_file_name.len() + 1) * size_of::<u16>()) as u32
}
}
impl IoctlRequestContent for ReqGetDfsReferralEx {
fn get_bin_size(&self) -> u32 {
(size_of::<u16>() * 2 + size_of::<u32>() + self.request_data.get_bin_size()) as u32
}
}
#[smb_message_binrw] #[derive(Default)]
pub struct QueryAllocRangesItem {
pub offset: u64,
pub len: u64,
}
impl IoctlRequestContent for QueryAllocRangesItem {
fn get_bin_size(&self) -> u32 {
(size_of::<u64>() * 2) as u32
}
}
#[smb_response_binrw]
#[derive(Default)]
pub struct QueryAllocRangesResult {
#[br(parse_with = binrw::helpers::until_eof)]
values: Vec<QueryAllocRangesItem>,
}
impl Deref for QueryAllocRangesResult {
type Target = Vec<QueryAllocRangesItem>;
fn deref(&self) -> &Self::Target {
&self.values
}
}
impl From<Vec<QueryAllocRangesItem>> for QueryAllocRangesResult {
fn from(value: Vec<QueryAllocRangesItem>) -> Self {
Self { values: value }
}
}
impl_fsctl_response!(QueryAllocatedRanges, QueryAllocRangesResult);
#[smb_request_binrw]
pub struct PipeWaitRequest {
pub timeout: u64,
#[bw(calc = name.size() as u32)]
#[br(temp)]
name_length: u32,
pub timeout_specified: Boolean,
reserved: u8,
#[br(args {size: SizedStringSize::bytes(name_length)})]
pub name: SizedWideString,
}
impl IoctlRequestContent for PipeWaitRequest {
fn get_bin_size(&self) -> u32 {
(size_of::<u64>()
+ size_of::<u32>()
+ size_of::<Boolean>()
+ size_of::<u8>()
+ self.name.size() as usize) as u32
}
}
#[smb_request_binrw]
pub struct SetReparsePointRequest {
#[bw(assert((reparse_tag & 0x80000000 == 0) == reparse_guid.is_some()))]
pub reparse_tag: u32,
#[bw(calc = reparse_data.len() as u32)]
reparse_data_length: u32,
#[br(if(reparse_tag & 0x80000000 == 0))]
pub reparse_guid: Option<Guid>,
#[br(count = reparse_data_length)]
pub reparse_data: Vec<u8>,
}
impl IoctlRequestContent for SetReparsePointRequest {
fn get_bin_size(&self) -> u32 {
(size_of::<u32>()
+ size_of::<u32>()
+ self.reparse_guid.as_ref().map_or(0, |_| size_of::<Guid>())
+ self.reparse_data.len()) as u32
}
}
#[smb_request_binrw]
pub struct FileLevelTrimRequest {
reserved: u32,
#[bw(calc = ranges.len() as u32)]
num_ranges: u32,
#[br(count = num_ranges)]
pub ranges: Vec<FileLevelTrimRange>,
}
#[smb_request_binrw]
pub struct FileLevelTrimRange {
pub offset: u64,
pub length: u64,
}
impl IoctlRequestContent for FileLevelTrimRequest {
fn get_bin_size(&self) -> u32 {
(size_of::<u32>() + size_of::<u32>() + self.ranges.len() * size_of::<FileLevelTrimRange>())
as u32
}
}
#[smb_response_binrw]
pub struct PipePeekResponse {
pub named_pipe_state: NamedPipeState,
#[bw(calc = data.len() as u32)]
read_data_available: u32,
pub number_of_messages: u32,
pub message_length: u32,
#[br(count = read_data_available as u64)]
pub data: Vec<u8>,
}
impl_fsctl_response!(PipePeek, PipePeekResponse);
#[smb_response_binrw]
pub struct SrvEnumerateSnapshotsResponse {
pub number_of_snap_shots: u32,
pub number_of_snap_shots_returned: u32,
#[bw(calc = PosMarker::default())]
#[br(temp)]
snap_shot_array_size: PosMarker<u32>,
#[br(map_stream = |s| s.take_seek(snap_shot_array_size.value as u64))]
#[bw(write_with = PosMarker::write_size, args(&snap_shot_array_size))]
pub snap_shots: MultiWSz,
}
impl_fsctl_response!(SrvEnumerateSnapshots, SrvEnumerateSnapshotsResponse);
#[smb_response_binrw]
pub struct FileLevelTrimResponse {
pub num_ranges_processed: u32,
}
impl_fsctl_response!(FileLevelTrim, FileLevelTrimResponse);
#[smb_request_binrw]
pub struct OffloadReadRequest {
#[bw(calc = 0x20)]
#[br(assert(_size == 0x20))]
#[br(temp)]
_size: u32,
pub flags: u32,
pub token_time_to_live: u32,
reserved: u32,
pub file_offset: u64,
pub copy_length: u64,
}
impl IoctlRequestContent for OffloadReadRequest {
fn get_bin_size(&self) -> u32 {
(size_of::<u32>() * 4 + size_of::<u64>() * 2) as u32
}
}
#[smb_response_binrw]
pub struct OffloadReadResponse {
#[bw(calc = 528)]
#[br(assert(_size == 528))]
_size: u32,
pub all_zero_beyond_current_range: Boolean,
_padding: u8,
_padding2: u16,
pub transfer_length: u64,
pub token: [u8; 512], }
impl_fsctl_response!(OffloadRead, OffloadReadResponse);
macro_rules! make_newtype {
($attr_type:ident $vis:vis $name:ident($inner:ty)) => {
#[$attr_type]
pub struct $name(pub $inner);
impl $name {
pub fn new(inner: $inner) -> Self {
Self(inner)
}
}
impl From<$inner> for $name {
fn from(inner: $inner) -> Self {
Self(inner)
}
}
impl Deref for $name {
type Target = $inner;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for $name {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
};
}
macro_rules! make_req_newtype {
($vis:vis $name:ident($inner:ty)) => {
make_newtype!(smb_request_binrw $vis $name($inner));
impl IoctlRequestContent for $name {
fn get_bin_size(&self) -> u32 {
self.0.get_bin_size()
}
}
}
}
macro_rules! make_res_newtype {
($fsctl:ident: $vis:vis $name:ident($inner:ty)) => {
make_newtype!(smb_response_binrw $vis $name($inner));
impl FsctlResponseContent for $name {
const FSCTL_CODES: &'static [FsctlCodes] = &[FsctlCodes::$fsctl];
}
}
}
make_req_newtype!(pub PipePeekRequest(()));
make_req_newtype!(pub SrvEnumerateSnapshotsRequest(()));
make_req_newtype!(pub SrvRequestResumeKeyRequest(()));
make_req_newtype!(pub QueryNetworkInterfaceInfoRequest(()));
make_req_newtype!(pub PipeTransceiveRequest(IoctlBuffer));
make_req_newtype!(pub SrvCopyChunkCopyWrite(SrvCopychunkCopy));
make_res_newtype!(
PipeWait: pub PipeWaitResponse(())
);
make_res_newtype!(
PipeTransceive: pub PipeTransceiveResponse(IoctlBuffer)
);
make_res_newtype!(
SetReparsePoint: pub SetReparsePointResponse(())
);
make_res_newtype!(
LmrRequestResiliency: pub LmrRequestResiliencyResponse(())
);
#[cfg(test)]
mod tests {
use super::*;
use crate::*;
test_binrw_request! {
struct OffloadReadRequest {
flags: 0,
token_time_to_live: 0,
file_offset: 0,
copy_length: 10485760,
} => "2000000000000000000000000000000000000000000000000000a00000000000"
}
test_binrw_response! {
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"
}
const CHUNK_SIZE: u32 = 1 << 20; const TOTAL_SIZE: u32 = 10417096;
const BLOCK_NUM: u32 = (TOTAL_SIZE + CHUNK_SIZE - 1) / CHUNK_SIZE;
test_binrw_request! {
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 {
TOTAL_SIZE % CHUNK_SIZE
} else {
CHUNK_SIZE
},
}).collect(),
} => "2d0300001c00000027116a2630d2db01fffe0000000000000a000000000000000
00000000000000000000000000000000000100000000000000010000000000000001000
00000000000010000000000000002000000000000000200000000000000010000000000
00000300000000000000030000000000000001000000000000000400000000000000040
00000000000000100000000000000050000000000000005000000000000000100000000
00000006000000000000000600000000000000010000000000000007000000000000000
70000000000000001000000000000000800000000000000080000000000000001000000
0000000009000000000000000900000000000c8f30e0000000000"
}
test_binrw_response! {
struct SrvCopychunkResponse {
chunks_written: 10,
chunk_bytes_written: 0,
total_bytes_written: 10417096,
} => "0a00000000000000c8f39e00"
}
test_binrw_response! {
struct QueryAllocRangesResult {
values: vec![
QueryAllocRangesItem {
offset: 0,
len: 4096,
},
QueryAllocRangesItem {
offset: 8192,
len: 46801,
},
],
} => "000000000000000000100000000000000020000000000000d1b6000000000000"
}
test_binrw_response! {
NetworkInterfacesInfo: NetworkInterfacesInfo::from(vec![
NetworkInterfaceInfo {
if_index: 2,
capability: NetworkInterfaceCapability::new().with_rdma(true),
link_speed: 1000000000,
sockaddr: SocketAddrStorage::V4(SocketAddrStorageV4 {
port: 0,
address: 0xac10cc84u32.to_be(),
})
},
NetworkInterfaceInfo {
if_index: 2,
capability: NetworkInterfaceCapability::new().with_rdma(true),
link_speed: 1000000000,
sockaddr: SocketAddrStorage::V6(SocketAddrStorageV6 {
port: 0,
flow_info: 0,
address: 0xfe80000000000000020c29fffe9f8bf3u128.to_be(),
scope_id: 0,
})
},
]) => "9800000002000000020000000000000000ca9a3b0000000002000000ac10cc8400000000000000
0000000000000000000000000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000020000000200000000000
00000ca9a3b000000001700000000000000fe80000000000000020c29fffe9f8bf3000000000000000000
0000000000000000000000000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000"
}
}