- Build and push fastsync-ci:v8 with lcov, valgrind, clang baked in - Remove all apt-get install steps from CI (v8 has them pre-installed) - Fix chunk.c use_metadata=true OOB: add remaining_size guard before metadata_from_buf reads past the buffer. The bug allowed network-facing chunk_deserialize to heap-buffer-overflow on crafted inputs. - Also guard against unsigned underflow on remaining_size - sizeof(int)
FastSync
A high-performance file synchronization system with SSH and TCP transport, TLS encryption, streaming zstd compression, multithreaded transfer, incremental sync, metadata preservation, and rsync-compatible CLI flags.
Technical Overview
- Dual transport: custom TCP client-server or SSH subprocess (rsync-style
user@host:/path) - TLS encryption: OpenSSL-based TLS 1.2+ for encrypted TCP connections
- Chunked file transfer: files grouped into configurable-size chunks (default ~10 MB)
- Streaming zstd compression (levels 1–22) using
ZSTD_compressStream2 - Multithreading: producer-consumer pipeline with thread-safe queues (scanner → loader → sender)
- Incremental sync: skip files unchanged since last transfer (compares size + mtime)
- Metadata preservation:
mode,uid,gid,mtimerestored on disk when enabled sendfile()zero-copy on TCP (~2× faster on loopback)- SSH ControlMaster for connection reuse across repeated invocations
- Bandwidth limiting: token-bucket throttling (
--bwlimit) --delete: receiver removes files not present in sender manifest--exclude/--include: glob-pattern filename filtering
System Architecture
Client
- Recursively scans source directories (BFS), supports exclude and include patterns
- Groups files into chunks (configurable size)
- Streaming zstd compression with configurable level
- Chunk serialization (compact binary format) or per-file transfer
- Incremental transfer: sends file metadata to server, skips unchanged files
- Manifests all sent paths when
--deleteis active - Sends via TCP
sendfile()or SSH pipe - Optional progress display with throughput
- Bandwidth limiting via token-bucket algorithm
Server
- TCP mode: listens on configurable port (default 8080); SSH mode: runs via
--stdio - TLS mode: wraps TCP connections with OpenSSL
- Receives and reassembles files
- Decompresses (streaming zstd), deserializes, restores metadata
- Handles incremental checks: compares size + mtime against destination files
- Processes
STATUS_MANIFESTfor--delete: walks destination tree, removes extras - Per-connection concurrency via
fork() - Thread pool for parallel processing
Protocol Details
Status Codes
| Code | Meaning |
|---|---|
STATUS_OK |
Operation successful |
STATUS_ERROR |
Error occurred |
STATUS_FINISHED |
Transfer complete |
STATUS_NEXT |
Ready for next file (per-file mode) |
STATUS_CHUNK |
Following data is a serialized chunk |
STATUS_MANIFEST |
Following data is a file manifest (for --delete) |
STATUS_CHECK |
Incremental check: client sends file path + size + mtime, server responds with OK (skip) or NEXT (send) |
Wire Format — Metadata
When use_metadata is enabled (-M), each file entry carries a 4-byte present flag followed by five fields (mode, uid, gid, mtime_sec, mtime_nsec). When disabled globally, no metadata bytes are sent — zero wire overhead.
Transfer Flow
Config → (STATUS_NEXT | STATUS_CHUNK | STATUS_CHECK)* → [STATUS_MANIFEST] → STATUS_FINISHED → STATUS_OK
Protocol Version
1.1.0 — server and client must match. Mismatch results in STATUS_ERROR.
Command-Line Arguments
Client
| Argument | Description |
|---|---|
| Positional | <source> <dest> — automatic SSH detection if dest contains : |
-c [level] |
Compression with optional level (1–22, default 5) |
-z [level] |
Alias for -c |
-a, --archive |
Archive mode: enables -c -m -M (no -s) |
-m |
Multithreading mode |
-s |
Chunk serialization (batch all files per chunk) |
-f, --sendfile |
Sendfile zero-copy. Incompatible with -c / -s. TCP only. |
-M, --preserve |
Preserve file metadata (mode, uid, gid, mtime) |
-n, --dry-run |
Scan and print what would be transferred |
-p <port> |
SSH port (default: 22) |
-v, --verbose |
Enable debug logging |
--progress |
Show real-time transfer speed |
--delete |
Delete files on receiver not present in source |
--exclude <pattern> |
Exclude files matching glob pattern (repeatable) |
--include <pattern> |
Only transfer files matching glob pattern (repeatable, whitelist) |
--max-size <n> |
Skip files larger than n bytes |
--min-size <n> |
Skip files smaller than n bytes |
--incremental |
Skip files unchanged since last transfer (size + mtime). Auto-enables --preserve. Incompatible with -s. |
--bwlimit <KB/s> |
Bandwidth limit in kilobytes per second |
--chunk-size <n> |
Chunk size in bytes (default: 10485760) |
--source-dir <path> |
Source directory (overrides FASTSYNC_SOURCE_DIR) |
--dest-dir <path> |
Server destination directory (overrides FASTSYNC_DEST_DIR) |
--save-to-disk |
Write received files to disk |
--server-host <ip> |
Server IP address (default: 127.0.0.1) |
--server-port <n> |
Server port (default: 8080) |
--tls |
Enable TLS encryption |
--cert <path> |
TLS certificate file (PEM) |
--key <path> |
TLS private key file (PEM) |
--ca <path> |
TLS CA certificate file for verification (PEM) |
Server
| Argument | Description |
|---|---|
--stdio |
Run in stdio mode (for SSH transport; single connection then exits) |
-p <port> |
TCP listen port (default: 8080, range: 1–65535) |
--tls |
Enable TLS encryption |
--cert <path> |
TLS certificate file (PEM) |
--key <path> |
TLS private key file (PEM) |
--ca <path> |
TLS CA certificate file for verification (PEM) |
-v, --verbose |
Enable debug logging |
--help |
Show help |
Environment Variables
| Variable | Default | Description |
|---|---|---|
FASTSYNC_SOURCE_DIR |
— | Source directory fallback |
FASTSYNC_DEST_DIR |
— | Destination directory fallback |
FASTSYNC_SAVE_TO_DISK |
false |
Disk persistence fallback |
Implementation Details
Data Structures
- Chunk — collection of files (~10 MB total by default)
- File — path, content (
Data), optionalFileMetadatapointer - FileMetadata —
mode,uid,gid,mtime_sec,mtime_nsec - Config — runtime parameters (transported over wire, TLS settings excluded)
- Queue — thread-safe bounded queue with condition variables
- DirectoryScanner — recursive BFS traversal with exclude and include pattern support
Key Algorithms
- File scanning — BFS directory traversal; entries matched against exclude and include patterns
- Chunking — files accumulated until
chunk_sizethreshold, then flushed - Compression — streaming zstd via
ZSTD_compressStream2/ZSTD_decompressStream - Network protocol — status-code-driven exchange with metadata packing
- Incremental check — client sends
STATUS_CHECK+ path + size + mtime; server compares against destination - Bandwidth limiting — token-bucket algorithm with
nanosleepthrottling on 64 KB write chunks - Metadata restoration —
chmod(),chown(),utimensat()on the receiving side --delete— sender tracks all sent paths; receiver walks destination tree and removes unlisted files/directories- SSH transport —
socketpair()+fork()+execvp("ssh", ...)withControlMasterand port support - TLS transport — OpenSSL
SSL_CTXwith TLS 1.2 minimum, optional CA verification, transparentSSL_read/SSL_writeviaio_set_ssl()
Build Requirements
- C11 compiler
- CMake >= 3.22
- zstd library
- OpenSSL (development headers and libraries)
- pthreads
- SSH client (for SSH transport mode only)
Installing Dependencies
Ubuntu/Debian:
sudo apt install cmake build-essential libzstd-dev libssl-dev openssh-client
Nix:
nix-shell # provides zstd, openssl, cmake, gcc
Building
cmake -B build -S . && cmake --build build -j$(nproc)
Running
Server (TCP mode)
./build/server
Server with TLS
./build/server --tls --cert server.pem --key server-key.pem
Server via SSH
Place the fastsync-server binary in the remote $PATH. The client runs ssh user@host fastsync-server --stdio automatically when an SSH-style destination is given.
Client — SSH (rsync-style)
./build/client /path/to/send user@host:/path/to/receive
Client — TCP
./build/client --source-dir /path/to/send --dest-dir /path/to/receive --save-to-disk
Client — TCP with TLS
./build/client --tls --cert client.pem --key client-key.pem --ca ca.pem \
--source-dir /path/to/send --dest-dir /path/to/receive --save-to-disk
Common Options
# Archive mode (compression + multithreading + metadata)
./build/client -a /path/to/send user@host:/path
# Dry run
./build/client -n /path/to/send /path/to/receive
# With progress and custom chunk size
./build/client --progress --chunk-size 2097152 /src user@host:/dst
# Exclude temporary files + delete extras on receiver
./build/client --exclude "*.tmp" --exclude "*.o" --delete /src user@host:/dst
# Incremental sync (skip unchanged files)
./build/client --incremental /src user@host:/dst
# Bandwidth limit to 1 MB/s
./build/client --bwlimit 1024 /src user@host:/dst
# All features
./build/client -a --progress --chunk-size 5242880 --exclude "*.log" --delete /src /dst
Testing
# Unit tests (7 suites)
./build/tests
# Integration + benchmark suite
python3 test.py
The benchmark prints throughput metrics, best configuration, and speedup vs rsync.
Performance Considerations
- Chunk size (~10 MB default) balances memory and transfer efficiency
- Compression level trades CPU for bandwidth
sendfile()bypasses userspace — ~2× faster on localhost for large files- Multithreading scales with core count
- Metadata transfer adds negligible overhead (~24 bytes per file when enabled)
- SSH socketpair buffer set to 1 MB for improved pipe throughput
- SSH ControlMaster reuses connections across repeated invocations
- Incremental sync eliminates redundant transfers entirely
- Bandwidth limiting uses token-bucket with nanosleep for accurate throttling
Benchmark Results
25 MB of mixed file sizes over localhost with disk I/O throttled (reads ≤ 15 MB/s, writes ≤ 10 MB/s) and network emulation via tc netem. Each test was run 3×; the median is reported below.
LAN (1000 Mbit, 20 ms ±1 ms, 0.1% loss)
| Configuration | Time | vs rsync (archive) | vs rsync (compress) |
|---|---|---|---|
Best: -m -c |
0.20 s | 11.2× faster | 3.6× faster |
Compression (-c) |
0.31 s | 7.3× faster | 2.3× faster |
| Standard | 1.27 s | 1.8× faster | — |
| rsync (archive) | 2.27 s | — | — |
| rsync (archive + compress) | 0.72 s | — | — |
WAN (100 Mbit, 50 ms ±10 ms, 1% loss)
| Configuration | Time | vs rsync (archive) | vs rsync (compress) |
|---|---|---|---|
Best: -m -c |
0.39 s | 44.8× faster | 3.8× faster |
Compression (-c) |
0.64 s | 27.3× faster | 2.3× faster |
| Standard | 7.12 s | 2.4× faster | — |
| rsync (archive) | 17.44 s | — | — |
| rsync (archive + compress) | 1.47 s | — | — |
Compression reduces the data on the wire enough that the transfer becomes latency-bound rather than bandwidth-bound. On WAN, the best configuration runs 10.8× faster than the theoretical limit for uncompressed data, since zstd shrinks the 25 MB payload to a fraction of its original size over the wire.