From b4ac92dea8e1b527c3dba7bdeea4cb3c209e4f87 Mon Sep 17 00:00:00 2001 From: aler9 <46489434+aler9@users.noreply.github.com> Date: Sun, 23 May 2021 22:09:01 +0200 Subject: [PATCH] headers: implement Range --- pkg/headers/range.go | 362 ++++++++++++++++++++++++++++++++++++++ pkg/headers/range_test.go | 272 ++++++++++++++++++++++++++++ 2 files changed, 634 insertions(+) create mode 100644 pkg/headers/range.go create mode 100644 pkg/headers/range_test.go diff --git a/pkg/headers/range.go b/pkg/headers/range.go new file mode 100644 index 00000000..fca2e636 --- /dev/null +++ b/pkg/headers/range.go @@ -0,0 +1,362 @@ +package headers + +import ( + "fmt" + "strconv" + "strings" + "time" + + "github.com/aler9/gortsplib/pkg/base" +) + +func leadingZero(v uint) string { + ret := "" + if v < 10 { + ret += "0" + } + ret += strconv.FormatUint(uint64(v), 10) + return ret +} + +// RangeSMPTETime is a time expressed in SMPTE unit. +type RangeSMPTETime struct { + Time time.Duration + Frame uint + Subframe uint +} + +func (t *RangeSMPTETime) read(s string) error { + parts := strings.Split(s, ":") + if len(parts) != 3 && len(parts) != 4 { + return fmt.Errorf("invalid SMPTE time (%v)", s) + } + + tmp, err := strconv.ParseUint(parts[0], 10, 64) + if err != nil { + return err + } + hours := tmp + + tmp, err = strconv.ParseUint(parts[1], 10, 64) + if err != nil { + return err + } + mins := tmp + + tmp, err = strconv.ParseUint(parts[2], 10, 64) + if err != nil { + return err + } + seconds := tmp + + t.Time = time.Duration(seconds+mins*60+hours*3600) * time.Second + + if len(parts) == 4 { + parts := strings.Split(parts[3], ".") + if len(parts) == 2 { + tmp, err := strconv.ParseUint(parts[0], 10, 64) + if err != nil { + return err + } + t.Frame = uint(tmp) + + tmp, err = strconv.ParseUint(parts[1], 10, 64) + if err != nil { + return err + } + t.Subframe = uint(tmp) + + } else { + tmp, err := strconv.ParseUint(parts[0], 10, 64) + if err != nil { + return err + } + t.Frame = uint(tmp) + } + } + + return nil +} + +func (t RangeSMPTETime) write() string { + d := uint64(t.Time.Seconds()) + hours := d / 3600 + d %= 3600 + mins := d / 60 + secs := d % 60 + + ret := strconv.FormatUint(hours, 10) + ":" + leadingZero(uint(mins)) + ":" + leadingZero(uint(secs)) + + if t.Frame > 0 || t.Subframe > 0 { + ret += ":" + leadingZero(t.Frame) + + if t.Subframe > 0 { + ret += "." + leadingZero(t.Subframe) + } + } + + return ret +} + +// RangeSMPTE is a range expressed in SMPTE unit. +type RangeSMPTE struct { + Start RangeSMPTETime + End *RangeSMPTETime +} + +func (r *RangeSMPTE) read(start string, end string) error { + err := r.Start.read(start) + if err != nil { + return err + } + + if end != "" { + var v RangeSMPTETime + err := v.read(end) + if err != nil { + return err + } + r.End = &v + } + + return nil +} + +func (r RangeSMPTE) write() string { + ret := "smpte=" + r.Start.write() + "-" + if r.End != nil { + ret += r.End.write() + } + return ret +} + +// RangeNPTTime is a time expressed in NPT unit. +type RangeNPTTime time.Duration + +func (t *RangeNPTTime) read(s string) error { + parts := strings.Split(s, ":") + if len(parts) > 3 { + return fmt.Errorf("invalid NPT time (%v)", s) + } + + var hours uint64 + if len(parts) == 3 { + tmp, err := strconv.ParseUint(parts[0], 10, 64) + if err != nil { + return err + } + hours = tmp + parts = parts[1:] + } + + var mins uint64 + if len(parts) >= 2 { + tmp, err := strconv.ParseUint(parts[0], 10, 64) + if err != nil { + return err + } + mins = tmp + parts = parts[1:] + } + + tmp, err := strconv.ParseFloat(parts[0], 64) + if err != nil { + return err + } + seconds := tmp + + *t = RangeNPTTime(time.Duration(seconds*float64(time.Second)) + + time.Duration(mins*60+hours*3600)*time.Second) + + return nil +} + +func (t RangeNPTTime) write() string { + return strconv.FormatFloat(time.Duration(t).Seconds(), 'f', -1, 64) +} + +// RangeNPT is a range expressed in NPT unit. +type RangeNPT struct { + Start RangeNPTTime + End *RangeNPTTime +} + +func (r *RangeNPT) read(start string, end string) error { + err := r.Start.read(start) + if err != nil { + return err + } + + if end != "" { + var v RangeNPTTime + err := v.read(end) + if err != nil { + return err + } + r.End = &v + } + + return nil +} + +func (r RangeNPT) write() string { + ret := "npt=" + r.Start.write() + "-" + if r.End != nil { + ret += r.End.write() + } + return ret +} + +// RangeUTCTime is a time expressed in UTC unit. +type RangeUTCTime time.Time + +func (t *RangeUTCTime) read(s string) error { + tmp, err := time.Parse("20060102T150405Z", s) + if err != nil { + return err + } + + *t = RangeUTCTime(tmp) + return nil +} + +func (t RangeUTCTime) write() string { + return time.Time(t).Format("20060102T150405Z") +} + +// RangeUTC is a range expressed in UTC unit. +type RangeUTC struct { + Start RangeUTCTime + End *RangeUTCTime +} + +func (r *RangeUTC) read(start string, end string) error { + err := r.Start.read(start) + if err != nil { + return err + } + + if end != "" { + var v RangeUTCTime + err := v.read(end) + if err != nil { + return err + } + r.End = &v + } + + return nil +} + +func (r RangeUTC) write() string { + ret := "clock=" + r.Start.write() + "-" + if r.End != nil { + ret += r.End.write() + } + return ret +} + +// RangeValue can be +// - RangeSMPTE +// - RangeNPT +// - RangeUTC +type RangeValue interface { + read(string, string) error + write() string +} + +func rangeValueRead(s RangeValue, v string) error { + parts := strings.Split(v, "-") + if len(parts) != 2 { + return fmt.Errorf("invalid value (%v)", v) + } + + return s.read(parts[0], parts[1]) +} + +// Range is a Range header. +type Range struct { + // range expressed in a certain unit. + Value RangeValue + + // time at which the operation is to be made effective. + Time *RangeUTCTime +} + +// Read decodes a Range header. +func (h *Range) Read(v base.HeaderValue) error { + if len(v) == 0 { + return fmt.Errorf("value not provided") + } + + if len(v) > 1 { + return fmt.Errorf("value provided multiple times (%v)", v) + } + + v0 := v[0] + + kvs, err := keyValParse(v0, ';') + if err != nil { + return err + } + + specFound := false + + for k, v := range kvs { + switch k { + case "smpte": + s := &RangeSMPTE{} + err := rangeValueRead(s, v) + if err != nil { + return err + } + + specFound = true + h.Value = s + + case "npt": + s := &RangeNPT{} + err := rangeValueRead(s, v) + if err != nil { + return err + } + + specFound = true + h.Value = s + + case "clock": + s := &RangeUTC{} + err := rangeValueRead(s, v) + if err != nil { + return err + } + + specFound = true + h.Value = s + + case "time": + t := &RangeUTCTime{} + err := t.read(v) + if err != nil { + return err + } + + h.Time = t + } + } + + if !specFound { + return fmt.Errorf("value not found (%v)", v[0]) + } + + return nil +} + +// Write encodes a Range header. +func (h Range) Write() base.HeaderValue { + v := h.Value.write() + if h.Time != nil { + v += ";time=" + h.Time.write() + } + return base.HeaderValue{v} +} diff --git a/pkg/headers/range_test.go b/pkg/headers/range_test.go new file mode 100644 index 00000000..fc4b13a7 --- /dev/null +++ b/pkg/headers/range_test.go @@ -0,0 +1,272 @@ +package headers + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/aler9/gortsplib/pkg/base" +) + +var casesRange = []struct { + name string + vin base.HeaderValue + vout base.HeaderValue + h Range +}{ + { + "smpte", + base.HeaderValue{`smpte=10:07:00-10:07:33:05.01`}, + base.HeaderValue{`smpte=10:07:00-10:07:33:05.01`}, + Range{ + Value: &RangeSMPTE{ + Start: RangeSMPTETime{ + Time: time.Duration(7*60+10*3600) * time.Second, + }, + End: &RangeSMPTETime{ + Time: time.Duration(33+7*60+10*3600) * time.Second, + Frame: 5, + Subframe: 1, + }, + }, + }, + }, + { + "smpte open ended", + base.HeaderValue{`smpte=0:10:00-`}, + base.HeaderValue{`smpte=0:10:00-`}, + Range{ + Value: &RangeSMPTE{ + Start: RangeSMPTETime{ + Time: time.Duration(10*60) * time.Second, + }, + }, + }, + }, + { + "smpte with frame", + base.HeaderValue{`smpte=0:10:00:01-`}, + base.HeaderValue{`smpte=0:10:00:01-`}, + Range{ + Value: &RangeSMPTE{ + Start: RangeSMPTETime{ + Time: time.Duration(10*60) * time.Second, + Frame: 1, + }, + }, + }, + }, + { + "npt", + base.HeaderValue{`npt=123.45-125`}, + base.HeaderValue{`npt=123.45-125`}, + Range{ + Value: &RangeNPT{ + Start: RangeNPTTime(123.45 * float64(time.Second)), + End: func() *RangeNPTTime { + v := RangeNPTTime(125 * time.Second) + return &v + }(), + }, + }, + }, + { + "npt open ended", + base.HeaderValue{`npt=12:05:35.3-`}, + base.HeaderValue{`npt=43535.3-`}, + Range{ + Value: &RangeNPT{ + Start: RangeNPTTime(float64(12*3600+5*60+35.3) * float64(time.Second)), + }, + }, + }, + { + "clock", + base.HeaderValue{`clock=19961108T142300Z-19961108T143520Z`}, + base.HeaderValue{`clock=19961108T142300Z-19961108T143520Z`}, + Range{ + Value: &RangeUTC{ + Start: RangeUTCTime(time.Date(1996, 11, 8, 14, 23, 0, 0, time.UTC)), + End: func() *RangeUTCTime { + v := RangeUTCTime(time.Date(1996, 11, 8, 14, 35, 20, 0, time.UTC)) + return &v + }(), + }, + }, + }, + { + "clock open ended", + base.HeaderValue{`clock=19960213T143205Z-`}, + base.HeaderValue{`clock=19960213T143205Z-`}, + Range{ + Value: &RangeUTC{ + Start: RangeUTCTime(time.Date(1996, 2, 13, 14, 32, 5, 0, time.UTC)), + }, + }, + }, + { + "time", + base.HeaderValue{`clock=19960213T143205Z-;time=19970123T143720Z`}, + base.HeaderValue{`clock=19960213T143205Z-;time=19970123T143720Z`}, + Range{ + Value: &RangeUTC{ + Start: RangeUTCTime(time.Date(1996, 2, 13, 14, 32, 5, 0, time.UTC)), + }, + Time: func() *RangeUTCTime { + v := RangeUTCTime(time.Date(1997, 1, 23, 14, 37, 20, 0, time.UTC)) + return &v + }(), + }, + }, +} + +func TestRangeRead(t *testing.T) { + for _, ca := range casesRange { + t.Run(ca.name, func(t *testing.T) { + var h Range + err := h.Read(ca.vin) + require.NoError(t, err) + require.Equal(t, ca.h, h) + }) + } +} + +func TestRangeWrite(t *testing.T) { + for _, ca := range casesRange { + t.Run(ca.name, func(t *testing.T) { + req := ca.h.Write() + require.Equal(t, ca.vout, req) + }) + } +} + +func TestRangeReadErrors(t *testing.T) { + for _, ca := range []struct { + name string + hv base.HeaderValue + err string + }{ + { + "empty", + base.HeaderValue{}, + "value not provided", + }, + { + "2 values", + base.HeaderValue{"a", "b"}, + "value provided multiple times ([a b])", + }, + { + "invalid keys", + base.HeaderValue{`key1="k`}, + "apexes not closed (key1=\"k)", + }, + { + "value not found", + base.HeaderValue{``}, + "value not found ()", + }, + { + "smpte without values", + base.HeaderValue{`smpte=`}, + "invalid value ()", + }, + { + "smtpe end invalid", + base.HeaderValue{`smpte=00:00:01-123`}, + "invalid SMPTE time (123)", + }, + { + "smpte invalid 1", + base.HeaderValue{`smpte=123-`}, + "invalid SMPTE time (123)", + }, + { + "smpte invalid 2", + base.HeaderValue{`smpte=aa:00:00-`}, + "strconv.ParseUint: parsing \"aa\": invalid syntax", + }, + { + "smpte invalid 3", + base.HeaderValue{`smpte=00:aa:00-`}, + "strconv.ParseUint: parsing \"aa\": invalid syntax", + }, + { + "smpte invalid 4", + base.HeaderValue{`smpte=00:00:aa-`}, + "strconv.ParseUint: parsing \"aa\": invalid syntax", + }, + { + "smpte invalid 5", + base.HeaderValue{`smpte=00:00:00:aa-`}, + "strconv.ParseUint: parsing \"aa\": invalid syntax", + }, + { + "smpte invalid 6", + base.HeaderValue{`smpte=00:00:00:aa.00-`}, + "strconv.ParseUint: parsing \"aa\": invalid syntax", + }, + { + "smpte invalid 7", + base.HeaderValue{`smpte=00:00:00:00.aa-`}, + "strconv.ParseUint: parsing \"aa\": invalid syntax", + }, + { + "npt without values", + base.HeaderValue{`npt=`}, + "invalid value ()", + }, + { + "npt end invalid", + base.HeaderValue{`npt=00:00:00-aa`}, + "strconv.ParseFloat: parsing \"aa\": invalid syntax", + }, + { + "npt invalid 1", + base.HeaderValue{`npt=00:00:00:00-`}, + "invalid NPT time (00:00:00:00)", + }, + { + "npt invalid 2", + base.HeaderValue{`npt=aa-`}, + "strconv.ParseFloat: parsing \"aa\": invalid syntax", + }, + { + "npt invalid 3", + base.HeaderValue{`npt=aa:00-`}, + "strconv.ParseUint: parsing \"aa\": invalid syntax", + }, + { + "npt invalid 4", + base.HeaderValue{`npt=aa:00:00-`}, + "strconv.ParseUint: parsing \"aa\": invalid syntax", + }, + { + "clock without values", + base.HeaderValue{`clock=`}, + "invalid value ()", + }, + { + "clock end invalid", + base.HeaderValue{`clock=20060102T150405Z-aa`}, + "parsing time \"aa\" as \"20060102T150405Z\": cannot parse \"aa\" as \"2006\"", + }, + { + "clock invalid 1", + base.HeaderValue{`clock=aa-`}, + "parsing time \"aa\" as \"20060102T150405Z\": cannot parse \"aa\" as \"2006\"", + }, + { + "time invalid", + base.HeaderValue{`time=aa`}, + "parsing time \"aa\" as \"20060102T150405Z\": cannot parse \"aa\" as \"2006\"", + }, + } { + t.Run(ca.name, func(t *testing.T) { + var h Range + err := h.Read(ca.hv) + require.Equal(t, ca.err, err.Error()) + }) + } +}