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
34 KiB
34 KiB
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 密钥派生
密钥生成算法
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)
某些数据包使用固定的假密钥进行加密:
const FAKE_KEY: [u8; 16] = *b"c:\\windows\\syste";
const FAKE_NONCE: [u8; 16] = *b"m\\firewall32.cpl";
使用假加密的场景:
- 初始化期间的命令包
- 客户端的第一个 Ack 包
- 服务器的第一个命令包
4.4 共享密钥计算
旧协议 (<3.1)
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)
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 选择性重传
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,
}
重传策略
-
超时计算: 使用指数退避算法
timeout = rtt_estimate + 4 * rtt_deviation timeout = min(timeout * 2^retry_count, MAX_TIMEOUT) -
重传条件:
- 数据包超时未确认
- 收到重复的确认包
-
放弃条件:
- 超过 30 秒未确认
- 达到最大重试次数
5.3 拥塞控制
TeamSpeak 3 使用类似 TCP CUBIC 的拥塞控制算法:
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 流量控制
接收窗口
struct ReceiveWindow {
// 下一个期望的数据包 ID
next_expected: u16,
// 接收队列大小
queue_size: u16,
// 最大队列长度
max_queue_len: u16, // 200
}
发送窗口
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 关键命令
连接初始化
// 客户端初始化
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, // 最大客户端数
// ... 更多参数
}
频道操作
// 创建频道
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, // 永久频道
// ... 更多参数
}
客户端操作
// 发送消息
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] | 语音数据 |
组私语类型
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 语音流处理
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 (客户端 → 服务器)
struct Init0 {
version: u32, // 客户端版本时间戳
step: u8, // 步骤号: 0x00
timestamp: u32, // 当前时间戳
random0: [u8; 4], // 随机数 A0
reserved: [u8; 8], // 保留字节
}
Init1 (服务器 → 客户端)
struct Init1 {
step: u8, // 步骤号: 0x01
random1: [u8; 16], // 服务器随机数 A1
random0_r: [u8; 4], // A0 反转 (通常)
}
Init3 (服务器 → 客户端)
struct Init3 {
step: u8, // 步骤号: 0x03
x: [u8; 64], // RSA 底数 x
n: [u8; 64], // RSA 模数 n
level: u32, // 难度级别
random2: [u8; 100], // 服务器随机数 A2
}
Init4 (客户端 → 服务器)
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 许可证验证 (新协议)
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 计数器:
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 接收窗口
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 心跳配置
struct HeartbeatConfig {
// 客户端 Ping 间隔
client_ping_interval: Duration, // 通常 30 秒
// 服务器 Ping 间隔
server_ping_interval: Duration, // 通常 60 秒
// 超时时间
timeout: Duration, // 通常 120 秒
}
11. 错误处理
11.1 错误码
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 密钥缓存
struct KeyCache {
// 缓存每个 (类型, 方向, 代ID) 的密钥
cache: [[CachedKey; 2]; 8],
}
struct CachedKey {
generation_id: u32,
key: [u8; 16],
nonce: [u8; 16],
}
12.2 数据包批处理
struct PacketBatch {
// 批量发送的数据包
packets: Vec<OutPacket>,
// 批量大小
batch_size: usize,
// 批量超时
batch_timeout: Duration,
}
12.3 音频优化
struct AudioOptimization {
// 抖动缓冲大小
jitter_buffer_size: usize,
// 丢包隐藏
packet_loss_concealment: bool,
// 前向纠错 (FEC)
forward_error_correction: bool,
// 速度调整
speed_adjustment: bool,
}
13. 安全考虑
13.1 防 DoS 攻击
RSA 拼图机制:
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
防止身份伪造:
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 身份验证
fn verify_identity(
public_key: &PublicKey,
signature: &[u8],
data: &[u8],
) -> bool {
// 使用 P-256 曲线验证签名
public_key.verify(signature, data)
}
14. 协议版本兼容性
14.1 版本检测
fn detect_protocol_version(server_response: &str) -> ProtocolVersion {
if server_response.contains("initivexpand2") {
ProtocolVersion::New // ≥3.1
} else {
ProtocolVersion::Old // <3.1
}
}
14.2 兼容性处理
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)
// 核心类型
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 关键算法
// 压缩和分片
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
}