validate audio configuration before connecting

Reject unsupported audio intervals and non-positive buffer sizes.
An interval like 15ms was truncated to 10ms frames while the send
ticker kept the original duration, so packets were produced at a rate
the frame size did not match. Checking at dial time gives a clear
error instead of malformed audio.

Require all three coordinates for positional audio.
Only X was checked, so supplying X without Y or Z wrote a header that
claimed positional data and then read past the values that were
actually provided.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Brandon McGinty
2026-08-20 14:25:35 -04:00
co-authored by Claude Opus 5
parent 1a6c13e8aa
commit e41d2fd8cb
4 changed files with 44 additions and 1 deletions
+3
View File
@@ -125,6 +125,9 @@ func tlsServerName(address string) (string, error) {
// min(time.Now() + dialer.Timeout, dialer.Deadline), or if the server rejects
// the client.
func DialWithDialer(dialer *net.Dialer, config *Config, tlsConfig *tls.Config) (*Client, error) {
if err := config.Validate(); err != nil {
return nil, err
}
start := time.Now()
rawConn, err := dialer.Dial("tcp", config.Address)
+17
View File
@@ -1,6 +1,7 @@
package gumble
import (
"fmt"
"time"
)
@@ -43,6 +44,22 @@ func NewConfig() *Config {
}
}
// Validate checks values that are used by the audio ticker and encoder.
func (c *Config) Validate() error {
switch c.AudioInterval {
case 10 * time.Millisecond, 20 * time.Millisecond, 40 * time.Millisecond, 60 * time.Millisecond:
default:
return fmt.Errorf("gumble: AudioInterval must be 10ms, 20ms, 40ms, or 60ms")
}
if c.AudioDataBytes <= 0 {
return fmt.Errorf("gumble: AudioDataBytes must be positive")
}
if c.Buffers <= 0 {
return fmt.Errorf("gumble: Buffers must be positive")
}
return nil
}
// Attach is an alias of c.Listeners.Attach.
func (c *Config) Attach(l EventListener) Detacher {
return c.Listeners.Attach(l)
+20
View File
@@ -0,0 +1,20 @@
package gumble
import (
"testing"
"time"
)
// Regression: arbitrary intervals were truncated to 10 ms frames while the
// ticker kept the original duration, producing malformed audio timing.
func TestConfigValidateRejectsUnsupportedAudioInterval(t *testing.T) {
config := NewConfig()
config.AudioInterval = 15 * time.Millisecond
if err := config.Validate(); err == nil {
t.Fatal("invalid audio interval was accepted")
}
config.AudioInterval = 60 * time.Millisecond
if err := config.Validate(); err != nil {
t.Fatalf("valid audio interval rejected: %v", err)
}
}
+4 -1
View File
@@ -80,7 +80,10 @@ func (c *Conn) WriteAudio(format, target byte, sequence int64, final bool, data
header := buff[:1+n+m]
var positionalLength int
if X != nil {
if X != nil || Y != nil || Z != nil {
if X == nil || Y == nil || Z == nil {
return errors.New("gumble: positional audio requires X, Y, and Z")
}
positionalLength = 3 * 4
}