diff --git a/pkg/headers/auth.go b/pkg/headers/auth.go index 65e2fece..ab1a6c38 100644 --- a/pkg/headers/auth.go +++ b/pkg/headers/auth.go @@ -79,7 +79,7 @@ func findValue(v0 string) (string, string, error) { } } -// ReadAuth parses an Authenticate or a WWW-Authenticate header. +// ReadAuth decodes an Authenticate or a WWW-Authenticate header. func ReadAuth(v base.HeaderValue) (*Auth, error) { if len(v) == 0 { return nil, fmt.Errorf("value not provided") @@ -89,7 +89,7 @@ func ReadAuth(v base.HeaderValue) (*Auth, error) { return nil, fmt.Errorf("value provided multiple times (%v)", v) } - ha := &Auth{} + h := &Auth{} v0 := v[0] @@ -100,10 +100,10 @@ func ReadAuth(v base.HeaderValue) (*Auth, error) { switch v0[:i] { case "Basic": - ha.Method = AuthBasic + h.Method = AuthBasic case "Digest": - ha.Method = AuthDigest + h.Method = AuthDigest default: return nil, fmt.Errorf("invalid method (%s)", v0[:i]) @@ -127,28 +127,28 @@ func ReadAuth(v base.HeaderValue) (*Auth, error) { switch key { case "username": - ha.Username = &val + h.Username = &val case "realm": - ha.Realm = &val + h.Realm = &val case "nonce": - ha.Nonce = &val + h.Nonce = &val case "uri": - ha.URI = &val + h.URI = &val case "response": - ha.Response = &val + h.Response = &val case "opaque": - ha.Opaque = &val + h.Opaque = &val case "stale": - ha.Stale = &val + h.Stale = &val case "algorithm": - ha.Algorithm = &val + h.Algorithm = &val // ignore non-standard keys } @@ -164,14 +164,14 @@ func ReadAuth(v base.HeaderValue) (*Auth, error) { } } - return ha, nil + return h, nil } // Write encodes an Authenticate or a WWW-Authenticate header. -func (ha Auth) Write() base.HeaderValue { +func (h Auth) Write() base.HeaderValue { ret := "" - switch ha.Method { + switch h.Method { case AuthBasic: ret += "Basic" @@ -181,41 +181,41 @@ func (ha Auth) Write() base.HeaderValue { ret += " " - var vals []string + var rets []string - if ha.Username != nil { - vals = append(vals, "username=\""+*ha.Username+"\"") + if h.Username != nil { + rets = append(rets, "username=\""+*h.Username+"\"") } - if ha.Realm != nil { - vals = append(vals, "realm=\""+*ha.Realm+"\"") + if h.Realm != nil { + rets = append(rets, "realm=\""+*h.Realm+"\"") } - if ha.Nonce != nil { - vals = append(vals, "nonce=\""+*ha.Nonce+"\"") + if h.Nonce != nil { + rets = append(rets, "nonce=\""+*h.Nonce+"\"") } - if ha.URI != nil { - vals = append(vals, "uri=\""+*ha.URI+"\"") + if h.URI != nil { + rets = append(rets, "uri=\""+*h.URI+"\"") } - if ha.Response != nil { - vals = append(vals, "response=\""+*ha.Response+"\"") + if h.Response != nil { + rets = append(rets, "response=\""+*h.Response+"\"") } - if ha.Opaque != nil { - vals = append(vals, "opaque=\""+*ha.Opaque+"\"") + if h.Opaque != nil { + rets = append(rets, "opaque=\""+*h.Opaque+"\"") } - if ha.Stale != nil { - vals = append(vals, "stale=\""+*ha.Stale+"\"") + if h.Stale != nil { + rets = append(rets, "stale=\""+*h.Stale+"\"") } - if ha.Algorithm != nil { - vals = append(vals, "algorithm=\""+*ha.Algorithm+"\"") + if h.Algorithm != nil { + rets = append(rets, "algorithm=\""+*h.Algorithm+"\"") } - ret += strings.Join(vals, ", ") + ret += strings.Join(rets, ", ") return base.HeaderValue{ret} } diff --git a/pkg/headers/rtpinfo.go b/pkg/headers/rtpinfo.go new file mode 100644 index 00000000..2fd1bed2 --- /dev/null +++ b/pkg/headers/rtpinfo.go @@ -0,0 +1,87 @@ +package headers + +import ( + "fmt" + "strconv" + "strings" + + "github.com/aler9/gortsplib/pkg/base" +) + +// RTPInfoEntry is an entry of an RTP-Info header. +type RTPInfoEntry struct { + URL *base.URL + SequenceNumber uint16 + RTPTime uint32 +} + +// RTPInfo is a RTP-Info header. +type RTPInfo []RTPInfoEntry + +// ReadRTPInfo decodes a RTP-Info header. +func ReadRTPInfo(v base.HeaderValue) (*RTPInfo, error) { + if len(v) == 0 { + return nil, fmt.Errorf("value not provided") + } + + if len(v) > 1 { + return nil, fmt.Errorf("value provided multiple times (%v)", v) + } + + h := &RTPInfo{} + + for _, tmp := range strings.Split(v[0], ",") { + e := RTPInfoEntry{} + + for _, kv := range strings.Split(tmp, ";") { + tmp := strings.SplitN(kv, "=", 2) + if len(tmp) != 2 { + return nil, fmt.Errorf("unable to parse key-value (%v)", kv) + } + + k, v := tmp[0], tmp[1] + switch k { + case "url": + vu, err := base.ParseURL(v) + if err != nil { + return nil, err + } + e.URL = vu + + case "seq": + vi, err := strconv.ParseUint(v, 10, 16) + if err != nil { + return nil, err + } + e.SequenceNumber = uint16(vi) + + case "rtptime": + vi, err := strconv.ParseUint(v, 10, 32) + if err != nil { + return nil, err + } + e.RTPTime = uint32(vi) + + default: + return nil, fmt.Errorf("invalid key: %v", k) + } + } + + *h = append(*h, e) + } + + return h, nil +} + +// Write encodes a RTP-Info header. +func (h RTPInfo) Write() base.HeaderValue { + var rets []string + + for _, e := range h { + rets = append(rets, "url="+e.URL.String()+ + ";seq="+strconv.FormatUint(uint64(e.SequenceNumber), 10)+ + ";rtptime="+strconv.FormatUint(uint64(e.RTPTime), 10)) + } + + return base.HeaderValue{strings.Join(rets, ",")} +} diff --git a/pkg/headers/rtpinfo_test.go b/pkg/headers/rtpinfo_test.go new file mode 100644 index 00000000..4916445d --- /dev/null +++ b/pkg/headers/rtpinfo_test.go @@ -0,0 +1,65 @@ +package headers + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/aler9/gortsplib/pkg/base" +) + +var casesRTPInfo = []struct { + name string + vin base.HeaderValue + vout base.HeaderValue + h *RTPInfo +}{ + { + "single value", + base.HeaderValue{`url=rtsp://127.0.0.1/test.mkv/track1;seq=35243;rtptime=717574556`}, + base.HeaderValue{`url=rtsp://127.0.0.1/test.mkv/track1;seq=35243;rtptime=717574556`}, + &RTPInfo{ + { + URL: base.MustParseURL("rtsp://127.0.0.1/test.mkv/track1"), + SequenceNumber: 35243, + RTPTime: 717574556, + }, + }, + }, + { + "multiple value", + base.HeaderValue{`url=rtsp://127.0.0.1/test.mkv/track1;seq=35243;rtptime=717574556,url=rtsp://127.0.0.1/test.mkv/track2;seq=13655;rtptime=2848846950`}, + base.HeaderValue{`url=rtsp://127.0.0.1/test.mkv/track1;seq=35243;rtptime=717574556,url=rtsp://127.0.0.1/test.mkv/track2;seq=13655;rtptime=2848846950`}, + &RTPInfo{ + { + URL: base.MustParseURL("rtsp://127.0.0.1/test.mkv/track1"), + SequenceNumber: 35243, + RTPTime: 717574556, + }, + { + URL: base.MustParseURL("rtsp://127.0.0.1/test.mkv/track2"), + SequenceNumber: 13655, + RTPTime: 2848846950, + }, + }, + }, +} + +func TestRTPInfoRead(t *testing.T) { + for _, c := range casesRTPInfo { + t.Run(c.name, func(t *testing.T) { + req, err := ReadRTPInfo(c.vin) + require.NoError(t, err) + require.Equal(t, c.h, req) + }) + } +} + +func TestRTPInfoWrite(t *testing.T) { + for _, c := range casesRTPInfo { + t.Run(c.name, func(t *testing.T) { + req := c.h.Write() + require.Equal(t, c.vout, req) + }) + } +} diff --git a/pkg/headers/session.go b/pkg/headers/session.go index f87d542f..2b5be3fe 100644 --- a/pkg/headers/session.go +++ b/pkg/headers/session.go @@ -17,7 +17,7 @@ type Session struct { Timeout *uint } -// ReadSession parses a Session header. +// ReadSession decodes a Session header. func ReadSession(v base.HeaderValue) (*Session, error) { if len(v) == 0 { return nil, fmt.Errorf("value not provided") @@ -32,20 +32,20 @@ func ReadSession(v base.HeaderValue) (*Session, error) { return nil, fmt.Errorf("invalid value (%v)", v) } - hs := &Session{} + h := &Session{} - hs.Session = parts[0] + h.Session = parts[0] for _, part := range parts[1:] { // remove leading spaces part = strings.TrimLeft(part, " ") - keyval := strings.Split(part, "=") - if len(keyval) != 2 { + kv := strings.Split(part, "=") + if len(kv) != 2 { return nil, fmt.Errorf("invalid value") } - key, strValue := keyval[0], keyval[1] + key, strValue := kv[0], kv[1] if key != "timeout" { return nil, fmt.Errorf("invalid key '%s'", key) } @@ -56,19 +56,19 @@ func ReadSession(v base.HeaderValue) (*Session, error) { } uiv := uint(iv) - hs.Timeout = &uiv + h.Timeout = &uiv } - return hs, nil + return h, nil } -// Write encodes a Session header -func (hs Session) Write() base.HeaderValue { - val := hs.Session +// Write encodes a Session header. +func (h Session) Write() base.HeaderValue { + ret := h.Session - if hs.Timeout != nil { - val += ";timeout=" + strconv.FormatUint(uint64(*hs.Timeout), 10) + if h.Timeout != nil { + ret += ";timeout=" + strconv.FormatUint(uint64(*h.Timeout), 10) } - return base.HeaderValue{val} + return base.HeaderValue{ret} } diff --git a/pkg/headers/transport.go b/pkg/headers/transport.go index e6846197..763e5e44 100644 --- a/pkg/headers/transport.go +++ b/pkg/headers/transport.go @@ -20,8 +20,8 @@ const ( ) // String implements fmt.Stringer. -func (sm TransportMode) String() string { - switch sm { +func (tm TransportMode) String() string { + switch tm { case TransportModePlay: return "play" @@ -89,7 +89,7 @@ func parsePorts(val string) (*[2]int, error) { return &[2]int{0, 0}, fmt.Errorf("invalid ports (%v)", val) } -// ReadTransport parses a Transport header. +// ReadTransport decodes a Transport header. func ReadTransport(v base.HeaderValue) (*Transport, error) { if len(v) == 0 { return nil, fmt.Errorf("value not provided") @@ -99,7 +99,7 @@ func ReadTransport(v base.HeaderValue) (*Transport, error) { return nil, fmt.Errorf("value provided multiple times (%v)", v) } - ht := &Transport{} + h := &Transport{} parts := strings.Split(v[0], ";") if len(parts) == 0 { @@ -108,10 +108,10 @@ func ReadTransport(v base.HeaderValue) (*Transport, error) { switch parts[0] { case "RTP/AVP", "RTP/AVP/UDP": - ht.Protocol = base.StreamProtocolUDP + h.Protocol = base.StreamProtocolUDP case "RTP/AVP/TCP": - ht.Protocol = base.StreamProtocolTCP + h.Protocol = base.StreamProtocolTCP default: return nil, fmt.Errorf("invalid protocol (%v)", v) @@ -121,12 +121,12 @@ func ReadTransport(v base.HeaderValue) (*Transport, error) { switch parts[0] { case "unicast": v := base.StreamDeliveryUnicast - ht.Delivery = &v + h.Delivery = &v parts = parts[1:] case "multicast": v := base.StreamDeliveryMulticast - ht.Delivery = &v + h.Delivery = &v parts = parts[1:] // cast is optional, do not return any error @@ -135,7 +135,7 @@ func ReadTransport(v base.HeaderValue) (*Transport, error) { for _, t := range parts { if strings.HasPrefix(t, "destination=") { v := t[len("destination="):] - ht.Destination = &v + h.Destination = &v } else if strings.HasPrefix(t, "ttl=") { v, err := strconv.ParseUint(t[len("ttl="):], 10, 64) @@ -143,35 +143,35 @@ func ReadTransport(v base.HeaderValue) (*Transport, error) { return nil, err } vu := uint(v) - ht.TTL = &vu + h.TTL = &vu } else if strings.HasPrefix(t, "port=") { ports, err := parsePorts(t[len("port="):]) if err != nil { return nil, err } - ht.Ports = ports + h.Ports = ports } else if strings.HasPrefix(t, "client_port=") { ports, err := parsePorts(t[len("client_port="):]) if err != nil { return nil, err } - ht.ClientPorts = ports + h.ClientPorts = ports } else if strings.HasPrefix(t, "server_port=") { ports, err := parsePorts(t[len("server_port="):]) if err != nil { return nil, err } - ht.ServerPorts = ports + h.ServerPorts = ports } else if strings.HasPrefix(t, "interleaved=") { ports, err := parsePorts(t[len("interleaved="):]) if err != nil { return nil, err } - ht.InterleavedIds = ports + h.InterleavedIds = ports } else if strings.HasPrefix(t, "mode=") { str := strings.ToLower(t[len("mode="):]) @@ -181,13 +181,13 @@ func ReadTransport(v base.HeaderValue) (*Transport, error) { switch str { case "play": v := TransportModePlay - ht.Mode = &v + h.Mode = &v // receive is an old alias for record, used by ffmpeg with the // -listen flag, and by Darwin Streaming Server case "record", "receive": v := TransportModeRecord - ht.Mode = &v + h.Mode = &v default: return nil, fmt.Errorf("invalid transport mode: '%s'", str) @@ -197,49 +197,49 @@ func ReadTransport(v base.HeaderValue) (*Transport, error) { // ignore non-standard keys } - return ht, nil + return h, nil } // Write encodes a Transport header -func (ht Transport) Write() base.HeaderValue { - var vals []string +func (h Transport) Write() base.HeaderValue { + var rets []string - if ht.Protocol == base.StreamProtocolUDP { - vals = append(vals, "RTP/AVP") + if h.Protocol == base.StreamProtocolUDP { + rets = append(rets, "RTP/AVP") } else { - vals = append(vals, "RTP/AVP/TCP") + rets = append(rets, "RTP/AVP/TCP") } - if ht.Delivery != nil { - if *ht.Delivery == base.StreamDeliveryUnicast { - vals = append(vals, "unicast") + if h.Delivery != nil { + if *h.Delivery == base.StreamDeliveryUnicast { + rets = append(rets, "unicast") } else { - vals = append(vals, "multicast") + rets = append(rets, "multicast") } } - if ht.ClientPorts != nil { - ports := *ht.ClientPorts - vals = append(vals, "client_port="+strconv.FormatInt(int64(ports[0]), 10)+"-"+strconv.FormatInt(int64(ports[1]), 10)) + if h.ClientPorts != nil { + ports := *h.ClientPorts + rets = append(rets, "client_port="+strconv.FormatInt(int64(ports[0]), 10)+"-"+strconv.FormatInt(int64(ports[1]), 10)) } - if ht.ServerPorts != nil { - ports := *ht.ServerPorts - vals = append(vals, "server_port="+strconv.FormatInt(int64(ports[0]), 10)+"-"+strconv.FormatInt(int64(ports[1]), 10)) + if h.ServerPorts != nil { + ports := *h.ServerPorts + rets = append(rets, "server_port="+strconv.FormatInt(int64(ports[0]), 10)+"-"+strconv.FormatInt(int64(ports[1]), 10)) } - if ht.InterleavedIds != nil { - ports := *ht.InterleavedIds - vals = append(vals, "interleaved="+strconv.FormatInt(int64(ports[0]), 10)+"-"+strconv.FormatInt(int64(ports[1]), 10)) + if h.InterleavedIds != nil { + ports := *h.InterleavedIds + rets = append(rets, "interleaved="+strconv.FormatInt(int64(ports[0]), 10)+"-"+strconv.FormatInt(int64(ports[1]), 10)) } - if ht.Mode != nil { - if *ht.Mode == TransportModePlay { - vals = append(vals, "mode=play") + if h.Mode != nil { + if *h.Mode == TransportModePlay { + rets = append(rets, "mode=play") } else { - vals = append(vals, "mode=record") + rets = append(rets, "mode=record") } } - return base.HeaderValue{strings.Join(vals, ";")} + return base.HeaderValue{strings.Join(rets, ";")} }