use crate::error::Result;
use crate::msg::header::Header;
use crate::pack::{Pack, ReadCursor, Unpack, WriteCursor};
use crate::types::flags::{ShareCapabilities, ShareFlags};
use crate::Error;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum ShareType {
Disk = 0x01,
Pipe = 0x02,
Print = 0x03,
}
impl ShareType {
pub fn try_from_u8(val: u8) -> Result<Self> {
match val {
0x01 => Ok(ShareType::Disk),
0x02 => Ok(ShareType::Pipe),
0x03 => Ok(ShareType::Print),
other => Err(Error::invalid_data(format!(
"invalid share type: 0x{:02X}",
other
))),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct TreeConnectRequestFlags(pub u16);
impl TreeConnectRequestFlags {
pub const CLUSTER_RECONNECT: u16 = 0x0001;
pub const REDIRECT_TO_OWNER: u16 = 0x0002;
pub const EXTENSION_PRESENT: u16 = 0x0004;
}
#[derive(Debug, Clone)]
pub struct TreeConnectRequest {
pub flags: TreeConnectRequestFlags,
pub path: String,
}
impl TreeConnectRequest {
pub const STRUCTURE_SIZE: u16 = 9;
}
impl Pack for TreeConnectRequest {
fn pack(&self, cursor: &mut WriteCursor) {
cursor.write_u16_le(Self::STRUCTURE_SIZE);
cursor.write_u16_le(self.flags.0);
let path_u16: Vec<u16> = self.path.encode_utf16().collect();
let path_byte_len = path_u16.len() * 2;
let offset = (Header::SIZE + 8) as u16; cursor.write_u16_le(offset);
cursor.write_u16_le(path_byte_len as u16);
cursor.write_utf16_le(&self.path);
}
}
impl Unpack for TreeConnectRequest {
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 TreeConnectRequest structure size: expected {}, got {}",
Self::STRUCTURE_SIZE,
structure_size
)));
}
let flags = TreeConnectRequestFlags(cursor.read_u16_le()?);
let _offset = cursor.read_u16_le()?;
let path_length = cursor.read_u16_le()? as usize;
if path_length > ReadCursor::MAX_UNPACK_BUFFER {
return Err(Error::invalid_data(format!(
"buffer size {} exceeds maximum {} bytes",
path_length,
ReadCursor::MAX_UNPACK_BUFFER
)));
}
let path = cursor.read_utf16_le(path_length)?;
Ok(TreeConnectRequest { flags, path })
}
}
#[derive(Debug, Clone)]
pub struct TreeConnectResponse {
pub share_type: ShareType,
pub share_flags: ShareFlags,
pub capabilities: ShareCapabilities,
pub maximal_access: u32,
}
impl TreeConnectResponse {
pub const STRUCTURE_SIZE: u16 = 16;
}
impl Pack for TreeConnectResponse {
fn pack(&self, cursor: &mut WriteCursor) {
cursor.write_u16_le(Self::STRUCTURE_SIZE);
cursor.write_u8(self.share_type as u8);
cursor.write_u8(0);
cursor.write_u32_le(self.share_flags.bits());
cursor.write_u32_le(self.capabilities.bits());
cursor.write_u32_le(self.maximal_access);
}
}
impl Unpack for TreeConnectResponse {
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 TreeConnectResponse structure size: expected {}, got {}",
Self::STRUCTURE_SIZE,
structure_size
)));
}
let share_type = ShareType::try_from_u8(cursor.read_u8()?)?;
let _reserved = cursor.read_u8()?;
let share_flags = ShareFlags::new(cursor.read_u32_le()?);
let capabilities = ShareCapabilities::new(cursor.read_u32_le()?);
let maximal_access = cursor.read_u32_le()?;
Ok(TreeConnectResponse {
share_type,
share_flags,
capabilities,
maximal_access,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tree_connect_request_roundtrip() {
let original = TreeConnectRequest {
flags: TreeConnectRequestFlags::default(),
path: r"\\server\share".to_string(),
};
let mut w = WriteCursor::new();
original.pack(&mut w);
let bytes = w.into_inner();
let mut r = ReadCursor::new(&bytes);
let decoded = TreeConnectRequest::unpack(&mut r).unwrap();
assert_eq!(decoded.flags, original.flags);
assert_eq!(decoded.path, original.path);
}
#[test]
fn tree_connect_request_with_utf16_path() {
let path = r"\\myserver.example.com\IPC$";
let original = TreeConnectRequest {
flags: TreeConnectRequestFlags::default(),
path: path.to_string(),
};
let mut w = WriteCursor::new();
original.pack(&mut w);
let bytes = w.into_inner();
let mut r = ReadCursor::new(&bytes);
let decoded = TreeConnectRequest::unpack(&mut r).unwrap();
assert_eq!(decoded.path, path);
}
#[test]
fn tree_connect_request_structure_size_field() {
let req = TreeConnectRequest {
flags: TreeConnectRequestFlags::default(),
path: r"\\s\d".to_string(),
};
let mut w = WriteCursor::new();
req.pack(&mut w);
let bytes = w.into_inner();
assert_eq!(u16::from_le_bytes([bytes[0], bytes[1]]), 9);
}
#[test]
fn tree_connect_request_wrong_structure_size() {
let mut buf = [0u8; 20];
buf[0..2].copy_from_slice(&99u16.to_le_bytes());
let mut cursor = ReadCursor::new(&buf);
let result = TreeConnectRequest::unpack(&mut cursor);
assert!(result.is_err());
let err = result.unwrap_err().to_string();
assert!(err.contains("structure size"), "error was: {err}");
}
#[test]
fn tree_connect_request_with_flags() {
let original = TreeConnectRequest {
flags: TreeConnectRequestFlags(TreeConnectRequestFlags::CLUSTER_RECONNECT),
path: r"\\s\d".to_string(),
};
let mut w = WriteCursor::new();
original.pack(&mut w);
let bytes = w.into_inner();
let mut r = ReadCursor::new(&bytes);
let decoded = TreeConnectRequest::unpack(&mut r).unwrap();
assert_eq!(decoded.flags.0, TreeConnectRequestFlags::CLUSTER_RECONNECT);
}
#[test]
fn tree_connect_response_roundtrip_disk() {
let original = TreeConnectResponse {
share_type: ShareType::Disk,
share_flags: ShareFlags::new(ShareFlags::DFS | ShareFlags::ACCESS_BASED_DIRECTORY_ENUM),
capabilities: ShareCapabilities::new(ShareCapabilities::DFS),
maximal_access: 0x001F_01FF,
};
let mut w = WriteCursor::new();
original.pack(&mut w);
let bytes = w.into_inner();
let mut r = ReadCursor::new(&bytes);
let decoded = TreeConnectResponse::unpack(&mut r).unwrap();
assert_eq!(decoded.share_type, ShareType::Disk);
assert_eq!(decoded.share_flags.bits(), original.share_flags.bits());
assert_eq!(decoded.capabilities.bits(), original.capabilities.bits());
assert_eq!(decoded.maximal_access, 0x001F_01FF);
}
#[test]
fn tree_connect_response_roundtrip_pipe() {
let original = TreeConnectResponse {
share_type: ShareType::Pipe,
share_flags: ShareFlags::default(),
capabilities: ShareCapabilities::default(),
maximal_access: 0x0012_019F,
};
let mut w = WriteCursor::new();
original.pack(&mut w);
let bytes = w.into_inner();
let mut r = ReadCursor::new(&bytes);
let decoded = TreeConnectResponse::unpack(&mut r).unwrap();
assert_eq!(decoded.share_type, ShareType::Pipe);
assert_eq!(decoded.maximal_access, 0x0012_019F);
}
#[test]
fn tree_connect_response_roundtrip_print() {
let original = TreeConnectResponse {
share_type: ShareType::Print,
share_flags: ShareFlags::new(ShareFlags::ENCRYPT_DATA),
capabilities: ShareCapabilities::new(
ShareCapabilities::CONTINUOUS_AVAILABILITY | ShareCapabilities::CLUSTER,
),
maximal_access: 0,
};
let mut w = WriteCursor::new();
original.pack(&mut w);
let bytes = w.into_inner();
let mut r = ReadCursor::new(&bytes);
let decoded = TreeConnectResponse::unpack(&mut r).unwrap();
assert_eq!(decoded.share_type, ShareType::Print);
assert!(decoded.share_flags.contains(ShareFlags::ENCRYPT_DATA));
assert!(decoded
.capabilities
.contains(ShareCapabilities::CONTINUOUS_AVAILABILITY));
assert!(decoded.capabilities.contains(ShareCapabilities::CLUSTER));
}
#[test]
fn tree_connect_response_structure_size_field() {
let resp = TreeConnectResponse {
share_type: ShareType::Disk,
share_flags: ShareFlags::default(),
capabilities: ShareCapabilities::default(),
maximal_access: 0,
};
let mut w = WriteCursor::new();
resp.pack(&mut w);
let bytes = w.into_inner();
assert_eq!(u16::from_le_bytes([bytes[0], bytes[1]]), 16);
assert_eq!(bytes.len(), 16);
}
#[test]
fn tree_connect_response_wrong_structure_size() {
let mut buf = [0u8; 16];
buf[0..2].copy_from_slice(&99u16.to_le_bytes());
buf[2] = 0x01; let mut cursor = ReadCursor::new(&buf);
let result = TreeConnectResponse::unpack(&mut cursor);
assert!(result.is_err());
let err = result.unwrap_err().to_string();
assert!(err.contains("structure size"), "error was: {err}");
}
#[test]
fn tree_connect_response_invalid_share_type() {
let mut buf = [0u8; 16];
buf[0..2].copy_from_slice(&16u16.to_le_bytes());
buf[2] = 0xFF; let mut cursor = ReadCursor::new(&buf);
let result = TreeConnectResponse::unpack(&mut cursor);
assert!(result.is_err());
let err = result.unwrap_err().to_string();
assert!(err.contains("share type"), "error was: {err}");
}
#[test]
fn tree_connect_response_known_bytes() {
let bytes: Vec<u8> = vec![
0x10, 0x00, 0x01, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x01, 0x1F, 0x00, ];
let mut r = ReadCursor::new(&bytes);
let decoded = TreeConnectResponse::unpack(&mut r).unwrap();
assert_eq!(decoded.share_type, ShareType::Disk);
assert!(decoded
.share_flags
.contains(ShareFlags::ACCESS_BASED_DIRECTORY_ENUM));
assert_eq!(decoded.maximal_access, 0x001F_01FF);
}
}