# File Transfer Protocol Design **Date:** 2026-06-11 **Status:** Implemented **Location:** `core/chanora_core/src/file_transfer.rs` ## Overview TeamSpeak 3 file transfer protocol for downloading avatars and icons. Uses a two-phase approach: command phase over encrypted UDP, then raw TCP data transfer. ## Protocol Specification ### Two-Phase Transfer 1. **Command Phase** — Client sends `ftinitdownload` over main encrypted UDP connection 2. **Transfer Phase** — Client opens TCP connection to server's file transfer port (default 30033), sends `ftkey`, receives raw bytes ### Relevant Commands | Command | Direction | Purpose | |---|---|---| | `ftinitdownload` | Client → Server | Initialize download, returns `ftkey`, `port`, `size` | | `ftgetfileinfo` | Client → Server | Get file metadata | | `ftgetfilelist` | Client → Server | List files in channel repository | | `ftinitupload` | Client → Server | Initialize upload | | `ftdeletefile` | Client → Server | Delete a file | | `ftcreatedir` | Client → Server | Create directory | | `ftrenamefile` | Client → Server | Rename/move file | ### `ftinitdownload` Command ``` ftinitdownload clientftfid={id} name={path} cid={channelId} cpw={password} seekpos={seek} proto=0 ``` **Parameters:** | Parameter | Type | Description | |---|---|---| | `clientftfid` | `u16` | Client-side transfer ID | | `name` | `string` | File path (e.g., `/avatar_abcdef`) | | `cid` | `ChannelId` | Channel scope (0 = server-level) | | `cpw` | `string` | Channel password (empty for server-level) | | `seekpos` | `u64` | Resume offset (0 for fresh download) | | `proto` | `u8` | Protocol version (always 0) | **Server Response:** | Field | Type | Description | |---|---|---| | `clientftfid` | `u16` | Echo of client transfer ID | | `serverftfid` | `u16` | Server-side transfer ID | | `ftkey` | `string` | One-time transfer key (hex) | | `port` | `u16` | File transfer TCP port (usually 30033) | | `size` | `u64` | File size in bytes | | `proto` | `u8` | Protocol version echo | | `ip` | `string` (optional) | Override IP for TCP connection | ### TCP Transfer Flow 1. Open TCP connection to `server_ip:port` 2. Send `ftkey` followed by newline 3. Read exactly `size` bytes of raw file data 4. Close TCP connection ### File Paths Files are addressed by path scoped to channel ID: - `cid=0` — Server-level repository (avatars, icons live here) - `cid=N` — Channel-specific repository **Avatar path:** `/avatar_` where `` is derived from client UID: ```rust fn uid_to_avatar_path(uid_b64: &str) -> String { let decoded = BASE64_STANDARD.decode(uid_b64).unwrap_or_default(); let mut rendered = String::with_capacity(decoded.len() * 2); for byte in decoded { rendered.push((b'a' + (byte >> 4)) as char); rendered.push((b'a' + (byte & 0x0f)) as char); } rendered } ``` **Icon path:** `/icon_` where `` is unsigned CRC32 of icon bytes. ## Implementation Details ### Architecture ``` Bridge (get_avatar/get_icon) ↓ FileTransferService ↓ 1. Check BlobCache → hit? return bytes 2. Check negative cache → hit? return None 3. Check in-flight map → already downloading? await existing 4. Acquire concurrency semaphore (max 2) 5. Download via ProtocolClient 6. Store in BlobCache 7. Notify all waiters ``` ### Code Location - `core/chanora_core/src/file_transfer.rs` — `FileTransferService` (344 lines) - `crates/chanora_cache/src/lib.rs` — `BlobCache` (cacache-backed) - `crates/chanora_protocol/src/adapter.rs` — `uid_to_avatar_path()` (line 1561) ### Features Implemented - [x] `ftinitdownload` command over encrypted UDP - [x] TCP data transfer with `ftkey` authentication - [x] Avatar download (`/avatar_`) - [x] Icon download (`/icon_`) - [x] Content-addressed blob cache (cacache-backed) - [x] Request coalescing (multiple requests for same hash = 1 download) - [x] Rate limiting (max 2 concurrent downloads) - [x] Negative cache (5-minute TTL for 404s) - [x] Cache integrity verification (SSRI) - [x] Cross-server dedup (same content = same blob) ### Cache Architecture **Storage:** `/chanora/blobs/` managed by `cacache` **Key mapping:** | Protocol key | cacache key | Example | |---|---|---| | Avatar MD5 | `av_` | `av_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6` | | Icon CRC32 | `ic_` | `ic_123456789` | **Why content-addressed:** - `client_flag_avatar` = MD5 of avatar bytes (computed by uploader) - `icon_id` = CRC32 of icon bytes (computed by uploader) - Same content on any server = same hash = stored once ### Error Handling | tsclientlib error | ProtocolError | Action | |---|---|---| | Permission denied | `ServerRejected { code, message }` | Negative cache (5 min) | | File not found | `ServerRejected { code, message }` | Negative cache (5 min) | | Network/TCP failure | `Backend(String)` | Retry with backoff | | Timeout | `Timeout` | Retry with backoff | | Connection lost | `Lost(String)` | Fail all pending downloads | ## Anti-Flood Strategy - Max 2 concurrent downloads per server - 5-second pause on flood error, then resume at reduced rate - Lazy download (only when UI needs to display) - No proactive download of all avatars on connect ## Testing Unit tests in `core/chanora_core/src/file_transfer.rs`: - `returns_cached_avatar_without_connection` (line 295) - `negative_cache_short_circuits_not_connected` (line 313) - `returns_cached_icon_without_connection` (line 333) ## References - YaTQA Reference §8.3: File transfer protocol details - `docs/architecture/file-transfer-design.md` — Full design document (737 lines) - `docs/architecture/file-transfer-research.md` — Research findings (770 lines) - `docs/architecture/file-transfer-implementation-plan.md` — Implementation plan (1315 lines) - `core/chanora_core/src/file_transfer.rs` — Implementation (344 lines)