diff --git a/internal/voice/player.go b/internal/voice/player.go index 0b21010..81eae5b 100644 --- a/internal/voice/player.go +++ b/internal/voice/player.go @@ -125,7 +125,7 @@ func (p *Player) seek(position time.Duration) error { offset += size } - decoder, err := oggopus.NewDecoder(io.MultiReader(bytes.NewReader(p.audio[:headers]), bytes.NewReader(p.audio[min(start, len(p.audio)):]))) + decoder, err := oggopus.NewDecoder(io.MultiReader(bytes.NewReader(p.audio[:min(headers, len(p.audio))]), bytes.NewReader(p.audio[min(start, len(p.audio)):]))) if err != nil { return err } diff --git a/internal/voice/player_test.go b/internal/voice/player_test.go index 821c2c6..e9f6be1 100644 --- a/internal/voice/player_test.go +++ b/internal/voice/player_test.go @@ -12,28 +12,43 @@ import ( ) func TestPlayerSeek(t *testing.T) { - const seconds = 10 + t.Run("valid", func(t *testing.T) { + const seconds = 10 - var audio bytes.Buffer - if err := oggopus.EncodeInterleaved(&audio, oggopus.Config{SampleRate: sampleRate, Channels: 1}, make([]byte, seconds*sampleRate*2)); err != nil { - t.Fatal(err) - } - - p := Player{audio: audio.Bytes()} - for _, position := range []time.Duration{0, 500 * time.Millisecond, 6 * time.Second, seconds * time.Second, time.Hour} { - if err := p.seek(position); err != nil { + var audio bytes.Buffer + if err := oggopus.EncodeInterleaved(&audio, oggopus.Config{SampleRate: sampleRate, Channels: 1}, make([]byte, seconds*sampleRate*2)); err != nil { t.Fatal(err) } - left, err := io.Copy(io.Discard, p.decoder) - if err != nil { - t.Fatal(err) + p := Player{audio: audio.Bytes()} + for _, position := range []time.Duration{0, 500 * time.Millisecond, 6 * time.Second, seconds * time.Second, time.Hour} { + if err := p.seek(position); err != nil { + t.Fatal(err) + } + + left, err := io.Copy(io.Discard, p.decoder) + if err != nil { + t.Fatal(err) + } + + // What is left is a packet longer at most, as the end is only trimmed when played from the start. + want := max(seconds*time.Second-position, 0) + if got := time.Duration(left/2) * time.Second / sampleRate; got < want || got > want+20*time.Millisecond { + t.Errorf("seek to %v: %v is left, want %v", position, got, want) + } + } + }) + + // A page whose segment table claims far more bytes than the file holds must not make seek read out of bounds. + t.Run("truncated page", func(t *testing.T) { + page := make([]byte, oggHeaderSize+255) + copy(page, "OggS") + page[oggSegmentsOffset] = 255 + for i := range page[oggHeaderSize:] { + page[oggHeaderSize+i] = 255 } - // What is left is a packet longer at most, as the end is only trimmed when played from the start. - want := max(seconds*time.Second-position, 0) - if got := time.Duration(left/2) * time.Second / sampleRate; got < want || got > want+20*time.Millisecond { - t.Errorf("seek to %v: %v is left, want %v", position, got, want) - } - } + p := Player{audio: page} + _ = p.seek(0) + }) }