# 组件设计文档 (SDD) ## 1. 概述 本文档详细描述系统各组件的设计,包括功能、接口、数据结构和实现细节。 ## 2. 核心组件 ### 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); // 用户唯一标识 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 } 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, pub channels: HashMap, pub channel_groups: HashMap, pub server_groups: HashMap, } pub struct Server { /* 名称、版本、最大客户端数、加密模式等 */ } pub struct Channel { /* 名称、类型、编解码器、权限等 */ } pub struct Client { /* 名称、频道、静音状态、权限等 */ } ``` **事件系统**: ```rust pub enum Event { PropertyAdded { id: PropertyId, invoker: Option, extra: ExtraInfo }, PropertyChanged { id: PropertyId, old: PropertyValue, invoker: Option, extra: ExtraInfo }, PropertyRemoved { id: PropertyId, old: PropertyValue, invoker: Option, 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, pub address: SocketAddr, pub resender: Resender, pub codec: PacketCodec, pub udp_socket: Box, } ``` **连接握手流程**: ``` 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>, } enum ConnectionState { Connecting(BoxFuture<...>, bool), IdentityLevelIncreasing { recv, state }, Connected { con: ConnectedConnection, book: data::Connection }, } pub enum StreamItem { BookEvents(Vec), 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 { queues: HashMap, avg_buffer_samples: usize, } pub struct AudioQueue { decoder: Decoder, // Opus 解码器 packet_buffer: VecDeque, decoded_buffer: Vec, last_buffer_size_min: SlidingWindowMinimum, } ``` ### 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; sendMessage(target, message): void; switchChannel(channelId): void; startWhispering(whisperData): void; } ``` **数据状态镜像 (book.ts)**: ```typescript class Book { channels: Map; clients: Map; serverGroups: Map; channelGroups: Map; processEvent(event: InBookChangeMsg): void; } ``` **后端抽象层 (backend/)**: ```typescript interface IBackend { createNewConnection(returnCodes: ReturnCodeTracker): IBackendConnection; graphql(query: string, variables?: Record): Promise<{data: T}>; get_settings(): Promise>; set_settings(diff: Record): Promise; } interface IBackendConnection { id: string; connect(onMsg, onError, onClose): Promise; send(data: OutMsg): void; close(): void; fetch_image(req: IFileRequest): Promise; upload_bytes(req: IFileRequest, data: Blob): Promise; } ``` #### 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 ``` **WebSocket 处理器 (websocket.rs)**: ```rust struct Ws { state: QintState, id: String, addr: Addr, } 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>>, pub audio_data: Arc, pub hotkeys: HotkeyManager, pub settings: RwLock, pub database: Addr, pub graphql_schema: Schema, 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, bridge: Box, audio_to_ts: Addr, ts_to_audio: Addr, database: Addr, } impl Actor for QintConnection { type Context = Context; } impl Handler 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; } impl Handler for DbHandler { fn handle(&mut self, msg: GetIdentityAndServerMsg, ctx: &mut Self::Context) -> Self::Result { // 从数据库获取身份和服务器信息 } } impl Handler for DbHandler { fn handle(&mut self, msg: WriteMessageMsg, ctx: &mut Self::Context) { // 写入聊天消息到数据库 } } ``` **音频管道**: ```rust // AudioToTs - 麦克风采集和编码 pub struct AudioToTs { connections: Vec>, encoder: OpusEncoder, vad: VadDetector, loudness_meter: LoudnessMeter, } impl Handler for AudioToTs { fn handle(&mut self, msg: SendPacketMsg, ctx: &mut Self::Context) { // 编码并发送音频数据 } } // TsToAudio - 音频解码和播放 pub struct TsToAudio { queues: HashMap, output_device: AudioDevice, } impl Handler 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, list: Vec, should_reload: Cell, } ``` **动作系统**: ```rust pub struct ActionDefinition { contains: Option, regex: Option, chat: Option, response: Option, command: Option, shell: Option, } pub struct Action { matchers: Vec, reaction: Option, } pub enum Matcher { Regex(Regex), Mode(Option), } 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, name: String, disconnect_message: String, rate_limit: u8, prefix: String, actions: ActionFile, } pub struct ActionFile { include: Vec, on_message: Vec, } ``` ### 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` | 获取设置 | | `set_settings` | `diff: Record` | `()` | 更新设置 | ### 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, pub input_muted: bool, pub output_muted: bool, pub hotkeys: Vec, pub client_volumes: HashMap, pub theme: String, pub language: String, } ``` ### 6.2 连接配置 ```rust pub struct ConnectOptions { address: ServerAddress, local_address: Option, identity: Option, server: Option, name: String, version: Version, channel: Option, channel_password: Option, server_password: Option, default_token: Option, } ``` ## 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 { 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 验证