feat: implement real ping RTT calculation instead of hardcoded 50ms

This commit is contained in:
Jose Luis Montañes Ojados
2026-01-16 19:43:31 +01:00
parent 184fff202f
commit 13f444193d
3 changed files with 79 additions and 5 deletions

View File

@@ -37,6 +37,12 @@ type Client struct {
PongPacketID uint16 // Type 0x05
AckPacketID uint16 // Type 0x06
// Ping RTT tracking
PingSentTimes map[uint16]time.Time // Map PingPacketID -> Time sent
PingRTT float64 // Rolling average RTT in ms
PingDeviation float64 // Rolling deviation in ms
PingSampleCount int // Number of samples for rolling avg
// State
Connected bool
ServerName string
@@ -70,6 +76,9 @@ func NewClient(nickname string) *Client {
VoiceDecoders: make(map[uint16]*opus.Decoder),
CommandQueue: make(map[uint16]*protocol.Packet),
ExpectedCommandPID: 0,
PingSentTimes: make(map[uint16]time.Time),
PingRTT: 0,
PingDeviation: 0,
done: make(chan struct{}),
}
}
@@ -187,6 +196,9 @@ func (c *Client) sendPing() error {
pkt.Data = encData
copy(pkt.Header.MAC[:], mac)
// Record send time for RTT calculation
c.PingSentTimes[pkt.Header.PacketID] = time.Now()
log.Printf("Sending proper Encrypted Ping (PID=%d)", pkt.Header.PacketID)
return c.Conn.SendPacket(pkt)
}

View File

@@ -459,11 +459,20 @@ func (c *Client) processCommand(data []byte, pkt *protocol.Packet) error {
case "notifyconnectioninforequest":
// Server asking for connection info. We MUST reply to update Ping in UI and avoid timeout.
log.Println("Server requested connection info. sending 'setconnectioninfo'...")
log.Println("Server requested connection info. Sending 'setconnectioninfo'...")
cmd := protocol.NewCommand("setconnectioninfo")
cmd.AddParam("connection_ping", "50")
cmd.AddParam("connection_ping_deviation", "5")
// Use real ping values if available, otherwise default to 50ms
pingMs := c.PingRTT
pingDev := c.PingDeviation
if pingMs == 0 {
pingMs = 50.0 // Default before first measurement
pingDev = 5.0
}
cmd.AddParam("connection_ping", fmt.Sprintf("%.4f", pingMs))
cmd.AddParam("connection_ping_deviation", fmt.Sprintf("%.4f", pingDev))
// Detailed stats for each kind as seen in ts3j (KEEPALIVE, SPEECH, CONTROL)
kinds := []string{"keepalive", "speech", "control"}

View File

@@ -4,6 +4,8 @@ import (
"bytes"
"encoding/binary"
"log"
"math"
"time"
"go-ts/pkg/protocol"
)
@@ -114,8 +116,59 @@ func (c *Client) handlePacket(pkt *protocol.Packet) error {
c.Conn.SendPacket(pong)
case protocol.PacketTypePong:
// Server acknowledged our Ping
log.Printf("Received Pong for sequence %d", pkt.Header.PacketID)
// Server acknowledged our Ping - calculate RTT
receivedAt := time.Now()
// Decrypt Pong body if needed to get the PingID it's acknowledging
var pongData []byte
if pkt.Header.FlagUnencrypted() {
pongData = pkt.Data
} else 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,
}
key, nonce := crypto.GenerateKeyNonce(&pkt.Header, false) // Server->Client
meta := make([]byte, 3)
binary.BigEndian.PutUint16(meta[0:2], pkt.Header.PacketID)
meta[2] = pkt.Header.Type
decrypted, err := protocol.DecryptEAX(key, nonce, meta, pkt.Data, pkt.Header.MAC[:])
if err == nil {
pongData = decrypted
}
}
// Extract PingID from Pong body
var pingID uint16
if len(pongData) >= 2 {
pingID = binary.BigEndian.Uint16(pongData[0:2])
} else {
pingID = pkt.Header.PacketID // fallback
}
// Calculate RTT if we have the send time
if sentTime, ok := c.PingSentTimes[pingID]; ok {
rtt := receivedAt.Sub(sentTime).Seconds() * 1000 // RTT in ms
delete(c.PingSentTimes, pingID)
// Update rolling average using Welford's algorithm
c.PingSampleCount++
if c.PingSampleCount == 1 {
c.PingRTT = rtt
c.PingDeviation = 0
} else {
oldMean := c.PingRTT
c.PingRTT = oldMean + (rtt-oldMean)/float64(c.PingSampleCount)
// Rolling deviation: exponential smoothing
c.PingDeviation = c.PingDeviation*0.9 + math.Abs(rtt-c.PingRTT)*0.1
}
log.Printf("Received Pong for Ping %d: RTT=%.2fms, AvgRTT=%.2fms, Dev=%.2fms",
pingID, rtt, c.PingRTT, c.PingDeviation)
} else {
log.Printf("Received Pong for unknown Ping %d", pingID)
}
case protocol.PacketTypeAck:
// Server acknowledged our packet
var data []byte