# smb2

[![Crates.io](https://img.shields.io/crates/v/smb2)](https://crates.io/crates/smb2)
[![docs.rs](https://img.shields.io/docsrs/smb2)](https://docs.rs/smb2)
[![CI](https://github.com/vdavid/smb2/actions/workflows/ci.yml/badge.svg)](https://github.com/vdavid/smb2/actions/workflows/ci.yml)
[![License](https://img.shields.io/crates/l/smb2)](LICENSE-MIT)
[![MSRV](https://img.shields.io/badge/MSRV-1.85-blue)](https://blog.rust-lang.org/2025/02/20/Rust-1.85.0.html)

A pure-Rust SMB2/3 client library with pipelined I/O. No C dependencies, no FFI. Faster than native macOS SMB in all
operations: 1.3-5x faster on uploads, downloads, listings, and deletes.

I built this because I needed fast SMB access for [Cmdr](https://github.com/vdavid/cmdr) (my file manager), and the
existing Rust SMB options weren't cutting it. The `smb` crate works fine for listing files but downloads are painfully
slow because it sends one read at a time. Native OS SMB clients pipeline their reads, and so does this library.

**Why this matters:**

- Cross-compile without system lib headaches (no `libsmbclient`, no `-sys` crates)
- Pipelined I/O by default, not as an afterthought
- Async and runtime-agnostic (uses `futures` traits)
- Works anywhere Rust compiles

## What it does

- Connect to SMB2/3 shares using NTLM or Kerberos authentication
- List directories, read files, write files, delete, rename, stat, create directories
- Compound requests (CREATE+READ+CLOSE in 1 round-trip, 4-way write compounds, compound delete/rename/stat)
- Batch operations (delete, rename, stat multiple files -- all requests sent before waiting for responses)
- Pipelined I/O with sliding window for large file transfers
- SMB 3.x signing (HMAC-SHA256, AES-CMAC, AES-GMAC) and encryption (AES-128/256-CCM/GCM)
- LZ4 compression
- Share enumeration (list shares on a server via IPC$ + srvsvc RPC)
- Streaming downloads and uploads with progress reporting and cancellation
- File watching (CHANGE_NOTIFY for live directory updates)
- Disk space queries (total, free, used)
- DFS path resolution (standalone DFS with transparent referral follow-through)
- Reconnection after network failures
- Auto-flush on writes (data safety for family photos and company docs)

## Limitations

Not yet supported:

- **Domain-based DFS**: standalone DFS links work; AD domain-based namespaces aren't supported yet
- **DFS target failback**: uses the first reachable target; no automatic failback to preferred targets
- **Multi-channel**: single TCP connection per client
- **QUIC/RDMA transport**: TCP only (covers ~99% of use cases)

If you need multi-channel or QUIC, check the [`smb`](https://crates.io/crates/smb) crate.

Not planned:

- Server implementation (this is a client library)
- SMB1 (deprecated, insecure)
- LZNT1 compression (LZ4 is supported; LZNT1 is legacy)

## Quick start

```rust
use smb2::{SmbClient, ClientConfig};

#[tokio::main]
async fn main() -> Result<(), smb2::Error> {
    let mut client = smb2::connect("192.168.1.100:445", "user", "pass").await?;

    // List shares
    let shares = client.list_shares().await?;
    for share in &shares {
        println!("{} - {}", share.name, share.comment);
    }

    // Connect to a share
    let mut share = client.connect_share("Documents").await?;

    // List files
    let entries = client.list_directory(&mut share, "projects/").await?;
    for entry in &entries {
        println!("{} ({} bytes)", entry.name, entry.size);
    }

    // Read a file
    let data = client.read_file(&mut share, "report.pdf").await?;
    std::fs::write("report.pdf", data)?;

    // Write a file
    let content = std::fs::read("local_file.txt")?;
    client.write_file(&mut share, "remote_file.txt", &content).await?;

    // Clean up
    client.disconnect_share(&share).await?;

    Ok(())
}
```

## Pipeline API

The pipeline is the core feature. It lets you batch multiple operations and execute them together:

```rust
use smb2::{Pipeline, Op, OpResult};

# async fn example(client: &mut smb2::SmbClient, share: &mut smb2::Tree) -> Result<(), smb2::Error> {
    let mut pipeline = Pipeline::new(client.connection_mut(), &share);

    let results = pipeline.execute(vec![
        Op::ReadFile("a.txt".into()),
        Op::ReadFile("b.txt".into()),
        Op::ListDirectory("docs/".into()),
        Op::Delete("temp.txt".into()),
    ]).await;

    for result in results {
        match result {
            OpResult::FileData { path, data } => println!("{}: {} bytes", path, data.len()),
            OpResult::DirEntries { path, entries } => println!("{}: {} entries", path, entries.len()),
            OpResult::Deleted { path } => println!("deleted {}", path),
            OpResult::Error { path, error } => eprintln!("{}: {}", path, error),
            other => println!("{:?}", other),
        }
    }
    # Ok(())
    #
}
```

For large file I/O, use the pipelined variants which fill the credit window:

```rust
# async fn example(client: &mut smb2::SmbClient, share: &mut smb2::Tree) -> Result<(), smb2::Error> {
    // Pipelined I/O with sliding window for large files
    let data = client.read_file_pipelined(&mut share, "big_file.iso").await?;
    client.write_file_pipelined(&mut share, "copy.iso", &data).await?;
    # Ok(())
    #
}
```

## Streaming I/O

For large files that don't fit in memory, use the streaming API. It downloads one chunk at a time and supports progress
reporting and cancellation.

### Streaming download

```rust
use tokio::io::AsyncWriteExt;

# async fn example(client: &mut smb2::SmbClient, share: &smb2::Tree) -> Result<(), smb2::Error> {
    let mut download = client.download(&share, "big_video.mp4").await?;
    println!("Downloading {} bytes...", download.size());

    let mut file = tokio::fs::File::create("big_video.mp4").await?;
    while let Some(chunk) = download.next_chunk().await {
        let bytes = chunk?;
        file.write_all(&bytes).await?;
        println!("{:.1}%", download.progress().percent());
    }
    # Ok(())
    #
}
```

### Write with progress and cancellation

```rust
use std::ops::ControlFlow;

# async fn example(client: &mut smb2::SmbClient, share: &mut smb2::Tree) -> Result<(), smb2::Error> {
    let data = std::fs::read("big_file.bin")?;
    client.write_file_with_progress(&mut share, "remote.bin", &data, |progress| {
        println!("{:.1}%", progress.percent());
        ControlFlow::Continue(()) // return ControlFlow::Break(()) to cancel
    }).await?;
    # Ok(())
    #
}
```

All write methods (`write_file`, `write_file_pipelined`, `write_file_with_progress`) flush data to persistent storage
before closing the file handle.

## Installation

Add to your `Cargo.toml`:

```toml
[dependencies]
smb2 = "0.2"
```

You'll also need an async runtime. The library is runtime-agnostic, but [tokio](https://github.com/tokio-rs/tokio) is
the most common choice:

```toml
[dependencies]
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
```

## API overview

### High-level API

For when you want to do one thing and get the result:

- `smb2::connect()` -- connect and authenticate (shorthand)
- `SmbClient::connect()` -- connect with full config
- `client.list_shares()` -- list available shares
- `client.connect_share()` -- connect to a share
- `client.list_directory(&mut share, path)` -- list a directory
- `client.read_file(&mut share, path)` -- download a file
- `client.write_file(&mut share, path, data)` -- upload a file
- `client.delete_file(&mut share, path)` -- delete a file
- `client.delete_files(&mut share, &paths)` -- batch delete (all requests sent before waiting)
- `client.stat(&mut share, path)` -- get file metadata
- `client.stat_files(&mut share, &paths)` -- batch stat
- `client.rename(&mut share, from, to)` -- rename a file
- `client.rename_files(&mut share, &renames)` -- batch rename
- `client.create_directory(&mut share, path)` -- create a directory
- `client.delete_directory(&mut share, path)` -- remove a directory
- `client.download(&share, path)` -- streaming download with progress (memory-efficient)
- `client.upload(&share, path, data)` -- streaming upload with progress
- `client.write_file_streamed(&mut share, path, callback)` -- write from a streaming source (memory-efficient, pipelined)
- `client.watch(&share, path, recursive)` -- watch for file changes (CHANGE_NOTIFY)
- `client.fs_info(&mut share)` -- disk space (total, free, used)
- `client.reconnect()` -- reconnect after network failure
- `client.credits()` -- current available credits
- `client.estimated_rtt()` -- round-trip time from negotiate
- `client.disconnect_share(&share)` -- disconnect from a share

### Pipeline API

For when you have many operations and want them fast:

- `Pipeline::new(conn, &share)` -- create a pipeline
- `pipeline.execute(ops)` -- run a batch of operations

### Low-level API

For advanced use cases, the underlying types are available:

- `Connection` -- message exchange, credit tracking
- `Session` -- NTLM authentication, key derivation
- `Tree` -- share-level file operations (take `&mut Connection`)
- `NegotiatedParams` -- protocol parameters from negotiate

## Performance

Benchmarked against native macOS SMB (with F_NOCACHE to disable kernel page cache) and the `smb` crate on a QNAP NAS
over Gigabit LAN, SMB 3.1.1:

### Small files (100 × 100 KB)

| Operation | native | smb2  | Speedup         |
|-----------|--------|-------|-----------------|
| Upload    | 3.69s  | 1.91s | **1.9x faster** |
| List      | 47ms   | 21ms  | **2.2x faster** |
| Download  | 3.10s  | 617ms | **5.0x faster** |
| Delete    | 3.08s  | 1.03s | **3.0x faster** |

### Medium files (10 × 10 MB)

| Operation | native | smb2  | Speedup         |
|-----------|--------|-------|-----------------|
| Upload    | 1.66s  | 1.23s | **1.3x faster** |
| List      | 27ms   | 19ms  | **1.4x faster** |
| Download  | 4.00s  | 2.93s | **1.4x faster** |
| Delete    | 301ms  | 128ms | **2.4x faster** |

### Large files (3 × 50 MB)

| Operation | native | smb2  | Speedup         |
|-----------|--------|-------|-----------------|
| Upload    | 1.69s  | 1.56s | ~parity         |
| List      | 27ms   | 18ms  | **1.5x faster** |
| Download  | 5.62s  | 1.11s | **5.1x faster** |
| Delete    | 117ms  | 54ms  | **2.1x faster** |

Key optimizations: compound requests (CREATE+READ+CLOSE in 1 round-trip), pipelined I/O with sliding window and adaptive
chunk sizing, and 256-credit request for wide pipeline windows.

**Note:** Native macOS download benchmarks use F_NOCACHE to bypass the kernel page cache. Without this, cached native
reads appear ~20x faster because they skip the network entirely. F_NOCACHE gives a fair comparison of actual network I/O
performance.

The benchmark tool is included at `benchmarks/smb/`. Run with `cargo run -p smb-benchmark --release`.

## Comparison with existing libraries

### vs `smb` crate

The [`smb`](https://crates.io/crates/smb) crate supports multi-channel, QUIC, and RDMA transport. If you need those, use
it. For everything else, `smb2` is a better fit:

- **Compound + pipelined I/O**: `smb` sends one request at a time; smb2 uses compound requests and pipelined reads with
  sliding window
- **Auto-reconnect with durable handles**: survives Wi-Fi drops without restarting transfers
- **Comprehensive test suite**: `smb` has almost no tests
- **MIT OR Apache-2.0**: `smb` is MIT-only

I initially considered forking `smb`, but the architecture didn't support pipelining well, and adding it would have been
a near-complete rewrite anyway.

### vs `pavao`

[`pavao`](https://crates.io/crates/pavao) wraps `libsmbclient` via FFI. It works, but you need `libsmbclient` installed,
which means system package management, cross-compilation headaches, and all the fun that comes with C dependencies.

`smb2` is pure Rust. `cargo build` and done.

## Implementation notes

- I used Claude extensively for this implementation. The code has a comprehensive test suite (unit tests with mock
  transport, property tests with proptest, integration tests against Docker Samba), and it works for my use case in
  Cmdr. If you distrust AI-generated code, that's fair, but please check the tests and decide for yourself.
- The protocol implementation is based on
  Microsoft's [MS-SMB2 spec](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/). I converted the
  relevant sections to Markdown so AI agents could work from them effectively. The spec files live in `docs/specs/`.

## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.

## License

MIT OR Apache-2.0, at your option.
