refactor: remove legacy tauri stack and update iced docs
This commit is contained in:
+7
-77
@@ -1,79 +1,9 @@
|
||||
# TeamSpeak 逆向工程分析文档
|
||||
# Docs
|
||||
|
||||
本文档包含对 TeamSpeak 3 协议和相关代码库的分析结果,用于指导后续开发工作。
|
||||
This directory now documents the current desktop client direction:
|
||||
|
||||
## 文档结构
|
||||
|
||||
- **[protocol.md](protocol.md)** - TeamSpeak 3 协议详细分析
|
||||
- **[protocol_stack.md](protocol_stack.md)** - TeamSpeak 3 协议栈深度分析(层次结构、加密机制、可靠传输等)
|
||||
- **[architecture.md](architecture.md)** - 系统架构分析 (SAD)
|
||||
- **[components.md](components.md)** - 组件设计分析 (SDD)
|
||||
- **[requirements.md](requirements.md)** - 需求分析 (SRS)
|
||||
|
||||
## 参考代码库
|
||||
|
||||
本分析基于以下参考代码库:
|
||||
|
||||
### 1. tsdeclarations
|
||||
- **位置**: `refercence/tsdeclarations/`
|
||||
- **描述**: TeamSpeak 3 协议的机器可读定义,包含消息、数据包、枚举、错误码等定义
|
||||
- **关键文件**:
|
||||
- `ts3protocol.md` - 协议详细规范
|
||||
- `Packets.txt` - 数据包结构定义
|
||||
- `Messages.toml` - 消息定义
|
||||
- `Book.toml` - 数据结构定义
|
||||
- `Enums.toml` - 枚举类型定义
|
||||
- `Errors.csv` - 错误码定义
|
||||
|
||||
### 2. tsclientlib
|
||||
- **位置**: `refercence/tsclientlib/`
|
||||
- **描述**: Rust 实现的 TeamSpeak 客户端库,提供高级 API
|
||||
- **关键组件**:
|
||||
- `tsclientlib` - 主客户端库
|
||||
- `tsproto` - 底层协议实现
|
||||
- `ts-bookkeeping` - 客户端/频道状态管理
|
||||
- `tsproto-packets` - 数据包解析
|
||||
- `tsproto-structs` - 协议结构体
|
||||
- `tsproto-types` - 基础类型定义
|
||||
|
||||
### 3. Qint
|
||||
- **位置**: `refercence/Qint/`
|
||||
- **描述**: 现代化的 TeamSpeak 客户端,基于 Tauri 框架
|
||||
- **技术栈**:
|
||||
- 后端: Rust (Tauri)
|
||||
- 前端: TypeScript/JavaScript
|
||||
- 代理层: Rust (websocket/web)
|
||||
|
||||
### 4. SimpleBot
|
||||
- **位置**: `refercence/SimpleBot/`
|
||||
- **描述**: 简单的 TeamSpeak 聊天机器人,展示 tsclientlib 的使用
|
||||
- **功能**: 连接到服务器,响应特定消息,支持脚本执行
|
||||
|
||||
### 5. ts3stats
|
||||
- **位置**: `refercence/ts3stats/`
|
||||
- **描述**: TeamSpeak 3 服务器统计工具
|
||||
- **技术栈**: Python
|
||||
- **功能**: 分析服务器日志,生成用户统计图表
|
||||
|
||||
## 协议概述
|
||||
|
||||
TeamSpeak 3 使用基于 UDP 的自定义协议,具有以下特点:
|
||||
|
||||
1. **加密**: 使用 AES-128-CTR + OMAC (EAX 模式)
|
||||
2. **压缩**: 使用 QuickLZ 算法
|
||||
3. **分片**: 支持大数据包分片传输
|
||||
4. **可靠性**: 使用选择性重传机制确保可靠传输
|
||||
5. **身份验证**: 使用 ECDH 密钥交换和 RSA 拼图防攻击
|
||||
|
||||
## 开发建议
|
||||
|
||||
基于分析结果,建议开发工作遵循以下原则:
|
||||
|
||||
1. **协议兼容性**: 严格遵循 tsdeclarations 中的协议定义
|
||||
2. **代码复用**: 优先使用 tsclientlib 作为底层库
|
||||
3. **模块化设计**: 参考 Qint 的架构,分离前端、代理和核心逻辑
|
||||
4. **测试覆盖**: 参考 SimpleBot 的测试方法,确保协议兼容性
|
||||
|
||||
## 法律声明
|
||||
|
||||
本分析仅用于学习和研究目的。TeamSpeak 是 TeamSpeak Systems GmbH 的商标。开发的客户端应遵守相关法律法规,不得用于商业用途或侵犯 TeamSpeak 的商业模式。
|
||||
- `SRS.md` - current product scope and user-facing requirements
|
||||
- `SAD.md` - current architecture (`iced` + `tsclientlib`)
|
||||
- `SDD.md` - implementation notes for the iced app
|
||||
- `protocol.md` - TeamSpeak protocol research reference
|
||||
- `protocol_stack.md` - lower-level protocol stack notes
|
||||
|
||||
+33
-453
@@ -1,472 +1,52 @@
|
||||
# Software Architecture Document (SAD)
|
||||
# ReTeamSpeak - Cross-Platform TeamSpeak Client
|
||||
# Software Architecture Document
|
||||
|
||||
**Version**: 1.0.0
|
||||
**Date**: 2026-05-12
|
||||
**Status**: Based on actual implementation
|
||||
## Overview
|
||||
|
||||
---
|
||||
The current application is a single desktop client crate built around `iced` and `tsclientlib`.
|
||||
|
||||
## 1. Architectural Overview
|
||||
## Architecture
|
||||
|
||||
### 1.1 System Context
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ User Environment │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ │
|
||||
│ │ Desktop │ │ Web │ │ Mobile │ │
|
||||
│ │ Windows │ │ Browser │ │ iOS / Android │ │
|
||||
│ │ macOS │ │ │ │ │ │
|
||||
│ │ Linux │ │ │ │ │ │
|
||||
│ └──────┬───────┘ └──────┬───────┘ └──────────┬───────────┘ │
|
||||
│ │ │ │ │
|
||||
│ └─────────────────┼──────────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌──────▼───────┐ │
|
||||
│ │ ReTeamSpeak │ │
|
||||
│ │ Client │ │
|
||||
│ └──────┬───────┘ │
|
||||
│ │ │
|
||||
│ ┌──────▼───────┐ │
|
||||
│ │ TS3 Server │ │
|
||||
│ └──────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```text
|
||||
User
|
||||
-> iced UI (`src/iced-app/src/main.rs`)
|
||||
-> tsclientlib sync connection/state
|
||||
-> optional local audio pipeline (`audio.rs`, `noise_cancel.rs`)
|
||||
-> TeamSpeak server
|
||||
```
|
||||
|
||||
### 1.2 Layered Architecture
|
||||
## Major Components
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Presentation Layer │
|
||||
│ ┌────────────────────────────────────────────────────────────┐ │
|
||||
│ │ React Frontend (TypeScript) │ │
|
||||
│ │ - Connection UI, Chat, Channel Tree, Settings │ │
|
||||
│ └────────────────────────────────────────────────────────────┘ │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ Application Layer │
|
||||
│ ┌────────────────────────────────────────────────────────────┐ │
|
||||
│ │ Tauri Shell (Rust) │ │
|
||||
│ │ - Command handlers, State management, IPC bridge │ │
|
||||
│ └────────────────────────────────────────────────────────────┘ │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ Business Logic Layer │
|
||||
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
|
||||
│ │ tscore │ │ tsaudio │ │ tsdb │ │ shared │ │
|
||||
│ │ Protocol │ │ Audio │ │ Database │ │ Types │ │
|
||||
│ └──────────┘ └──────────┘ └──────────┘ └──────────────┘ │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ Infrastructure Layer │
|
||||
│ ┌────────────────────────────────────────────────────────────┐ │
|
||||
│ │ - Tokio (Async Runtime) │ │
|
||||
│ │ - rusqlite (SQLite) │ │
|
||||
│ │ - cpal (Audio I/O) │ │
|
||||
│ │ - AES/EAX, SHA, ECDH (Crypto) │ │
|
||||
│ └────────────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
### UI Layer
|
||||
|
||||
---
|
||||
- `main.rs`
|
||||
- Handles navigation, connection flow, channel/client/chat rendering, settings UI
|
||||
|
||||
## 2. Module Architecture
|
||||
### TeamSpeak Integration
|
||||
|
||||
### 2.1 Crate Dependency Graph
|
||||
- `tsclientlib`
|
||||
- Connection setup, state mirror, channel moves, text messages, client updates, voice packet send/receive
|
||||
|
||||
```
|
||||
┌─────────────────┐
|
||||
│ tauri-app │
|
||||
│ (Application) │
|
||||
└────┬───┬───┬────┘
|
||||
│ │ │
|
||||
┌────────────┘ │ └────────────┐
|
||||
▼ ▼ ▼
|
||||
┌──────────┐ ┌──────────┐ ┌──────────┐
|
||||
│ tscore │ │ tsaudio │ │ tsdb │
|
||||
│ Protocol │ │ Audio │ │ Database │
|
||||
└────┬─────┘ └────┬─────┘ └────┬─────┘
|
||||
│ │ │
|
||||
└───────────────┼──────────────────┘
|
||||
▼
|
||||
┌──────────┐
|
||||
│ shared │
|
||||
│ Types │
|
||||
└──────────┘
|
||||
```
|
||||
### Audio Layer
|
||||
|
||||
### 2.2 Module Responsibilities
|
||||
- `audio.rs`
|
||||
- `cpal` input/output, Opus encode, simple voice activation logic
|
||||
|
||||
#### `shared` - Shared Types Library
|
||||
**Path**: `src/shared/`
|
||||
**Lines**: ~700
|
||||
**Purpose**: Defines core data types shared across all modules
|
||||
### Noise Reduction
|
||||
|
||||
| File | Responsibility |
|
||||
|------|---------------|
|
||||
| `types.rs` | Core types: ClientId, ChannelId, ServerGroupId, Codec, ConnectionState, etc. |
|
||||
| `events.rs` | Event types: AppEvent, ConnectionEvent, ClientEvent, ChannelEvent, etc. |
|
||||
| `errors.rs` | Error types: AppError with variants for each subsystem |
|
||||
| `config.rs` | Configuration: ConfigManager, SavedConnection, RecentServer |
|
||||
- `noise_cancel.rs`
|
||||
- Supports `None`, `nnnoiseless`, and `sonora`
|
||||
|
||||
**Key Types**:
|
||||
```rust
|
||||
pub struct ClientId(pub u16);
|
||||
pub struct ChannelId(pub u64);
|
||||
pub struct ServerGroupId(pub u64);
|
||||
pub struct Uid(pub String);
|
||||
### Identity Import
|
||||
|
||||
pub enum ConnectionState {
|
||||
Disconnected, Connecting, IdentityLevelIncreasing,
|
||||
Connected, ChannelListFinished, DisconnectedTemporarily, Error,
|
||||
}
|
||||
- `identity.rs`
|
||||
- Reads TeamSpeak `settings.db` and imports `identity_secret_key`
|
||||
|
||||
pub enum Codec {
|
||||
SpeexNarrowband, SpeexWideband, SpeexUltrawideband,
|
||||
CeltMono, OpusVoice, OpusMusic,
|
||||
}
|
||||
```
|
||||
## Removed Architecture
|
||||
|
||||
---
|
||||
The following older architecture is no longer part of the product:
|
||||
|
||||
#### `tscore` - Protocol Core Library
|
||||
**Path**: `src/tscore/`
|
||||
**Lines**: ~1800
|
||||
**Purpose**: Implements TS3 protocol (packets, encryption, connection)
|
||||
|
||||
**Sub-modules**:
|
||||
|
||||
##### `protocol/` - Packet and Command Handling
|
||||
| File | Lines | Responsibility |
|
||||
|------|-------|---------------|
|
||||
| `packet.rs` | 607 | Packet structures (InPacket, OutPacket, Header, InitPacket) |
|
||||
| `types.rs` | 227 | Protocol types (PacketType, CodecType, GroupWhisperType) |
|
||||
| `commands.rs` | 239 | Command parsing/serialization with escape sequences |
|
||||
|
||||
**Packet Structure**:
|
||||
```rust
|
||||
pub struct Header {
|
||||
pub mac: [u8; 8], // EAX authentication tag
|
||||
pub packet_id: u16, // Packet sequence number
|
||||
pub client_id: Option<u16>, // Client ID (C2S only)
|
||||
pub flags: Flags, // Type + UE/CP/NP/FR flags
|
||||
}
|
||||
|
||||
pub struct InPacket {
|
||||
pub direction: Direction,
|
||||
pub header: Header,
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
```
|
||||
|
||||
##### `crypto/` - Encryption and Key Management
|
||||
| File | Lines | Responsibility |
|
||||
|------|-------|---------------|
|
||||
| `eax.rs` | 118 | AES-128-EAX encrypt/decrypt |
|
||||
| `keys.rs` | 229 | Key derivation, KeyCache, SharedSecret |
|
||||
| `hash.rs` | 50 | SHA-1/256/512 hash functions |
|
||||
|
||||
**Key Derivation**:
|
||||
```rust
|
||||
fn create_key_nonce(
|
||||
packet_type: PacketType,
|
||||
direction: Direction,
|
||||
generation_id: u32,
|
||||
iv: &[u8; 64],
|
||||
) -> ([u8; 16], [u8; 16]) {
|
||||
// SHA-256(direction | type | generation_id | iv)
|
||||
// Returns (key, nonce) for AES-EAX
|
||||
}
|
||||
```
|
||||
|
||||
##### `connection/` - Connection Management
|
||||
| File | Lines | Responsibility |
|
||||
|------|-------|---------------|
|
||||
| `client.rs` | 519 | Client connection with full handshake implementation |
|
||||
| `state.rs` | 90 | Connection state machine |
|
||||
| `resend.rs` | 240 | Packet retransmission with RTT estimation |
|
||||
|
||||
**Handshake Flow**:
|
||||
```rust
|
||||
impl Client {
|
||||
pub fn start_handshake(&mut self) -> Result<Vec<u8>, ProtocolError>;
|
||||
pub fn handle_data(&mut self, data: &[u8]) -> Result<Vec<Vec<u8>>, ProtocolError>;
|
||||
fn build_init2(&mut self) -> Result<Vec<u8>, ProtocolError>;
|
||||
fn build_init4(&mut self) -> Result<Vec<u8>, ProtocolError>;
|
||||
fn handle_initivexpand(&mut self, cmd: &Command) -> Result<Vec<u8>, ProtocolError>;
|
||||
fn handle_initivexpand2(&mut self, cmd: &Command) -> Result<Vec<u8>, ProtocolError>;
|
||||
fn solve_rsa_puzzle(x: &[u8; 64], n: &[u8; 64], level: u32) -> [u8; 64];
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### `tsaudio` - Audio Engine
|
||||
**Path**: `src/tsaudio/`
|
||||
**Lines**: ~280
|
||||
**Purpose**: Audio capture, playback, and codec operations
|
||||
|
||||
| File | Lines | Responsibility |
|
||||
|------|-------|---------------|
|
||||
| `lib.rs` | 80 | Core types (AudioConfig, AudioFrame, AudioError) |
|
||||
| `capture.rs` | 30 | Audio capture (cpal-based, optional) |
|
||||
| `playback.rs` | 30 | Audio playback (cpal-based, optional) |
|
||||
| `codec.rs` | 55 | Opus encoder/decoder (optional) |
|
||||
| `vad.rs` | 45 | Voice Activity Detection |
|
||||
| `buffer.rs` | 65 | Jitter buffer for smooth playback |
|
||||
|
||||
**Audio Frame**:
|
||||
```rust
|
||||
pub struct AudioFrame {
|
||||
pub sample_rate: u32, // 48000 Hz
|
||||
pub channels: u16, // 1 (mono) or 2 (stereo)
|
||||
pub samples: Vec<f32>, // PCM samples
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### `tsdb` - Database Layer
|
||||
**Path**: `src/tsdb/`
|
||||
**Lines**: ~400
|
||||
**Purpose**: SQLite database for persistent storage
|
||||
|
||||
| File | Lines | Responsibility |
|
||||
|------|-------|---------------|
|
||||
| `lib.rs` | 104 | Database initialization and table creation |
|
||||
| `identity.rs` | 115 | Identity CRUD operations |
|
||||
| `bookmark.rs` | 176 | Bookmark CRUD operations |
|
||||
| `message.rs` | 139 | Message storage and retrieval |
|
||||
| `config.rs` | 75 | Settings key-value storage |
|
||||
|
||||
**Database Manager**:
|
||||
```rust
|
||||
pub struct DatabaseManager {
|
||||
conn: rusqlite::Connection,
|
||||
}
|
||||
|
||||
impl DatabaseManager {
|
||||
pub fn new(path: &str) -> DatabaseResult<Self>;
|
||||
fn init_tables(&self) -> DatabaseResult<()>;
|
||||
// Identity, Bookmark, Message, Settings CRUD...
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### `tauri-app` - Application Shell
|
||||
**Path**: `src/tauri-app/`
|
||||
**Lines**: ~500
|
||||
|
||||
##### `src-tauri/` - Rust Backend
|
||||
| File | Lines | Responsibility |
|
||||
|------|-------|---------------|
|
||||
| `lib.rs` | 60 | Tauri setup, plugin registration, state init |
|
||||
| `main.rs` | 5 | Entry point |
|
||||
| `commands.rs` | 155 | Tauri command handlers (IPC bridge) |
|
||||
| `state.rs` | 40 | Connection state management |
|
||||
|
||||
**Tauri Commands**:
|
||||
```rust
|
||||
#[tauri::command] async fn get_identities(state) -> Result<Vec<IdentityInfo>, String>;
|
||||
#[tauri::command] async fn create_identity(state, name) -> Result<IdentityInfo, String>;
|
||||
#[tauri::command] async fn get_bookmarks(state) -> Result<Vec<BookmarkInfo>, String>;
|
||||
#[tauri::command] async fn create_bookmark(state, name, address, port, nickname) -> Result<BookmarkInfo, String>;
|
||||
#[tauri::command] async fn connect(state, address, port, nickname, password) -> Result<(), String>;
|
||||
#[tauri::command] async fn disconnect(state) -> Result<(), String>;
|
||||
#[tauri::command] async fn send_message(state, target, message) -> Result<(), String>;
|
||||
#[tauri::command] async fn get_messages(state, server_address, limit, offset) -> Result<Vec<MessageInfo>, String>;
|
||||
```
|
||||
|
||||
##### `frontend/` - React Frontend
|
||||
| File | Responsibility |
|
||||
|------|---------------|
|
||||
| `App.tsx` | Main application component |
|
||||
| `main.tsx` | Entry point |
|
||||
| `styles.css` | Application styles |
|
||||
|
||||
---
|
||||
|
||||
## 3. Data Flow Architecture
|
||||
|
||||
### 3.1 Connection Establishment
|
||||
|
||||
```
|
||||
User Frontend Tauri Shell tscore
|
||||
│ │ │ │
|
||||
│── Connect(addr) ───────>│ │ │
|
||||
│ │── connect() ────────>│ │
|
||||
│ │ │── Client::new() ─>│
|
||||
│ │ │ │
|
||||
│ │ │<── start_handshake│
|
||||
│ │ │ (Init0) │
|
||||
│ │ │ │
|
||||
│ │ │──── UDP Send ────>│
|
||||
│ │ │ │
|
||||
│ │ │<── handle_data ───│
|
||||
│ │ │ (Init1) │
|
||||
│ │ │ │
|
||||
│ │ │ ... (Init2-4) ... │
|
||||
│ │ │ │
|
||||
│ │ │<── Connected ─────│
|
||||
│ │<── Connected ────────│ │
|
||||
│<── Connected ───────────│ │ │
|
||||
```
|
||||
|
||||
### 3.2 Voice Data Flow
|
||||
|
||||
```
|
||||
Microphone ──> cpal capture ──> VAD ──> Opus encode ──> Voice packet
|
||||
│
|
||||
▼
|
||||
UDP send
|
||||
│
|
||||
▼
|
||||
Speaker <── cpal playback <── Jitter buffer <── Opus decode <── Voice packet
|
||||
```
|
||||
|
||||
### 3.3 Message Flow
|
||||
|
||||
```
|
||||
User input ──> Frontend ──> Tauri command ──> tscore
|
||||
│
|
||||
▼
|
||||
Command serialization
|
||||
│
|
||||
▼
|
||||
Encryption (AES-EAX)
|
||||
│
|
||||
▼
|
||||
UDP send
|
||||
│
|
||||
▼
|
||||
Server ──> UDP recv ──> Decrypt ──> Parse ──> Event ──> Frontend
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Cross-Cutting Concerns
|
||||
|
||||
### 4.1 Error Handling
|
||||
```rust
|
||||
// Protocol errors
|
||||
pub enum ProtocolError {
|
||||
PacketParse(String), Encryption(String), Decryption(String),
|
||||
Compression(String), Decompression(String), InvalidPacketType(u8),
|
||||
PacketTooLarge { size, max }, PacketTooSmall { size, min },
|
||||
MacVerificationFailed, Timeout(String), ConnectionClosed,
|
||||
Command(String), Network(std::io::Error),
|
||||
}
|
||||
|
||||
// Application errors
|
||||
pub enum AppError {
|
||||
Connection(String), Protocol { code, message }, Network(std::io::Error),
|
||||
Crypto(String), Audio(String), Database(String), Serialization(serde_json::Error),
|
||||
Config(String), Identity(String), Permission(String), Timeout(String),
|
||||
NotConnected, AlreadyConnected, InvalidArgument(String),
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 Logging
|
||||
- Framework: `tracing` with `tracing-subscriber`
|
||||
- Levels: ERROR, WARN, INFO, DEBUG, TRACE
|
||||
- Environment filter: `RUST_LOG=tscore=debug,tsaudio=debug`
|
||||
|
||||
### 4.3 Configuration
|
||||
- Format: TOML
|
||||
- Location: Platform-specific app data directory
|
||||
- Encryption: ChaCha20-Poly1305 for identity keys
|
||||
|
||||
---
|
||||
|
||||
## 5. Deployment Architecture
|
||||
|
||||
### 5.1 Desktop (Windows/macOS/Linux)
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ Tauri Application │
|
||||
│ ┌───────────────────────────────┐ │
|
||||
│ │ WebView (System) │ │
|
||||
│ │ React Frontend (dist/) │ │
|
||||
│ └───────────────────────────────┘ │
|
||||
│ ┌───────────────────────────────┐ │
|
||||
│ │ Rust Backend (lib) │ │
|
||||
│ │ tscore + tsaudio + tsdb │ │
|
||||
│ └───────────────────────────────┘ │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 5.2 Mobile (iOS/Android)
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ Tauri Mobile App │
|
||||
│ ┌───────────────────────────────┐ │
|
||||
│ │ WebView (Platform) │ │
|
||||
│ │ React Frontend (dist/) │ │
|
||||
│ └───────────────────────────────┘ │
|
||||
│ ┌───────────────────────────────┐ │
|
||||
│ │ Rust Backend (cdylib) │ │
|
||||
│ │ + Platform audio (Oboe) │ │
|
||||
│ └───────────────────────────────┘ │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Performance Characteristics
|
||||
|
||||
### 6.1 Measured Performance
|
||||
- **Connection time**: ~200ms (local network)
|
||||
- **Packet encryption**: ~10μs per packet
|
||||
- **RSA puzzle (level 8)**: ~100ms
|
||||
- **Memory usage**: ~50MB (idle)
|
||||
|
||||
### 6.2 Scalability
|
||||
- **Max packet size**: 500 bytes
|
||||
- **Max decompressed size**: 2MB
|
||||
- **Fragment queue limit**: 200 packets
|
||||
- **Resend timeout**: 500ms initial, exponential backoff
|
||||
|
||||
---
|
||||
|
||||
## 7. Security Architecture
|
||||
|
||||
### 7.1 Encryption Layers
|
||||
1. **Transport**: AES-128-EAX per packet
|
||||
2. **Key Exchange**: ECDH (P-256 for identity, Curve25519 for session)
|
||||
3. **Storage**: ChaCha20-Poly1305 for identity keys
|
||||
4. **Passwords**: base64(sha1(password))
|
||||
|
||||
### 7.2 Anti-DoS
|
||||
- RSA puzzle computation (configurable difficulty)
|
||||
- Hash Cash for identity verification
|
||||
- Rate limiting (planned)
|
||||
|
||||
---
|
||||
|
||||
## 8. Build and Test
|
||||
|
||||
### 8.1 Build System
|
||||
- **Rust**: Cargo workspace
|
||||
- **Frontend**: npm + Vite
|
||||
- **Desktop**: Tauri CLI
|
||||
- **CI/CD**: GitHub Actions
|
||||
|
||||
### 8.2 Test Coverage
|
||||
```
|
||||
Module Tests Status
|
||||
────────────────────────────────
|
||||
shared 0 -
|
||||
tscore 32 ✓ All passing
|
||||
tsaudio 0 -
|
||||
tsdb 0 -
|
||||
────────────────────────────────
|
||||
Total 32 ✓
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. References
|
||||
|
||||
- TS3 Protocol Paper: `refercence/tsdeclarations/ts3protocol.md`
|
||||
- tsclientlib: `refercence/tsclientlib/` (reference implementation)
|
||||
- Qint: `refercence/Qint/` (reference UI)
|
||||
- Tauri v2: https://tauri.app
|
||||
- Tauri application shell
|
||||
- Custom `tscore` protocol implementation
|
||||
- Custom `tsaudio` audio engine
|
||||
- Custom `tsdb` persistence layer
|
||||
- Shared internal model crate used only by the removed stack
|
||||
|
||||
+33
-718
@@ -1,729 +1,44 @@
|
||||
# Software Design Document (SDD)
|
||||
# ReTeamSpeak - Cross-Platform TeamSpeak Client
|
||||
# Software Design Document
|
||||
|
||||
**Version**: 1.0.0
|
||||
**Date**: 2026-05-12
|
||||
**Status**: Based on actual implementation
|
||||
## iced-app
|
||||
|
||||
---
|
||||
### `main.rs`
|
||||
|
||||
## 1. Detailed Design
|
||||
- Defines the full app state and message enum
|
||||
- Uses `SyncConnection` / `SyncConnectionHandle` from `tsclientlib`
|
||||
- Maintains local UI mirrors for:
|
||||
- bookmarks
|
||||
- server metadata
|
||||
- channels
|
||||
- clients
|
||||
- chat messages
|
||||
- device selection
|
||||
- mute / AFK state
|
||||
|
||||
### 1.1 `shared` Module Design
|
||||
### `audio.rs`
|
||||
|
||||
#### 1.1.1 Core Type System
|
||||
- Output playback through `cpal`
|
||||
- Input capture through `cpal`
|
||||
- Opus encode for outgoing voice
|
||||
- Simple VAD for continuous talk mode
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Type Hierarchy │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Identifier Types (Newtype Pattern) │
|
||||
│ ├── ClientId(u16) // In-session client ID │
|
||||
│ ├── ChannelId(u64) // Channel identifier │
|
||||
│ ├── ServerGroupId(u64) // Server group │
|
||||
│ ├── ChannelGroupId(u64) // Channel group │
|
||||
│ ├── ClientDbId(u64) // Database client ID │
|
||||
│ ├── Uid(String) // Unique identity (base64) │
|
||||
│ ├── PermissionId(u32) // Permission ID │
|
||||
│ └── IconId(i32) // Icon identifier │
|
||||
│ │
|
||||
│ Enumerations │
|
||||
│ ├── Codec { SpeexNB, SpeexWB, SpeexUWB, Celt, OpusVoice, │
|
||||
│ │ OpusMusic } │
|
||||
│ ├── ChannelType { Permanent, SemiPermanent, Temporary } │
|
||||
│ ├── ClientType { Normal, Query { admin } } │
|
||||
│ ├── ConnectionState { Disconnected, Connecting, ... } │
|
||||
│ ├── Reason { None, Moved, LostConnection, KickChannel, ... }│
|
||||
│ ├── CodecEncryptionMode { PerChannel, ForcedOff, ForcedOn }│
|
||||
│ ├── HostMessageMode { None, Log, Modal, Modalquit } │
|
||||
│ ├── GroupType { Template, Regular, Query } │
|
||||
│ └── GroupNamingMode { None, Before, After } │
|
||||
│ │
|
||||
│ Data Structures │
|
||||
│ ├── ServerInfo { id, name, platform, version, max_clients, │
|
||||
│ │ clients_online, ... } │
|
||||
│ ├── ChannelInfo { id, parent_id, name, codec, max_clients, │
|
||||
│ │ channel_type, ... } │
|
||||
│ ├── ClientInfo { id, channel_id, uid, name, muted, ... } │
|
||||
│ ├── ChatMessage { id, timestamp, invoker, target, message }│
|
||||
│ └── AppConfig { nickname, audio, hotkeys, theme, ... } │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
### `noise_cancel.rs`
|
||||
|
||||
#### 1.1.2 Event System Design
|
||||
- Runtime-selected noise cancellation backend
|
||||
- Current backends:
|
||||
- `None`
|
||||
- `nnnoiseless`
|
||||
- `sonora`
|
||||
|
||||
```
|
||||
AppEvent
|
||||
├── Connection(ConnectionEvent)
|
||||
│ ├── Connecting { address }
|
||||
│ ├── Connected { server, own_client }
|
||||
│ ├── StateChanged { state }
|
||||
│ ├── DisconnectedTemporarily { reason }
|
||||
│ ├── Disconnected { reason }
|
||||
│ └── ConnectionFailed { error }
|
||||
│
|
||||
├── Client(ClientEvent)
|
||||
│ ├── EnteredView { client, reason }
|
||||
│ ├── LeftView { client_id, reason, reason_message }
|
||||
│ ├── Updated { client_id, changes: ClientChanges }
|
||||
│ ├── Moved { client_id, from_channel, to_channel, reason }
|
||||
│ ├── StartedTalking { client_id }
|
||||
│ ├── StoppedTalking { client_id }
|
||||
│ ├── ServerGroupChanged { client_id, group_id, added }
|
||||
│ └── ChannelGroupChanged { client_id, group_id }
|
||||
│
|
||||
├── Channel(ChannelEvent)
|
||||
│ ├── Created { channel }
|
||||
│ ├── Deleted { channel_id }
|
||||
│ ├── Updated { channel_id, changes: ChannelChanges }
|
||||
│ ├── Moved { channel_id, new_parent, new_order }
|
||||
│ ├── PasswordChanged { channel_id }
|
||||
│ ├── DescriptionChanged { channel_id }
|
||||
│ └── Subscribed { channel_id, subscribed }
|
||||
│
|
||||
├── Server(ServerEvent)
|
||||
│ ├── Updated { changes: ServerChanges }
|
||||
│ ├── ServerGroupList { groups }
|
||||
│ └── ChannelGroupList { groups }
|
||||
│
|
||||
├── Message(MessageEvent)
|
||||
│ ├── Received { message }
|
||||
│ ├── Sent { message }
|
||||
│ ├── Read { message_id }
|
||||
│ └── UnreadCountChanged { count }
|
||||
│
|
||||
├── Audio(AudioEvent)
|
||||
│ ├── InputDeviceChanged { device }
|
||||
│ ├── OutputDeviceChanged { device }
|
||||
│ ├── InputVolumeChanged { volume }
|
||||
│ ├── OutputVolumeChanged { volume }
|
||||
│ ├── InputMutedChanged { muted }
|
||||
│ ├── OutputMutedChanged { muted }
|
||||
│ ├── DeviceList { input_devices, output_devices }
|
||||
│ ├── InputLevel { level }
|
||||
│ └── OutputLevel { level }
|
||||
│
|
||||
├── FileTransfer(FileTransferEvent)
|
||||
│ ├── Started { transfer_id, file_name, file_size, is_upload }
|
||||
│ ├── Progress { transfer_id, progress }
|
||||
│ ├── Completed { transfer_id }
|
||||
│ ├── Failed { transfer_id, error }
|
||||
│ └── Cancelled { transfer_id }
|
||||
│
|
||||
└── Error(ErrorEvent)
|
||||
├── Protocol { code, message }
|
||||
├── Network { message }
|
||||
├── Audio { message }
|
||||
├── Database { message }
|
||||
└── Other { message }
|
||||
```
|
||||
### `identity.rs`
|
||||
|
||||
---
|
||||
- Locates TeamSpeak config database in common Linux paths
|
||||
- Extracts `identity_secret_key`
|
||||
- Parses it into `tsclientlib::Identity`
|
||||
|
||||
### 1.2 `tscore` Module Design
|
||||
## Known Design Limitations
|
||||
|
||||
#### 1.2.1 Packet Processing Pipeline
|
||||
|
||||
```
|
||||
SEND RECEIVE
|
||||
┌──────────────────────────────┐ ┌──────────────────────────────┐
|
||||
│ Command String │ │ UDP Packet │
|
||||
│ │ │ │ │ │
|
||||
│ ▼ │ │ ▼ │
|
||||
│ Command::serialize() │ │ InPacket::parse() │
|
||||
│ │ │ │ │ │
|
||||
│ ▼ │ │ ▼ │
|
||||
│ QuickLZ compress │ │ AES-EAX decrypt │
|
||||
│ (if Command/CommandLow) │ │ (or fake decrypt) │
|
||||
│ │ │ │ │ │
|
||||
│ ▼ │ │ ▼ │
|
||||
│ Fragment (if > 500 bytes) │ │ QuickLZ decompress │
|
||||
│ │ │ │ (if COMPRESSED flag) │
|
||||
│ ▼ │ │ │ │
|
||||
│ AES-EAX encrypt │ │ ▼ │
|
||||
│ (or fake encrypt) │ │ Defragment │
|
||||
│ │ │ │ (if FRAGMENTED flag) │
|
||||
│ ▼ │ │ │ │
|
||||
│ Assign Packet ID │ │ ▼ │
|
||||
│ │ │ │ Command::parse() │
|
||||
│ ▼ │ │ │ │
|
||||
│ UDP Send │ │ ▼ │
|
||||
└──────────────────────────────┘ │ Application Layer │
|
||||
└──────────────────────────────┘
|
||||
```
|
||||
|
||||
#### 1.2.2 Encryption Key Derivation
|
||||
|
||||
```
|
||||
Input: packet_type, direction, generation_id, shared_iv[64]
|
||||
│
|
||||
▼
|
||||
┌───────────────────────────────────────────────────────┐
|
||||
│ temp[0] = direction_byte (0x30=S2C, 0x31=C2S) │
|
||||
│ temp[1] = packet_type.u8() │
|
||||
│ temp[2..6] = generation_id.to_be_bytes() │
|
||||
│ temp[6..70] = shared_iv[0..64] │
|
||||
│ │
|
||||
│ key_nonce = SHA-256(temp) │
|
||||
│ key = key_nonce[0..16] │
|
||||
│ nonce = key_nonce[16..32] │
|
||||
│ │
|
||||
│ key[0] ^= (packet_id >> 8) as u8 │
|
||||
│ key[1] ^= (packet_id & 0xFF) as u8 │
|
||||
└───────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
Output: key[16], nonce[16] → AES-128-EAX
|
||||
```
|
||||
|
||||
#### 1.2.3 Connection State Machine
|
||||
|
||||
```
|
||||
┌──────────────┐
|
||||
│ Disconnected │
|
||||
└──────┬───────┘
|
||||
│ start_handshake()
|
||||
▼
|
||||
┌──────────────┐
|
||||
┌──────│ Connecting │◄─────────────────┐
|
||||
│ └──────┬───────┘ │
|
||||
│ │ Init1 received │
|
||||
│ ▼ │
|
||||
│ ┌──────────────────────┐ │
|
||||
│ │ IdentityLevelIncreasing│ │
|
||||
│ └──────┬───────────────┘ │
|
||||
│ │ Init3 received │
|
||||
│ ▼ │
|
||||
│ ┌──────────────┐ │
|
||||
│ │ Connected │──────────────────┤
|
||||
│ └──────┬───────┘ │
|
||||
│ │ channellistfinished │
|
||||
│ ▼ │
|
||||
│ ┌─────────────────────┐ │
|
||||
│ │ ChannelListFinished │ │
|
||||
│ └──────┬──────────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌──────────────────────────┐ │
|
||||
└─────>│ DisconnectedTemporarily │──────┘
|
||||
└──────┬───────────────────┘
|
||||
│ timeout / manual
|
||||
▼
|
||||
┌──────────────┐
|
||||
│ Error │
|
||||
└──────┬───────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────┐
|
||||
│ Disconnected │
|
||||
└──────────────┘
|
||||
```
|
||||
|
||||
#### 1.2.4 RSA Puzzle Solver
|
||||
|
||||
```rust
|
||||
/// Solves y = x^(2^level) mod n
|
||||
///
|
||||
/// Algorithm:
|
||||
/// y = x
|
||||
/// for i in 0..level:
|
||||
/// y = (y * y) mod n
|
||||
///
|
||||
/// Time complexity: O(level * M(n)) where M(n) is multiplication cost
|
||||
/// Space complexity: O(n) for big integer storage
|
||||
fn solve_rsa_puzzle(x: &[u8; 64], n: &[u8; 64], level: u32) -> [u8; 64] {
|
||||
let x_big = BigUint::from_bytes_be(x);
|
||||
let n_big = BigUint::from_bytes_be(n);
|
||||
let mut y = x_big;
|
||||
for _ in 0..level {
|
||||
y = (y.clone() * y) % &n_big;
|
||||
}
|
||||
// Convert back to 64-byte array (big-endian, zero-padded)
|
||||
}
|
||||
```
|
||||
|
||||
#### 1.2.5 Retransmission System
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ ResendManager │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ pending: BTreeMap<PacketId, SentPacket> │
|
||||
│ max_retries: u32 (default: 10) │
|
||||
│ connection_timeout: Duration (default: 30s) │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ add_sent(id, data) → Add to pending queue │
|
||||
│ ack(id) → bool → Remove from pending │
|
||||
│ get_retransmissions() → Vec<(id, data)> to resend │
|
||||
│ is_connection_timeout()→ Check for dead connection │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ SentPacket: │
|
||||
│ data: Vec<u8> │
|
||||
│ sent_at: Instant │
|
||||
│ retry_count: u32 │
|
||||
│ timeout: Duration (starts at 500ms, doubles) │
|
||||
│ │
|
||||
│ RttEstimator: │
|
||||
│ srtt: Duration (smoothed RTT) │
|
||||
│ rtt_var: Duration (RTT variance) │
|
||||
│ rto: Duration (retransmission timeout) │
|
||||
│ update(measured_rtt) → recalculate SRTT, RTO │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 1.3 `tsaudio` Module Design
|
||||
|
||||
#### 1.3.1 Audio Pipeline
|
||||
|
||||
```
|
||||
CAPTURE PIPELINE:
|
||||
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
|
||||
│ cpal │───>│ VAD │───>│ Opus │───>│ Packet │
|
||||
│ capture │ │ detect │ │ encode │ │ output │
|
||||
└──────────┘ └──────────┘ └──────────┘ └──────────┘
|
||||
|
||||
PLAYBACK PIPELINE:
|
||||
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
|
||||
│ Packet │───>│ Jitter │───>│ Opus │───>│ cpal │
|
||||
│ input │ │ buffer │ │ decode │ │ playback │
|
||||
└──────────┘ └──────────┘ └──────────┘ └──────────┘
|
||||
```
|
||||
|
||||
#### 1.3.2 Voice Activity Detection
|
||||
|
||||
```rust
|
||||
pub struct VadDetector {
|
||||
threshold: f32,
|
||||
state: VadState,
|
||||
}
|
||||
|
||||
impl VadDetector {
|
||||
pub fn detect(&mut self, samples: &[f32]) -> VadState {
|
||||
let energy = samples.iter().map(|s| s * s).sum::<f32>()
|
||||
/ samples.len() as f32;
|
||||
if energy > self.threshold {
|
||||
self.state = VadState::Speaking;
|
||||
} else {
|
||||
self.state = VadState::Silent;
|
||||
}
|
||||
self.state
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 1.3.3 Jitter Buffer
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ JitterBuffer │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ buffer: Vec<Option<AudioFrame>> (ring buffer) │
|
||||
│ head: usize │
|
||||
│ tail: usize │
|
||||
│ size: usize │
|
||||
│ capacity: usize │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ push(frame) → Result<()> // Add frame │
|
||||
│ pop() → Option<AudioFrame> // Get next frame │
|
||||
│ len() → usize // Current buffer size │
|
||||
│ is_empty() → bool │
|
||||
│ is_full() → bool │
|
||||
│ clear() // Reset buffer │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 1.4 `tsdb` Module Design
|
||||
|
||||
#### 1.4.1 Database Schema
|
||||
|
||||
```sql
|
||||
-- Identity storage
|
||||
CREATE TABLE identities (
|
||||
id TEXT PRIMARY KEY, -- UUID
|
||||
name TEXT NOT NULL, -- Display name
|
||||
private_key TEXT NOT NULL, -- Base64 encoded ECC private key
|
||||
counter INTEGER DEFAULT 0, -- Hash Cash counter
|
||||
max_counter INTEGER DEFAULT 0, -- Maximum counter tried
|
||||
created_at TEXT NOT NULL, -- ISO 8601 timestamp
|
||||
updated_at TEXT NOT NULL -- ISO 8601 timestamp
|
||||
);
|
||||
|
||||
-- Server bookmarks
|
||||
CREATE TABLE bookmarks (
|
||||
id TEXT PRIMARY KEY, -- UUID
|
||||
name TEXT NOT NULL, -- Display name
|
||||
address TEXT NOT NULL, -- Server address
|
||||
port INTEGER DEFAULT 9987, -- Server port
|
||||
nickname TEXT, -- Preferred nickname
|
||||
server_password TEXT, -- Encrypted server password
|
||||
channel TEXT, -- Default channel
|
||||
channel_password TEXT, -- Encrypted channel password
|
||||
default_token TEXT, -- Permission token
|
||||
auto_connect INTEGER DEFAULT 0,-- Auto-connect on startup
|
||||
last_connected TEXT, -- Last connection timestamp
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
-- Message history
|
||||
CREATE TABLE messages (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
server_address TEXT NOT NULL, -- Server address
|
||||
invoker_id INTEGER NOT NULL, -- Client ID
|
||||
invoker_name TEXT NOT NULL, -- Display name
|
||||
invoker_uid TEXT NOT NULL, -- Unique ID
|
||||
target_type TEXT NOT NULL, -- "server", "channel", "client"
|
||||
target_id INTEGER, -- Target ID
|
||||
message TEXT NOT NULL, -- Message content
|
||||
is_read INTEGER DEFAULT 0, -- Read status
|
||||
timestamp TEXT NOT NULL -- ISO 8601 timestamp
|
||||
);
|
||||
|
||||
-- Key-value settings
|
||||
CREATE TABLE settings (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
```
|
||||
|
||||
#### 1.4.2 CRUD Operations
|
||||
|
||||
```rust
|
||||
impl DatabaseManager {
|
||||
// Identity operations
|
||||
fn create_identity(&self, name, private_key) -> DatabaseResult<Identity>;
|
||||
fn get_identity(&self, id) -> DatabaseResult<Identity>;
|
||||
fn get_all_identities(&self) -> DatabaseResult<Vec<Identity>>;
|
||||
fn update_identity(&self, id, name?, counter?) -> DatabaseResult<()>;
|
||||
fn delete_identity(&self, id) -> DatabaseResult<()>;
|
||||
|
||||
// Bookmark operations
|
||||
fn create_bookmark(&self, name, address, port, nickname?) -> DatabaseResult<Bookmark>;
|
||||
fn get_bookmark(&self, id) -> DatabaseResult<Bookmark>;
|
||||
fn get_all_bookmarks(&self) -> DatabaseResult<Vec<Bookmark>>;
|
||||
fn update_bookmark(&self, id, name?, address?, port?, nickname?) -> DatabaseResult<()>;
|
||||
fn delete_bookmark(&self, id) -> DatabaseResult<()>;
|
||||
|
||||
// Message operations
|
||||
fn create_message(&self, server_address, invoker_id, invoker_name,
|
||||
invoker_uid, target_type, target_id?, message) -> DatabaseResult<Message>;
|
||||
fn get_server_messages(&self, server_address, limit, offset) -> DatabaseResult<Vec<Message>>;
|
||||
fn mark_message_read(&self, id) -> DatabaseResult<()>;
|
||||
|
||||
// Settings operations
|
||||
fn get_setting(&self, key) -> DatabaseResult<Option<String>>;
|
||||
fn set_setting(&self, key, value) -> DatabaseResult<()>;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 1.5 `tauri-app` Design
|
||||
|
||||
#### 1.5.1 Tauri Command Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Frontend (React) │
|
||||
│ ┌───────────────────────────────────────────────────────┐ │
|
||||
│ │ invoke("get_bookmarks") → Promise<BookmarkInfo[]> │ │
|
||||
│ │ invoke("connect", {addr, port, nick, pass}) │ │
|
||||
│ │ invoke("send_message", {target, message}) │ │
|
||||
│ └───────────────────────────────────────────────────────┘ │
|
||||
└───────────────────────────┬─────────────────────────────────┘
|
||||
│ Tauri IPC
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Tauri Shell (Rust) │
|
||||
│ ┌───────────────────────────────────────────────────────┐ │
|
||||
│ │ #[tauri::command] │ │
|
||||
│ │ async fn get_bookmarks(state: State<AppState>) │ │
|
||||
│ │ -> Result<Vec<BookmarkInfo>, String> │ │
|
||||
│ │ { │ │
|
||||
│ │ state.db.get_all_bookmarks() │ │
|
||||
│ │ .map(|b| b.into_iter().map(Into::into)) │ │
|
||||
│ │ .map_err(|e| e.to_string()) │ │
|
||||
│ │ } │ │
|
||||
│ └───────────────────────────────────────────────────────┘ │
|
||||
│ ┌───────────────────────────────────────────────────────┐ │
|
||||
│ │ AppState { │ │
|
||||
│ │ db: DatabaseManager, │ │
|
||||
│ │ connection_state: Mutex<ConnectionState>, │ │
|
||||
│ │ } │ │
|
||||
│ └───────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
#### 1.5.2 React Component Structure
|
||||
|
||||
```
|
||||
App
|
||||
├── Header
|
||||
│ ├── Logo
|
||||
│ ├── ConnectionStatus
|
||||
│ └── SettingsButton
|
||||
│
|
||||
├── Sidebar
|
||||
│ ├── BookmarkList
|
||||
│ │ └── BookmarkItem (clickable)
|
||||
│ └── RecentServers
|
||||
│
|
||||
├── MainContent
|
||||
│ ├── ConnectForm (when no connection)
|
||||
│ │ ├── AddressInput
|
||||
│ │ ├── NicknameInput
|
||||
│ │ ├── PasswordInput
|
||||
│ │ └── ConnectButton
|
||||
│ │
|
||||
│ └── ChatView (when connected)
|
||||
│ ├── ChannelTree
|
||||
│ ├── ClientList
|
||||
│ ├── MessageList
|
||||
│ └── MessageInput
|
||||
│
|
||||
└── StatusBar
|
||||
├── ConnectionInfo
|
||||
├── AudioStatus
|
||||
└── LatencyDisplay
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Algorithm Specifications
|
||||
|
||||
### 2.1 Shared Secret Computation (Old Protocol <3.1)
|
||||
|
||||
```
|
||||
Input: alpha[10], beta[10], shared_data[32]
|
||||
Output: SharedIV[64], SharedMac[8]
|
||||
|
||||
1. SharedIV[0..20] = SHA-1(shared_data)
|
||||
2. SharedIV[0..10] ^= alpha[0..10]
|
||||
3. SharedIV[10..20] ^= beta[0..10]
|
||||
4. SharedMac[0..8] = SHA-1(SharedIV)[0..8]
|
||||
```
|
||||
|
||||
### 2.2 Shared Secret Computation (New Protocol ≥3.1)
|
||||
|
||||
```
|
||||
Input: alpha[10], beta[54], shared_data[32]
|
||||
Output: SharedIV[64], SharedMac[8]
|
||||
|
||||
1. SharedIV[0..64] = SHA-512(shared_data)
|
||||
2. SharedIV[0..10] ^= alpha[0..10]
|
||||
3. SharedIV[10..64] ^= beta[0..54]
|
||||
4. SharedMac[0..8] = SHA-1(SharedIV)[0..8]
|
||||
```
|
||||
|
||||
### 2.3 Hash Cash Level Computation
|
||||
|
||||
```
|
||||
Input: omega (public key string), offset (u64)
|
||||
Output: level (u8)
|
||||
|
||||
1. data = SHA-1(omega + offset.to_string())
|
||||
2. level = 0
|
||||
3. for byte in data:
|
||||
4. if byte == 0:
|
||||
5. level += 8
|
||||
6. else:
|
||||
7. level += trailing_zeros(byte)
|
||||
8. break
|
||||
9. return level
|
||||
```
|
||||
|
||||
### 2.4 UID Computation
|
||||
|
||||
```
|
||||
Input: publicKey (ASN.1-DER encoded)
|
||||
Output: uid (base64 string)
|
||||
|
||||
1. hash = SHA-1(publicKey)
|
||||
2. uid = base64(hash)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Interface Specifications
|
||||
|
||||
### 3.1 Tauri IPC Interface
|
||||
|
||||
```typescript
|
||||
// TypeScript interface for Tauri commands
|
||||
interface ITauriCommands {
|
||||
// Identity management
|
||||
get_identities(): Promise<IdentityInfo[]>;
|
||||
create_identity(name: string): Promise<IdentityInfo>;
|
||||
delete_identity(id: string): Promise<void>;
|
||||
|
||||
// Bookmark management
|
||||
get_bookmarks(): Promise<BookmarkInfo[]>;
|
||||
create_bookmark(name: string, address: string, port: number,
|
||||
nickname?: string): Promise<BookmarkInfo>;
|
||||
delete_bookmark(id: string): Promise<void>;
|
||||
|
||||
// Connection
|
||||
connect(address: string, port: number, nickname: string,
|
||||
password?: string): Promise<void>;
|
||||
disconnect(): Promise<void>;
|
||||
|
||||
// Messaging
|
||||
send_message(target: string, message: string): Promise<void>;
|
||||
get_messages(server_address: string, limit: number,
|
||||
offset: number): Promise<MessageInfo[]>;
|
||||
}
|
||||
|
||||
interface IdentityInfo {
|
||||
id: string;
|
||||
name: string;
|
||||
counter: number;
|
||||
max_counter: number;
|
||||
}
|
||||
|
||||
interface BookmarkInfo {
|
||||
id: string;
|
||||
name: string;
|
||||
address: string;
|
||||
port: number;
|
||||
nickname: string | null;
|
||||
auto_connect: boolean;
|
||||
last_connected: string | null;
|
||||
}
|
||||
|
||||
interface MessageInfo {
|
||||
id: number;
|
||||
invoker_name: string;
|
||||
message: string;
|
||||
timestamp: string;
|
||||
is_read: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 Internal Rust Interfaces
|
||||
|
||||
```rust
|
||||
// Protocol layer
|
||||
pub trait PacketProcessor {
|
||||
fn encode(&self, packet: OutPacket) -> Result<Vec<OutUdpPacket>>;
|
||||
fn decode(&self, data: &[u8]) -> Result<InPacket>;
|
||||
}
|
||||
|
||||
// Audio layer
|
||||
pub trait AudioCapture {
|
||||
async fn start(&mut self) -> AudioResult<()>;
|
||||
async fn stop(&mut self) -> AudioResult<()>;
|
||||
async fn capture(&mut self) -> AudioResult<AudioFrame>;
|
||||
}
|
||||
|
||||
pub trait AudioPlayback {
|
||||
async fn start(&mut self) -> AudioResult<()>;
|
||||
async fn stop(&mut self) -> AudioResult<()>;
|
||||
async fn play(&mut self, frame: AudioFrame) -> AudioResult<()>;
|
||||
}
|
||||
|
||||
// Database layer
|
||||
pub trait IdentityStore {
|
||||
fn create(&self, name: &str, key: &str) -> DatabaseResult<Identity>;
|
||||
fn get(&self, id: &str) -> DatabaseResult<Identity>;
|
||||
fn list(&self) -> DatabaseResult<Vec<Identity>>;
|
||||
fn update(&self, id: &str, updates: IdentityUpdates) -> DatabaseResult<()>;
|
||||
fn delete(&self, id: &str) -> DatabaseResult<()>;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Data Dictionary
|
||||
|
||||
### 4.1 Protocol Fields
|
||||
|
||||
| Field | Type | Size | Description |
|
||||
|-------|------|------|-------------|
|
||||
| MAC | [u8; 8] | 8 bytes | EAX message authentication code |
|
||||
| PId | u16 | 2 bytes | Packet sequence ID |
|
||||
| CId | u16 | 2 bytes | Client ID (C2S only) |
|
||||
| PT | u8 | 1 byte | Packet type + flags |
|
||||
| VId | u16 | 2 bytes | Voice packet ID |
|
||||
| Codec | u8 | 1 byte | Audio codec type |
|
||||
|
||||
### 4.2 Flag Bits
|
||||
|
||||
| Bit | Name | Mask | Description |
|
||||
|-----|------|------|-------------|
|
||||
| 7 | UE | 0x80 | Unencrypted |
|
||||
| 6 | CP | 0x40 | Compressed (QuickLZ) |
|
||||
| 5 | NP | 0x20 | New protocol |
|
||||
| 4 | FR | 0x10 | Fragmented |
|
||||
| 3-0 | Type | 0x0F | Packet type (0-8) |
|
||||
|
||||
### 4.3 Error Codes
|
||||
|
||||
| Code | Name | Description |
|
||||
|------|------|-------------|
|
||||
| 0x0000 | ok | Success |
|
||||
| 0x0200 | client_invalid_id | Invalid client ID |
|
||||
| 0x0201 | client_nickname_inuse | Nickname already in use |
|
||||
| 0x0208 | client_invalid_password | Wrong password |
|
||||
| 0x0300 | channel_invalid_id | Invalid channel ID |
|
||||
| 0x0400 | server_invalid_id | Invalid server ID |
|
||||
| 0x0403 | server_maxclients_reached | Server full |
|
||||
| 0x0701 | connection_lost | Connection lost |
|
||||
|
||||
---
|
||||
|
||||
## 5. Test Design
|
||||
|
||||
### 5.1 Test Cases (32 total)
|
||||
|
||||
#### Protocol Tests (14)
|
||||
1. `test_packet_type_conversion` - PacketType enum conversion
|
||||
2. `test_flags` - Flag bit manipulation
|
||||
3. `test_header_c2s` - C2S header parsing
|
||||
4. `test_header_s2c` - S2C header parsing
|
||||
5. `test_in_packet_parse` - Input packet parsing
|
||||
6. `test_out_packet` - Output packet creation
|
||||
7. `test_command_parse` - Command string parsing
|
||||
8. `test_command_serialize` - Command serialization
|
||||
9. `test_command_builder` - CommandBuilder pattern
|
||||
10. `test_escape_sequences` - Escape/unescape
|
||||
11. `test_init_packet_parse` - Init packet parsing
|
||||
12. `test_init_packet_serialize` - Init packet serialization
|
||||
13. `test_ack_packet` - Acknowledgement packet
|
||||
14. `test_packet_type_properties` - Type property queries
|
||||
|
||||
#### Crypto Tests (12)
|
||||
1. `test_sha1` - SHA-1 hash
|
||||
2. `test_sha256` - SHA-256 hash
|
||||
3. `test_sha512` - SHA-512 hash
|
||||
4. `test_hash_password` - Password hashing
|
||||
5. `test_create_key_nonce` - Key derivation
|
||||
6. `test_create_encryption_key` - Packet-specific key
|
||||
7. `test_shared_secret_old` - Old protocol shared secret
|
||||
8. `test_shared_secret_new` - New protocol shared secret
|
||||
9. `test_key_cache` - Key caching
|
||||
10. `test_eax_encrypt_decrypt` - EAX encryption/decryption
|
||||
11. `test_fake_encrypt_decrypt` - Fake encryption
|
||||
12. `test_hash_cash_level` - Hash Cash computation
|
||||
13. `test_compute_uid` - UID computation
|
||||
|
||||
#### Connection Tests (6)
|
||||
1. `test_encode_version` - Version encoding
|
||||
2. `test_rsa_puzzle` - RSA puzzle solver
|
||||
3. `test_resend_manager` - Retransmission manager
|
||||
4. `test_rtt_estimator` - RTT estimation
|
||||
5. `test_sent_packet_retry` - Packet retry logic
|
||||
|
||||
---
|
||||
|
||||
## 6. References
|
||||
|
||||
1. TS3 Protocol Paper: `refercence/tsdeclarations/ts3protocol.md`
|
||||
2. Packet Definitions: `refercence/tsdeclarations/Packets.txt`
|
||||
3. Message Definitions: `refercence/tsdeclarations/Messages.toml`
|
||||
4. Source Code: `src/` (4780 lines, 39 Rust files)
|
||||
- `main.rs` is still monolithic and should be split later
|
||||
- Bookmarks are not persisted
|
||||
- ServerQuery is not wired to a real backend yet
|
||||
- Audio settings apply to runtime state, but broader config persistence is not implemented
|
||||
|
||||
+30
-311
@@ -1,322 +1,41 @@
|
||||
# Software Requirements Specification (SRS)
|
||||
# ReTeamSpeak - Cross-Platform TeamSpeak Client
|
||||
# Software Requirements Specification
|
||||
|
||||
**Version**: 1.0.0
|
||||
**Date**: 2026-05-12
|
||||
**Status**: Based on actual implementation
|
||||
## Product
|
||||
|
||||
---
|
||||
ReTeamSpeak is a desktop TeamSpeak 3 client for Windows, Linux, and macOS.
|
||||
|
||||
## 1. Introduction
|
||||
## Current Functional Scope
|
||||
|
||||
### 1.1 Purpose
|
||||
ReTeamSpeak is a cross-platform TeamSpeak 3 voice communication client supporting Windows, macOS, Linux, iOS, and Android. This document specifies the software requirements based on the implemented system.
|
||||
### Implemented
|
||||
|
||||
### 1.2 Scope
|
||||
The system implements:
|
||||
- TeamSpeak 3 protocol (UDP-based, encrypted voice/text communication)
|
||||
- Cross-platform UI via Tauri v2 + React
|
||||
- Audio engine with Opus codec
|
||||
- Local data storage (SQLite)
|
||||
- Identity and bookmark management
|
||||
- Connect to a TeamSpeak server with nickname and optional password
|
||||
- Observe live server, channel, and client state
|
||||
- Join channels
|
||||
- Send and receive channel text messages
|
||||
- Show connected clients and channels
|
||||
- Toggle microphone mute and sync state to TeamSpeak
|
||||
- Toggle speaker/output mute and sync state to TeamSpeak
|
||||
- Toggle output hardware state and sync state to TeamSpeak
|
||||
- Toggle AFK state and sync state to TeamSpeak
|
||||
- Select audio input and output devices
|
||||
- Support push-to-talk and continuous talk modes
|
||||
- Import an existing TeamSpeak identity from local client config
|
||||
- Select a noise-cancellation method in settings
|
||||
|
||||
### 1.3 Definitions
|
||||
| Term | Definition |
|
||||
|------|-----------|
|
||||
| TS3 | TeamSpeak 3 protocol |
|
||||
| EAX | AES-128-CTR with OMAC encryption mode |
|
||||
| ECDH | Elliptic Curve Diffie-Hellman key exchange |
|
||||
| Opus | Audio codec used for voice transmission |
|
||||
| VAD | Voice Activity Detection |
|
||||
| PTT | Push-To-Talk |
|
||||
### Partially Implemented / Stubbed
|
||||
|
||||
---
|
||||
- ServerQuery page exists, but query execution is stubbed
|
||||
- Bookmark management exists only in local in-memory UI state
|
||||
|
||||
## 2. System Requirements
|
||||
### Not In Scope Right Now
|
||||
|
||||
### 2.1 Functional Requirements
|
||||
- Tauri frontend/backend shell
|
||||
- Custom in-repo TeamSpeak protocol implementation
|
||||
- Separate in-repo database layer
|
||||
- Mobile and web targets
|
||||
|
||||
#### FR-01: Connection Management
|
||||
- **FR-01.1**: Connect to TS3 servers via UDP
|
||||
- **FR-01.2**: Support RSA puzzle handshake (DoS protection)
|
||||
- **FR-01.3**: Support ECDH key exchange (P-256 and Curve25519)
|
||||
- **FR-01.4**: Support AES-128-EAX encrypted communication
|
||||
- **FR-01.5**: Support QuickLZ packet compression
|
||||
- **FR-01.6**: Support packet fragmentation (max 500 bytes)
|
||||
- **FR-01.7**: Support selective repeat reliable delivery
|
||||
- **FR-01.8**: Support connection state machine (Disconnected → Connecting → Connected → ChannelListFinished)
|
||||
## Non-Functional Requirements
|
||||
|
||||
#### FR-02: Authentication
|
||||
- **FR-02.1**: ECC P-256 identity key generation
|
||||
- **FR-02.2**: Hash Cash level computation (anti-spam)
|
||||
- **FR-02.3**: Server password authentication (base64(sha1(password)))
|
||||
- **FR-02.4**: Channel password authentication
|
||||
- **FR-02.5**: Permission token support
|
||||
- **FR-02.6**: UID computation (base64(sha1(publicKey)))
|
||||
|
||||
#### FR-03: Voice Communication
|
||||
- **FR-03.1**: Opus codec encoding/decoding (48kHz)
|
||||
- **FR-03.2**: Voice Activity Detection (VAD)
|
||||
- **FR-03.3**: Push-To-Talk (PTT) mode
|
||||
- **FR-03.4**: Per-client volume control
|
||||
- **FR-03.5**: Whisper support (direct and group)
|
||||
- **FR-03.6**: Codec types: Speex NB/WB/UWB, CELT, Opus Voice/Music
|
||||
|
||||
#### FR-04: Text Messaging
|
||||
- **FR-04.1**: Server messages
|
||||
- **FR-04.2**: Channel messages
|
||||
- **FR-04.3**: Private messages
|
||||
- **FR-04.4**: BBCode formatting support
|
||||
- **FR-04.5**: Message history (SQLite storage)
|
||||
|
||||
#### FR-05: Channel Management
|
||||
- **FR-05.1**: Channel tree display
|
||||
- **FR-05.2**: Channel join/leave
|
||||
- **FR-05.3**: Channel creation/editing (with permissions)
|
||||
- **FR-05.4**: Channel subscription
|
||||
|
||||
#### FR-06: Client Management
|
||||
- **FR-06.1**: Online client list
|
||||
- **FR-06.2**: Client info display
|
||||
- **FR-06.3**: Server group management
|
||||
- **FR-06.4**: Channel group management
|
||||
- **FR-06.5**: Client kick/ban (with permissions)
|
||||
|
||||
#### FR-07: Data Storage
|
||||
- **FR-07.1**: Identity storage (encrypted private keys)
|
||||
- **FR-07.2**: Server bookmarks
|
||||
- **FR-07.3**: Chat message history
|
||||
- **FR-07.4**: Application settings
|
||||
|
||||
#### FR-08: File Transfer
|
||||
- **FR-08.1**: File upload to channels
|
||||
- **FR-08.2**: File download from channels
|
||||
- **FR-08.3**: File browsing
|
||||
- **FR-08.4**: Transfer progress tracking
|
||||
|
||||
### 2.2 Non-Functional Requirements
|
||||
|
||||
#### NFR-01: Performance
|
||||
- **NFR-01.1**: Connection establishment < 3 seconds
|
||||
- **NFR-01.2**: Voice latency < 200ms
|
||||
- **NFR-01.3**: Message delivery < 100ms
|
||||
- **NFR-01.4**: Support 1000+ client servers
|
||||
|
||||
#### NFR-02: Security
|
||||
- **NFR-02.1**: AES-128-EAX encryption for all commands
|
||||
- **NFR-02.2**: ECDH key exchange (forward secrecy)
|
||||
- **NFR-02.3**: RSA puzzle DoS protection
|
||||
- **NFR-02.4**: ChaCha20-Poly1305 identity storage encryption
|
||||
|
||||
#### NFR-03: Compatibility
|
||||
- **NFR-03.1**: Windows 10/11
|
||||
- **NFR-03.2**: macOS 11+
|
||||
- **NFR-03.3**: Linux (Ubuntu 20.04+, Debian 11+)
|
||||
- **NFR-03.4**: iOS 15+
|
||||
- **NFR-03.5**: Android 10+
|
||||
- **NFR-03.6**: TS3 server versions 3.0.x and 3.1.x
|
||||
|
||||
#### NFR-04: Reliability
|
||||
- **NFR-04.1**: Automatic reconnection on temporary disconnect
|
||||
- **NFR-04.2**: Packet retransmission with exponential backoff
|
||||
- **NFR-04.3**: Connection timeout detection (30 seconds)
|
||||
- **NFR-04.4**: Graceful degradation on packet loss
|
||||
|
||||
---
|
||||
|
||||
## 3. System Architecture
|
||||
|
||||
### 3.1 Module Structure
|
||||
```
|
||||
src/
|
||||
├── shared/ # Shared types (Client, Channel, Server, Events)
|
||||
├── tscore/ # Protocol core (packets, crypto, connection)
|
||||
├── tsaudio/ # Audio engine (capture, playback, codec)
|
||||
├── tsdb/ # Database (SQLite via rusqlite)
|
||||
└── tauri-app/ # Application shell
|
||||
├── src-tauri/ # Rust backend (Tauri commands)
|
||||
└── frontend/ # React frontend (TypeScript)
|
||||
```
|
||||
|
||||
### 3.2 Technology Stack
|
||||
| Layer | Technology |
|
||||
|-------|-----------|
|
||||
| Language | Rust 1.70+, TypeScript 5.x |
|
||||
| Desktop Framework | Tauri v2 |
|
||||
| Frontend | React 18, Vite 5 |
|
||||
| Async Runtime | Tokio |
|
||||
| Actor Framework | Actix |
|
||||
| Database | SQLite (rusqlite) |
|
||||
| Audio | Opus, cpal |
|
||||
| Crypto | AES-EAX, P-256, Curve25519, SHA-256/512 |
|
||||
|
||||
---
|
||||
|
||||
## 4. Protocol Specification
|
||||
|
||||
### 4.1 Packet Format
|
||||
```
|
||||
C2S: [MAC:8][PId:2][CId:2][PT:1][Data:≤487]
|
||||
S2C: [MAC:8][PId:2][PT:1][Data:≤489]
|
||||
```
|
||||
|
||||
### 4.2 Packet Types
|
||||
| Type | Value | Encrypted | Reliable | Fragmentable |
|
||||
|------|-------|-----------|----------|--------------|
|
||||
| Voice | 0x00 | Optional | No | No |
|
||||
| VoiceWhisper | 0x01 | Optional | No | No |
|
||||
| Command | 0x02 | Yes | Yes | Yes |
|
||||
| CommandLow | 0x03 | Yes | Yes | Yes |
|
||||
| Ping | 0x04 | No | No | No |
|
||||
| Pong | 0x05 | No | No | No |
|
||||
| Ack | 0x06 | Yes | Yes | No |
|
||||
| AckLow | 0x07 | Yes | Yes | No |
|
||||
| Init | 0x08 | No | Yes | No |
|
||||
|
||||
### 4.3 Handshake Sequence
|
||||
```
|
||||
Client → Server: Init0 (version, timestamp, random0)
|
||||
Server → Client: Init1 (random1, random0_r)
|
||||
Client → Server: Init2 (version, random1, random0_r)
|
||||
Server → Client: Init3 (x, n, level, random2) [RSA puzzle]
|
||||
Client → Server: Init4 (x, n, level, random2, y, clientinitiv)
|
||||
Server → Client: initivexpand2 (beta, omega, proof, license)
|
||||
Client → Server: clientek (ek, proof)
|
||||
Server → Client: initserver
|
||||
Server → Client: channellist...channellistfinished
|
||||
Server → Client: notifycliententerview...
|
||||
```
|
||||
|
||||
### 4.4 Encryption
|
||||
- **Algorithm**: AES-128-EAX (AES-128-CTR + OMAC)
|
||||
- **Key derivation**: SHA-256(direction | type | generation_id | shared_iv)
|
||||
- **Shared IV**: SHA-512(ECDH shared secret) XOR alpha/beta
|
||||
- **MAC**: 8 bytes (EAX tag)
|
||||
|
||||
---
|
||||
|
||||
## 5. Data Structures
|
||||
|
||||
### 5.1 Core Types (from shared/src/types.rs)
|
||||
```rust
|
||||
ClientId(u16) // Client identifier
|
||||
ChannelId(u64) // Channel identifier
|
||||
ServerGroupId(u64) // Server group identifier
|
||||
ClientDbId(u64) // Client database identifier
|
||||
Uid(String) // Unique identifier (base64)
|
||||
PermissionId(u32) // Permission identifier
|
||||
```
|
||||
|
||||
### 5.2 Database Schema (from tsdb/src/lib.rs)
|
||||
```sql
|
||||
CREATE TABLE identities (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
private_key TEXT NOT NULL,
|
||||
counter INTEGER DEFAULT 0,
|
||||
max_counter INTEGER DEFAULT 0,
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE bookmarks (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
address TEXT NOT NULL,
|
||||
port INTEGER DEFAULT 9987,
|
||||
nickname TEXT,
|
||||
server_password TEXT,
|
||||
channel TEXT,
|
||||
channel_password TEXT,
|
||||
auto_connect INTEGER DEFAULT 0,
|
||||
last_connected TEXT,
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE messages (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
server_address TEXT NOT NULL,
|
||||
invoker_id INTEGER NOT NULL,
|
||||
invoker_name TEXT NOT NULL,
|
||||
invoker_uid TEXT NOT NULL,
|
||||
target_type TEXT NOT NULL,
|
||||
target_id INTEGER,
|
||||
message TEXT NOT NULL,
|
||||
is_read INTEGER DEFAULT 0,
|
||||
timestamp TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE settings (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Test Results
|
||||
|
||||
### 6.1 Unit Tests (32 tests passing)
|
||||
```
|
||||
tscore::protocol::tests - 14 tests
|
||||
✓ test_packet_type_conversion
|
||||
✓ test_flags
|
||||
✓ test_header_c2s
|
||||
✓ test_header_s2c
|
||||
✓ test_in_packet_parse
|
||||
✓ test_out_packet
|
||||
✓ test_command_parse
|
||||
✓ test_command_serialize
|
||||
✓ test_command_builder
|
||||
✓ test_escape_sequences
|
||||
✓ test_init_packet_parse
|
||||
✓ test_init_packet_serialize
|
||||
✓ test_ack_packet
|
||||
✓ test_packet_type_properties
|
||||
|
||||
tscore::crypto::tests - 12 tests
|
||||
✓ test_sha1, test_sha256, test_sha512
|
||||
✓ test_hash_password
|
||||
✓ test_create_key_nonce
|
||||
✓ test_create_encryption_key
|
||||
✓ test_shared_secret_old, test_shared_secret_new
|
||||
✓ test_key_cache
|
||||
✓ test_eax_encrypt_decrypt
|
||||
✓ test_fake_encrypt_decrypt
|
||||
✓ test_hash_cash_level
|
||||
✓ test_compute_uid
|
||||
|
||||
tscore::connection::tests - 6 tests
|
||||
✓ test_encode_version
|
||||
✓ test_rsa_puzzle
|
||||
✓ test_resend_manager
|
||||
✓ test_rtt_estimator
|
||||
✓ test_sent_packet_retry
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Constraints
|
||||
|
||||
### 7.1 Technical Constraints
|
||||
- Rust edition 2021
|
||||
- Tauri v2 for desktop/mobile
|
||||
- Must maintain TS3 protocol compatibility
|
||||
- UDP transport only (no TCP fallback)
|
||||
|
||||
### 7.2 Legal Constraints
|
||||
- TeamSpeak is a trademark of TeamSpeak Systems GmbH
|
||||
- Implementation is for educational/research purposes
|
||||
- No server-side code (client-only)
|
||||
|
||||
---
|
||||
|
||||
## 8. References
|
||||
|
||||
1. TS3 Protocol Paper (`refercence/tsdeclarations/ts3protocol.md`)
|
||||
2. Packet Definitions (`refercence/tsdeclarations/Packets.txt`)
|
||||
3. Message Definitions (`refercence/tsdeclarations/Messages.toml`)
|
||||
4. tsclientlib implementation (`refercence/tsclientlib/`)
|
||||
5. Qint implementation (`refercence/Qint/`)
|
||||
- Use `tsclientlib` for TeamSpeak compatibility instead of custom protocol code
|
||||
- Keep the shipped desktop app codebase small and maintainable
|
||||
- Prefer Podman-based reproducible builds when host system packages are missing
|
||||
|
||||
+7
-398
@@ -1,401 +1,10 @@
|
||||
# 系统架构设计文档 (SAD)
|
||||
# Architecture Notes
|
||||
|
||||
## 1. 概述
|
||||
The authoritative architecture is now documented in `SAD.md`.
|
||||
|
||||
本文档描述 TeamSpeak 3 客户端系统的整体架构设计,基于对参考代码库(tsclientlib、Qint、SimpleBot、ts3stats)的分析结果。
|
||||
Short version:
|
||||
|
||||
## 2. 系统架构图
|
||||
|
||||
```
|
||||
+-----------------------------------------------------------------------+
|
||||
| 前端层 (Frontend) |
|
||||
| |
|
||||
| +-----------+ +----------+ +--------+ +------+ +-----+ +-----+ |
|
||||
| | 连接对话框 | | 聊天视图 | | 频道树 | | 面板 | |文件 | |插件 | |
|
||||
| +-----------+ +----------+ +--------+ +------+ +-----+ +-----+ |
|
||||
| |
|
||||
| +---------------------------+ +--------------------------+ |
|
||||
| | 连接状态管理 | | 数据状态镜像 | |
|
||||
| +---------------------------+ +--------------------------+ |
|
||||
| |
|
||||
| +------------------------------------------------------------------+ |
|
||||
| | 后端抽象层 (Backend Abstraction) | |
|
||||
| | IBackend <--- TauriBackend / BrowserBackend ---> | |
|
||||
| +------------------------------------------------------------------+ |
|
||||
+-----------------------------------------------------------------------+
|
||||
| Tauri: IPC (invoke + event) | Browser: WebSocket + HTTP
|
||||
v v
|
||||
+-----------------------------------------------------------------------+
|
||||
| 壳层 (Shell Layer) |
|
||||
| |
|
||||
| 桌面壳 (Tauri) Web壳 (Actix-web) |
|
||||
| +---------------------+ +------------------------+ |
|
||||
| | 命令处理器 | | REST/WebSocket 端点 | |
|
||||
| | 窗口桥接器 | | GraphQL 端点 | |
|
||||
| +---------------------+ +------------------------+ |
|
||||
| | 核心协调器 | | WebSocket 处理器 | |
|
||||
| | 文件传输管理器 | +------------------------+ |
|
||||
| +---------------------+ |
|
||||
+-----------------------------------------------------------------------+
|
||||
| |
|
||||
| 共享依赖 |
|
||||
v v
|
||||
+-----------------------------------------------------------------------+
|
||||
| 代理库 (Proxy Library) - 核心业务逻辑 |
|
||||
| |
|
||||
| +---------------------+ +--------------------+ +----------------+ |
|
||||
| | 全局状态管理 | | 连接管理器 | | 数据库管理器 | |
|
||||
| | (QintState) | | (QintConnection) | | (DbHandler) | |
|
||||
| +---------------------+ +--------------------+ +----------------+ |
|
||||
| +--------------------+ +----------------+ |
|
||||
| | 音频管道 | | 工具组件 | |
|
||||
| | AudioToTs | | 文件缓存 | |
|
||||
| | TsToAudio | | 链接预览 | |
|
||||
| +--------------------+ | 全文搜索 | |
|
||||
| | 热键管理 | |
|
||||
| | 身份加密 | |
|
||||
| +----------------+ |
|
||||
+-----------------------------------------------------------------------+
|
||||
|
|
||||
| 依赖
|
||||
v
|
||||
+-----------------------------------------------------------------------+
|
||||
| 代码生成层 (proxy-codegen) |
|
||||
| |
|
||||
| build.rs 读取 tsproto-structs -> 生成: |
|
||||
| - Rust: JsEvent, JsProperty, JsM2B, convert_event() 等 |
|
||||
| - TypeScript: book_events.ts (PropertyId, PropertyValue, OChange) |
|
||||
+-----------------------------------------------------------------------+
|
||||
|
|
||||
v
|
||||
+-----------------------------------------------------------------------+
|
||||
| TeamSpeak 协议库 (tsclientlib / tsproto) |
|
||||
| - TeamSpeak 协议实现 |
|
||||
| - 连接管理、状态维护、音频编解码 |
|
||||
| - 事件系统(属性变更、消息、音频) |
|
||||
+-----------------------------------------------------------------------+
|
||||
```
|
||||
|
||||
## 3. 分层架构
|
||||
|
||||
### 3.1 前端层 (Frontend Layer)
|
||||
|
||||
**职责**: 用户界面展示和用户交互处理
|
||||
|
||||
**技术栈**: Svelte 5 + TypeScript + Rsbuild
|
||||
|
||||
**主要组件**:
|
||||
- **连接对话框**: 服务器连接配置界面
|
||||
- **聊天视图**: 文本消息收发界面
|
||||
- **频道树**: 频道和客户端树状视图
|
||||
- **面板**: 设置、文件浏览器等侧边面板
|
||||
- **插件系统**: 动态加载的插件模块
|
||||
|
||||
**关键特性**:
|
||||
- 响应式状态管理 (Svelte stores)
|
||||
- 跨平台兼容 (Tauri/Web)
|
||||
- 实时事件更新
|
||||
|
||||
### 3.2 壳层 (Shell Layer)
|
||||
|
||||
**职责**: 平台特定的传输适配和系统集成
|
||||
|
||||
**两种实现**:
|
||||
|
||||
#### 3.2.1 桌面壳 (Tauri Desktop)
|
||||
- **技术栈**: Rust + Tauri v2
|
||||
- **职责**:
|
||||
- 原生窗口管理
|
||||
- 系统托盘集成
|
||||
- 文件对话框
|
||||
- 全局热键
|
||||
- IPC 通信 (invoke + event)
|
||||
|
||||
#### 3.2.2 Web 壳 (Actix-web Server)
|
||||
- **技术栈**: Rust + Actix-web
|
||||
- **职责**:
|
||||
- HTTP REST 端点
|
||||
- WebSocket 通信
|
||||
- GraphQL 查询端点
|
||||
- 静态文件服务
|
||||
|
||||
### 3.3 代理层 (Proxy Layer)
|
||||
|
||||
**职责**: 核心业务逻辑,平台无关
|
||||
|
||||
**技术栈**: Rust + Actix actor 模型
|
||||
|
||||
**主要组件**:
|
||||
|
||||
#### 3.3.1 QintState (全局状态管理)
|
||||
- 连接映射管理
|
||||
- 音频数据管理
|
||||
- 热键配置
|
||||
- 设置管理
|
||||
- 文件缓存
|
||||
- 链接预览
|
||||
- 全文搜索索引
|
||||
|
||||
#### 3.3.2 QintConnection (连接管理器)
|
||||
- TeamSpeak 服务器连接生命周期
|
||||
- 事件处理和分发
|
||||
- 消息路由
|
||||
- 音频路由
|
||||
- 文件传输管理
|
||||
|
||||
#### 3.3.3 DbHandler (数据库管理器)
|
||||
- SQLite/Diesel ORM
|
||||
- 身份管理
|
||||
- 服务器/书签管理
|
||||
- 聊天消息存储
|
||||
- GraphQL 查询支持
|
||||
|
||||
#### 3.3.4 音频管道
|
||||
- **AudioToTs**: 麦克风采集 → Opus 编码 → 发送到服务器
|
||||
- **TsToAudio**: 接收服务器音频 → Opus 解码 → 混音 → 播放
|
||||
- 支持 SDL2 (桌面) 和 Oboe (Android) 后端
|
||||
|
||||
### 3.4 代码生成层 (Code Generation Layer)
|
||||
|
||||
**职责**: 从协议定义自动生成类型安全的代码
|
||||
|
||||
**输入**: tsproto-structs 的 TOML/CSV 声明文件
|
||||
|
||||
**输出**:
|
||||
- Rust 类型 (JsEvent, JsProperty, JsM2B 等)
|
||||
- TypeScript 类型 (book_events.ts)
|
||||
- 事件转换函数
|
||||
- 消息序列化/反序列化代码
|
||||
|
||||
### 3.5 协议库层 (Protocol Library Layer)
|
||||
|
||||
**职责**: TeamSpeak 3 协议的底层实现
|
||||
|
||||
**主要组件**:
|
||||
|
||||
#### 3.5.1 tsclientlib (高层客户端库)
|
||||
- 连接配置和构建
|
||||
- 状态同步
|
||||
- 音频处理
|
||||
- 地址解析 (IP/DNS SRV/TSDNS)
|
||||
|
||||
#### 3.5.2 tsproto (协议引擎)
|
||||
- UDP 数据包处理
|
||||
- 加密/解密 (AES-128-EAX)
|
||||
- 压缩/解压 (QuickLZ)
|
||||
- 可靠传输 (CUBIC 拥塞控制)
|
||||
- 连接握手
|
||||
|
||||
#### 3.5.3 ts-bookkeeping (状态管理)
|
||||
- 服务器状态维护
|
||||
- 客户端/频道/组数据
|
||||
- 事件生成
|
||||
|
||||
#### 3.5.4 tsproto-packets (包解析)
|
||||
- 数据包格式定义
|
||||
- 命令解析器
|
||||
- 零拷贝解析
|
||||
|
||||
## 4. 设计模式
|
||||
|
||||
### 4.1 Actor 模型
|
||||
- 使用 Actix 框架实现并发隔离
|
||||
- 每个 TeamSpeak 连接是独立的 Actor
|
||||
- 通过消息传递进行通信
|
||||
|
||||
### 4.2 桥接模式
|
||||
- `AppToFrontendBridge` trait 解耦核心逻辑和传输层
|
||||
- 两种实现: WindowBridge (Tauri) 和 WsBridge (Web)
|
||||
|
||||
### 4.3 策略模式
|
||||
- 前端的 `IBackend` 接口
|
||||
- 两种实现: TauriBackend 和 BrowserBackend
|
||||
|
||||
### 4.4 代码生成模式
|
||||
- 从声明式 TOML/CSV 生成重复代码
|
||||
- 确保类型安全和一致性
|
||||
|
||||
### 4.5 状态机模式
|
||||
- 连接状态管理 (Uninitialized → Connecting → Connected → Disconnected)
|
||||
|
||||
### 4.6 观察者模式
|
||||
- Svelte stores 实现响应式状态传播
|
||||
- 事件系统实现组件间通信
|
||||
|
||||
## 5. 数据流
|
||||
|
||||
### 5.1 入站数据流 (服务器 → 应用)
|
||||
|
||||
```
|
||||
UDP Socket
|
||||
↓
|
||||
Connection::poll_incoming_udp_packet()
|
||||
↓
|
||||
PacketCodec::handle_udp_packet()
|
||||
├── 解密 (AES-128-EAX)
|
||||
├── 重组分片
|
||||
├── 解压 (QuickLZ)
|
||||
└── 生成 StreamItem
|
||||
├── Command → InCommandBuf
|
||||
├── Audio → InAudioBuf
|
||||
└── Ack → 更新发送队列
|
||||
↓
|
||||
Client::handle_command()
|
||||
↓
|
||||
Connection (tsclientlib)::poll_next()
|
||||
├── 解析为 InMessage
|
||||
├── 应用到 data::Connection 状态
|
||||
├── 生成 events::Event
|
||||
└── 返回 StreamItem::BookEvents
|
||||
```
|
||||
|
||||
### 5.2 出站数据流 (应用 → 服务器)
|
||||
|
||||
```
|
||||
应用层调用
|
||||
↓
|
||||
OutCommandExt::send_with_result()
|
||||
↓
|
||||
Connection::send_command_with_result()
|
||||
├── 添加 return_code
|
||||
├── 更新本地状态
|
||||
↓
|
||||
client::Client::send_packet()
|
||||
↓
|
||||
PacketCodec::encode_packet()
|
||||
├── 压缩 + 分片 (QuickLZ)
|
||||
├── 加密 (AES-128-EAX)
|
||||
└── 分配 packet_id
|
||||
↓
|
||||
Resender::send_packet()
|
||||
├── 加入发送队列
|
||||
└── CUBIC 拥塞控制
|
||||
↓
|
||||
UDP Socket 发送
|
||||
```
|
||||
|
||||
### 5.3 音频数据流
|
||||
|
||||
```
|
||||
麦克风 → AudioToTs (Actor)
|
||||
├── VAD 检测
|
||||
├── 响度测量
|
||||
├── Opus 编码
|
||||
└── 发送到服务器
|
||||
|
||||
服务器 → tsclientlib::Connection
|
||||
├── AudioData::S2C
|
||||
└── TsToAudio (Actor)
|
||||
├── Opus 解码
|
||||
├── 每客户端音量
|
||||
├── 混音
|
||||
├── 噪声抑制
|
||||
└── SDL2/Oboe 输出
|
||||
```
|
||||
|
||||
## 6. 关键接口
|
||||
|
||||
### 6.1 AppToFrontendBridge
|
||||
```rust
|
||||
pub trait AppToFrontendBridge {
|
||||
fn send(&self, msg: &MessageP2F);
|
||||
fn close(&self);
|
||||
}
|
||||
```
|
||||
|
||||
### 6.2 MessageF2P / MessageP2F
|
||||
```rust
|
||||
// 前端 → 代理
|
||||
pub enum MessageF2P {
|
||||
Connect(ConnectOptions),
|
||||
Disconnect(DisconnectOptions),
|
||||
SendMessage { target, message, return_code },
|
||||
SetClientVolume { client, volume },
|
||||
SetWhispering(Option<WhisperData>),
|
||||
Change { change: JsM2B, return_code },
|
||||
}
|
||||
|
||||
// 代理 → 前端
|
||||
pub enum MessageP2F {
|
||||
Error(String),
|
||||
Connected { server, own_client },
|
||||
DisconnectedTemporarily(),
|
||||
TalkersChanged(Vec<(String, bool)>),
|
||||
Events(Vec<JsEvent>),
|
||||
Message(JsInMessage),
|
||||
Loudnesses(HashMap<String, f32>),
|
||||
Result(ResultStruct),
|
||||
}
|
||||
```
|
||||
|
||||
### 6.3 IBackend / IBackendConnection
|
||||
```typescript
|
||||
interface IBackend {
|
||||
createNewConnection(returnCodes: ReturnCodeTracker): IBackendConnection;
|
||||
graphql<T>(query: string, variables?: Record<string, unknown>): Promise<{data: T}>;
|
||||
get_settings(): Promise<Record<string, unknown>>;
|
||||
set_settings(diff: Record<string, unknown>): Promise<void>;
|
||||
}
|
||||
|
||||
interface IBackendConnection {
|
||||
id: string;
|
||||
connect(onMsg, onError, onClose): Promise<void>;
|
||||
send(data: OutMsg): void;
|
||||
close(): void;
|
||||
fetch_image(req: IFileRequest): Promise<string | undefined>;
|
||||
upload_bytes(req: IFileRequest, data: Blob): Promise<TransferResult>;
|
||||
}
|
||||
```
|
||||
|
||||
## 7. 平台适配
|
||||
|
||||
| 功能 | 桌面 (Tauri) | Web (Browser) | Android |
|
||||
|------|-------------|---------------|---------|
|
||||
| 音频后端 | SDL2 | N/A | Oboe |
|
||||
| TLS | OpenSSL | N/A | rustls |
|
||||
| 文件对话框 | Tauri 插件 | HTML input | Tauri |
|
||||
| 系统托盘 | Tauri tray-icon | N/A | N/A |
|
||||
| 全局热键 | livesplit-hotkey | N/A | N/A |
|
||||
|
||||
## 8. 依赖关系
|
||||
|
||||
```
|
||||
src-tauri ──────depends on──────> qint-proxy ──────depends on──────> tsclientlib
|
||||
│ │ │
|
||||
└──depends on──> proxy-codegen ────┘ tsproto
|
||||
│ │ tsproto-packets
|
||||
└──depends on──> tauri v2 └──depends on──> diesel (SQLite) tsproto-types
|
||||
└──depends on──> audiopus/opus
|
||||
└──depends on──> sdl2/oboe
|
||||
└──depends on──> tantivy (search)
|
||||
└──depends on──> juniper (GraphQL)
|
||||
|
||||
webapp ──────depends on──────> qint-proxy (same as above)
|
||||
│
|
||||
└──depends on──> actix-web
|
||||
└──depends on──> proxy-codegen
|
||||
```
|
||||
|
||||
## 9. 关键技术决策
|
||||
|
||||
| 决策 | 选择 | 理由 |
|
||||
|------|------|------|
|
||||
| 异步运行时 | Tokio | Rust 生态最成熟的异步运行时 |
|
||||
| Actor 框架 | Actix | 成熟的 Actor 模型实现 |
|
||||
| 加密 | AES-128-EAX + ECDH | TeamSpeak 协议规范要求 |
|
||||
| 压缩 | QuickLZ | TeamSpeak 协议使用的压缩算法 |
|
||||
| 拥塞控制 | CUBIC | 类似 TCP CUBIC,适合实时通信 |
|
||||
| 代码生成 | t4rust-derive | 从声明式数据生成大量重复代码 |
|
||||
| 音频编解码 | Opus | TeamSpeak 3 默认编解码器 |
|
||||
| 数据库 | SQLite + Diesel | 轻量级嵌入式数据库 + 类型安全 ORM |
|
||||
| 前端框架 | Svelte 5 | 轻量级响应式框架 |
|
||||
| 桌面框架 | Tauri v2 | 跨平台原生桌面应用 |
|
||||
| Web 框架 | Actix-web | 高性能 Rust Web 框架 |
|
||||
|
||||
## 10. 安全考虑
|
||||
|
||||
1. **身份加密**: 使用 ChaCha20-Poly1305 加密存储身份私钥
|
||||
2. **传输加密**: 使用 AES-128-EAX 加密所有命令和语音数据
|
||||
3. **密钥交换**: 使用 ECDH (prime256v1) 进行密钥交换
|
||||
4. **防 DoS**: 使用 RSA 拼图防止连接洪水攻击
|
||||
5. **身份验证**: 使用 Hashcash 机制防止身份伪造
|
||||
- one desktop client crate
|
||||
- `iced` UI
|
||||
- `tsclientlib` protocol/state/audio integration
|
||||
- optional local audio helpers for capture/playback/noise reduction
|
||||
|
||||
+15
-709
@@ -1,713 +1,19 @@
|
||||
# 组件设计文档 (SDD)
|
||||
# Components
|
||||
|
||||
## 1. 概述
|
||||
## Shipped Components
|
||||
|
||||
本文档详细描述系统各组件的设计,包括功能、接口、数据结构和实现细节。
|
||||
- `iced-app/src/main.rs` - app state, views, TeamSpeak actions
|
||||
- `iced-app/src/audio.rs` - local audio capture/playback/encoding helpers
|
||||
- `iced-app/src/noise_cancel.rs` - noise reduction backends
|
||||
- `iced-app/src/identity.rs` - TeamSpeak identity import
|
||||
- `iced-app/src/theme.rs` - UI theme styles
|
||||
|
||||
## 2. 核心组件
|
||||
## External Core Dependencies
|
||||
|
||||
### 2.1 协议库组件 (tsclientlib)
|
||||
|
||||
#### 2.1.1 tsproto-types (基础类型)
|
||||
|
||||
**职责**: 定义 TeamSpeak 协议中使用的基础类型、枚举和加密原语
|
||||
|
||||
**关键类型**:
|
||||
```rust
|
||||
pub struct ClientId(pub u16); // 客户端 ID
|
||||
pub struct ChannelId(pub u64); // 频道 ID
|
||||
pub struct UidBuf(pub Vec<u8>); // 用户唯一标识
|
||||
pub struct Permission(pub u32); // 权限 ID
|
||||
pub enum ClientType { Normal, Query { admin: bool } }
|
||||
pub enum MaxClients { Unlimited, Inherited, Limited(u16) }
|
||||
```
|
||||
|
||||
**加密模块**:
|
||||
```rust
|
||||
pub struct EccKeyPubP256(p256::PublicKey); // P-256 公钥
|
||||
pub struct EccKeyPrivP256(p256::SecretKey); // P-256 私钥
|
||||
pub struct EccKeyPubEd25519(CompressedEdwardsY); // Ed25519 公钥
|
||||
pub struct EccKeyPrivEd25519(Scalar); // Ed25519 私钥
|
||||
```
|
||||
|
||||
**错误码枚举**: 从 CSV 自动生成,包含所有 TeamSpeak 错误码
|
||||
|
||||
#### 2.1.2 tsproto-structs (声明式数据)
|
||||
|
||||
**职责**: 提供协议的机器可读声明数据
|
||||
|
||||
**声明文件**:
|
||||
- `Book.toml` - 服务器状态数据模型
|
||||
- `Messages.toml` - 协议消息结构
|
||||
- `Enums.toml` - 枚举定义
|
||||
- `Errors.csv` - 错误码列表
|
||||
- `Versions.csv` - 版本信息
|
||||
|
||||
#### 2.1.3 tsproto-packets (包解析)
|
||||
|
||||
**职责**: 解析和序列化 TeamSpeak 网络包和命令
|
||||
|
||||
**关键类型**:
|
||||
```rust
|
||||
pub enum PacketType { Voice, VoiceWhisper, Command, CommandLow, Ping, Pong, Ack, AckLow, Init }
|
||||
pub enum Direction { S2C, C2S }
|
||||
|
||||
// 输入包(零拷贝)
|
||||
pub struct InPacket<'a> { header: InHeader<'a>, content: &'a [u8] }
|
||||
pub struct InCommand<'a> { packet: InPacket<'a> }
|
||||
pub struct InAudio<'a> { packet: InPacket<'a>, data: AudioData<'a> }
|
||||
|
||||
// 输出包
|
||||
pub struct OutPacket { dir: Direction, data: Vec<u8> }
|
||||
pub struct OutCommand(pub OutPacket);
|
||||
```
|
||||
|
||||
**命令解析器**:
|
||||
```rust
|
||||
pub struct CommandParser<'a> { data: &'a [u8], index: usize }
|
||||
pub enum CommandItem<'a> { Argument(CommandArgument<'a>), NextCommand }
|
||||
```
|
||||
|
||||
#### 2.1.4 ts-bookkeeping (状态管理)
|
||||
|
||||
**职责**: 维护 TeamSpeak 服务器的完整状态模型
|
||||
|
||||
**核心数据模型**:
|
||||
```rust
|
||||
pub struct Connection {
|
||||
pub own_client: ClientId,
|
||||
pub server: Server,
|
||||
pub clients: HashMap<ClientId, Client>,
|
||||
pub channels: HashMap<ChannelId, Channel>,
|
||||
pub channel_groups: HashMap<ChannelGroupId, ChannelGroup>,
|
||||
pub server_groups: HashMap<ServerGroupId, ServerGroup>,
|
||||
}
|
||||
|
||||
pub struct Server { /* 名称、版本、最大客户端数、加密模式等 */ }
|
||||
pub struct Channel { /* 名称、类型、编解码器、权限等 */ }
|
||||
pub struct Client { /* 名称、频道、静音状态、权限等 */ }
|
||||
```
|
||||
|
||||
**事件系统**:
|
||||
```rust
|
||||
pub enum Event {
|
||||
PropertyAdded { id: PropertyId, invoker: Option<Invoker>, extra: ExtraInfo },
|
||||
PropertyChanged { id: PropertyId, old: PropertyValue, invoker: Option<Invoker>, extra: ExtraInfo },
|
||||
PropertyRemoved { id: PropertyId, old: PropertyValue, invoker: Option<Invoker>, extra: ExtraInfo },
|
||||
Message { target: MessageTarget, invoker: Invoker, message: String },
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.1.5 tsproto (协议引擎)
|
||||
|
||||
**职责**: 实现 TeamSpeak 3 协议的底层网络通信
|
||||
|
||||
**核心类型**:
|
||||
```rust
|
||||
pub struct Identity {
|
||||
key: EccKeyPrivP256,
|
||||
counter: u64, // Hash Cash 计数器
|
||||
max_counter: u64,
|
||||
}
|
||||
|
||||
pub struct Client {
|
||||
con: Connection,
|
||||
pub private_key: EccKeyPrivP256,
|
||||
}
|
||||
|
||||
pub struct Connection {
|
||||
pub is_client: bool,
|
||||
pub params: Option<ConnectedParams>,
|
||||
pub address: SocketAddr,
|
||||
pub resender: Resender,
|
||||
pub codec: PacketCodec,
|
||||
pub udp_socket: Box<dyn Socket + Send>,
|
||||
}
|
||||
```
|
||||
|
||||
**连接握手流程**:
|
||||
```
|
||||
Client Server
|
||||
│ │
|
||||
│──── Init0 (version, ts) ────>│
|
||||
│<─── Init1 (random1) ─────────│
|
||||
│──── Init2 (random1_r) ──────>│
|
||||
│<─── Init3 (RSA puzzle) ──────│
|
||||
│──── Init4 (solve + ECDH) ───>│
|
||||
│<─── initivexpand2 (license) ─│
|
||||
│──── clientek (ephemeral key) >│
|
||||
│<─── initserver ──────────────│
|
||||
│ (connected) │
|
||||
```
|
||||
|
||||
#### 2.1.6 tsclientlib (高层客户端库)
|
||||
|
||||
**职责**: 提供用户友好的客户端 API
|
||||
|
||||
**核心类型**:
|
||||
```rust
|
||||
pub struct Connection {
|
||||
state: ConnectionState,
|
||||
options: ConnectOptions,
|
||||
stream_items: VecDeque<Result<StreamItem>>,
|
||||
}
|
||||
|
||||
enum ConnectionState {
|
||||
Connecting(BoxFuture<...>, bool),
|
||||
IdentityLevelIncreasing { recv, state },
|
||||
Connected { con: ConnectedConnection, book: data::Connection },
|
||||
}
|
||||
|
||||
pub enum StreamItem {
|
||||
BookEvents(Vec<events::Event>),
|
||||
MessageEvent(InMessage),
|
||||
Audio(InAudioBuf),
|
||||
IdentityLevelIncreasing(u8),
|
||||
IdentityLevelIncreased,
|
||||
DisconnectedTemporarily(TemporaryDisconnectReason),
|
||||
MessageResult(MessageHandle, Result<(), CommandError>),
|
||||
FileDownload(...), FileUpload(...), FiletransferFailed(...),
|
||||
NetworkStatsUpdated,
|
||||
AudioChange(AudioEvent),
|
||||
}
|
||||
```
|
||||
|
||||
**地址解析**:
|
||||
解析优先级:
|
||||
1. 直接 IP 地址
|
||||
2. 服务器昵称 (HTTP 查询)
|
||||
3. DNS SRV 记录
|
||||
4. TSDNS 服务
|
||||
5. 系统 DNS 解析
|
||||
|
||||
**音频处理**:
|
||||
```rust
|
||||
pub struct AudioHandler<Id> {
|
||||
queues: HashMap<Id, AudioQueue>,
|
||||
avg_buffer_samples: usize,
|
||||
}
|
||||
|
||||
pub struct AudioQueue {
|
||||
decoder: Decoder, // Opus 解码器
|
||||
packet_buffer: VecDeque<QueuePacket>,
|
||||
decoded_buffer: Vec<f32>,
|
||||
last_buffer_size_min: SlidingWindowMinimum<u8>,
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 客户端组件 (Qint)
|
||||
|
||||
#### 2.2.1 前端组件
|
||||
|
||||
**连接状态管理 (connection.ts)**:
|
||||
```typescript
|
||||
class Connection {
|
||||
private book: Book;
|
||||
private backend: IBackendConnection;
|
||||
private state: ConnectionState;
|
||||
|
||||
// 状态机: Uninitialized -> Connecting -> Connected -> ChannelListFinished -> Disconnected
|
||||
async connect(onMsg, onError, onClose): Promise<void>;
|
||||
sendMessage(target, message): void;
|
||||
switchChannel(channelId): void;
|
||||
startWhispering(whisperData): void;
|
||||
}
|
||||
```
|
||||
|
||||
**数据状态镜像 (book.ts)**:
|
||||
```typescript
|
||||
class Book {
|
||||
channels: Map<ChannelId, Channel>;
|
||||
clients: Map<ClientId, Client>;
|
||||
serverGroups: Map<ServerGroupId, ServerGroup>;
|
||||
channelGroups: Map<ChannelGroupId, ChannelGroup>;
|
||||
|
||||
processEvent(event: InBookChangeMsg): void;
|
||||
}
|
||||
```
|
||||
|
||||
**后端抽象层 (backend/)**:
|
||||
```typescript
|
||||
interface IBackend {
|
||||
createNewConnection(returnCodes: ReturnCodeTracker): IBackendConnection;
|
||||
graphql<T>(query: string, variables?: Record<string, unknown>): Promise<{data: T}>;
|
||||
get_settings(): Promise<Record<string, unknown>>;
|
||||
set_settings(diff: Record<string, unknown>): Promise<void>;
|
||||
}
|
||||
|
||||
interface IBackendConnection {
|
||||
id: string;
|
||||
connect(onMsg, onError, onClose): Promise<void>;
|
||||
send(data: OutMsg): void;
|
||||
close(): void;
|
||||
fetch_image(req: IFileRequest): Promise<string | undefined>;
|
||||
upload_bytes(req: IFileRequest, data: Blob): Promise<TransferResult>;
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.2.2 壳层组件
|
||||
|
||||
**Tauri 命令处理器 (cmd.rs)**:
|
||||
```rust
|
||||
#[command]
|
||||
async fn create_ws(state: State<'_, QintCore>, window: Window, con: String) -> Result<(), String>
|
||||
|
||||
#[command]
|
||||
async fn pass_ws_msg(state: State<'_, QintCore>, con: String, msg: MessageF2P) -> Result<(), String>
|
||||
|
||||
#[command]
|
||||
async fn db(state: State<'_, QintState>, query: String, variables: String) -> Result<String, String>
|
||||
```
|
||||
|
||||
**WebSocket 处理器 (websocket.rs)**:
|
||||
```rust
|
||||
struct Ws {
|
||||
state: QintState,
|
||||
id: String,
|
||||
addr: Addr<QintConnection>,
|
||||
}
|
||||
|
||||
impl Ws {
|
||||
fn handle_message(&mut self, msg: F2PMsg) {
|
||||
match msg.cmd {
|
||||
"create_ws" => { /* 创建连接 */ }
|
||||
"pass_ws_msg" => { /* 转发消息 */ }
|
||||
"get_settings" => { /* 获取设置 */ }
|
||||
_ => { /* 其他命令 */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.2.3 代理层组件
|
||||
|
||||
**全局状态管理 (QintState)**:
|
||||
```rust
|
||||
pub struct QintState {
|
||||
pub connections: Mutex<HashMap<String, Addr<QintConnection>>>,
|
||||
pub audio_data: Arc<AudioData>,
|
||||
pub hotkeys: HotkeyManager,
|
||||
pub settings: RwLock<Settings>,
|
||||
pub database: Addr<DbHandler>,
|
||||
pub graphql_schema: Schema<QueryRoot, MutationRoot, EmptySubscription>,
|
||||
pub file_cache: FileCache,
|
||||
pub link_previewer: LinkPreviewer,
|
||||
pub secret: chacha20poly1305::Key,
|
||||
pub search_index: SearchIndex,
|
||||
}
|
||||
```
|
||||
|
||||
**连接管理器 (QintConnection)**:
|
||||
```rust
|
||||
pub struct QintConnection {
|
||||
state: QintState,
|
||||
id: String,
|
||||
con: Option<tsclientlib::Connection>,
|
||||
bridge: Box<dyn AppToFrontendBridge>,
|
||||
audio_to_ts: Addr<AudioToTs>,
|
||||
ts_to_audio: Addr<TsToAudio>,
|
||||
database: Addr<DbHandler>,
|
||||
}
|
||||
|
||||
impl Actor for QintConnection {
|
||||
type Context = Context<Self>;
|
||||
}
|
||||
|
||||
impl Handler<MessageF2PWrapper> for QintConnection {
|
||||
fn handle(&mut self, msg: MessageF2PWrapper, ctx: &mut Self::Context) {
|
||||
match msg.0 {
|
||||
MessageF2P::Connect(options) => { /* 建立连接 */ }
|
||||
MessageF2P::Disconnect(options) => { /* 断开连接 */ }
|
||||
MessageF2P::SendMessage { target, message, return_code } => { /* 发送消息 */ }
|
||||
// ...
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**数据库管理器 (DbHandler)**:
|
||||
```rust
|
||||
pub struct DbHandler {
|
||||
pool: SqlitePool,
|
||||
}
|
||||
|
||||
impl Actor for DbHandler {
|
||||
type Context = Context<Self>;
|
||||
}
|
||||
|
||||
impl Handler<GetIdentityAndServerMsg> for DbHandler {
|
||||
fn handle(&mut self, msg: GetIdentityAndServerMsg, ctx: &mut Self::Context) -> Self::Result {
|
||||
// 从数据库获取身份和服务器信息
|
||||
}
|
||||
}
|
||||
|
||||
impl Handler<WriteMessageMsg> for DbHandler {
|
||||
fn handle(&mut self, msg: WriteMessageMsg, ctx: &mut Self::Context) {
|
||||
// 写入聊天消息到数据库
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**音频管道**:
|
||||
```rust
|
||||
// AudioToTs - 麦克风采集和编码
|
||||
pub struct AudioToTs {
|
||||
connections: Vec<Addr<QintConnection>>,
|
||||
encoder: OpusEncoder,
|
||||
vad: VadDetector,
|
||||
loudness_meter: LoudnessMeter,
|
||||
}
|
||||
|
||||
impl Handler<SendPacketMsg> for AudioToTs {
|
||||
fn handle(&mut self, msg: SendPacketMsg, ctx: &mut Self::Context) {
|
||||
// 编码并发送音频数据
|
||||
}
|
||||
}
|
||||
|
||||
// TsToAudio - 音频解码和播放
|
||||
pub struct TsToAudio {
|
||||
queues: HashMap<String, AudioQueue>,
|
||||
output_device: AudioDevice,
|
||||
}
|
||||
|
||||
impl Handler<PlayMsg> for TsToAudio {
|
||||
fn handle(&mut self, msg: PlayMsg, ctx: &mut Self::Context) {
|
||||
// 解码并播放音频
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2.3 机器人组件 (SimpleBot)
|
||||
|
||||
**核心结构**:
|
||||
```rust
|
||||
pub struct Bot {
|
||||
base_dir: PathBuf,
|
||||
settings_path: PathBuf,
|
||||
actions: ActionList,
|
||||
settings: Settings,
|
||||
rate_limiting: Vec<Instant>,
|
||||
list: Vec<String>,
|
||||
should_reload: Cell<bool>,
|
||||
}
|
||||
```
|
||||
|
||||
**动作系统**:
|
||||
```rust
|
||||
pub struct ActionDefinition {
|
||||
contains: Option<String>,
|
||||
regex: Option<String>,
|
||||
chat: Option<String>,
|
||||
response: Option<String>,
|
||||
command: Option<String>,
|
||||
shell: Option<String>,
|
||||
}
|
||||
|
||||
pub struct Action {
|
||||
matchers: Vec<Matcher>,
|
||||
reaction: Option<Reaction>,
|
||||
}
|
||||
|
||||
pub enum Matcher {
|
||||
Regex(Regex),
|
||||
Mode(Option<TextMessageTargetMode>),
|
||||
}
|
||||
|
||||
pub enum Reaction {
|
||||
Plain(String),
|
||||
Command(String),
|
||||
Shell(String),
|
||||
Function(ReactionFunction),
|
||||
}
|
||||
```
|
||||
|
||||
**配置系统**:
|
||||
```rust
|
||||
pub struct Settings {
|
||||
key_file: String,
|
||||
dynamic_actions: String,
|
||||
address: String,
|
||||
channel: Option<ChannelDefinition>,
|
||||
name: String,
|
||||
disconnect_message: String,
|
||||
rate_limit: u8,
|
||||
prefix: String,
|
||||
actions: ActionFile,
|
||||
}
|
||||
|
||||
pub struct ActionFile {
|
||||
include: Vec<String>,
|
||||
on_message: Vec<ActionDefinition>,
|
||||
}
|
||||
```
|
||||
|
||||
### 2.4 统计工具组件 (ts3stats)
|
||||
|
||||
**核心类**:
|
||||
```python
|
||||
class DiagramCreator:
|
||||
env: jinja2.Environment
|
||||
diagramTemplate: jinja2.Template
|
||||
htmlTemplate: jinja2.Template
|
||||
users: dict[int, User]
|
||||
vip: list[User]
|
||||
tabs: list[Tab]
|
||||
|
||||
def load_meta(self): pass
|
||||
def load_data(self): pass
|
||||
def create_diagrams(self): pass
|
||||
def fun_per_connected_slot(self, users, callback): pass
|
||||
|
||||
class User:
|
||||
name: str
|
||||
lastConnected: list[datetime]
|
||||
connections: list[Connection]
|
||||
botPlays: list[tuple]
|
||||
botCommands: list[tuple]
|
||||
|
||||
class Connection:
|
||||
start: datetime
|
||||
end: datetime
|
||||
timeout: bool
|
||||
|
||||
class Diagram:
|
||||
filename: str
|
||||
title: str
|
||||
plots: list[str]
|
||||
|
||||
def render(self): pass
|
||||
|
||||
class Tab:
|
||||
name: str
|
||||
diagrams: list[Diagram]
|
||||
```
|
||||
|
||||
**插件架构**:
|
||||
每个 `diags/*.py` 文件暴露一个函数:
|
||||
```python
|
||||
def create_diag(dc: DiagramCreator) -> None
|
||||
```
|
||||
|
||||
**配置系统**:
|
||||
```python
|
||||
# Settings.py
|
||||
vips = ["MyName", "friend42"]
|
||||
merges = [["MyName", "MyNameLaptop"]]
|
||||
maxUsers = 50
|
||||
botStats = True
|
||||
inputFolder = "Logs"
|
||||
outputFolder = "Result"
|
||||
```
|
||||
|
||||
## 3. 接口设计
|
||||
|
||||
### 3.1 Tauri IPC 接口
|
||||
|
||||
| 命令 | 参数 | 返回值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| `create_ws` | `con: String` | `()` | 创建 WebSocket 连接 |
|
||||
| `close_ws` | `con: String` | `()` | 关闭 WebSocket 连接 |
|
||||
| `pass_ws_msg` | `con: String, msg: MessageF2P` | `()` | 转发前端消息 |
|
||||
| `db` | `query: String, variables: String` | `String` | GraphQL 查询 |
|
||||
| `get_settings` | | `Record<string, unknown>` | 获取设置 |
|
||||
| `set_settings` | `diff: Record<string, unknown>` | `()` | 更新设置 |
|
||||
|
||||
### 3.2 WebSocket 协议
|
||||
|
||||
**前端发送**:
|
||||
```json
|
||||
{"cmd": "create_ws", "returnCode": "0", "args": {"con": "uuid"}}
|
||||
{"cmd": "pass_ws_msg", "returnCode": "1", "args": {"con": "uuid", "msg": {"Connect": {...}}}}
|
||||
{"cmd": "get_settings", "returnCode": "2", "args": {}}
|
||||
```
|
||||
|
||||
**后端发送**:
|
||||
```json
|
||||
{"cmd": "resp", "returnCode": "2", "msg": {...}}
|
||||
{"cmd": "resp_err", "returnCode": "1", "msg": "error"}
|
||||
{"cmd": "ws", "con": "uuid", "msg": {"Connected": {...}}}
|
||||
{"cmd": "ws_close", "con": "uuid"}
|
||||
{"cmd": "loudness", "msg": [0.5, 0.3]}
|
||||
```
|
||||
|
||||
### 3.3 GraphQL 查询
|
||||
|
||||
```graphql
|
||||
type Query {
|
||||
bookmarks: [Bookmark!]!
|
||||
servers: [Server!]!
|
||||
channels(serverId: ID!): [Channel!]!
|
||||
clients(serverId: ID!): [Client!]!
|
||||
identities: [Identity!]!
|
||||
chats(serverId: ID!): [Chat!]!
|
||||
messages(chatId: ID!, limit: Int): [Message!]!
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
updateIdentity(id: ID!, input: IdentityInput!): Identity!
|
||||
updateBookmark(id: ID!, input: BookmarkInput!): Bookmark!
|
||||
}
|
||||
```
|
||||
|
||||
## 4. 数据流
|
||||
|
||||
### 4.1 连接建立流程
|
||||
|
||||
```
|
||||
前端 代理 TeamSpeak 服务器
|
||||
│ │ │
|
||||
│── Connect(addr, name) ───>│ │
|
||||
│ │── GetIdentityAndServerMsg ────> DbHandler
|
||||
│ │<── (identity, server) ──────────│
|
||||
│ │── Connection::build().connect ─>│
|
||||
│ │ │
|
||||
│ │<── TsStreamItem::BookEvents ────│
|
||||
│ │ (PropertyAdded: Server) │
|
||||
│ │ │
|
||||
│<── Connected {server, own} │ │
|
||||
│ │── ConnectedMsg ────────────────> DbHandler
|
||||
│ │ │
|
||||
│<── Events [PropertyAdded..]│ │
|
||||
```
|
||||
|
||||
### 4.2 消息发送流程
|
||||
|
||||
```
|
||||
前端 代理 TeamSpeak 服务器
|
||||
│ │ │
|
||||
│── SendMessage {target, msg}│ │
|
||||
│ │── state.send_message(target) ──>│
|
||||
│ │── WriteMessageMsg ─────────────> DbHandler
|
||||
│ │ │
|
||||
│ │<── InMessage (from other) ──────│
|
||||
│ │── JsInMessage ─────────────────>│
|
||||
│<── Message(JsInMessage) ──│ │
|
||||
│ │── WriteMessageMsg ─────────────> DbHandler
|
||||
```
|
||||
|
||||
### 4.3 音频流程
|
||||
|
||||
```
|
||||
麦克风 → AudioToTs (Actor)
|
||||
├── VAD 检测
|
||||
├── 响度测量
|
||||
├── Opus 编码
|
||||
└── 发送到服务器
|
||||
|
||||
服务器 → tsclientlib::Connection
|
||||
├── AudioData::S2C
|
||||
└── TsToAudio (Actor)
|
||||
├── Opus 解码
|
||||
├── 每客户端音量
|
||||
├── 混音
|
||||
├── 噪声抑制
|
||||
└── SDL2/Oboe 输出
|
||||
```
|
||||
|
||||
## 5. 错误处理
|
||||
|
||||
### 5.1 协议错误
|
||||
|
||||
```rust
|
||||
pub enum CommandError {
|
||||
TsError(Ts3ErrorCode),
|
||||
ConnectionClosed,
|
||||
Timeout,
|
||||
InvalidResponse,
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 连接错误
|
||||
|
||||
```rust
|
||||
pub enum TemporaryDisconnectReason {
|
||||
Timeout,
|
||||
ServerShutdown,
|
||||
ConnectionLost,
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### 5.3 前端错误
|
||||
|
||||
```typescript
|
||||
interface ErrorMessage {
|
||||
type: "error";
|
||||
message: string;
|
||||
code?: string;
|
||||
}
|
||||
```
|
||||
|
||||
## 6. 配置管理
|
||||
|
||||
### 6.1 应用配置
|
||||
|
||||
```rust
|
||||
pub struct Settings {
|
||||
pub name: String,
|
||||
pub away: Option<String>,
|
||||
pub input_muted: bool,
|
||||
pub output_muted: bool,
|
||||
pub hotkeys: Vec<HotkeyAction>,
|
||||
pub client_volumes: HashMap<String, f32>,
|
||||
pub theme: String,
|
||||
pub language: String,
|
||||
}
|
||||
```
|
||||
|
||||
### 6.2 连接配置
|
||||
|
||||
```rust
|
||||
pub struct ConnectOptions {
|
||||
address: ServerAddress,
|
||||
local_address: Option<SocketAddr>,
|
||||
identity: Option<Identity>,
|
||||
server: Option<UidBuf>,
|
||||
name: String,
|
||||
version: Version,
|
||||
channel: Option<String>,
|
||||
channel_password: Option<String>,
|
||||
server_password: Option<String>,
|
||||
default_token: Option<String>,
|
||||
}
|
||||
```
|
||||
|
||||
## 7. 性能考虑
|
||||
|
||||
### 7.1 音频处理
|
||||
- Opus 编码/解码使用硬件加速(如果可用)
|
||||
- 自适应抖动缓冲减少延迟
|
||||
- 噪声抑制减少带宽使用
|
||||
|
||||
### 7.2 状态同步
|
||||
- 增量更新减少数据传输
|
||||
- 事件批处理减少 IPC 调用
|
||||
- 懒加载减少初始加载时间
|
||||
|
||||
### 7.3 数据库
|
||||
- SQLite WAL 模式支持并发读取
|
||||
- 连接池减少连接开销
|
||||
- 索引优化查询性能
|
||||
|
||||
## 8. 安全设计
|
||||
|
||||
### 8.1 身份加密
|
||||
```rust
|
||||
// ChaCha20-Poly1305 加密身份私钥
|
||||
fn encrypt_identity(key: &[u8; 32], identity: &[u8]) -> Vec<u8> {
|
||||
let cipher = ChaCha20Poly1305::new(key.into());
|
||||
let nonce = generate_nonce();
|
||||
cipher.encrypt(&nonce, identity)
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 传输加密
|
||||
- 所有命令和语音数据使用 AES-128-EAX 加密
|
||||
- ECDH 密钥交换确保前向保密
|
||||
- RSA 拼图防止 DoS 攻击
|
||||
|
||||
### 8.3 输入验证
|
||||
- 所有用户输入进行验证和转义
|
||||
- SQL 查询使用参数化语句
|
||||
- WebSocket 消息进行 JSON 验证
|
||||
- `tsclientlib`
|
||||
- `tsproto-packets`
|
||||
- `iced`
|
||||
- `cpal`
|
||||
- `audiopus`
|
||||
- `nnnoiseless`
|
||||
- `sonora`
|
||||
|
||||
+6
-570
@@ -1,573 +1,9 @@
|
||||
# 需求分析文档 (SRS)
|
||||
# Requirements Notes
|
||||
|
||||
## 1. 概述
|
||||
The authoritative requirements are now in `SRS.md`.
|
||||
|
||||
本文档定义 TeamSpeak 3 客户端系统的功能需求和非功能需求,基于对参考代码库的分析和 TeamSpeak 3 协议规范。
|
||||
Important current constraints:
|
||||
|
||||
## 2. 系统目标
|
||||
|
||||
### 2.1 主要目标
|
||||
1. 实现一个功能完整的 TeamSpeak 3 客户端
|
||||
2. 支持跨平台运行 (Windows, Linux, macOS, Android, Web)
|
||||
3. 提供现代化的用户界面
|
||||
4. 保持与官方 TeamSpeak 服务器的兼容性
|
||||
5. 支持音频通信和文本聊天
|
||||
|
||||
### 2.2 次要目标
|
||||
1. 支持插件扩展机制
|
||||
2. 提供统计和分析功能
|
||||
3. 支持多服务器同时连接
|
||||
4. 提供机器人开发框架
|
||||
|
||||
## 3. 功能需求
|
||||
|
||||
### 3.1 连接管理
|
||||
|
||||
#### FR-3.1.1 服务器连接
|
||||
**描述**: 用户能够连接到 TeamSpeak 3 服务器
|
||||
|
||||
**需求**:
|
||||
- 支持通过 IP 地址连接
|
||||
- 支持通过域名连接
|
||||
- 支持通过服务器昵称连接
|
||||
- 支持 DNS SRV 和 TSDNS 解析
|
||||
- 支持服务器密码验证
|
||||
- 支持身份选择和管理
|
||||
- 支持默认频道设置
|
||||
- 支持频道密码
|
||||
|
||||
**输入**:
|
||||
- 服务器地址 (IP/域名/昵称)
|
||||
- 服务器密码 (可选)
|
||||
- 身份 (可选)
|
||||
- 昵称
|
||||
- 默认频道 (可选)
|
||||
- 频道密码 (可选)
|
||||
|
||||
**输出**:
|
||||
- 连接成功/失败状态
|
||||
- 服务器信息
|
||||
- 客户端 ID
|
||||
|
||||
#### FR-3.1.2 连接状态管理
|
||||
**描述**: 管理连接的生命周期状态
|
||||
|
||||
**状态**:
|
||||
- Uninitialized: 未初始化
|
||||
- Connecting: 连接中
|
||||
- IdentityLevelIncreasing: 身份等级提升中
|
||||
- Connected: 已连接
|
||||
- ChannelListFinished: 频道列表接收完成
|
||||
- DisconnectedTemporarily: 临时断开
|
||||
- Disconnected: 已断开
|
||||
- Error: 错误
|
||||
|
||||
**需求**:
|
||||
- 状态转换应有明确的触发条件
|
||||
- 状态变化应通知前端
|
||||
- 支持自动重连(临时断开时)
|
||||
- 支持手动断开连接
|
||||
|
||||
#### FR-3.1.3 多服务器连接
|
||||
**描述**: 支持同时连接多个 TeamSpeak 服务器
|
||||
|
||||
**需求**:
|
||||
- 每个连接独立管理
|
||||
- 连接间互不影响
|
||||
- 支持连接切换
|
||||
- 支持连接列表管理
|
||||
|
||||
### 3.2 频道管理
|
||||
|
||||
#### FR-3.2.1 频道浏览
|
||||
**描述**: 浏览服务器的频道结构
|
||||
|
||||
**需求**:
|
||||
- 显示频道树状结构
|
||||
- 显示频道名称、主题、编解码器
|
||||
- 显示频道最大客户端数
|
||||
- 显示频道类型(永久/半永久/临时)
|
||||
- 显示频道密码状态
|
||||
- 支持频道搜索
|
||||
|
||||
#### FR-3.2.2 频道操作
|
||||
**描述**: 执行频道相关操作
|
||||
|
||||
**需求**:
|
||||
- 加入频道
|
||||
- 离开频道
|
||||
- 创建频道(需要权限)
|
||||
- 编辑频道(需要权限)
|
||||
- 删除频道(需要权限)
|
||||
- 移动频道(需要权限)
|
||||
- 设置频道密码
|
||||
- 设置频道排序
|
||||
|
||||
#### FR-3.2.3 频道订阅
|
||||
**描述**: 订阅频道以接收通知
|
||||
|
||||
**需求**:
|
||||
- 订阅频道
|
||||
- 取消订阅频道
|
||||
- 接收频道事件通知
|
||||
- 显示订阅状态
|
||||
|
||||
### 3.3 客户端管理
|
||||
|
||||
#### FR-3.3.1 客户端列表
|
||||
**描述**: 显示在线客户端列表
|
||||
|
||||
**需求**:
|
||||
- 显示客户端昵称
|
||||
- 显示客户端状态(离开/录音/静音等)
|
||||
- 显示客户端所在频道
|
||||
- 显示客户端服务器组
|
||||
- 显示客户端频道组
|
||||
- 支持客户端搜索
|
||||
|
||||
#### FR-3.3.2 客户端操作
|
||||
**描述**: 执行客户端相关操作
|
||||
|
||||
**需求**:
|
||||
- 发送私聊消息
|
||||
- 发送戳一戳
|
||||
- 移动客户端(需要权限)
|
||||
- 踢出客户端(需要权限)
|
||||
- 封禁客户端(需要权限)
|
||||
- 设置客户端音量
|
||||
- 设置客户端静音
|
||||
|
||||
#### FR-3.3.3 客户端信息
|
||||
**描述**: 查看客户端详细信息
|
||||
|
||||
**需求**:
|
||||
- 显示客户端唯一标识符
|
||||
- 显示客户端数据库 ID
|
||||
- 显示客户端版本信息
|
||||
- 显示客户端平台
|
||||
- 显示客户端连接信息
|
||||
- 显示客户端权限
|
||||
|
||||
### 3.4 文本聊天
|
||||
|
||||
#### FR-3.4.1 消息发送
|
||||
**描述**: 发送文本消息
|
||||
|
||||
**需求**:
|
||||
- 发送服务器消息
|
||||
- 发送频道消息
|
||||
- 发送私聊消息
|
||||
- 支持 BBCode 格式
|
||||
- 支持消息历史
|
||||
- 支持消息撤回(如果服务器支持)
|
||||
|
||||
#### FR-3.4.2 消息接收
|
||||
**描述**: 接收和显示文本消息
|
||||
|
||||
**需求**:
|
||||
- 显示服务器消息
|
||||
- 显示频道消息
|
||||
- 显示私聊消息
|
||||
- 显示系统通知
|
||||
- 支持消息通知
|
||||
- 支持消息过滤
|
||||
|
||||
#### FR-3.4.3 消息存储
|
||||
**描述**: 存储聊天历史
|
||||
|
||||
**需求**:
|
||||
- 本地存储消息历史
|
||||
- 支持消息搜索
|
||||
- 支持消息导出
|
||||
- 支持消息清理
|
||||
|
||||
### 3.5 音频通信
|
||||
|
||||
#### FR-3.5.1 音频输入
|
||||
**描述**: 捕获和处理麦克风音频
|
||||
|
||||
**需求**:
|
||||
- 支持音频设备选择
|
||||
- 支持音频编码 (Opus)
|
||||
- 支持语音活动检测 (VAD)
|
||||
- 支持推按说话 (PTT)
|
||||
- 支持输入音量调节
|
||||
- 支持输入静音
|
||||
- 支持噪声抑制
|
||||
- 支持回声消除
|
||||
|
||||
#### FR-3.5.2 音频输出
|
||||
**描述**: 播放接收到的音频
|
||||
|
||||
**需求**:
|
||||
- 支持音频设备选择
|
||||
- 支持音频解码 (Opus)
|
||||
- 支持输出音量调节
|
||||
- 支持输出静音
|
||||
- 支持每客户端音量
|
||||
- 支持音频混音
|
||||
- 支持抖动缓冲
|
||||
- 支持丢包隐藏
|
||||
|
||||
#### FR-3.5.3 音频路由
|
||||
**描述**: 管理音频数据的路由
|
||||
|
||||
**需求**:
|
||||
- 支持频道音频
|
||||
- 支持私语
|
||||
- 支持组私语
|
||||
- 支持频道指挥官
|
||||
- 支持优先发言者
|
||||
- 支持音频编码质量设置
|
||||
|
||||
### 3.6 文件传输
|
||||
|
||||
#### FR-3.6.1 文件浏览
|
||||
**描述**: 浏览频道文件
|
||||
|
||||
**需求**:
|
||||
- 显示文件列表
|
||||
- 显示文件大小
|
||||
- 显示文件修改时间
|
||||
- 支持文件搜索
|
||||
- 支持目录创建
|
||||
|
||||
#### FR-3.6.2 文件上传
|
||||
**描述**: 上传文件到频道
|
||||
|
||||
**需求**:
|
||||
- 支持单文件上传
|
||||
- 支持多文件上传
|
||||
- 支持断点续传
|
||||
- 支持上传进度显示
|
||||
- 支持上传取消
|
||||
|
||||
#### FR-3.6.3 文件下载
|
||||
**描述**: 下载频道文件
|
||||
|
||||
**需求**:
|
||||
- 支持单文件下载
|
||||
- 支持多文件下载
|
||||
- 支持断点续传
|
||||
- 支持下载进度显示
|
||||
- 支持下载取消
|
||||
|
||||
### 3.7 身份管理
|
||||
|
||||
#### FR-3.7.1 身份创建
|
||||
**描述**: 创建新的 TeamSpeak 身份
|
||||
|
||||
**需求**:
|
||||
- 自动生成 ECC 密钥对
|
||||
- 支持身份命名
|
||||
- 支持身份导出
|
||||
- 支持身份导入
|
||||
|
||||
#### FR-3.7.2 身份存储
|
||||
**描述**: 安全存储身份信息
|
||||
|
||||
**需求**:
|
||||
- 加密存储私钥
|
||||
- 支持多身份管理
|
||||
- 支持身份备份
|
||||
- 支持身份恢复
|
||||
|
||||
#### FR-3.7.3 身份验证
|
||||
**描述**: 使用身份进行验证
|
||||
|
||||
**需求**:
|
||||
- 支持 Hashcash 计算
|
||||
- 支持身份等级提升
|
||||
- 支持权限令牌
|
||||
- 支持身份唯一标识符计算
|
||||
|
||||
### 3.8 权限管理
|
||||
|
||||
#### FR-3.8.1 权限查看
|
||||
**描述**: 查看权限信息
|
||||
|
||||
**需求**:
|
||||
- 显示服务器组权限
|
||||
- 显示频道组权限
|
||||
- 显示客户端权限
|
||||
- 显示权限值
|
||||
- 显示权限描述
|
||||
|
||||
#### FR-3.8.2 权限操作
|
||||
**描述**: 执行权限相关操作
|
||||
|
||||
**需求**:
|
||||
- 添加权限(需要权限)
|
||||
- 删除权限(需要权限)
|
||||
- 修改权限(需要权限)
|
||||
- 添加服务器组客户端
|
||||
- 删除服务器组客户端
|
||||
|
||||
### 3.9 服务器管理
|
||||
|
||||
#### FR-3.9.1 服务器信息
|
||||
**描述**: 查看服务器信息
|
||||
|
||||
**需求**:
|
||||
- 显示服务器名称
|
||||
- 显示服务器版本
|
||||
- 显示服务器平台
|
||||
- 显示在线客户端数
|
||||
- 显示频道数
|
||||
- 显示服务器运行时间
|
||||
- 显示服务器加密模式
|
||||
|
||||
#### FR-3.9.2 服务器操作
|
||||
**描述**: 执行服务器相关操作
|
||||
|
||||
**需求**:
|
||||
- 修改服务器密码(需要权限)
|
||||
- 修改服务器名称(需要权限)
|
||||
- 查看服务器日志(需要权限)
|
||||
- 发送服务器消息(需要权限)
|
||||
|
||||
### 3.10 热键管理
|
||||
|
||||
#### FR-3.10.1 热键配置
|
||||
**描述**: 配置全局热键
|
||||
|
||||
**需求**:
|
||||
- 支持输入静音切换
|
||||
- 支持输出静音切换
|
||||
- 支持离开状态切换
|
||||
- 支持推按说话
|
||||
- 支持自定义热键
|
||||
|
||||
#### FR-3.10.2 热键执行
|
||||
**描述**: 执行热键绑定的操作
|
||||
|
||||
**需求**:
|
||||
- 全局热键支持
|
||||
- 应用内热键支持
|
||||
- 热键冲突检测
|
||||
- 热键状态反馈
|
||||
|
||||
### 3.11 设置管理
|
||||
|
||||
#### FR-3.11.1 应用设置
|
||||
**描述**: 管理应用程序设置
|
||||
|
||||
**需求**:
|
||||
- 用户界面设置
|
||||
- 音频设备设置
|
||||
- 热键设置
|
||||
- 通知设置
|
||||
- 语言设置
|
||||
- 主题设置
|
||||
|
||||
#### FR-3.11.2 连接设置
|
||||
**描述**: 管理连接相关设置
|
||||
|
||||
**需求**:
|
||||
- 默认昵称
|
||||
- 默认身份
|
||||
- 默认频道
|
||||
- 自动重连设置
|
||||
- 音频编码设置
|
||||
|
||||
### 3.12 插件系统
|
||||
|
||||
#### FR-3.12.1 插件加载
|
||||
**描述**: 加载和管理插件
|
||||
|
||||
**需求**:
|
||||
- 支持动态加载插件
|
||||
- 支持插件启用/禁用
|
||||
- 支持插件配置
|
||||
- 支持插件更新
|
||||
|
||||
#### FR-3.12.2 插件接口
|
||||
**描述**: 提供插件开发接口
|
||||
|
||||
**需求**:
|
||||
- 事件监听接口
|
||||
- 命令注册接口
|
||||
- UI 扩展接口
|
||||
- 数据访问接口
|
||||
|
||||
## 4. 非功能需求
|
||||
|
||||
### 4.1 性能需求
|
||||
|
||||
#### NFR-4.1.1 连接性能
|
||||
- 连接建立时间 < 3 秒
|
||||
- 消息延迟 < 100ms
|
||||
- 音频延迟 < 200ms
|
||||
- 支持 1000+ 客户端的服务器
|
||||
|
||||
#### NFR-4.1.2 资源使用
|
||||
- CPU 使用率 < 10% (空闲时)
|
||||
- 内存使用 < 200MB
|
||||
- 网络带宽 < 1Mbps (语音通信时)
|
||||
- 磁盘空间 < 100MB (应用程序)
|
||||
|
||||
#### NFR-4.1.3 并发性能
|
||||
- 支持 10+ 同时连接
|
||||
- 支持 100+ 消息/秒
|
||||
- 支持 50+ 音频流同时播放
|
||||
|
||||
### 4.2 可靠性需求
|
||||
|
||||
#### NFR-4.2.1 连接可靠性
|
||||
- 支持自动重连
|
||||
- 支持断点续传
|
||||
- 支持数据包重传
|
||||
- 支持拥塞控制
|
||||
|
||||
#### NFR-4.2.2 数据可靠性
|
||||
- 消息不丢失
|
||||
- 文件传输完整性
|
||||
- 身份数据安全
|
||||
- 配置数据备份
|
||||
|
||||
### 4.3 安全性需求
|
||||
|
||||
#### NFR-4.3.1 传输安全
|
||||
- 使用 AES-128-EAX 加密
|
||||
- 使用 ECDH 密钥交换
|
||||
- 支持证书验证
|
||||
- 防止中间人攻击
|
||||
|
||||
#### NFR-4.3.2 数据安全
|
||||
- 私钥加密存储
|
||||
- 敏感数据不落盘
|
||||
- 安全内存处理
|
||||
- 防止内存泄露
|
||||
|
||||
#### NFR-4.3.3 访问控制
|
||||
- 身份验证
|
||||
- 权限检查
|
||||
- 操作审计
|
||||
- 防止未授权访问
|
||||
|
||||
### 4.4 可用性需求
|
||||
|
||||
#### NFR-4.4.1 用户界面
|
||||
- 响应式设计
|
||||
- 键盘导航支持
|
||||
- 屏幕阅读器支持
|
||||
- 高对比度模式
|
||||
|
||||
#### NFR-4.4.2 国际化
|
||||
- 支持多语言
|
||||
- 支持 RTL 布局
|
||||
- 支持本地化日期格式
|
||||
- 支持本地化数字格式
|
||||
|
||||
#### NFR-4.4.3 可访问性
|
||||
- 支持字体大小调整
|
||||
- 支持颜色主题切换
|
||||
- 支持快捷键自定义
|
||||
- 支持语音反馈
|
||||
|
||||
### 4.5 可维护性需求
|
||||
|
||||
#### NFR-4.5.1 代码质量
|
||||
- 模块化设计
|
||||
- 清晰的接口定义
|
||||
- 完整的文档
|
||||
- 单元测试覆盖
|
||||
|
||||
#### NFR-4.5.2 日志和监控
|
||||
- 详细的日志记录
|
||||
- 错误报告机制
|
||||
- 性能监控
|
||||
- 使用统计
|
||||
|
||||
### 4.6 可扩展性需求
|
||||
|
||||
#### NFR-4.6.1 架构扩展
|
||||
- 支持插件系统
|
||||
- 支持自定义主题
|
||||
- 支持自定义命令
|
||||
- 支持自定义通知
|
||||
|
||||
#### NFR-4.6.2 协议扩展
|
||||
- 支持协议版本协商
|
||||
- 支持功能特性检测
|
||||
- 支持向后兼容
|
||||
- 支持向前兼容
|
||||
|
||||
## 5. 约束条件
|
||||
|
||||
### 5.1 技术约束
|
||||
- 使用 Rust 作为主要开发语言
|
||||
- 使用 Tauri v2 作为桌面框架
|
||||
- 使用 Svelte 5 作为前端框架
|
||||
- 使用 Actix 作为 Actor 框架
|
||||
- 使用 SQLite 作为本地数据库
|
||||
|
||||
### 5.2 协议约束
|
||||
- 兼容 TeamSpeak 3 协议
|
||||
- 支持 IPv4 和 IPv6
|
||||
- 使用 UDP 传输
|
||||
- 最大数据包大小 500 字节
|
||||
|
||||
### 5.3 法律约束
|
||||
- 不得侵犯 TeamSpeak 商标
|
||||
- 不得用于商业用途(除非获得许可)
|
||||
- 遵守相关法律法规
|
||||
- 尊重用户隐私
|
||||
|
||||
## 6. 验收标准
|
||||
|
||||
### 6.1 功能验收
|
||||
- 能够连接到官方 TeamSpeak 服务器
|
||||
- 能够发送和接收文本消息
|
||||
- 能够进行语音通信
|
||||
- 能够浏览和管理频道
|
||||
- 能够管理客户端
|
||||
|
||||
### 6.2 性能验收
|
||||
- 连接建立时间 < 3 秒
|
||||
- 消息延迟 < 100ms
|
||||
- 音频延迟 < 200ms
|
||||
- CPU 使用率 < 10% (空闲时)
|
||||
|
||||
### 6.3 安全验收
|
||||
- 通过安全审计
|
||||
- 无已知漏洞
|
||||
- 数据加密存储
|
||||
- 传输加密验证
|
||||
|
||||
### 6.4 兼容性验收
|
||||
- Windows 10/11 兼容
|
||||
- Linux (Ubuntu 20.04+) 兼容
|
||||
- macOS 11+ 兼容
|
||||
- Android 10+ 兼容
|
||||
- Chrome/Firefox/Safari 兼容
|
||||
|
||||
## 7. 术语表
|
||||
|
||||
| 术语 | 定义 |
|
||||
|------|------|
|
||||
| TeamSpeak | 一种 VoIP 应用程序,用于语音通信 |
|
||||
| Channel | 语音/文本通信的房间 |
|
||||
| Client | 连接到服务器的用户或机器人 |
|
||||
| Server | TeamSpeak 服务器实例 |
|
||||
| Identity | 用户的唯一标识符 |
|
||||
| Permission | 用户的操作权限 |
|
||||
| Codec | 音频编码格式 |
|
||||
| Opus | 现代音频编解码器 |
|
||||
| ECDH | 椭圆曲线 Diffie-Hellman 密钥交换 |
|
||||
| AES | 高级加密标准 |
|
||||
| EAX | 认证加密模式 |
|
||||
| Hashcash | 工作量证明系统 |
|
||||
| VAD | 语音活动检测 |
|
||||
| PTT | 推按说话 |
|
||||
| TSDNS | TeamSpeak DNS 服务 |
|
||||
| SRV | DNS 服务记录 |
|
||||
|
||||
## 8. 参考文献
|
||||
|
||||
1. TeamSpeak 3 协议规范 (ts3protocol.md)
|
||||
2. tsdeclarations 项目文档
|
||||
3. tsclientlib 项目文档
|
||||
4. Qint 项目文档
|
||||
5. SimpleBot 项目文档
|
||||
6. ts3stats 项目文档
|
||||
- desktop-only target for now
|
||||
- `tsclientlib` is the required TeamSpeak integration layer
|
||||
- Podman should be used when host system libraries are missing for audio builds
|
||||
|
||||
Reference in New Issue
Block a user