streaming

Media streaming and broadcast systems in Go
Log | Files | Refs | README | LICENSE

commit 1146e3cfa554e40c95835cd5fd7c65eb7afb40aa
parent e5a2561859aaf24e6368af6a4e82fc8112bc3d08
Author: Oliver Lowe <o@olowe.co>
Date:   Thu, 15 May 2025 21:15:21 +1000

m3u8: add validation to segment parsing tests

Tests fail, but at least we have exposed the bugs

Diffstat:
Mm3u8/parse.go | 5++++-
Mm3u8/segment.go | 61++++++++++++++++++++++++++++++++-----------------------------
Mm3u8/segment_test.go | 29+++++++++++++++++++++++++++++
3 files changed, 65 insertions(+), 30 deletions(-)

diff --git a/m3u8/parse.go b/m3u8/parse.go @@ -70,6 +70,7 @@ func Decode(rd io.Reader) (*Playlist, error) { return p, fmt.Errorf("parse playlist type: %w", err) } p.Type = typ + case tagTargetDuration: it = <-lex.items dur, err := parseTargetDuration(it) @@ -77,12 +78,14 @@ func Decode(rd io.Reader) (*Playlist, error) { return p, fmt.Errorf("parse target duration: %w", err) } p.TargetDuration = dur - case tagSegmentDuration, tagByteRange: + + case tagSegmentDuration, tagByteRange, tagKey: segment, err := parseSegment(lex.items, it) if err != nil { return p, fmt.Errorf("parse segment: %w", err) } p.Segments = append(p.Segments, *segment) + case tagEndList: p.End = true case tagMediaSequence: diff --git a/m3u8/segment.go b/m3u8/segment.go @@ -25,7 +25,7 @@ const ( ) // parseSegment returns the next segment from items and the leading -// item which indecated the start of a segment. +// item which indicated the start of a segment. func parseSegment(items chan item, leading item) (*Segment, error) { var seg Segment switch leading.typ { @@ -38,42 +38,45 @@ func parseSegment(items chan item, leading item) (*Segment, error) { return nil, fmt.Errorf("parse segment duration: %w", err) } seg.Duration = dur + default: + return nil, fmt.Errorf("parse leading item %s: unsupported", leading) } } + for it := range items { - if it.typ == itemError { - return nil, errors.New(it.val) - } switch it.typ { + case itemError: + return nil, errors.New(it.val) case itemURL: seg.URI = it.val return &seg, nil - case itemTag: - switch it.val { - case tagSegmentDuration: - it = <-items - dur, err := parseSegmentDuration(it) - if err != nil { - return nil, fmt.Errorf("parse segment duration: %w", err) - } - seg.Duration = dur - case tagByteRange: - it = <-items - if it.typ != itemString { - return nil, fmt.Errorf("parse byte range: got %s, want item type string", it) - } - r, err := parseByteRange(it.val) - if err != nil { - return nil, fmt.Errorf("parse byte range: %w", err) - } - seg.Range = r - case tagDiscontinuity: - seg.Discontinuity = true - case tagKey: - return nil, fmt.Errorf("parsing %s unsupported", it) - default: - return nil, fmt.Errorf("parsing %s unsupported", it) + case itemNewline: + continue + default: + if it.typ != itemTag { + return nil, fmt.Errorf("unexpected %s", it) + } + } + + switch it.val { + case tagSegmentDuration: + it = <-items + dur, err := parseSegmentDuration(it) + if err != nil { + return nil, fmt.Errorf("parse segment duration: %w", err) + } + seg.Duration = dur + case tagByteRange: + it = <-items + r, err := parseByteRange(it.val) + if err != nil { + return nil, fmt.Errorf("parse byte range: %w", err) } + seg.Range = r + case tagDiscontinuity: + seg.Discontinuity = true + default: + return nil, fmt.Errorf("parsing %s unsupported", it) } } return nil, fmt.Errorf("no url") diff --git a/m3u8/segment_test.go b/m3u8/segment_test.go @@ -2,6 +2,8 @@ package m3u8 import ( "encoding/binary" + "os" + "reflect" "testing" "time" ) @@ -93,3 +95,30 @@ func TestWriteKey(t *testing.T) { t.Log("want:", want) } } + +func TestParseSegment(t *testing.T) { + f, err := os.Open("testdata/discontinuities.m3u8") + if err != nil { + t.Fatal(err) + } + defer f.Close() + + plist, err := Decode(f) + if err != nil { + t.Fatalf("decode playlist: %v", err) + } + + encrypted := Segment{ + Duration: 10 * time.Second, + Key: &Key{ + Method: EncryptMethodAES128, + URI: "key1.json?f=1041&s=0&p=1822767&m=1506045858", + IV: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x1B, 0xD0, 0x2F}, + }, + URI: "1041_6_1822767.ts?m=1506045858", + } + + if !reflect.DeepEqual(plist.Segments[0], encrypted) { + t.Errorf("decode encrypted segment: got %v, want %v", plist.Segments[0], encrypted) + } +}