Files
chanora/docs/designs/file-transfer-protocol-design.md
T
Edison Jwa fe3da41e41 docs: add TSDNS/file transfer designs, review p256 PR draft (TODO-057,066,067)
TSDNS: verified fully implemented in chanora_resolver (port 41144,
magic bytes, TCP query). File transfer: verified fully implemented
for avatars/icons (ftinitdownload, TCP data, cacache). p256 PR draft
reviewed and corrected (function path, probability figure).
2026-06-11 21:44:31 +09:00

170 lines
5.8 KiB
Markdown

# 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_<hex>` where `<hex>` 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_<id>` where `<id>` 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_<hex>`)
- [x] Icon download (`/icon_<crc32>`)
- [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:** `<cache_dir>/chanora/blobs/` managed by `cacache`
**Key mapping:**
| Protocol key | cacache key | Example |
|---|---|---|
| Avatar MD5 | `av_<md5hex>` | `av_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6` |
| Icon CRC32 | `ic_<crc32u>` | `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)