CI/CD / Build Frontend (push) Failing after 11s
CI/CD / Test (macos-latest) (push) Has been cancelled
CI/CD / Test (windows-latest) (push) Has been cancelled
CI/CD / Build Desktop (linux) (push) Has been cancelled
CI/CD / Build Desktop (macos) (push) Has been cancelled
CI/CD / Build Desktop (windows) (push) Has been cancelled
CI/CD / Release (push) Has been cancelled
CI/CD / Test (ubuntu-latest) (push) Failing after 2s
- tscore: Protocol implementation (packets, crypto, connection handshake) - tsaudio: Audio engine (capture, playback, codec, VAD, jitter buffer) - tsdb: SQLite database (identities, bookmarks, messages, settings) - shared: Core types and events - tauri-app: Tauri v2 desktop application with React frontend - docs: SRS, SAD, SDD documentation - CI/CD: GitHub Actions workflow - 32 unit tests passing
1166 lines
34 KiB
Markdown
1166 lines
34 KiB
Markdown
# TeamSpeak 3 协议栈分析
|
||
|
||
## 1. 协议栈层次结构
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ 应用层 (Application Layer) │
|
||
│ - 命令/通知 (Commands/Notifications) │
|
||
│ - 文本消息、频道管理、客户端管理 │
|
||
├─────────────────────────────────────────────────────────────┤
|
||
│ 消息层 (Message Layer) │
|
||
│ - 命令解析 (Command Parsing) │
|
||
│ - 参数编码/解码 (Parameter Encoding/Decoding) │
|
||
│ - BBCode 格式 │
|
||
├─────────────────────────────────────────────────────────────┤
|
||
│ 数据包层 (Packet Layer) │
|
||
│ - 数据包格式 (Packet Format) │
|
||
│ - 数据包类型 (Packet Types) │
|
||
│ - 分片/重组 (Fragmentation/Reassembly) │
|
||
│ - 压缩/解压 (Compression/Decompression) │
|
||
├─────────────────────────────────────────────────────────────┤
|
||
│ 加密层 (Encryption Layer) │
|
||
│ - AES-128-EAX 加密/解密 │
|
||
│ - ECDH 密钥交换 │
|
||
│ - 密钥派生 (Key Derivation) │
|
||
├─────────────────────────────────────────────────────────────┤
|
||
│ 可靠传输层 (Reliable Transport Layer) │
|
||
│ - 选择性重传 (Selective Repeat) │
|
||
│ - 拥塞控制 (Congestion Control) │
|
||
│ - 流量控制 (Flow Control) │
|
||
├─────────────────────────────────────────────────────────────┤
|
||
│ 传输层 (Transport Layer) │
|
||
│ - UDP 协议 │
|
||
│ - 最大包大小: 500 字节 │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
## 2. 数据包格式详解
|
||
|
||
### 2.1 数据包头结构
|
||
|
||
#### 客户端 → 服务器 (C2S)
|
||
```
|
||
0 1 2 3
|
||
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||
| MAC (8 bytes) |
|
||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||
| Packet ID | Client ID |
|
||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||
| PT | Data (≤487 bytes) |
|
||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||
```
|
||
|
||
#### 服务器 → 客户端 (S2C)
|
||
```
|
||
0 1 2 3
|
||
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||
| MAC (8 bytes) |
|
||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||
| Packet ID | PT | Data (≤489 bytes) |
|
||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||
```
|
||
|
||
**字段说明**:
|
||
|
||
| 字段 | 大小 | 类型 | 说明 |
|
||
|------|------|------|------|
|
||
| MAC | 8 字节 | [u8; 8] | EAX 消息认证码 |
|
||
| PId | 2 字节 | u16 | 数据包 ID (网络字节序) |
|
||
| CId | 2 字节 | u16 | 客户端 ID (仅 C2S) |
|
||
| PT | 1 字节 | u8 | 数据包类型 + 标志位 |
|
||
| Data | 变长 | [u8] | 数据包负载 |
|
||
|
||
### 2.2 数据包类型和标志位
|
||
|
||
#### PT 字段结构
|
||
```
|
||
7 6 5 4 3 2 1 0
|
||
+---+---+---+---+---+---+---+---+
|
||
|UE |CP |NP |FR | Type |
|
||
+---+---+---+---+---+---+---+---+
|
||
```
|
||
|
||
| 标志位 | 位 | 十六进制 | 说明 |
|
||
|--------|-----|----------|------|
|
||
| UE | 7 | 0x80 | 未加密 (Unencrypted) |
|
||
| CP | 6 | 0x40 | 已压缩 (Compressed) |
|
||
| NP | 5 | 0x20 | 新协议 (Newprotocol) |
|
||
| FR | 4 | 0x10 | 已分片 (Fragmented) |
|
||
| Type | 3-0 | 0x00-0x08 | 数据包类型 |
|
||
|
||
#### 数据包类型
|
||
| 类型 | 值 | 说明 | 方向 |
|
||
|------|-----|------|------|
|
||
| Voice | 0x00 | 语音数据 | 双向 |
|
||
| VoiceWhisper | 0x01 | 私语数据 | 双向 |
|
||
| Command | 0x02 | 命令数据 | 双向 |
|
||
| CommandLow | 0x03 | 低优先级命令 | 双向 |
|
||
| Ping | 0x04 | Ping 包 | C2S |
|
||
| Pong | 0x05 | Pong 包 | S2C |
|
||
| Ack | 0x06 | 确认包 | 双向 |
|
||
| AckLow | 0x07 | 低优先级确认 | 双向 |
|
||
| Init1 | 0x08 | 初始化包 | 双向 |
|
||
|
||
### 2.3 数据包类型特性
|
||
|
||
| 类型 | 需要确认 | 可重传 | 可加密 | 可分片 | 可压缩 |
|
||
|------|----------|--------|--------|--------|--------|
|
||
| Voice | ✗ | ✗ | 可选 | ✗ | ✗ |
|
||
| VoiceWhisper | ✗ | ✗ | 可选 | ✗ | ✗ |
|
||
| Command | ✓ (Ack) | ✓ | ✓ | ✓ | ✓ |
|
||
| CommandLow | ✓ (AckLow) | ✓ | ✓ | ✓ | ✓ |
|
||
| Ping | ✓ (Pong) | ✗ | ✗ | ✗ | ✗ |
|
||
| Pong | ✗ | ✗ | ✗ | ✗ | ✗ |
|
||
| Ack | ✗ | ✓ | ✓ | ✗ | ✗ |
|
||
| AckLow | ✗ | ✓ | ✓ | ✗ | ✗ |
|
||
| Init1 | ✓ (Init1) | ✓ | ✗ | ✗ | ✗ |
|
||
|
||
## 3. 数据包处理流程
|
||
|
||
### 3.1 发送流程
|
||
|
||
```
|
||
应用数据
|
||
↓
|
||
┌─────────────────────────────────────┐
|
||
│ 1. 命令序列化 │
|
||
│ - 参数编码 (key=value) │
|
||
│ - UTF-8 编码 │
|
||
│ - BBCode 格式化 │
|
||
└─────────────────────────────────────┘
|
||
↓
|
||
┌─────────────────────────────────────┐
|
||
│ 2. 压缩 (QuickLZ Level 1) │
|
||
│ - 仅 Command/CommandLow │
|
||
│ - 仅当压缩后更小时 │
|
||
└─────────────────────────────────────┘
|
||
↓
|
||
┌─────────────────────────────────────┐
|
||
│ 3. 分片 │
|
||
│ - 最大 500 字节 (含头) │
|
||
│ - 仅 Command/CommandLow │
|
||
│ - 首尾包设置 FR 标志 │
|
||
│ - 首包设置 CP 标志 │
|
||
└─────────────────────────────────────┘
|
||
↓
|
||
┌─────────────────────────────────────┐
|
||
│ 4. 加密 (AES-128-EAX) │
|
||
│ - 生成密钥和 Nonce │
|
||
│ - EAX 模式加密 │
|
||
│ - 生成 MAC │
|
||
└─────────────────────────────────────┘
|
||
↓
|
||
┌─────────────────────────────────────┐
|
||
│ 5. 分配 Packet ID │
|
||
│ - 每种类型独立计数器 │
|
||
│ - 溢出时增加 Generation ID │
|
||
└─────────────────────────────────────┘
|
||
↓
|
||
┌─────────────────────────────────────┐
|
||
│ 6. 发送 │
|
||
│ - UDP 发送 │
|
||
│ - 加入重传队列 (如需要) │
|
||
└─────────────────────────────────────┘
|
||
```
|
||
|
||
### 3.2 接收流程
|
||
|
||
```
|
||
UDP 数据包
|
||
↓
|
||
┌─────────────────────────────────────┐
|
||
│ 1. 验证 │
|
||
│ - 检查地址 │
|
||
│ - 检查客户端 ID │
|
||
│ - 检查接收窗口 │
|
||
└─────────────────────────────────────┘
|
||
↓
|
||
┌─────────────────────────────────────┐
|
||
│ 2. 解密 (AES-128-EAX) │
|
||
│ - 生成密钥和 Nonce │
|
||
│ - EAX 模式解密 │
|
||
│ - 验证 MAC │
|
||
└─────────────────────────────────────┘
|
||
↓
|
||
┌─────────────────────────────────────┐
|
||
│ 3. 重组 │
|
||
│ - 处理分片队列 │
|
||
│ - 按 Packet ID 排序 │
|
||
└─────────────────────────────────────┘
|
||
↓
|
||
┌─────────────────────────────────────┐
|
||
│ 4. 解压 (QuickLZ) │
|
||
│ - 仅当 CP 标志设置时 │
|
||
│ - 最大解压大小: 2MB │
|
||
└─────────────────────────────────────┘
|
||
↓
|
||
┌─────────────────────────────────────┐
|
||
│ 5. 命令解析 │
|
||
│ - UTF-8 解码 │
|
||
│ - 参数解析 (key=value) │
|
||
│ - 转义序列处理 │
|
||
└─────────────────────────────────────┘
|
||
↓
|
||
┌─────────────────────────────────────┐
|
||
│ 6. 发送确认 │
|
||
│ - Command → Ack │
|
||
│ - CommandLow → AckLow │
|
||
│ - Ping → Pong │
|
||
└─────────────────────────────────────┘
|
||
```
|
||
|
||
## 4. 加密机制详解
|
||
|
||
### 4.1 EAX 模式加密
|
||
|
||
TeamSpeak 3 使用 AES-128-EAX 模式进行加密,EAX 模式是一种认证加密模式,提供:
|
||
- **机密性**: AES-128-CTR 加密
|
||
- **完整性**: OMAC (One-key MAC) 消息认证
|
||
- **真实性**: 8 字节 MAC 标签
|
||
|
||
### 4.2 密钥派生
|
||
|
||
#### 密钥生成算法
|
||
```rust
|
||
fn create_key_nonce(
|
||
p_type: PacketType, // 数据包类型
|
||
c_id: Option<u16>, // 客户端 ID (方向)
|
||
p_id: u16, // 数据包 ID
|
||
generation_id: u32, // 代 ID
|
||
iv: &[u8; 64], // 共享 IV
|
||
) -> (Key, Nonce) {
|
||
// 构建临时数组
|
||
let mut temp = [0; 70];
|
||
temp[0] = if c_id.is_some() { 0x31 } else { 0x30 }; // 方向
|
||
temp[1] = p_type as u8; // 类型
|
||
temp[2..6] = generation_id.to_be_bytes(); // 代 ID
|
||
temp[6..] = iv; // 共享 IV
|
||
|
||
// SHA-256 哈希
|
||
let keynonce = sha256(&temp);
|
||
|
||
// 提取密钥和 Nonce
|
||
let key = keynonce[0..16];
|
||
let nonce = keynonce[16..32];
|
||
|
||
// 用 Packet ID 修改密钥
|
||
key[0] ^= (p_id >> 8) as u8;
|
||
key[1] ^= (p_id & 0xff) as u8;
|
||
|
||
(key, nonce)
|
||
}
|
||
```
|
||
|
||
#### 密钥缓存
|
||
为提高性能,可以缓存每个 `(PacketType, Direction, GenerationId)` 组合的密钥:
|
||
- 缓存大小: 8 种类型 × 2 种方向 = 16 个条目
|
||
- 仅在 Generation ID 变化时重新计算
|
||
|
||
### 4.3 假加密 (Fake Encryption)
|
||
|
||
某些数据包使用固定的假密钥进行加密:
|
||
```rust
|
||
const FAKE_KEY: [u8; 16] = *b"c:\\windows\\syste";
|
||
const FAKE_NONCE: [u8; 16] = *b"m\\firewall32.cpl";
|
||
```
|
||
|
||
**使用假加密的场景**:
|
||
1. 初始化期间的命令包
|
||
2. 客户端的第一个 Ack 包
|
||
3. 服务器的第一个命令包
|
||
|
||
### 4.4 共享密钥计算
|
||
|
||
#### 旧协议 (<3.1)
|
||
```rust
|
||
fn compute_shared_iv_mac_old(
|
||
alpha: &[u8; 10], // 客户端随机数
|
||
beta: &[u8; 10], // 服务器随机数
|
||
server_pubkey: &PublicKey, // 服务器公钥
|
||
client_privkey: &PrivateKey, // 客户端私钥
|
||
) -> (SharedIV, SharedMac) {
|
||
// ECDH 密钥交换
|
||
let shared_secret = ecdh(server_pubkey, client_privkey);
|
||
let x = shared_secret.x.to_bytes();
|
||
|
||
// 填充到 32 字节
|
||
let mut shared_data = [0; 32];
|
||
shared_data[32 - x.len()..].copy_from_slice(&x);
|
||
|
||
// SHA-1 哈希
|
||
let mut shared_iv = sha1(&shared_data);
|
||
|
||
// 与 alpha/beta 异或
|
||
shared_iv[0..10] ^= alpha;
|
||
shared_iv[10..20] ^= beta;
|
||
|
||
// 计算 MAC
|
||
let shared_mac = sha1(&shared_iv)[0..8];
|
||
|
||
(shared_iv, shared_mac)
|
||
}
|
||
```
|
||
|
||
#### 新协议 (≥3.1)
|
||
```rust
|
||
fn compute_shared_iv_mac_new(
|
||
alpha: &[u8; 10], // 客户端随机数
|
||
beta: &[u8; 54], // 服务器随机数 (更长)
|
||
licenses: &Licenses, // 许可证链
|
||
client_privkey: &Ed25519PrivateKey, // 客户端临时私钥
|
||
) -> (SharedIV, SharedMac) {
|
||
// 从许可证链派生服务器公钥
|
||
let server_pubkey = derive_key_from_licenses(licenses);
|
||
|
||
// Curve25519 密钥交换
|
||
let shared_secret = client_privkey.create_shared_secret(&server_pubkey);
|
||
|
||
// SHA-512 哈希
|
||
let mut shared_iv = sha512(&shared_secret);
|
||
|
||
// 与 alpha/beta 异或
|
||
shared_iv[0..10] ^= alpha;
|
||
shared_iv[10..64] ^= beta;
|
||
|
||
// 计算 MAC
|
||
let shared_mac = sha1(&shared_iv)[0..8];
|
||
|
||
(shared_iv, shared_mac)
|
||
}
|
||
```
|
||
|
||
## 5. 可靠传输机制
|
||
|
||
### 5.1 数据包确认
|
||
|
||
#### 确认机制
|
||
- **Command** 包需要 **Ack** 确认
|
||
- **CommandLow** 包需要 **AckLow** 确认
|
||
- **Ping** 包需要 **Pong** 确认
|
||
- **Init1** 包需要下一个 **Init1** 确认
|
||
|
||
#### 确认包格式
|
||
```
|
||
+--+--+
|
||
| PId |
|
||
+--+--+
|
||
```
|
||
- PId: 被确认的数据包 ID (2 字节)
|
||
|
||
### 5.2 选择性重传
|
||
|
||
```rust
|
||
struct Resender {
|
||
// 发送队列: 等待确认的数据包
|
||
send_queue: BTreeMap<PacketId, SentPacket>,
|
||
|
||
// 重传超时计算
|
||
rtt_estimate: Duration,
|
||
rtt_deviation: Duration,
|
||
|
||
// 拥塞窗口
|
||
congestion_window: u32,
|
||
}
|
||
|
||
struct SentPacket {
|
||
data: Vec<u8>,
|
||
send_time: Instant,
|
||
retry_count: u32,
|
||
}
|
||
```
|
||
|
||
#### 重传策略
|
||
1. **超时计算**: 使用指数退避算法
|
||
```
|
||
timeout = rtt_estimate + 4 * rtt_deviation
|
||
timeout = min(timeout * 2^retry_count, MAX_TIMEOUT)
|
||
```
|
||
|
||
2. **重传条件**:
|
||
- 数据包超时未确认
|
||
- 收到重复的确认包
|
||
|
||
3. **放弃条件**:
|
||
- 超过 30 秒未确认
|
||
- 达到最大重试次数
|
||
|
||
### 5.3 拥塞控制
|
||
|
||
TeamSpeak 3 使用类似 TCP CUBIC 的拥塞控制算法:
|
||
|
||
```rust
|
||
struct CongestionControl {
|
||
// 拥塞窗口大小 (数据包数量)
|
||
cwnd: u32,
|
||
|
||
// 慢启动阈值
|
||
ssthresh: u32,
|
||
|
||
// 丢包检测
|
||
loss_detector: LossDetector,
|
||
}
|
||
|
||
impl CongestionControl {
|
||
fn on_ack(&mut self, acked_bytes: u32) {
|
||
if self.cwnd < self.ssthresh {
|
||
// 慢启动阶段: 指数增长
|
||
self.cwnd += acked_bytes;
|
||
} else {
|
||
// 拥塞避免阶段: 线性增长
|
||
self.cwnd += acked_bytes / self.cwnd;
|
||
}
|
||
}
|
||
|
||
fn on_loss(&mut self) {
|
||
// 丢包检测: 减小拥塞窗口
|
||
self.ssthresh = self.cwnd / 2;
|
||
self.cwnd = self.ssthresh;
|
||
}
|
||
}
|
||
```
|
||
|
||
### 5.4 流量控制
|
||
|
||
#### 接收窗口
|
||
```rust
|
||
struct ReceiveWindow {
|
||
// 下一个期望的数据包 ID
|
||
next_expected: u16,
|
||
|
||
// 接收队列大小
|
||
queue_size: u16,
|
||
|
||
// 最大队列长度
|
||
max_queue_len: u16, // 200
|
||
}
|
||
```
|
||
|
||
#### 发送窗口
|
||
```rust
|
||
struct SendWindow {
|
||
// 当前发送的数据包 ID
|
||
current_id: u16,
|
||
|
||
// 拥塞窗口大小
|
||
congestion_window: u32,
|
||
|
||
// 已发送未确认的数据包数量
|
||
in_flight: u32,
|
||
}
|
||
```
|
||
|
||
## 6. 命令协议详解
|
||
|
||
### 6.1 命令格式
|
||
|
||
命令使用类似 TeamSpeak 3 Query 的文本格式,UTF-8 编码:
|
||
|
||
```
|
||
command_name key1=value1 key2=value2 ...
|
||
```
|
||
|
||
#### 转义序列
|
||
| 原始字符 | 转义序列 | 说明 |
|
||
|----------|----------|------|
|
||
| `\` | `\\` | 反斜杠 |
|
||
| ` ` | `\s` | 空格 |
|
||
| `|` | `\p` | 管道符 |
|
||
| `/` | `\/` | 斜杠 |
|
||
| `\n` | `\n` | 换行符 |
|
||
|
||
### 6.2 命令类型
|
||
|
||
#### 请求-响应命令
|
||
```
|
||
客户端发送:
|
||
clientinit client_nickname=MyName client_version=3.0.19.3 ...
|
||
|
||
服务器响应:
|
||
initserver server_name=MyServer server_platform=Linux ...
|
||
```
|
||
|
||
#### 通知命令
|
||
```
|
||
服务器主动推送:
|
||
notifycliententerview cfid=1 ctid=0 reasonid=0 clid=1 client_database_id=1 ...
|
||
```
|
||
|
||
#### 列表命令
|
||
```
|
||
客户端请求:
|
||
channellist -topic -flags -limits
|
||
|
||
服务器响应 (多行):
|
||
cid=1 cpid=0 channel_name=Default\sChannel ...
|
||
cid=2 cpid=1 channel_name=Sub\sChannel ...
|
||
channellistfinished
|
||
```
|
||
|
||
### 6.3 关键命令
|
||
|
||
#### 连接初始化
|
||
```rust
|
||
// 客户端初始化
|
||
clientinit {
|
||
client_nickname: str, // 昵称
|
||
client_version: str, // 版本
|
||
client_platform: str, // 平台
|
||
client_input_hardware: bool, // 输入设备
|
||
client_output_hardware: bool, // 输出设备
|
||
client_default_channel: str, // 默认频道
|
||
client_default_channel_password: str, // 频道密码
|
||
client_server_password: str, // 服务器密码
|
||
client_meta_data: str, // 元数据
|
||
client_version_sign: str, // 版本签名
|
||
client_key_offset: u64, // Hash Cash 偏移
|
||
client_nickname_phonetic: str, // 语音昵称
|
||
client_default_token: str, // 权限令牌
|
||
hwid: str, // 硬件 ID
|
||
}
|
||
|
||
// 服务器初始化响应
|
||
initserver {
|
||
server_name: str, // 服务器名称
|
||
server_platform: str, // 平台
|
||
server_version: str, // 版本
|
||
server_maxclients: u16, // 最大客户端数
|
||
// ... 更多参数
|
||
}
|
||
```
|
||
|
||
#### 频道操作
|
||
```rust
|
||
// 创建频道
|
||
channelcreate {
|
||
channel_name: str, // 频道名称
|
||
channel_topic: str, // 主题
|
||
channel_codec: Codec, // 编解码器
|
||
channel_codec_quality: u8, // 质量
|
||
channel_maxclients: i32, // 最大客户端数
|
||
channel_flag_permanent: bool, // 永久频道
|
||
channel_password: str, // 密码
|
||
// ... 更多参数
|
||
}
|
||
|
||
// 频道列表
|
||
channellist {
|
||
cid: ChannelId, // 频道 ID
|
||
cpid: ChannelId, // 父频道 ID
|
||
channel_name: str, // 频道名称
|
||
channel_topic: str, // 主题
|
||
channel_codec: Codec, // 编解码器
|
||
channel_maxclients: i32, // 最大客户端数
|
||
channel_flag_permanent: bool, // 永久频道
|
||
// ... 更多参数
|
||
}
|
||
```
|
||
|
||
#### 客户端操作
|
||
```rust
|
||
// 发送消息
|
||
sendtextmessage {
|
||
targetmode: TextMessageTargetMode, // 目标模式
|
||
target: ClientId, // 目标客户端
|
||
msg: str, // 消息内容
|
||
}
|
||
|
||
// 客户端进入视图
|
||
notifycliententerview {
|
||
cfid: ChannelId, // 来源频道
|
||
ctid: ChannelId, // 目标频道
|
||
reasonid: Reason, // 原因
|
||
clid: ClientId, // 客户端 ID
|
||
client_database_id: ClientDbId, // 数据库 ID
|
||
client_nickname: str, // 昵称
|
||
client_unique_identifier: Uid, // 唯一标识符
|
||
client_type: ClientType, // 客户端类型
|
||
// ... 更多参数
|
||
}
|
||
```
|
||
|
||
## 7. 语音传输协议
|
||
|
||
### 7.1 语音包格式
|
||
|
||
#### 客户端 → 服务器 (C2S)
|
||
```
|
||
+--+--+--+---------//---------+
|
||
| VId |C | Data |
|
||
+--+--+--+---------//---------+
|
||
```
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| VId | u16 | 语音包 ID |
|
||
| C | u8 | 编解码器类型 |
|
||
| Data | [u8] | 语音数据 |
|
||
|
||
#### 服务器 → 客户端 (S2C)
|
||
```
|
||
+--+--+--+--+--+---------//---------+
|
||
| VId | CId |C | Data |
|
||
+--+--+--+--+--+---------//---------+
|
||
```
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| VId | u16 | 语音包 ID |
|
||
| CId | u16 | 说话客户端 ID |
|
||
| C | u8 | 编解码器类型 |
|
||
| Data | [u8] | 语音数据 |
|
||
|
||
### 7.2 私语包格式
|
||
|
||
#### 直接目标模式 (NP=0)
|
||
```
|
||
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+---------//---------+
|
||
| VId |C |N |M | U* | T* | Data |
|
||
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+---------//---------+
|
||
```
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| VId | u16 | 语音包 ID |
|
||
| C | u8 | 编解码器类型 |
|
||
| N | u8 | 目标频道数 |
|
||
| M | u8 | 目标客户端数 |
|
||
| U | [u64] | 目标频道 ID (N 个) |
|
||
| T | [u16] | 目标客户端 ID (M 个) |
|
||
| Data | [u8] | 语音数据 |
|
||
|
||
#### 组目标模式 (NP=1)
|
||
```
|
||
+--+--+--+--+--+--+--+--+--+--+--+--+--+---------//---------+
|
||
| VId |C |TY|TA| U | Data |
|
||
+--+--+--+--+--+--+--+--+--+--+--+--+--+---------//---------+
|
||
```
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| VId | u16 | 语音包 ID |
|
||
| C | u8 | 编解码器类型 |
|
||
| TY | u8 | 组私语类型 |
|
||
| TA | u8 | 组私语目标 |
|
||
| U | u64 | 目标频道/组 ID |
|
||
| Data | [u8] | 语音数据 |
|
||
|
||
#### 组私语类型
|
||
```rust
|
||
enum GroupWhisperType : u8 {
|
||
ServerGroup = 0, // 服务器组
|
||
ChannelGroup = 1, // 频道组
|
||
ChannelCommander = 2, // 频道指挥官
|
||
AllClients = 3, // 所有客户端
|
||
}
|
||
|
||
enum GroupWhisperTarget : u8 {
|
||
AllChannels = 0, // 所有频道
|
||
CurrentChannel = 1, // 当前频道
|
||
ParentChannel = 2, // 父频道
|
||
AllParentChannel = 3, // 所有父频道
|
||
ChannelFamily = 4, // 频道家族
|
||
CompleteChannelFamily = 5, // 完整频道家族
|
||
Subchannels = 6, // 子频道
|
||
}
|
||
```
|
||
|
||
### 7.3 编解码器
|
||
|
||
| 编解码器 | 值 | 采样率 | 声道 | 说明 |
|
||
|----------|-----|--------|------|------|
|
||
| SpeexNarrowband | 0 | 8 kHz | 单声道 | 窄带语音 |
|
||
| SpeexWideband | 1 | 16 kHz | 单声道 | 宽带语音 |
|
||
| SpeexUltrawideband | 2 | 32 kHz | 单声道 | 超宽带语音 |
|
||
| CeltMono | 3 | 48 kHz | 单声道 | CELT 编码 |
|
||
| OpusVoice | 4 | 48 kHz | 单声道 | Opus 语音优化 |
|
||
| OpusMusic | 5 | 48 kHz | 立体声 | Opus 音乐优化 |
|
||
|
||
### 7.4 语音流处理
|
||
|
||
```rust
|
||
struct VoiceStream {
|
||
// 语音包 ID
|
||
packet_id: u16,
|
||
|
||
// 编解码器
|
||
codec: Codec,
|
||
|
||
// 解码器
|
||
decoder: OpusDecoder,
|
||
|
||
// 抖动缓冲
|
||
jitter_buffer: JitterBuffer,
|
||
|
||
// 丢包隐藏
|
||
packet_loss_concealment: PlcState,
|
||
}
|
||
|
||
impl VoiceStream {
|
||
fn process_packet(&mut self, packet: &InAudio) -> Vec<f32> {
|
||
// 1. 检查丢包
|
||
let lost_packets = self.check_packet_loss(packet.id);
|
||
|
||
// 2. 丢包隐藏
|
||
if lost_packets > 0 {
|
||
self.conceal_packet_loss(lost_packets);
|
||
}
|
||
|
||
// 3. 解码音频
|
||
let samples = self.decoder.decode(&packet.data);
|
||
|
||
// 4. 添加到抖动缓冲
|
||
self.jitter_buffer.push(samples);
|
||
|
||
// 5. 获取输出
|
||
self.jitter_buffer.get()
|
||
}
|
||
}
|
||
```
|
||
|
||
## 8. 初始化握手详解
|
||
|
||
### 8.1 低级握手 (Init1)
|
||
|
||
```
|
||
客户端 服务器
|
||
| |
|
||
|──── Init0 (version, timestamp, A0) ────>|
|
||
| |
|
||
|<─── Init1 (A1, A0r) ────────────────────|
|
||
| |
|
||
|──── Init2 (version, A1, A0r) ──────────>|
|
||
| |
|
||
|<─── Init3 (x, n, level, A2) ────────────|
|
||
| |
|
||
|──── Init4 (version, x, n, level, A2, |
|
||
| y, clientinitiv) ────────────>|
|
||
| |
|
||
```
|
||
|
||
#### Init0 (客户端 → 服务器)
|
||
```rust
|
||
struct Init0 {
|
||
version: u32, // 客户端版本时间戳
|
||
step: u8, // 步骤号: 0x00
|
||
timestamp: u32, // 当前时间戳
|
||
random0: [u8; 4], // 随机数 A0
|
||
reserved: [u8; 8], // 保留字节
|
||
}
|
||
```
|
||
|
||
#### Init1 (服务器 → 客户端)
|
||
```rust
|
||
struct Init1 {
|
||
step: u8, // 步骤号: 0x01
|
||
random1: [u8; 16], // 服务器随机数 A1
|
||
random0_r: [u8; 4], // A0 反转 (通常)
|
||
}
|
||
```
|
||
|
||
#### Init3 (服务器 → 客户端)
|
||
```rust
|
||
struct Init3 {
|
||
step: u8, // 步骤号: 0x03
|
||
x: [u8; 64], // RSA 底数 x
|
||
n: [u8; 64], // RSA 模数 n
|
||
level: u32, // 难度级别
|
||
random2: [u8; 100], // 服务器随机数 A2
|
||
}
|
||
```
|
||
|
||
#### Init4 (客户端 → 服务器)
|
||
```rust
|
||
struct Init4 {
|
||
version: u32, // 客户端版本时间戳
|
||
step: u8, // 步骤号: 0x04
|
||
x: [u8; 64], // 接收的 x
|
||
n: [u8; 64], // 接收的 n
|
||
level: u32, // 接收的 level
|
||
random2: [u8; 100], // 接收的 A2
|
||
y: [u8; 64], // y = x^(2^level) mod n
|
||
command: Command, // clientinitiv 命令
|
||
}
|
||
```
|
||
|
||
### 8.2 高级握手
|
||
|
||
#### clientinitiv (客户端 → 服务器)
|
||
```
|
||
clientinitiv alpha={alpha} omega={omega} ot={ot} ip={ip}
|
||
```
|
||
|
||
- **alpha**: 10 随机字节的 base64 编码
|
||
- **omega**: ASN.1-DER 编码的 ECDH 公钥
|
||
- **ot**: 固定为 1
|
||
- **ip**: 服务器 IP 地址
|
||
|
||
#### initivexpand/initivexpand2 (服务器 → 客户端)
|
||
|
||
**旧协议 (<3.1)**:
|
||
```
|
||
initivexpand alpha={alpha} beta={beta} omega={omega}
|
||
```
|
||
|
||
**新协议 (≥3.1)**:
|
||
```
|
||
initivexpand2 l={l} beta={beta} omega={omega} ot={ot} proof={proof} tvd={tvd}
|
||
```
|
||
|
||
#### clientek (客户端 → 服务器)
|
||
```
|
||
clientek ek={ek} proof={proof}
|
||
```
|
||
|
||
- **ek**: 客户端临时公钥 (base64)
|
||
- **proof**: 签名的客户端公钥 + beta
|
||
|
||
### 8.3 许可证验证 (新协议)
|
||
|
||
```rust
|
||
struct License {
|
||
version: u8, // 版本: 0x01
|
||
blocks: Vec<LicenseBlock>, // 许可证块
|
||
}
|
||
|
||
struct LicenseBlock {
|
||
key_type: u8, // 密钥类型: 0x00
|
||
public_key: [u8; 32], // 块公钥
|
||
block_type: LicenseBlockType, // 块类型
|
||
not_before: u32, // 有效期开始
|
||
not_after: u32, // 有效期结束
|
||
content: Vec<u8>, // 内容
|
||
}
|
||
|
||
enum LicenseBlockType {
|
||
Intermediate = 0x00, // 中间证书
|
||
Website = 0x01, // 网站证书
|
||
Code = 0x03, // 代码证书
|
||
Server = 0x02, // 服务器证书
|
||
TS5Server = 0x08, // TS5 服务器证书
|
||
Ephemeral = 0x32, // 临时证书
|
||
}
|
||
```
|
||
|
||
## 9. 数据包 ID 管理
|
||
|
||
### 9.1 数据包 ID 计数器
|
||
|
||
每种数据包类型和方向有独立的 ID 计数器:
|
||
|
||
```rust
|
||
struct PacketIdManager {
|
||
// 出站计数器 (客户端有 9 个)
|
||
outgoing: [PartialPacketId; 9],
|
||
|
||
// 入站计数器
|
||
incoming: [PartialPacketId; 9],
|
||
}
|
||
|
||
struct PartialPacketId {
|
||
generation_id: u32, // 代 ID
|
||
packet_id: u16, // 数据包 ID
|
||
}
|
||
```
|
||
|
||
### 9.2 代 ID (Generation ID)
|
||
|
||
数据包 ID 存储为 u16,范围 0-65535:
|
||
- 当 ID 从 65535 溢出到 0 时,代 ID 增加 1
|
||
- 代 ID 仅用于加密参数生成
|
||
- 新的代 ID 立即应用于溢出的数据包
|
||
|
||
### 9.3 接收窗口
|
||
|
||
```rust
|
||
struct ReceiveWindow {
|
||
next_expected: u16, // 下一个期望的 ID
|
||
max_queue_len: u16, // 最大队列长度 (200)
|
||
queue: Vec<Vec<u8>>, // 乱序数据包队列
|
||
}
|
||
|
||
impl ReceiveWindow {
|
||
fn in_window(&self, id: u16) -> bool {
|
||
let diff = id.wrapping_sub(self.next_expected);
|
||
diff < self.max_queue_len
|
||
}
|
||
}
|
||
```
|
||
|
||
## 10. 心跳机制
|
||
|
||
### 10.1 Ping/Pong
|
||
|
||
```
|
||
客户端 服务器
|
||
| |
|
||
|──── Ping (空) ─────────────────────────>|
|
||
| |
|
||
|<─── Pong (PId) ─────────────────────────|
|
||
| |
|
||
|<─── Ping (空) ──────────────────────────|
|
||
| |
|
||
|──── Pong (PId) ────────────────────────>|
|
||
```
|
||
|
||
### 10.2 心跳配置
|
||
|
||
```rust
|
||
struct HeartbeatConfig {
|
||
// 客户端 Ping 间隔
|
||
client_ping_interval: Duration, // 通常 30 秒
|
||
|
||
// 服务器 Ping 间隔
|
||
server_ping_interval: Duration, // 通常 60 秒
|
||
|
||
// 超时时间
|
||
timeout: Duration, // 通常 120 秒
|
||
}
|
||
```
|
||
|
||
## 11. 错误处理
|
||
|
||
### 11.1 错误码
|
||
|
||
```rust
|
||
enum Ts3ErrorCode {
|
||
// 成功
|
||
Ok = 0x0000,
|
||
|
||
// 客户端错误 (0x0200-0x020F)
|
||
ClientInvalidId = 0x0200,
|
||
ClientNicknameInuse = 0x0201,
|
||
ClientInvalidPassword = 0x0208,
|
||
|
||
// 频道错误 (0x0300-0x030F)
|
||
ChannelInvalidId = 0x0300,
|
||
ChannelNameInuse = 0x0303,
|
||
|
||
// 服务器错误 (0x0400-0x040D)
|
||
ServerInvalidId = 0x0400,
|
||
ServerMaxclientsReached = 0x0403,
|
||
|
||
// 数据库错误 (0x0500-0x0505)
|
||
Database = 0x0500,
|
||
|
||
// 参数错误 (0x0600-0x0607)
|
||
ParameterQuote = 0x0600,
|
||
ParameterInvalidCount = 0x0601,
|
||
|
||
// 连接错误 (0x0700-0x070C)
|
||
ConnectionLost = 0x0701,
|
||
NotConnected = 0x0702,
|
||
}
|
||
```
|
||
|
||
### 11.2 错误响应格式
|
||
|
||
```
|
||
error id=0 msg=ok
|
||
error id=256 msg=invalid\sclientID
|
||
```
|
||
|
||
## 12. 性能优化
|
||
|
||
### 12.1 密钥缓存
|
||
|
||
```rust
|
||
struct KeyCache {
|
||
// 缓存每个 (类型, 方向, 代ID) 的密钥
|
||
cache: [[CachedKey; 2]; 8],
|
||
}
|
||
|
||
struct CachedKey {
|
||
generation_id: u32,
|
||
key: [u8; 16],
|
||
nonce: [u8; 16],
|
||
}
|
||
```
|
||
|
||
### 12.2 数据包批处理
|
||
|
||
```rust
|
||
struct PacketBatch {
|
||
// 批量发送的数据包
|
||
packets: Vec<OutPacket>,
|
||
|
||
// 批量大小
|
||
batch_size: usize,
|
||
|
||
// 批量超时
|
||
batch_timeout: Duration,
|
||
}
|
||
```
|
||
|
||
### 12.3 音频优化
|
||
|
||
```rust
|
||
struct AudioOptimization {
|
||
// 抖动缓冲大小
|
||
jitter_buffer_size: usize,
|
||
|
||
// 丢包隐藏
|
||
packet_loss_concealment: bool,
|
||
|
||
// 前向纠错 (FEC)
|
||
forward_error_correction: bool,
|
||
|
||
// 速度调整
|
||
speed_adjustment: bool,
|
||
}
|
||
```
|
||
|
||
## 13. 安全考虑
|
||
|
||
### 13.1 防 DoS 攻击
|
||
|
||
RSA 拼图机制:
|
||
```rust
|
||
fn solve_rsa_puzzle(x: &[u8; 64], n: &[u8; 64], level: u32) -> [u8; 64] {
|
||
let x = BigUint::from_bytes_be(x);
|
||
let n = BigUint::from_bytes_be(n);
|
||
|
||
// y = x^(2^level) mod n
|
||
let mut y = x.clone();
|
||
for _ in 0..(1 << level) {
|
||
y = y.pow(2) % &n;
|
||
}
|
||
|
||
biguint_to_array(&y)
|
||
}
|
||
```
|
||
|
||
### 13.2 Hash Cash
|
||
|
||
防止身份伪造:
|
||
```rust
|
||
fn get_hash_cash_level(omega: &str, offset: u64) -> u8 {
|
||
let data = sha1(format!("{}{}", omega, offset));
|
||
let mut level = 0;
|
||
|
||
for byte in data {
|
||
if byte == 0 {
|
||
level += 8;
|
||
} else {
|
||
level += byte.trailing_zeros() as u8;
|
||
break;
|
||
}
|
||
}
|
||
|
||
level
|
||
}
|
||
```
|
||
|
||
### 13.3 身份验证
|
||
|
||
```rust
|
||
fn verify_identity(
|
||
public_key: &PublicKey,
|
||
signature: &[u8],
|
||
data: &[u8],
|
||
) -> bool {
|
||
// 使用 P-256 曲线验证签名
|
||
public_key.verify(signature, data)
|
||
}
|
||
```
|
||
|
||
## 14. 协议版本兼容性
|
||
|
||
### 14.1 版本检测
|
||
|
||
```rust
|
||
fn detect_protocol_version(server_response: &str) -> ProtocolVersion {
|
||
if server_response.contains("initivexpand2") {
|
||
ProtocolVersion::New // ≥3.1
|
||
} else {
|
||
ProtocolVersion::Old // <3.1
|
||
}
|
||
}
|
||
```
|
||
|
||
### 14.2 兼容性处理
|
||
|
||
```rust
|
||
struct ProtocolHandler {
|
||
version: ProtocolVersion,
|
||
|
||
// 根据版本选择不同的处理逻辑
|
||
fn handle_init(&self, packet: &InPacket) -> Result<()> {
|
||
match self.version {
|
||
ProtocolVersion::Old => self.handle_init_old(packet),
|
||
ProtocolVersion::New => self.handle_init_new(packet),
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
## 15. 实现参考
|
||
|
||
### 15.1 Rust 实现 (tsproto)
|
||
|
||
```rust
|
||
// 核心类型
|
||
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>,
|
||
}
|
||
|
||
// 加密参数
|
||
pub struct ConnectedParams {
|
||
pub shared_iv: [u8; 64],
|
||
pub shared_mac: [u8; 8],
|
||
pub key_cache: [[CachedKey; 2]; 8],
|
||
pub c_id: u16,
|
||
pub voice_encryption: bool,
|
||
}
|
||
|
||
// 数据包编解码器
|
||
pub struct PacketCodec {
|
||
pub outgoing_p_ids: [PartialPacketId; 8],
|
||
pub incoming_p_ids: [PartialPacketId; 8],
|
||
pub receive_queue: [Vec<Vec<u8>>; 2],
|
||
pub fragmented_queue: [Option<Vec<u8>>; 2],
|
||
}
|
||
```
|
||
|
||
### 15.2 关键算法
|
||
|
||
```rust
|
||
// 压缩和分片
|
||
pub fn compress_and_split(is_client: bool, packet: OutPacket) -> Vec<OutPacket> {
|
||
// 1. 检查是否需要压缩
|
||
// 2. QuickLZ 压缩
|
||
// 3. 分片 (最大 500 字节)
|
||
// 4. 设置标志位
|
||
}
|
||
|
||
// 加密
|
||
pub fn encrypt(
|
||
packet: &mut OutPacket,
|
||
generation_id: u32,
|
||
iv: &[u8; 64],
|
||
cache: &mut [[CachedKey; 2]; 8],
|
||
) -> Result<()> {
|
||
// 1. 生成密钥和 Nonce
|
||
// 2. AES-128-EAX 加密
|
||
// 3. 生成 MAC
|
||
}
|
||
|
||
// 解密
|
||
pub fn decrypt(
|
||
packet: &InPacket,
|
||
generation_id: u32,
|
||
iv: &[u8; 64],
|
||
cache: &mut [[CachedKey; 2]; 8],
|
||
) -> Result<Vec<u8>> {
|
||
// 1. 生成密钥和 Nonce
|
||
// 2. AES-128-EAX 解密
|
||
// 3. 验证 MAC
|
||
}
|
||
``` |