Skip to main content

rustsmb/io/
mod.rs

1//! Typestate request/response pipeline (see `docs/typestate_plan.md`).
2//!
3//! This is the live dispatch path: every SMB2 command the server answers is
4//! decoded into a typed request ([`SmbRequest`]), moved into its owning
5//! [`Command`] handler, and resolved to an [`Outcome`]. `smb2::process_single`
6//! routes each command code here through `via_typestate` and then applies the
7//! shared framing/crypto stage (pre-auth hash, signing, sealing) common to all
8//! commands.
9//!
10//! Dispatch is static (a concrete `match` + monomorphic calls); the only dynamic
11//! seam in the server is the transport, deliberately (see the plan's §12/§13).
12
13#![allow(dead_code)] // some server-event scaffolding (Unsolicited origin) is unused pending Phase 5
14
15/// Generate the data-less lifecycle state markers: a zero-sized struct plus its
16/// sealed `IoState` impls. States that carry data (e.g. `Pending`) are written
17/// by hand because they have fields.
18macro_rules! io_states {
19    ($($state:ident),+ $(,)?) => {
20        $(
21            #[doc = concat!("`", stringify!($state), "` request-lifecycle state marker (zero-sized).")]
22            #[derive(Debug, Clone, Copy)]
23            pub struct $state;
24            impl sealed::Sealed for $state {}
25            impl IoState for $state {}
26        )+
27    };
28}
29
30/// The command table: the single source of truth mapping an SMB2 command code to
31/// its request type. Generates `SmbRequest`, `SmbRequest::command`, and
32/// `SmbRequest::parse`, so a decodable command is one line and cannot desync.
33macro_rules! smb_request_table {
34    ( $( $variant:ident = $code:path => $req:ty ; )* ) => {
35        /// Sum type over every client request the server decodes (generated).
36        #[derive(Debug)]
37        pub enum SmbRequest { $(
38            #[doc = concat!("A decoded `", stringify!($variant), "` request.")]
39            $variant($req),
40        )* }
41
42        impl SmbRequest {
43            /// The SMB2 command code this request decodes to.
44            pub fn command(&self) -> u16 {
45                match self { $( SmbRequest::$variant(_) => $code, )* }
46            }
47
48            /// Decode one single-command frame selected by its command code.
49            pub fn parse(command: u16, frame: &[u8]) -> Result<SmbRequest, Status> {
50                match command {
51                    $( $code => <$req as Decode>::decode(frame).map(SmbRequest::$variant), )*
52                    _ => Err(Status::NOT_IMPLEMENTED),
53                }
54            }
55        }
56    };
57}
58
59/// Static, monomorphic dispatch to command handlers. Generated separately from
60/// the request table so the two concerns stay decoupled; the `_` arm is
61/// unreachable now that every decodable command has a handler, but is kept so a
62/// future table-only addition still compiles.
63macro_rules! smb_dispatch {
64    ( $( $variant:ident => $handler:ty ; )* ) => {
65        /// Serve a request by moving it into its owning handler (generated).
66        pub async fn dispatch(
67            ctx: IoContext<Accepted, Solicited>,
68            res: &mut Resources<'_>,
69        ) -> Outcome {
70            let (ctx, req) = ctx.split();
71            match req {
72                $( SmbRequest::$variant(r) => <$handler as Command>::serve(ctx, r, res).await, )*
73                #[allow(unreachable_patterns)]
74                _ => Outcome::Silent,
75            }
76        }
77    };
78}
79
80mod context;
81mod decode;
82mod origin;
83mod request;
84mod state;
85mod wire;
86
87pub use context::{Command, IoContext, Outcome, Resources};
88pub use origin::{Bare, Origin, Solicited, Unsolicited};
89pub use request::{dispatch, EchoCmd, EchoReq, SmbRequest};
90pub use state::{Accepted, Completed, IoState, Pending};
91pub use wire::{Decode, Encode, ReplyHeader, SealIntent, SmbResponse};