use crate::error::Result;
use crate::msg::header::Header;
use crate::pack::{FileTime, Pack, ReadCursor, Unpack, WriteCursor};
use crate::types::flags::FileAccessMask;
use crate::types::{FileId, OplockLevel};
use crate::Error;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum ImpersonationLevel {
Anonymous = 0,
Identification = 1,
Impersonation = 2,
Delegate = 3,
}
impl TryFrom<u32> for ImpersonationLevel {
type Error = Error;
fn try_from(value: u32) -> Result<Self> {
match value {
0 => Ok(Self::Anonymous),
1 => Ok(Self::Identification),
2 => Ok(Self::Impersonation),
3 => Ok(Self::Delegate),
_ => Err(Error::invalid_data(format!(
"invalid ImpersonationLevel: {}",
value
))),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct ShareAccess(pub u32);
impl ShareAccess {
pub const FILE_SHARE_READ: u32 = 0x0000_0001;
pub const FILE_SHARE_WRITE: u32 = 0x0000_0002;
pub const FILE_SHARE_DELETE: u32 = 0x0000_0004;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum CreateDisposition {
FileSupersede = 0,
FileOpen = 1,
FileCreate = 2,
FileOpenIf = 3,
FileOverwrite = 4,
FileOverwriteIf = 5,
}
impl TryFrom<u32> for CreateDisposition {
type Error = Error;
fn try_from(value: u32) -> Result<Self> {
match value {
0 => Ok(Self::FileSupersede),
1 => Ok(Self::FileOpen),
2 => Ok(Self::FileCreate),
3 => Ok(Self::FileOpenIf),
4 => Ok(Self::FileOverwrite),
5 => Ok(Self::FileOverwriteIf),
_ => Err(Error::invalid_data(format!(
"invalid CreateDisposition: {}",
value
))),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum CreateAction {
FileSuperseded = 0,
FileOpened = 1,
FileCreated = 2,
FileOverwritten = 3,
}
impl TryFrom<u32> for CreateAction {
type Error = Error;
fn try_from(value: u32) -> Result<Self> {
match value {
0 => Ok(Self::FileSuperseded),
1 => Ok(Self::FileOpened),
2 => Ok(Self::FileCreated),
3 => Ok(Self::FileOverwritten),
_ => Err(Error::invalid_data(format!(
"invalid CreateAction: {}",
value
))),
}
}
}
#[derive(Debug, Clone)]
pub struct CreateRequest {
pub requested_oplock_level: OplockLevel,
pub impersonation_level: ImpersonationLevel,
pub desired_access: FileAccessMask,
pub file_attributes: u32,
pub share_access: ShareAccess,
pub create_disposition: CreateDisposition,
pub create_options: u32,
pub name: String,
pub create_contexts: Vec<u8>,
}
impl CreateRequest {
pub const STRUCTURE_SIZE: u16 = 57;
}
impl Pack for CreateRequest {
fn pack(&self, cursor: &mut WriteCursor) {
let start = cursor.position();
cursor.write_u16_le(Self::STRUCTURE_SIZE);
cursor.write_u8(0);
cursor.write_u8(self.requested_oplock_level as u8);
cursor.write_u32_le(self.impersonation_level as u32);
cursor.write_u64_le(0);
cursor.write_u64_le(0);
cursor.write_u32_le(self.desired_access.bits());
cursor.write_u32_le(self.file_attributes);
cursor.write_u32_le(self.share_access.0);
cursor.write_u32_le(self.create_disposition as u32);
cursor.write_u32_le(self.create_options);
let name_offset_pos = cursor.position();
cursor.write_u16_le(0);
let name_length_pos = cursor.position();
cursor.write_u16_le(0);
let ctx_offset_pos = cursor.position();
cursor.write_u32_le(0);
let ctx_length_pos = cursor.position();
cursor.write_u32_le(0);
let name_offset = Header::SIZE + (cursor.position() - start);
let name_start = cursor.position();
cursor.write_utf16_le(&self.name);
let name_byte_len = cursor.position() - name_start;
cursor.set_u16_le_at(name_offset_pos, name_offset as u16);
cursor.set_u16_le_at(name_length_pos, name_byte_len as u16);
if !self.create_contexts.is_empty() {
cursor.align_to(8);
let ctx_offset = Header::SIZE + (cursor.position() - start);
cursor.write_bytes(&self.create_contexts);
let ctx_len = self.create_contexts.len();
cursor.set_u32_le_at(ctx_offset_pos, ctx_offset as u32);
cursor.set_u32_le_at(ctx_length_pos, ctx_len as u32);
} else if name_byte_len == 0 {
cursor.write_u8(0);
}
}
}
impl Unpack for CreateRequest {
fn unpack(cursor: &mut ReadCursor<'_>) -> Result<Self> {
let start = cursor.position();
let structure_size = cursor.read_u16_le()?;
if structure_size != Self::STRUCTURE_SIZE {
return Err(Error::invalid_data(format!(
"invalid CreateRequest structure size: expected {}, got {}",
Self::STRUCTURE_SIZE,
structure_size
)));
}
let _security_flags = cursor.read_u8()?;
let oplock_raw = cursor.read_u8()?;
let requested_oplock_level = OplockLevel::try_from(oplock_raw)?;
let imp_raw = cursor.read_u32_le()?;
let impersonation_level = ImpersonationLevel::try_from(imp_raw)?;
let _smb_create_flags = cursor.read_u64_le()?;
let _reserved = cursor.read_u64_le()?;
let desired_access = FileAccessMask::new(cursor.read_u32_le()?);
let file_attributes = cursor.read_u32_le()?;
let share_access = ShareAccess(cursor.read_u32_le()?);
let disp_raw = cursor.read_u32_le()?;
let create_disposition = CreateDisposition::try_from(disp_raw)?;
let create_options = cursor.read_u32_le()?;
let name_offset = cursor.read_u16_le()? as usize;
let name_length = cursor.read_u16_le()? as usize;
let ctx_offset = cursor.read_u32_le()? as usize;
let ctx_length = cursor.read_u32_le()? as usize;
let name = if name_length > 0 {
let current = cursor.position();
let body_offset = name_offset.saturating_sub(Header::SIZE);
let target = start + body_offset;
if target > current {
cursor.skip(target - current)?;
}
cursor.read_utf16_le(name_length)?
} else {
String::new()
};
let create_contexts = if ctx_length > 0 {
let current = cursor.position();
let body_offset = ctx_offset.saturating_sub(Header::SIZE);
let target = start + body_offset;
if target > current {
cursor.skip(target - current)?;
}
cursor.read_bytes_bounded(ctx_length)?.to_vec()
} else {
Vec::new()
};
Ok(CreateRequest {
requested_oplock_level,
impersonation_level,
desired_access,
file_attributes,
share_access,
create_disposition,
create_options,
name,
create_contexts,
})
}
}
#[derive(Debug, Clone)]
pub struct CreateResponse {
pub oplock_level: OplockLevel,
pub flags: u8,
pub create_action: CreateAction,
pub creation_time: FileTime,
pub last_access_time: FileTime,
pub last_write_time: FileTime,
pub change_time: FileTime,
pub allocation_size: u64,
pub end_of_file: u64,
pub file_attributes: u32,
pub file_id: FileId,
pub create_contexts: Vec<u8>,
}
impl CreateResponse {
pub const STRUCTURE_SIZE: u16 = 89;
}
impl Pack for CreateResponse {
fn pack(&self, cursor: &mut WriteCursor) {
let start = cursor.position();
cursor.write_u16_le(Self::STRUCTURE_SIZE);
cursor.write_u8(self.oplock_level as u8);
cursor.write_u8(self.flags);
cursor.write_u32_le(self.create_action as u32);
self.creation_time.pack(cursor);
self.last_access_time.pack(cursor);
self.last_write_time.pack(cursor);
self.change_time.pack(cursor);
cursor.write_u64_le(self.allocation_size);
cursor.write_u64_le(self.end_of_file);
cursor.write_u32_le(self.file_attributes);
cursor.write_u32_le(0);
cursor.write_u64_le(self.file_id.persistent);
cursor.write_u64_le(self.file_id.volatile);
let ctx_offset_pos = cursor.position();
cursor.write_u32_le(0);
let ctx_length_pos = cursor.position();
cursor.write_u32_le(0);
if !self.create_contexts.is_empty() {
cursor.align_to(8);
let ctx_offset = Header::SIZE + (cursor.position() - start);
cursor.write_bytes(&self.create_contexts);
let ctx_len = self.create_contexts.len();
cursor.set_u32_le_at(ctx_offset_pos, ctx_offset as u32);
cursor.set_u32_le_at(ctx_length_pos, ctx_len as u32);
}
}
}
impl Unpack for CreateResponse {
fn unpack(cursor: &mut ReadCursor<'_>) -> Result<Self> {
let start = cursor.position();
let structure_size = cursor.read_u16_le()?;
if structure_size != Self::STRUCTURE_SIZE {
return Err(Error::invalid_data(format!(
"invalid CreateResponse structure size: expected {}, got {}",
Self::STRUCTURE_SIZE,
structure_size
)));
}
let oplock_level = OplockLevel::try_from(cursor.read_u8()?)?;
let flags = cursor.read_u8()?;
let create_action = CreateAction::try_from(cursor.read_u32_le()?)?;
let creation_time = FileTime::unpack(cursor)?;
let last_access_time = FileTime::unpack(cursor)?;
let last_write_time = FileTime::unpack(cursor)?;
let change_time = FileTime::unpack(cursor)?;
let allocation_size = cursor.read_u64_le()?;
let end_of_file = cursor.read_u64_le()?;
let file_attributes = cursor.read_u32_le()?;
let _reserved2 = cursor.read_u32_le()?;
let persistent = cursor.read_u64_le()?;
let volatile = cursor.read_u64_le()?;
let file_id = FileId {
persistent,
volatile,
};
let ctx_offset = cursor.read_u32_le()? as usize;
let ctx_length = cursor.read_u32_le()? as usize;
let create_contexts = if ctx_length > 0 {
let current = cursor.position();
let body_offset = ctx_offset.saturating_sub(Header::SIZE);
let target = start + body_offset;
if target > current {
cursor.skip(target - current)?;
}
cursor.read_bytes_bounded(ctx_length)?.to_vec()
} else {
Vec::new()
};
Ok(CreateResponse {
oplock_level,
flags,
create_action,
creation_time,
last_access_time,
last_write_time,
change_time,
allocation_size,
end_of_file,
file_attributes,
file_id,
create_contexts,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn create_request_roundtrip_no_contexts() {
let original = CreateRequest {
requested_oplock_level: OplockLevel::Exclusive,
impersonation_level: ImpersonationLevel::Impersonation,
desired_access: FileAccessMask::new(
FileAccessMask::GENERIC_READ | FileAccessMask::FILE_READ_ATTRIBUTES,
),
file_attributes: 0x80, share_access: ShareAccess(ShareAccess::FILE_SHARE_READ | ShareAccess::FILE_SHARE_WRITE),
create_disposition: CreateDisposition::FileOpenIf,
create_options: 0,
name: "test\\file.txt".to_string(),
create_contexts: Vec::new(),
};
let mut w = WriteCursor::new();
original.pack(&mut w);
let bytes = w.into_inner();
let mut r = ReadCursor::new(&bytes);
let decoded = CreateRequest::unpack(&mut r).unwrap();
assert_eq!(
decoded.requested_oplock_level,
original.requested_oplock_level
);
assert_eq!(decoded.impersonation_level, original.impersonation_level);
assert_eq!(decoded.desired_access, original.desired_access);
assert_eq!(decoded.file_attributes, original.file_attributes);
assert_eq!(decoded.share_access, original.share_access);
assert_eq!(decoded.create_disposition, original.create_disposition);
assert_eq!(decoded.create_options, original.create_options);
assert_eq!(decoded.name, original.name);
assert!(decoded.create_contexts.is_empty());
}
#[test]
fn create_request_roundtrip_with_create_contexts() {
let fake_ctx = vec![
0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x04, 0x00, 0x00, 0x00, 0x18, 0x00, 0x04, 0x00, 0x00, 0x00, b'M', b'x', b'A', b'c', 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, ];
let original = CreateRequest {
requested_oplock_level: OplockLevel::Batch,
impersonation_level: ImpersonationLevel::Delegate,
desired_access: FileAccessMask::new(FileAccessMask::GENERIC_ALL),
file_attributes: 0x20, share_access: ShareAccess(ShareAccess::FILE_SHARE_DELETE),
create_disposition: CreateDisposition::FileCreate,
create_options: 0x0000_0040, name: "share\\docs\\report.docx".to_string(),
create_contexts: fake_ctx.clone(),
};
let mut w = WriteCursor::new();
original.pack(&mut w);
let bytes = w.into_inner();
let mut r = ReadCursor::new(&bytes);
let decoded = CreateRequest::unpack(&mut r).unwrap();
assert_eq!(decoded.requested_oplock_level, OplockLevel::Batch);
assert_eq!(decoded.impersonation_level, ImpersonationLevel::Delegate);
assert_eq!(decoded.name, "share\\docs\\report.docx");
assert_eq!(decoded.create_contexts, fake_ctx);
}
#[test]
fn create_request_structure_size_field() {
let req = CreateRequest {
requested_oplock_level: OplockLevel::None,
impersonation_level: ImpersonationLevel::Anonymous,
desired_access: FileAccessMask::default(),
file_attributes: 0,
share_access: ShareAccess::default(),
create_disposition: CreateDisposition::FileOpen,
create_options: 0,
name: "x".to_string(),
create_contexts: Vec::new(),
};
let mut w = WriteCursor::new();
req.pack(&mut w);
let bytes = w.into_inner();
assert_eq!(bytes[0], 57);
assert_eq!(bytes[1], 0);
}
#[test]
fn create_request_wrong_structure_size() {
let mut buf = vec![0u8; 64];
buf[0..2].copy_from_slice(&99u16.to_le_bytes());
let mut cursor = ReadCursor::new(&buf);
let result = CreateRequest::unpack(&mut cursor);
assert!(result.is_err());
let err = result.unwrap_err().to_string();
assert!(err.contains("structure size"), "error was: {err}");
}
#[test]
fn create_response_roundtrip() {
let original = CreateResponse {
oplock_level: OplockLevel::LevelII,
flags: 0,
create_action: CreateAction::FileOpened,
creation_time: FileTime(133_485_408_000_000_000),
last_access_time: FileTime(133_485_408_100_000_000),
last_write_time: FileTime(133_485_408_200_000_000),
change_time: FileTime(133_485_408_300_000_000),
allocation_size: 4096,
end_of_file: 1234,
file_attributes: 0x20, file_id: FileId {
persistent: 0x1111_2222_3333_4444,
volatile: 0x5555_6666_7777_8888,
},
create_contexts: Vec::new(),
};
let mut w = WriteCursor::new();
original.pack(&mut w);
let bytes = w.into_inner();
let mut r = ReadCursor::new(&bytes);
let decoded = CreateResponse::unpack(&mut r).unwrap();
assert_eq!(decoded.oplock_level, original.oplock_level);
assert_eq!(decoded.flags, original.flags);
assert_eq!(decoded.create_action, original.create_action);
assert_eq!(decoded.creation_time, original.creation_time);
assert_eq!(decoded.last_access_time, original.last_access_time);
assert_eq!(decoded.last_write_time, original.last_write_time);
assert_eq!(decoded.change_time, original.change_time);
assert_eq!(decoded.allocation_size, original.allocation_size);
assert_eq!(decoded.end_of_file, original.end_of_file);
assert_eq!(decoded.file_attributes, original.file_attributes);
assert_eq!(decoded.file_id, original.file_id);
assert!(decoded.create_contexts.is_empty());
}
#[test]
fn create_response_with_contexts() {
let ctx_data = vec![0xAA, 0xBB, 0xCC, 0xDD];
let original = CreateResponse {
oplock_level: OplockLevel::None,
flags: 0x01,
create_action: CreateAction::FileCreated,
creation_time: FileTime(100),
last_access_time: FileTime(200),
last_write_time: FileTime(300),
change_time: FileTime(400),
allocation_size: 0,
end_of_file: 0,
file_attributes: 0,
file_id: FileId {
persistent: 1,
volatile: 2,
},
create_contexts: ctx_data.clone(),
};
let mut w = WriteCursor::new();
original.pack(&mut w);
let bytes = w.into_inner();
let mut r = ReadCursor::new(&bytes);
let decoded = CreateResponse::unpack(&mut r).unwrap();
assert_eq!(decoded.create_action, CreateAction::FileCreated);
assert_eq!(decoded.file_id.persistent, 1);
assert_eq!(decoded.file_id.volatile, 2);
assert_eq!(decoded.create_contexts, ctx_data);
}
#[test]
fn create_response_structure_size_field() {
let resp = CreateResponse {
oplock_level: OplockLevel::None,
flags: 0,
create_action: CreateAction::FileOpened,
creation_time: FileTime::ZERO,
last_access_time: FileTime::ZERO,
last_write_time: FileTime::ZERO,
change_time: FileTime::ZERO,
allocation_size: 0,
end_of_file: 0,
file_attributes: 0,
file_id: FileId::default(),
create_contexts: Vec::new(),
};
let mut w = WriteCursor::new();
resp.pack(&mut w);
let bytes = w.into_inner();
assert_eq!(bytes[0], 89);
assert_eq!(bytes[1], 0);
}
#[test]
fn create_response_wrong_structure_size() {
let mut buf = vec![0u8; 96];
buf[0..2].copy_from_slice(&42u16.to_le_bytes());
let mut cursor = ReadCursor::new(&buf);
let result = CreateResponse::unpack(&mut cursor);
assert!(result.is_err());
let err = result.unwrap_err().to_string();
assert!(err.contains("structure size"), "error was: {err}");
}
#[test]
fn oplock_level_roundtrip() {
for &level in &[
OplockLevel::None,
OplockLevel::LevelII,
OplockLevel::Exclusive,
OplockLevel::Batch,
OplockLevel::Lease,
] {
let raw = level as u8;
let decoded = OplockLevel::try_from(raw).unwrap();
assert_eq!(decoded, level);
}
}
#[test]
fn oplock_level_invalid() {
assert!(OplockLevel::try_from(0x42).is_err());
}
#[test]
fn impersonation_level_roundtrip() {
for &level in &[
ImpersonationLevel::Anonymous,
ImpersonationLevel::Identification,
ImpersonationLevel::Impersonation,
ImpersonationLevel::Delegate,
] {
let raw = level as u32;
let decoded = ImpersonationLevel::try_from(raw).unwrap();
assert_eq!(decoded, level);
}
}
#[test]
fn create_disposition_roundtrip() {
for &disp in &[
CreateDisposition::FileSupersede,
CreateDisposition::FileOpen,
CreateDisposition::FileCreate,
CreateDisposition::FileOpenIf,
CreateDisposition::FileOverwrite,
CreateDisposition::FileOverwriteIf,
] {
let raw = disp as u32;
let decoded = CreateDisposition::try_from(raw).unwrap();
assert_eq!(decoded, disp);
}
}
#[test]
fn create_action_roundtrip() {
for &action in &[
CreateAction::FileSuperseded,
CreateAction::FileOpened,
CreateAction::FileCreated,
CreateAction::FileOverwritten,
] {
let raw = action as u32;
let decoded = CreateAction::try_from(raw).unwrap();
assert_eq!(decoded, action);
}
}
}