All notable changes to smb2 will be documented in this file.
The format is based on keep a changelog, and we use Semantic Versioning 2.0.0.
- In-process diagnostics.
SmbClient::diagnostics()andConnection::diagnostics()return aDiagnostics/ConnectionDiagnosticssnapshot of the client's current state — negotiated dialect, credits + in-flight count + nextMessageId, signing/encryption/compression status, RTT estimate, DFS cache, the per-connection session, and aMetricsSnapshotof 17 monotonicAtomicU64counters (requests sent, wire bytes sent/received, the four disjoint routing outcomes —responses_routed_ok,responses_routed_err,responses_late_after_drop,responses_stray— plusstatus_pending_loops,unsolicited_notifications_received,signature_failures,decrypt_failures,decompress_failures,malformed_frames,session_expired_events,compound_requests_sent,explicit_cancels_sent,requests_returned_err). Counters survive connection teardown; per-connection counters reset onSmbClient::reconnect, client-level counters (reconnects,dfs_referrals_resolved,dfs_cache_hits) survive. ADisplayimpl renders a compact terminal view. Seedocs/specs/diagnostics-plan.md. - Optional
serdefeature (off by default):Serializeimpls on every diagnostics type plus the protocol enums they embed (Dialect,Cipher,SigningAlgorithm,Capabilities,Guid,SessionId,TreeId,MessageId). For consumers building MCP tools, dashboards, or any structured exporter. JSON form is the in-memory shape (newtypes aretransparent,Capabilitiesis the rawu32bits). examples/diagnostics.rs: connect, run a couple of ops, dump the snapshot viaDisplay— or--jsonwhen built with--features serde.
- Stale
src/client/CLAUDE.mdgotcha about silent frame discard on decrypt/decompress/malformed-header errors. Phase 3 P3.4 fixed this — the receiver task tears down the connection on those paths and fansErr(Disconnected)to every pending waiter. Note now matches the code, and the diagnostics counters above attribute each tear-down to its trigger.
- Breaking:
Watcherowns itsConnectionandTreeinstead of borrowing them. The lifetime parameter is gone;Watcheris now'static.Tree::watchandSmbClient::watchlose their'aparameter and returnWatcherinstead ofWatcher<'a>. Existing call sites that wrotelet mut w = client.watch(&tree, "path", true).await?;keep compiling unchanged — only code that explicitly named the type asWatcher<'_>needs the<'_>removed. The connection clone is cheap (Arc::clone, multiplexes over the same SMB session), so the caller is free to keep using theirConnectionfor other ops while a watcher runs. The previous "use a separateSmbClientto perform operations while watching" workaround is no longer needed.
- CHANGE_NOTIFY consumer-side loss window closed.
Watcher::next_eventsnow keeps one CHANGE_NOTIFY request continuously outstanding on the wire by pre-issuing the next request before awaiting the current response. Without this, server-side events that arrived during the consumer's response-processing window were dropped silently by strict servers (older Samba builds, NAS firmware) that don't queue events between consecutive CHANGE_NOTIFY requests. Reproduced in the field as 9 file copies → 4 watcher events delivered. Confirmed against Docker Samba, QNAP TS-464, and Windows-shape servers viatests/integration.rs::nas_accepts_stacked_change_notifyand the new unit test insrc/client/watcher.rs::loss_window_tests.
Connection::dispatch/Connection::dispatch_with_credits(crate-internal): variant ofexecutethat returns oncetransport.send().awaitcompletes, handing back the responseoneshot::Receiverfor the caller to await separately. Enables pipelining patterns where the next request must be on the wire before the previous one's response is processed.
- Server compatibility check: the fix relies on the server accepting two simultaneous CHANGE_NOTIFY requests on the same
FileId. MS-SMB2 allows it; Windows, modern Samba (Docker fixtures), and QNAP TS-464 firmware all confirmed-good. Servers that reject would surfaceErr(Error::Protocol { status: ..., command: ChangeNotify })on the secondnext_events()call. If you hit this on a particular NAS, please file an issue with the NTSTATUS — a config-flag fallback to depth-0 (pre-issue off) is straightforward to add but hasn't been needed yet.
- New consumer-class fixture
smb-consumer-maxreadsize(port 10494, envSMB_CONSUMER_MAXREADSIZE_PORT) withsmb2 max read = smb2 max write = 65536. Mirrors the internal-fixturesmb-maxreadsizeso consumer apps can guard against streaming-fallback regressions (every transfer >64 KB is chunked, exercising the same code path that hung pre-0.9.0) without standing up smb2'sinternal/fixtures. Exposed viasmb2::testing::maxreadsize_port()and shipped throughwrite_compose_files.
- Breaking:
FileWriterowns itsConnectionandArc<Tree>instead of borrowing&'a mut Connection. The lifetime parameter is gone;FileWriteris now'static. Multiple writers built from clones of the sameConnectionpipeline their WRITEs over one SMB session, multiplexed byMessageIdin the receiver task — no external locking needed. - Breaking:
Tree::create_file_writersignature changed from(&self, conn: &mut Connection, path)to(self: &Arc<Self>, conn: Connection, path). Callers that previously held an ownedTreeneedArc::new(tree)once; callers usingSmbClient::create_file_writerare unaffected at the call site (the client wraps the cloning internally). - Breaking:
SmbClient::create_file_writertakes&selfinstead of&mut selfand returnsFileWriter(no lifetime) instead ofFileWriter<'_>. The previous&mut selfrequirement was the root cause of a deadlock in the cmdr SMB volume'swrite_from_streamunder sustained concurrent pressure on QNAP — the consumer had to hold its session mutex for the entire streaming upload. - Breaking:
Treeis now#[derive(Clone)]. Lets convenience constructors wrap aTreeinArcwithout reconstructing field-by-field. Cloning aTreeis cheap (oneTreeId, three shortStrings, two bools).
client::stream::open_file_writer(tree: Arc<Tree>, conn: Connection, path: &str) -> Result<FileWriter>— free function for building aFileWriterwhen you already hold a clonedConnectionand anArc<Tree>and don't want the convenience-wrapper indirection.Tree::create_file_writerandSmbClient::create_file_writerboth delegate to it.
- Internal callers in
Tree::write_file_pipelinedandTree::write_file_streamedwere not touched: they use their own per-chunkexecute_with_creditsloop and never built aFileWriter.
- Breaking:
ErrorKindis now#[non_exhaustive]. Match expressions overErrorKindmust include a_arm. Adding a new variant in a future release is now a non-breaking change. Consumers that already use a_fallback (the pattern recommended by the doc example) need no update.
ErrorKind::AlreadyExists—STATUS_OBJECT_NAME_COLLISION(returned byCreatewhen a file or directory of the same name exists) now classifies asAlreadyExistsinstead of falling through toOther. Lets callers handle "merge into existing directory" or surface a friendly "name taken" message without substring-matching the error text.ErrorKind::IsADirectory—STATUS_FILE_IS_A_DIRECTORYnow classifies asIsADirectory. Useful for delete-fast-path callers that trydelete_filefirst and fall back todelete_directoryon this kind.ErrorKind::NotADirectory—STATUS_NOT_A_DIRECTORYnow classifies asNotADirectory(e.g.,list_directoryon a file).error::tests::classify_status_contract: a single table-driven test that documents the fullNtStatus → ErrorKindmapping, including statuses intentionally left asOther. Replaces the per-arm classification tests so the contract lives in one auditable place; adding a newNtStatusshould also add a row here.
STATUS_OBJECT_NAME_INVALID,STATUS_DELETE_PENDING,STATUS_INSUFFICIENT_RESOURCES, andSTATUS_INSUFF_SERVER_RESOURCESdeliberately still classify asErrorKind::Other— no current consumer needs to branch on them. Promoting any of these to its own variant is non-breaking under the#[non_exhaustive]policy.
- DFS referral response parser (
msg::dfs::RespGetDfsReferral::unpack): a V2 entry whose server-declaredentry_sizewas small enough to pass the initialentry_size < 4guard but short enough to truncate the fixed body would trigger a panic (out-of-bounds) on the finalnetwork_address_offsetread. The V2 body is 18 bytes after the 4-byte version/size prefix, not 16. Found by fuzzing; regression covered bymsg::dfs::tests::resp_parse_v2_short_entry_returns_clean_error.
fuzz/crate usingcargo-fuzzwith 12 fuzz targets across the parse entry points: top-level SMB2 header, TRANSFORM/COMPRESSION transform headers, compound-frame splitter, full frame + sub-frame body parse, Negotiate request/response (negotiate contexts), Create request/response (create contexts), QueryInfo response, and DFS referral response. Seed corpus generator attests/fuzz_seeds.rs(run withcargo test --test fuzz_seeds -- --ignored). Targets are feature-gated behindfuzzing; the feature exposes parse entry points viasmb2::fuzzingand is not meant for application use..github/workflows/fuzz.yml: weekly 5-minute-per-target fuzz run on schedule + manual dispatch.
Tree::downloadfor streaming reads driven by a borrowedConnection, mirroring the existingSmbClient::downloadbut unlocking concurrent downloads on a single SMB session (viaConnection::clone).
Tree::open_fileandFileDownload::neware nowpub(werepub(crate)), so callers can build custom chunk loops or reuse a handle across multiple readers.SmbClient::downloadnow delegates toTree::download— zero behavior change.
First public release on crates.io. Bundles the FileWriter streaming write API with the Phase 3 Connection actor refactor (breaking API change).
- Breaking:
Connection's send/receive API collapsed intoexecute/execute_compound(Phase 3 of the actor refactor). The legacysend_request,send_request_with_credits,send_compound,receive_response,receive_compound, andreceive_compound_expectedare gone; callers use a single awaitable call per op.Connection::execute(command, body, tree_id)returnsResult<Frame>whereFrame = { header, body, raw }.Connection::execute_compound(&[CompoundOp])returnsResult<Vec<Result<Frame>>>— the outerResultis "did the compound hit the wire", the inner one is per-sub-op so partial-failure handling (for example,CREATEok,READfails, issue standaloneCLOSEwith the returnedFileId) is straightforward.Connection: Clone; clones share the receiver task so concurrentexecutecalls from different tasks/clones multiplex over the same SMB session. Cancelling (dropping) a future mid-flight is safe by construction — the matching late-arriving frame is discarded. Connectionnow uses a background receiver task with per-requestoneshot::Senderrouting (Phase 2 of the actor refactor). Seedocs/specs/connection-actor.md. A caller's dropped future (for example,tokio::task::JoinHandle::abort()in a downstream consumer) now correctly discards the corresponding late-arriving response instead of letting it pollute the next operation's receive. Credits still tick on dropped-caller frames so throughput stays correct under cancellation churn.tokiois formalized as a hard runtime requirement; added"rt"to the tokio feature set. The library spawns a receiver task perConnection.MockTransport::receive()now awaits viatokio::sync::Notifywhen the queue is empty instead of returningErr(Disconnected)immediately. AddedMockTransport::close()to signal end-of-stream. This lets the background receiver task stay alive across a test's interleavedqueue_responsecalls. External consumers writing tests withMockTransportdirectly need to callclose()to get the old behavior.MockTransport::enable_auto_rewrite_msg_id()— opt-in test-mode shim that rewrites each zero-msg_id sub-frame of a queued response to match the next pending sent msg_id in FIFO order, so cannedbuild_*_responsehelpers that hardcodeMessageId(0)route through the Phase 3 receiver task without needing to predict the caller's allocated msg_ids. Replaces the pre-Phase-3Connection::set_orphan_filter_enabled(false)escape hatch.
Single request:
// Before
conn.send_request(Command::Create, &req, Some(tree_id)).await?;
let (header, body, _raw) = conn.receive_response().await?;
// After
let frame = conn.execute(Command::Create, &req, Some(tree_id)).await?;
// frame.header, frame.body, frame.rawCompound request:
// Before
let ops = vec![(Command::Create, &create_req, CreditCharge(1)), /*...*/];
conn.send_compound(tree_id, &ops).await?;
let responses = conn.receive_compound_expected(3).await?;
// responses: Vec<(Header, Vec<u8>)>
// After
let ops = [
CompoundOp { command: Command::Create, body: &create_req, tree_id: Some(tree_id), credit_charge: CreditCharge(1) },
// ...
];
let frames = conn.execute_compound(&ops).await?;
// frames: Vec<Result<Frame>> — each entry may independently be Err (session expired, signature
// verify failure, etc.). Sub-op status codes (OBJECT_NAME_NOT_FOUND and friends) ride in
// frames[i].as_ref()?.header.status, NOT in the inner Result.Pipelined sliding-window reads/writes: use futures_util::stream::FuturesUnordered of boxed
execute_with_credits futures across conn.clone()s — see src/client/tree.rs::read_pipelined_loop for
the canonical pattern. MAX_PIPELINE_WINDOW and conn.credits()-based pacing stay on the caller side.
FileWriter— push-based streaming write API with pipelined I/O. Consumer drives the loop, pushing chunks viawrite_chunk()with automatic backpressure (sliding window, credit-aware). Complement toFileDownloadfor reads. Created viaSmbClient::create_file_writer().FileWriter::abort()— fast-cancel companion tofinish(). Discards any unsent data, drains in-flight WRITE responses to keep credits and message IDs in sync, skips the server-side FLUSH (fsync), and does a best-effort CLOSE. Errors during drain/close are swallowed so callers can exit quickly. Use on cancellation or error paths where the partial remote file is going to be deleted anyway — it saves the fsync round-trip vs. callingfinish(). The caller is responsible for deleting the partial file.Connection::receive_compound_expected(n)— gathers exactlyncompound sub-responses, transparently reading additional transport frames when the server splits the chain. All compound-using methods (read_file_compound,write_file_compound,fs_info,stat,rename,delete_file/delete_directory, and the batchdelete_files/rename_files/stat_files) now use it.- 13 new Docker integration tests for
FileWriter: basic, large (5 MB), empty, single byte, overwrite, equivalence withwrite_file_pipelined, binary data integrity, 64 KB max-write-size, signing, encryption, read-only rejection, 100 MB stress (guest), 100 MB stress (200ms latency) - 10 new unit tests for
FileWriterpipelining, backpressure, chunk splitting, error handling
- Unrecoverable frame errors (decrypt auth-tag mismatch, decompression failure, malformed sub-frame
header after compound splitting) no longer hang pending waiters indefinitely. Previously the
receiver task log-at-WARN'd and
continued, which left any waiter matching the discarded frame'sMessageIdstuck onrx.awaitforever — themsg_idisn't recoverable from an unparseable frame, so no targeted error could be delivered. Per decision E6 in the Phase 3 design, the receiver task now tears the connection down on these conditions: fansErr(Disconnected)to every pending waiter and exits. Callers see the error promptly and can reconnect. Sub-frame signature-verification failures andSTATUS_NETWORK_SESSION_EXPIREDstay targeted (delivered only to the matching waiter) because themsg_idis known. - Caller futures that get dropped mid-flight (for example, a cancelled listing task in a consuming
application) no longer leave in-flight SMB requests on the wire that corrupt subsequent operations.
The receiver task discards late-arriving frames whose waiter's
oneshot::Receiverhas been dropped. Reproduces and fixes the cmdrlisting_task.abort()regression. - Compound requests no longer error with
invalid_data: expected N compound responses, got Mwhen the server sends responses as separate frames instead of one compounded frame. Per MS-SMB2 3.3.4.1.3 the server SHOULD compound but MAY split, and Samba (including QNAP NAS firmware built on Samba) splits in some scenarios. Hit in the wild viafs_infoagainst a QNAP NAS.
write_file_streamed— write files from a streaming callback source with pipelined I/O, bounded memory usage (sliding window, not full file), automatic chunk splitting atMaxWriteSize, works with signing and encryption (f5ade78)- 14 new tests: 3 unit (basic, empty, callback error), 9 Docker integration (guest, small, large 10 MB, empty, early
stop, 64 KB max-write-size, mandatory signing, mandatory encryption, read-only rejection), 2 NAS integration
(write + verify, performance comparison vs
write_file_pipelined)
- Bumped
rand0.9.2 → 0.9.4 (RUSTSEC-2026-0097)
- DFS (Distributed File System) support — resolve
\\domain\dfs-namespace\pathtransparently, follow referrals across servers, multi-target failover, TTL-based referral cache (d353490, bfd8557, 03c4c2a, 87a7d78) - Compound delete (1 RTT instead of 2), compound rename (1 RTT instead of 3), compound stat (1 RTT instead of 4) (33938591, 5e0f7a5, 4dc8fb8)
read_file_with_progressfor pipelined reads with progress callback and cancellation (37e3370)- Batch operations —
delete_files,rename_files,stat_filessend all compound requests before waiting for responses, partial failures are independent (afe4395) - DFS wire format types:
ReqGetDfsReferral,RespGetDfsReferralwith V2/V3/V4 referral entries (e9e5bf9) - Auto-set
SMB2_FLAGS_DFS_OPERATIONSbased on tree capabilities (f254e96) - Connection pool for DFS cross-server routing,
ClientConfig.dfs_enabledanddfs_target_overrides(03c4c2a, edcf730) - Docker DFS test containers (smb-dfs-root:10456, smb-dfs-target:10457) with 4 integration tests (edcf730)
- IOCTL
InputOffsetdouble-countedHeader::SIZE, causingSTATUS_INVALID_PARAMETERon DFS referral requests (edcf730) - DFS paths missing
server\shareprefix inTree::format_path(edcf730) - Cross-server routing matched hostname-only instead of addr:port (edcf730)
- Kerberos authentication — full AS + TGS + AP-REQ flow with pre-auth, tested end-to-end against Windows Server 2022 with Active Directory Domain Services (9b40b00)
- Kerberos credential cache (ccache) support — read MIT Kerberos ccache files (v3 and v4) for password-less auth from
kinittickets,Session::setup_kerberos_from_ccache()(2344f15) - AP-REP mutual authentication — server sub-session key extraction for cryptographic server identity proof (b966d2c)
- SPNEGO token wrapping — hand-rolled ASN.1/DER encoding for NegTokenInit/NegTokenResp (RFC 4178 / MS-SPNG), no external ASN.1 dependency (c27c88f)
- Kerberos crypto: AES-CTS (RFC 3962), RC4-HMAC (RFC 4757), PBKDF2 string-to-key, n-fold, HMAC-SHA1-96 checksums (e23b851)
- Kerberos ASN.1 messages: AS-REQ, TGS-REQ, AP-REQ, Authenticator, KDC-REP parsing — all hand-rolled DER (97b57a5)
- KDC client: UDP primary with TCP fallback on
KRB_ERR_RESPONSE_TOO_BIG, exponential backoff retries (97b57a5) Session::setup_kerberos()andSession::setup_kerberos_from_ccache()public API (3a63337, 2344f15)- Support for AES-256, AES-128, and RC4-HMAC encryption types, with AES-256 preferred (01ad252)
- KDC-REP field tags: pvno is
[0]not[1]per RFC 4120 (3a63337) - AES-CTS key derivation: Ki constant is 0x55 (encrypt/decrypt integrity), not 0x99 (standalone checksum) (661a245)
- TGS-REQ AP-REQ Authenticator was missing body checksum over KDC-REQ-BODY (RFC 4120 section 7.2.2, key usage 6) (661a245)
Hard-won lessons from testing against Windows AD (documented in src/auth/CLAUDE.md):
- MS Kerberos OID (
1.2.840.48018.1.2.2) required as primary SPNEGO mechanism for Windows - Key usage 11 (not 7) for AP-REQ Authenticator encryption in SPNEGO exchanges
- GSS-API wrapping of AP-REQ inside SPNEGO mechToken
- Raw ticket byte pass-through (re-encoding corrupts the encrypted ticket)
- Session key etype detection from TGS-REP (may differ from ticket encryption type)
- Compound requests — CREATE+READ+CLOSE (3-way) and CREATE+WRITE+FLUSH+CLOSE (4-way) as single transport frames, reducing round-trips from 3-4 to 1 per file operation (a9293b6, cb022bc)
read_file()andwrite_file()auto-select compound (small) or pipelined (large) — callers don't choose (25b2f68, cb022bc)- Streaming upload with
FileUpload— compound for small files, chunked for large, same caller API either way (8b2283a) - Streaming download with
FileDownload, progress reporting withControlFlow-based cancellation (c11f5f3) - Sliding window pipeline — each response immediately triggers the next chunk, keeping the TCP pipe full (dd36181)
- SMB 3.x encryption wired into client layer — TRANSFORM_HEADER wrapping, sign-then-compress-then-encrypt send path, AES-128/256-CCM/GCM (101d22d)
- LZ4 compression wired into connection layer — negotiated during handshake, applied per-message when it reduces size (e2921f9)
- File watching via
CHANGE_NOTIFY—Watcherstruct withnext_events()long-poll, recursive watching support (75b281d) - Disk space query (
fs_info) via compound CREATE+QUERY_INFO+CLOSE (6d3a05c) ErrorKindfor high-level error classification —NotFound,AccessDenied,ConnectionLost, etc. instead of raw NTSTATUS codes (58aead2)- Oplock break notification handling — detected by MessageId
0xFFFF..., logged and skipped without crashing (371b984) STATUS_BUFFER_OVERFLOWaccepted as partial success in QueryInfo responses (4122d16)- CANCEL requests and session expiry detection with
Error::SessionExpired(4924a2a) - Docker test infrastructure — 12 Samba containers covering guest, auth, signing, readonly, ancient (SMB1), flaky, slow, encryption (GCM + CCM), 50 shares, max read size, with 43 integration tests (8edf837, 7ee088f, 812ad39)
- Docker tests in CI — builds containers and runs 43 tests on every PR (488e1d0)
- 7 runnable examples:
list_shares,list_directory,read_file,streaming_download,disk_space,watch_directory,write_file(7203bdb) Send + Syncbounds onPacktrait for async callers (4538205)- GitHub Actions CI: fmt, clippy, test, doc, MSRV 1.85 (f0b00cd)
STATUS_PENDINGhandling —receive_response()now loops past interim responses instead of treating them as errors (8edf837)- Multi-credit charges — streaming download/upload and
write_file_with_progresswere sendingcredit_charge=1for MB-sized payloads, Samba rejects withSTATUS_INVALID_PARAMETER(8edf837) - Cipher fallback — fall back to AES-128-CCM when server omits encryption negotiate context (812ad39)
- Smart read selection — sequential for files < MaxReadSize (1 RTT), pipelined only when beneficial (3f0cd77)
- Credit request bumped from 32 to 256 per request, credits grow rapidly (dd36181)
- Pipeline uses server-negotiated MaxReadSize/MaxWriteSize with correct multi-credit CreditCharge (b0cacdd)
trivial_message!andnt_status_codes!macros to reduce boilerplate (fb4b9e4)- CLAUDE.md files for all 8 modules, agent docs colocated with code (2d884d0)
- Concurrent pipelined read and write — send multiple READ/WRITE requests by filling the credit window, reassemble responses in offset order, handles out-of-order responses (7f3068a)
- Share enumeration — IPC$ + srvsvc RPC flow, QNAP returns 8 disk shares, Pi returns 1 (cbed0ab)
SmbClienthigh-level API —connect(),list_shares(),connect_share(),reconnect(), stored credentials (cbed0ab)- Convenience
smb2::client::connect("host:445", "user", "pass")one-liner (cbed0ab) - File operations:
write_file,delete_file,stat,rename,create_directory,delete_directory(c80f126) - Clean re-exports from
lib.rs—use smb2::{SmbClient, Tree}instead of reaching into submodules (cdb203e) - 4 runnable examples:
list_shares,list_directory,read_file,write_file(cdb203e) - Benchmarks against native macOS SMB and
smbcrate: small files 2-3x faster, medium files match native on upload, 8.5x faster thansmbon download (031d52b, 4cbc961) - Integration tests against QNAP NAS (SMB 3.1.1, NTLM, AES-GMAC) and Raspberry Pi 4 (SMB 3.1.1, guest) (c80f126)
- Preauth hash: exclude final SESSION_SETUP success response from hash — including it produces wrong keys for SMB 3.1.1 (32f0f30)
- QueryDirectory: cap
OutputBufferLengthto 65536, always send"*"pattern (empty filename rejected by QNAP + Samba) (b9d49f7) - GMAC uses AES-128-GCM (16-byte key), not AES-256-GCM (dc91351)
- GMAC nonce needs server role bit for response verification (dc91351)
- Only verify signature when
SMB2_FLAGS_SIGNEDis set (skip STATUS_PENDING and oplock breaks) (dc91351)
- SMB 2.0.2, 2.1, 3.0, 3.0.2, 3.1.1 dialect support — negotiate with all five dialects, preauth integrity, encryption, compression, and signing negotiate contexts (c3a2e43, 36428728)
- NTLM authentication (NTLMv2 with MIC) — full NEGOTIATE/CHALLENGE/AUTHENTICATE flow, session key exchange, known-answer test vectors from MS-NLMP section 4.2.4 (60c4163)
- Wire format pack/unpack for all 19 SMB2 commands — header, negotiate, session setup, tree connect, create, close, read, write, flush, lock, ioctl, query directory, change notify, query info, set info, echo, cancel, logoff, oplock break, transform header, compression header (c3a2e43)
- Binary serialization primitives —
ReadCursor/WriteCursorwith LE primitives, UTF-16LE, alignment, backpatching,Pack/Unpacktraits,Guidwith mixed-endian layout,FileTimewith Windows epoch conversion (8be0549) - Newtypes for all protocol IDs:
SessionId,MessageId,TreeId,CreditCharge,FileIdwith sentinel constants (8be0549) - TCP transport with split send/receive traits (avoids deadlock in pipeline's
select!loop), correct framing (0x00 + 3-byte BE length), 16 MB max frame, Nagle disabled (5ae8027) - MockTransport for TDD — FIFO response queue, message recording, assertion helpers (5ae8027)
- SMB 3.x signing: HMAC-SHA256 (2.0.2/2.1), AES-128-CMAC (3.0/3.0.2), AES-256-GMAC (3.1.1) (a7080f3)
- SMB 3.x encryption: AES-128/256-CCM and AES-128/256-GCM with monotonic nonce generator (a7080f3)
- SP800-108 key derivation for SMB 3.0/3.0.2 (legacy labels) and 3.1.1 (preauth hash context) (a7080f3)
- LZ4 compression via
lz4_flex(pure Rust, zero C deps) (a7080f3) - Connection layer: negotiate, credit management, message ID sequencing, preauth hash tracking, signing integration (36428728)
- Session layer: multi-round-trip SESSION_SETUP with NTLM, key derivation per dialect, signing activation (36428728)
- Tree layer: TREE_CONNECT with UNC path, directory listing, file reading (36428728)
- RPC module: DCE/RPC PDU encoding, NDR encoding for
NetShareEnumAll(36428728) - Structured logging via
logcrate — info for lifecycle, debug for protocol, trace for bytes, never logs secrets (1d7273a) - Error types with
is_retryable(),status(),Auth,Timeout,Disconnected,SessionExpiredvariants (5ae8027) MAX_UNPACK_BUFFER(16 MB) allocation cap to prevent OOM from malicious packets (073452c)- 512 unit tests, 10 integration tests against real hardware, zero clippy warnings