commit 1146e3cfa554e40c95835cd5fd7c65eb7afb40aa from: Oliver Lowe date: Thu May 15 11:15:21 2025 UTC m3u8: add validation to segment parsing tests Tests fail, but at least we have exposed the bugs commit - e5a2561859aaf24e6368af6a4e82fc8112bc3d08 commit + 1146e3cfa554e40c95835cd5fd7c65eb7afb40aa blob - a1b372a4d257e5b477fb38b5925e2734f7f9da01 blob + a71b7f91295d88116f9117e9fd2185a9749e5751 --- m3u8/parse.go +++ 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: blob - 553b404c29bccc04438c237fefeb17266afe3f02 blob + 22fc0b281c644f6e25183ceb6de969d198189b80 --- m3u8/segment.go +++ 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,43 +38,46 @@ func parseSegment(items chan item, leading item) (*Seg 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") } blob - 061e68b153f2260427194b6e0c257d9d62b39428 blob + 40d13ffe7d9e39b9073e50c523d8d9341d1e6072 --- m3u8/segment_test.go +++ 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) + } +}