use crate::error::Result;
use crate::pack::{Pack, ReadCursor, Unpack, WriteCursor};
use crate::types::FileId;
use crate::Error;
pub const SMB2_0_IOCTL_IS_FSCTL: u32 = 0x0000_0001;
pub const FSCTL_PIPE_TRANSCEIVE: u32 = 0x0011_C017;
pub const FSCTL_SRV_COPYCHUNK: u32 = 0x0014_40F2;
pub const FSCTL_SRV_COPYCHUNK_WRITE: u32 = 0x0014_80F2;
pub const FSCTL_DFS_GET_REFERRALS: u32 = 0x0006_0194;
pub const FSCTL_VALIDATE_NEGOTIATE_INFO: u32 = 0x0014_0204;
#[derive(Debug, Clone)]
pub struct IoctlRequest {
pub ctl_code: u32,
pub file_id: FileId,
pub max_input_response: u32,
pub max_output_response: u32,
pub flags: u32,
pub input_data: Vec<u8>,
}
impl IoctlRequest {
pub const STRUCTURE_SIZE: u16 = 57;
const FIXED_SIZE: u32 = 56;
}
impl Pack for IoctlRequest {
fn pack(&self, cursor: &mut WriteCursor) {
let start = cursor.position();
cursor.write_u16_le(Self::STRUCTURE_SIZE);
cursor.write_u16_le(0);
cursor.write_u32_le(self.ctl_code);
cursor.write_u64_le(self.file_id.persistent);
cursor.write_u64_le(self.file_id.volatile);
let input_count = self.input_data.len() as u32;
let input_offset = if input_count > 0 {
(start as u32) + Self::FIXED_SIZE
} else {
0
};
cursor.write_u32_le(input_offset);
cursor.write_u32_le(input_count);
cursor.write_u32_le(self.max_input_response);
cursor.write_u32_le(0);
cursor.write_u32_le(0);
cursor.write_u32_le(self.max_output_response);
cursor.write_u32_le(self.flags);
cursor.write_u32_le(0);
cursor.write_bytes(&self.input_data);
}
}
impl Unpack for IoctlRequest {
fn unpack(cursor: &mut ReadCursor<'_>) -> Result<Self> {
let structure_size = cursor.read_u16_le()?;
if structure_size != Self::STRUCTURE_SIZE {
return Err(Error::invalid_data(format!(
"invalid IoctlRequest structure size: expected {}, got {}",
Self::STRUCTURE_SIZE,
structure_size
)));
}
let _reserved = cursor.read_u16_le()?;
let ctl_code = cursor.read_u32_le()?;
let persistent = cursor.read_u64_le()?;
let volatile = cursor.read_u64_le()?;
let _input_offset = cursor.read_u32_le()?;
let input_count = cursor.read_u32_le()?;
let max_input_response = cursor.read_u32_le()?;
let _output_offset = cursor.read_u32_le()?;
let _output_count = cursor.read_u32_le()?;
let max_output_response = cursor.read_u32_le()?;
let flags = cursor.read_u32_le()?;
let _reserved2 = cursor.read_u32_le()?;
let input_data = if input_count > 0 {
cursor.read_bytes_bounded(input_count as usize)?.to_vec()
} else {
Vec::new()
};
Ok(IoctlRequest {
ctl_code,
file_id: FileId {
persistent,
volatile,
},
max_input_response,
max_output_response,
flags,
input_data,
})
}
}
#[derive(Debug, Clone)]
pub struct IoctlResponse {
pub ctl_code: u32,
pub file_id: FileId,
pub flags: u32,
pub output_data: Vec<u8>,
}
impl IoctlResponse {
pub const STRUCTURE_SIZE: u16 = 49;
const FIXED_SIZE: u32 = 48;
}
impl Pack for IoctlResponse {
fn pack(&self, cursor: &mut WriteCursor) {
let start = cursor.position();
cursor.write_u16_le(Self::STRUCTURE_SIZE);
cursor.write_u16_le(0);
cursor.write_u32_le(self.ctl_code);
cursor.write_u64_le(self.file_id.persistent);
cursor.write_u64_le(self.file_id.volatile);
let output_count = self.output_data.len() as u32;
let output_offset = if output_count > 0 {
(start as u32) + Self::FIXED_SIZE
} else {
0
};
cursor.write_u32_le(0);
cursor.write_u32_le(0);
cursor.write_u32_le(output_offset);
cursor.write_u32_le(output_count);
cursor.write_u32_le(self.flags);
cursor.write_u32_le(0);
cursor.write_bytes(&self.output_data);
}
}
impl Unpack for IoctlResponse {
fn unpack(cursor: &mut ReadCursor<'_>) -> Result<Self> {
let structure_size = cursor.read_u16_le()?;
if structure_size != Self::STRUCTURE_SIZE {
return Err(Error::invalid_data(format!(
"invalid IoctlResponse structure size: expected {}, got {}",
Self::STRUCTURE_SIZE,
structure_size
)));
}
let _reserved = cursor.read_u16_le()?;
let ctl_code = cursor.read_u32_le()?;
let persistent = cursor.read_u64_le()?;
let volatile = cursor.read_u64_le()?;
let _input_offset = cursor.read_u32_le()?;
let _input_count = cursor.read_u32_le()?;
let _output_offset = cursor.read_u32_le()?;
let output_count = cursor.read_u32_le()?;
let flags = cursor.read_u32_le()?;
let _reserved2 = cursor.read_u32_le()?;
let output_data = if output_count > 0 {
cursor.read_bytes_bounded(output_count as usize)?.to_vec()
} else {
Vec::new()
};
Ok(IoctlResponse {
ctl_code,
file_id: FileId {
persistent,
volatile,
},
flags,
output_data,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ioctl_request_roundtrip_with_input_data() {
let original = IoctlRequest {
ctl_code: FSCTL_PIPE_TRANSCEIVE,
file_id: FileId {
persistent: 0x1122_3344_5566_7788,
volatile: 0xAABB_CCDD_EEFF_0011,
},
max_input_response: 0,
max_output_response: 4096,
flags: SMB2_0_IOCTL_IS_FSCTL,
input_data: vec![0x01, 0x02, 0x03, 0x04, 0x05],
};
let mut w = WriteCursor::new();
original.pack(&mut w);
let bytes = w.into_inner();
assert_eq!(bytes.len(), 61);
let mut r = ReadCursor::new(&bytes);
let decoded = IoctlRequest::unpack(&mut r).unwrap();
assert_eq!(decoded.ctl_code, FSCTL_PIPE_TRANSCEIVE);
assert_eq!(decoded.file_id, original.file_id);
assert_eq!(decoded.max_input_response, 0);
assert_eq!(decoded.max_output_response, 4096);
assert_eq!(decoded.flags, SMB2_0_IOCTL_IS_FSCTL);
assert_eq!(decoded.input_data, vec![0x01, 0x02, 0x03, 0x04, 0x05]);
}
#[test]
fn ioctl_request_roundtrip_no_input_data() {
let original = IoctlRequest {
ctl_code: FSCTL_VALIDATE_NEGOTIATE_INFO,
file_id: FileId::SENTINEL,
max_input_response: 0,
max_output_response: 256,
flags: SMB2_0_IOCTL_IS_FSCTL,
input_data: Vec::new(),
};
let mut w = WriteCursor::new();
original.pack(&mut w);
let bytes = w.into_inner();
assert_eq!(bytes.len(), 56);
let mut r = ReadCursor::new(&bytes);
let decoded = IoctlRequest::unpack(&mut r).unwrap();
assert_eq!(decoded.ctl_code, FSCTL_VALIDATE_NEGOTIATE_INFO);
assert_eq!(decoded.file_id, FileId::SENTINEL);
assert!(decoded.input_data.is_empty());
}
#[test]
fn ioctl_request_wrong_structure_size() {
let mut buf = [0u8; 56];
buf[0..2].copy_from_slice(&99u16.to_le_bytes());
let mut cursor = ReadCursor::new(&buf);
let result = IoctlRequest::unpack(&mut cursor);
assert!(result.is_err());
let err = result.unwrap_err().to_string();
assert!(err.contains("structure size"), "error was: {err}");
}
#[test]
fn ioctl_response_roundtrip_with_output_data() {
let original = IoctlResponse {
ctl_code: FSCTL_PIPE_TRANSCEIVE,
file_id: FileId {
persistent: 0x42,
volatile: 0x99,
},
flags: SMB2_0_IOCTL_IS_FSCTL,
output_data: vec![0xDE, 0xAD, 0xBE, 0xEF],
};
let mut w = WriteCursor::new();
original.pack(&mut w);
let bytes = w.into_inner();
assert_eq!(bytes.len(), 52);
let mut r = ReadCursor::new(&bytes);
let decoded = IoctlResponse::unpack(&mut r).unwrap();
assert_eq!(decoded.ctl_code, FSCTL_PIPE_TRANSCEIVE);
assert_eq!(decoded.file_id, original.file_id);
assert_eq!(decoded.flags, SMB2_0_IOCTL_IS_FSCTL);
assert_eq!(decoded.output_data, vec![0xDE, 0xAD, 0xBE, 0xEF]);
}
#[test]
fn ioctl_response_roundtrip_no_output_data() {
let original = IoctlResponse {
ctl_code: FSCTL_SRV_COPYCHUNK,
file_id: FileId::default(),
flags: 0,
output_data: Vec::new(),
};
let mut w = WriteCursor::new();
original.pack(&mut w);
let bytes = w.into_inner();
assert_eq!(bytes.len(), 48);
let mut r = ReadCursor::new(&bytes);
let decoded = IoctlResponse::unpack(&mut r).unwrap();
assert_eq!(decoded.ctl_code, FSCTL_SRV_COPYCHUNK);
assert!(decoded.output_data.is_empty());
}
#[test]
fn ioctl_response_wrong_structure_size() {
let mut buf = [0u8; 48];
buf[0..2].copy_from_slice(&42u16.to_le_bytes());
let mut cursor = ReadCursor::new(&buf);
let result = IoctlResponse::unpack(&mut cursor);
assert!(result.is_err());
let err = result.unwrap_err().to_string();
assert!(err.contains("structure size"), "error was: {err}");
}
}