Files
go-ts/internal/client/client.go

158 lines
3.4 KiB
Go
Raw Normal View History

2026-01-15 16:49:16 +01:00
package client
import (
"log"
"time"
"go-ts/pkg/protocol"
"go-ts/pkg/transport"
"gopkg.in/hraban/opus.v2"
2026-01-15 16:49:16 +01:00
)
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)
2026-01-15 16:49:16 +01:00
type Client struct {
Conn *transport.TS3Conn
Handshake *HandshakeState
Nickname string
ClientID uint16
// Counters
PacketIDCounterC2S uint16
VoicePacketID uint16
2026-01-15 16:49:16 +01:00
// State
Connected bool
ServerName string
2026-01-15 16:49:16 +01:00
2026-01-15 17:09:32 +01:00
// Fragment reassembly
FragmentBuffer []byte
FragmentStartPktID uint16
FragmentCompressed bool
Fragmenting bool
2026-01-15 16:49:16 +01:00
// 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
// Event handler for public API
eventHandler EventHandler
// Done channel to signal shutdown
done chan struct{}
2026-01-15 16:49:16 +01:00
}
func NewClient(nickname string) *Client {
return &Client{
Nickname: nickname,
PacketIDCounterC2S: 1,
VoicePacketID: 1,
2026-01-15 16:49:16 +01:00
Channels: make(map[uint64]*Channel),
VoiceDecoders: make(map[uint16]*opus.Decoder),
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)
2026-01-15 16:49:16 +01:00
}
}
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
2026-01-15 16:49:16 +01:00
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
2026-01-15 16:49:16 +01:00
if err := c.Handshake.SendPacket0(); err != nil {
return err
}
// Listen Loop
pktChan := c.Conn.PacketChan()
2026-01-15 16:49:16 +01:00
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
}
2026-01-15 16:49:16 +01:00
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
}
2026-01-15 16:49:16 +01:00
ping := protocol.NewPacket(protocol.PacketTypePing, nil)
c.PacketIDCounterC2S++
2026-01-15 16:49:16 +01:00
ping.Header.PacketID = c.PacketIDCounterC2S
ping.Header.ClientID = c.ClientID
// Must NOT have NewProtocol (0x20) flag for Pings/Pongs
ping.Header.Type = uint8(protocol.PacketTypePing) | protocol.PacketFlagUnencrypted
2026-01-15 16:49:16 +01:00
// Use SharedMac if available, otherwise zeros (as per ts3j InitPacketTransformation)
if c.Handshake != nil && len(c.Handshake.SharedMac) > 0 {
copy(ping.Header.MAC[:], c.Handshake.SharedMac)
2026-01-15 16:49:16 +01:00
} else {
// Initialize Header.MAC with zeros
for i := 0; i < 8; i++ {
ping.Header.MAC[i] = 0
2026-01-15 17:09:32 +01:00
}
}
2026-01-15 16:49:16 +01:00
log.Printf("Sending KeepAlive Ping (PID=%d)", ping.Header.PacketID)
c.Conn.SendPacket(ping)
}
2026-01-15 16:49:16 +01:00
}
}