diff --git a/gumble/gumble/config.go b/gumble/gumble/config.go index 63c9978..8cdd167 100644 --- a/gumble/gumble/config.go +++ b/gumble/gumble/config.go @@ -38,6 +38,12 @@ type Config struct { Buffers int } +// MaximumBuffers caps Config.Buffers. Each buffer holds up to one maximum +// sized audio frame and is allocated per speaking user, so a large value +// multiplied by a populated channel is a substantial amount of memory. A few +// seconds of buffering is already far more than playback needs. +const MaximumBuffers = 1024 + // NewConfig returns a new Config struct with default values set. func NewConfig() *Config { return &Config{ @@ -64,6 +70,12 @@ func (c *Config) Validate() error { if c.Buffers <= 0 { return fmt.Errorf("gumble: Buffers must be positive") } + // Buffers is allocated per speaking user, both as a queue of decoded + // frames and as OpenAL playback buffers, so an unbounded value multiplies + // straight into memory use as a channel fills up. + if c.Buffers > MaximumBuffers { + return fmt.Errorf("gumble: Buffers must be at most %d", MaximumBuffers) + } return nil } diff --git a/gumble/gumble/config_buffers_regression_test.go b/gumble/gumble/config_buffers_regression_test.go new file mode 100644 index 0000000..feef9da --- /dev/null +++ b/gumble/gumble/config_buffers_regression_test.go @@ -0,0 +1,17 @@ +package gumble + +import "testing" + +// Regression: Buffers had no upper bound, but it is allocated per speaking +// user both as a decoded-frame queue and as OpenAL playback buffers. +func TestConfigRejectsOversizedBuffers(t *testing.T) { + config := NewConfig() + config.Buffers = MaximumBuffers + 1 + if err := config.Validate(); err == nil { + t.Fatal("expected Buffers above the maximum to be rejected") + } + config.Buffers = MaximumBuffers + if err := config.Validate(); err != nil { + t.Fatalf("Buffers at the maximum should be accepted: %v", err) + } +} diff --git a/main.go b/main.go index 4b3696b..62f8829 100644 --- a/main.go +++ b/main.go @@ -105,6 +105,10 @@ func main() { if err != nil { handle_raw_error(err) } + if *buffers <= 0 || *buffers > gumble.MaximumBuffers { + handle_raw_error(fmt.Errorf("buffers must be between 1 and %d, got %d", + gumble.MaximumBuffers, *buffers)) + } // Set up logging var level barnlog.Level