You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
* fix(network): improve error handling in ProtocolMessage parsing and TcpListener binding
refactor(storage): simplify filename sanitization and directory creation logic
* docs(README): update CLI example to recommend a minimal block size of 2048 bytes
* docs(architecture): add flow diagrams for FerrisShare protocol (v1 and v2)
* docs(README): clarify purpose of README and link to architecture documentation
We recommend using a minimal block size of 2048 bytes (as shown in the example above). Larger blocks reduce protocol overhead and typically improve throughput for local transfers. Be aware larger blocks use more memory and may be less forgiving on very unreliable networks — adjust down if you see timeouts or memory pressure.
43
+
41
44
Logs printed to both terminals show the protocol exchange (HELLO, OK, YEET blocks, OK-HOUSTEN responses, MISSION-ACCOMPLISHED, SUCCESS, BYE-RIS).
@@ -16,38 +16,50 @@ The primary goals of this project are:
16
16
4.**Reliability**: Implement a simple protocol with handshake verification to ensure successful transfers
17
17
5.**Simplicity**: Provide a straightforward CLI interface similar to common networking tools
18
18
19
-
### Non-Goals
20
-
21
-
-**Discovery Protocol**: The sender must know the receiver's IP address (no automatic peer discovery)
22
-
-**Encryption**: File transfers are not encrypted (local network trust assumed)
23
19
-**Resume Support**: Interrupted transfers cannot be resumed
24
20
-**Multi-file Transfers**: Each transfer handles exactly one file
25
21
26
-
## **Choice of Dependencies**
22
+
### 1.1**Choice of Dependencies**
27
23
28
-
### Tokyo
24
+
This project uses a small set of well-established crates chosen to support an async, networked CLI tool implemented in Rust. Below are the main dependencies and why they were selected.
29
25
30
-
The project uses **Tokio**, an asynchronous runtime for Rust, to manage networking operations and concurrency. Tokio provides powerful primitives such as `TcpStream`, `TcpListener`, and asynchronous task spawning (`tokio::spawn`), allowing efficient, non-blocking I/O.
26
+
#### Tokio
31
27
32
-
This choice is motivated by several reasons:
28
+
Tokio is the async runtime and is central to the project. Reasons for using Tokio include:
33
29
34
-
1.**Asynchronous I/O efficiency** – Tokio leverages Rust’s `async/await` syntax to handle thousands of simultaneous client connections without blocking threads.
30
+
1.**Asynchronous I/O efficiency** – Tokio leverages Rust’s `async/await` syntax to handle many simultaneous client connections without blocking threads.
35
31
2.**Task scheduling and runtime** – Tokio includes a lightweight task scheduler that runs asynchronous functions concurrently on a single or multi-threaded runtime.
36
-
3.**Ecosystem integration** – Many crates (like `warp`, `hyper`, `reqwest`, `tokio-tungstenite`) are built on top of Tokio, ensuring good compatibility and extensibility.
37
-
4.**Fine-grained control** – Tokio allows precise management of I/O events, making it suitable for custom protocol implementations, chunked file transfer, and streaming optimizations.
38
-
5.**Performance and safety** – The runtime is highly optimized for low-latency operations, while maintaining Rust’s guarantees of memory safety and thread safety.
32
+
3.**Ecosystem integration** – Many crates (like `warp`, `hyper`, `reqwest`, `tokio-tungstenite`) are built on top of Tokio, ensuring compatibility and extensibility.
33
+
4.**Performance and safety** – The runtime is optimized for low-latency operations while preserving Rust’s memory- and thread-safety guarantees.
34
+
35
+
Practical notes for this repo:
36
+
37
+
- Tokio primitives used: `TcpListener`, `TcpStream`, `tokio::spawn`, `tokio::fs`, and `tokio::sync::mpsc`.
38
+
- The code creates an `mpsc::channel::<TcpStream>(1)` in `src/main.rs` and sends accepted `TcpStream`s from the listener to the handler task. This decouples socket acceptance from protocol handling, provides backpressure (buffer size 1), and keeps a clear service boundary between network IO and command processing.
39
+
- When changing concurrency or channel buffer sizes, review the places that consume the channel (network handler) and tests that rely on the current backpressure semantics.
40
+
41
+
#### clap
42
+
43
+
`clap` (with the `derive` feature) is used for command-line parsing. It provides ergonomic derive-based parsing for flags and subcommands used by the binaries (see `src/cli/main.rs`). Use `clap` to add user-facing options, help text, and subcommands. Keep CLI changes backward-compatible where possible.
44
+
45
+
#### dotenv
39
46
40
-
Without Tokio, the implementation would require manually managing threads and blocking I/O, which would be less efficient, harder to scale, and more error-prone.
47
+
`dotenv` is used in `src/main.rs` to load local environment variables from a `.env` file during development. The project uses environment variables for configuration keys (see `src/application/config.rs`): `FERRIS_BASE_PATH`, `FERRIS_PORT`, and `FERRIS_HOST`. `Config::from_env()` provides sensible defaults when vars are absent.
41
48
42
-
## **FerrisShare File Transfer Protocol**
49
+
Other dependencies
43
50
44
-
### **Overview**
51
+
-`async-trait` — used to express async traits for domain ports/interfaces implemented by infra repositories.
52
+
-`anyhow` — convenience error handling for higher-level paths or tooling code.
53
+
54
+
If you add dependencies, prefer small, widely-used crates and keep Tokio feature flags minimal to avoid pulling unnecessary code.
55
+
56
+
### 1.2 **Overview**
45
57
46
58
The protocol defines a simple, **text-based command layer** over TCP for transferring a single file between two peers on the same network. It relies on TCP for reliable, ordered delivery, while adding **application-level commands** to coordinate the transfer, manage file chunks, and confirm completion. The connection is **bi-directional**, allowing the receiver to respond directly through the same TCP stream.
47
59
48
-
FerrisShare uses an asynchronous channel (`mpsc::channel`) to transmit accepted TCP connections from the network listener to the handler responsible for processing protocol commands. This mechanism decouples the management of incoming connections from the business logic, ensures synchronization between asynchronous tasks, and guarantees that only one active connection is handled at a time. The channel thus facilitates internal communication and enhances the modularity of the network service.
@@ -59,9 +71,130 @@ FerrisShare uses an asynchronous channel (`mpsc::channel`) to transmit accepted
59
71
|**MISSION-ACCOMPLISHED**| Client | — |`SUCCESS` / `ERROR`| Marks the end of file transmission. The server verifies that all blocks were received correctly. |
60
72
|**BYE-RIS**| Either | — | — | Gracefully terminates or cancels the transfer. |
61
73
62
-
### **Notes**
74
+
## 2. **High-Level Architecture**
75
+
76
+
### 2.1 Overview
77
+
78
+
FerrisShare is organized following a **hexagonal (ports and adapters)** architecture.
79
+
The system is divided into three primary layers:
80
+
81
+
1.**Core Domain (`src/core/domain`)**
82
+
Defines the business logic, entities, and service traits (ports).
83
+
It is _infrastructure-agnostic_ and models how files are transferred, validated, and finalized.
84
+
85
+
2.**Application Layer (`src/application`)**
86
+
Orchestrates interactions between domain services and infrastructure.
87
+
It is responsible for:
88
+
89
+
- managing runtime state (via `FerrisShareState`),
90
+
- loading configuration from the environment (via `main.rs`),
91
+
- wiring dependencies and initializing services (via `main.rs`).
92
+
93
+
3.**Infrastructure Layer (`src/infra`)**
94
+
Provides concrete implementations of domain ports, such as:
This separation ensures that **business logic remains pure** and testable while the infrastructure can evolve independently (e.g., changing from filesystem to S3 storage would only require a new repository implementing the same trait).
99
+
100
+
---
101
+
102
+
## 3. **Runtime Model**
103
+
104
+
FerrisShare uses a bounded Tokio mpsc channel (mpsc::channel::<TcpStream>(1)) to forward accepted TcpStream connections from the listener task to the network handler. This decouples socket acceptance from protocol processing, provides backpressure (buffer size = 1) so the listener will await when the handler is busy, and enforces sequential handling of active connections. Do not change the channel semantics or buffer size without review — consumers and tests rely on the current backpressure behavior.
| Handler |`mpsc::Receiver<TcpStream>`| Sequentially handles active connections (bounded by channel size). |
120
+
| File IO |`tokio::fs`| Asynchronous file operations for write and rename. |
121
+
| CPU-bound Tasks |`tokio::task::spawn_blocking`| Used for checksum validation or heavy file operations. |
122
+
123
+
> The bounded channel (`size = 1`) acts as a **backpressure control**, ensuring the runtime does not accept more concurrent transfers than it can safely process.
124
+
125
+
---
126
+
127
+
## 5. **Storage Design**
128
+
129
+
### 5.1 Storage Repository
130
+
131
+
The **FSStorageRepository** provides a file-based implementation of the `StorageRepository` trait defined in `core/domain/storage/ports.rs`.
132
+
133
+
Responsibilities:
134
+
135
+
- Validate and sanitize filenames to prevent directory traversal.
136
+
- Create a temporary file with suffix `.ferrisshare`.
137
+
- Write incoming blocks asynchronously.
138
+
- Rename the file to its final name once all blocks are received.
139
+
140
+
Error handling is implemented using a domain-level `StorageError` enum, with variants such as:
141
+
142
+
-`InvalidPath`
143
+
-`WriteError`
144
+
-`FinalizeError`
145
+
-`ChecksumMismatch`
146
+
147
+
---
148
+
149
+
## 6. **Error Management**
150
+
151
+
The architecture distinguishes between **domain errors** and **infrastructure errors**:
Itsmodulararchitecture (domain/application/infraseparation), use of Tokio primitives, and simple custom protocol make it easy to extend while ensuring predictable runtime behavior and strong safety guarantees.
0 commit comments