1use metrics::{counter, gauge};
6use smb_server_proto::types::Status;
7use smb_server_proto_smb2::commands::{
8 self as c, ChangeNotifyReq, CloseReq, CreateReq, FlushReq, IoctlReq, LockReq, QueryDirReq,
9 QueryInfoReq, ReadReq, SetInfoReq, WriteReq,
10};
11use smb_server_proto_smb2::session_setup::cmd;
12
13use super::context::{Command, IoContext, Outcome, Resources};
14use super::origin::{Bare, Solicited};
15use super::state::Accepted;
16use super::wire::Decode;
17
18#[derive(Debug)]
20pub struct EchoReq;
21impl Decode for EchoReq {
22 const COMMAND: u16 = cmd::ECHO;
23 fn decode(_frame: &[u8]) -> Result<Self, Status> {
24 Ok(EchoReq)
25 }
26}
27
28#[derive(Debug)]
31pub struct SessionSetupReq;
32impl Decode for SessionSetupReq {
33 const COMMAND: u16 = cmd::SESSION_SETUP;
34 fn decode(_frame: &[u8]) -> Result<Self, Status> {
35 Ok(SessionSetupReq)
36 }
37}
38
39#[derive(Debug)]
42pub struct NegotiateReq;
43impl Decode for NegotiateReq {
44 const COMMAND: u16 = cmd::NEGOTIATE;
45 fn decode(_frame: &[u8]) -> Result<Self, Status> {
46 Ok(NegotiateReq)
47 }
48}
49
50#[derive(Debug)]
52pub struct TreeDisconnectReq;
53impl Decode for TreeDisconnectReq {
54 const COMMAND: u16 = cmd::TREE_DISCONNECT;
55 fn decode(_frame: &[u8]) -> Result<Self, Status> {
56 Ok(TreeDisconnectReq)
57 }
58}
59
60#[derive(Debug)]
63pub struct TreeConnectReq;
64impl Decode for TreeConnectReq {
65 const COMMAND: u16 = cmd::TREE_CONNECT;
66 fn decode(_frame: &[u8]) -> Result<Self, Status> {
67 Ok(TreeConnectReq)
68 }
69}
70
71#[derive(Debug)]
73pub struct LogoffReq;
74impl Decode for LogoffReq {
75 const COMMAND: u16 = cmd::LOGOFF;
76 fn decode(_frame: &[u8]) -> Result<Self, Status> {
77 Ok(LogoffReq)
78 }
79}
80
81#[derive(Debug)]
84pub struct CancelReq;
85impl Decode for CancelReq {
86 const COMMAND: u16 = cmd::CANCEL;
87 fn decode(_frame: &[u8]) -> Result<Self, Status> {
88 Ok(CancelReq)
89 }
90}
91
92#[derive(Debug)]
96pub struct OplockBreakReq;
97impl Decode for OplockBreakReq {
98 const COMMAND: u16 = cmd::OPLOCK_BREAK;
99 fn decode(_frame: &[u8]) -> Result<Self, Status> {
100 Ok(OplockBreakReq)
101 }
102}
103
104pub struct EchoCmd;
106impl Command for EchoCmd {
107 type Request = EchoReq;
108 async fn serve(ctx: IoContext<Accepted, Bare>, _req: EchoReq, _res: &mut Resources<'_>) -> Outcome {
109 Outcome::Final(ctx.respond(Status::SUCCESS, c::build_echo_resp()))
110 }
111}
112
113pub struct FlushCmd;
115impl Command for FlushCmd {
116 type Request = FlushReq;
117 async fn serve(ctx: IoContext<Accepted, Bare>, req: FlushReq, res: &mut Resources<'_>) -> Outcome {
118 let tid = ctx.reply.tree_id;
119 let Some(vfs) = crate::smb2::share_vfs(res.server, res.conn, tid) else {
120 return Outcome::Final(ctx.respond(Status::INVALID_HANDLE, Vec::new()));
121 };
122 if let Some(mut open) = res.conn.handle_take(&req.file_id.0) {
123 let r = vfs.flush(&mut open).await;
124 res.conn.handle_insert(req.file_id.0, open);
125 if let Err(e) = r {
126 return Outcome::Final(ctx.respond(crate::smb2::vfs_err(e), Vec::new()));
127 }
128 }
129 Outcome::Final(ctx.respond(Status::SUCCESS, c::build_flush_resp()))
130 }
131}
132
133pub struct TreeDisconnectCmd;
135impl Command for TreeDisconnectCmd {
136 type Request = TreeDisconnectReq;
137 async fn serve(ctx: IoContext<Accepted, Bare>, _req: TreeDisconnectReq, res: &mut Resources<'_>) -> Outcome {
138 res.conn.tree_remove(ctx.reply.tree_id);
139 Outcome::Final(ctx.respond(Status::SUCCESS, c::build_tree_disconnect_resp()))
140 }
141}
142
143pub struct LogoffCmd;
146impl Command for LogoffCmd {
147 type Request = LogoffReq;
148 async fn serve(ctx: IoContext<Accepted, Bare>, _req: LogoffReq, res: &mut Resources<'_>) -> Outcome {
149 let sid = res.conn.session_id;
150 crate::smb2::close_all_handles(res.conn);
151 res.server.locks.release_session(sid);
152 res.server.share_modes.close_session(sid);
153 res.server.oplocks.release_session(sid);
154 res.server.leases.release_session(sid);
155 res.server.app_instances.close_session(sid);
156 res.server.sessions.remove(sid);
157 crate::session_scope::remove(sid);
158 res.conn.searches.clear();
159 if res.conn.authenticated {
160 gauge!("smb_sessions_active").decrement(1.0);
161 }
162 res.conn.authenticated = false;
163 counter!("smb_logoffs_total").increment(1);
164 Outcome::Final(ctx.respond(Status::SUCCESS, c::build_logoff_resp()))
165 }
166}
167
168pub struct ReadCmd;
170impl Command for ReadCmd {
171 type Request = ReadReq;
172 async fn serve(ctx: IoContext<Accepted, Bare>, _req: ReadReq, res: &mut Resources<'_>) -> Outcome {
173 if let Some(body) = crate::smb2::pipe_read(res.conn, res.frame) {
174 return Outcome::Final(ctx.respond(Status::SUCCESS, body));
175 }
176 let Some(vfs) = crate::smb2::share_vfs(res.server, res.conn, ctx.reply.tree_id) else {
177 return Outcome::Final(ctx.respond(Status::INVALID_HANDLE, Vec::new()));
178 };
179 match crate::smb2::read(res.conn, vfs, res.server, res.frame).await {
180 Ok(body) => Outcome::Final(ctx.respond(Status::SUCCESS, body)),
181 Err(status) => Outcome::Final(ctx.respond(status, Vec::new())),
182 }
183 }
184}
185
186pub struct WriteCmd;
188impl Command for WriteCmd {
189 type Request = WriteReq;
190 async fn serve(ctx: IoContext<Accepted, Bare>, _req: WriteReq, res: &mut Resources<'_>) -> Outcome {
191 if let Some(body) = crate::smb2::pipe_write(res.server, res.conn, res.frame) {
192 return Outcome::Final(ctx.respond(Status::SUCCESS, body));
193 }
194 let Some(vfs) = crate::smb2::share_vfs(res.server, res.conn, ctx.reply.tree_id) else {
195 return Outcome::Final(ctx.respond(Status::INVALID_HANDLE, Vec::new()));
196 };
197 match crate::smb2::write(res.conn, vfs, res.server, res.frame).await {
198 Ok(body) => Outcome::Final(ctx.respond(Status::SUCCESS, body)),
199 Err(status) => Outcome::Final(ctx.respond(status, Vec::new())),
200 }
201 }
202}
203
204pub struct CloseCmd;
208impl Command for CloseCmd {
209 type Request = CloseReq;
210 async fn serve(ctx: IoContext<Accepted, Bare>, _req: CloseReq, res: &mut Resources<'_>) -> Outcome {
211 if crate::smb2::pipe_close(res.conn, res.frame) {
212 let body = c::build_close_resp([0u64; 4], 0, 0, 0);
213 return Outcome::Final(ctx.respond(Status::SUCCESS, body));
214 }
215 let Some(vfs) = crate::smb2::share_vfs(res.server, res.conn, ctx.reply.tree_id) else {
216 return Outcome::Final(ctx.respond(Status::INVALID_HANDLE, Vec::new()));
217 };
218 let close_path = CloseReq::parse(res.frame)
219 .and_then(|r| res.conn.with_handle(&r.file_id.0, |h| (r.file_id.0, h.path.clone(), h.delete_on_close)));
220 match crate::smb2::close(res.conn, vfs, res.frame).await {
221 Ok(body) => {
222 if let Some((fid, path, deleted)) = close_path {
223 complete_pending_notifies(res.conn, fid);
224 res.server.locks.release_owner((res.conn.session_id, fid));
225 res.server.share_modes.close(&path, (res.conn.session_id, fid));
226 res.server.oplocks.release(&path, (res.conn.session_id, fid));
227 res.server.leases.release(&path, (res.conn.session_id, fid));
228 res.server.app_instances.close((res.conn.session_id, fid));
229 res.conn.durable.remove(&fid);
230 let _ = res.server.durables.remove(&fid).await;
231 if deleted {
234 crate::smb2::break_dir_lease(
235 res.server,
236 res.conn,
237 crate::smb2::parent_dir(&path),
238 fid,
239 c::lease::RH,
240 );
241 }
242 }
243 Outcome::Final(ctx.respond(Status::SUCCESS, body))
244 }
245 Err(status) => Outcome::Final(ctx.respond(status, Vec::new())),
246 }
247 }
248}
249
250fn complete_pending_notifies(conn: &mut crate::smb2::Smb2Conn, fid: [u8; 16]) {
253 let Some(ids) = conn.async_by_file.remove(&fid) else {
254 return;
255 };
256 for aid in ids {
257 conn.async_msgids.retain(|_, v| *v != aid);
258 if let Some(tx) = conn.async_cancels.remove(&aid) {
259 let _ = tx.send(Status::NOTIFY_CLEANUP);
260 }
261 }
262}
263
264pub struct QueryDirCmd;
266impl Command for QueryDirCmd {
267 type Request = QueryDirReq;
268 async fn serve(ctx: IoContext<Accepted, Bare>, _req: QueryDirReq, res: &mut Resources<'_>) -> Outcome {
269 let Some(vfs) = crate::smb2::share_vfs(res.server, res.conn, ctx.reply.tree_id) else {
270 return Outcome::Final(ctx.respond(Status::INVALID_HANDLE, Vec::new()));
271 };
272 match crate::smb2::query_directory(res.conn, vfs, res.frame).await {
273 Ok(Some(buffer)) => Outcome::Final(ctx.respond(Status::SUCCESS, c::build_info_resp(&buffer))),
274 Ok(None) => Outcome::Final(ctx.respond(Status::NO_MORE_FILES, Vec::new())),
275 Err(status) => Outcome::Final(ctx.respond(status, Vec::new())),
276 }
277 }
278}
279
280pub struct QueryInfoCmd;
282impl Command for QueryInfoCmd {
283 type Request = QueryInfoReq;
284 async fn serve(ctx: IoContext<Accepted, Bare>, _req: QueryInfoReq, res: &mut Resources<'_>) -> Outcome {
285 let Some(vfs) = crate::smb2::share_vfs(res.server, res.conn, ctx.reply.tree_id) else {
286 return Outcome::Final(ctx.respond(Status::INVALID_HANDLE, Vec::new()));
287 };
288 match crate::smb2::query_info(res.conn, vfs, res.frame).await {
289 Ok(Some(buffer)) => Outcome::Final(ctx.respond(Status::SUCCESS, c::build_info_resp(&buffer))),
290 Ok(None) => Outcome::Final(ctx.respond(Status::NOT_IMPLEMENTED, Vec::new())),
291 Err(status) => Outcome::Final(ctx.respond(status, Vec::new())),
292 }
293 }
294}
295
296pub struct SetInfoCmd;
298impl Command for SetInfoCmd {
299 type Request = SetInfoReq;
300 async fn serve(ctx: IoContext<Accepted, Bare>, _req: SetInfoReq, res: &mut Resources<'_>) -> Outcome {
301 let Some(vfs) = crate::smb2::share_vfs(res.server, res.conn, ctx.reply.tree_id) else {
302 return Outcome::Final(ctx.respond(Status::INVALID_HANDLE, Vec::new()));
303 };
304 let rename = SetInfoReq::parse(res.frame)
308 .filter(|r| r.info_type == c::info_type::FILE && r.class == smb_server_proto_smb2::info::file_class::RENAME)
309 .and_then(|r| res.conn.with_handle(&r.file_id.0, |h| (r.file_id.0, h.path.clone(), h.is_dir)));
310 if let Some((fid, old_path, true)) = &rename {
315 let owner = (res.conn.session_id, *fid);
316 crate::smb2::break_subtree_lease_wait(
317 res.server,
318 res.conn,
319 old_path,
320 *fid,
321 c::lease::HANDLE_CACHING,
322 )
323 .await;
324 if res.server.share_modes.has_open_under(old_path, owner) {
325 return Outcome::Final(ctx.respond(Status::ACCESS_DENIED, Vec::new()));
326 }
327 }
328 let rename_old = rename.map(|(fid, path, _)| (fid, path));
329 match crate::smb2::set_info(res.conn, vfs, res.frame).await {
330 Ok(()) => {
331 if let Some((fid, old_path)) = rename_old {
332 crate::smb2::break_dir_lease(
333 res.server,
334 res.conn,
335 crate::smb2::parent_dir(&old_path),
336 fid,
337 c::lease::RH,
338 );
339 if let Some(new_path) = res.conn.with_handle(&fid, |h| h.path.clone()) {
340 crate::smb2::break_dir_lease(
341 res.server,
342 res.conn,
343 crate::smb2::parent_dir(&new_path),
344 fid,
345 c::lease::RH,
346 );
347 }
348 }
349 Outcome::Final(ctx.respond(Status::SUCCESS, c::build_set_info_resp()))
350 }
351 Err(status) => Outcome::Final(ctx.respond(status, Vec::new())),
352 }
353 }
354}
355
356pub struct CreateCmd;
361impl Command for CreateCmd {
362 type Request = CreateReq;
363 async fn serve(ctx: IoContext<Accepted, Bare>, _req: CreateReq, res: &mut Resources<'_>) -> Outcome {
364 if crate::smb2::share_is_ipc(res.server, res.conn, ctx.reply.tree_id) {
365 return match crate::smb2::pipe_create(res.conn, res.frame) {
366 Ok(body) => Outcome::Final(ctx.respond(Status::SUCCESS, body)),
367 Err(status) => Outcome::Final(ctx.respond(status, Vec::new())),
368 };
369 }
370 let Some(vfs) = crate::smb2::share_vfs(res.server, res.conn, ctx.reply.tree_id) else {
371 return Outcome::Final(ctx.respond(Status::INVALID_HANDLE, Vec::new()));
372 };
373 let signed = smb_server_proto_smb2::Header2::parse(res.frame)
374 .map(|h| h.is_signed())
375 .unwrap_or(false);
376 let is_ca = crate::smb2::share_is_ca(res.server, res.conn, ctx.reply.tree_id);
377 match crate::smb2::create(res.conn, vfs, res.server, signed, is_ca, res.frame).await {
378 Ok(body) => Outcome::Final(ctx.respond(Status::SUCCESS, body)),
379 Err(status) if status == Status::STOPPED_ON_SYMLINK => {
380 let body = res.conn.symlink_error.take().unwrap_or_else(crate::smb2::error_resp);
381 Outcome::Final(ctx.respond(status, body))
382 }
383 Err(status) => Outcome::Final(ctx.respond(status, Vec::new())),
384 }
385 }
386}
387
388pub struct IoctlCmd;
393impl Command for IoctlCmd {
394 type Request = IoctlReq;
395 async fn serve(ctx: IoContext<Accepted, Bare>, _req: IoctlReq, res: &mut Resources<'_>) -> Outcome {
396 match crate::smb2::ioctl(res.conn, res.server, ctx.reply.tree_id, res.frame).await {
397 crate::smb2::IoctlReply::Reply(status, body) => Outcome::Final(ctx.respond(status, body)),
398 crate::smb2::IoctlReply::Silent => Outcome::Silent,
399 }
400 }
401}
402
403pub struct LockCmd;
407impl Command for LockCmd {
408 type Request = LockReq;
409 async fn serve(ctx: IoContext<Accepted, Bare>, _req: LockReq, res: &mut Resources<'_>) -> Outcome {
410 let Some(hdr) = smb_server_proto_smb2::Header2::parse(res.frame) else {
411 return Outcome::Final(ctx.respond(Status::INVALID_PARAMETER, Vec::new()));
412 };
413 match crate::smb2::begin_lock(res.conn, res.server, &hdr, res.frame) {
414 crate::smb2::AsyncStart::Reply(status, body) => Outcome::Final(ctx.respond(status, body)),
415 crate::smb2::AsyncStart::Pending(async_id) => {
416 let parked = ctx.defer(async_id);
417 let interim = parked.interim(Status::PENDING, crate::smb2::error_resp());
418 Outcome::Interim { parked, interim }
419 }
420 }
421 }
422}
423
424pub struct ChangeNotifyCmd;
428impl Command for ChangeNotifyCmd {
429 type Request = ChangeNotifyReq;
430 async fn serve(ctx: IoContext<Accepted, Bare>, _req: ChangeNotifyReq, res: &mut Resources<'_>) -> Outcome {
431 let Some(hdr) = smb_server_proto_smb2::Header2::parse(res.frame) else {
432 return Outcome::Final(ctx.respond(Status::INVALID_PARAMETER, Vec::new()));
433 };
434 match crate::smb2::begin_change_notify(res.conn, &hdr, res.frame) {
435 crate::smb2::AsyncStart::Reply(status, body) => Outcome::Final(ctx.respond(status, body)),
436 crate::smb2::AsyncStart::Pending(async_id) => {
437 let parked = ctx.defer(async_id);
438 let interim = parked.interim(Status::PENDING, crate::smb2::error_resp());
439 Outcome::Interim { parked, interim }
440 }
441 }
442 }
443}
444
445pub struct CancelCmd;
448impl Command for CancelCmd {
449 type Request = CancelReq;
450 async fn serve(_ctx: IoContext<Accepted, Bare>, _req: CancelReq, res: &mut Resources<'_>) -> Outcome {
451 if let Some(hdr) = smb_server_proto_smb2::Header2::parse(res.frame) {
452 crate::smb2::cancel(res.conn, &hdr, res.frame);
453 }
454 Outcome::Silent
455 }
456}
457
458pub struct OplockBreakCmd;
462impl Command for OplockBreakCmd {
463 type Request = OplockBreakReq;
464 async fn serve(ctx: IoContext<Accepted, Bare>, _req: OplockBreakReq, res: &mut Resources<'_>) -> Outcome {
465 let frame = res.frame;
466 let hdr_len = smb_server_proto_smb2::consts::hdr::LEN;
467 let structure_size = u16::from_le_bytes([
468 *frame.get(hdr_len).unwrap_or(&0),
469 *frame.get(hdr_len + 1).unwrap_or(&0),
470 ]);
471 if structure_size == c::LeaseBreakAck::STRUCTURE_SIZE {
472 return match c::LeaseBreakAck::parse(frame) {
473 Some(ack) => {
474 use crate::state::LeaseAckError;
475 match res.server.leases.acknowledge(res.conn.client_guid, ack.key, ack.state) {
476 Ok(state) => {
477 res.server.leases.signal_ack(ack.key);
478 Outcome::Final(
479 ctx.respond(Status::SUCCESS, c::build_lease_break_resp(ack.key, state)),
480 )
481 }
482 Err(LeaseAckError::NotFound) => {
483 Outcome::Final(ctx.respond(Status::OBJECT_NAME_NOT_FOUND, Vec::new()))
484 }
485 Err(LeaseAckError::NotBreaking) => {
486 Outcome::Final(ctx.respond(Status::UNSUCCESSFUL, Vec::new()))
487 }
488 Err(LeaseAckError::StateNotAccepted) => {
489 Outcome::Final(ctx.respond(Status::REQUEST_NOT_ACCEPTED, Vec::new()))
490 }
491 }
492 }
493 None => Outcome::Final(ctx.respond(Status::INVALID_PARAMETER, Vec::new())),
494 };
495 }
496 match c::OplockBreakAck::parse(frame) {
497 Some(ack) => Outcome::Final(ctx.respond(Status::SUCCESS, c::build_oplock_break_resp(ack.file_id, ack.level))),
500 None => Outcome::Final(ctx.respond(Status::INVALID_PARAMETER, Vec::new())),
501 }
502 }
503}
504
505pub struct NegotiateCmd;
509impl Command for NegotiateCmd {
510 type Request = NegotiateReq;
511 async fn serve(ctx: IoContext<Accepted, Bare>, _req: NegotiateReq, res: &mut Resources<'_>) -> Outcome {
512 let Some(hdr) = smb_server_proto_smb2::Header2::parse(res.frame) else {
513 return Outcome::Final(ctx.respond(Status::INVALID_PARAMETER, Vec::new()));
514 };
515 match crate::smb2::negotiate(res.conn, res.server, &hdr, res.frame) {
516 crate::smb2::NegotiateReply::Reply(status, body) => Outcome::Final(ctx.respond(status, body)),
517 crate::smb2::NegotiateReply::Silent => Outcome::Silent,
518 }
519 }
520}
521
522pub struct SessionSetupCmd;
526impl Command for SessionSetupCmd {
527 type Request = SessionSetupReq;
528 async fn serve(ctx: IoContext<Accepted, Bare>, _req: SessionSetupReq, res: &mut Resources<'_>) -> Outcome {
529 match crate::smb2::session_setup(res.server, res.conn, res.frame) {
530 Ok((status, body)) => Outcome::Final(ctx.respond(status, body)),
531 Err(status) => Outcome::Final(ctx.respond(status, Vec::new())),
532 }
533 }
534}
535
536pub struct TreeConnectCmd;
540impl Command for TreeConnectCmd {
541 type Request = TreeConnectReq;
542 async fn serve(ctx: IoContext<Accepted, Bare>, _req: TreeConnectReq, res: &mut Resources<'_>) -> Outcome {
543 match crate::smb2::tree_connect(res.server, res.conn, res.frame) {
544 Ok((name, share_type, encrypt, compress, ca)) => {
545 let new_tid = crate::smb2::next_tree_id();
546 res.conn.tree_insert(new_tid, name);
547 res.conn.resp_tree_id = Some(new_tid);
548 let mut share_flags = 0;
549 let mut capabilities = 0;
550 if encrypt {
551 res.conn.seal_current = true;
552 share_flags |= c::SHAREFLAG_ENCRYPT_DATA;
553 }
554 if compress {
555 share_flags |= c::SHAREFLAG_COMPRESS_DATA;
556 }
557 if ca {
558 share_flags |= c::SHAREFLAG_CONTINUOUSLY_AVAILABLE;
559 capabilities |= c::SHARE_CAP_CONTINUOUS_AVAILABILITY;
560 }
561 counter!("smb_tcons_total").increment(1);
562 gauge!("smb_trees_active").increment(1.0);
563 Outcome::Final(ctx.respond(Status::SUCCESS, c::build_tree_connect_resp(share_type, share_flags, capabilities)))
564 }
565 Err(status) => Outcome::Final(ctx.respond(status, Vec::new())),
566 }
567 }
568}
569
570smb_request_table! {
573 Negotiate = cmd::NEGOTIATE => NegotiateReq;
574 Echo = cmd::ECHO => EchoReq;
575 SessionSetup = cmd::SESSION_SETUP => SessionSetupReq;
576 TreeConnect = cmd::TREE_CONNECT => TreeConnectReq;
577 TreeDisconnect = cmd::TREE_DISCONNECT => TreeDisconnectReq;
578 Logoff = cmd::LOGOFF => LogoffReq;
579 Create = cmd::CREATE => CreateReq;
580 Close = cmd::CLOSE => CloseReq;
581 Flush = cmd::FLUSH => FlushReq;
582 Read = cmd::READ => ReadReq;
583 Write = cmd::WRITE => WriteReq;
584 Lock = cmd::LOCK => LockReq;
585 Ioctl = cmd::IOCTL => IoctlReq;
586 QueryDir = cmd::QUERY_DIRECTORY => QueryDirReq;
587 ChangeNotify = cmd::CHANGE_NOTIFY => ChangeNotifyReq;
588 QueryInfo = cmd::QUERY_INFO => QueryInfoReq;
589 SetInfo = cmd::SET_INFO => SetInfoReq;
590 Cancel = cmd::CANCEL => CancelReq;
591 OplockBreak = cmd::OPLOCK_BREAK => OplockBreakReq;
592}
593
594smb_dispatch! {
595 Negotiate => NegotiateCmd;
596 Echo => EchoCmd;
597 SessionSetup => SessionSetupCmd;
598 TreeConnect => TreeConnectCmd;
599 Flush => FlushCmd;
600 TreeDisconnect => TreeDisconnectCmd;
601 Logoff => LogoffCmd;
602 Read => ReadCmd;
603 Write => WriteCmd;
604 Close => CloseCmd;
605 QueryDir => QueryDirCmd;
606 QueryInfo => QueryInfoCmd;
607 SetInfo => SetInfoCmd;
608 Ioctl => IoctlCmd;
609 Create => CreateCmd;
610 Lock => LockCmd;
611 ChangeNotify => ChangeNotifyCmd;
612 Cancel => CancelCmd;
613 OplockBreak => OplockBreakCmd;
614}
615
616#[cfg(test)]
617mod tests {
618 use super::*;
619 use crate::io::wire::{ReplyHeader, SealIntent};
620
621 fn reply(command: u16, message_id: u64) -> ReplyHeader {
622 ReplyHeader {
623 command,
624 message_id,
625 session_id: 1,
626 tree_id: 1,
627 credits: 1,
628 seal: SealIntent::default(),
629 }
630 }
631
632 #[test]
633 fn accept_split_respond_moves_through_states() {
634 let req = SmbRequest::parse(cmd::ECHO, &[]).expect("decode echo");
638 assert_eq!(req.command(), cmd::ECHO);
639 let ctx = IoContext::accept(reply(cmd::ECHO, 7), req);
640 let (ctx, req) = ctx.split();
641 assert!(matches!(req, SmbRequest::Echo(_)));
642 let resp = ctx.respond(Status::SUCCESS, vec![1, 2, 3]);
643 assert_eq!(resp.status, Status::SUCCESS);
644 assert_eq!(resp.reply.message_id, 7);
645 assert_eq!(resp.body, vec![1, 2, 3]);
646 }
647
648 #[test]
649 fn unknown_command_is_not_implemented() {
650 assert_eq!(SmbRequest::parse(0xBEEF, &[]).unwrap_err(), Status::NOT_IMPLEMENTED);
651 }
652
653 #[test]
654 fn every_migrated_command_routes_to_its_decoder() {
655 for &code in &[
658 cmd::CREATE, cmd::CLOSE, cmd::FLUSH, cmd::READ, cmd::WRITE, cmd::LOCK,
659 cmd::IOCTL, cmd::QUERY_DIRECTORY, cmd::CHANGE_NOTIFY, cmd::QUERY_INFO, cmd::SET_INFO,
660 ] {
661 assert_eq!(
662 SmbRequest::parse(code, &[]).unwrap_err(),
663 Status::INVALID_PARAMETER,
664 "command {code} should route to a decoder, not fall through",
665 );
666 }
667 for &code in &[cmd::ECHO, cmd::TREE_DISCONNECT, cmd::LOGOFF] {
668 assert!(SmbRequest::parse(code, &[]).is_ok());
669 }
670 }
671
672 #[test]
673 fn decode_command_codes_match_the_table() {
674 assert_eq!(<CreateReq as Decode>::COMMAND, cmd::CREATE);
675 assert_eq!(<ReadReq as Decode>::COMMAND, cmd::READ);
676 assert_eq!(<WriteReq as Decode>::COMMAND, cmd::WRITE);
677 assert_eq!(<LockReq as Decode>::COMMAND, cmd::LOCK);
678 assert_eq!(<IoctlReq as Decode>::COMMAND, cmd::IOCTL);
679 assert_eq!(<SetInfoReq as Decode>::COMMAND, cmd::SET_INFO);
680 }
681}