Skip to content

Conversation

@afiffon
Copy link
Owner

@afiffon afiffon commented Nov 21, 2025

  • Add more documentation to the smb-msg crate, and improve code formatting.

  • Bug Fixes & Improvements

    • Improved message structure parsing and validation
    • Better support for SMB2/SMB3 feature detection and negotiation
  • Chores

    • Workspace reorganization with modular crate structure
    • Added safety constraints across crates

Summary by CodeRabbit

  • New Features

    • Richer negotiation (pre-auth, encryption, compression, signing), expanded CREATE contexts, compressed/encrypted message support, improved IOCTL/FSCTL and error-context handling, and a more expressive header/status display.
  • Bug Fixes & Improvements

    • Improved parsing, validation, and safer handling across many request/response flows (query, read/write, notify, lock, session); preservation of buffers/contexts and more robust size/offset handling.
  • Chores

    • Workspace reorganization, new procedural/derive helpers, stricter safety (forbid unsafe code), and CLI workspace addition.

✏️ Tip: You can customize this high-level summary in your review settings.

Remove leftover conditional server/client compilation
Add note on auth to bug report template
- ChainedItemList simplified, support for items without Read+Write
- Fix more client/server feature stuff in smb-msg
mroe reserved fields fixups
templatize negotiation ctx getters
@afiffon afiffon linked an issue Nov 21, 2025 that may be closed by this pull request
@coderabbitai
Copy link

coderabbitai bot commented Nov 21, 2025

Note

Reviews paused

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Walkthrough

Large workspace refactor: adds two proc-macro crates, migrates many bitfield/binrw derives to new mbitfield and smb_msg_derive macros, rewrites numerous SMB message types and ChainedItemList, adds #[forbid(unsafe_code)] across crates, updates CI, manifests, docs, and test macros.

Changes

