# Software Design Document (SDD) # ReTeamSpeak - Cross-Platform TeamSpeak Client **Version**: 1.0.0 **Date**: 2026-05-12 **Status**: Based on actual implementation --- ## 1. Detailed Design ### 1.1 `shared` Module Design #### 1.1.1 Core Type System ``` ┌─────────────────────────────────────────────────────────────┐ │ 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, ... } │ └─────────────────────────────────────────────────────────────┘ ``` #### 1.1.2 Event System Design ``` 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 } ``` --- ### 1.2 `tscore` Module Design #### 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 │ │ 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 │ │ 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::() / 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> (ring buffer) │ │ head: usize │ │ tail: usize │ │ size: usize │ │ capacity: usize │ ├─────────────────────────────────────────────────────────┤ │ push(frame) → Result<()> // Add frame │ │ pop() → Option // 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; fn get_identity(&self, id) -> DatabaseResult; fn get_all_identities(&self) -> DatabaseResult>; 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; fn get_bookmark(&self, id) -> DatabaseResult; fn get_all_bookmarks(&self) -> DatabaseResult>; 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; fn get_server_messages(&self, server_address, limit, offset) -> DatabaseResult>; fn mark_message_read(&self, id) -> DatabaseResult<()>; // Settings operations fn get_setting(&self, key) -> DatabaseResult>; fn set_setting(&self, key, value) -> DatabaseResult<()>; } ``` --- ### 1.5 `tauri-app` Design #### 1.5.1 Tauri Command Architecture ``` ┌─────────────────────────────────────────────────────────────┐ │ Frontend (React) │ │ ┌───────────────────────────────────────────────────────┐ │ │ │ invoke("get_bookmarks") → Promise │ │ │ │ invoke("connect", {addr, port, nick, pass}) │ │ │ │ invoke("send_message", {target, message}) │ │ │ └───────────────────────────────────────────────────────┘ │ └───────────────────────────┬─────────────────────────────────┘ │ Tauri IPC ▼ ┌─────────────────────────────────────────────────────────────┐ │ Tauri Shell (Rust) │ │ ┌───────────────────────────────────────────────────────┐ │ │ │ #[tauri::command] │ │ │ │ async fn get_bookmarks(state: State) │ │ │ │ -> Result, 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, │ │ │ │ } │ │ │ └───────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ ``` #### 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; create_identity(name: string): Promise; delete_identity(id: string): Promise; // Bookmark management get_bookmarks(): Promise; create_bookmark(name: string, address: string, port: number, nickname?: string): Promise; delete_bookmark(id: string): Promise; // Connection connect(address: string, port: number, nickname: string, password?: string): Promise; disconnect(): Promise; // Messaging send_message(target: string, message: string): Promise; get_messages(server_address: string, limit: number, offset: number): Promise; } 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>; fn decode(&self, data: &[u8]) -> Result; } // Audio layer pub trait AudioCapture { async fn start(&mut self) -> AudioResult<()>; async fn stop(&mut self) -> AudioResult<()>; async fn capture(&mut self) -> AudioResult; } 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; fn get(&self, id: &str) -> DatabaseResult; fn list(&self) -> DatabaseResult>; 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)