1use std::io::Cursor;
2
3use binrw::prelude::*;
4use modular_bitfield::prelude::*;
5
6#[derive(BinRead, BinWrite, Debug, PartialEq, Eq, Clone, Copy)]
7#[brw(repr(u16))]
8pub enum Command {
9 Negotiate = 0,
10 SessionSetup = 1,
11 Logoff = 2,
12 TreeConnect = 3,
13 TreeDisconnect = 4,
14 Create = 5,
15 Close = 6,
16 Flush = 7,
17 Read = 8,
18 Write = 9,
19 Lock = 0xA,
20 Ioctl = 0xB,
21 Cancel = 0xC,
22 Echo = 0xD,
23 QueryDirectory = 0xE,
24 ChangeNotify = 0xF,
25 QueryInfo = 0x10,
26 SetInfo = 0x11,
27 OplockBreak = 0x12,
28 ServerToClientNotification = 0x13,
29}
30
31impl std::fmt::Display for Command {
32 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33 let message_as_string = match self {
34 Command::Negotiate => "Negotiate",
35 Command::SessionSetup => "Session Setup",
36 Command::Logoff => "Logoff",
37 Command::TreeConnect => "Tree Connect",
38 Command::TreeDisconnect => "Tree Disconnect",
39 Command::Create => "Create",
40 Command::Close => "Close",
41 Command::Flush => "Flush",
42 Command::Read => "Read",
43 Command::Write => "Write",
44 Command::Lock => "Lock",
45 Command::Ioctl => "Ioctl",
46 Command::Cancel => "Cancel",
47 Command::Echo => "Echo",
48 Command::QueryDirectory => "Query Directory",
49 Command::ChangeNotify => "Change Notify",
50 Command::QueryInfo => "Query Info",
51 Command::SetInfo => "Set Info",
52 Command::OplockBreak => "Oplock Break",
53 Command::ServerToClientNotification => "Server to Client Notification",
54 };
55 write!(f, "{} ({:#x})", message_as_string, *self as u16)
56 }
57}
58
59macro_rules! make_status {
60 (
61 $($name:ident = $value:literal: $description:literal, )+
62 ) => {
63
64#[binrw::binrw]
66#[derive(Debug, PartialEq, Eq, Clone, Copy)]
67#[repr(u32)]
68#[brw(repr(u32))]
69pub enum Status {
70 $(
71 $name = $value,
72 )+
73}
74
75impl std::fmt::Display for Status {
76 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77 let message_as_string = match self {
78 $(
79 Status::$name => $description,
80 )+
81 };
82 write!(f, "{} ({:#x})", message_as_string, *self as u32)
83 }
84}
85
86impl Status {
87 pastey::paste! {
89 $(
90 #[doc = concat!("`", stringify!($name), "` as u32")]
91 pub const [<U32_ $name:snake:upper>]: u32 = $value;
92 )+
93 }
94
95 pub fn try_display_as_status(value: u32) -> String {
101 match Self::try_from(value) {
102 Ok(status) => format!("{}", status),
103 Err(_) => format!("{:#06x}", value),
104 }
105 }
106}
107
108impl TryFrom<u32> for Status {
109 type Error = crate::SmbMsgError;
110
111 fn try_from(value: u32) -> Result<Self, Self::Error> {
112 Status::read_le(&mut Cursor::new(value.to_le_bytes())).map_err(|_| {
113 Self::Error::MissingErrorCodeDefinition(value)
114 })
115 }
116}
117 };
118}
119
120make_status! {
121 Success = 0x00000000: "Success",
122 Pending = 0x00000103: "Pending",
123 NotifyCleanup = 0x0000010B: "Notify Cleanup",
124 NotifyEnumDir = 0x0000010C: "Notify Enum Dir",
125 InvalidSmb = 0x00010002: "Invalid SMB",
126 SmbBadTid = 0x00050002: "SMB Bad TID",
127 SmbBadCommand = 0x00160002: "SMB Bad Command",
128 SmbBadUid = 0x005B0002: "SMB Bad UID",
129 SmbUseStandard = 0x00FB0002: "SMB Use Standard",
130 BufferOverflow = 0x80000005: "Buffer Overflow",
131 NoMoreFiles = 0x80000006: "No More Files",
132 StoppedOnSymlink = 0x8000002D: "Stopped on Symlink",
133 NotImplemented = 0xC0000002: "Not Implemented",
134 InvalidInfoClass = 0xC0000003: "Invalid Info Class",
135 InfoLengthMismatch = 0xC0000004: "Info Length Mismatch",
136 InvalidParameter = 0xC000000D: "Invalid Parameter",
137 NoSuchDevice = 0xC000000E: "No Such Device",
138 InvalidDeviceRequest0 = 0xC0000010: "Invalid Device Request",
139 EndOfFile = 0xC0000011: "End of File",
140 MoreProcessingRequired = 0xC0000016: "More Processing Required",
141 AccessDenied = 0xC0000022: "Access Denied",
142 BufferTooSmall = 0xC0000023: "Buffer Too Small",
143 ObjectNameInvalid = 0xC0000033: "Object Name Invalid",
144 ObjectNameNotFound = 0xC0000034: "Object Name Not Found",
145 ObjectNameCollision = 0xC0000035: "Object Name Collision",
146 SharingViolation = 0xC0000043: "Sharing Violation",
147 ObjectPathNotFound = 0xC000003A: "Object Path Not Found",
148 NoEasOnFile = 0xC0000044: "No EAs on File",
149 LogonFailure = 0xC000006D: "Logon Failure",
150 BadImpersonationLevel = 0xC00000A5: "Bad Impersonation Level",
151 IoTimeout = 0xC00000B5: "I/O Timeout",
152 FileIsADirectory = 0xC00000BA: "File is a Directory",
153 NotSupported = 0xC00000BB: "Not Supported",
154 NetworkNameDeleted = 0xC00000C9: "Network Name Deleted",
155 BadNetworkName = 0xC00000CC: "Bad Network Name",
156 RequestNotAccepted = 0xC00000D0: "Request Not Accepted",
157 DirectoryNotEmpty = 0xC0000101: "Directory Not Empty",
158 Cancelled = 0xC0000120: "Cancelled",
159 UserSessionDeleted = 0xC0000203: "User Session Deleted",
160 UserAccountLockedOut = 0xC0000234: "User Account Locked Out",
161 PathNotCovered = 0xC0000257: "Path Not Covered",
162 NetworkSessionExpired = 0xC000035C: "Network Session Expired",
163 SmbTooManyUids = 0xC000205A: "SMB Too Many UIDs",
164 DeviceFeatureNotSupported = 0xC0000463: "Device Feature Not Supported",
165}
166
167#[binrw::binrw]
169#[derive(Debug, Clone, PartialEq, Eq)]
170#[brw(magic(b"\xfeSMB"), little)]
171pub struct Header {
172 #[bw(calc = Self::STRUCT_SIZE as u16)]
173 #[br(assert(_structure_size == Self::STRUCT_SIZE as u16))]
174 _structure_size: u16,
175 pub credit_charge: u16,
176 pub status: u32,
178 pub command: Command,
179 pub credit_request: u16,
180 pub flags: HeaderFlags,
181 pub next_command: u32,
182 pub message_id: u64,
183
184 #[brw(if(!flags.async_command()))]
186 #[bw(calc = 0)]
187 _reserved: u32,
188 #[br(if(!flags.async_command()))]
189 #[bw(assert(tree_id.is_some() != flags.async_command()))]
190 pub tree_id: Option<u32>,
191
192 #[brw(if(flags.async_command()))]
194 #[bw(assert(tree_id.is_none() == flags.async_command()))]
195 pub async_id: Option<u64>,
196
197 pub session_id: u64,
198 pub signature: u128,
199}
200
201impl Header {
202 pub const STRUCT_SIZE: usize = 64;
203
204 pub fn status(&self) -> crate::Result<Status> {
207 self.status.try_into()
208 }
209
210 pub fn to_async(&mut self, async_id: u64) {
214 self.flags.set_async_command(true);
215 self.tree_id = None;
216 self.async_id = Some(async_id);
217 }
218}
219
220#[bitfield]
221#[derive(BinWrite, BinRead, Debug, Default, Clone, Copy, PartialEq, Eq)]
222#[bw(map = |&x| Self::into_bytes(x))]
223#[br(map = Self::from_bytes)]
224pub struct HeaderFlags {
225 pub server_to_redir: bool,
226 pub async_command: bool,
227 pub related_operations: bool,
228 pub signed: bool,
229 pub priority_mask: B3,
230 #[skip]
231 __: B21,
232 pub dfs_operation: bool,
233 pub replay_operation: bool,
234 #[skip]
235 __: B2,
236}
237
238#[cfg(test)]
239mod tests {
240 use super::*;
241 use std::io::Cursor;
242
243 #[test]
244 pub fn test_async_header_parse() {
245 let arr = &[
246 0xfe, 0x53, 0x4d, 0x42, 0x40, 0x0, 0x0, 0x0, 0x3, 0x1, 0x0, 0x0, 0xf, 0x0, 0x1, 0x0,
247 0x13, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8,
248 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xd7, 0x27, 0x53, 0x8, 0x0, 0x0, 0x0, 0x0, 0x63,
249 0xf8, 0x25, 0xde, 0xae, 0x2, 0x95, 0x2f, 0xa3, 0xd8, 0xc8, 0xaa, 0xf4, 0x6e, 0x7c,
250 0x99,
251 ];
252 let mut cursor = Cursor::new(arr);
253 let header = Header::read_le(&mut cursor).unwrap();
254 assert_eq!(
255 header,
256 Header {
257 credit_charge: 0,
258 status: Status::Pending as u32,
259 command: Command::ChangeNotify,
260 credit_request: 1,
261 flags: HeaderFlags::new()
262 .with_async_command(true)
263 .with_server_to_redir(true)
264 .with_priority_mask(1),
265 next_command: 0,
266 message_id: 8,
267 tree_id: None,
268 async_id: Some(8),
269 session_id: 0x00000000085327d7,
270 signature: u128::from_le_bytes(u128::to_be_bytes(
271 0x63f825deae02952fa3d8c8aaf46e7c99
272 )),
273 }
274 )
275 }
276}