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