Skip to content

Commit 3bcd56f

Browse files
authored
update: sync russh/russh-sftp forks to upstream and unify ssh-key (#207)
Sync bssh-russh to upstream russh 0.61.1 (was 0.60.3): brings the new RustCrypto generation (sha2/sha1 0.11, hmac 0.13, aes 0.9, cbc 0.2, ctr 0.10, digest 0.11, pbkdf2 0.13, ssh-key 0.7.0-rc.10, ssh-encoding 0.3.0-rc.9). The PTY Handle::data() drain fix is re-ported onto the new server/session.rs, and three patches now merged upstream (channel-write-ordering, agent-frame-length-cap, sha1-mac-exclude) are removed so only handle-data-fix.patch remains. Adds [lib] name = "russh" so vendored doctests resolve, a clippy lints allow for the one vendored style lint, and a PTY regression test in tests/pty_handle_data.rs. Sync bssh-russh-sftp to upstream russh-sftp 2.3.0 (was 2.1.2): the pipelined File I/O helpers (write_all_pipelined / read_to_writer_pipelined) are re-applied on top of 2.3.0, and bssh's SFTP server is adapted to the 2.3.0 Handler::Error change (Into<StatusReply>, which now also carries the error message). Unify ssh-key to =0.7.0-rc.10 in the bssh crate so the workspace resolves a single ssh-key version instead of 0.6 and 0.7-rc side by side; bump the russh/russh-sftp dependency requirements, enable argon2's std feature (restores rand_core OsRng), and refresh transitive deps via cargo update. Make both forks' create-patch.sh self-contained (clone upstream, correct patch names, graceful fallback where upstream publishes no tags), generalize sync-upstream.sh to apply every patches/*.patch with upstream-detection, and rewrite both fork READMEs for the new versions.
1 parent 43b56c1 commit 3bcd56f

61 files changed

Lines changed: 4910 additions & 1715 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Cargo.lock

Lines changed: 395 additions & 706 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,14 @@ edition = "2024"
1919

2020
[dependencies]
2121
bytes = "1.11.1"
22-
tokio = { version = "1.52.1", features = ["full"] }
22+
tokio = { version = "1.52.3", features = ["full"] }
2323
# Use our internal russh fork with session loop fixes
2424
# - Development: uses local path (crates/bssh-russh)
2525
# - Publishing: uses crates.io version (path ignored)
26-
russh = { package = "bssh-russh", version = "0.60.3", path = "crates/bssh-russh" }
27-
# Use our internal russh-sftp fork tracking upstream 2.1.2
26+
russh = { package = "bssh-russh", version = "0.61.1", path = "crates/bssh-russh" }
27+
# Use our internal russh-sftp fork tracking upstream 2.3.0
2828
# (adds pipelined File I/O; serde_bytes perf fix is now upstreamed)
29-
russh-sftp = { package = "bssh-russh-sftp", version = "2.1.2", path = "crates/bssh-russh-sftp" }
29+
russh-sftp = { package = "bssh-russh-sftp", version = "2.3.0", path = "crates/bssh-russh-sftp" }
3030
clap = { version = "4.6.1", features = ["derive", "env"] }
3131
anyhow = "1.0.102"
3232
thiserror = "2.0.18"
@@ -62,9 +62,11 @@ shell-words = "1.1.1"
6262
libc = "0.2"
6363
ipnetwork = "0.21"
6464
bcrypt = "0.19"
65-
argon2 = "0.5"
65+
argon2 = { version = "0.5", features = ["std"] }
6666
rand = "0.10"
67-
ssh-key = { version = "0.6", features = ["std"] }
67+
# Pinned to match russh's ssh-key (0.61.x uses =0.7.0-rc.10) so the workspace
68+
# resolves a single ssh-key version instead of 0.6 + 0.7-rc side by side.
69+
ssh-key = { version = "=0.7.0-rc.10", features = ["std"] }
6870
async-compression = { version = "0.4", features = ["tokio", "gzip"] }
6971
serde_json = "1.0"
7072
opentelemetry = "0.32"

crates/bssh-russh-sftp/Cargo.toml

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
[package]
22
name = "bssh-russh-sftp"
3-
version = "2.1.2"
3+
version = "2.3.0"
44
authors = ["Jeongkyu Shin <inureyes@gmail.com>"]
5-
description = "Temporary fork of russh-sftp 2.1.2 adding pipelined SFTP File I/O (write_all_pipelined / read_to_writer_pipelined). Note: the serde_bytes perf fix that originally motivated this fork is now upstreamed in russh-sftp 2.1.2; only the pipelined helpers remain as fork value-add."
5+
description = "Temporary fork of russh-sftp 2.3.0 adding pipelined SFTP File I/O (write_all_pipelined / read_to_writer_pipelined). These helpers hide per-request RTT for fast bulk transfers and are the only value-add over upstream russh-sftp."
66
documentation = "https://docs.rs/bssh-russh-sftp"
77
edition = "2021"
88
homepage = "https://github.com/lablup/bssh"
@@ -11,23 +11,26 @@ license = "Apache-2.0"
1111
readme = "README.md"
1212
repository = "https://github.com/lablup/bssh"
1313

14+
# Dependency versions mirror upstream russh-sftp 2.3.0 (AspectUnk/russh-sftp).
15+
# Update via ./sync-upstream.sh; the only fork addition is the `futures` dep,
16+
# needed by the forward-ported pipelined helpers in src/client/fs/file.rs.
1417
[dependencies]
15-
tokio = { version = "1", default-features = false, features = [
18+
tokio = { version = "1.52.3", default-features = false, features = [
1619
"io-util",
1720
"rt",
1821
"sync",
1922
"time",
2023
"macros",
2124
] }
22-
tokio-util = "0.7.18"
25+
tokio-util = { version = "0.7.18", default-features = false, features = ["rt"] }
2326
serde = { version = "1.0.228", features = ["derive"] }
24-
serde_bytes = "0.11"
27+
serde_bytes = "0.11.19"
2528
bitflags = { version = "2.11.1", features = ["serde"] }
2629
async-trait = { version = "0.1.89", optional = true }
2730

2831
# futures: required by our forward-ported pipelined helpers
2932
# (write_all_pipelined / read_to_writer_pipelined use FuturesUnordered).
30-
# Upstream russh-sftp 2.1.2 does not need this dependency.
33+
# Upstream russh-sftp only needs this as a dev-dependency.
3134
futures = { version = "0.3.32", default-features = false, features = ["std", "async-await"] }
3235

3336
thiserror = "2.0.18"
@@ -36,5 +39,9 @@ bytes = "1.11.1"
3639
log = "0.4.29"
3740
dashmap = "6.1.0"
3841

42+
[target.'cfg(target_arch = "wasm32")'.dependencies]
43+
wasm-bindgen-futures = "0.4.71"
44+
gloo-timers = { version = "0.4.0", features = ["futures"] }
45+
3946
[features]
4047
async-trait = ["dep:async-trait"]

crates/bssh-russh-sftp/README.md

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,35 @@
11
# bssh-russh-sftp
22

3-
Temporary fork of [russh-sftp](https://crates.io/crates/russh-sftp) with a `serde_bytes` performance fix for SFTP `Write` and `Data` packets.
3+
**Temporary fork of [russh-sftp](https://crates.io/crates/russh-sftp) (tracking upstream `2.3.0`) adding pipelined SFTP file I/O.**
44

5-
This crate exists so bssh can ship the packet serialization fix independently while keeping the public crate name usable through Cargo's `package = "bssh-russh-sftp"` dependency alias.
5+
This crate exists so bssh can ship faster bulk SFTP transfers independently, while keeping the public crate name usable through Cargo's `package = "bssh-russh-sftp"` dependency alias.
66

7-
## The Problem
7+
## The Value-Add
88

9-
`russh-sftp` 2.1.1 derives serde for `Vec<u8>` fields in `SSH_FXP_WRITE` and `SSH_FXP_DATA`. With the crate's custom deserializer, that routes through `deserialize_seq` and reads payload bytes one at a time. Large transfers spend substantial CPU in serde's generic `VecVisitor` path.
9+
The fork adds two helpers to `client::fs::File` that keep many SFTP requests in flight at once, hiding per-request round-trip latency (mirroring how OpenSSH's `sftp` client keeps ~64 requests outstanding):
1010

11-
## The Fix
11+
- `File::write_all_pipelined(reader, max_inflight)` — streams a reader to the remote file with up to `max_inflight` concurrent `SSH_FXP_WRITE`s.
12+
- `File::read_to_writer_pipelined(writer, max_inflight)` — streams the remote file to a writer with up to `max_inflight` concurrent `SSH_FXP_READ`s, reassembling chunks in offset order so the output matches a sequential read.
1213

13-
The fork annotates the binary payload fields with `#[serde(with = "serde_bytes")]` and implements compatible `serialize_bytes` framing in the SFTP serializer. The wire format remains `u32 length + bytes`, but deserialization uses the existing bulk byte-buffer path.
14+
These are the only additions over upstream. They live in `src/client/fs/file.rs` and are re-applied on each sync from `patches/pipelined-file-io.patch`.
15+
16+
> The `serde_bytes` packet-serialization performance fix that originally motivated this fork was upstreamed in russh-sftp 2.1.2; it is kept for reference under `patches/historical/`.
17+
18+
## Usage
19+
20+
```toml
21+
[dependencies]
22+
russh-sftp = { package = "bssh-russh-sftp", version = "2.3.0" }
23+
```
1424

1525
## Sync with Upstream
1626

1727
```bash
1828
cd crates/bssh-russh-sftp
19-
./sync-upstream.sh 2.1.1
29+
./sync-upstream.sh 2.3.0 # omit the version to use upstream's default branch
2030
```
2131

22-
Local changes are kept as patch files under `patches/`.
32+
`sync-upstream.sh` copies upstream `src` verbatim and re-applies every patch directly under `patches/` (anything under `patches/historical/` is excluded). Patches already merged upstream are detected via reverse-apply and skipped.
2333

2434
## License
2535

crates/bssh-russh-sftp/create-patch.sh

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,66 @@
11
#!/bin/bash
22
# create-patch.sh
3-
# Creates a patch file from the current bssh-russh-sftp changes compared to upstream russh-sftp.
3+
# Regenerates patches/pipelined-file-io.patch by diffing the current vendored
4+
# source against a fresh checkout of upstream russh-sftp.
45
#
5-
# Usage: ./create-patch.sh
6+
# Self-contained: clones upstream into a temp dir (no manually-maintained
7+
# references/ directory needed), so it always diffs against the exact version.
8+
#
9+
# Usage: ./create-patch.sh [version]
10+
# version: optional, e.g. "2.3.0" (default: upstream's default branch, since
11+
# russh-sftp does not publish git tags)
612

713
set -e
814

915
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
10-
BSSH_ROOT="$SCRIPT_DIR/../.."
11-
UPSTREAM_DIR="$BSSH_ROOT/references/russh-sftp/src"
12-
CURRENT_DIR="$SCRIPT_DIR/src"
16+
UPSTREAM_URL="https://github.com/AspectUnk/russh-sftp.git"
17+
TEMP_DIR="/tmp/russh-sftp-createpatch-$$"
1318
PATCH_DIR="$SCRIPT_DIR/patches"
14-
PATCH_FILE="$PATCH_DIR/sftp-serde-bytes-perf.patch"
19+
PATCH_FILE="$PATCH_DIR/pipelined-file-io.patch"
1520

1621
GREEN='\033[0;32m'
1722
YELLOW='\033[1;33m'
1823
NC='\033[0m'
19-
2024
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
2125
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
2226

23-
if [ ! -d "$UPSTREAM_DIR" ]; then
24-
echo "Error: Upstream russh-sftp not found at $UPSTREAM_DIR"
25-
echo "Please ensure references/russh-sftp exists with the upstream source."
26-
exit 1
27+
cleanup() { [ -d "$TEMP_DIR" ] && rm -rf "$TEMP_DIR"; }
28+
trap cleanup EXIT
29+
30+
VERSION="${1:-}"
31+
32+
log_info "Cloning upstream russh-sftp..."
33+
git clone --quiet "$UPSTREAM_URL" "$TEMP_DIR"
34+
cd "$TEMP_DIR"
35+
36+
if [ -z "$VERSION" ]; then
37+
VERSION=$(git describe --tags --abbrev=0 2>/dev/null || echo "master")
38+
fi
39+
if [ "$VERSION" != "master" ]; then
40+
# russh-sftp publishes no git tags, so a version string may not be a ref.
41+
if ! { git checkout --quiet "v$VERSION" 2>/dev/null || git checkout --quiet "$VERSION" 2>/dev/null; }; then
42+
log_warn "No git ref '$VERSION' (russh-sftp publishes no tags); diffing against the default branch."
43+
VERSION="master"
44+
fi
2745
fi
46+
log_info "Diffing against upstream $VERSION ($(git rev-parse --short HEAD))"
2847

48+
UPSTREAM_SRC="$TEMP_DIR/src"
49+
CURRENT_SRC="$SCRIPT_DIR/src"
2950
mkdir -p "$PATCH_DIR"
3051

31-
log_info "Creating patch from differences..."
32-
33-
/usr/bin/diff -urN "$UPSTREAM_DIR" "$CURRENT_DIR" \
34-
| sed "s|$UPSTREAM_DIR|a/src|g" \
35-
| sed "s|$CURRENT_DIR|b/src|g" \
52+
# The only fork change is the pipelined File I/O in client/fs/file.rs
53+
# (write_all_pipelined / read_to_writer_pipelined). Emit a -p1 patch.
54+
diff -u \
55+
--label a/src/client/fs/file.rs \
56+
--label b/src/client/fs/file.rs \
57+
"$UPSTREAM_SRC/client/fs/file.rs" \
58+
"$CURRENT_SRC/client/fs/file.rs" \
3659
> "$PATCH_FILE" || true
3760

3861
if [ -s "$PATCH_FILE" ]; then
3962
LINES=$(wc -l < "$PATCH_FILE" | tr -d ' ')
4063
log_info "Patch created: $PATCH_FILE ($LINES lines)"
41-
4264
echo ""
4365
echo "Patch summary:"
4466
echo "=============="

0 commit comments

Comments
 (0)