mirror of
https://github.com/aler9/gortsplib
synced 2025-10-06 15:46:51 +08:00
move headers into dedicated folder
This commit is contained in:
11
auth.go
11
auth.go
@@ -10,14 +10,3 @@ func md5Hex(in string) string {
|
|||||||
h.Write([]byte(in))
|
h.Write([]byte(in))
|
||||||
return hex.EncodeToString(h.Sum(nil))
|
return hex.EncodeToString(h.Sum(nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
// AuthMethod is an authentication method.
|
|
||||||
type AuthMethod int
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Basic authentication method
|
|
||||||
Basic AuthMethod = iota
|
|
||||||
|
|
||||||
// Digest authentication method
|
|
||||||
Digest
|
|
||||||
)
|
|
||||||
|
12
auth_test.go
12
auth_test.go
@@ -7,23 +7,24 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/aler9/gortsplib/base"
|
"github.com/aler9/gortsplib/base"
|
||||||
|
"github.com/aler9/gortsplib/headers"
|
||||||
)
|
)
|
||||||
|
|
||||||
var casesAuth = []struct {
|
var casesAuth = []struct {
|
||||||
name string
|
name string
|
||||||
methods []AuthMethod
|
methods []headers.AuthMethod
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
"basic",
|
"basic",
|
||||||
[]AuthMethod{Basic},
|
[]headers.AuthMethod{headers.AuthBasic},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"digest",
|
"digest",
|
||||||
[]AuthMethod{Digest},
|
[]headers.AuthMethod{headers.AuthDigest},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"both",
|
"both",
|
||||||
[]AuthMethod{Basic, Digest},
|
[]headers.AuthMethod{headers.AuthBasic, headers.AuthDigest},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,7 +47,8 @@ func TestAuthMethods(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestAuthBasePath(t *testing.T) {
|
func TestAuthBasePath(t *testing.T) {
|
||||||
authServer := NewAuthServer("testuser", "testpass", []AuthMethod{Basic, Digest})
|
authServer := NewAuthServer("testuser", "testpass",
|
||||||
|
[]headers.AuthMethod{headers.AuthBasic, headers.AuthDigest})
|
||||||
wwwAuthenticate := authServer.GenerateHeader()
|
wwwAuthenticate := authServer.GenerateHeader()
|
||||||
|
|
||||||
ac, err := newAuthClient(wwwAuthenticate, "testuser", "testpass")
|
ac, err := newAuthClient(wwwAuthenticate, "testuser", "testpass")
|
||||||
|
@@ -7,6 +7,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/aler9/gortsplib/base"
|
"github.com/aler9/gortsplib/base"
|
||||||
|
"github.com/aler9/gortsplib/headers"
|
||||||
)
|
)
|
||||||
|
|
||||||
// authClient is an object that helps a client to send its credentials to a
|
// authClient is an object that helps a client to send its credentials to a
|
||||||
@@ -14,7 +15,7 @@ import (
|
|||||||
type authClient struct {
|
type authClient struct {
|
||||||
user string
|
user string
|
||||||
pass string
|
pass string
|
||||||
method AuthMethod
|
method headers.AuthMethod
|
||||||
realm string
|
realm string
|
||||||
nonce string
|
nonce string
|
||||||
}
|
}
|
||||||
@@ -31,7 +32,7 @@ func newAuthClient(v base.HeaderValue, user string, pass string) (*authClient, e
|
|||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}(); headerAuthDigest != "" {
|
}(); headerAuthDigest != "" {
|
||||||
auth, err := ReadHeaderAuth(base.HeaderValue{headerAuthDigest})
|
auth, err := headers.ReadAuth(base.HeaderValue{headerAuthDigest})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -47,7 +48,7 @@ func newAuthClient(v base.HeaderValue, user string, pass string) (*authClient, e
|
|||||||
return &authClient{
|
return &authClient{
|
||||||
user: user,
|
user: user,
|
||||||
pass: pass,
|
pass: pass,
|
||||||
method: Digest,
|
method: headers.AuthDigest,
|
||||||
realm: *auth.Realm,
|
realm: *auth.Realm,
|
||||||
nonce: *auth.Nonce,
|
nonce: *auth.Nonce,
|
||||||
}, nil
|
}, nil
|
||||||
@@ -61,7 +62,7 @@ func newAuthClient(v base.HeaderValue, user string, pass string) (*authClient, e
|
|||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}(); headerAuthBasic != "" {
|
}(); headerAuthBasic != "" {
|
||||||
auth, err := ReadHeaderAuth(base.HeaderValue{headerAuthBasic})
|
auth, err := headers.ReadAuth(base.HeaderValue{headerAuthBasic})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -73,7 +74,7 @@ func newAuthClient(v base.HeaderValue, user string, pass string) (*authClient, e
|
|||||||
return &authClient{
|
return &authClient{
|
||||||
user: user,
|
user: user,
|
||||||
pass: pass,
|
pass: pass,
|
||||||
method: Basic,
|
method: headers.AuthBasic,
|
||||||
realm: *auth.Realm,
|
realm: *auth.Realm,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@@ -85,17 +86,17 @@ func newAuthClient(v base.HeaderValue, user string, pass string) (*authClient, e
|
|||||||
// the given method and url.
|
// the given method and url.
|
||||||
func (ac *authClient) GenerateHeader(method base.Method, ur *url.URL) base.HeaderValue {
|
func (ac *authClient) GenerateHeader(method base.Method, ur *url.URL) base.HeaderValue {
|
||||||
switch ac.method {
|
switch ac.method {
|
||||||
case Basic:
|
case headers.AuthBasic:
|
||||||
response := base64.StdEncoding.EncodeToString([]byte(ac.user + ":" + ac.pass))
|
response := base64.StdEncoding.EncodeToString([]byte(ac.user + ":" + ac.pass))
|
||||||
|
|
||||||
return base.HeaderValue{"Basic " + response}
|
return base.HeaderValue{"Basic " + response}
|
||||||
|
|
||||||
case Digest:
|
case headers.AuthDigest:
|
||||||
response := md5Hex(md5Hex(ac.user+":"+ac.realm+":"+ac.pass) + ":" +
|
response := md5Hex(md5Hex(ac.user+":"+ac.realm+":"+ac.pass) + ":" +
|
||||||
ac.nonce + ":" + md5Hex(string(method)+":"+ur.String()))
|
ac.nonce + ":" + md5Hex(string(method)+":"+ur.String()))
|
||||||
|
|
||||||
return (&HeaderAuth{
|
return (&headers.Auth{
|
||||||
Method: Digest,
|
Method: headers.AuthDigest,
|
||||||
Username: &ac.user,
|
Username: &ac.user,
|
||||||
Realm: &ac.realm,
|
Realm: &ac.realm,
|
||||||
Nonce: &ac.nonce,
|
Nonce: &ac.nonce,
|
||||||
|
@@ -9,6 +9,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/aler9/gortsplib/base"
|
"github.com/aler9/gortsplib/base"
|
||||||
|
"github.com/aler9/gortsplib/headers"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AuthServer is an object that helps a server to validate the credentials of
|
// AuthServer is an object that helps a server to validate the credentials of
|
||||||
@@ -16,16 +17,16 @@ import (
|
|||||||
type AuthServer struct {
|
type AuthServer struct {
|
||||||
user string
|
user string
|
||||||
pass string
|
pass string
|
||||||
methods []AuthMethod
|
methods []headers.AuthMethod
|
||||||
realm string
|
realm string
|
||||||
nonce string
|
nonce string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAuthServer allocates an AuthServer.
|
// NewAuthServer allocates an AuthServer.
|
||||||
// If methods is nil, the Basic and Digest methods are used.
|
// If methods is nil, the Basic and Digest methods are used.
|
||||||
func NewAuthServer(user string, pass string, methods []AuthMethod) *AuthServer {
|
func NewAuthServer(user string, pass string, methods []headers.AuthMethod) *AuthServer {
|
||||||
if methods == nil {
|
if methods == nil {
|
||||||
methods = []AuthMethod{Basic, Digest}
|
methods = []headers.AuthMethod{headers.AuthBasic, headers.AuthDigest}
|
||||||
}
|
}
|
||||||
|
|
||||||
nonceByts := make([]byte, 16)
|
nonceByts := make([]byte, 16)
|
||||||
@@ -46,15 +47,15 @@ func (as *AuthServer) GenerateHeader() base.HeaderValue {
|
|||||||
var ret base.HeaderValue
|
var ret base.HeaderValue
|
||||||
for _, m := range as.methods {
|
for _, m := range as.methods {
|
||||||
switch m {
|
switch m {
|
||||||
case Basic:
|
case headers.AuthBasic:
|
||||||
ret = append(ret, (&HeaderAuth{
|
ret = append(ret, (&headers.Auth{
|
||||||
Method: Basic,
|
Method: headers.AuthBasic,
|
||||||
Realm: &as.realm,
|
Realm: &as.realm,
|
||||||
}).Write()...)
|
}).Write()...)
|
||||||
|
|
||||||
case Digest:
|
case headers.AuthDigest:
|
||||||
ret = append(ret, (&HeaderAuth{
|
ret = append(ret, (&headers.Auth{
|
||||||
Method: Digest,
|
Method: headers.AuthDigest,
|
||||||
Realm: &as.realm,
|
Realm: &as.realm,
|
||||||
Nonce: &as.nonce,
|
Nonce: &as.nonce,
|
||||||
}).Write()...)
|
}).Write()...)
|
||||||
@@ -85,7 +86,7 @@ func (as *AuthServer) ValidateHeader(v base.HeaderValue, method base.Method, ur
|
|||||||
}
|
}
|
||||||
|
|
||||||
} else if strings.HasPrefix(v0, "Digest ") {
|
} else if strings.HasPrefix(v0, "Digest ") {
|
||||||
auth, err := ReadHeaderAuth(base.HeaderValue{v0})
|
auth, err := headers.ReadAuth(base.HeaderValue{v0})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@@ -19,6 +19,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/aler9/gortsplib/base"
|
"github.com/aler9/gortsplib/base"
|
||||||
|
"github.com/aler9/gortsplib/headers"
|
||||||
"github.com/aler9/gortsplib/rtcpreceiver"
|
"github.com/aler9/gortsplib/rtcpreceiver"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -297,7 +298,7 @@ func (c *ConnClient) Do(req *base.Request) (*base.Response, error) {
|
|||||||
|
|
||||||
// get session from response
|
// get session from response
|
||||||
if v, ok := res.Header["Session"]; ok {
|
if v, ok := res.Header["Session"]; ok {
|
||||||
sx, err := ReadHeaderSession(v)
|
sx, err := headers.ReadSession(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to parse session header: %s", err)
|
return nil, fmt.Errorf("unable to parse session header: %s", err)
|
||||||
}
|
}
|
||||||
@@ -448,7 +449,7 @@ func (c *ConnClient) urlForTrack(baseUrl *url.URL, mode SetupMode, track *Track)
|
|||||||
return u
|
return u
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ConnClient) setup(u *url.URL, mode SetupMode, track *Track, ht *HeaderTransport) (*base.Response, error) {
|
func (c *ConnClient) setup(u *url.URL, mode SetupMode, track *Track, ht *headers.Transport) (*base.Response, error) {
|
||||||
res, err := c.Do(&base.Request{
|
res, err := c.Do(&base.Request{
|
||||||
Method: base.SETUP,
|
Method: base.SETUP,
|
||||||
Url: c.urlForTrack(u, mode, track),
|
Url: c.urlForTrack(u, mode, track),
|
||||||
@@ -533,7 +534,7 @@ func (c *ConnClient) SetupUDP(u *url.URL, mode SetupMode, track *Track, rtpPort
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := c.setup(u, mode, track, &HeaderTransport{
|
res, err := c.setup(u, mode, track, &headers.Transport{
|
||||||
Protocol: StreamProtocolUDP,
|
Protocol: StreamProtocolUDP,
|
||||||
Cast: func() *StreamCast {
|
Cast: func() *StreamCast {
|
||||||
ret := StreamUnicast
|
ret := StreamUnicast
|
||||||
@@ -556,7 +557,7 @@ func (c *ConnClient) SetupUDP(u *url.URL, mode SetupMode, track *Track, rtpPort
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
th, err := ReadHeaderTransport(res.Header["Transport"])
|
th, err := headers.ReadTransport(res.Header["Transport"])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rtpListener.close()
|
rtpListener.close()
|
||||||
rtcpListener.close()
|
rtcpListener.close()
|
||||||
@@ -608,7 +609,7 @@ func (c *ConnClient) SetupTCP(u *url.URL, mode SetupMode, track *Track) (*base.R
|
|||||||
}
|
}
|
||||||
|
|
||||||
interleavedIds := [2]int{(track.Id * 2), (track.Id * 2) + 1}
|
interleavedIds := [2]int{(track.Id * 2), (track.Id * 2) + 1}
|
||||||
res, err := c.setup(u, mode, track, &HeaderTransport{
|
res, err := c.setup(u, mode, track, &headers.Transport{
|
||||||
Protocol: StreamProtocolTCP,
|
Protocol: StreamProtocolTCP,
|
||||||
Cast: func() *StreamCast {
|
Cast: func() *StreamCast {
|
||||||
ret := StreamUnicast
|
ret := StreamUnicast
|
||||||
@@ -629,7 +630,7 @@ func (c *ConnClient) SetupTCP(u *url.URL, mode SetupMode, track *Track) (*base.R
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
th, err := ReadHeaderTransport(res.Header["Transport"])
|
th, err := headers.ReadTransport(res.Header["Transport"])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("transport header: %s", err)
|
return nil, fmt.Errorf("transport header: %s", err)
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
package gortsplib
|
// Package headers contains various RTSP headers.
|
||||||
|
package headers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -7,8 +8,19 @@ import (
|
|||||||
"github.com/aler9/gortsplib/base"
|
"github.com/aler9/gortsplib/base"
|
||||||
)
|
)
|
||||||
|
|
||||||
// HeaderAuth is an Authenticate or a WWWW-Authenticate header.
|
// AuthMethod is an authentication method.
|
||||||
type HeaderAuth struct {
|
type AuthMethod int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// AuthBasic is the Basic authentication method
|
||||||
|
AuthBasic AuthMethod = iota
|
||||||
|
|
||||||
|
// AuthDigest is the Digest authentication method
|
||||||
|
AuthDigest
|
||||||
|
)
|
||||||
|
|
||||||
|
// Auth is an Authenticate or a WWWW-Authenticate header.
|
||||||
|
type Auth struct {
|
||||||
// authentication method
|
// authentication method
|
||||||
Method AuthMethod
|
Method AuthMethod
|
||||||
|
|
||||||
@@ -67,8 +79,8 @@ func findValue(v0 string) (string, string, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadHeaderAuth parses an Authenticate or a WWW-Authenticate header.
|
// ReadAuth parses an Authenticate or a WWW-Authenticate header.
|
||||||
func ReadHeaderAuth(v base.HeaderValue) (*HeaderAuth, error) {
|
func ReadAuth(v base.HeaderValue) (*Auth, error) {
|
||||||
if len(v) == 0 {
|
if len(v) == 0 {
|
||||||
return nil, fmt.Errorf("value not provided")
|
return nil, fmt.Errorf("value not provided")
|
||||||
}
|
}
|
||||||
@@ -77,7 +89,7 @@ func ReadHeaderAuth(v base.HeaderValue) (*HeaderAuth, error) {
|
|||||||
return nil, fmt.Errorf("value provided multiple times (%v)", v)
|
return nil, fmt.Errorf("value provided multiple times (%v)", v)
|
||||||
}
|
}
|
||||||
|
|
||||||
ha := &HeaderAuth{}
|
ha := &Auth{}
|
||||||
|
|
||||||
v0 := v[0]
|
v0 := v[0]
|
||||||
|
|
||||||
@@ -88,10 +100,10 @@ func ReadHeaderAuth(v base.HeaderValue) (*HeaderAuth, error) {
|
|||||||
|
|
||||||
switch v0[:i] {
|
switch v0[:i] {
|
||||||
case "Basic":
|
case "Basic":
|
||||||
ha.Method = Basic
|
ha.Method = AuthBasic
|
||||||
|
|
||||||
case "Digest":
|
case "Digest":
|
||||||
ha.Method = Digest
|
ha.Method = AuthDigest
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("invalid method (%s)", v0[:i])
|
return nil, fmt.Errorf("invalid method (%s)", v0[:i])
|
||||||
@@ -156,14 +168,14 @@ func ReadHeaderAuth(v base.HeaderValue) (*HeaderAuth, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Write encodes an Authenticate or a WWW-Authenticate header.
|
// Write encodes an Authenticate or a WWW-Authenticate header.
|
||||||
func (ha *HeaderAuth) Write() base.HeaderValue {
|
func (ha *Auth) Write() base.HeaderValue {
|
||||||
ret := ""
|
ret := ""
|
||||||
|
|
||||||
switch ha.Method {
|
switch ha.Method {
|
||||||
case Basic:
|
case AuthBasic:
|
||||||
ret += "Basic"
|
ret += "Basic"
|
||||||
|
|
||||||
case Digest:
|
case AuthDigest:
|
||||||
ret += "Digest"
|
ret += "Digest"
|
||||||
}
|
}
|
||||||
|
|
@@ -1,4 +1,4 @@
|
|||||||
package gortsplib
|
package headers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
@@ -8,18 +8,18 @@ import (
|
|||||||
"github.com/aler9/gortsplib/base"
|
"github.com/aler9/gortsplib/base"
|
||||||
)
|
)
|
||||||
|
|
||||||
var casesHeaderAuth = []struct {
|
var casesAuth = []struct {
|
||||||
name string
|
name string
|
||||||
vin base.HeaderValue
|
vin base.HeaderValue
|
||||||
vout base.HeaderValue
|
vout base.HeaderValue
|
||||||
h *HeaderAuth
|
h *Auth
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
"basic",
|
"basic",
|
||||||
base.HeaderValue{`Basic realm="4419b63f5e51"`},
|
base.HeaderValue{`Basic realm="4419b63f5e51"`},
|
||||||
base.HeaderValue{`Basic realm="4419b63f5e51"`},
|
base.HeaderValue{`Basic realm="4419b63f5e51"`},
|
||||||
&HeaderAuth{
|
&Auth{
|
||||||
Method: Basic,
|
Method: AuthBasic,
|
||||||
Realm: func() *string {
|
Realm: func() *string {
|
||||||
v := "4419b63f5e51"
|
v := "4419b63f5e51"
|
||||||
return &v
|
return &v
|
||||||
@@ -30,8 +30,8 @@ var casesHeaderAuth = []struct {
|
|||||||
"digest request 1",
|
"digest request 1",
|
||||||
base.HeaderValue{`Digest realm="4419b63f5e51", nonce="8b84a3b789283a8bea8da7fa7d41f08b", stale="FALSE"`},
|
base.HeaderValue{`Digest realm="4419b63f5e51", nonce="8b84a3b789283a8bea8da7fa7d41f08b", stale="FALSE"`},
|
||||||
base.HeaderValue{`Digest realm="4419b63f5e51", nonce="8b84a3b789283a8bea8da7fa7d41f08b", stale="FALSE"`},
|
base.HeaderValue{`Digest realm="4419b63f5e51", nonce="8b84a3b789283a8bea8da7fa7d41f08b", stale="FALSE"`},
|
||||||
&HeaderAuth{
|
&Auth{
|
||||||
Method: Digest,
|
Method: AuthDigest,
|
||||||
Realm: func() *string {
|
Realm: func() *string {
|
||||||
v := "4419b63f5e51"
|
v := "4419b63f5e51"
|
||||||
return &v
|
return &v
|
||||||
@@ -50,8 +50,8 @@ var casesHeaderAuth = []struct {
|
|||||||
"digest request 2",
|
"digest request 2",
|
||||||
base.HeaderValue{`Digest realm="4419b63f5e51", nonce="8b84a3b789283a8bea8da7fa7d41f08b", stale=FALSE`},
|
base.HeaderValue{`Digest realm="4419b63f5e51", nonce="8b84a3b789283a8bea8da7fa7d41f08b", stale=FALSE`},
|
||||||
base.HeaderValue{`Digest realm="4419b63f5e51", nonce="8b84a3b789283a8bea8da7fa7d41f08b", stale="FALSE"`},
|
base.HeaderValue{`Digest realm="4419b63f5e51", nonce="8b84a3b789283a8bea8da7fa7d41f08b", stale="FALSE"`},
|
||||||
&HeaderAuth{
|
&Auth{
|
||||||
Method: Digest,
|
Method: AuthDigest,
|
||||||
Realm: func() *string {
|
Realm: func() *string {
|
||||||
v := "4419b63f5e51"
|
v := "4419b63f5e51"
|
||||||
return &v
|
return &v
|
||||||
@@ -70,8 +70,8 @@ var casesHeaderAuth = []struct {
|
|||||||
"digest request 3",
|
"digest request 3",
|
||||||
base.HeaderValue{`Digest realm="4419b63f5e51",nonce="133767111917411116111311118211673010032", stale="FALSE"`},
|
base.HeaderValue{`Digest realm="4419b63f5e51",nonce="133767111917411116111311118211673010032", stale="FALSE"`},
|
||||||
base.HeaderValue{`Digest realm="4419b63f5e51", nonce="133767111917411116111311118211673010032", stale="FALSE"`},
|
base.HeaderValue{`Digest realm="4419b63f5e51", nonce="133767111917411116111311118211673010032", stale="FALSE"`},
|
||||||
&HeaderAuth{
|
&Auth{
|
||||||
Method: Digest,
|
Method: AuthDigest,
|
||||||
Realm: func() *string {
|
Realm: func() *string {
|
||||||
v := "4419b63f5e51"
|
v := "4419b63f5e51"
|
||||||
return &v
|
return &v
|
||||||
@@ -90,8 +90,8 @@ var casesHeaderAuth = []struct {
|
|||||||
"digest response generic",
|
"digest response generic",
|
||||||
base.HeaderValue{`Digest username="aa", realm="bb", nonce="cc", uri="dd", response="ee"`},
|
base.HeaderValue{`Digest username="aa", realm="bb", nonce="cc", uri="dd", response="ee"`},
|
||||||
base.HeaderValue{`Digest username="aa", realm="bb", nonce="cc", uri="dd", response="ee"`},
|
base.HeaderValue{`Digest username="aa", realm="bb", nonce="cc", uri="dd", response="ee"`},
|
||||||
&HeaderAuth{
|
&Auth{
|
||||||
Method: Digest,
|
Method: AuthDigest,
|
||||||
Username: func() *string {
|
Username: func() *string {
|
||||||
v := "aa"
|
v := "aa"
|
||||||
return &v
|
return &v
|
||||||
@@ -118,8 +118,8 @@ var casesHeaderAuth = []struct {
|
|||||||
"digest response with empty field",
|
"digest response with empty field",
|
||||||
base.HeaderValue{`Digest username="", realm="IPCAM", nonce="5d17cd12b9fa8a85ac5ceef0926ea5a6", uri="rtsp://localhost:8554/mystream", response="c072ae90eb4a27f4cdcb90d62266b2a1"`},
|
base.HeaderValue{`Digest username="", realm="IPCAM", nonce="5d17cd12b9fa8a85ac5ceef0926ea5a6", uri="rtsp://localhost:8554/mystream", response="c072ae90eb4a27f4cdcb90d62266b2a1"`},
|
||||||
base.HeaderValue{`Digest username="", realm="IPCAM", nonce="5d17cd12b9fa8a85ac5ceef0926ea5a6", uri="rtsp://localhost:8554/mystream", response="c072ae90eb4a27f4cdcb90d62266b2a1"`},
|
base.HeaderValue{`Digest username="", realm="IPCAM", nonce="5d17cd12b9fa8a85ac5ceef0926ea5a6", uri="rtsp://localhost:8554/mystream", response="c072ae90eb4a27f4cdcb90d62266b2a1"`},
|
||||||
&HeaderAuth{
|
&Auth{
|
||||||
Method: Digest,
|
Method: AuthDigest,
|
||||||
Username: func() *string {
|
Username: func() *string {
|
||||||
v := ""
|
v := ""
|
||||||
return &v
|
return &v
|
||||||
@@ -146,8 +146,8 @@ var casesHeaderAuth = []struct {
|
|||||||
"digest response with no spaces and additional fields",
|
"digest response with no spaces and additional fields",
|
||||||
base.HeaderValue{`Digest realm="Please log in with a valid username",nonce="752a62306daf32b401a41004555c7663",opaque="",stale=FALSE,algorithm=MD5`},
|
base.HeaderValue{`Digest realm="Please log in with a valid username",nonce="752a62306daf32b401a41004555c7663",opaque="",stale=FALSE,algorithm=MD5`},
|
||||||
base.HeaderValue{`Digest realm="Please log in with a valid username", nonce="752a62306daf32b401a41004555c7663", opaque="", stale="FALSE", algorithm="MD5"`},
|
base.HeaderValue{`Digest realm="Please log in with a valid username", nonce="752a62306daf32b401a41004555c7663", opaque="", stale="FALSE", algorithm="MD5"`},
|
||||||
&HeaderAuth{
|
&Auth{
|
||||||
Method: Digest,
|
Method: AuthDigest,
|
||||||
Realm: func() *string {
|
Realm: func() *string {
|
||||||
v := "Please log in with a valid username"
|
v := "Please log in with a valid username"
|
||||||
return &v
|
return &v
|
||||||
@@ -172,18 +172,18 @@ var casesHeaderAuth = []struct {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHeaderAuthRead(t *testing.T) {
|
func TestAuthRead(t *testing.T) {
|
||||||
for _, c := range casesHeaderAuth {
|
for _, c := range casesAuth {
|
||||||
t.Run(c.name, func(t *testing.T) {
|
t.Run(c.name, func(t *testing.T) {
|
||||||
req, err := ReadHeaderAuth(c.vin)
|
req, err := ReadAuth(c.vin)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, c.h, req)
|
require.Equal(t, c.h, req)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHeaderAuthWrite(t *testing.T) {
|
func TestAuthWrite(t *testing.T) {
|
||||||
for _, c := range casesHeaderAuth {
|
for _, c := range casesAuth {
|
||||||
t.Run(c.name, func(t *testing.T) {
|
t.Run(c.name, func(t *testing.T) {
|
||||||
req := c.h.Write()
|
req := c.h.Write()
|
||||||
require.Equal(t, c.vout, req)
|
require.Equal(t, c.vout, req)
|
@@ -1,4 +1,4 @@
|
|||||||
package gortsplib
|
package headers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -8,8 +8,8 @@ import (
|
|||||||
"github.com/aler9/gortsplib/base"
|
"github.com/aler9/gortsplib/base"
|
||||||
)
|
)
|
||||||
|
|
||||||
// HeaderSession is a Session header.
|
// Session is a Session header.
|
||||||
type HeaderSession struct {
|
type Session struct {
|
||||||
// session id
|
// session id
|
||||||
Session string
|
Session string
|
||||||
|
|
||||||
@@ -17,8 +17,8 @@ type HeaderSession struct {
|
|||||||
Timeout *uint
|
Timeout *uint
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadHeaderSession parses a Session header.
|
// ReadSession parses a Session header.
|
||||||
func ReadHeaderSession(v base.HeaderValue) (*HeaderSession, error) {
|
func ReadSession(v base.HeaderValue) (*Session, error) {
|
||||||
if len(v) == 0 {
|
if len(v) == 0 {
|
||||||
return nil, fmt.Errorf("value not provided")
|
return nil, fmt.Errorf("value not provided")
|
||||||
}
|
}
|
||||||
@@ -32,7 +32,7 @@ func ReadHeaderSession(v base.HeaderValue) (*HeaderSession, error) {
|
|||||||
return nil, fmt.Errorf("invalid value (%v)", v)
|
return nil, fmt.Errorf("invalid value (%v)", v)
|
||||||
}
|
}
|
||||||
|
|
||||||
hs := &HeaderSession{}
|
hs := &Session{}
|
||||||
|
|
||||||
hs.Session = parts[0]
|
hs.Session = parts[0]
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@ func ReadHeaderSession(v base.HeaderValue) (*HeaderSession, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Write encodes a Session header
|
// Write encodes a Session header
|
||||||
func (hs *HeaderSession) Write() base.HeaderValue {
|
func (hs *Session) Write() base.HeaderValue {
|
||||||
val := hs.Session
|
val := hs.Session
|
||||||
|
|
||||||
if hs.Timeout != nil {
|
if hs.Timeout != nil {
|
@@ -1,4 +1,4 @@
|
|||||||
package gortsplib
|
package headers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
@@ -8,17 +8,17 @@ import (
|
|||||||
"github.com/aler9/gortsplib/base"
|
"github.com/aler9/gortsplib/base"
|
||||||
)
|
)
|
||||||
|
|
||||||
var casesHeaderSession = []struct {
|
var casesSession = []struct {
|
||||||
name string
|
name string
|
||||||
vin base.HeaderValue
|
vin base.HeaderValue
|
||||||
vout base.HeaderValue
|
vout base.HeaderValue
|
||||||
h *HeaderSession
|
h *Session
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
"base",
|
"base",
|
||||||
base.HeaderValue{`A3eqwsafq3rFASqew`},
|
base.HeaderValue{`A3eqwsafq3rFASqew`},
|
||||||
base.HeaderValue{`A3eqwsafq3rFASqew`},
|
base.HeaderValue{`A3eqwsafq3rFASqew`},
|
||||||
&HeaderSession{
|
&Session{
|
||||||
Session: "A3eqwsafq3rFASqew",
|
Session: "A3eqwsafq3rFASqew",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -26,7 +26,7 @@ var casesHeaderSession = []struct {
|
|||||||
"with timeout",
|
"with timeout",
|
||||||
base.HeaderValue{`A3eqwsafq3rFASqew;timeout=47`},
|
base.HeaderValue{`A3eqwsafq3rFASqew;timeout=47`},
|
||||||
base.HeaderValue{`A3eqwsafq3rFASqew;timeout=47`},
|
base.HeaderValue{`A3eqwsafq3rFASqew;timeout=47`},
|
||||||
&HeaderSession{
|
&Session{
|
||||||
Session: "A3eqwsafq3rFASqew",
|
Session: "A3eqwsafq3rFASqew",
|
||||||
Timeout: func() *uint {
|
Timeout: func() *uint {
|
||||||
v := uint(47)
|
v := uint(47)
|
||||||
@@ -38,7 +38,7 @@ var casesHeaderSession = []struct {
|
|||||||
"with timeout and space",
|
"with timeout and space",
|
||||||
base.HeaderValue{`A3eqwsafq3rFASqew; timeout=47`},
|
base.HeaderValue{`A3eqwsafq3rFASqew; timeout=47`},
|
||||||
base.HeaderValue{`A3eqwsafq3rFASqew;timeout=47`},
|
base.HeaderValue{`A3eqwsafq3rFASqew;timeout=47`},
|
||||||
&HeaderSession{
|
&Session{
|
||||||
Session: "A3eqwsafq3rFASqew",
|
Session: "A3eqwsafq3rFASqew",
|
||||||
Timeout: func() *uint {
|
Timeout: func() *uint {
|
||||||
v := uint(47)
|
v := uint(47)
|
||||||
@@ -48,18 +48,18 @@ var casesHeaderSession = []struct {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHeaderSessionRead(t *testing.T) {
|
func TestSessionRead(t *testing.T) {
|
||||||
for _, c := range casesHeaderSession {
|
for _, c := range casesSession {
|
||||||
t.Run(c.name, func(t *testing.T) {
|
t.Run(c.name, func(t *testing.T) {
|
||||||
req, err := ReadHeaderSession(c.vin)
|
req, err := ReadSession(c.vin)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, c.h, req)
|
require.Equal(t, c.h, req)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHeaderSessionWrite(t *testing.T) {
|
func TestSessionWrite(t *testing.T) {
|
||||||
for _, c := range casesHeaderSession {
|
for _, c := range casesSession {
|
||||||
t.Run(c.name, func(t *testing.T) {
|
t.Run(c.name, func(t *testing.T) {
|
||||||
req := c.h.Write()
|
req := c.h.Write()
|
||||||
require.Equal(t, c.vout, req)
|
require.Equal(t, c.vout, req)
|
@@ -1,4 +1,4 @@
|
|||||||
package gortsplib
|
package headers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -8,8 +8,54 @@ import (
|
|||||||
"github.com/aler9/gortsplib/base"
|
"github.com/aler9/gortsplib/base"
|
||||||
)
|
)
|
||||||
|
|
||||||
// HeaderTransport is a Transport header.
|
// StreamProtocol is the protocol of a stream.
|
||||||
type HeaderTransport struct {
|
type StreamProtocol int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// StreamProtocolUDP means that the stream uses the UDP protocol
|
||||||
|
StreamProtocolUDP StreamProtocol = iota
|
||||||
|
|
||||||
|
// StreamProtocolTCP means that the stream uses the TCP protocol
|
||||||
|
StreamProtocolTCP
|
||||||
|
)
|
||||||
|
|
||||||
|
// String implements fmt.Stringer.
|
||||||
|
func (sp StreamProtocol) String() string {
|
||||||
|
switch sp {
|
||||||
|
case StreamProtocolUDP:
|
||||||
|
return "udp"
|
||||||
|
|
||||||
|
case StreamProtocolTCP:
|
||||||
|
return "tcp"
|
||||||
|
}
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
// StreamCast is the cast method of a stream.
|
||||||
|
type StreamCast int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// StreamUnicast means that the stream is unicasted
|
||||||
|
StreamUnicast StreamCast = iota
|
||||||
|
|
||||||
|
// StreamMulticast means that the stream is multicasted
|
||||||
|
StreamMulticast
|
||||||
|
)
|
||||||
|
|
||||||
|
// String implements fmt.Stringer.
|
||||||
|
func (sc StreamCast) String() string {
|
||||||
|
switch sc {
|
||||||
|
case StreamUnicast:
|
||||||
|
return "unicast"
|
||||||
|
|
||||||
|
case StreamMulticast:
|
||||||
|
return "multicast"
|
||||||
|
}
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transport is a Transport header.
|
||||||
|
type Transport struct {
|
||||||
// protocol of the stream
|
// protocol of the stream
|
||||||
Protocol StreamProtocol
|
Protocol StreamProtocol
|
||||||
|
|
||||||
@@ -57,8 +103,8 @@ func parsePorts(val string) (*[2]int, error) {
|
|||||||
return &[2]int{int(port1), int(port2)}, nil
|
return &[2]int{int(port1), int(port2)}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadHeaderTransport parses a Transport header.
|
// ReadTransport parses a Transport header.
|
||||||
func ReadHeaderTransport(v base.HeaderValue) (*HeaderTransport, error) {
|
func ReadTransport(v base.HeaderValue) (*Transport, error) {
|
||||||
if len(v) == 0 {
|
if len(v) == 0 {
|
||||||
return nil, fmt.Errorf("value not provided")
|
return nil, fmt.Errorf("value not provided")
|
||||||
}
|
}
|
||||||
@@ -67,7 +113,7 @@ func ReadHeaderTransport(v base.HeaderValue) (*HeaderTransport, error) {
|
|||||||
return nil, fmt.Errorf("value provided multiple times (%v)", v)
|
return nil, fmt.Errorf("value provided multiple times (%v)", v)
|
||||||
}
|
}
|
||||||
|
|
||||||
ht := &HeaderTransport{}
|
ht := &Transport{}
|
||||||
|
|
||||||
parts := strings.Split(v[0], ";")
|
parts := strings.Split(v[0], ";")
|
||||||
if len(parts) == 0 {
|
if len(parts) == 0 {
|
||||||
@@ -153,7 +199,7 @@ func ReadHeaderTransport(v base.HeaderValue) (*HeaderTransport, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Write encodes a Transport header
|
// Write encodes a Transport header
|
||||||
func (ht *HeaderTransport) Write() base.HeaderValue {
|
func (ht *Transport) Write() base.HeaderValue {
|
||||||
var vals []string
|
var vals []string
|
||||||
|
|
||||||
if ht.Protocol == StreamProtocolUDP {
|
if ht.Protocol == StreamProtocolUDP {
|
@@ -1,4 +1,4 @@
|
|||||||
package gortsplib
|
package headers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
@@ -8,17 +8,17 @@ import (
|
|||||||
"github.com/aler9/gortsplib/base"
|
"github.com/aler9/gortsplib/base"
|
||||||
)
|
)
|
||||||
|
|
||||||
var casesHeaderTransport = []struct {
|
var casesTransport = []struct {
|
||||||
name string
|
name string
|
||||||
vin base.HeaderValue
|
vin base.HeaderValue
|
||||||
vout base.HeaderValue
|
vout base.HeaderValue
|
||||||
h *HeaderTransport
|
h *Transport
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
"udp unicast play request",
|
"udp unicast play request",
|
||||||
base.HeaderValue{`RTP/AVP;unicast;client_port=3456-3457;mode="PLAY"`},
|
base.HeaderValue{`RTP/AVP;unicast;client_port=3456-3457;mode="PLAY"`},
|
||||||
base.HeaderValue{`RTP/AVP;unicast;client_port=3456-3457;mode=play`},
|
base.HeaderValue{`RTP/AVP;unicast;client_port=3456-3457;mode=play`},
|
||||||
&HeaderTransport{
|
&Transport{
|
||||||
Protocol: StreamProtocolUDP,
|
Protocol: StreamProtocolUDP,
|
||||||
Cast: func() *StreamCast {
|
Cast: func() *StreamCast {
|
||||||
v := StreamUnicast
|
v := StreamUnicast
|
||||||
@@ -35,7 +35,7 @@ var casesHeaderTransport = []struct {
|
|||||||
"udp unicast play response",
|
"udp unicast play response",
|
||||||
base.HeaderValue{`RTP/AVP/UDP;unicast;client_port=3056-3057;server_port=5000-5001`},
|
base.HeaderValue{`RTP/AVP/UDP;unicast;client_port=3056-3057;server_port=5000-5001`},
|
||||||
base.HeaderValue{`RTP/AVP;unicast;client_port=3056-3057;server_port=5000-5001`},
|
base.HeaderValue{`RTP/AVP;unicast;client_port=3056-3057;server_port=5000-5001`},
|
||||||
&HeaderTransport{
|
&Transport{
|
||||||
Protocol: StreamProtocolUDP,
|
Protocol: StreamProtocolUDP,
|
||||||
Cast: func() *StreamCast {
|
Cast: func() *StreamCast {
|
||||||
v := StreamUnicast
|
v := StreamUnicast
|
||||||
@@ -49,7 +49,7 @@ var casesHeaderTransport = []struct {
|
|||||||
"udp multicast play request / response",
|
"udp multicast play request / response",
|
||||||
base.HeaderValue{`RTP/AVP;multicast;destination=225.219.201.15;port=7000-7001;ttl=127`},
|
base.HeaderValue{`RTP/AVP;multicast;destination=225.219.201.15;port=7000-7001;ttl=127`},
|
||||||
base.HeaderValue{`RTP/AVP;multicast`},
|
base.HeaderValue{`RTP/AVP;multicast`},
|
||||||
&HeaderTransport{
|
&Transport{
|
||||||
Protocol: StreamProtocolUDP,
|
Protocol: StreamProtocolUDP,
|
||||||
Cast: func() *StreamCast {
|
Cast: func() *StreamCast {
|
||||||
v := StreamMulticast
|
v := StreamMulticast
|
||||||
@@ -70,25 +70,25 @@ var casesHeaderTransport = []struct {
|
|||||||
"tcp play request / response",
|
"tcp play request / response",
|
||||||
base.HeaderValue{`RTP/AVP/TCP;interleaved=0-1`},
|
base.HeaderValue{`RTP/AVP/TCP;interleaved=0-1`},
|
||||||
base.HeaderValue{`RTP/AVP/TCP;interleaved=0-1`},
|
base.HeaderValue{`RTP/AVP/TCP;interleaved=0-1`},
|
||||||
&HeaderTransport{
|
&Transport{
|
||||||
Protocol: StreamProtocolTCP,
|
Protocol: StreamProtocolTCP,
|
||||||
InterleavedIds: &[2]int{0, 1},
|
InterleavedIds: &[2]int{0, 1},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHeaderTransportRead(t *testing.T) {
|
func TestTransportRead(t *testing.T) {
|
||||||
for _, c := range casesHeaderTransport {
|
for _, c := range casesTransport {
|
||||||
t.Run(c.name, func(t *testing.T) {
|
t.Run(c.name, func(t *testing.T) {
|
||||||
req, err := ReadHeaderTransport(c.vin)
|
req, err := ReadTransport(c.vin)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, c.h, req)
|
require.Equal(t, c.h, req)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHeaderTransportWrite(t *testing.T) {
|
func TestTransportWrite(t *testing.T) {
|
||||||
for _, c := range casesHeaderTransport {
|
for _, c := range casesTransport {
|
||||||
t.Run(c.name, func(t *testing.T) {
|
t.Run(c.name, func(t *testing.T) {
|
||||||
req := c.h.Write()
|
req := c.h.Write()
|
||||||
require.Equal(t, c.vout, req)
|
require.Equal(t, c.vout, req)
|
37
utils.go
37
utils.go
@@ -2,54 +2,31 @@ package gortsplib
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/aler9/gortsplib/base"
|
"github.com/aler9/gortsplib/base"
|
||||||
|
"github.com/aler9/gortsplib/headers"
|
||||||
)
|
)
|
||||||
|
|
||||||
// StreamProtocol is the protocol of a stream.
|
// StreamProtocol is the protocol of a stream.
|
||||||
type StreamProtocol int
|
type StreamProtocol = headers.StreamProtocol
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// StreamProtocolUDP means that the stream uses the UDP protocol
|
// StreamProtocolUDP means that the stream uses the UDP protocol
|
||||||
StreamProtocolUDP StreamProtocol = iota
|
StreamProtocolUDP = headers.StreamProtocolUDP
|
||||||
|
|
||||||
// StreamProtocolTCP means that the stream uses the TCP protocol
|
// StreamProtocolTCP means that the stream uses the TCP protocol
|
||||||
StreamProtocolTCP
|
StreamProtocolTCP = headers.StreamProtocolTCP
|
||||||
)
|
)
|
||||||
|
|
||||||
// String implements fmt.Stringer.
|
|
||||||
func (sp StreamProtocol) String() string {
|
|
||||||
switch sp {
|
|
||||||
case StreamProtocolUDP:
|
|
||||||
return "udp"
|
|
||||||
|
|
||||||
case StreamProtocolTCP:
|
|
||||||
return "tcp"
|
|
||||||
}
|
|
||||||
return "unknown"
|
|
||||||
}
|
|
||||||
|
|
||||||
// StreamCast is the cast method of a stream.
|
// StreamCast is the cast method of a stream.
|
||||||
type StreamCast int
|
type StreamCast = headers.StreamCast
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// StreamUnicast means that the stream is unicasted
|
// StreamUnicast means that the stream is unicasted
|
||||||
StreamUnicast StreamCast = iota
|
StreamUnicast = headers.StreamUnicast
|
||||||
|
|
||||||
// StreamMulticast means that the stream is multicasted
|
// StreamMulticast means that the stream is multicasted
|
||||||
StreamMulticast
|
StreamMulticast = headers.StreamMulticast
|
||||||
)
|
)
|
||||||
|
|
||||||
// String implements fmt.Stringer.
|
|
||||||
func (sc StreamCast) String() string {
|
|
||||||
switch sc {
|
|
||||||
case StreamUnicast:
|
|
||||||
return "unicast"
|
|
||||||
|
|
||||||
case StreamMulticast:
|
|
||||||
return "multicast"
|
|
||||||
}
|
|
||||||
return "unknown"
|
|
||||||
}
|
|
||||||
|
|
||||||
// StreamType is the stream type.
|
// StreamType is the stream type.
|
||||||
type StreamType = base.StreamType
|
type StreamType = base.StreamType
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user