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 paste::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 InvalidSmb = 0x00010002: "Invalid SMB",
125 SmbBadTid = 0x00050002: "SMB Bad TID",
126 SmbBadCommand = 0x00160002: "SMB Bad Command",
127 SmbBadUid = 0x005B0002: "SMB Bad UID",
128 SmbUseStandard = 0x00FB0002: "SMB Use Standard",
129 BufferOverflow = 0x80000005: "Buffer Overflow",
130 NoMoreFiles = 0x80000006: "No More Files",
131 StoppedOnSymlink = 0x8000002D: "Stopped on Symlink",
132 NotImplemented = 0xC0000002: "Not Implemented",
133 InfoLengthMismatch = 0xC0000004: "Info Length Mismatch",
134 InvalidParameter = 0xC000000D: "Invalid Parameter",
135 NoSuchDevice = 0xC000000E: "No Such Device",
136 InvalidDeviceRequest0 = 0xC0000010: "Invalid Device Request",
137 EndOfFile = 0xC0000011: "End of File",
138 MoreProcessingRequired = 0xC0000016: "More Processing Required",
139 AccessDenied = 0xC0000022: "Access Denied",
140 BufferTooSmall = 0xC0000023: "Buffer Too Small",
141 ObjectNameInvalid = 0xC0000033: "Object Name Invalid",
142 ObjectNameNotFound = 0xC0000034: "Object Name Not Found",
143 ObjectNameCollision = 0xC0000035: "Object Name Collision",
144 SharingViloation = 0xC0000043: "Sharing Violation",
145 ObjectPathNotFound = 0xC000003A: "Object Path Not Found",
146 LogonFailure = 0xC000006D: "Logon Failure",
147 BadImpersonationLevel = 0xC00000A5: "Bad Impersonation Level",
148 IoTimeout = 0xC00000B5: "I/O Timeout",
149 FileIsADirectory = 0xC00000BA: "File is a Directory",
150 NotSupported = 0xC00000BB: "Not Supported",
151 NetworkNameDeleted = 0xC00000C9: "Network Name Deleted",
152 BadNetworkName = 0xC00000CC: "Bad Network Name",
153 RequestNotAccepted = 0xC00000D0: "Request Not Accepted",
154 DirectoryNotEmpty = 0xC0000101: "Directory Not Empty",
155 Cancelled = 0xC0000120: "Cancelled",
156 UserSessionDeleted = 0xC0000203: "User Session Deleted",
157 UserAccountLockedOut = 0xC0000234: "User Account Locked Out",
158 PathNotCovered = 0xC0000257: "Path Not Covered",
159 NetworkSessionExpired = 0xC000035C: "Network Session Expired",
160 SmbTooManyUids = 0xC000205A: "SMB Too Many UIDs",
161 DeviceFeatureNotSupported = 0xC0000463: "Device Feature Not Supported",
162}
163
164#[binrw::binrw]
167#[derive(Debug, Clone, PartialEq, Eq)]
168#[brw(magic(b"\xfeSMB"), little)]
169pub struct Header {
170 #[bw(calc = Self::STRUCT_SIZE as u16)]
171 #[br(assert(_structure_size == Self::STRUCT_SIZE as u16))]
172 _structure_size: u16,
173 pub credit_charge: u16,
174 pub status: u32,
176 pub command: Command,
177 pub credit_request: u16,
178 pub flags: HeaderFlags,
179 pub next_command: u32,
180 pub message_id: u64,
181
182 #[brw(if(!flags.async_command()))]
184 #[bw(calc = 0)]
185 _reserved: u32,
186 #[br(if(!flags.async_command()))]
187 #[bw(assert(tree_id.is_some() != flags.async_command()))]
188 pub tree_id: Option<u32>,
189
190 #[brw(if(flags.async_command()))]
192 #[bw(assert(tree_id.is_none() == flags.async_command()))]
193 pub async_id: Option<u64>,
194
195 pub session_id: u64,
196 pub signature: u128,
197}
198
199impl Header {
200 pub const STRUCT_SIZE: usize = 64;
201
202 pub fn status(&self) -> crate::Result<Status> {
205 self.status.try_into()
206 }
207}
208
209#[bitfield]
210#[derive(BinWrite, BinRead, Debug, Default, Clone, Copy, PartialEq, Eq)]
211#[bw(map = |&x| Self::into_bytes(x))]
212#[br(map = Self::from_bytes)]
213pub struct HeaderFlags {
214 pub server_to_redir: bool,
215 pub async_command: bool,
216 pub related_operations: bool,
217 pub signed: bool,
218 pub priority_mask: B3,
219 #[skip]
220 __: B21,
221 pub dfs_operation: bool,
222 pub replay_operation: bool,
223 #[skip]
224 __: B2,
225}
226
227#[cfg(test)]
228mod tests {
229 use super::*;
230 use std::io::Cursor;
231
232 #[test]
233 pub fn test_async_header_parse() {
234 let arr = &[
235 0xfe, 0x53, 0x4d, 0x42, 0x40, 0x0, 0x0, 0x0, 0x3, 0x1, 0x0, 0x0, 0xf, 0x0, 0x1, 0x0,
236 0x13, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8,
237 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xd7, 0x27, 0x53, 0x8, 0x0, 0x0, 0x0, 0x0, 0x63,
238 0xf8, 0x25, 0xde, 0xae, 0x2, 0x95, 0x2f, 0xa3, 0xd8, 0xc8, 0xaa, 0xf4, 0x6e, 0x7c,
239 0x99,
240 ];
241 let mut cursor = Cursor::new(arr);
242 let header = Header::read_le(&mut cursor).unwrap();
243 assert_eq!(
244 header,
245 Header {
246 credit_charge: 0,
247 status: Status::Pending as u32,
248 command: Command::ChangeNotify,
249 credit_request: 1,
250 flags: HeaderFlags::new()
251 .with_async_command(true)
252 .with_server_to_redir(true)
253 .with_priority_mask(1),
254 next_command: 0,
255 message_id: 8,
256 tree_id: None,
257 async_id: Some(8),
258 session_id: 0x00000000085327d7,
259 signature: u128::from_le_bytes(u128::to_be_bytes(
260 0x63f825deae02952fa3d8c8aaf46e7c99
261 )),
262 }
263 )
264 }
265}