Cohort / File(s) Summary
Repo metadata & CI
.github/ISSUE_TEMPLATE/bug_report.md, .github/copilot-instructions.md, .github/workflows/build.yml
Bug template clarified; added copilot/documentation guidance; reworked GitHub Actions matrix with no-default-features build/test/doc steps.
Workspace & manifests
Cargo.toml, smb-cli/Cargo.toml, crates/*/Cargo.toml
Added smb-cli, new derive crates to workspace, converted many path deps to workspace = true, adjusted features (e.g., smb-rpc is_sync).
New proc-macro crates
crates/smb-dtyp-derive/*, crates/smb-msg-derive/*
Added mbitfield proc-macro and multiple smb_* message macros that inject size fields, binrw attrs, reserved handling and cfg wiring.
Re-exports & crate attrs
crates/smb-dtyp/src/lib.rs, crates/smb-fscc/src/lib.rs, crates/smb-transport/src/lib.rs, crates/smb/src/lib.rs, crates/smb-msg/src/lib.rs
Re-exported mbitfield; added crate-level #![forbid(unsafe_code)] in several crates.
Bitfield → mbitfield migrations
crates/smb-dtyp/src/security/*.rs, crates/smb-fscc/src/*.rs, crates/smb-rpc/src/*.rs, crates/smb-transport/src/rdma/smbd.rs, ...
Replaced #[bitfield]+binrw mappings with #[smb_dtyp::mbitfield], adjusted padding/skips, added #[br(temp)] on many internal fields.
Chained list rewrite
crates/smb-fscc/src/chained_list.rs
Removed per-item wrapper, made ChainedItemList hold Vec<T>, added custom BinRead/BinWrite, From/FromIterator/Deref impls and changed public API.
SMB message macro integration
many crates/smb-msg/src/* (header, create, ioctl, negotiate, info, file, lock, query_dir, session_setup, tree_connect, compressed, encrypted, error, dfsc, notify, oplock, plain, message, test, cancel, echo, ...)
Converted many message types to `#[smb_request
Ioctl / FSCTL rework
crates/smb-msg/src/ioctl/*.rs, crates/smb-msg/src/ioctl/msg.rs
Reworked IoctlRequest/Response shapes, introduced IoctlReqData enum, FSCTL traits (cfg-gated), flags bitfield, and client-side parse_fsctl validation.
Query / Set / Directory / Info rewrites
crates/smb-msg/src/info/*, crates/smb-msg/src/query_dir.rs, crates/smb-msg/src/info/query.rs, crates/smb-msg/src/info/set.rs
Replaced fixed structure-size layouts with richer typed request/response classes, new enums for info classes, Get/Set data enums, and parsing helpers; many public types introduced/changed.
Reserved/temp attribute additions & minor docs
crates/smb-fscc/src/*, crates/smb-dtyp/src/binrw_util/*, many docstrings
Added #[br(temp)] to many reserved/internal fields, doc/comment tweaks, README and module documentation updates.
Message/test macro changes
crates/smb-msg/src/message.rs, crates/smb-msg/src/test.rs, various tests
Reworked message enum generation and test macros: added test_binrw_request/test_binrw_response, updated test gating and TryFrom<&[u8]> for responses (client-only).
Small behavioral/borrowing tweaks
crates/smb/src/resource.rs, crates/smb/src/dialects.rs, tests under crates/smb/tests/*
Minor borrow/consumption fixes and negotiation capability lookups now use context getters; some test gating relaxed/changed.

Sequence Diagram(s)

sequenceDiagram
    participant Proc as Proc-macros (smb_msg_derive / smb_dtyp_derive)
    participant Item as Source Item (struct/enum)
    participant Generated as Generated Item (with injected fields/attrs)
    participant Binrw as binrw / runtime IO

    Item->>Proc: attribute macro invocation (e.g., #[smb_request], #[mbitfield])
    Proc-->>Generated: inject size/reserved/temp attrs, derives, cfgs
    Generated->>Binrw: uses generated BinRead/BinWrite mapping
    Binrw-->>Generated: serialize / deserialize per injected logic
Loading
sequenceDiagram
    participant Caller
    participant Old as Old ChainedItemList (per-item wrapper)
    participant New as New ChainedItemList (Vec<T> + custom BinRead/BinWrite)
    participant IO

    Caller->>Old: read item sequence (wrapper per item)
    Old->>IO: read next_offset, read wrapper+value, repeat
    Caller->>New: read items until next_entry_offset == 0
    New->>IO: read next_entry_offset, validate alignment, read value, push Vec<T>, repeat
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Areas to focus on:

  • proc-macros: crates/smb-dtyp-derive/src/lib.rs, crates/smb-msg-derive/src/lib.rs — correctness of injected attrs, cfgs, size/reserved handling.
  • Large public API files: crates/smb-msg/src/{create.rs,negotiate.rs,ioctl/*,header.rs,info/*,query_dir.rs,tree_connect.rs} — ensure layout/serde and public types are consistent.
  • crates/smb-fscc/src/chained_list.rs — BinRead/BinWrite alignment/offset math and API changes.
  • Cross-crate workspace/feature changes — verify feature gating and workspace dependency resolution locally.

Possibly related PRs

Poem

🐰
I hop through bytes and stitch the bits just right,
I graft new macros in the pale compile-night.
I tuck reserved fields in temp-sweet nests,
Forbid unsafe burrows, and tidy up rests.
Hop, docs, and tests — may builds pass light.

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Document and format smb-msg' accurately summarizes the main change: adding documentation and improving formatting for the smb-msg crate. It is concise and clearly conveys the primary objective.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 144-document-smb-msg

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Move smb-cli outside of crates/
Make the CI shorter
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 25

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (20)
crates/smb/tests/dialects.rs (1)

23-23: Fix typo in function name.

The function name contains a typo: encrpytion should be encryption. While the code works because the typo is consistent between the call site and definition, it should be corrected for clarity.

Apply this diff to fix the typo:

-                pub async fn [<test_smbint_ $dialect:lower _e $encrypt_mode:lower>]() -> Result<(), Box<dyn std::error::Error>> {
-                    test_smb_integration_dialect_encrpytion_mode(Dialect::$dialect, EncryptionMode::$encrypt_mode).await
+                pub async fn [<test_smbint_ $dialect:lower _e $encrypt_mode:lower>]() -> Result<(), Box<dyn std::error::Error>> {
+                    test_smb_integration_dialect_encryption_mode(Dialect::$dialect, EncryptionMode::$encrypt_mode).await
-async fn test_smb_integration_dialect_encrpytion_mode(
+async fn test_smb_integration_dialect_encryption_mode(

Also applies to: 46-46

crates/smb/src/dialects.rs (2)

183-195: Signing algorithm selection only considers the first advertised entry

This is functionally fine, but if a server advertises multiple signing_algorithms and the first one is unsupported while a later one is supported, negotiation will fail even though a compatible option exists. Consider scanning the list for the first supported algorithm instead of hard-failing on the first entry.

For example, conceptually:

-        let signing_algo = if let Some(signing_algo) =
-            ctx_signing.and_then(|ctx| ctx.signing_algorithms.first())
-        {
-            if !crypto::SIGNING_ALGOS.contains(signing_algo) {
-                return Err(Error::NegotiationError(
-                    "Unsupported signing algorithm selected!".into(),
-                ));
-            }
-            Some(signing_algo)
-        } else {
-            None
-        };
+        let signing_caps = response.get_ctx_signing_capabilities();
+        let signing_algo = signing_caps.and_then(|ctx| {
+            ctx.signing_algorithms
+                .iter()
+                .find(|algo| crypto::SIGNING_ALGOS.contains(algo))
+        });
+        if signing_caps.is_some() && signing_algo.is_none() {
+            return Err(Error::NegotiationError(
+                "No supported signing algorithm offered by server".into(),
+            ));
+        }

You can still keep state.signing_algo = signing_algo.copied(); as-is.


208-220: Encryption handling is reasonable; confirm behavior for unsupported-but-optional ciphers

The logic correctly:

  • Validates the (first) advertised cipher against crypto::ENCRYPTING_ALGOS.
  • Fails negotiation when encryption is required but the server provides no ciphers.

One behavioral nuance: if encryption_mode is not required and the server advertises only unsupported ciphers, negotiation will currently fail with “Unsupported encryption algorithm received” instead of silently downgrading to no encryption. That might be intended (strict) or you might prefer to mirror the “scan for first supported cipher” approach and only error when encryption is required and no supported cipher exists.

If you want the more tolerant behavior, analogous to signing:

  • Search ctx.ciphers for the first supported cipher.
  • Only treat “no supported cipher” as fatal when encryption_mode.is_required() is true.
crates/smb-fscc/src/chained_list.rs (2)

1-5: Top‑level docs still describe a per‑item wrapper, not the new list‑centric API

The intro comments talk about “this struct wraps the value, and the offset”, which no longer matches the design now that only ChainedItemList<T, OFFSET_PAD> exists and T is stored directly. There’s also a small typo (“genric”).

Consider updating these docs to describe the list‑level encoding (per‑entry prefix next_entry_offset, alignment via OFFSET_PAD, and Vec<T>-like ergonomics) and fixing the typo so that users aren’t misled by the old ChainedItem wording.


34-57: Offset/alignment logic looks sound, but OFFSET_PAD == 0 is a footgun

The new vec‑backed ChainedItemList<T, OFFSET_PAD> and its BinWrite/BinRead implementations look correct:

  • Writing:
    • For each item, you emit a placeholder next_entry_offset, write T, then (for non‑last items) pad up to the next OFFSET_PAD boundary.
    • You then patch next_entry_offset to position_after - position_before, so the offset spans prefix + payload + padding.
    • For the last element you rely on the initial placeholder 0 and skip the padding, which matches the “0 == no next entry” convention.
  • Reading:
    • You correctly detect an empty list by comparing current position to a computed stream_end (nice fit with take_seek).
    • You assert position_before % OFFSET_PAD == 0 for each entry, enforce that invariant, and use next_item_offset to jump to the next entry.

Two edge cases are worth tightening:

  1. OFFSET_PAD == 0 will panic:

    • Both the alignment assert and padding computation do modulo by OFFSET_PAD and will panic if it’s zero.
    • Since OFFSET_PAD is a const generic with a default of 4, callers can accidentally instantiate ChainedItemList<T, 0>.
  2. Initial alignment is only enforced on read:

    • BinRead asserts every item’s start is aligned, but BinWrite doesn’t check the start position of the first item.
    • You currently rely on the surrounding field using align_before (as in the CREATE contexts). A debug_assert_eq!(writer.stream_position()? % OFFSET_PAD as u64, 0) before the loop would make misuse easier to catch.

Suggestion:

  • At minimum, document that OFFSET_PAD must be non‑zero and that callers are responsible for aligning the start of the list.
  • Preferably, add a debug_assert!(OFFSET_PAD > 0) and a debug_assert_eq!(writer.stream_position()? % OFFSET_PAD as u64, 0) inside write_options to surface configuration mistakes early.

Also applies to: 59-113, 116-167

crates/smb-msg/src/create.rs (2)

19-53: FileId conversions rely on invariants but introduce unwrap() panics

The new FileId helpers are convenient, but they do rely on assumptions:

  • From<[u8; 16]>:
    • Uses Self::read_le(&mut cursor).unwrap(). With #[binrw::binrw] on a simple u64, u64 struct and an in‑memory Cursor<[u8; 16]>, this should be infallible, but any future change to the binrw attributes (e.g., extra asserts) could turn this into a panic.
  • From<Guid>:
    • Serializes the GUID into a Vec<u8> and then does try_into().unwrap() to force it into a [u8; 16]. This assumes the GUID always serializes to exactly 16 bytes.

Given these invariants are effectively guaranteed by the types (Guid being 128‑bit and the binrw layout being fixed), this is probably acceptable, but the panic surface is non‑obvious.

If you want to make this more robust, you could:

  • Use expect("FileId::read_le must succeed for 16 bytes") with a descriptive message, or
  • Provide fallible versions (e.g., try_from_bytes) alongside these From impls and keep the panicking ones as thin wrappers.

309-405: CREATE context macro: offset math with CHAINED_ITEM_PREFIX_SIZE and helper APIs

The create_context_half! macro is doing a lot, but the pieces line up:

  • Struct layout:

    • _name_offset and _data_offset are PosMarker<u16> fields with comments and seek logic that treat the base of the chained entry as “start of entry minus CHAINED_ITEM_PREFIX_SIZE”.
    • With CHAINED_ITEM_PREFIX_SIZE = size_of::<NextEntryOffsetType>() == 4, the expression
      • seek_before = _name_offset.seek_from(_name_offset.value as u64 - CHAINED_ITEM_PREFIX_SIZE as u64)
        resolves to base + name_offset, where base is the start of the entry (right where the NextEntryOffset lives).
    • The analogous expression for data,
      • seek_before = _name_offset.seek_from_if(_data_offset.value as u64 - CHAINED_ITEM_PREFIX_SIZE as u64, _data_length.value > 0),
        similarly yields base + data_offset when data is present.
    • Using _name_offset.pos as the reference for both is fine because base = _name_offset.pos - CHAINED_ITEM_PREFIX_SIZE.
  • Write side:

    • PosMarker::write_roff_plus and write_roff_size_b_plus with offset_plus = CHAINED_ITEM_PREFIX_SIZE as u64 match the same “offset from entry start” convention for the on‑wire NameOffset/DataOffset fields.
    • align_before = 8 and the _data_offset.value % 8 == 0 assert keep the payloads 8‑aligned as required.
  • Data enum and helpers:

    • #[br(pre_assert(name.as_slice() == CreateContextType::FOO.name()))] gives a nice type‑safe dispatch on context name.
    • as_* and first_* helpers are convenient; the first_* implementation walks a Vec<CreateContext...> and matches on ctx.data, which is straightforward.

Two API nits:

  1. The first_* helpers take &Vec<CreateContext...>:

    • Given that the rest of the code now uses ChainedItemList<CreateContext..., 8>, it would be more flexible to take &[CreateContext...] instead, allowing direct use on both Vec and ChainedItemList via its Deref<[T]>.
  2. TryInto<$req_type> error reporting:

    • The UnexpectedContent error has actual: "" with a TODO about exposing a stringified name.
    • It might be worth at least wiring through CreateContextType::from_name(self.data.name()) and formatting that, even if you don’t convert it to UTF‑8 yet.

Overall, the offset math and list integration look correct; the main changes I’d consider are those small API tweaks to make the helpers easier to use and errors more informative.

Also applies to: 407-433

crates/smb-msg/src/message.rs (1)

6-21: Fix small docstring spacing in make_message!

concat!("This struct represents all the ", stringify!($name), "message types.") produces “Requestmessage”/“Responsemessage” without a space. Consider inserting a space before " message types.":

-        #[doc = concat!("This struct represents all the ", stringify!($name), "message types.")]
+        #[doc = concat!("This struct represents all the ", stringify!($name), " message types.")]
crates/smb-msg/src/info/set.rs (1)

86-118: Fix the proposed error construction; use self.name() instead of self.info_type().into()

The review correctly identifies that to_req() should return Result<SetInfoRequest, SmbMsgError> instead of panicking. However, the proposed diff has an implementation bug:

  • info_type() returns InfoType enum, not &'static str
  • The error field actual expects &'static str
  • InfoType has no Into<&'static str> implementation

The codebase already provides a name() method (generated by the same macro at line 89–95 of common.rs) that returns &'static str. Use that instead:

-            _ => {
-                return Err(SmbMsgError::UnexpectedContent {
-                    expected: "matching SetInfoClass and SetInfoData variant",
-                    actual: self.info_type().into(),
-                });
-            }
+            _ => {
+                return Err(SmbMsgError::UnexpectedContent {
+                    expected: "matching SetInfoClass and SetInfoData variant",
+                    actual: self.name(),
+                });
+            }

There is one caller at crates/smb/src/resource.rs:416 that will need updating to handle Result:

let data = data.into().to_req(cls, self.file_id()?, additional_info)?;
crates/smb-msg/src/compressed.rs (2)

99-106: Fix type mismatch in bw attribute on line 130

The add_original_size_to_total_length function expects &CompressionAlgorithm, but line 130's bw attribute passes compression_algorithm by value instead of by reference. Line 129's br attribute correctly uses &compression_algorithm.

-    #[bw(write_with = PosMarker::write_size_plus, args(&length, add_original_size_to_total_length(compression_algorithm)))]
+    #[bw(write_with = PosMarker::write_size_plus, args(&length, add_original_size_to_total_length(&compression_algorithm)))]

Consider also guarding the subtraction on line 129 against underflow for malformed input.


25-38: CompressedMessage::total_size undercounts chained message sizes

For the Chained variant, total_size only sums payload_data.len() plus m.items.len() * 4 and the top-level STRUCT_SIZE. This omits:

  • The fixed per-item header (CompressionAlgorithm + flags + length = 2 + 2 + 4 = 8 bytes per item).
  • The optional original_size (u32, 4 bytes) for algorithms where original_size_required() is true.

Current code only accounts for 4 bytes per item instead of 8, and completely misses the conditional 4-byte original_size field. The reported size is too small for every item.

Consider something along these lines instead:

-    pub fn total_size(&self) -> usize {
-        match self {
-            CompressedMessage::Unchained(m) => {
-                m.data.len() + CompressedUnchainedMessage::STRUCT_SIZE
-            }
-            CompressedMessage::Chained(m) => {
-                m.items.iter().map(|i| i.payload_data.len()).sum::<usize>()
-                    + m.items.len() * 4
-                    + CompressedChainedMessage::STRUCT_SIZE
-            }
-        }
-    }
+    pub fn total_size(&self) -> usize {
+        match self {
+            CompressedMessage::Unchained(m) => {
+                CompressedUnchainedMessage::STRUCT_SIZE + m.data.len()
+            }
+            CompressedMessage::Chained(m) => {
+                let item_header_base =
+                    std::mem::size_of::<CompressionAlgorithm>() +
+                    std::mem::size_of::<u16>() +
+                    std::mem::size_of::<u32>();
+
+                CompressedChainedMessage::STRUCT_SIZE
+                    + m.items
+                        .iter()
+                        .map(|item| {
+                            let extra_original = if item.compression_algorithm.original_size_required() {
+                                std::mem::size_of::<u32>()
+                            } else {
+                                0
+                            };
+                            item.payload_data.len() + item_header_base + extra_original
+                        })
+                        .sum::<usize>()
+            }
+        }
+    }
crates/smb-msg/src/info/query.rs (1)

132-167: QueryQuotaInfo constructors leave required fields uninitialised

Both QueryQuotaInfo::new() and QueryQuotaInfo::new_sid() constructors initialise only 4 fields but the struct has 8. The missing fields (reserved, sid_list_length, start_sid_length, start_sid_offset) cause compilation errors.

Initialise all fields explicitly in both constructors:

 impl QueryQuotaInfo {
     pub fn new(
         return_single: bool,
         restart_scan: bool,
         content: Vec<FileGetQuotaInformation>,
     ) -> Self {
         Self {
             return_single: return_single.into(),
             restart_scan: restart_scan.into(),
+            reserved: 0,
+            sid_list_length: PosMarker::default(),
+            start_sid_length: PosMarker::default(),
+            start_sid_offset: PosMarker::default(),
             get_quota_info_content: Some(content.into()),
             sid: None,
         }
     }

     pub fn new_sid(return_single: bool, restart_scan: bool, sid: SID) -> Self {
         Self {
             return_single: return_single.into(),
             restart_scan: restart_scan.into(),
+            reserved: 0,
+            sid_list_length: PosMarker::default(),
+            start_sid_length: PosMarker::default(),
+            start_sid_offset: PosMarker::default(),
             get_quota_info_content: None,
             sid: Some(sid),
         }
     }
 }

Lines 169–197 in crates/smb-msg/src/info/query.rs.

crates/smb-msg/src/negotiate.rs (1)

215-228: impl From<$name> for NegotiateContext omits required field initializations

The macro-generated From implementation at lines 269–276 only initializes context_type and data:

impl From<$name> for NegotiateContext {
    fn from(val: $name) -> Self {
        NegotiateContext {
            context_type: NegotiateContextType::$name,
            data: NegotiateContextValue::$name(val),
        }
    }
}

This is a compile error; NegotiateContext has four fields and both data_length and reserved must be initialized:

 impl From<$name> for NegotiateContext {
     fn from(val: $name) -> Self {
         NegotiateContext {
             context_type: NegotiateContextType::$name,
+            data_length: PosMarker::default(),
+            reserved: 0,
             data: NegotiateContextValue::$name(val),
         }
     }
 }

Additionally, RdmaTransformCapabilities (lines 482–492) declares the field reserved twice with different types (u16 and u32), which is a syntax error:

pub struct RdmaTransformCapabilities {
    #[bw(try_calc(u16::try_from(transforms.len())))]
    transform_count: u16,

    reserved: u16,    // ← duplicate name
    reserved: u32,    // ← duplicate name

    #[br(count = transform_count)]
    pub transforms: Vec<RdmaTransformId>,
}

Rename one of them to match the SMB2 specification (likely the first should be reserved1: u16).

crates/smb-msg/src/smb1.rs (1)

11-52: Code will not compile due to duplicate field names and incomplete Default impl — all identified issues confirmed

The review comment is accurate and identifies genuine critical compilation errors:

  1. Duplicate reserved fields confirmed: The struct contains three fields all named reserved: u16 (lines 32, 39, 41), which is a Rust compilation error.

  2. Macro renaming strategy fails: The common_struct_changes macro uses proc_macro2::Span::call_site().start().line to generate unique identifiers for reserved fields. However, call_site() returns the macro invocation location (the #[smb_request_binrw] line), not the individual field locations. All three reserved fields will be renamed to the same identifier (e.g., _reserved15), still causing collisions after macro expansion.

  3. Default impl missing required fields confirmed: The Default implementation (lines 63-84) initializes only 6 fields but the struct has 14 fields total. Missing: _command, _pid_high, _tid, _pid_low, _word_count, and all three reserved fields. Even fields with #[br(temp)] or #[bw(calc = ...)] attributes must be initialized in Self { ... }.

The recommended fixes are appropriate: rename the duplicate fields to unique names before macro expansion (e.g., uid_reserved, mid_reserved), update common_struct_changes to generate genuinely unique identifiers (e.g., using a per-field counter), and initialize all struct fields in Default.

crates/smb-msg/src/tree_connect.rs (2)

80-181: RemotedIdentityTreeConnect and Blob/Array helpers look well-structured; consider auto-calculating sizes

The TreeConnectContext + make_remoted_identity_connect! macro, BlobData<T>, ArrayData<T>, SidAttrData, SidAttrSeGroup, and LuidAttrData form a coherent representation of the remoted identity TREE_CONNECT context. The offset/size handling with PosMarker and write_roff_b is a nice fit for the spec’s layout.

One thing to consider for robustness:

  • RemotedIdentityTreeConnect.ticket_size and BlobData::blob_size are currently caller-controlled; there’s no #[bw(calc = ..)] or assert tying them to the actual serialized size. If a caller passes inconsistent values, you can end up emitting malformed packets.

If feasible, adding bw(calc = ..) and/or br(assert(..)) on these size markers would make the encoding more self-consistent and reduce manual bookkeeping for users of these types.

Given the complexity of the remoted identity layout, please sanity‑check these helpers against the examples in MS-SMB2 2.2.9.2.1 and existing interop traces before tightening any assertions.


26-78: Address flag-context consistency to prevent malformed wire protocol

The concern is valid. MS-SMB2 specifies that when the RemotedIdentity parameter is true, the client MUST add a tree connect request extension and MUST set the SMB2_TREE_CONNECT_FLAG_EXTENSION_PRESENT bit in the Flags field. Additionally, if the EXTENSION_PRESENT bit is set, the extension structure MUST be added at the beginning of the Buffer field.

The current implementation creates an asymmetry: read operations check flags.extension_present() while write operations check !tree_connect_contexts.is_empty(). Since flags and tree_connect_contexts are public fields, code can construct invalid states (e.g., flag=false with non-empty contexts, or flag=true with empty contexts).

The suggested #[bw(assert(...))] would catch these at serialization time. However, since this is a protocol-critical invariant, consider also enforcing it during construction through a builder pattern or validation method to prevent invalid intermediate states.

Verify test coverage includes both base (no extension) and extension-present cases to ensure round-trip consistency.

crates/smb-msg/src/notify.rs (2)

16-100: ChangeNotifyRequest, NotifyFlags, and NotifyFilter definitions look good

The ChangeNotifyRequest layout (flags, output_buffer_length, file_id, completion_filter, reserved) and the NotifyFlags/NotifyFilter bitfields closely follow the MS-SMB2 2.2.35 structures, and the NotifyFilter::all() helper is a convenient way to express “monitor everything”.

Only minor potential improvement: similar to other protocol structs, you might want to make the trailing reserved: u32 write as zero automatically via #[bw(calc = 0)] and either drop or assert it on read.

If you tighten the reserved handling, please verify that existing fixtures (especially the ChangeNotifyRequest test) still match their expected hex.


125-181: ServerToClientNotification/Notification plumbing is correct; consider enforcing structure_size

The ServerToClientNotification, NotificationType, Notification (with imported notification_type), and NotifySessionClosed types correctly express the SMB2 server-to-client notification pattern and allow future extension with new notification variants.

For extra safety you might:

  • Add a #[bw(calc = 9)] and #[br(assert(structure_size == 9))] on ServerToClientNotification::structure_size to match the spec’s fixed structure size and prevent malformed messages.

Example:

#[smb_response_binrw]
pub struct ServerToClientNotification {
-    /// Size of the SMB2_SERVER_TO_CLIENT_NOTIFICATION structure.
-    structure_size: u16,
+    /// Size of the SMB2_SERVER_TO_CLIENT_NOTIFICATION structure.
+    #[bw(calc = 9)]
+    #[br(assert(structure_size == 9))]
+    structure_size: u16,

After adding such an assertion, please ensure any existing notification fixtures still round‑trip correctly.

crates/smb-msg/src/dfsc.rs (1)

107-117: Bitfield definitions: duplicate __ names and a typo in target_failbacl

  • ReferralHeaderFlags uses field name target_failbacl, which looks like a typo for target_failback and will be part of the public API.
  • Both ReferralEntryFlags and ReferralEntryFlagsV4 declare two #[skip] fields with the same identifier __ in the same struct. With smb_dtyp::mbitfield (wrapping modular_bitfield), duplicate field names are at best confusing and are likely to cause compilation or tooling issues.

Consider:

  • Renaming target_failbacl to target_failback.
  • Giving the skipped fields distinct names (e.g. __reserved0, __reserved1) or merging them into a single wider skipped field if layout permits.

Also applies to: 262-269, 382-391

crates/smb-msg/src/ioctl/msg.rs (1)

176-248: Avoid unwrap() in IoctlResponse::parse_fsctl to prevent panics on malformed data

parse_fsctl currently does:

let mut cursor = std::io::Cursor::new(&self.out_buffer);
Ok(T::read_le(&mut cursor).unwrap())

This will panic on any BinRead error (truncated buffer, malformed server response), even though the method returns crate::Result<T> and already handles one error case (MissingFsctlDefinition) gracefully. Since IOCTL responses come from the network, panicking here is a reliability and safety issue.

Consider mapping the BinRead error into your error type instead, e.g.:

pub fn parse_fsctl<T>(&self) -> crate::Result<T>
where
    T: FsctlResponseContent,
{
    if !T::FSCTL_CODES.iter().any(|&f| f as u32 == self.ctl_code) {
        return Err(crate::SmbMsgError::MissingFsctlDefinition(self.ctl_code));
    }

    let mut cursor = std::io::Cursor::new(&self.out_buffer);
    T::read_le(&mut cursor).map_err(Into::into)
}

(or another appropriate conversion into crate::SmbMsgError).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 095ef3f and 1955c22.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (72)
  • .github/ISSUE_TEMPLATE/bug_report.md (1 hunks)
  • .github/copilot-instructions.md (1 hunks)
  • .github/workflows/build.yml (1 hunks)
  • Cargo.toml (2 hunks)
  • crates/smb-dtyp-derive/Cargo.toml (1 hunks)
  • crates/smb-dtyp-derive/src/lib.rs (1 hunks)
  • crates/smb-dtyp/Cargo.toml (1 hunks)
  • crates/smb-dtyp/src/binrw_util/boolean.rs (1 hunks)
  • crates/smb-dtyp/src/binrw_util/helpers.rs (1 hunks)
  • crates/smb-dtyp/src/binrw_util/multi_sz.rs (1 hunks)
  • crates/smb-dtyp/src/lib.rs (2 hunks)
  • crates/smb-dtyp/src/security/ace.rs (8 hunks)
  • crates/smb-dtyp/src/security/acl.rs (1 hunks)
  • crates/smb-dtyp/src/security/security_descriptor.rs (2 hunks)
  • crates/smb-fscc/Cargo.toml (2 hunks)
  • crates/smb-fscc/src/chained_list.rs (4 hunks)
  • crates/smb-fscc/src/common_info.rs (3 hunks)
  • crates/smb-fscc/src/directory_info.rs (5 hunks)
  • crates/smb-fscc/src/file_attributes.rs (1 hunks)
  • crates/smb-fscc/src/filesystem_info.rs (5 hunks)
  • crates/smb-fscc/src/lib.rs (1 hunks)
  • crates/smb-fscc/src/query_file_info.rs (3 hunks)
  • crates/smb-fscc/src/quota.rs (2 hunks)
  • crates/smb-fscc/src/set_file_info.rs (2 hunks)
  • crates/smb-msg-derive/Cargo.toml (1 hunks)
  • crates/smb-msg-derive/src/lib.rs (1 hunks)
  • crates/smb-msg/Cargo.toml (2 hunks)
  • crates/smb-msg/README.md (1 hunks)
  • crates/smb-msg/src/cancel.rs (1 hunks)
  • crates/smb-msg/src/compressed.rs (3 hunks)
  • crates/smb-msg/src/create.rs (13 hunks)
  • crates/smb-msg/src/dfsc.rs (15 hunks)
  • crates/smb-msg/src/echo.rs (2 hunks)
  • crates/smb-msg/src/encrypted.rs (2 hunks)
  • crates/smb-msg/src/error.rs (2 hunks)
  • crates/smb-msg/src/file.rs (5 hunks)
  • crates/smb-msg/src/header.rs (6 hunks)
  • crates/smb-msg/src/info.rs (1 hunks)
  • crates/smb-msg/src/info/common.rs (3 hunks)
  • crates/smb-msg/src/info/query.rs (8 hunks)
  • crates/smb-msg/src/info/set.rs (5 hunks)
  • crates/smb-msg/src/ioctl.rs (1 hunks)
  • crates/smb-msg/src/ioctl/fsctl.rs (32 hunks)
  • crates/smb-msg/src/ioctl/msg.rs (2 hunks)
  • crates/smb-msg/src/lib.rs (1 hunks)
  • crates/smb-msg/src/lock.rs (1 hunks)
  • crates/smb-msg/src/message.rs (2 hunks)
  • crates/smb-msg/src/negotiate.rs (11 hunks)
  • crates/smb-msg/src/notify.rs (4 hunks)
  • crates/smb-msg/src/oplock.rs (2 hunks)
  • crates/smb-msg/src/plain.rs (5 hunks)
  • crates/smb-msg/src/query_dir.rs (2 hunks)
  • crates/smb-msg/src/session_setup.rs (3 hunks)
  • crates/smb-msg/src/smb1.rs (4 hunks)
  • crates/smb-msg/src/test.rs (2 hunks)
  • crates/smb-msg/src/tree_connect.rs (9 hunks)
  • crates/smb-rpc/Cargo.toml (2 hunks)
  • crates/smb-rpc/src/interface/srvsvc.rs (1 hunks)
  • crates/smb-rpc/src/pdu.rs (7 hunks)
  • crates/smb-transport/Cargo.toml (2 hunks)
  • crates/smb-transport/src/lib.rs (1 hunks)
  • crates/smb-transport/src/rdma/smbd.rs (4 hunks)
  • crates/smb/Cargo.toml (1 hunks)
  • crates/smb/src/compression.rs (1 hunks)
  • crates/smb/src/dialects.rs (3 hunks)
  • crates/smb/src/lib.rs (1 hunks)
  • crates/smb/src/session.rs (1 hunks)
  • crates/smb/src/session/encryptor_decryptor.rs (1 hunks)
  • crates/smb/src/session/signer.rs (1 hunks)
  • crates/smb/tests/dialects.rs (1 hunks)
  • crates/smb/tests/long_dir.rs (0 hunks)
  • smb-cli/Cargo.toml (2 hunks)
💤 Files with no reviewable changes (1)
  • crates/smb/tests/long_dir.rs
🧰 Additional context used
🧬 Code graph analysis (35)
crates/smb-dtyp/src/lib.rs (1)
crates/smb-dtyp-derive/src/lib.rs (1)
  • mbitfield (8-19)
crates/smb-fscc/src/file_attributes.rs (1)
crates/smb-dtyp-derive/src/lib.rs (1)
  • mbitfield (8-19)
crates/smb-msg/src/test.rs (1)
crates/smb-rpc/src/pdu.rs (1)
  • test_request_write (512-552)
crates/smb-rpc/src/interface/srvsvc.rs (1)
crates/smb-dtyp-derive/src/lib.rs (1)
  • mbitfield (8-19)
crates/smb-transport/src/rdma/smbd.rs (1)
crates/smb-dtyp-derive/src/lib.rs (1)
  • mbitfield (8-19)
crates/smb-rpc/src/pdu.rs (1)
crates/smb-dtyp-derive/src/lib.rs (1)
  • mbitfield (8-19)
crates/smb-msg/src/message.rs (1)
crates/smb-msg-derive/src/lib.rs (2)
  • smb_request_binrw (230-232)
  • smb_response_binrw (238-240)
crates/smb-msg/src/info/common.rs (2)
crates/smb-msg-derive/src/lib.rs (1)
  • smb_message_binrw (246-248)
crates/smb-dtyp-derive/src/lib.rs (1)
  • mbitfield (8-19)
crates/smb-msg/src/cancel.rs (1)
crates/smb-msg-derive/src/lib.rs (1)
  • smb_request (206-208)
crates/smb-msg/src/smb1.rs (1)
crates/smb-msg-derive/src/lib.rs (1)
  • smb_request_binrw (230-232)
crates/smb-msg/src/info/set.rs (1)
crates/smb-msg-derive/src/lib.rs (3)
  • smb_request (206-208)
  • smb_message_binrw (246-248)
  • smb_response (214-216)
crates/smb-msg/src/tree_connect.rs (2)
crates/smb-dtyp-derive/src/lib.rs (1)
  • mbitfield (8-19)
crates/smb-msg-derive/src/lib.rs (4)
  • smb_request (206-208)
  • smb_request_binrw (230-232)
  • smb_response (214-216)
  • smb_response_binrw (238-240)
crates/smb-msg/src/echo.rs (1)
crates/smb-msg-derive/src/lib.rs (1)
  • smb_request_response (222-224)
crates/smb-fscc/src/common_info.rs (1)
crates/smb-dtyp-derive/src/lib.rs (1)
  • mbitfield (8-19)
crates/smb-msg/src/lock.rs (2)
crates/smb-msg-derive/src/lib.rs (3)
  • smb_request (206-208)
  • smb_request_binrw (230-232)
  • smb_response (214-216)
crates/smb-dtyp-derive/src/lib.rs (1)
  • mbitfield (8-19)
crates/smb-dtyp/src/security/ace.rs (1)
crates/smb-dtyp-derive/src/lib.rs (1)
  • mbitfield (8-19)
crates/smb-dtyp/src/security/security_descriptor.rs (1)
crates/smb-dtyp-derive/src/lib.rs (1)
  • mbitfield (8-19)
crates/smb-msg/src/error.rs (1)
crates/smb-msg-derive/src/lib.rs (2)
  • smb_response (214-216)
  • smb_response_binrw (238-240)
crates/smb-msg/src/encrypted.rs (1)
crates/smb-msg-derive/src/lib.rs (1)
  • smb_message_binrw (246-248)
crates/smb-msg/src/query_dir.rs (4)
crates/smb-msg-derive/src/lib.rs (2)
  • smb_request (206-208)
  • smb_response (214-216)
crates/smb-dtyp/src/binrw_util/sized_string.rs (2)
  • size (22-24)
  • bytes16 (42-44)
crates/smb-dtyp/src/binrw_util/pos_marker.rs (1)
  • write_aoff (344-363)
crates/smb-dtyp-derive/src/lib.rs (1)
  • mbitfield (8-19)
crates/smb-msg/src/session_setup.rs (3)
crates/smb-msg-derive/src/lib.rs (2)
  • smb_request (206-208)
  • smb_response (214-216)
crates/smb-dtyp/src/binrw_util/pos_marker.rs (1)
  • write_aoff (344-363)
crates/smb-dtyp-derive/src/lib.rs (1)
  • mbitfield (8-19)
crates/smb-msg/src/header.rs (2)
crates/smb-msg-derive/src/lib.rs (2)
  • smb_message_binrw (246-248)
  • smb_request_response (222-224)
crates/smb-dtyp-derive/src/lib.rs (1)
  • mbitfield (8-19)
crates/smb-msg/src/dfsc.rs (2)
crates/smb-msg-derive/src/lib.rs (1)
  • smb_request_binrw (230-232)
crates/smb-dtyp-derive/src/lib.rs (1)
  • mbitfield (8-19)
crates/smb-msg/src/compressed.rs (2)
crates/smb-msg-derive/src/lib.rs (1)
  • smb_message_binrw (246-248)
crates/smb-msg/src/negotiate.rs (1)
  • original_size_required (413-421)
crates/smb-msg/src/negotiate.rs (4)
crates/smb-msg-derive/src/lib.rs (3)
  • smb_request (206-208)
  • smb_response (214-216)
  • smb_message_binrw (246-248)
crates/smb-dtyp/src/binrw_util/sized_string.rs (3)
  • size (22-24)
  • try_from (185-188)
  • try_from (256-258)
crates/smb-dtyp/src/binrw_util/pos_marker.rs (1)
  • write_aoff (344-363)
crates/smb-dtyp-derive/src/lib.rs (1)
  • mbitfield (8-19)
crates/smb-msg/src/info/query.rs (3)
crates/smb-msg-derive/src/lib.rs (5)
  • smb_request (206-208)
  • smb_request_binrw (230-232)
  • smb_message_binrw (246-248)
  • smb_response (214-216)
  • smb_response_binrw (238-240)
crates/smb-dtyp/src/binrw_util/pos_marker.rs (2)
  • write_aoff_size_a (545-570)
  • write_size (602-620)
crates/smb-dtyp-derive/src/lib.rs (1)
  • mbitfield (8-19)
crates/smb-msg/src/ioctl/msg.rs (3)
crates/smb-msg-derive/src/lib.rs (3)
  • smb_request (206-208)
  • smb_request_binrw (230-232)
  • smb_response (214-216)
crates/smb-dtyp/src/binrw_util/pos_marker.rs (2)
  • write_aoff_size (573-597)
  • write_aoff (344-363)
crates/smb-dtyp-derive/src/lib.rs (1)
  • mbitfield (8-19)
crates/smb-msg/src/ioctl/fsctl.rs (3)
crates/smb-msg-derive/src/lib.rs (3)
  • smb_message_binrw (246-248)
  • smb_request_binrw (230-232)
  • smb_response_binrw (238-240)
crates/smb-dtyp/src/binrw_util/pos_marker.rs (2)
  • write_size (602-620)
  • fmt (652-657)
crates/smb-dtyp-derive/src/lib.rs (1)
  • mbitfield (8-19)
crates/smb-fscc/src/chained_list.rs (1)
crates/smb-dtyp/src/binrw_util/pos_marker.rs (2)
  • write_options (92-105)
  • read_options (73-83)
crates/smb-fscc/src/filesystem_info.rs (1)
crates/smb-dtyp-derive/src/lib.rs (1)
  • mbitfield (8-19)
crates/smb-msg/src/plain.rs (1)
crates/smb-msg-derive/src/lib.rs (2)
  • smb_request_binrw (230-232)
  • smb_response_binrw (238-240)
crates/smb-msg/src/file.rs (3)
crates/smb-msg-derive/src/lib.rs (3)
  • smb_request (206-208)
  • smb_response (214-216)
  • smb_request_binrw (230-232)
crates/smb-dtyp/src/binrw_util/pos_marker.rs (1)
  • write_aoff (344-363)
crates/smb-dtyp-derive/src/lib.rs (1)
  • mbitfield (8-19)
crates/smb-msg/src/create.rs (3)
crates/smb-msg-derive/src/lib.rs (6)
  • smb_request (206-208)
  • smb_request_binrw (230-232)
  • smb_response (214-216)
  • smb_response_binrw (238-240)
  • smb_message_binrw (246-248)
  • smb_request_response (222-224)
crates/smb-dtyp/src/binrw_util/pos_marker.rs (4)
  • write_roff_size (421-445)
  • seek_from (24-26)
  • write_roff_size_b_plus (481-506)
  • seek_from_if (29-35)
crates/smb-dtyp-derive/src/lib.rs (1)
  • mbitfield (8-19)
crates/smb-msg/src/notify.rs (3)
crates/smb-msg-derive/src/lib.rs (3)
  • smb_request (206-208)
  • smb_response (214-216)
  • smb_response_binrw (238-240)
crates/smb-dtyp-derive/src/lib.rs (1)
  • mbitfield (8-19)
crates/smb-dtyp/src/binrw_util/pos_marker.rs (1)
  • write_aoff_size (573-597)
crates/smb-msg/src/oplock.rs (2)
crates/smb-msg-derive/src/lib.rs (4)
  • smb_request_response (222-224)
  • smb_response (214-216)
  • smb_message_binrw (246-248)
  • parse (49-69)
crates/smb-dtyp-derive/src/lib.rs (1)
  • mbitfield (8-19)
🪛 GitHub Actions: Build
crates/smb-msg/src/smb1.rs

[warning] 5-5: unused import: binrw::io::TakeSeekExt

crates/smb-msg/src/dfsc.rs

[warning] 5-5: unused import: io::TakeSeekExt

🪛 Gitleaks (8.29.0)
crates/smb-msg/src/oplock.rs

[high] 152-152: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

🪛 LanguageTool
crates/smb-msg/README.md

[uncategorized] ~22-~22: The official name of this software platform is spelled with a capital “H”.
Context: ...dingly (you can see the instructions in .github/copilot-instructions.md :)). My tip f...

(GITHUB)


[style] ~26-~26: Consider a different adjective to strengthen your wording.
Context: ...fficial documentation, so you can get a deep understanding of the protocol that way....

(DEEP_PROFOUND)

.github/copilot-instructions.md

[style] ~18-~18: Using many exclamation marks might seem excessive (in this case: 4 exclamation marks for a text that’s 1432 characters long)
Context: ...example, or bitfield structs - of course! - NEVER document reserved fields.

(EN_EXCESSIVE_EXCLAMATION)

@afiffon afiffon force-pushed the 144-document-smb-msg branch from 1955c22 to f0c051e Compare November 21, 2025 16:29
@afiffon afiffon enabled auto-merge (squash) November 21, 2025 16:30
@afiffon afiffon force-pushed the 144-document-smb-msg branch from f0c051e to 070a7e3 Compare November 21, 2025 16:32
@afiffon
Copy link
Owner Author

afiffon commented Nov 21, 2025

@coderabbitai pause

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
crates/smb-msg/src/error.rs (2)

65-92: Reduce duplication between as_u32 and as_u64 with a small helper (optional)

The as_u32 and as_u64 methods are nearly identical aside from element size and conversion function. The current implementation is correct and safe (the length check guarantees the unwrap()), but you could slightly simplify and centralize the length check.

One possible refactor:

 impl ErrorResponseContext {
-    /// Interprets the error data as a u32, if possible.
-    /// Returns an error if the data length is not 4 bytes.
-    pub fn as_u32(&self) -> crate::Result<u32> {
-        if self.error_data.len() == std::mem::size_of::<u32>() {
-            Ok(u32::from_le_bytes(
-                self.error_data.as_slice().try_into().unwrap(),
-            ))
-        } else {
-            Err(crate::SmbMsgError::InvalidData(
-                "Invalid error data length for u32".into(),
-            ))
-        }
-    }
-
-    /// Interprets the error data as a u64, if possible.
-    /// Returns an error if the data length is not 8 bytes.
-    pub fn as_u64(&self) -> crate::Result<u64> {
-        if self.error_data.len() == std::mem::size_of::<u64>() {
-            Ok(u64::from_le_bytes(
-                self.error_data.as_slice().try_into().unwrap(),
-            ))
-        } else {
-            Err(crate::SmbMsgError::InvalidData(
-                "Invalid error data length for u64".into(),
-            ))
-        }
-    }
+    fn expect_len(&self, expected: usize, type_name: &str) -> crate::Result<&[u8]> {
+        if self.error_data.len() == expected {
+            Ok(self.error_data.as_slice())
+        } else {
+            Err(crate::SmbMsgError::InvalidData(
+                format!("Invalid error data length for {type_name}").into(),
+            ))
+        }
+    }
+
+    /// Interprets the error data as a u32, if possible.
+    /// Returns an error if the data length is not 4 bytes.
+    pub fn as_u32(&self) -> crate::Result<u32> {
+        let bytes: [u8; std::mem::size_of::<u32>()] =
+            self.expect_len(std::mem::size_of::<u32>(), "u32")?
+                .try_into()
+                .unwrap();
+        Ok(u32::from_le_bytes(bytes))
+    }
+
+    /// Interprets the error data as a u64, if possible.
+    /// Returns an error if the data length is not 8 bytes.
+    pub fn as_u64(&self) -> crate::Result<u64> {
+        let bytes: [u8; std::mem::size_of::<u64>()] =
+            self.expect_len(std::mem::size_of::<u64>(), "u64")?
+                .try_into()
+                .unwrap();
+        Ok(u64::from_le_bytes(bytes))
+    }
 }

This keeps behavior identical while making future additions (e.g., as_i32) simpler.


107-116: Add tests covering non-empty error contexts to lock in ByteCount / alignment behavior

The existing error_simple test only exercises the empty‑context case (error_data: vec![]), which doesn’t validate:

  • ErrorContextCount and ByteCount interaction when there are one or more ErrorResponseContext entries.
  • 8‑byte alignment of successive contexts.
  • Round‑trip correctness of ErrorResponseContext layouts for SMB 3.1.1.

The // TODO(TEST): Add a test with added context items. comment (lines 115–116) is spot‑on. I recommend adding at least one test that:

  • Builds an ErrorResponse with a single ErrorResponseContext and asserts the encoded hex matches expectations (correct ErrorContextCount, ByteCount, and data).
  • Optionally, another with two contexts, to verify alignment and cumulative ByteCount.

If helpful, I can sketch concrete test cases following the existing test_response! pattern.

♻️ Duplicate comments (2)
crates/smb-msg/src/ioctl.rs (1)

1-1: Fix typo in module doc comment (previously flagged).

This issue was already noted in a prior review. The typo "requessts" should be "requests":

-//! IOCTL requessts and responses implementation, and FSCTLs.
+//! IOCTL requests and responses implementation, and FSCTLs.
crates/smb-msg/src/echo.rs (1)

14-17: Doc’d aliases for EchoRequest/EchoResponse are a good public API shape

Re‑exporting EchoMessage as EchoRequest/EchoResponse with /// comments both matches the spec terminology and ensures these aliases show up properly in generated docs, addressing the earlier documentation nit.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f0c051e and 070a7e3.

📒 Files selected for processing (5)
  • crates/smb-msg/src/create.rs (13 hunks)
  • crates/smb-msg/src/echo.rs (2 hunks)
  • crates/smb-msg/src/error.rs (3 hunks)
  • crates/smb-msg/src/ioctl.rs (1 hunks)
  • crates/smb/src/resource.rs (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-11-21T16:25:23.358Z
Learnt from: afiffon
Repo: afiffon/smb-rs PR: 157
File: crates/smb-msg/src/file.rs:14-25
Timestamp: 2025-11-21T16:25:23.358Z
Learning: In the smb-rs codebase, multiple fields named `reserved` with different types can appear in the same struct when using the `#[smb_request]`, `#[smb_response]`, or similar derive macros from smb_msg_derive. The macro system automatically renames these to unique identifiers (e.g., `_reserved1`, `_reserved2`) during expansion, so what appears to be duplicate field names is intentional and compiles correctly.

Applied to files:

  • crates/smb-msg/src/echo.rs
  • crates/smb-msg/src/error.rs
  • crates/smb-msg/src/create.rs
🧬 Code graph analysis (3)
crates/smb-msg/src/echo.rs (1)
crates/smb-msg-derive/src/lib.rs (1)
  • smb_request_response (222-224)
crates/smb-msg/src/error.rs (2)
crates/smb-msg-derive/src/lib.rs (2)
  • smb_response (214-216)
  • smb_response_binrw (238-240)
crates/smb-dtyp/src/binrw_util/pos_marker.rs (1)
  • write_size (602-620)
crates/smb-msg/src/create.rs (4)
crates/smb-msg-derive/src/lib.rs (6)
  • smb_request (206-208)
  • smb_request_binrw (230-232)
  • smb_response (214-216)
  • smb_response_binrw (238-240)
  • smb_message_binrw (246-248)
  • smb_request_response (222-224)
crates/smb-dtyp/src/binrw_util/sized_string.rs (2)
  • size (22-24)
  • bytes16 (42-44)
crates/smb-dtyp/src/binrw_util/pos_marker.rs (6)
  • write_aoff (344-363)
  • write_roff_size (421-445)
  • seek_from (24-26)
  • write_roff_plus (222-240)
  • write_roff_size_b_plus (481-506)
  • seek_from_if (29-35)
crates/smb-dtyp-derive/src/lib.rs (1)
  • mbitfield (8-19)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: build (windows-latest, stable)
  • GitHub Check: build (windows-latest, 1.85)
  • GitHub Check: test (single_threaded)
  • GitHub Check: test (async)
  • GitHub Check: test (multi_threaded)
🔇 Additional comments (7)
crates/smb-msg/src/ioctl.rs (1)

3-9: Module structure and exports look good.

The module organization (common, fsctl, msg) and public re-exports are clean and well-structured.

crates/smb-msg/src/echo.rs (2)

3-11: EchoMessage macro usage and layout look sound

#[smb_request_response(size = 4)] on a single reserved: u16 field, plus Default, is consistent with a 4‑byte SMB2 Echo body (structure size + reserved) and gives a sensible zeroed default message. No issues from a layout or API perspective.


25-27: Binrw test correctly validates the EchoMessage wire format

The test_binrw! case for EchoMessage expecting "04000000" matches the 4‑byte little‑endian layout (structure size 4 + reserved 0), so this gives good coverage for the derived encoding/decoding.

crates/smb/src/resource.rs (1)

157-165: LGTM! Improved context handling by preserving the container.

The removal of .into() is a beneficial change that avoids consuming response.create_contexts, allowing the container to be reused for potential additional context inspections without re-materialization.

crates/smb-msg/src/error.rs (3)

10-14: ByteCount / ErrorContextCount wiring now looks correct and well-documented

The updated ErrorResponse definition is in good shape:

  • Docs clearly describe SMB 3.1.1 behavior and the meaning of ErrorContextCount / ErrorData.
  • _error_context_count is derived from error_data.len() on write, ensuring the count tracks the vector length.
  • _byte_count: PosMarker<u32> is used symmetrically:
    • On read: map_stream = |s| s.take_seek(_byte_count.value.into()) limits parsing to the declared ByteCount.
    • On write: #[bw(write_with = PosMarker::write_size, args(&_byte_count))] causes ByteCount to reflect the serialized size of all contexts (including alignment/padding).
  • This resolves the earlier issue where ByteCount was not tied to error_data.

No changes needed here; this is a solid, spec-aligned shape.

Also applies to: 16-21, 23-23, 25-27, 29-33


36-41: ErrorResponseContext layout and length handling are consistent with SMB 3.1.1

ErrorResponseContext’s structure looks correct:

  • #[brw(align_before = 8)] on _error_data_length ensures each context starts at an 8‑byte boundary relative to the error data area, matching the spec’s alignment requirement.
  • _error_data_length is automatically calculated from error_data.len() on write, and used as the count when reading error_data, keeping length metadata consistent.
  • The use of #[smb_response_binrw] plus ErrorId as a separate repr(u32) enum is a clean separation of concerns.

This layout should interoperate well with the surrounding ErrorResponse parsing and ByteCount handling.

Also applies to: 47-55


95-105: ErrorId enum shape and documentation look good

The ErrorId enum is well-defined:

  • #[smb_response_binrw] plus #[brw(repr(u32))] give it a clear and stable on‑wire representation.
  • Default = 0 and ShareRedirect = 0x72645253 match the common SMB2 ERROR Context identifiers, and the doc comments describe their usage.

No changes needed here.

Comment on lines +5 to +6
#[cfg(feature = "client")]
use binrw::io::TakeSeekExt;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify TakeSeekExt gating vs. unconditional .take_seek usage

error_data’s map_stream closure calls s.take_seek(_byte_count.value.into()) (lines 31–32), which relies on TakeSeekExt, but the import is gated behind #[cfg(feature = "client")] (lines 5–6). If this module is compiled without the client feature and is not itself cfg‑gated, this will fail to compile due to the extension trait not being in scope.

Please confirm that either:

  • the error module (or this struct) is also #[cfg(feature = "client")], or
  • TakeSeekExt is otherwise brought into scope for non‑client builds, or
  • the cfg on the use is not needed and can be dropped.

You can quickly check how error.rs is included by running:

Also applies to: 31-32


🏁 Script executed:

#!/bin/bash
# Inspect how the error module is wired and whether it's cfg-gated
rg -n "mod error" crates/smb-msg/src -C3 || true

Length of output: 306


Now I need to directly examine the error.rs source code to see if the error_data field and its map_stream closure are feature-gated or unconditional.

[/run_scripts]


🏁 Script executed:

#!/bin/bash
# Examine error.rs lines 1-50 to see the structure and whether map_stream is feature-gated
cat crates/smb-msg/src/error.rs | head -50

Length of output: 1976


TakeSeekExt must be imported unconditionally or the map_stream closure must be feature-gated

The issue is confirmed: TakeSeekExt is imported only when feature = "client" (lines 5–6), but the error_data field's map_stream closure calls s.take_seek() unconditionally (line 31). Since the error module is not feature-gated in lib.rs, the struct and its field compile regardless of features. Without the client feature enabled, the trait will not be in scope when the binrw macro expands the closure, causing a compile error.

Fix by one of:

  • Remove the #[cfg(feature = "client")] guard on the TakeSeekExt import (lines 5–6), or
  • Gate the error_data field (and any other code using take_seek) behind #[cfg(feature = "client")], or
  • Gate the entire ErrorResponse struct behind #[cfg(feature = "client")].
🤖 Prompt for AI Agents
crates/smb-msg/src/error.rs (around lines 5–6 and ~31): the TakeSeekExt trait is
only imported under #[cfg(feature = "client")] but the error_data.map_stream
closure calls s.take_seek() unconditionally, causing a missing-trait compile
error when the client feature is off; fix by either remove the #[cfg(feature =
"client")] on the TakeSeekExt import so the trait is always in scope, or gate
the error_data field (and any closures calling take_seek) with #[cfg(feature =
"client")] or gate the entire ErrorResponse struct with #[cfg(feature =
"client")] — pick one and apply it consistently so calls to take_seek are only
compiled when the trait is available.

@coderabbitai
Copy link

coderabbitai bot commented Nov 21, 2025

✅ Actions performed

Reviews paused.

1 similar comment
@coderabbitai
Copy link

coderabbitai bot commented Nov 21, 2025

✅ Actions performed

Reviews paused.

@afiffon afiffon merged commit 303ec33 into main Nov 21, 2025
9 checks passed
@afiffon afiffon deleted the 144-document-smb-msg branch November 21, 2025 16:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Document smb-msg

2 participants