package client import ( "encoding/binary" "fmt" "log" "sync" "time" "go-ts/pkg/protocol" "go-ts/pkg/transport" "gopkg.in/hraban/opus.v2" ) type Channel struct { ID uint64 ParentID uint64 Name string Order uint64 } // EventHandler is called when events occur type EventHandler func(eventType string, data map[string]any) type Client struct { Conn *transport.TS3Conn Handshake *HandshakeState Nickname string ClientID uint16 // Counters PacketIDCounterC2S uint16 // Commands (Type 0x02) VoicePacketID uint16 // Voice (Type 0x00) PingPacketID uint16 // Type 0x04 PongPacketID uint16 // Type 0x05 AckPacketID uint16 // Type 0x06 // State Connected bool ServerName string // Fragment reassembly (packet queue like ts3j) CommandQueue map[uint16]*protocol.Packet // Packets waiting for reassembly ExpectedCommandPID uint16 // Next expected packet ID FragmentState bool // Toggle: true = collecting, false = ready // Server Data Channels map[uint64]*Channel // Audio VoiceDecoders map[uint16]*opus.Decoder // Map VID (sender ID) to decoder VoiceEncoder *opus.Encoder // Encoder for outgoing audio VoiceEncoderMu sync.Mutex // Protects VoiceEncoder // Event handler for public API eventHandler EventHandler // Done channel to signal shutdown done chan struct{} } func NewClient(nickname string) *Client { return &Client{ Nickname: nickname, PacketIDCounterC2S: 1, VoicePacketID: 1, Channels: make(map[uint64]*Channel), VoiceDecoders: make(map[uint16]*opus.Decoder), CommandQueue: make(map[uint16]*protocol.Packet), ExpectedCommandPID: 0, done: make(chan struct{}), } } // SetEventHandler sets the callback for events func (c *Client) SetEventHandler(handler EventHandler) { c.eventHandler = handler } // emitEvent sends an event to the handler if set func (c *Client) emitEvent(eventType string, data map[string]any) { if c.eventHandler != nil { c.eventHandler(eventType, data) } } // Stop signals the client to stop its loops func (c *Client) Stop() { select { case <-c.done: // Already closed default: close(c.done) } } func (c *Client) Connect(address string) error { conn, err := transport.NewTS3Conn(address) if err != nil { return err } c.Conn = conn log.Printf("Connected to UDP. Starting Handshake...") // Initialize Handshake State hs, err := NewHandshakeState(c.Conn) if err != nil { return err } c.Handshake = hs // Improve Identity Security Level to 8 (Standard Requirement) c.Handshake.ImproveSecurityLevel(8) // Send Init1 if err := c.Handshake.SendPacket0(); err != nil { return err } // Listen Loop pktChan := c.Conn.PacketChan() ticker := time.NewTicker(3 * time.Second) defer ticker.Stop() for { select { case <-c.done: log.Println("Client loop stopped") return nil case pkt := <-pktChan: if pkt == nil { // Channel closed return nil } if err := c.handlePacket(pkt); err != nil { log.Printf("Error handling packet: %v", err) } case <-ticker.C: if !c.Connected { continue // Don't send pings if not connected yet } // Send KeepAlive Ping (Encrypted, No NewProtocol) if err := c.sendPing(); err != nil { log.Printf("Error sending Ping: %v", err) } } } } // sendPing sends an encrypted Ping packet WITHOUT the NewProtocol flag func (c *Client) sendPing() error { pType := protocol.PacketTypePing pkt := protocol.NewPacket(pType, nil) c.PingPacketID++ pkt.Header.PacketID = c.PingPacketID pkt.Header.ClientID = c.ClientID // Note: We do NOT set PacketFlagNewProtocol for Pings // Encryption key := protocol.HandshakeKey[:] nonce := protocol.HandshakeNonce[:] if c.Handshake != nil && c.Handshake.Step >= 6 && len(c.Handshake.SharedIV) > 0 { crypto := &protocol.CryptoState{ SharedIV: c.Handshake.SharedIV, SharedMac: c.Handshake.SharedMac, GenerationID: 0, } keyArr, nonceArr := crypto.GenerateKeyNonce(&pkt.Header, true) // Client->Server=true key = keyArr nonce = nonceArr } // Meta for Client->Server: PID(2) + CID(2) + PT(1) = 5 bytes meta := make([]byte, 5) binary.BigEndian.PutUint16(meta[0:2], pkt.Header.PacketID) binary.BigEndian.PutUint16(meta[2:4], pkt.Header.ClientID) meta[4] = pkt.Header.Type encData, mac, err := protocol.EncryptEAX(key, nonce, meta, pkt.Data) if err != nil { return fmt.Errorf("encryption failed: %w", err) } pkt.Data = encData copy(pkt.Header.MAC[:], mac) log.Printf("Sending proper Encrypted Ping (PID=%d)", pkt.Header.PacketID) return c.Conn.SendPacket(pkt) }